diff --git a/.devcontainer/compose.yaml b/.devcontainer/compose.yaml index ced5ecfe884c88..217415c528bf97 100644 --- a/.devcontainer/compose.yaml +++ b/.devcontainer/compose.yaml @@ -73,7 +73,7 @@ services: hard: -1 libretranslate: - image: libretranslate/libretranslate:v1.6.2 + image: libretranslate/libretranslate:v1.7.3 restart: unless-stopped volumes: - lt-data:/home/libretranslate/.local diff --git a/.env.production.sample b/.env.production.sample index f687053d502403..e341c07801f721 100644 --- a/.env.production.sample +++ b/.env.production.sample @@ -318,24 +318,3 @@ MAX_POLL_OPTION_CHARS=100 # ----------------------- IP_RETENTION_PERIOD=31556952 SESSION_RETENTION_PERIOD=31556952 - -# Fetch All Replies Behavior -# -------------------------- -# When a user expands a post (DetailedStatus view), fetch all of its replies -# (default: false) -FETCH_REPLIES_ENABLED=false - -# Period to wait between fetching replies (in minutes) -FETCH_REPLIES_COOLDOWN_MINUTES=15 - -# Period to wait after a post is first created before fetching its replies (in minutes) -FETCH_REPLIES_INITIAL_WAIT_MINUTES=5 - -# Max number of replies to fetch - total, recursively through a whole reply tree -FETCH_REPLIES_MAX_GLOBAL=1000 - -# Max number of replies to fetch - for a single post -FETCH_REPLIES_MAX_SINGLE=500 - -# Max number of replies Collection pages to fetch - total -FETCH_REPLIES_MAX_PAGES=500 diff --git a/.github/actions/setup-javascript/action.yml b/.github/actions/setup-javascript/action.yml index 808adc7de64f96..0c7ead1c15f1bd 100644 --- a/.github/actions/setup-javascript/action.yml +++ b/.github/actions/setup-javascript/action.yml @@ -9,7 +9,7 @@ runs: using: 'composite' steps: - name: Set up Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version-file: '.nvmrc' diff --git a/.github/renovate.json5 b/.github/renovate.json5 index c1a1c99eb708e7..678a256b748e16 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -5,7 +5,6 @@ 'customManagers:dockerfileVersions', ':labels(dependencies)', ':prConcurrentLimitNone', // Remove limit for open PRs at any time. - ':prHourlyLimit2', // Rate limit PR creation to a maximum of two per hour. ':enableVulnerabilityAlertsWithLabel(security)', ], rebaseWhen: 'conflicted', @@ -23,8 +22,6 @@ // Require Dependency Dashboard Approval for major version bumps of these node packages matchManagers: ['npm'], matchPackageNames: [ - 'tesseract.js', // Requires code changes - // react-router: Requires manual upgrade 'history', 'react-router-dom', diff --git a/.github/workflows/build-container-image.yml b/.github/workflows/build-container-image.yml index 260730004cc774..84b729df434f74 100644 --- a/.github/workflows/build-container-image.yml +++ b/.github/workflows/build-container-image.yml @@ -35,7 +35,7 @@ jobs: - linux/arm64 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Prepare env: @@ -100,7 +100,7 @@ jobs: - name: Upload digest if: ${{ inputs.push_to_images != '' }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: # `hashFiles` is used to disambiguate between streaming and non-streaming images name: digests-${{ hashFiles(inputs.file_to_build) }}-${{ env.PLATFORM_PAIR }} @@ -119,10 +119,10 @@ jobs: PUSH_TO_IMAGES: ${{ inputs.push_to_images }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Download digests - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v6 with: path: ${{ runner.temp }}/digests # `hashFiles` is used to disambiguate between streaming and non-streaming images diff --git a/.github/workflows/build-push-pr.yml b/.github/workflows/build-push-pr.yml index 6ec561d2fb589b..f934f2e550e855 100644 --- a/.github/workflows/build-push-pr.yml +++ b/.github/workflows/build-push-pr.yml @@ -18,7 +18,7 @@ jobs: steps: # Repository needs to be cloned so `git rev-parse` below works - name: Clone repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - id: version_vars run: | echo mastodon_version_metadata=pr-${{ github.event.pull_request.number }}-$(git rev-parse --short ${{github.event.pull_request.head.sha}}) >> $GITHUB_OUTPUT diff --git a/.github/workflows/build-releases.yml b/.github/workflows/build-releases.yml index 8266ff43f3d60c..487e3fda300bc7 100644 --- a/.github/workflows/build-releases.yml +++ b/.github/workflows/build-releases.yml @@ -9,7 +9,44 @@ permissions: packages: write jobs: + check-latest-stable: + runs-on: ubuntu-latest + outputs: + latest: ${{ steps.check.outputs.is_latest_stable }} + steps: + # Repository needs to be cloned to list branches + - name: Clone repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Check latest stable + shell: bash + id: check + run: | + ref="${GITHUB_REF#refs/tags/}" + + if [[ "$ref" =~ ^v([0-9]+)\.([0-9]+)(\.[0-9]+)?$ ]]; then + current="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}" + else + echo "tag $ref is not semver" + echo "is_latest_stable=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + latest=$(git for-each-ref --format='%(refname:short)' "refs/remotes/origin/stable-*.*" \ + | sed -E 's#^origin/stable-##' \ + | sort -Vr \ + | head -n1) + + if [[ "$current" == "$latest" ]]; then + echo "is_latest_stable=true" >> "$GITHUB_OUTPUT" + else + echo "is_latest_stable=false" >> "$GITHUB_OUTPUT" + fi + build-image: + needs: check-latest-stable uses: ./.github/workflows/build-container-image.yml with: file_to_build: Dockerfile @@ -20,13 +57,14 @@ jobs: # Only tag with latest when ran against the latest stable branch # This needs to be updated after each minor version release flavor: | - latest=${{ startsWith(github.ref, 'refs/tags/v4.3.') }} + latest=${{ needs.check-latest-stable.outputs.latest }} tags: | type=pep440,pattern={{raw}} type=pep440,pattern=v{{major}}.{{minor}} secrets: inherit build-image-streaming: + needs: check-latest-stable uses: ./.github/workflows/build-container-image.yml with: file_to_build: streaming/Dockerfile @@ -37,7 +75,7 @@ jobs: # Only tag with latest when ran against the latest stable branch # This needs to be updated after each minor version release flavor: | - latest=${{ startsWith(github.ref, 'refs/tags/v4.3.') }} + latest=${{ needs.check-latest-stable.outputs.latest }} tags: | type=pep440,pattern={{raw}} type=pep440,pattern=v{{major}}.{{minor}} diff --git a/.github/workflows/bundler-audit.yml b/.github/workflows/bundler-audit.yml index fa28d28f740c45..9cc49a7f79715f 100644 --- a/.github/workflows/bundler-audit.yml +++ b/.github/workflows/bundler-audit.yml @@ -28,7 +28,7 @@ jobs: steps: - name: Clone repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up Ruby uses: ruby/setup-ruby@v1 diff --git a/.github/workflows/check-i18n.yml b/.github/workflows/check-i18n.yml index c46090c1b565bc..9d500ffc44e2e6 100644 --- a/.github/workflows/check-i18n.yml +++ b/.github/workflows/check-i18n.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Ruby environment uses: ./.github/actions/setup-ruby diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml index 4e6179bc7748db..fbc0d9da0c2564 100644 --- a/.github/workflows/chromatic.yml +++ b/.github/workflows/chromatic.yml @@ -1,31 +1,51 @@ name: 'Chromatic' +permissions: + contents: read on: push: branches-ignore: - renovate/* - stable-* - paths: - - 'package.json' - - 'yarn.lock' - - '**/*.js' - - '**/*.jsx' - - '**/*.ts' - - '**/*.tsx' - - '**/*.css' - - '**/*.scss' - - '.github/workflows/chromatic.yml' jobs: + pathcheck: + name: Check for relevant changes + runs-on: ubuntu-latest + outputs: + changed: ${{ steps.filter.outputs.src }} + steps: + - name: Checkout code + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + src: + - 'package.json' + - 'yarn.lock' + - '**/*.js' + - '**/*.jsx' + - '**/*.ts' + - '**/*.tsx' + - '**/*.css' + - '**/*.scss' + - '.github/workflows/chromatic.yml' + chromatic: name: Run Chromatic runs-on: ubuntu-latest - if: github.repository == 'mastodon/mastodon' + needs: pathcheck + if: github.repository == 'mastodon/mastodon' && needs.pathcheck.outputs.changed == 'true' steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 + - name: Set up Javascript environment uses: ./.github/actions/setup-javascript @@ -33,9 +53,10 @@ jobs: run: yarn build-storybook - name: Run Chromatic - uses: chromaui/action@v12 + uses: chromaui/action@v13 with: - # ⚠️ Make sure to configure a `CHROMATIC_PROJECT_TOKEN` repository secret projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} zip: true storybookBuildDir: 'storybook-static' + exitZeroOnChanges: false # Fail workflow if changes are found + autoAcceptChanges: 'main' # Auto-accept changes on main branch only diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c864e12d2d8c09..cf038ae4809b78 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -31,11 +31,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -48,7 +48,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v3 + uses: github/codeql-action/autobuild@v4 # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -61,6 +61,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 with: category: '/language:${{matrix.language}}' diff --git a/.github/workflows/crowdin-download-stable.yml b/.github/workflows/crowdin-download-stable.yml index 28890321977479..6e7862b36867df 100644 --- a/.github/workflows/crowdin-download-stable.yml +++ b/.github/workflows/crowdin-download-stable.yml @@ -9,11 +9,11 @@ permissions: jobs: download-translations-stable: runs-on: ubuntu-latest - if: github.repository == 'mastodon/mastodon' + if: github.repository == 'glitch-soc/mastodon' steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Increase Git http.postBuffer # This is needed due to a bug in Ubuntu's cURL version? diff --git a/.github/workflows/crowdin-download.yml b/.github/workflows/crowdin-download.yml index 1fdd1e08b4fc53..c0c3374219d634 100644 --- a/.github/workflows/crowdin-download.yml +++ b/.github/workflows/crowdin-download.yml @@ -15,7 +15,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Increase Git http.postBuffer # This is needed due to a bug in Ubuntu's cURL version? diff --git a/.github/workflows/crowdin-upload.yml b/.github/workflows/crowdin-upload.yml index d6c542eb361b0e..6bbd931c532818 100644 --- a/.github/workflows/crowdin-upload.yml +++ b/.github/workflows/crowdin-upload.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: crowdin action uses: crowdin/github-action@v2 diff --git a/.github/workflows/format-check.yml b/.github/workflows/format-check.yml index c10f350a02ef28..6803686b4ff6c0 100644 --- a/.github/workflows/format-check.yml +++ b/.github/workflows/format-check.yml @@ -13,7 +13,7 @@ jobs: steps: - name: Clone repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up Javascript environment uses: ./.github/actions/setup-javascript diff --git a/.github/workflows/lint-css.yml b/.github/workflows/lint-css.yml index c1385bf789b0bc..51a78d679fcaa7 100644 --- a/.github/workflows/lint-css.yml +++ b/.github/workflows/lint-css.yml @@ -34,7 +34,7 @@ jobs: steps: - name: Clone repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up Javascript environment uses: ./.github/actions/setup-javascript diff --git a/.github/workflows/lint-haml.yml b/.github/workflows/lint-haml.yml index 499be2010adc99..d3452c9ffc295f 100644 --- a/.github/workflows/lint-haml.yml +++ b/.github/workflows/lint-haml.yml @@ -33,7 +33,7 @@ jobs: steps: - name: Clone repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up Ruby uses: ruby/setup-ruby@v1 diff --git a/.github/workflows/lint-js.yml b/.github/workflows/lint-js.yml index 86e9af23e7efb1..c9ba1a13f17678 100644 --- a/.github/workflows/lint-js.yml +++ b/.github/workflows/lint-js.yml @@ -38,7 +38,7 @@ jobs: steps: - name: Clone repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up Javascript environment uses: ./.github/actions/setup-javascript diff --git a/.github/workflows/lint-ruby.yml b/.github/workflows/lint-ruby.yml index 87f8aee24e01e9..e73617d85dace6 100644 --- a/.github/workflows/lint-ruby.yml +++ b/.github/workflows/lint-ruby.yml @@ -35,7 +35,7 @@ jobs: steps: - name: Clone repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up Ruby uses: ruby/setup-ruby@v1 diff --git a/.github/workflows/test-js.yml b/.github/workflows/test-js.yml index 0699e6c9ef8193..b8e1cc89aaa632 100644 --- a/.github/workflows/test-js.yml +++ b/.github/workflows/test-js.yml @@ -34,7 +34,7 @@ jobs: steps: - name: Clone repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up Javascript environment uses: ./.github/actions/setup-javascript diff --git a/.github/workflows/test-migrations.yml b/.github/workflows/test-migrations.yml index 7aab34f0cf4ee6..b1b94692f0c75c 100644 --- a/.github/workflows/test-migrations.yml +++ b/.github/workflows/test-migrations.yml @@ -72,7 +72,7 @@ jobs: BUNDLE_RETRY: 3 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Ruby environment uses: ./.github/actions/setup-ruby diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yml index 63d317250436ae..8f05812d600505 100644 --- a/.github/workflows/test-ruby.yml +++ b/.github/workflows/test-ruby.yml @@ -32,7 +32,7 @@ jobs: SECRET_KEY_BASE_DUMMY: 1 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Ruby environment uses: ./.github/actions/setup-ruby @@ -65,7 +65,7 @@ jobs: run: | tar --exclude={"*.br","*.gz"} -zcf artifacts.tar.gz public/assets public/packs* tmp/cache/vite/last-build*.json - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v5 if: matrix.mode == 'test' with: path: |- @@ -128,9 +128,9 @@ jobs: - '3.3' - '.ruby-version' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v6 with: path: './' name: ${{ github.sha }} @@ -230,9 +230,9 @@ jobs: - '3.3' - '.ruby-version' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v6 with: path: './' name: ${{ github.sha }} @@ -309,9 +309,9 @@ jobs: - '.ruby-version' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v6 with: path: './' name: ${{ github.sha }} @@ -350,14 +350,14 @@ jobs: - run: bin/rspec spec/system --tag streaming --tag js - name: Archive logs - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 if: failure() with: name: e2e-logs-${{ matrix.ruby-version }} path: log/ - name: Archive test screenshots - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 if: failure() with: name: e2e-screenshots-${{ matrix.ruby-version }} @@ -447,9 +447,9 @@ jobs: search-image: opensearchproject/opensearch:2 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v6 with: path: './' name: ${{ github.sha }} @@ -469,14 +469,14 @@ jobs: - run: bin/rspec --tag search - name: Archive logs - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 if: failure() with: name: test-search-logs-${{ matrix.ruby-version }} path: log/ - name: Archive test screenshots - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 if: failure() with: name: test-search-screenshots diff --git a/.gitignore b/.gitignore index db63bc07f0d003..4727d9ec27f983 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ /public/packs /public/packs-dev /public/packs-test +stats.html .env .env.production node_modules/ diff --git a/.nvmrc b/.nvmrc index 6e77d0a7496300..b0195acf7815c7 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -22.19 +24.12 diff --git a/.prettierignore b/.prettierignore index 4e3b925f73873e..58e9b122adc11a 100644 --- a/.prettierignore +++ b/.prettierignore @@ -95,6 +95,7 @@ AUTHORS.md # Ignore glitch-soc vendored CSS reset app/javascript/flavours/glitch/styles/reset.scss +app/javascript/flavours/glitch/styles_new/mastodon/reset.scss # Ignore win95 theme app/javascript/styles/win95.scss \ No newline at end of file diff --git a/.ruby-version b/.ruby-version index 1cf8253024ccd6..7921bd0c892723 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.4.6 +3.4.8 diff --git a/.storybook/main.ts b/.storybook/main.ts index bb69f0c664957c..2f70c80dbfe00a 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -27,11 +27,12 @@ const config: StorybookConfig = { 'oops.gif', 'oops.png', ].map((path) => ({ from: `../public/${path}`, to: `/${path}` })), + { from: '../app/javascript/images/logo.svg', to: '/custom-emoji/logo.svg' }, ], viteFinal(config) { // For an unknown reason, Storybook does not use the root // from the Vite config so we need to set it manually. - config.root = resolve(__dirname, '../app/javascript'); + config.root = resolve(import.meta.dirname, '../app/javascript'); return config; }, }; diff --git a/.storybook/preview-body.html b/.storybook/preview-body.html index 1870d95b8fe3db..7a92b6f95ff869 100644 --- a/.storybook/preview-body.html +++ b/.storybook/preview-body.html @@ -1,2 +1,2 @@ - + \ No newline at end of file diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index fcba9230308dd7..abbd193c68190e 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -11,6 +11,11 @@ import type { Preview } from '@storybook/react-vite'; import { initialize, mswLoader } from 'msw-storybook-addon'; import { action } from 'storybook/actions'; +import { + importCustomEmojiData, + importLegacyShortcodes, + importEmojiData, +} from '@/mastodon/features/emoji/loader'; import type { LocaleData } from '@/mastodon/locales'; import { reducerWithInitialState } from '@/mastodon/reducers'; import { defaultMiddleware } from '@/mastodon/store/store'; @@ -50,9 +55,32 @@ const preview: Preview = { locale: 'en', }, decorators: [ - (Story, { parameters, globals }) => { + (Story, { parameters, globals, args, argTypes }) => { + // Get the locale from the global toolbar + // and merge it with any parameters or args state. const { locale } = globals as { locale: string }; const { state = {} } = parameters; + + const argsState: Record = {}; + for (const [key, value] of Object.entries(args)) { + const argType = argTypes[key]; + if (argType?.reduxPath) { + const reduxPath = Array.isArray(argType.reduxPath) + ? argType.reduxPath.map((p) => p.toString()) + : argType.reduxPath.split('.'); + + reduxPath.reduce((acc, key, i) => { + if (acc[key] === undefined) { + acc[key] = {}; + } + if (i === reduxPath.length - 1) { + acc[key] = value; + } + return acc[key] as Record; + }, argsState); + } + } + const reducer = reducerWithInitialState( { meta: { @@ -60,7 +88,9 @@ const preview: Preview = { }, }, state as Record, + argsState, ); + const store = configureStore({ reducer, middleware(getDefaultMiddleware) { @@ -121,7 +151,12 @@ const preview: Preview = { ), ], - loaders: [mswLoader], + loaders: [ + mswLoader, + importCustomEmojiData, + importLegacyShortcodes, + ({ globals: { locale } }) => importEmojiData(locale as string), + ], parameters: { layout: 'centered', diff --git a/.storybook/static/mockServiceWorker.js b/.storybook/static/mockServiceWorker.js index 15623f1090b9f1..02115fb4d994f0 100644 --- a/.storybook/static/mockServiceWorker.js +++ b/.storybook/static/mockServiceWorker.js @@ -7,7 +7,7 @@ * - Please do NOT modify this file. */ -const PACKAGE_VERSION = '2.11.3' +const PACKAGE_VERSION = '2.12.1' const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82' const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') const activeClientIds = new Set() @@ -205,6 +205,7 @@ async function resolveMainClient(event) { * @param {FetchEvent} event * @param {Client | undefined} client * @param {string} requestId + * @param {number} requestInterceptedAt * @returns {Promise} */ async function getResponse(event, client, requestId, requestInterceptedAt) { diff --git a/.storybook/storybook-addon-vitest.d.ts b/.storybook/storybook.d.ts similarity index 54% rename from .storybook/storybook-addon-vitest.d.ts rename to .storybook/storybook.d.ts index 86852faca9f8f4..47624d1e9c6783 100644 --- a/.storybook/storybook-addon-vitest.d.ts +++ b/.storybook/storybook.d.ts @@ -1,7 +1,20 @@ // The addon package.json incorrectly exports types, so we need to override them here. + +import type { RootState } from '@/mastodon/store'; + // See: https://github.com/storybookjs/storybook/blob/v9.0.4/code/addons/vitest/package.json#L70-L76 declare module '@storybook/addon-vitest/vitest-plugin' { export * from '@storybook/addon-vitest/dist/vitest-plugin/index'; } +type RootPathKeys = keyof RootState; + +declare module 'storybook/internal/csf' { + export interface InputType { + reduxPath?: + | `${RootPathKeys}.${string}` + | [RootPathKeys, ...(string | number)[]]; + } +} + export {}; diff --git a/.yarn/patches/babel-plugin-lodash-npm-3.3.4-c7161075b6.patch b/.yarn/patches/babel-plugin-lodash-npm-3.3.4-c7161075b6.patch deleted file mode 100644 index 0b3f94d09ee83a..00000000000000 --- a/.yarn/patches/babel-plugin-lodash-npm-3.3.4-c7161075b6.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/lib/index.js b/lib/index.js -index 16ed6be8be8f555cc99096c2ff60954b42dc313d..d009c069770d066ad0db7ad02de1ea473a29334e 100644 ---- a/lib/index.js -+++ b/lib/index.js -@@ -99,7 +99,7 @@ function lodash(_ref) { - - var node = _ref3; - -- if ((0, _types.isModuleDeclaration)(node)) { -+ if ((0, _types.isImportDeclaration)(node) || (0, _types.isExportDeclaration)(node)) { - isModule = true; - break; - } diff --git a/AUTHORS.md b/AUTHORS.md index 78cc37a17b9350..5d243ed43b1b51 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -538,7 +538,7 @@ and provided thanks to the work of the following contributors: * [Drew Schuster](mailto:dtschust@gmail.com) * [Dryusdan](mailto:dryusdan@dryusdan.fr) * [Eai](mailto:eai@mizle.net) -* [Eashwar Ranganathan](mailto:eranganathan@lyft.com) +* [Eashwar Ranganathan](mailto:eashwar@eashwar.com) * [Ed Knutson](mailto:knutsoned@gmail.com) * [Elizabeth Martín Campos](mailto:me@elizabeth.sh) * [Elizabeth Myers](mailto:elizabeth@interlinked.me) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ad88c51070c5a..399b2fe0844b58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,227 @@ All notable changes to this project will be documented in this file. +## [4.5.3] - 2025-12-08 + +### Security + +- Fix inconsistent error handling leaking information on existence of private posts ([GHSA-gwhw-gcjx-72v8](https://github.com/mastodon/mastodon/security/advisories/GHSA-gwhw-gcjx-72v8)) + +### Fixed + +- Fix “Delete and Redraft” on a non-quote being treated as a quote post in some cases (#37140 by @ClearlyClaire) +- Fix YouTube embeds by sending referer (#37126 by @ChaosExAnima) +- Fix streamed quoted polls not being hydrated correctly (#37118 by @ClearlyClaire) +- Fix creation of duplicate conversations (#37108 by @oneiros) +- Fix extraneous `noreferrer` in external links (#37107 by @ChaosExAnima) +- Fix edge case error handling in some database migrations (#37079 by @ClearlyClaire) +- Fix error handling when re-fetching already-known statuses (#37077 by @ClearlyClaire) +- Fix post navigation in single-column mode when Advanced UI is enabled (#37044 by @diondiondion) +- Fix `tootctl status remove` removing quoted posts and remote quotes of local posts (#37009 by @ClearlyClaire) +- Fix known expensive S3 batch delete operation failing because of short timeouts (#37004 by @ClearlyClaire) +- Fix compose autosuggest always lowercasing input token (#36995 by @ClearlyClaire) + +## [4.5.2] - 2025-11-20 + +### Changed + +- Change private quote education modal to not show up on self-quotes (#36926 by @ClearlyClaire) + +### Fixed + +- Fix missing fallback link in CW-only quote posts (#36963 by @ClearlyClaire) +- Fix statuses without text being hidden while loading (#36962 by @ClearlyClaire) +- Fix `g` + `h` keyboard shortcut not working when a post is focused (#36935 by @diondiondion) +- Fix quoting overwriting current content warning (#36934 by @ClearlyClaire) +- Fix scroll-to-status in threaded view being unreliable (#36927 by @ClearlyClaire) +- Fix path resolution for emoji worker (#36897 by @ChaosExAnima) +- Fix `tootctl upgrade storage-schema` failing with `ArgumentError` (#36914 by @shugo) +- Fix cross-origin handling of CSS modules (#36890 by @ClearlyClaire) +- Fix error with remote tags including percent signs (#36886 and #36925 by @ChaosExAnima and @ClearlyClaire) +- Fix bogus quote approval policy not always being replaced correctly (#36885 by @ClearlyClaire) +- Fix hashtag completion not being inserted correctly (#36884 by @ClearlyClaire) +- Fix Cmd/Ctrl + Enter in the composer triggering confirmation dialog action (#36870 by @diondiondion) + +## [4.5.1] - 2025-11-13 + +### Fixed + +- Fix Cmd/Ctrl + Enter not submitting Alt text modal on some browsers (#36866 by @diondiondion) +- Fix posts coming from public/hashtag streaming being marked as unquotable (#36860 and #36869 by @ClearlyClaire) +- Fix old previously-undiscovered posts being treated as new when receiving an `Update` (#36848 by @ClearlyClaire) +- Fix blank screen in browsers that don't support `Intl.DisplayNames` (#36847 by @diondiondion) +- Fix filters not being applied to quotes in detailed view (#36843 by @ClearlyClaire) +- Fix scroll shift caused by fetch-all-replies alerts (#36807 by @diondiondion) +- Fix dropdown menu not focusing first item when opened via keyboard (#36804 by @diondiondion) +- Fix assets build issue on arch64 (#36781 by @ClearlyClaire) +- Fix `/api/v1/statuses/:id/context` sometimes returing `Mastodon-Async-Refresh` without `result_count` (#36779 by @ClearlyClaire) +- Fix prepared quote not being discarded with contents when replying (#36778 by @ClearlyClaire) + +## [4.5.0] - 2025-11-06 + +### Added + +- **Add support for allowing and authoring quotes** (#35355, #35578, #35614, #35618, #35624, #35626, #35652, #35629, #35665, #35653, #35670, #35677, #35690, #35697, #35689, #35699, #35700, #35701, #35709, #35714, #35713, #35715, #35725, #35749, #35769, #35780, #35762, #35804, #35808, #35805, #35819, #35824, #35828, #35822, #35835, #35865, #35860, #35832, #35891, #35894, #35895, #35820, #35917, #35924, #35925, #35914, #35930, #35941, #35939, #35948, #35955, #35967, #35990, #35991, #35975, #35971, #36002, #35986, #36031, #36034, #36038, #36054, #36052, #36055, #36065, #36068, #36083, #36087, #36080, #36091, #36090, #36118, #36119, #36128, #36094, #36129, #36138, #36132, #36151, #36158, #36171, #36194, #36220, #36169, #36130, #36249, #36153, #36299, #36291, #36301, #36315, #36317, #36364, #36383, #36381, #36459, #36464, #36461, #36516, #36528, #36549, #36550, #36559, #36693, #36704, #36690, #36689, #36696, #36721, #36695 and #36736 by @ChaosExAnima, @ClearlyClaire, @Lycolia, @diondiondion, and @tribela)\ + This includes a revamp of the composer interface.\ + See https://blog.joinmastodon.org/2025/09/introducing-quote-posts/ for a user-centric overview of the feature, and https://docs.joinmastodon.org/client/quotes/ for API documentation. +- **Add support for fetching and refreshing replies to the web UI** (#35210, #35496, #35575, #35500, #35577, #35602, #35603, #35654, #36141, #36237, #36172, #36256, #36271, #36334, #36382, #36239, #36484, #36481, #36583, #36627 and #36547 by @ClearlyClaire, @diondiondion, @Gargron and @renchap) +- **Add ability to block words in usernames** (#35407, #35655, and #35806 by @ClearlyClaire and @Gargron) +- Add ability to individually disable local or remote feeds for visitors or logged-in users `disabled` value to server setting for live and topic feeds, as well as user permission to bypass that (#36338, #36467, #36497, #36563, #36577, #36585, #36607 and #36703 by @ClearlyClaire)\ + This splits the `timeline_preview` setting into four more granular settings controlling live feeds and topic (hashtag, trending link) feeds.\ + The setting for local topic feeds has 2 values: `public` and `authenticated`. Every other setting has 3 values: `public`, `authenticated`, `disabled`.\ + When `disabled`, users with the “View live and topic feeds” will still be able to view them. +- Add support for displaying of quote posts in Moderator UI (#35964 by @ThisIsMissEm) +- Add support for displaying link previews for Admin UI (#35958 by @ThisIsMissEm) +- Add a new server setting to choose the server landing page (#36588 and #36602 by @ClearlyClaire and @renchap) +- Add support for `Update` activities on converted object types (#36322 by @ClearlyClaire) +- Add support for dynamic viewport height (#36272 by @e1berd) +- Add support for numeric-based URIs for new local accounts (#32724, #36304, #36316, and #36365 by @ClearlyClaire) +- Add default visualizer for audio upload without poster (#36734 by @ChaosExAnima) +- Add Traditional Mongolian to posting languages (#36196 by @shimon1024) +- Add example post with manual quote approval policy to `dev:populate_sample_data` (#36099 by @ClearlyClaire) +- Add server-side support for handling posts with a quote policy allowing followers to quote (#36093 and #36127 by @ClearlyClaire) +- Add schema.org markup to SEO-enabled posts (#36075 by @Gargron) +- Add migration to fill unset default quote policy based on default post privacy (#36041 by @ClearlyClaire) +- Add “Posting defaults” setting page, moving existing settings from “Other” (#35896, #36033, #35966, #35969, and #36084 by @ClearlyClaire and @diondiondion) +- Added emoji from Twemoji v16 (#36501 and #36530 by @ChaosExAnima) +- Add feature to select custom emoji rendering (#35229, #35282, #35253, #35424, #35473, #35483, #35505, #35568, #35605, #35659, #35664, #35739, #35985, #36051, #36071, #36137, #36165, #36248, #36262, #36275, #36293, #36341, #36342, #36366, #36377, #36378, #36385, #36393, #36397, #36403, #36413, #36410, #36454, #36402, #36503, #36502, #36532, #36603, #36409, #36638 and #36750 by @ChaosExAnima, @ClearlyClaire and @braddunbar)\ + This also completely reworks the processing and rendering of emojis and server-rendered HTML in statuses and other places. +- Add support for exposing conversation context for new public conversations according to FEP-7888 (#35959 and #36064 by @ClearlyClaire and @jesseplusplus) +- Add digest re-check before removing followers in synchronization mechanism (#34273 by @ClearlyClaire) +- Add support for displaying Valkey version on admin dashboard (#35785 by @ykzts) +- Add delivery failure tracking and handling to FASP jobs (#35625, #35628, and #35723 by @oneiros) +- Add example of quote post with a preview card to development sample data (#35616 by @ClearlyClaire) +- Add second set of blocked text that applies to accounts regardless of account age for spam-blocking (#35563 by @ClearlyClaire) + +### Changed + +- Change confirmation dialogs for follow button actions “unfollow”, “unblock”, and “withdraw request” (#36289 by @diondiondion) +- Change “Follow” button labels (#36264 by @diondiondion) +- Change appearance settings to introduce new Advanced settings section (#36496 and #36506 by @diondiondion) +- Change display of blocked and muted quoted users (#36619 by @ClearlyClaire)\ + This adds `blocked_account`, `blocked_domain` and `muted_account` values to the `state` attribute of `Quote` and `ShallowQuote` REST API entities. +- Change submitting an empty post to show an error rather than failing silently (#36650 by @diondiondion) +- Change "Privacy and reach" settings from "Public profile" to their own top-level category (#27294 by @ChaelCodes) +- Change number of times quote verification is retried to better deal with temporary failures (#36698 by @ClearlyClaire) +- Change display of content warnings in Admin UI (#35935 by @ThisIsMissEm) +- Change styling of column banners (#36531 by @ClearlyClaire) +- Change recommended Node version to 24 (LTS) (#36539 by @renchap) +- Change min. characters required for logged-out account search from 5 to 3 (#36487 by @Gargron) +- Change browser target to Vite legacy plugin defaults (#36611 by @larouxn) +- Change index on `follows` table to improve performance of some queries (#36374 by @ClearlyClaire) +- Change links to accounts in settings and moderation views to link to local view unless account is suspended (#36340 by @diondiondion) +- Change redirection for denied registration from web app to sign-in page with error message (#36384 by @ClearlyClaire) +- Change support for RFC9421 HTTP signatures to be enabled unconditionally (#36610 by @oneiros) +- Change wording and design of interaction dialog to simplify it (#36124 by @diondiondion) +- Change dropdown menus to allow disabled items to be focused (#36078 by @diondiondion) +- Change modal background colours in light mode (#36069 by @diondiondion) +- Change “Posting defaults” settings page to enforce `nobody` quote policy for `private` default visibility (#36040 by @ClearlyClaire) +- Change description of “Quiet public” (#36032 by @ClearlyClaire) +- Change “Boost with original visibility” to “Share again with your followers” (#36035 by @ClearlyClaire) +- Change handling of push subscriptions to automatically delete invalid ones on delivery (#35987 by @ThisIsMissEm) +- Change design of quote posts in web UI (#35584 and #35834 by @Gargron) +- Change auditable accounts to be sorted by username in admin action logs interface (#35272 by @breadtk) +- Change order of translation restoration and service credit on post card (#33619 by @colindean) +- Change position of ‘add more’ to be inside table toolbar on reports (#35963 by @ThisIsMissEm) +- Change docker-compose.yml sidekiq health check to work for both 4.4 and 4.5 (#36498 by @ClearlyClaire) + +### Fixed + +- Fix relationship not being fetched to evaluate whether to show a quote post (#36517 by @ClearlyClaire) +- Fix rendering of poll options in status history modal (#35633 by @ThisIsMissEm) +- Fix “mute” button being displayed to unauthenticated visitors in hashtag dropdown (#36353 by @mkljczk) +- Fix initially selected language in Rules panel, hide selector when no alternative translations exist (#36672 by @diondiondion) +- Fix URL comparison for mentions in case of empty path (#36613 and #36626 by @ClearlyClaire) +- Fix hashtags not being picked up when full-width hash sign is used (#36103 and #36625 by @ClearlyClaire and @Gargron) +- Fix layout of severed relationships when purged events are listed (#36593 by @mejofi) +- Fix Skeleton placeholders being animated when setting to reduce animations is enabled (#36716 by @ClearlyClaire) +- Fix vacuum tasks being interrupted by a single batch failure (#36606 by @Gargron) +- Fix handling of unreachable network error for search services (#36587 by @mjankowski) +- Fix bookmarks export when a bookmarked status is soft-deleted (#36576 by @ClearlyClaire) +- Fix text overflow alignment for long author names in News (#36562 by @diondiondion) +- Fix discovery preamble missing word in admin settings (#36560 by @belatedly) +- Fix overflow handling of `.more-from-author` (#36310 by @edent) +- Fix unfortunate action button wrapping in admin area (#36247 by @diondiondion) +- Fix translate button width in Safari (#36164 and #36216 by @diondiondion) +- Fix login page linking to other pages within OAuth authorization flow (#36115 by @Gargron) +- Fix stale search results being displayed in Web UI while new query is in progress (#36053 by @ChaosExAnima) +- Fix YouTube iframe not being able to start at a defined time (#26584 by @BrunoViveiros) +- Fix banned text being able to be circumvented via unicode (#35978 by @Gargron) +- Fix batch table toolbar displaying under status media (#35962 by @ThisIsMissEm) +- Fix incorrect RSS feed MIME type in gzip_types directive (#35562 by @iioflow) +- Fix 404 error after deleting status from detail view (#35800) (#35881 by @crafkaz) +- Fix feeds keyboard navigation issues (#35853, #35864, and #36267 by @braddunbar and @diondiondion) +- Fix layout shift caused by “Who to follow” widget (#35861 by @diondiondion) +- Fix Vagrantfile (#35765 by @ClearlyClaire) +- Fix reply indicator displaying wrong avatar in rare cases (#35756 by @ClearlyClaire) +- Fix `Chewy::UndefinedUpdateStrategy` in `dev:populate_sample_data` task when Elasticsearch is enabled (#35615 by @ClearlyClaire) +- Fix unnecessary account note addition for already-muted moved-to users (#35566 by @mjankowski) +- Fix seeded admin user creation failing on specific configurations (#35565 by @oneiros) +- Fix media modal images in Web UI having redundant `title` attribute (#35468 by @mayank99) +- Fix inconsistent default privacy post setting when unset in settings (#35422 by @oneiros) +- Fix glitchy status keyboard navigation (#35455 and #35504 by @diondiondion) +- Fix post being submitted when pressing “Enter” in the CW field (#35445 by @diondiondion) + +### Removed + +- Remove support for PostgreSQL 13 (#36540 by @renchap) + +## [4.4.8] - 2025-10-21 + +### Security + +- Fix quote control bypass ([GHSA-8h43-rcqj-wpc6](https://github.com/mastodon/mastodon/security/advisories/GHSA-8h43-rcqj-wpc6)) + +## [4.4.7] - 2025-10-15 + +### Fixed + +- Fix forwarder being called with `nil` status when quote post is soft-deleted (#36463 by @ClearlyClaire) +- Fix moderation warning e-mails that include posts (#36462 by @ClearlyClaire) +- Fix allow_referrer_origin typo (#36460 by @ShadowJonathan) + +## [4.4.6] - 2025-10-13 + +### Security + +- Update dependencies `rack` and `uri` +- Fix streaming server connection not being closed on user suspension (by @ThisIsMissEm, [GHSA-r2fh-jr9c-9pxh](https://github.com/mastodon/mastodon/security/advisories/GHSA-r2fh-jr9c-9pxh)) +- Fix password change through admin CLI not invalidating existing sessions and access tokens (by @ThisIsMissEm, [GHSA-f3q3-rmf7-9655](https://github.com/mastodon/mastodon/security/advisories/GHSA-f3q3-rmf7-9655)) +- Fix streaming server allowing access to public timelines even without the `read` or `read:statuses` OAuth scopes (by @ThisIsMissEm, [GHSA-7gwh-mw97-qjgp](https://github.com/mastodon/mastodon/security/advisories/GHSA-7gwh-mw97-qjgp)) + +### Added + +- Add support for processing quotes of deleted posts signaled through a `Tombstone` (#36381 by @ClearlyClaire) + +### Fixed + +- Fix quote post state sometimes not being updated through streaming server (#36408 by @ClearlyClaire) +- Fix inconsistent “pending tags” count on admin dashboard (#36404 by @mjankowski) +- Fix JSON payload being potentially mutated when processing interaction policies (#36392 by @ClearlyClaire) +- Fix quotes not being displayed in email notifications (#36379 by @diondiondion) +- Fix redirect to external object when URL is missing or malformed (#36347 by @ClearlyClaire) +- Fix quotes not being displayed in the featured carousel (#36335 by @diondiondion) + +## [4.4.5] - 2025-09-23 + +### Security + +- Update dependencies + +### Added + +- Add support for `has:quote` in search (#36217 by @ClearlyClaire) + +### Changed + +- Change quoted posts from silenced accounts to use a click-through rather than being hidden (#36166 and #36167 by @ClearlyClaire) + +### Fixed + +- Fix processing of out-of-order `Update` as implicit updates (#36190 by @ClearlyClaire) +- Fix getting `Create` and `Update` out of order (#36176 by @ClearlyClaire) +- Fix quotes with Content Warnings but no text being shown without Content Warnings (#36150 by @ClearlyClaire) + ## [4.4.4] - 2025-09-16 ### Security diff --git a/Dockerfile b/Dockerfile index f2164ffd94102a..865d14402cd88e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,10 +13,10 @@ ARG BASE_REGISTRY="docker.io" # Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.4.x"] # renovate: datasource=docker depName=docker.io/ruby -ARG RUBY_VERSION="3.4.6" -# # Node.js version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"] +ARG RUBY_VERSION="3.4.8" +# # Node.js version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="22"] # renovate: datasource=node-version depName=node -ARG NODE_MAJOR_VERSION="22" +ARG NODE_MAJOR_VERSION="24" # Debian image to use for base image, change with [--build-arg DEBIAN_VERSION="trixie"] ARG DEBIAN_VERSION="trixie" # Node.js image to use for base image based on combined variables (ex: 20-trixie-slim) @@ -183,7 +183,7 @@ FROM build AS libvips # libvips version to compile, change with [--build-arg VIPS_VERSION="8.15.2"] # renovate: datasource=github-releases depName=libvips packageName=libvips/libvips -ARG VIPS_VERSION=8.17.2 +ARG VIPS_VERSION=8.17.3 # libvips download URL, change with [--build-arg VIPS_URL="https://github.com/libvips/libvips/releases/download"] ARG VIPS_URL=https://github.com/libvips/libvips/releases/download @@ -208,12 +208,12 @@ FROM build AS ffmpeg # renovate: datasource=repology depName=ffmpeg packageName=openpkg_current/ffmpeg ARG FFMPEG_VERSION=8.0 # ffmpeg download URL, change with [--build-arg FFMPEG_URL="https://ffmpeg.org/releases"] -ARG FFMPEG_URL=https://ffmpeg.org/releases +ARG FFMPEG_URL=https://github.com/FFmpeg/FFmpeg/archive/refs/tags WORKDIR /usr/local/ffmpeg/src # Download and extract ffmpeg source code -ADD ${FFMPEG_URL}/ffmpeg-${FFMPEG_VERSION}.tar.xz /usr/local/ffmpeg/src/ -RUN tar xf ffmpeg-${FFMPEG_VERSION}.tar.xz; +ADD ${FFMPEG_URL}/n${FFMPEG_VERSION}.tar.gz /usr/local/ffmpeg/src/ +RUN tar xf n${FFMPEG_VERSION}.tar.gz && mv FFmpeg-n${FFMPEG_VERSION} ffmpeg-${FFMPEG_VERSION}; WORKDIR /usr/local/ffmpeg/src/ffmpeg-${FFMPEG_VERSION} diff --git a/Gemfile b/Gemfile index 126d73f9cab1e3..0a7c00b11e343b 100644 --- a/Gemfile +++ b/Gemfile @@ -9,11 +9,11 @@ gem 'rails', '~> 8.0' gem 'thor', '~> 1.2' gem 'dotenv' -gem 'haml-rails', '~>2.0' +gem 'haml-rails', '~>3.0' gem 'pg', '~> 1.5' gem 'pghero' -gem 'aws-sdk-core', '< 3.216.0', require: false # TODO: https://github.com/mastodon/mastodon/pull/34173#issuecomment-2733378873 +gem 'aws-sdk-core', require: false gem 'aws-sdk-s3', '~> 1.123', require: false gem 'blurhash', '~> 0.1' gem 'fog-core', '<= 2.6.0' @@ -24,7 +24,7 @@ gem 'ruby-vips', '~> 2.2', require: false gem 'active_model_serializers', '~> 0.10' gem 'addressable', '~> 2.8' -gem 'bootsnap', '~> 1.18.0', require: false +gem 'bootsnap', '~> 1.19.0', require: false gem 'browser' gem 'charlock_holmes', '~> 0.7.7' gem 'chewy', '~> 7.3' @@ -40,7 +40,7 @@ gem 'net-ldap', '~> 0.18' gem 'omniauth', '~> 2.0' gem 'omniauth-cas', '~> 3.0.0.beta.1' gem 'omniauth_openid_connect', '~> 0.8.0' -gem 'omniauth-rails_csrf_protection', '~> 1.0' +gem 'omniauth-rails_csrf_protection', '~> 2.0' gem 'omniauth-saml', '~> 2.0' gem 'color_diff', '~> 0.1' @@ -71,7 +71,7 @@ gem 'oj', '~> 3.14' gem 'ox', '~> 2.14' gem 'parslet' gem 'premailer-rails' -gem 'public_suffix', '~> 6.0' +gem 'public_suffix', '~> 7.0' gem 'pundit', '~> 2.3' gem 'rack-attack', '~> 6.6' gem 'rack-cors', require: 'rack/cors' @@ -105,20 +105,20 @@ gem 'prometheus_exporter', '~> 2.2', require: false gem 'opentelemetry-api', '~> 1.7.0' group :opentelemetry do - gem 'opentelemetry-exporter-otlp', '~> 0.30.0', require: false - gem 'opentelemetry-instrumentation-active_job', '~> 0.8.0', require: false - gem 'opentelemetry-instrumentation-active_model_serializers', '~> 0.22.0', require: false - gem 'opentelemetry-instrumentation-concurrent_ruby', '~> 0.22.0', require: false - gem 'opentelemetry-instrumentation-excon', '~> 0.24.0', require: false - gem 'opentelemetry-instrumentation-faraday', '~> 0.28.0', require: false - gem 'opentelemetry-instrumentation-http', '~> 0.25.0', require: false - gem 'opentelemetry-instrumentation-http_client', '~> 0.24.0', require: false - gem 'opentelemetry-instrumentation-net_http', '~> 0.24.0', require: false - gem 'opentelemetry-instrumentation-pg', '~> 0.30.0', require: false - gem 'opentelemetry-instrumentation-rack', '~> 0.27.0', require: false - gem 'opentelemetry-instrumentation-rails', '~> 0.37.0', require: false - gem 'opentelemetry-instrumentation-redis', '~> 0.26.0', require: false - gem 'opentelemetry-instrumentation-sidekiq', '~> 0.26.0', require: false + gem 'opentelemetry-exporter-otlp', '~> 0.31.0', require: false + gem 'opentelemetry-instrumentation-active_job', '~> 0.10.0', require: false + gem 'opentelemetry-instrumentation-active_model_serializers', '~> 0.24.0', require: false + gem 'opentelemetry-instrumentation-concurrent_ruby', '~> 0.24.0', require: false + gem 'opentelemetry-instrumentation-excon', '~> 0.26.0', require: false + gem 'opentelemetry-instrumentation-faraday', '~> 0.30.0', require: false + gem 'opentelemetry-instrumentation-http', '~> 0.27.0', require: false + gem 'opentelemetry-instrumentation-http_client', '~> 0.26.0', require: false + gem 'opentelemetry-instrumentation-net_http', '~> 0.26.0', require: false + gem 'opentelemetry-instrumentation-pg', '~> 0.34.0', require: false + gem 'opentelemetry-instrumentation-rack', '~> 0.29.0', require: false + gem 'opentelemetry-instrumentation-rails', '~> 0.39.0', require: false + gem 'opentelemetry-instrumentation-redis', '~> 0.28.0', require: false + gem 'opentelemetry-instrumentation-sidekiq', '~> 0.28.0', require: false gem 'opentelemetry-sdk', '~> 1.4', require: false end @@ -138,7 +138,7 @@ group :test do # Browser integration testing gem 'capybara', '~> 3.39' gem 'capybara-playwright-driver' - gem 'playwright-ruby-client', '1.55.0', require: false # Pinning the exact version as it needs to be kept in sync with the installed npm package + gem 'playwright-ruby-client', '1.57.0', require: false # Pinning the exact version as it needs to be kept in sync with the installed npm package # Used to reset the database between system tests gem 'database_cleaner-active_record' @@ -160,6 +160,9 @@ group :test do # Stub web requests for specs gem 'webmock', '~> 3.18' + + # Websocket driver for testing integration between rails/sidekiq and streaming + gem 'websocket-driver', '~> 0.8', require: false end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index 64ef3057d8a664..20e4ff9fe71b6f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -10,29 +10,29 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (8.0.2.1) - actionpack (= 8.0.2.1) - activesupport (= 8.0.2.1) + actioncable (8.0.3) + actionpack (= 8.0.3) + activesupport (= 8.0.3) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (8.0.2.1) - actionpack (= 8.0.2.1) - activejob (= 8.0.2.1) - activerecord (= 8.0.2.1) - activestorage (= 8.0.2.1) - activesupport (= 8.0.2.1) + actionmailbox (8.0.3) + actionpack (= 8.0.3) + activejob (= 8.0.3) + activerecord (= 8.0.3) + activestorage (= 8.0.3) + activesupport (= 8.0.3) mail (>= 2.8.0) - actionmailer (8.0.2.1) - actionpack (= 8.0.2.1) - actionview (= 8.0.2.1) - activejob (= 8.0.2.1) - activesupport (= 8.0.2.1) + actionmailer (8.0.3) + actionpack (= 8.0.3) + actionview (= 8.0.3) + activejob (= 8.0.3) + activesupport (= 8.0.3) mail (>= 2.8.0) rails-dom-testing (~> 2.2) - actionpack (8.0.2.1) - actionview (= 8.0.2.1) - activesupport (= 8.0.2.1) + actionpack (8.0.3) + actionview (= 8.0.3) + activesupport (= 8.0.3) nokogiri (>= 1.8.5) rack (>= 2.2.4) rack-session (>= 1.0.1) @@ -40,40 +40,40 @@ GEM rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) useragent (~> 0.16) - actiontext (8.0.2.1) - actionpack (= 8.0.2.1) - activerecord (= 8.0.2.1) - activestorage (= 8.0.2.1) - activesupport (= 8.0.2.1) + actiontext (8.0.3) + actionpack (= 8.0.3) + activerecord (= 8.0.3) + activestorage (= 8.0.3) + activesupport (= 8.0.3) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (8.0.2.1) - activesupport (= 8.0.2.1) + actionview (8.0.3) + activesupport (= 8.0.3) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - active_model_serializers (0.10.15) + active_model_serializers (0.10.16) actionpack (>= 4.1) activemodel (>= 4.1) case_transform (>= 0.2) jsonapi-renderer (>= 0.1.1.beta1, < 0.3) - activejob (8.0.2.1) - activesupport (= 8.0.2.1) + activejob (8.0.3) + activesupport (= 8.0.3) globalid (>= 0.3.6) - activemodel (8.0.2.1) - activesupport (= 8.0.2.1) - activerecord (8.0.2.1) - activemodel (= 8.0.2.1) - activesupport (= 8.0.2.1) + activemodel (8.0.3) + activesupport (= 8.0.3) + activerecord (8.0.3) + activemodel (= 8.0.3) + activesupport (= 8.0.3) timeout (>= 0.4.0) - activestorage (8.0.2.1) - actionpack (= 8.0.2.1) - activejob (= 8.0.2.1) - activerecord (= 8.0.2.1) - activesupport (= 8.0.2.1) + activestorage (8.0.3) + actionpack (= 8.0.3) + activejob (= 8.0.3) + activerecord (= 8.0.3) + activesupport (= 8.0.3) marcel (~> 1.0) - activesupport (8.0.2.1) + activesupport (8.0.3) base64 benchmark (>= 0.3) bigdecimal @@ -86,27 +86,30 @@ GEM securerandom (>= 0.3) tzinfo (~> 2.0, >= 2.0.5) uri (>= 0.13.1) - addressable (2.8.7) - public_suffix (>= 2.0.2, < 7.0) + addressable (2.8.8) + public_suffix (>= 2.0.2, < 8.0) aes_key_wrap (1.1.0) android_key_attestation (0.3.0) - annotaterb (4.19.0) + annotaterb (4.20.0) activerecord (>= 6.0.0) activesupport (>= 6.0.0) ast (2.4.3) attr_required (1.0.2) aws-eventstream (1.4.0) - aws-partitions (1.1135.0) - aws-sdk-core (3.215.1) + aws-partitions (1.1194.0) + aws-sdk-core (3.239.2) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) aws-sigv4 (~> 1.9) + base64 + bigdecimal jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.96.0) - aws-sdk-core (~> 3, >= 3.210.0) + logger + aws-sdk-kms (1.118.0) + aws-sdk-core (~> 3, >= 3.239.1) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.177.0) - aws-sdk-core (~> 3, >= 3.210.0) + aws-sdk-s3 (1.207.0) + aws-sdk-core (~> 3, >= 3.234.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) aws-sigv4 (1.12.1) @@ -116,24 +119,24 @@ GEM base64 (0.3.0) bcp47_spec (0.2.1) bcrypt (3.1.20) - benchmark (0.4.1) + benchmark (0.5.0) better_errors (2.10.1) erubi (>= 1.0.0) rack (>= 0.9.0) rouge (>= 1.0.0) - bigdecimal (3.2.3) + bigdecimal (3.3.1) bindata (2.5.1) binding_of_caller (1.0.1) debug_inspector (>= 1.2.0) blurhash (0.1.8) - bootsnap (1.18.6) + bootsnap (1.19.0) msgpack (~> 1.2) - brakeman (7.0.2) + brakeman (7.1.1) racc browser (6.2.0) builder (3.3.0) - bundler-audit (0.9.2) - bundler (>= 1.2.0, < 3) + bundler-audit (0.9.3) + bundler (>= 1.2.0) thor (~> 1.0) capybara (3.40.0) addressable @@ -150,7 +153,7 @@ GEM playwright-ruby-client (>= 1.16.0) case_transform (0.2) activesupport - cbor (0.5.9.8) + cbor (0.5.10.1) cgi (0.4.2) charlock_holmes (0.7.9) chewy (7.6.0) @@ -163,12 +166,12 @@ GEM climate_control (1.2.0) cocoon (1.2.15) color_diff (0.1) - concurrent-ruby (1.3.5) - connection_pool (2.5.4) + concurrent-ruby (1.3.6) + connection_pool (2.5.5) cose (1.3.1) cbor (~> 0.5.9) openssl-signature_algorithm (~> 1.0) - crack (1.0.0) + crack (1.0.1) bigdecimal rexml crass (1.0.6) @@ -179,7 +182,7 @@ GEM activerecord (>= 5.a) database_cleaner-core (~> 2.0) database_cleaner-core (2.0.1) - date (3.4.1) + date (3.5.1) debug (1.11.0) irb (~> 1.10) reline (>= 0.3.8) @@ -190,10 +193,10 @@ GEM railties (>= 4.1.0) responders warden (~> 1.2.3) - devise-two-factor (6.1.0) - activesupport (>= 7.0, < 8.1) + devise-two-factor (6.2.0) + activesupport (>= 7.0, < 8.2) devise (~> 4.0) - railties (>= 7.0, < 8.1) + railties (>= 7.0, < 8.2) rotp (~> 6.0) devise_pam_authenticatable2 (9.2.0) devise (>= 4.0.0) @@ -205,9 +208,9 @@ GEM domain_name (0.6.20240107) doorkeeper (5.8.2) railties (>= 5) - dotenv (3.1.8) + dotenv (3.2.0) drb (2.2.3) - dry-cli (1.2.0) + dry-cli (1.3.0) elasticsearch (7.17.11) elasticsearch-api (= 7.17.11) elasticsearch-transport (= 7.17.11) @@ -224,25 +227,25 @@ GEM mail (~> 2.7) email_validator (2.2.4) activemodel - erb (5.0.2) + erb (6.0.1) erubi (1.13.1) - et-orbi (1.2.11) + et-orbi (1.4.0) tzinfo - excon (1.2.8) + excon (1.3.2) logger fabrication (3.0.0) - faker (3.5.2) + faker (3.5.3) i18n (>= 1.8.11, < 2) - faraday (2.13.4) + faraday (2.14.0) faraday-net_http (>= 2.0, < 3.5) json logger - faraday-follow_redirects (0.3.0) + faraday-follow_redirects (0.4.0) faraday (>= 1, < 3) faraday-httpclient (2.0.2) httpclient (>= 2.2) - faraday-net_http (3.4.1) - net-http (>= 0.5.0) + faraday-net_http (3.4.2) + net-http (~> 0.5) fast_blank (1.0.1) fastimage (2.4.0) ffi (1.17.2) @@ -266,42 +269,43 @@ GEM fog-openstack (1.1.5) fog-core (~> 2.1) fog-json (>= 1.0) - formatador (1.1.1) + formatador (1.2.3) + reline forwardable (1.3.3) - fugit (1.11.1) - et-orbi (~> 1, >= 1.2.11) + fugit (1.12.1) + et-orbi (~> 1.4) raabro (~> 1.4) - globalid (1.2.1) + globalid (1.3.0) activesupport (>= 6.1) - google-protobuf (4.31.1) + google-protobuf (4.33.2) bigdecimal rake (>= 13) - googleapis-common-protos-types (1.20.0) - google-protobuf (>= 3.18, < 5.a) - haml (6.3.0) + googleapis-common-protos-types (1.22.0) + google-protobuf (~> 4.26) + haml (7.1.0) temple (>= 0.8.2) thor tilt - haml-rails (2.1.0) + haml-rails (3.0.0) actionpack (>= 5.1) activesupport (>= 5.1) haml (>= 4.0.6) railties (>= 5.1) - haml_lint (0.66.0) + haml_lint (0.68.0) haml (>= 5.0) parallel (~> 1.10) rainbow rubocop (>= 1.0) sysexits (~> 1.1) - hashdiff (1.2.0) + hashdiff (1.2.1) hashie (5.0.0) hcaptcha (7.1.0) json highline (3.1.2) reline hiredis (0.6.3) - hiredis-client (0.25.3) - redis-client (= 0.25.3) + hiredis-client (0.26.2) + redis-client (= 0.26.2) hkdf (0.3.0) htmlentities (4.3.4) http (5.3.1) @@ -309,7 +313,7 @@ GEM http-cookie (~> 1.0) http-form_data (~> 2.2) llhttp-ffi (~> 0.5.0) - http-cookie (1.0.8) + http-cookie (1.1.0) domain_name (~> 0.5) http-form_data (2.3.0) http_accept_language (2.1.1) @@ -320,13 +324,14 @@ GEM rainbow (>= 2.0.0) i18n (1.14.7) concurrent-ruby (~> 1.0) - i18n-tasks (1.0.15) + i18n-tasks (1.1.2) activesupport (>= 4.0.2) ast (>= 2.1.0) erubi - highline (>= 2.0.0) + highline (>= 3.0.0) i18n parser (>= 3.2.2.1) + prism rails-i18n rainbow (>= 2.2.2, < 4.0) ruby-progressbar (~> 1.8, >= 1.8.1) @@ -335,8 +340,8 @@ GEM inline_svg (1.10.0) activesupport (>= 3.0) nokogiri (>= 1.6) - io-console (0.8.1) - irb (1.15.2) + io-console (0.8.2) + irb (1.15.3) pp (>= 0.6.0) rdoc (>= 4.0.0) reline (>= 0.4.2) @@ -345,9 +350,9 @@ GEM azure-blob (~> 0.5.2) hashie (~> 5.0) jmespath (1.6.2) - json (2.13.2) + json (2.18.0) json-canonicalization (1.0.0) - json-jwt (1.16.7) + json-jwt (1.17.0) activesupport (>= 4.2) aes_key_wrap base64 @@ -425,7 +430,8 @@ GEM loofah (2.24.1) crass (~> 1.0.2) nokogiri (>= 1.12.0) - mail (2.8.1) + mail (2.9.0) + logger mini_mime (>= 0.1.1) net-imap net-pop @@ -438,16 +444,16 @@ GEM mime-types (3.7.0) logger mime-types-data (~> 3.2025, >= 3.2025.0507) - mime-types-data (3.2025.0916) + mime-types-data (3.2025.0924) mini_mime (1.1.5) mini_portile2 (2.8.9) - minitest (5.25.5) + minitest (5.27.0) msgpack (1.8.0) - multi_json (1.17.0) + multi_json (1.18.0) mutex_m (0.3.0) net-http (0.6.0) uri - net-imap (0.5.9) + net-imap (0.6.0) date net-protocol net-ldap (0.20.0) @@ -459,22 +465,23 @@ GEM timeout net-smtp (0.5.1) net-protocol - nio4r (2.7.4) + nio4r (2.7.5) nokogiri (1.18.10) mini_portile2 (~> 2.8.2) racc (~> 1.4) - oj (3.16.11) + oj (3.16.13) bigdecimal (>= 3.0) ostruct (>= 0.2) - omniauth (2.1.3) + omniauth (2.1.4) hashie (>= 3.4.6) + logger rack (>= 2.2.3) rack-protection omniauth-cas (3.0.2) addressable (~> 2.8) nokogiri (~> 1.12) omniauth (~> 2.1) - omniauth-rails_csrf_protection (1.0.2) + omniauth-rails_csrf_protection (2.0.1) actionpack (>= 4.2) omniauth (~> 2.0) omniauth-saml (2.2.4) @@ -496,102 +503,77 @@ GEM tzinfo validate_url webfinger (~> 2.0) - openssl (3.3.0) + openssl (3.3.2) openssl-signature_algorithm (1.3.0) openssl (> 2.0) opentelemetry-api (1.7.0) - opentelemetry-common (0.22.0) + opentelemetry-common (0.23.0) opentelemetry-api (~> 1.0) - opentelemetry-exporter-otlp (0.30.0) + opentelemetry-exporter-otlp (0.31.1) google-protobuf (>= 3.18) googleapis-common-protos-types (~> 1.3) opentelemetry-api (~> 1.1) opentelemetry-common (~> 0.20) - opentelemetry-sdk (~> 1.2) + opentelemetry-sdk (~> 1.10) opentelemetry-semantic_conventions - opentelemetry-helpers-sql (0.1.1) - opentelemetry-api (~> 1.0) - opentelemetry-helpers-sql-obfuscation (0.3.0) + opentelemetry-helpers-sql (0.3.0) + opentelemetry-api (~> 1.7) + opentelemetry-helpers-sql-processor (0.3.1) opentelemetry-common (~> 0.21) - opentelemetry-instrumentation-action_mailer (0.4.0) - opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-active_support (~> 0.7) - opentelemetry-instrumentation-base (~> 0.23.0) - opentelemetry-instrumentation-action_pack (0.13.0) - opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.23.0) - opentelemetry-instrumentation-rack (~> 0.21) - opentelemetry-instrumentation-action_view (0.9.0) - opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-active_support (~> 0.7) - opentelemetry-instrumentation-base (~> 0.23.0) - opentelemetry-instrumentation-active_job (0.8.0) - opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.23.0) - opentelemetry-instrumentation-active_model_serializers (0.22.0) - opentelemetry-api (~> 1.0) + opentelemetry-instrumentation-action_mailer (0.6.1) + opentelemetry-instrumentation-active_support (~> 0.10) + opentelemetry-instrumentation-action_pack (0.15.1) + opentelemetry-instrumentation-rack (~> 0.29) + opentelemetry-instrumentation-action_view (0.11.1) + opentelemetry-instrumentation-active_support (~> 0.10) + opentelemetry-instrumentation-active_job (0.10.1) + opentelemetry-instrumentation-base (~> 0.25) + opentelemetry-instrumentation-active_model_serializers (0.24.0) opentelemetry-instrumentation-active_support (>= 0.7.0) - opentelemetry-instrumentation-base (~> 0.23.0) - opentelemetry-instrumentation-active_record (0.9.0) - opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.23.0) - opentelemetry-instrumentation-active_storage (0.1.1) - opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-active_support (~> 0.7) - opentelemetry-instrumentation-base (~> 0.23.0) - opentelemetry-instrumentation-active_support (0.8.0) - opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.23.0) - opentelemetry-instrumentation-base (0.23.0) - opentelemetry-api (~> 1.0) + opentelemetry-instrumentation-active_record (0.11.1) + opentelemetry-instrumentation-base (~> 0.25) + opentelemetry-instrumentation-active_storage (0.3.1) + opentelemetry-instrumentation-active_support (~> 0.10) + opentelemetry-instrumentation-active_support (0.10.1) + opentelemetry-instrumentation-base (~> 0.25) + opentelemetry-instrumentation-base (0.25.0) + opentelemetry-api (~> 1.7) opentelemetry-common (~> 0.21) opentelemetry-registry (~> 0.1) - opentelemetry-instrumentation-concurrent_ruby (0.22.0) - opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.23.0) - opentelemetry-instrumentation-excon (0.24.0) - opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.23.0) - opentelemetry-instrumentation-faraday (0.28.0) - opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.23.0) - opentelemetry-instrumentation-http (0.25.1) - opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.23.0) - opentelemetry-instrumentation-http_client (0.24.0) - opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.23.0) - opentelemetry-instrumentation-net_http (0.24.0) - opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.23.0) - opentelemetry-instrumentation-pg (0.30.1) - opentelemetry-api (~> 1.0) + opentelemetry-instrumentation-concurrent_ruby (0.24.0) + opentelemetry-instrumentation-base (~> 0.25) + opentelemetry-instrumentation-excon (0.26.1) + opentelemetry-instrumentation-base (~> 0.25) + opentelemetry-instrumentation-faraday (0.30.1) + opentelemetry-instrumentation-base (~> 0.25) + opentelemetry-instrumentation-http (0.27.1) + opentelemetry-instrumentation-base (~> 0.25) + opentelemetry-instrumentation-http_client (0.26.1) + opentelemetry-instrumentation-base (~> 0.25) + opentelemetry-instrumentation-net_http (0.26.1) + opentelemetry-instrumentation-base (~> 0.25) + opentelemetry-instrumentation-pg (0.34.1) opentelemetry-helpers-sql - opentelemetry-helpers-sql-obfuscation - opentelemetry-instrumentation-base (~> 0.23.0) - opentelemetry-instrumentation-rack (0.27.1) - opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.23.0) - opentelemetry-instrumentation-rails (0.37.0) - opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-action_mailer (~> 0.4.0) - opentelemetry-instrumentation-action_pack (~> 0.13.0) - opentelemetry-instrumentation-action_view (~> 0.9.0) - opentelemetry-instrumentation-active_job (~> 0.8.0) - opentelemetry-instrumentation-active_record (~> 0.9.0) - opentelemetry-instrumentation-active_storage (~> 0.1.0) - opentelemetry-instrumentation-active_support (~> 0.8.0) - opentelemetry-instrumentation-base (~> 0.23.0) - opentelemetry-instrumentation-concurrent_ruby (~> 0.22.0) - opentelemetry-instrumentation-redis (0.26.1) - opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.23.0) - opentelemetry-instrumentation-sidekiq (0.26.1) - opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.23.0) + opentelemetry-helpers-sql-processor + opentelemetry-instrumentation-base (~> 0.25) + opentelemetry-instrumentation-rack (0.29.0) + opentelemetry-instrumentation-base (~> 0.25) + opentelemetry-instrumentation-rails (0.39.1) + opentelemetry-instrumentation-action_mailer (~> 0.6) + opentelemetry-instrumentation-action_pack (~> 0.15) + opentelemetry-instrumentation-action_view (~> 0.11) + opentelemetry-instrumentation-active_job (~> 0.10) + opentelemetry-instrumentation-active_record (~> 0.11) + opentelemetry-instrumentation-active_storage (~> 0.3) + opentelemetry-instrumentation-active_support (~> 0.10) + opentelemetry-instrumentation-concurrent_ruby (~> 0.23) + opentelemetry-instrumentation-redis (0.28.0) + opentelemetry-instrumentation-base (~> 0.25) + opentelemetry-instrumentation-sidekiq (0.28.1) + opentelemetry-instrumentation-base (~> 0.25) opentelemetry-registry (0.4.0) opentelemetry-api (~> 1.1) - opentelemetry-sdk (1.9.0) + opentelemetry-sdk (1.10.0) opentelemetry-api (~> 1.1) opentelemetry-common (~> 0.20) opentelemetry-registry (~> 0.2) @@ -603,7 +585,7 @@ GEM ox (2.14.23) bigdecimal (>= 3.0) parallel (1.27.0) - parser (3.3.9.0) + parser (3.3.10.0) ast (~> 2.4.1) racc parslet (2.0.0) @@ -612,10 +594,10 @@ GEM pg (1.6.2) pghero (3.7.0) activerecord (>= 7.1) - playwright-ruby-client (1.55.0) + playwright-ruby-client (1.57.0) concurrent-ruby (>= 1.1.6) mime-types (>= 3.0) - pp (0.6.2) + pp (0.6.3) prettyprint premailer (1.27.0) addressable @@ -626,37 +608,37 @@ GEM net-smtp premailer (~> 1.7, >= 1.7.9) prettyprint (0.2.0) - prism (1.4.0) - prometheus_exporter (2.3.0) + prism (1.6.0) + prometheus_exporter (2.3.1) webrick - propshaft (1.2.1) + propshaft (1.3.1) actionpack (>= 7.0.0) activesupport (>= 7.0.0) rack - psych (5.2.6) + psych (5.3.0) date stringio - public_suffix (6.0.2) - puma (7.0.3) + public_suffix (7.0.0) + puma (7.1.0) nio4r (~> 2.0) - pundit (2.5.1) + pundit (2.5.2) activesupport (>= 3.0.0) raabro (1.4.0) racc (1.8.1) - rack (3.1.16) - rack-attack (6.7.0) + rack (3.2.4) + rack-attack (6.8.0) rack (>= 1.0, < 4) rack-cors (3.0.0) logger rack (>= 3.0.14) - rack-oauth2 (2.2.1) + rack-oauth2 (2.3.0) activesupport attr_required faraday (~> 2.0) faraday-follow_redirects json-jwt (>= 1.11.0) rack (>= 2.1.0) - rack-protection (4.1.1) + rack-protection (4.2.1) base64 (>= 0.1.0) logger (>= 1.6.0) rack (>= 3.0.0, < 4) @@ -667,22 +649,22 @@ GEM rack (>= 3.0.0) rack-test (2.2.0) rack (>= 1.3) - rackup (2.2.1) + rackup (2.3.1) rack (>= 3) - rails (8.0.2.1) - actioncable (= 8.0.2.1) - actionmailbox (= 8.0.2.1) - actionmailer (= 8.0.2.1) - actionpack (= 8.0.2.1) - actiontext (= 8.0.2.1) - actionview (= 8.0.2.1) - activejob (= 8.0.2.1) - activemodel (= 8.0.2.1) - activerecord (= 8.0.2.1) - activestorage (= 8.0.2.1) - activesupport (= 8.0.2.1) + rails (8.0.3) + actioncable (= 8.0.3) + actionmailbox (= 8.0.3) + actionmailer (= 8.0.3) + actionpack (= 8.0.3) + actiontext (= 8.0.3) + actionview (= 8.0.3) + activejob (= 8.0.3) + activemodel (= 8.0.3) + activerecord (= 8.0.3) + activestorage (= 8.0.3) + activesupport (= 8.0.3) bundler (>= 1.15.0) - railties (= 8.0.2.1) + railties (= 8.0.3) rails-dom-testing (2.3.0) activesupport (>= 5.0.0) minitest @@ -690,19 +672,20 @@ GEM rails-html-sanitizer (1.6.2) loofah (~> 2.21) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) - rails-i18n (8.0.2) + rails-i18n (8.1.0) i18n (>= 0.7, < 2) railties (>= 8.0.0, < 9) - railties (8.0.2.1) - actionpack (= 8.0.2.1) - activesupport (= 8.0.2.1) + railties (8.0.3) + actionpack (= 8.0.3) + activesupport (= 8.0.3) irb (~> 1.13) rackup (>= 1.0.0) rake (>= 12.2) thor (~> 1.0, >= 1.2.2) + tsort (>= 0.2) zeitwerk (~> 2.6) rainbow (3.1.1) - rake (13.3.0) + rake (13.3.1) rdf (3.3.4) bcp47_spec (~> 0.2) bigdecimal (~> 3.1, >= 3.1.5) @@ -712,43 +695,44 @@ GEM readline (~> 0.0) rdf-normalize (0.7.0) rdf (~> 3.3) - rdoc (6.14.2) + rdoc (6.17.0) erb psych (>= 4.0.0) + tsort readline (0.0.4) reline redcarpet (3.6.1) redis (4.8.1) - redis-client (0.25.3) + redis-client (0.26.2) connection_pool - regexp_parser (2.11.2) - reline (0.6.2) + regexp_parser (2.11.3) + reline (0.6.3) io-console (~> 0.5) request_store (1.7.0) rack (>= 1.4) - responders (3.1.1) - actionpack (>= 5.2) - railties (>= 5.2) + responders (3.2.0) + actionpack (>= 7.0) + railties (>= 7.0) rexml (3.4.4) rotp (6.3.0) - rouge (4.6.0) + rouge (4.6.1) rpam2 (4.0.2) - rqrcode (3.1.0) + rqrcode (3.1.1) chunky_png (~> 1.0) rqrcode_core (~> 2.0) - rqrcode_core (2.0.0) - rspec (3.13.1) + rqrcode_core (2.0.1) + rspec (3.13.2) rspec-core (~> 3.13.0) rspec-expectations (~> 3.13.0) rspec-mocks (~> 3.13.0) - rspec-core (3.13.5) + rspec-core (3.13.6) rspec-support (~> 3.13.0) rspec-expectations (3.13.5) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-github (3.0.0) rspec-core (~> 3.0) - rspec-mocks (3.13.5) + rspec-mocks (3.13.7) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-rails (8.0.2) @@ -764,8 +748,8 @@ GEM rspec-expectations (~> 3.0) rspec-mocks (~> 3.0) sidekiq (>= 5, < 9) - rspec-support (3.13.4) - rubocop (1.80.2) + rspec-support (3.13.6) + rubocop (1.81.7) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) @@ -773,10 +757,10 @@ GEM parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.46.0, < 2.0) + rubocop-ast (>= 1.47.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.46.0) + rubocop-ast (1.48.0) parser (>= 3.3.7.2) prism (~> 1.4) rubocop-capybara (2.22.1) @@ -785,20 +769,20 @@ GEM rubocop-i18n (3.2.3) lint_roller (~> 1.1) rubocop (>= 1.72.1) - rubocop-performance (1.26.0) + rubocop-performance (1.26.1) lint_roller (~> 1.1) rubocop (>= 1.75.0, < 2.0) - rubocop-ast (>= 1.44.0, < 2.0) - rubocop-rails (2.33.3) + rubocop-ast (>= 1.47.1, < 2.0) + rubocop-rails (2.34.2) activesupport (>= 4.2.0) lint_roller (~> 1.1) rack (>= 1.1) rubocop (>= 1.75.0, < 2.0) rubocop-ast (>= 1.44.0, < 2.0) - rubocop-rspec (3.7.0) + rubocop-rspec (3.8.0) lint_roller (~> 1.1) - rubocop (~> 1.72, >= 1.72.1) - rubocop-rspec_rails (2.31.0) + rubocop (~> 1.81) + rubocop-rspec_rails (2.32.0) lint_roller (~> 1.1) rubocop (~> 1.72, >= 1.72.1) rubocop-rspec (~> 3.5) @@ -808,14 +792,14 @@ GEM ruby-saml (1.18.1) nokogiri (>= 1.13.10) rexml - ruby-vips (2.2.5) + ruby-vips (2.3.0) ffi (~> 1.12) logger - rubyzip (3.1.0) + rubyzip (3.2.2) rufus-scheduler (3.9.2) fugit (~> 1.1, >= 1.11.1) - safety_net_attestation (0.4.0) - jwt (~> 2.0) + safety_net_attestation (0.5.0) + jwt (>= 2.0, < 4.0) sanitize (7.0.0) crass (~> 1.0.2) nokogiri (>= 1.16.8) @@ -823,9 +807,9 @@ GEM activerecord (>= 4.0.0) railties (>= 4.0.0) securerandom (0.4.1) - shoulda-matchers (6.5.0) - activesupport (>= 5.2.0) - sidekiq (8.0.7) + shoulda-matchers (7.0.1) + activesupport (>= 7.1) + sidekiq (8.0.10) connection_pool (>= 2.5.0) json (>= 2.9.0) logger (>= 1.6.2) @@ -836,15 +820,15 @@ GEM sidekiq-scheduler (6.0.1) rufus-scheduler (~> 3.2) sidekiq (>= 7.3, < 9) - sidekiq-unique-jobs (8.0.11) + sidekiq-unique-jobs (8.0.12) concurrent-ruby (~> 1.0, >= 1.0.5) sidekiq (>= 7.0.0, < 9.0.0) thor (>= 1.0, < 3.0) simple-navigation (4.4.0) activesupport (>= 2.3.2) - simple_form (5.3.1) - actionpack (>= 5.2) - activemodel (>= 5.2) + simple_form (5.4.0) + actionpack (>= 7.0) + activemodel (>= 7.0) simplecov (0.22.0) docile (~> 1.1) simplecov-html (~> 0.11) @@ -855,10 +839,11 @@ GEM stackprof (0.2.27) starry (0.2.0) base64 - stoplight (5.3.8) + stoplight (5.7.0) + concurrent-ruby zeitwerk - stringio (3.1.7) - strong_migrations (2.5.0) + stringio (3.1.9) + strong_migrations (2.5.1) activerecord (>= 7.1) swd (2.0.3) activesupport (>= 3) @@ -871,14 +856,15 @@ GEM unicode-display_width (>= 1.1.1, < 4) terrapin (1.1.1) climate_control - test-prof (1.4.4) + test-prof (1.5.0) thor (1.4.0) tilt (2.6.1) - timeout (0.4.3) + timeout (0.5.0) tpm-key_attestation (0.14.1) bindata (~> 2.4) openssl (> 2.0) openssl-signature_algorithm (~> 1.0) + tsort (0.2.0) tty-color (0.6.0) tty-cursor (0.7.1) tty-prompt (0.23.1) @@ -894,15 +880,15 @@ GEM unf (~> 0.1.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - tzinfo-data (1.2025.2) + tzinfo-data (1.2025.3) tzinfo (>= 1.0.0) unf (0.1.4) unf_ext unf_ext (0.0.9.1) - unicode-display_width (3.1.5) - unicode-emoji (~> 4.0, >= 4.0.4) - unicode-emoji (4.0.4) - uri (1.0.3) + unicode-display_width (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.1.0) + uri (1.1.1) useragent (0.16.11) validate_url (1.0.15) activemodel (>= 3.0.0) @@ -918,23 +904,23 @@ GEM zeitwerk (~> 2.2) warden (1.2.9) rack (>= 2.0.9) - webauthn (3.4.1) + webauthn (3.4.3) android_key_attestation (~> 0.3.0) bindata (~> 2.4) cbor (~> 0.5.9) cose (~> 1.1) openssl (>= 2.2) - safety_net_attestation (~> 0.4.0) + safety_net_attestation (~> 0.5.0) tpm-key_attestation (~> 0.14.0) webfinger (2.1.3) activesupport faraday (~> 2.0) faraday-follow_redirects - webmock (3.25.1) + webmock (3.26.1) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - webrick (1.9.1) + webrick (1.9.2) websocket-driver (0.8.0) base64 websocket-extensions (>= 0.1.0) @@ -952,12 +938,12 @@ DEPENDENCIES active_model_serializers (~> 0.10) addressable (~> 2.8) annotaterb (~> 4.13) - aws-sdk-core (< 3.216.0) + aws-sdk-core aws-sdk-s3 (~> 1.123) better_errors (~> 2.9) binding_of_caller (~> 1.0) blurhash (~> 0.1) - bootsnap (~> 1.18.0) + bootsnap (~> 1.19.0) brakeman (~> 7.0) browser bundler-audit (~> 0.9) @@ -988,7 +974,7 @@ DEPENDENCIES flatware-rspec fog-core (<= 2.6.0) fog-openstack (~> 1.0) - haml-rails (~> 2.0) + haml-rails (~> 3.0) haml_lint hcaptcha (~> 7.1) hiredis (~> 0.6) @@ -1024,34 +1010,34 @@ DEPENDENCIES oj (~> 3.14) omniauth (~> 2.0) omniauth-cas (~> 3.0.0.beta.1) - omniauth-rails_csrf_protection (~> 1.0) + omniauth-rails_csrf_protection (~> 2.0) omniauth-saml (~> 2.0) omniauth_openid_connect (~> 0.8.0) opentelemetry-api (~> 1.7.0) - opentelemetry-exporter-otlp (~> 0.30.0) - opentelemetry-instrumentation-active_job (~> 0.8.0) - opentelemetry-instrumentation-active_model_serializers (~> 0.22.0) - opentelemetry-instrumentation-concurrent_ruby (~> 0.22.0) - opentelemetry-instrumentation-excon (~> 0.24.0) - opentelemetry-instrumentation-faraday (~> 0.28.0) - opentelemetry-instrumentation-http (~> 0.25.0) - opentelemetry-instrumentation-http_client (~> 0.24.0) - opentelemetry-instrumentation-net_http (~> 0.24.0) - opentelemetry-instrumentation-pg (~> 0.30.0) - opentelemetry-instrumentation-rack (~> 0.27.0) - opentelemetry-instrumentation-rails (~> 0.37.0) - opentelemetry-instrumentation-redis (~> 0.26.0) - opentelemetry-instrumentation-sidekiq (~> 0.26.0) + opentelemetry-exporter-otlp (~> 0.31.0) + opentelemetry-instrumentation-active_job (~> 0.10.0) + opentelemetry-instrumentation-active_model_serializers (~> 0.24.0) + opentelemetry-instrumentation-concurrent_ruby (~> 0.24.0) + opentelemetry-instrumentation-excon (~> 0.26.0) + opentelemetry-instrumentation-faraday (~> 0.30.0) + opentelemetry-instrumentation-http (~> 0.27.0) + opentelemetry-instrumentation-http_client (~> 0.26.0) + opentelemetry-instrumentation-net_http (~> 0.26.0) + opentelemetry-instrumentation-pg (~> 0.34.0) + opentelemetry-instrumentation-rack (~> 0.29.0) + opentelemetry-instrumentation-rails (~> 0.39.0) + opentelemetry-instrumentation-redis (~> 0.28.0) + opentelemetry-instrumentation-sidekiq (~> 0.28.0) opentelemetry-sdk (~> 1.4) ox (~> 2.14) parslet pg (~> 1.5) pghero - playwright-ruby-client (= 1.55.0) + playwright-ruby-client (= 1.57.0) premailer-rails prometheus_exporter (~> 2.2) propshaft - public_suffix (~> 6.0) + public_suffix (~> 7.0) puma (~> 7.0) pundit (~> 2.3) rack-attack (~> 6.6) @@ -1100,10 +1086,11 @@ DEPENDENCIES webauthn (~> 3.0) webmock (~> 3.18) webpush! + websocket-driver (~> 0.8) xorcist (~> 1.1) RUBY VERSION - ruby 3.4.1p0 + ruby 3.4.1p0 BUNDLED WITH - 2.7.1 + 4.0.2 diff --git a/README.md b/README.md index ba800f59d5aed8..9cb0d3e61f8cee 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ Mastodon is a **free, open-source social network server** based on [ActivityPub] ### Requirements - **Ruby** 3.2+ -- **PostgreSQL** 13+ +- **PostgreSQL** 14+ - **Redis** 7.0+ - **Node.js** 20+ diff --git a/SECURITY.md b/SECURITY.md index 19f431fac5948e..12052652e6ca89 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -15,7 +15,8 @@ A "vulnerability in Mastodon" is a vulnerability in the code distributed through | Version | Supported | | ------- | ---------------- | +| 4.5.x | Yes | | 4.4.x | Yes | -| 4.3.x | Yes | +| 4.3.x | Until 2026-05-06 | | 4.2.x | Until 2026-01-08 | | < 4.2 | No | diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 685b02ae6d99b1..990d08ca7f478a 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -71,6 +71,10 @@ def username_param params[:username] end + def account_id_param + params[:id] + end + def skip_temporary_suspension_response? request.format == :json end diff --git a/app/controllers/activitypub/inboxes_controller.rb b/app/controllers/activitypub/inboxes_controller.rb index 49cfc8ad1cbd8c..1f7abb97fa5750 100644 --- a/app/controllers/activitypub/inboxes_controller.rb +++ b/app/controllers/activitypub/inboxes_controller.rb @@ -39,7 +39,7 @@ def body return @body if defined?(@body) @body = request.body.read - @body.force_encoding('UTF-8') if @body.present? + @body.presence&.force_encoding('UTF-8') request.body.rewind if request.body.respond_to?(:rewind) diff --git a/app/controllers/activitypub/likes_controller.rb b/app/controllers/activitypub/likes_controller.rb index 4aa6a4a771f156..4dcddb88e4bfa1 100644 --- a/app/controllers/activitypub/likes_controller.rb +++ b/app/controllers/activitypub/likes_controller.rb @@ -22,13 +22,13 @@ def pundit_user def set_status @status = @account.statuses.find(params[:status_id]) authorize @status, :show? - rescue Mastodon::NotPermittedError + rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError not_found end def likes_collection_presenter ActivityPub::CollectionPresenter.new( - id: account_status_likes_url(@account, @status), + id: ActivityPub::TagManager.instance.likes_uri_for(@status), type: :unordered, size: @status.favourites_count ) diff --git a/app/controllers/activitypub/outboxes_controller.rb b/app/controllers/activitypub/outboxes_controller.rb index a9476b806f54e1..928977768b9ed6 100644 --- a/app/controllers/activitypub/outboxes_controller.rb +++ b/app/controllers/activitypub/outboxes_controller.rb @@ -73,6 +73,8 @@ def page_params end def set_account - @account = params[:account_username].present? ? Account.find_local!(username_param) : Account.representative + return super if params[:account_username].present? || params[:account_id].present? + + @account = Account.representative end end diff --git a/app/controllers/activitypub/quote_authorizations_controller.rb b/app/controllers/activitypub/quote_authorizations_controller.rb index f2f5313e1ad3c6..ff4a76df34ced2 100644 --- a/app/controllers/activitypub/quote_authorizations_controller.rb +++ b/app/controllers/activitypub/quote_authorizations_controller.rb @@ -9,7 +9,7 @@ class ActivityPub::QuoteAuthorizationsController < ActivityPub::BaseController before_action :set_quote_authorization def show - expires_in 30.seconds, public: true if @quote.status.distributable? && public_fetch_mode? + expires_in 30.seconds, public: true if @quote.quoted_status.distributable? && public_fetch_mode? render json: @quote, serializer: ActivityPub::QuoteAuthorizationSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json' end @@ -23,8 +23,8 @@ def set_quote_authorization @quote = Quote.accepted.where(quoted_account: @account).find(params[:id]) return not_found unless @quote.status.present? && @quote.quoted_status.present? - authorize @quote.status, :show? - rescue Mastodon::NotPermittedError + authorize @quote.quoted_status, :show? + rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError not_found end end diff --git a/app/controllers/activitypub/replies_controller.rb b/app/controllers/activitypub/replies_controller.rb index 0a19275d38e942..a857ba03faf6c3 100644 --- a/app/controllers/activitypub/replies_controller.rb +++ b/app/controllers/activitypub/replies_controller.rb @@ -25,7 +25,7 @@ def pundit_user def set_status @status = @account.statuses.find(params[:status_id]) authorize @status, :show? - rescue Mastodon::NotPermittedError + rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError not_found end @@ -37,7 +37,7 @@ def set_replies def replies_collection_presenter page = ActivityPub::CollectionPresenter.new( - id: account_status_replies_url(@account, @status, page_params), + id: ActivityPub::TagManager.instance.replies_uri_for(@status, page_params), type: :unordered, part_of: account_status_replies_url(@account, @status), next: next_page, @@ -47,7 +47,7 @@ def replies_collection_presenter return page if page_requested? ActivityPub::CollectionPresenter.new( - id: account_status_replies_url(@account, @status), + id: ActivityPub::TagManager.instance.replies_uri_for(@status), type: :unordered, first: page ) @@ -66,8 +66,7 @@ def next_page # Only consider remote accounts return nil if @replies.size < DESCENDANTS_LIMIT - account_status_replies_url( - @account, + ActivityPub::TagManager.instance.replies_uri_for( @status, page: true, min_id: @replies&.last&.id, @@ -77,8 +76,7 @@ def next_page # For now, we're serving only self-replies, but next page might be other accounts next_only_other_accounts = @replies&.last&.account_id != @account.id || @replies.size < DESCENDANTS_LIMIT - account_status_replies_url( - @account, + ActivityPub::TagManager.instance.replies_uri_for( @status, page: true, min_id: next_only_other_accounts ? nil : @replies&.last&.id, diff --git a/app/controllers/activitypub/shares_controller.rb b/app/controllers/activitypub/shares_controller.rb index 65b4a5b3831326..3733dfbd6f3fa6 100644 --- a/app/controllers/activitypub/shares_controller.rb +++ b/app/controllers/activitypub/shares_controller.rb @@ -22,13 +22,13 @@ def pundit_user def set_status @status = @account.statuses.find(params[:status_id]) authorize @status, :show? - rescue Mastodon::NotPermittedError + rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError not_found end def shares_collection_presenter ActivityPub::CollectionPresenter.new( - id: account_status_shares_url(@account, @status), + id: ActivityPub::TagManager.instance.shares_uri_for(@status), type: :unordered, size: @status.reblogs_count ) diff --git a/app/controllers/admin/custom_emojis_controller.rb b/app/controllers/admin/custom_emojis_controller.rb index fbef61810d42c6..55a03b9804cfaf 100644 --- a/app/controllers/admin/custom_emojis_controller.rb +++ b/app/controllers/admin/custom_emojis_controller.rb @@ -5,6 +5,15 @@ class CustomEmojisController < BaseController def index authorize :custom_emoji, :index? + # If filtering by local emojis, remove by_domain filter. + params.delete(:by_domain) if params[:local].present? + + # If filtering by domain, ensure remote filter is set. + if params[:by_domain].present? + params.delete(:local) + params[:remote] = '1' + end + @custom_emojis = filtered_custom_emojis.eager_load(:local_counterpart).page(params[:page]) @form = Form::CustomEmojiBatch.new end diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb index 5b0867dcfbac2f..fe314daeca69f6 100644 --- a/app/controllers/admin/dashboard_controller.rb +++ b/app/controllers/admin/dashboard_controller.rb @@ -9,10 +9,16 @@ def index @pending_appeals_count = Appeal.pending.async_count @pending_reports_count = Report.unresolved.async_count - @pending_tags_count = Tag.pending_review.async_count + @pending_tags_count = pending_tags.async_count @pending_users_count = User.pending.async_count @system_checks = Admin::SystemCheck.perform(current_user) @time_period = (29.days.ago.to_date...Time.now.utc.to_date) end + + private + + def pending_tags + ::Trends::TagFilter.new(status: :pending_review).results + end end end diff --git a/app/controllers/admin/site_uploads_controller.rb b/app/controllers/admin/site_uploads_controller.rb index 96e61cf6bbc194..e30c783a493dfd 100644 --- a/app/controllers/admin/site_uploads_controller.rb +++ b/app/controllers/admin/site_uploads_controller.rb @@ -9,7 +9,7 @@ def destroy @site_upload.destroy! - redirect_back fallback_location: admin_settings_path, notice: I18n.t('admin.site_uploads.destroyed_msg') + redirect_back_or_to admin_settings_path, notice: I18n.t('admin.site_uploads.destroyed_msg') end private diff --git a/app/controllers/api/v1/annual_reports_controller.rb b/app/controllers/api/v1/annual_reports_controller.rb index b1aee288dd8595..71a97e1d9a68b2 100644 --- a/app/controllers/api/v1/annual_reports_controller.rb +++ b/app/controllers/api/v1/annual_reports_controller.rb @@ -1,10 +1,12 @@ # frozen_string_literal: true class Api::V1::AnnualReportsController < Api::BaseController - before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, only: :index - before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, except: :index + include AsyncRefreshesConcern + + before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, except: [:read, :generate] + before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, only: [:read, :generate] before_action :require_user! - before_action :set_annual_report, except: :index + before_action :set_annual_report, only: [:show, :read] def index with_read_replica do @@ -28,6 +30,28 @@ def show relationships: @relationships end + def state + render json: { state: report_state } + end + + def generate + return render_empty unless year == AnnualReport.current_campaign + return render_empty if GeneratedAnnualReport.exists?(account_id: current_account.id, year: year) + + async_refresh = AsyncRefresh.new(refresh_key) + + if async_refresh.running? + add_async_refresh_header(async_refresh, retry_seconds: 2) + return head 202 + end + + add_async_refresh_header(AsyncRefresh.create(refresh_key), retry_seconds: 2) + + GenerateAnnualReportWorker.perform_async(current_account.id, year) + + head 202 + end + def read @annual_report.view! render_empty @@ -35,7 +59,21 @@ def read private + def report_state + AnnualReport.new(current_account, year).state do |async_refresh| + add_async_refresh_header(async_refresh, retry_seconds: 2) + end + end + + def refresh_key + "wrapstodon:#{current_account.id}:#{year}" + end + + def year + params[:id]&.to_i + end + def set_annual_report - @annual_report = GeneratedAnnualReport.find_by!(account_id: current_account.id, year: params[:id]) + @annual_report = GeneratedAnnualReport.find_by!(account_id: current_account.id, year: year) end end diff --git a/app/controllers/api/v1/polls/votes_controller.rb b/app/controllers/api/v1/polls/votes_controller.rb index 2833687a38cb1f..659e52bac48fba 100644 --- a/app/controllers/api/v1/polls/votes_controller.rb +++ b/app/controllers/api/v1/polls/votes_controller.rb @@ -17,7 +17,7 @@ def create def set_poll @poll = Poll.find(params[:poll_id]) authorize @poll.status, :show? - rescue Mastodon::NotPermittedError + rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError not_found end diff --git a/app/controllers/api/v1/polls_controller.rb b/app/controllers/api/v1/polls_controller.rb index b4c25476e8544b..bf30c178571e93 100644 --- a/app/controllers/api/v1/polls_controller.rb +++ b/app/controllers/api/v1/polls_controller.rb @@ -17,7 +17,7 @@ def show def set_poll @poll = Poll.find(params[:id]) authorize @poll.status, :show? - rescue Mastodon::NotPermittedError + rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError not_found end diff --git a/app/controllers/api/v1/statuses/base_controller.rb b/app/controllers/api/v1/statuses/base_controller.rb index 3f56b68bcf41f1..0c4c49a2c3ff26 100644 --- a/app/controllers/api/v1/statuses/base_controller.rb +++ b/app/controllers/api/v1/statuses/base_controller.rb @@ -10,7 +10,7 @@ class Api::V1::Statuses::BaseController < Api::BaseController def set_status @status = Status.find(params[:status_id]) authorize @status, :show? - rescue Mastodon::NotPermittedError + rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError not_found end end diff --git a/app/controllers/api/v1/statuses/bookmarks_controller.rb b/app/controllers/api/v1/statuses/bookmarks_controller.rb index 109b12f467efe0..b4b976ac3c5ce6 100644 --- a/app/controllers/api/v1/statuses/bookmarks_controller.rb +++ b/app/controllers/api/v1/statuses/bookmarks_controller.rb @@ -23,7 +23,7 @@ def destroy bookmark&.destroy! render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_account.id, bookmarks_map: { @status.id => false }) - rescue Mastodon::NotPermittedError + rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError not_found end end diff --git a/app/controllers/api/v1/statuses/favourites_controller.rb b/app/controllers/api/v1/statuses/favourites_controller.rb index dbc75a03644dbb..17eeccdbe749f0 100644 --- a/app/controllers/api/v1/statuses/favourites_controller.rb +++ b/app/controllers/api/v1/statuses/favourites_controller.rb @@ -25,7 +25,7 @@ def destroy relationships = StatusRelationshipsPresenter.new([@status], current_account.id, favourites_map: { @status.id => false }, attributes_map: { @status.id => { favourites_count: count } }) render json: @status, serializer: REST::StatusSerializer, relationships: relationships - rescue Mastodon::NotPermittedError + rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError not_found end end diff --git a/app/controllers/api/v1/statuses/interaction_policies_controller.rb b/app/controllers/api/v1/statuses/interaction_policies_controller.rb index 6e2745806d8d1b..5cfb2d0e8fd185 100644 --- a/app/controllers/api/v1/statuses/interaction_policies_controller.rb +++ b/app/controllers/api/v1/statuses/interaction_policies_controller.rb @@ -22,7 +22,7 @@ def status_params end def broadcast_updates! - DistributionWorker.perform_async(@status.id, { 'update' => true }) + DistributionWorker.perform_async(@status.id, { 'update' => true, 'skip_notifications' => true }) ActivityPub::StatusUpdateDistributionWorker.perform_async(@status.id, { 'updated_at' => Time.now.utc.iso8601 }) end end diff --git a/app/controllers/api/v1/statuses/quotes_controller.rb b/app/controllers/api/v1/statuses/quotes_controller.rb index 962855884ec87c..d851e55c293fef 100644 --- a/app/controllers/api/v1/statuses/quotes_controller.rb +++ b/app/controllers/api/v1/statuses/quotes_controller.rb @@ -4,13 +4,13 @@ class Api::V1::Statuses::QuotesController < Api::V1::Statuses::BaseController before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, only: :index before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: :revoke - before_action :check_owner! + before_action :set_statuses, only: :index + before_action :set_quote, only: :revoke after_action :insert_pagination_headers, only: :index def index cache_if_unauthenticated! - @statuses = load_statuses render json: @statuses, each_serializer: REST::StatusSerializer end @@ -24,18 +24,26 @@ def revoke private - def check_owner! - authorize @status, :list_quotes? - end - def set_quote @quote = @status.quotes.find_by!(status_id: params[:id]) end - def load_statuses + def set_statuses scope = default_statuses scope = scope.not_excluded_by_account(current_account) unless current_account.nil? - scope.merge(paginated_quotes).to_a + @statuses = scope.merge(paginated_quotes).to_a + + # Store next page info before filtering + @records_continue = @statuses.size == limit_param(DEFAULT_STATUSES_LIMIT) + @pagination_since_id = @statuses.first.quote.id unless @statuses.empty? + @pagination_max_id = @statuses.last.quote.id if @records_continue + + if current_account&.id != @status.account_id + domains = @statuses.filter_map(&:account_domain).uniq + account_ids = @statuses.map(&:account_id).uniq + current_account&.preload_relations!(account_ids, domains) + @statuses.reject! { |status| StatusFilter.new(status, current_account).filtered? } + end end def default_statuses @@ -58,15 +66,9 @@ def prev_path api_v1_status_quotes_url pagination_params(since_id: pagination_since_id) unless @statuses.empty? end - def pagination_max_id - @statuses.last.quote.id - end - - def pagination_since_id - @statuses.first.quote.id - end + attr_reader :pagination_max_id, :pagination_since_id def records_continue? - @statuses.size == limit_param(DEFAULT_STATUSES_LIMIT) + @records_continue end end diff --git a/app/controllers/api/v1/statuses/reblogs_controller.rb b/app/controllers/api/v1/statuses/reblogs_controller.rb index 971b054c548f19..6a5788fca3015d 100644 --- a/app/controllers/api/v1/statuses/reblogs_controller.rb +++ b/app/controllers/api/v1/statuses/reblogs_controller.rb @@ -36,7 +36,7 @@ def destroy relationships = StatusRelationshipsPresenter.new([@status], current_account.id, reblogs_map: { @reblog.id => false }, attributes_map: { @reblog.id => { reblogs_count: count } }) render json: @reblog, serializer: REST::StatusSerializer, relationships: relationships - rescue Mastodon::NotPermittedError + rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError not_found end @@ -45,7 +45,7 @@ def destroy def set_reblog @reblog = Status.find(params[:status_id]) authorize @reblog, :show? - rescue Mastodon::NotPermittedError + rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError not_found end diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index 6619899041fa95..2fd1555bcd4099 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -66,7 +66,7 @@ def context if async_refresh.running? add_async_refresh_header(async_refresh) elsif !current_account.nil? && @status.should_fetch_replies? - add_async_refresh_header(AsyncRefresh.create(refresh_key)) + add_async_refresh_header(AsyncRefresh.create(refresh_key, count_results: true)) WorkerBatch.new.within do |batch| batch.connect(refresh_key, threshold: 1.0) @@ -128,10 +128,11 @@ def destroy @status = Status.where(account: current_account).find(params[:id]) authorize @status, :destroy? + json = render_to_body json: @status, serializer: REST::StatusSerializer, source_requested: true + @status.discard_with_reblogs StatusPin.find_by(status: @status)&.destroy @status.account.statuses_count = @status.account.statuses_count - 1 - json = render_to_body json: @status, serializer: REST::StatusSerializer, source_requested: true RemovalWorker.perform_async(@status.id, { 'redraft' => !truthy_param?(:delete_media) }) @@ -147,7 +148,7 @@ def set_statuses def set_status @status = Status.find(params[:id]) authorize @status, :show? - rescue Mastodon::NotPermittedError + rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError not_found end @@ -159,7 +160,7 @@ def set_thread end def set_quoted_status - @quoted_status = Status.find(status_params[:quoted_status_id]) if status_params[:quoted_status_id].present? + @quoted_status = Status.find(status_params[:quoted_status_id])&.proper if status_params[:quoted_status_id].present? authorize(@quoted_status, :quote?) if @quoted_status.present? rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError # TODO: distinguish between non-existing and non-quotable posts diff --git a/app/controllers/api/v1/timelines/base_controller.rb b/app/controllers/api/v1/timelines/base_controller.rb index 1dba4a5bb21d58..e79eba79ee575d 100644 --- a/app/controllers/api/v1/timelines/base_controller.rb +++ b/app/controllers/api/v1/timelines/base_controller.rb @@ -3,14 +3,8 @@ class Api::V1::Timelines::BaseController < Api::BaseController after_action :insert_pagination_headers, unless: -> { @statuses.empty? } - before_action :require_user!, if: :require_auth? - private - def require_auth? - !Setting.timeline_preview - end - def pagination_collection @statuses end diff --git a/app/controllers/api/v1/timelines/home_controller.rb b/app/controllers/api/v1/timelines/home_controller.rb index b8384a13687d73..a07faae7208ab2 100644 --- a/app/controllers/api/v1/timelines/home_controller.rb +++ b/app/controllers/api/v1/timelines/home_controller.rb @@ -3,8 +3,8 @@ class Api::V1::Timelines::HomeController < Api::V1::Timelines::BaseController include AsyncRefreshesConcern - before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, only: [:show] - before_action :require_user!, only: [:show] + before_action -> { doorkeeper_authorize! :read, :'read:statuses' } + before_action :require_user! PERMITTED_PARAMS = %i(local limit).freeze diff --git a/app/controllers/api/v1/timelines/link_controller.rb b/app/controllers/api/v1/timelines/link_controller.rb index 37ed084f0626ad..9e6ddd69243701 100644 --- a/app/controllers/api/v1/timelines/link_controller.rb +++ b/app/controllers/api/v1/timelines/link_controller.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Api::V1::Timelines::LinkController < Api::V1::Timelines::BaseController +class Api::V1::Timelines::LinkController < Api::V1::Timelines::TopicController before_action -> { authorize_if_got_token! :read, :'read:statuses' } before_action :set_preview_card before_action :set_statuses diff --git a/app/controllers/api/v1/timelines/public_controller.rb b/app/controllers/api/v1/timelines/public_controller.rb index cd5445617be0ec..7110972dea4310 100644 --- a/app/controllers/api/v1/timelines/public_controller.rb +++ b/app/controllers/api/v1/timelines/public_controller.rb @@ -2,6 +2,7 @@ class Api::V1::Timelines::PublicController < Api::V1::Timelines::BaseController before_action -> { authorize_if_got_token! :read, :'read:statuses' } + before_action :require_user!, if: :require_auth? PERMITTED_PARAMS = %i(local remote limit only_media allow_local_only).freeze @@ -13,6 +14,16 @@ def show private + def require_auth? + if truthy_param?(:local) + Setting.local_live_feed_access != 'public' + elsif truthy_param?(:remote) + Setting.remote_live_feed_access != 'public' + else + Setting.local_live_feed_access != 'public' || Setting.remote_live_feed_access != 'public' + end + end + def load_statuses preloaded_public_statuses_page end diff --git a/app/controllers/api/v1/timelines/tag_controller.rb b/app/controllers/api/v1/timelines/tag_controller.rb index 2b097aab0f85b8..dc3c6a72157ba7 100644 --- a/app/controllers/api/v1/timelines/tag_controller.rb +++ b/app/controllers/api/v1/timelines/tag_controller.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Api::V1::Timelines::TagController < Api::V1::Timelines::BaseController +class Api::V1::Timelines::TagController < Api::V1::Timelines::TopicController before_action -> { authorize_if_got_token! :read, :'read:statuses' } before_action :load_tag @@ -14,10 +14,6 @@ def show private - def require_auth? - !Setting.timeline_preview - end - def load_tag @tag = Tag.find_normalized(params[:id]) end diff --git a/app/controllers/api/v1/timelines/topic_controller.rb b/app/controllers/api/v1/timelines/topic_controller.rb new file mode 100644 index 00000000000000..6faf54f708311f --- /dev/null +++ b/app/controllers/api/v1/timelines/topic_controller.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class Api::V1::Timelines::TopicController < Api::V1::Timelines::BaseController + before_action :require_user!, if: :require_auth? + + private + + def require_auth? + if truthy_param?(:local) + Setting.local_topic_feed_access != 'public' + elsif truthy_param?(:remote) + Setting.remote_topic_feed_access != 'public' + else + Setting.local_topic_feed_access != 'public' || Setting.remote_topic_feed_access != 'public' + end + end +end diff --git a/app/controllers/api/v1_alpha/collection_items_controller.rb b/app/controllers/api/v1_alpha/collection_items_controller.rb new file mode 100644 index 00000000000000..21699a5b6f2676 --- /dev/null +++ b/app/controllers/api/v1_alpha/collection_items_controller.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +class Api::V1Alpha::CollectionItemsController < Api::BaseController + include Authorization + + before_action :check_feature_enabled + + before_action -> { doorkeeper_authorize! :write, :'write:collections' } + + before_action :require_user! + + before_action :set_collection + before_action :set_account, only: [:create] + before_action :set_collection_item, only: [:destroy] + + after_action :verify_authorized + + def create + authorize @collection, :update? + authorize @account, :feature? + + @item = AddAccountToCollectionService.new.call(@collection, @account) + + render json: @item, serializer: REST::CollectionItemSerializer + end + + def destroy + authorize @collection, :update? + + @collection_item.destroy + + head 200 + end + + private + + def set_collection + @collection = Collection.find(params[:collection_id]) + end + + def set_account + return render(json: { error: '`account_id` parameter is missing' }, status: 422) if params[:account_id].blank? + + @account = Account.find(params[:account_id]) + end + + def set_collection_item + @collection_item = @collection.collection_items.find(params[:id]) + end + + def check_feature_enabled + raise ActionController::RoutingError unless Mastodon::Feature.collections_enabled? + end +end diff --git a/app/controllers/api/v1_alpha/collections_controller.rb b/app/controllers/api/v1_alpha/collections_controller.rb new file mode 100644 index 00000000000000..d0c4e0f3f04c6d --- /dev/null +++ b/app/controllers/api/v1_alpha/collections_controller.rb @@ -0,0 +1,114 @@ +# frozen_string_literal: true + +class Api::V1Alpha::CollectionsController < Api::BaseController + include Authorization + + DEFAULT_COLLECTIONS_LIMIT = 40 + + rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e| + render json: { error: ValidationErrorFormatter.new(e).as_json }, status: 422 + end + + before_action :check_feature_enabled + + before_action -> { authorize_if_got_token! :read, :'read:collections' }, only: [:index, :show] + before_action -> { doorkeeper_authorize! :write, :'write:collections' }, only: [:create, :update, :destroy] + + before_action :require_user!, only: [:create, :update, :destroy] + + before_action :set_account, only: [:index] + before_action :set_collections, only: [:index] + before_action :set_collection, only: [:show, :update, :destroy] + + after_action :insert_pagination_headers, only: [:index] + + after_action :verify_authorized + + def index + cache_if_unauthenticated! + authorize Collection, :index? + + render json: @collections, each_serializer: REST::BaseCollectionSerializer + end + + def show + cache_if_unauthenticated! + authorize @collection, :show? + + render json: @collection, serializer: REST::CollectionSerializer + end + + def create + authorize Collection, :create? + + @collection = CreateCollectionService.new.call(collection_creation_params, current_user.account) + + render json: @collection, serializer: REST::CollectionSerializer + end + + def update + authorize @collection, :update? + + @collection.update!(collection_update_params) # TODO: Create a service for this to federate changes + + render json: @collection, serializer: REST::CollectionSerializer + end + + def destroy + authorize @collection, :destroy? + + @collection.destroy + + head 200 + end + + private + + def set_account + @account = Account.find(params[:account_id]) + end + + def set_collections + @collections = @account.collections + .with_tag + .order(created_at: :desc) + .offset(offset_param) + .limit(limit_param(DEFAULT_COLLECTIONS_LIMIT)) + end + + def set_collection + @collection = Collection.find(params[:id]) + end + + def collection_creation_params + params.permit(:name, :description, :sensitive, :discoverable, :tag_name, account_ids: []) + end + + def collection_update_params + params.permit(:name, :description, :sensitive, :discoverable, :tag_name) + end + + def check_feature_enabled + raise ActionController::RoutingError unless Mastodon::Feature.collections_enabled? + end + + def next_path + return unless records_continue? + + api_v1_alpha_account_collections_url(@account, pagination_params(offset: offset_param + limit_param(DEFAULT_COLLECTIONS_LIMIT))) + end + + def prev_path + return if offset_param.zero? + + api_v1_alpha_account_collections_url(@account, pagination_params(offset: offset_param - limit_param(DEFAULT_COLLECTIONS_LIMIT))) + end + + def records_continue? + ((offset_param * limit_param(DEFAULT_COLLECTIONS_LIMIT)) + @collections.size) < @account.collections.size + end + + def offset_param + params[:offset].to_i + end +end diff --git a/app/controllers/api/web/embeds_controller.rb b/app/controllers/api/web/embeds_controller.rb index f82c1c50d79502..fba56b405864ef 100644 --- a/app/controllers/api/web/embeds_controller.rb +++ b/app/controllers/api/web/embeds_controller.rb @@ -30,7 +30,7 @@ def show def set_status @status = Status.find(params[:id]) authorize @status, :show? - rescue Mastodon::NotPermittedError + rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError not_found end end diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb index fc430544fbef50..b8c21f3ccd7b51 100644 --- a/app/controllers/auth/registrations_controller.rb +++ b/app/controllers/auth/registrations_controller.rb @@ -89,7 +89,7 @@ def after_update_path_for(_resource) end def check_enabled_registrations - redirect_to root_path unless allowed_registration?(request.remote_ip, @invite) + redirect_to new_user_session_path, alert: I18n.t('devise.failure.closed_registrations', email: Setting.site_contact_email) unless allowed_registration?(request.remote_ip, @invite) end def invite_code @@ -135,7 +135,7 @@ def require_rules_acceptance! @accept_token = session[:accept_token] = SecureRandom.hex @invite_code = invite_code - set_locale { render :rules } + render :rules end def is_flashing_format? # rubocop:disable Naming/PredicatePrefix diff --git a/app/controllers/authorize_interactions_controller.rb b/app/controllers/authorize_interactions_controller.rb index 99eed018b070ed..03cad3e3175f03 100644 --- a/app/controllers/authorize_interactions_controller.rb +++ b/app/controllers/authorize_interactions_controller.rb @@ -21,7 +21,7 @@ def show def set_resource @resource = located_resource authorize(@resource, :show?) if @resource.is_a?(Status) - rescue Mastodon::NotPermittedError + rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError not_found end diff --git a/app/controllers/concerns/account_owned_concern.rb b/app/controllers/concerns/account_owned_concern.rb index 2b132417f7cf33..7b3cd4d3ea607c 100644 --- a/app/controllers/concerns/account_owned_concern.rb +++ b/app/controllers/concerns/account_owned_concern.rb @@ -18,7 +18,11 @@ def account_required? end def set_account - @account = Account.find_local!(username_param) + @account = username_param.present? ? Account.find_local!(username_param) : Account.local.find(account_id_param) + end + + def account_id_param + params[:account_id] end def username_param diff --git a/app/controllers/concerns/api/interaction_policies_concern.rb b/app/controllers/concerns/api/interaction_policies_concern.rb index f1e1480c0c0cb9..0679c3c691e41f 100644 --- a/app/controllers/concerns/api/interaction_policies_concern.rb +++ b/app/controllers/concerns/api/interaction_policies_concern.rb @@ -6,9 +6,9 @@ module Api::InteractionPoliciesConcern def quote_approval_policy case status_params[:quote_approval_policy].presence || current_user.setting_default_quote_policy when 'public' - Status::QUOTE_APPROVAL_POLICY_FLAGS[:public] << 16 + InteractionPolicy::POLICY_FLAGS[:public] << 16 when 'followers' - Status::QUOTE_APPROVAL_POLICY_FLAGS[:followers] << 16 + InteractionPolicy::POLICY_FLAGS[:followers] << 16 when 'nobody' 0 else diff --git a/app/controllers/concerns/async_refreshes_concern.rb b/app/controllers/concerns/async_refreshes_concern.rb index 29122e16b5e14e..2d0e9ff4ff4ba5 100644 --- a/app/controllers/concerns/async_refreshes_concern.rb +++ b/app/controllers/concerns/async_refreshes_concern.rb @@ -6,6 +6,9 @@ module AsyncRefreshesConcern def add_async_refresh_header(async_refresh, retry_seconds: 3) return unless async_refresh.running? - response.headers['Mastodon-Async-Refresh'] = "id=\"#{async_refresh.id}\", retry=#{retry_seconds}" + value = "id=\"#{async_refresh.id}\", retry=#{retry_seconds}" + value += ", result_count=#{async_refresh.result_count}" unless async_refresh.result_count.nil? + + response.headers['Mastodon-Async-Refresh'] = value end end diff --git a/app/controllers/concerns/signature_verification.rb b/app/controllers/concerns/signature_verification.rb index 2bdd3558643526..1e83ab9c69b6b7 100644 --- a/app/controllers/concerns/signature_verification.rb +++ b/app/controllers/concerns/signature_verification.rb @@ -72,10 +72,13 @@ def signed_request_actor rescue Mastodon::SignatureVerificationError => e fail_with! e.message rescue *Mastodon::HTTP_CONNECTION_ERRORS => e + @signature_verification_failure_code ||= 503 fail_with! "Failed to fetch remote data: #{e.message}" rescue Mastodon::UnexpectedResponseError + @signature_verification_failure_code ||= 503 fail_with! 'Failed to fetch remote data (got unexpected reply from server)' rescue Stoplight::Error::RedLight + @signature_verification_failure_code ||= 503 fail_with! 'Fetching attempt skipped because of recent connection failure' end diff --git a/app/controllers/follower_accounts_controller.rb b/app/controllers/follower_accounts_controller.rb index ab8d77f353e396..909129c11b152f 100644 --- a/app/controllers/follower_accounts_controller.rb +++ b/app/controllers/follower_accounts_controller.rb @@ -7,6 +7,7 @@ class FollowerAccountsController < ApplicationController vary_by -> { public_fetch_mode? ? 'Accept, Accept-Language, Cookie' : 'Accept, Accept-Language, Cookie, Signature' } before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? } + before_action :protect_hidden_collections, if: -> { request.format.json? } skip_around_action :set_locale, if: -> { request.format == :json } skip_before_action :require_functional!, unless: :limited_federation_mode? @@ -18,8 +19,6 @@ def index end format.json do - raise Mastodon::NotPermittedError if page_requested? && @account.hide_collections? - expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode?) render json: collection_presenter, @@ -41,6 +40,10 @@ def follows @follows = scope.recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:account) end + def protect_hidden_collections + raise Mastodon::NotPermittedError if page_requested? && @account.hide_collections? + end + def page_requested? params[:page].present? end @@ -58,20 +61,22 @@ def prev_page_url end def collection_presenter - options = { type: :ordered } + options = {} options[:size] = @account.followers_count unless Setting.hide_followers_count || @account.user&.setting_hide_followers_count if page_requested? ActivityPub::CollectionPresenter.new( - id: account_followers_url(@account, page: params.fetch(:page, 1)), + id: page_url(params.fetch(:page, 1)), + type: :ordered, items: follows.map { |follow| ActivityPub::TagManager.instance.uri_for(follow.account) }, - part_of: account_followers_url(@account), + part_of: ActivityPub::TagManager.instance.followers_uri_for(@account), next: next_page_url, prev: prev_page_url, **options ) else ActivityPub::CollectionPresenter.new( - id: account_followers_url(@account), + id: ActivityPub::TagManager.instance.followers_uri_for(@account), + type: :ordered, first: page_url(1), **options ) diff --git a/app/controllers/following_accounts_controller.rb b/app/controllers/following_accounts_controller.rb index 268fad96d09b68..7a0f37887de320 100644 --- a/app/controllers/following_accounts_controller.rb +++ b/app/controllers/following_accounts_controller.rb @@ -7,6 +7,7 @@ class FollowingAccountsController < ApplicationController vary_by -> { public_fetch_mode? ? 'Accept, Accept-Language, Cookie' : 'Accept, Accept-Language, Cookie, Signature' } before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? } + before_action :protect_hidden_collections, if: -> { request.format.json? } skip_around_action :set_locale, if: -> { request.format == :json } skip_before_action :require_functional!, unless: :limited_federation_mode? @@ -18,11 +19,6 @@ def index end format.json do - if page_requested? && @account.hide_collections? - forbidden - next - end - expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode?) render json: collection_presenter, @@ -44,12 +40,16 @@ def follows @follows = scope.recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:target_account) end + def protect_hidden_collections + raise Mastodon::NotPermittedError if page_requested? && @account.hide_collections? + end + def page_requested? params[:page].present? end def page_url(page) - account_following_index_url(@account, page: page) unless page.nil? + ActivityPub::TagManager.instance.following_uri_for(@account, page: page) unless page.nil? end def next_page_url @@ -63,17 +63,17 @@ def prev_page_url def collection_presenter if page_requested? ActivityPub::CollectionPresenter.new( - id: account_following_index_url(@account, page: params.fetch(:page, 1)), + id: page_url(params.fetch(:page, 1)), type: :ordered, size: @account.following_count, items: follows.map { |follow| ActivityPub::TagManager.instance.uri_for(follow.target_account) }, - part_of: account_following_index_url(@account), + part_of: ActivityPub::TagManager.instance.following_uri_for(@account), next: next_page_url, prev: prev_page_url ) else ActivityPub::CollectionPresenter.new( - id: account_following_index_url(@account), + id: ActivityPub::TagManager.instance.following_uri_for(@account), type: :ordered, size: @account.following_count, first: page_url(1) diff --git a/app/controllers/media_controller.rb b/app/controllers/media_controller.rb index 9d10468e69330e..0590ea40270cd3 100644 --- a/app/controllers/media_controller.rb +++ b/app/controllers/media_controller.rb @@ -34,7 +34,7 @@ def set_media_attachment def verify_permitted_status! authorize @media_attachment.status, :show? - rescue Mastodon::NotPermittedError + rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError not_found end diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index af6bebf36fd753..e673faca04536b 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -62,7 +62,7 @@ def set_link_headers def set_status @status = @account.statuses.find(params[:id]) authorize @status, :show? - rescue Mastodon::NotPermittedError + rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError not_found end diff --git a/app/controllers/wrapstodon_controller.rb b/app/controllers/wrapstodon_controller.rb new file mode 100644 index 00000000000000..b1fe521fb114d5 --- /dev/null +++ b/app/controllers/wrapstodon_controller.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class WrapstodonController < ApplicationController + include WebAppControllerConcern + include Authorization + include AccountOwnedConcern + + vary_by 'Accept, Accept-Language, Cookie' + + before_action :set_generated_annual_report + + skip_before_action :require_functional!, only: :show, unless: :limited_federation_mode? + + def show + expires_in 10.minutes, public: true if current_account.nil? + end + + private + + def set_generated_annual_report + @generated_annual_report = GeneratedAnnualReport.find_by!(account: @account, year: params[:year], share_key: params[:share_key]) + end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index e759c7e3bbffc5..8d408234179f27 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -113,6 +113,7 @@ def conditional_link_to(condition, name, options = {}, html_options = {}, &block end def material_symbol(icon, attributes = {}) + whitespace = attributes.delete(:whitespace) { true } safe_join( [ inline_svg_tag( @@ -121,7 +122,7 @@ def material_symbol(icon, attributes = {}) role: :img, data: attributes[:data] ), - ' ', + whitespace ? ' ' : '', ] ) end @@ -152,11 +153,9 @@ def opengraph(property, content) tag.meta(content: content, property: property) end - def body_classes + def html_classes output = [] - output << content_for(:body_classes) - output << "flavour-#{current_flavour.parameterize}" - output << "skin-#{current_skin.parameterize}" + output << content_for(:html_classes) output << 'system-font' if current_account&.user&.setting_system_font_ui output << 'custom-scrollbars' unless current_account&.user&.setting_system_scrollbars_ui output << (current_account&.user&.setting_reduce_motion ? 'reduce-motion' : 'no-reduce-motion') @@ -164,6 +163,12 @@ def body_classes output.compact_blank.join(' ') end + def body_classes + output = [] + output << content_for(:body_classes) + output.compact_blank.join(' ') + end + def cdn_host Rails.configuration.action_controller.asset_host end diff --git a/app/helpers/database_helper.rb b/app/helpers/database_helper.rb index 62a26a0c2a05c8..f245d303d10ba9 100644 --- a/app/helpers/database_helper.rb +++ b/app/helpers/database_helper.rb @@ -2,7 +2,7 @@ module DatabaseHelper def replica_enabled? - ENV['REPLICA_DB_NAME'] || ENV.fetch('REPLICA_DATABASE_URL', nil) + ENV['REPLICA_DB_NAME'] || ENV['REPLICA_DB_HOST'] || ENV.fetch('REPLICA_DATABASE_URL', nil) end module_function :replica_enabled? diff --git a/app/helpers/filters_helper.rb b/app/helpers/filters_helper.rb index 22a1c172de2c86..ec0d55788327d7 100644 --- a/app/helpers/filters_helper.rb +++ b/app/helpers/filters_helper.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true module FiltersHelper + KEYWORDS_LIMIT = 5 + def filter_action_label(action) safe_join( [ @@ -9,4 +11,10 @@ def filter_action_label(action) ] ) end + + def filter_keywords(filter) + filter.keywords.map(&:keyword).take(KEYWORDS_LIMIT).tap do |list| + list << '…' if filter.keywords.size > KEYWORDS_LIMIT + end.join(', ') + end end diff --git a/app/helpers/home_helper.rb b/app/helpers/home_helper.rb index 79e28c983af40f..59bc06031eea1a 100644 --- a/app/helpers/home_helper.rb +++ b/app/helpers/home_helper.rb @@ -21,7 +21,13 @@ def account_link_to(account, button = '', path: nil) end end else - link_to(path || ActivityPub::TagManager.instance.url_for(account), class: 'account__display-name') do + account_url = if account.suspended? + ActivityPub::TagManager.instance.url_for(account) + else + web_url("@#{account.pretty_acct}") + end + + link_to(path || account_url, class: 'account__display-name') do content_tag(:div, class: 'account__avatar-wrapper') do image_tag(full_asset_url(current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url), class: 'account__avatar', width: 46, height: 46) end + diff --git a/app/helpers/languages_helper.rb b/app/helpers/languages_helper.rb index ddb6b79c8667b2..dbf56f45a04ab6 100644 --- a/app/helpers/languages_helper.rb +++ b/app/helpers/languages_helper.rb @@ -35,7 +35,7 @@ module LanguagesHelper cy: ['Welsh', 'Cymraeg'].freeze, da: ['Danish', 'dansk'].freeze, de: ['German', 'Deutsch'].freeze, - dv: ['Divehi', 'Dhivehi'].freeze, + dv: ['Divehi', 'ދިވެހި'].freeze, dz: ['Dzongkha', 'རྫོང་ཁ'].freeze, ee: ['Ewe', 'Eʋegbe'].freeze, el: ['Greek', 'Ελληνικά'].freeze, @@ -100,7 +100,7 @@ module LanguagesHelper lo: ['Lao', 'ລາວ'].freeze, lt: ['Lithuanian', 'lietuvių kalba'].freeze, lu: ['Luba-Katanga', 'Tshiluba'].freeze, - lv: ['Latvian', 'latviešu valoda'].freeze, + lv: ['Latvian', 'Latviski'].freeze, mg: ['Malagasy', 'fiteny malagasy'].freeze, mh: ['Marshallese', 'Kajin M̧ajeļ'].freeze, mi: ['Māori', 'te reo Māori'].freeze, diff --git a/app/helpers/statuses_helper.rb b/app/helpers/statuses_helper.rb index 9cf64d09b4d0ad..84dea96faf3559 100644 --- a/app/helpers/statuses_helper.rb +++ b/app/helpers/statuses_helper.rb @@ -46,6 +46,14 @@ def poll_summary(status) status.preloadable_poll.options.map { |o| "[ ] #{o}" }.join("\n") end + def status_classnames(status, is_quote) + if is_quote + 'status--is-quote' + elsif status.quote.present? + 'status--has-quote' + end + end + def status_description(status) components = [[media_summary(status), status_text_summary(status)].compact_blank.join(' · ')] @@ -57,6 +65,20 @@ def status_description(status) components.compact_blank.join("\n\n") end + # This logic should be kept in sync with https://github.com/mastodon/mastodon/blob/425311e1d95c8a64ddac6c724fca247b8b893a82/app/javascript/mastodon/features/status/components/card.jsx#L160 + def preview_card_aspect_ratio_classname(preview_card) + interactive = preview_card.type == 'video' + large_image = (preview_card.image.present? && preview_card.width > preview_card.height) || interactive + + if large_image && interactive + 'status-card__image--video' + elsif large_image + 'status-card__image--large' + else + 'status-card__image--normal' + end + end + def visibility_icon(status) VISIBLITY_ICONS[status.visibility.to_sym] end diff --git a/app/helpers/wrapstodon_helper.rb b/app/helpers/wrapstodon_helper.rb new file mode 100644 index 00000000000000..5a0075a0e58be8 --- /dev/null +++ b/app/helpers/wrapstodon_helper.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module WrapstodonHelper + def render_wrapstodon_share_data(report) + payload = ActiveModelSerializers::SerializableResource.new( + AnnualReportsPresenter.new([report]), + serializer: REST::AnnualReportsSerializer, + scope: nil, + scope_name: :current_user + ).as_json + + payload[:me] = current_account.id.to_s if user_signed_in? + payload[:domain] = Addressable::IDNA.to_unicode(Rails.configuration.x.local_domain) + + json_string = payload.to_json + + # rubocop:disable Rails/OutputSafety + content_tag(:script, json_escape(json_string).html_safe, type: 'application/json', id: 'wrapstodon-data') + # rubocop:enable Rails/OutputSafety + end +end diff --git a/app/inputs/date_of_birth_input.rb b/app/inputs/date_of_birth_input.rb index 131234b02ebe4c..a7aec1b39bdcf3 100644 --- a/app/inputs/date_of_birth_input.rb +++ b/app/inputs/date_of_birth_input.rb @@ -1,31 +1,49 @@ # frozen_string_literal: true class DateOfBirthInput < SimpleForm::Inputs::Base - OPTIONS = [ - { autocomplete: 'bday-day', maxlength: 2, pattern: '[0-9]+', placeholder: 'DD' }.freeze, - { autocomplete: 'bday-month', maxlength: 2, pattern: '[0-9]+', placeholder: 'MM' }.freeze, - { autocomplete: 'bday-year', maxlength: 4, pattern: '[0-9]+', placeholder: 'YYYY' }.freeze, - ].freeze + OPTIONS = { + day: { autocomplete: 'bday-day', maxlength: 2, pattern: '[0-9]+', placeholder: 'DD' }, + month: { autocomplete: 'bday-month', maxlength: 2, pattern: '[0-9]+', placeholder: 'MM' }, + year: { autocomplete: 'bday-year', maxlength: 4, pattern: '[0-9]+', placeholder: 'YYYY' }, + }.freeze def input(wrapper_options = nil) merged_input_options = merge_wrapper_options(input_html_options, wrapper_options) merged_input_options[:inputmode] = 'numeric' - values = (object.public_send(attribute_name) || '').split('.') - - safe_join(Array.new(3) do |index| - options = merged_input_options.merge(OPTIONS[index]).merge id: generate_id(index), 'aria-label': I18n.t("simple_form.labels.user.date_of_birth_#{index + 1}i"), value: values[index] - @builder.text_field("#{attribute_name}(#{index + 1}i)", options) - end) + safe_join( + ordered_options.map do |option| + options = merged_input_options + .merge(OPTIONS[option]) + .merge( + id: generate_id(option), + 'aria-label': I18n.t("simple_form.labels.user.date_of_birth_#{param_for(option)}"), + value: values[option] + ) + @builder.text_field("#{attribute_name}(#{param_for(option)})", options) + end + ) end def label_target - "#{attribute_name}_1i" + "#{attribute_name}_#{param_for(ordered_options.first)}" end private - def generate_id(index) - "#{object_name}_#{attribute_name}_#{index + 1}i" + def ordered_options + I18n.t('date.order').map(&:to_sym) + end + + def generate_id(option) + "#{object_name}_#{attribute_name}_#{param_for(option)}" + end + + def param_for(option) + "#{ActionView::Helpers::DateTimeSelector::POSITION[option]}i" + end + + def values + Date._parse((object.public_send(attribute_name) || '').to_s).transform_keys(mon: :month, mday: :day) end end diff --git a/app/javascript/config/html-tags.json b/app/javascript/config/html-tags.json new file mode 100644 index 00000000000000..cf5c96540a5e72 --- /dev/null +++ b/app/javascript/config/html-tags.json @@ -0,0 +1,78 @@ +{ + "global": { + "class": "className", + "id": true, + "title": true, + "dir": true, + "lang": true + }, + "tags": { + "p": {}, + "br": { + "children": false + }, + "span": { + "attributes": { + "translate": true + } + }, + "a": { + "attributes": { + "href": true, + "rel": true, + "translate": true, + "target": true, + "title": true + } + }, + "abbr": { + "attributes": { + "title": true + } + }, + "del": {}, + "s": {}, + "pre": {}, + "blockquote": { + "attributes": { + "cite": true + } + }, + "code": {}, + "b": {}, + "strong": {}, + "u": {}, + "sub": {}, + "sup": {}, + "i": {}, + "img": { + "children": false, + "attributes": { + "src": true, + "alt": true, + "title": true + } + }, + "em": {}, + "h1": {}, + "h2": {}, + "h3": {}, + "h4": {}, + "h5": {}, + "ul": {}, + "ol": { + "attributes": { + "start": true, + "reversed": true + } + }, + "li": { + "attributes": { + "value": true + } + }, + "ruby": {}, + "rt": {}, + "rp": {} + } +} diff --git a/app/javascript/entrypoints/admin.tsx b/app/javascript/entrypoints/admin.tsx index a60778f0c045f7..92b9d1d9177ddb 100644 --- a/app/javascript/entrypoints/admin.tsx +++ b/app/javascript/entrypoints/admin.tsx @@ -1,6 +1,7 @@ import { createRoot } from 'react-dom/client'; -import Rails from '@rails/ujs'; +import { decode, ValidationError } from 'blurhash'; +import { on } from 'delegated-events'; import ready from '../mastodon/ready'; @@ -23,10 +24,9 @@ const setAnnouncementEndsAttributes = (target: HTMLInputElement) => { } }; -Rails.delegate( - document, - 'input[type="datetime-local"]#announcement_starts_at', +on( 'change', + 'input[type="datetime-local"]#announcement_starts_at', ({ target }) => { if (target instanceof HTMLInputElement) setAnnouncementEndsAttributes(target); @@ -62,7 +62,7 @@ const hideSelectAll = () => { if (hiddenField) hiddenField.value = '0'; }; -Rails.delegate(document, '#batch_checkbox_all', 'change', ({ target }) => { +on('change', '#batch_checkbox_all', ({ target }) => { if (!(target instanceof HTMLInputElement)) return; const selectAllMatchingElement = document.querySelector( @@ -84,7 +84,7 @@ Rails.delegate(document, '#batch_checkbox_all', 'change', ({ target }) => { } }); -Rails.delegate(document, '.batch-table__select-all button', 'click', () => { +on('click', '.batch-table__select-all button', () => { const hiddenField = document.querySelector( '#select_all_matching', ); @@ -112,7 +112,7 @@ Rails.delegate(document, '.batch-table__select-all button', 'click', () => { } }); -Rails.delegate(document, batchCheckboxClassName, 'change', () => { +on('change', batchCheckboxClassName, () => { const checkAllElement = document.querySelector( 'input#batch_checkbox_all', ); @@ -139,14 +139,9 @@ Rails.delegate(document, batchCheckboxClassName, 'change', () => { } }); -Rails.delegate( - document, - '.filter-subset--with-select select', - 'change', - ({ target }) => { - if (target instanceof HTMLSelectElement) target.form?.submit(); - }, -); +on('change', '.filter-subset--with-select select', ({ target }) => { + if (target instanceof HTMLSelectElement) target.form?.submit(); +}); const onDomainBlockSeverityChange = (target: HTMLSelectElement) => { const rejectMediaDiv = document.querySelector( @@ -167,11 +162,11 @@ const onDomainBlockSeverityChange = (target: HTMLSelectElement) => { } }; -Rails.delegate(document, '#domain_block_severity', 'change', ({ target }) => { +on('change', '#domain_block_severity', ({ target }) => { if (target instanceof HTMLSelectElement) onDomainBlockSeverityChange(target); }); -const onEnableBootstrapTimelineAccountsChange = (target: HTMLInputElement) => { +function onEnableBootstrapTimelineAccountsChange(target: HTMLInputElement) { const bootstrapTimelineAccountsField = document.querySelector( '#form_admin_settings_bootstrap_timeline_accounts', @@ -193,12 +188,11 @@ const onEnableBootstrapTimelineAccountsChange = (target: HTMLInputElement) => { ); } } -}; +} -Rails.delegate( - document, - '#form_admin_settings_enable_bootstrap_timeline_accounts', +on( 'change', + '#form_admin_settings_enable_bootstrap_timeline_accounts', ({ target }) => { if (target instanceof HTMLInputElement) onEnableBootstrapTimelineAccountsChange(target); @@ -238,11 +232,11 @@ const onChangeRegistrationMode = (target: HTMLSelectElement) => { }); }; -const convertUTCDateTimeToLocal = (value: string) => { +function convertUTCDateTimeToLocal(value: string) { const date = new Date(value + 'Z'); const twoChars = (x: number) => x.toString().padStart(2, '0'); return `${date.getFullYear()}-${twoChars(date.getMonth() + 1)}-${twoChars(date.getDate())}T${twoChars(date.getHours())}:${twoChars(date.getMinutes())}`; -}; +} function convertLocalDatetimeToUTC(value: string) { const date = new Date(value); @@ -250,14 +244,9 @@ function convertLocalDatetimeToUTC(value: string) { return fullISO8601.slice(0, fullISO8601.indexOf('T') + 6); } -Rails.delegate( - document, - '#form_admin_settings_registrations_mode', - 'change', - ({ target }) => { - if (target instanceof HTMLSelectElement) onChangeRegistrationMode(target); - }, -); +on('change', '#form_admin_settings_registrations_mode', ({ target }) => { + if (target instanceof HTMLSelectElement) onChangeRegistrationMode(target); +}); async function mountReactComponent(element: Element) { const componentName = element.getAttribute('data-admin-component'); @@ -267,9 +256,8 @@ async function mountReactComponent(element: Element) { const componentProps = JSON.parse(stringProps) as object; - const { default: AdminComponent } = await import( - '@/mastodon/containers/admin_component' - ); + const { default: AdminComponent } = + await import('@/mastodon/containers/admin_component'); const { default: Component } = (await import( `@/mastodon/components/admin/${componentName}.jsx` @@ -304,7 +292,7 @@ ready(() => { if (registrationMode) onChangeRegistrationMode(registrationMode); const checkAllElement = document.querySelector( - 'input#batch_checkbox_all', + '#batch_checkbox_all', ); if (checkAllElement) { const allCheckboxes = Array.from( @@ -317,7 +305,7 @@ ready(() => { } document - .querySelector('a#add-instance-button') + .querySelector('a#add-instance-button') ?.addEventListener('click', (e) => { const domain = document.querySelector( 'input[type="text"]#by_domain', @@ -341,7 +329,7 @@ ready(() => { } }); - Rails.delegate(document, 'form', 'submit', ({ target }) => { + on('submit', 'form', ({ target }) => { if (target instanceof HTMLFormElement) target .querySelectorAll('input[type="datetime-local"]') @@ -362,6 +350,46 @@ ready(() => { document.querySelectorAll('[data-admin-component]').forEach((element) => { void mountReactComponent(element); }); + + document + .querySelectorAll('canvas[data-blurhash]') + .forEach((canvas) => { + const blurhash = canvas.dataset.blurhash; + if (blurhash) { + try { + // decode returns a Uint8ClampedArray not Uint8ClampedArray + const pixels = decode( + blurhash, + 32, + 32, + ) as Uint8ClampedArray; + const ctx = canvas.getContext('2d'); + const imageData = new ImageData(pixels, 32, 32); + + ctx?.putImageData(imageData, 0, 0); + } catch (err) { + if (err instanceof ValidationError) { + // ignore blurhash validation errors + return; + } + + throw err; + } + } + }); + + document + .querySelectorAll('.preview-card') + .forEach((previewCard) => { + const spoilerButton = previewCard.querySelector('.spoiler-button'); + if (!spoilerButton) { + return; + } + + spoilerButton.addEventListener('click', () => { + previewCard.classList.toggle('preview-card--image-visible'); + }); + }); }).catch((reason: unknown) => { throw reason; }); diff --git a/app/javascript/entrypoints/public.tsx b/app/javascript/entrypoints/public.tsx index dd1956446daeeb..6e88eb8778068a 100644 --- a/app/javascript/entrypoints/public.tsx +++ b/app/javascript/entrypoints/public.tsx @@ -4,8 +4,8 @@ import { IntlMessageFormat } from 'intl-messageformat'; import type { MessageDescriptor, PrimitiveType } from 'react-intl'; import { defineMessages } from 'react-intl'; -import Rails from '@rails/ujs'; import axios from 'axios'; +import { on } from 'delegated-events'; import { throttle } from 'lodash'; import { timeAgoString } from '../mastodon/components/relative_timestamp'; @@ -175,10 +175,9 @@ function loaded() { }); } - Rails.delegate( - document, - 'input#user_account_attributes_username', + on( 'input', + 'input#user_account_attributes_username', throttle( ({ target }) => { if (!(target instanceof HTMLInputElement)) return; @@ -202,60 +201,47 @@ function loaded() { ), ); - Rails.delegate( - document, - '#user_password,#user_password_confirmation', - 'input', - () => { - const password = document.querySelector( - 'input#user_password', + on('input', '#user_password,#user_password_confirmation', () => { + const password = document.querySelector( + 'input#user_password', + ); + const confirmation = document.querySelector( + 'input#user_password_confirmation', + ); + if (!confirmation || !password) return; + + if (confirmation.value && confirmation.value.length > password.maxLength) { + confirmation.setCustomValidity( + formatMessage(messages.passwordExceedsLength), ); - const confirmation = document.querySelector( - 'input#user_password_confirmation', + } else if (password.value && password.value !== confirmation.value) { + confirmation.setCustomValidity( + formatMessage(messages.passwordDoesNotMatch), ); - if (!confirmation || !password) return; - - if ( - confirmation.value && - confirmation.value.length > password.maxLength - ) { - confirmation.setCustomValidity( - formatMessage(messages.passwordExceedsLength), - ); - } else if (password.value && password.value !== confirmation.value) { - confirmation.setCustomValidity( - formatMessage(messages.passwordDoesNotMatch), - ); - } else { - confirmation.setCustomValidity(''); - } - }, - ); + } else { + confirmation.setCustomValidity(''); + } + }); } -Rails.delegate( - document, - '#edit_profile input[type=file]', - 'change', - ({ target }) => { - if (!(target instanceof HTMLInputElement)) return; +on('change', '#edit_profile input[type=file]', ({ target }) => { + if (!(target instanceof HTMLInputElement)) return; - const avatar = document.querySelector( - `img#${target.id}-preview`, - ); + const avatar = document.querySelector( + `img#${target.id}-preview`, + ); - if (!avatar) return; + if (!avatar) return; - let file: File | undefined; - if (target.files) file = target.files[0]; + let file: File | undefined; + if (target.files) file = target.files[0]; - const url = file ? URL.createObjectURL(file) : avatar.dataset.originalSrc; + const url = file ? URL.createObjectURL(file) : avatar.dataset.originalSrc; - if (url) avatar.src = url; - }, -); + if (url) avatar.src = url; +}); -Rails.delegate(document, '.input-copy input', 'click', ({ target }) => { +on('click', '.input-copy input', ({ target }) => { if (!(target instanceof HTMLInputElement)) return; target.focus(); @@ -263,7 +249,7 @@ Rails.delegate(document, '.input-copy input', 'click', ({ target }) => { target.setSelectionRange(0, target.value.length); }); -Rails.delegate(document, '.input-copy button', 'click', ({ target }) => { +on('click', '.input-copy button', ({ target }) => { if (!(target instanceof HTMLButtonElement)) return; const input = target.parentNode?.querySelector( @@ -312,22 +298,22 @@ const toggleSidebar = () => { sidebar.classList.toggle('visible'); }; -Rails.delegate(document, '.sidebar__toggle__icon', 'click', () => { +on('click', '.sidebar__toggle__icon', () => { toggleSidebar(); }); -Rails.delegate(document, '.sidebar__toggle__icon', 'keydown', (e) => { +on('keydown', '.sidebar__toggle__icon', (e) => { if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); toggleSidebar(); } }); -Rails.delegate(document, 'img.custom-emoji', 'mouseover', ({ target }) => { +on('mouseover', 'img.custom-emoji', ({ target }) => { if (target instanceof HTMLImageElement && target.dataset.original) target.src = target.dataset.original; }); -Rails.delegate(document, 'img.custom-emoji', 'mouseout', ({ target }) => { +on('mouseout', 'img.custom-emoji', ({ target }) => { if (target instanceof HTMLImageElement && target.dataset.static) target.src = target.dataset.static; }); @@ -376,22 +362,17 @@ const setInputHint = ( } }; -Rails.delegate( - document, - '#account_statuses_cleanup_policy_enabled', - 'change', - ({ target }) => { - if (!(target instanceof HTMLInputElement) || !target.form) return; - - target.form - .querySelectorAll< - HTMLInputElement | HTMLSelectElement - >('input:not([type=hidden], #account_statuses_cleanup_policy_enabled), select') - .forEach((input) => { - setInputDisabled(input, !target.checked); - }); - }, -); +on('change', '#account_statuses_cleanup_policy_enabled', ({ target }) => { + if (!(target instanceof HTMLInputElement) || !target.form) return; + + target.form + .querySelectorAll< + HTMLInputElement | HTMLSelectElement + >('input:not([type=hidden], #account_statuses_cleanup_policy_enabled), select') + .forEach((input) => { + setInputDisabled(input, !target.checked); + }); +}); const updateDefaultQuotePrivacyFromPrivacy = ( privacySelect: EventTarget | null, @@ -414,18 +395,13 @@ const updateDefaultQuotePrivacyFromPrivacy = ( } }; -Rails.delegate( - document, - '#user_settings_attributes_default_privacy', - 'change', - ({ target }) => { - updateDefaultQuotePrivacyFromPrivacy(target); - }, -); +on('change', '#user_settings_attributes_default_privacy', ({ target }) => { + updateDefaultQuotePrivacyFromPrivacy(target); +}); // Empty the honeypot fields in JS in case something like an extension // automatically filled them. -Rails.delegate(document, '#registration_new_user,#new_user', 'submit', () => { +on('submit', '#registration_new_user,#new_user', () => { [ 'user_website', 'user_confirm_password', @@ -439,7 +415,7 @@ Rails.delegate(document, '#registration_new_user,#new_user', 'submit', () => { }); }); -Rails.delegate(document, '.rules-list button', 'click', ({ target }) => { +on('click', '.rules-list button', ({ target }) => { if (!(target instanceof HTMLElement)) { return; } diff --git a/app/javascript/entrypoints/wrapstodon.tsx b/app/javascript/entrypoints/wrapstodon.tsx new file mode 100644 index 00000000000000..9fff41a1330438 --- /dev/null +++ b/app/javascript/entrypoints/wrapstodon.tsx @@ -0,0 +1,67 @@ +import { createRoot } from 'react-dom/client'; + +import { Provider as ReduxProvider } from 'react-redux'; + +import { importFetchedStatuses } from '@/mastodon/actions/importer'; +import { hydrateStore } from '@/mastodon/actions/store'; +import type { ApiAnnualReportResponse } from '@/mastodon/api/annual_report'; +import { Router } from '@/mastodon/components/router'; +import { WrapstodonSharedPage } from '@/mastodon/features/annual_report/shared_page'; +import { IntlProvider, loadLocale } from '@/mastodon/locales'; +import { loadPolyfills } from '@/mastodon/polyfills'; +import ready from '@/mastodon/ready'; +import { setReport } from '@/mastodon/reducers/slices/annual_report'; +import { store } from '@/mastodon/store'; + +function loaded() { + const mountNode = document.getElementById('wrapstodon'); + if (!mountNode) { + throw new Error('Mount node not found'); + } + const propsNode = document.getElementById('wrapstodon-data'); + if (!propsNode) { + throw new Error('Initial state prop not found'); + } + + const initialState = JSON.parse( + propsNode.textContent, + ) as ApiAnnualReportResponse & { me?: string; domain: string }; + + const report = initialState.annual_reports[0]; + if (!report) { + throw new Error('Initial state report not found'); + } + + // Set up store + store.dispatch( + hydrateStore({ + meta: { + locale: document.documentElement.lang, + me: initialState.me, + domain: initialState.domain, + }, + accounts: initialState.accounts, + }), + ); + store.dispatch(importFetchedStatuses(initialState.statuses)); + + store.dispatch(setReport(report)); + + const root = createRoot(mountNode); + root.render( + + + + + + + , + ); +} + +loadPolyfills() + .then(loadLocale) + .then(() => ready(loaded)) + .catch((err: unknown) => { + console.error(err); + }); diff --git a/app/javascript/flavours/glitch/actions/bundles.js b/app/javascript/flavours/glitch/actions/bundles.js deleted file mode 100644 index ecc9c8f7d3ec22..00000000000000 --- a/app/javascript/flavours/glitch/actions/bundles.js +++ /dev/null @@ -1,25 +0,0 @@ -export const BUNDLE_FETCH_REQUEST = 'BUNDLE_FETCH_REQUEST'; -export const BUNDLE_FETCH_SUCCESS = 'BUNDLE_FETCH_SUCCESS'; -export const BUNDLE_FETCH_FAIL = 'BUNDLE_FETCH_FAIL'; - -export function fetchBundleRequest(skipLoading) { - return { - type: BUNDLE_FETCH_REQUEST, - skipLoading, - }; -} - -export function fetchBundleSuccess(skipLoading) { - return { - type: BUNDLE_FETCH_SUCCESS, - skipLoading, - }; -} - -export function fetchBundleFail(error, skipLoading) { - return { - type: BUNDLE_FETCH_FAIL, - error, - skipLoading, - }; -} diff --git a/app/javascript/flavours/glitch/actions/compose.js b/app/javascript/flavours/glitch/actions/compose.js index 387982325c3e75..a7d491377bcf5f 100644 --- a/app/javascript/flavours/glitch/actions/compose.js +++ b/app/javascript/flavours/glitch/actions/compose.js @@ -5,6 +5,7 @@ import { throttle } from 'lodash'; import api from 'flavours/glitch/api'; import { browserHistory } from 'flavours/glitch/components/router'; +import { countableText } from 'flavours/glitch/features/compose/util/counter'; import { search as emojiSearch } from 'flavours/glitch/features/emoji/emoji_mart_search_light'; import { tagHistory } from 'flavours/glitch/settings'; import { recoverHashtags } from 'flavours/glitch/utils/hashtag'; @@ -57,7 +58,6 @@ export const COMPOSE_ADVANCED_OPTIONS_CHANGE = 'COMPOSE_ADVANCED_OPTIONS_CHANGE' export const COMPOSE_SENSITIVITY_CHANGE = 'COMPOSE_SENSITIVITY_CHANGE'; export const COMPOSE_SPOILERNESS_CHANGE = 'COMPOSE_SPOILERNESS_CHANGE'; export const COMPOSE_SPOILER_TEXT_CHANGE = 'COMPOSE_SPOILER_TEXT_CHANGE'; -export const COMPOSE_VISIBILITY_CHANGE = 'COMPOSE_VISIBILITY_CHANGE'; export const COMPOSE_COMPOSING_CHANGE = 'COMPOSE_COMPOSING_CHANGE'; export const COMPOSE_CONTENT_TYPE_CHANGE = 'COMPOSE_CONTENT_TYPE_CHANGE'; export const COMPOSE_LANGUAGE_CHANGE = 'COMPOSE_LANGUAGE_CHANGE'; @@ -93,6 +93,7 @@ const messages = defineMessages({ open: { id: 'compose.published.open', defaultMessage: 'Open' }, published: { id: 'compose.published.body', defaultMessage: 'Post published.' }, saved: { id: 'compose.saved.body', defaultMessage: 'Post saved.' }, + blankPostError: { id: 'compose.error.blank_post', defaultMessage: 'Post can\'t be blank.' }, }); export const ensureComposeIsVisible = (getState) => { @@ -197,19 +198,33 @@ export function directCompose(account) { }; } +/** + * @callback ComposeSuccessCallback + * @param {Object} status + */ + /** * @param {null | string} overridePrivacy - * @param {undefined | Function} successCallback + * @param {undefined | ComposeSuccessCallback} successCallback */ export function submitCompose(overridePrivacy = null, successCallback = undefined) { return function (dispatch, getState) { let status = getState().getIn(['compose', 'text'], ''); const media = getState().getIn(['compose', 'media_attachments']); const statusId = getState().getIn(['compose', 'id'], null); + const hasQuote = !!getState().getIn(['compose', 'quoted_status_id']); const spoilers = getState().getIn(['compose', 'spoiler']) || getState().getIn(['local_settings', 'always_show_spoilers_field']); - let spoilerText = spoilers ? getState().getIn(['compose', 'spoiler_text'], '') : ''; + const spoiler_text = spoilers ? getState().getIn(['compose', 'spoiler_text'], '') : ''; + + const fulltext = `${spoiler_text ?? ''}${countableText(status ?? '')}`; + const hasText = fulltext.trim().length > 0; + + if (!(hasText || media.size !== 0 || (hasQuote && spoiler_text?.length))) { + dispatch(showAlert({ + message: messages.blankPostError, + })); + dispatch(focusCompose()); - if ((!status || !status.length) && media.size === 0) { return; } @@ -245,12 +260,12 @@ export function submitCompose(overridePrivacy = null, successCallback = undefine method: statusId === null ? 'post' : 'put', data: { status, + spoiler_text, content_type: getState().getIn(['compose', 'content_type']), in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null), media_ids: media.map(item => item.get('id')), media_attributes, - sensitive: getState().getIn(['compose', 'sensitive']) || (spoilerText.length > 0 && media.size !== 0), - spoiler_text: spoilerText, + sensitive: getState().getIn(['compose', 'sensitive']) || (spoiler_text.length > 0 && media.size !== 0), visibility: visibility, poll: getState().getIn(['compose', 'poll'], null), language: getState().getIn(['compose', 'language']), @@ -652,6 +667,7 @@ export function fetchComposeSuggestions(token) { fetchComposeSuggestionsEmojis(dispatch, getState, token); break; case '#': + case '#': fetchComposeSuggestionsTags(dispatch, getState, token); break; default: @@ -693,11 +709,20 @@ export function selectComposeSuggestion(position, token, suggestion, path) { dispatch(useEmoji(suggestion)); } else if (suggestion.type === 'hashtag') { - completion = `#${suggestion.name}`; + // TODO: it could make sense to keep the “most capitalized” of the two + const tokenName = token.slice(1); // strip leading '#' + const suggestionPrefix = suggestion.name.slice(0, tokenName.length); + const prefixMatchesSuggestion = suggestionPrefix.localeCompare(tokenName, undefined, { sensitivity: 'accent' }) === 0; + if (prefixMatchesSuggestion) { + completion = token + suggestion.name.slice(tokenName.length); + } else { + completion = `${token.slice(0, 1)}${suggestion.name}`; + } + startPosition = position - 1; } else if (suggestion.type === 'account') { - completion = getState().getIn(['accounts', suggestion.id, 'acct']); - startPosition = position; + completion = `@${getState().getIn(['accounts', suggestion.id, 'acct'])}`; + startPosition = position - 1; } // We don't want to replace hashtags that vary only in case due to accessibility, but we need to fire off an event so that @@ -808,13 +833,6 @@ export function changeComposeSpoilerText(text) { }; } -export function changeComposeVisibility(value) { - return { - type: COMPOSE_VISIBILITY_CHANGE, - value, - }; -} - export function insertEmojiCompose(position, emoji, needsSpace) { return { type: COMPOSE_EMOJI_INSERT, diff --git a/app/javascript/flavours/glitch/actions/compose_typed.ts b/app/javascript/flavours/glitch/actions/compose_typed.ts index f0219f7da7949b..257c867034d7f3 100644 --- a/app/javascript/flavours/glitch/actions/compose_typed.ts +++ b/app/javascript/flavours/glitch/actions/compose_typed.ts @@ -4,6 +4,7 @@ import { createAction } from '@reduxjs/toolkit'; import type { List as ImmutableList, Map as ImmutableMap } from 'immutable'; import { apiUpdateMedia } from 'flavours/glitch/api/compose'; +import { apiGetSearch } from 'flavours/glitch/api/search'; import type { ApiMediaAttachmentJSON } from 'flavours/glitch/api_types/media_attachments'; import type { MediaAttachment } from 'flavours/glitch/models/media_attachment'; import { @@ -12,13 +13,19 @@ import { } from 'flavours/glitch/store/typed_functions'; import type { ApiQuotePolicy } from '../api_types/quotes'; -import type { Status } from '../models/status'; +import type { Status, StatusVisibility } from '../models/status'; +import type { RootState } from '../store'; import { showAlert } from './alerts'; -import { focusCompose } from './compose'; +import { changeCompose, focusCompose } from './compose'; +import { importFetchedStatuses } from './importer'; import { openModal } from './modal'; const messages = defineMessages({ + quoteErrorEdit: { + id: 'quote_error.edit', + defaultMessage: 'Quotes cannot be added when editing a post.', + }, quoteErrorUpload: { id: 'quote_error.upload', defaultMessage: 'Quoting is not allowed with media attachments.', @@ -35,6 +42,10 @@ const messages = defineMessages({ id: 'quote_error.unauthorized', defaultMessage: 'You are not authorized to quote this post.', }, + quoteErrorPrivateMention: { + id: 'quote_error.private_mentions', + defaultMessage: 'Quoting is not allowed with direct mentions.', + }, }); type SimulatedMediaAttachmentJSON = ApiMediaAttachmentJSON & { @@ -61,6 +72,39 @@ const simulateModifiedApiResponse = ( return data; }; +export const changeComposeVisibility = createAppThunk( + 'compose/visibility_change', + (visibility: StatusVisibility, { dispatch, getState }) => { + if (visibility !== 'direct') { + return visibility; + } + + const state = getState(); + const quotedStatusId = state.compose.get('quoted_status_id') as + | string + | null; + if (!quotedStatusId) { + return visibility; + } + + // Remove the quoted status + dispatch(quoteComposeCancel()); + const quotedStatus = state.statuses.get(quotedStatusId) as Status | null; + if (!quotedStatus) { + return visibility; + } + + // Append the quoted status URL to the compose text + const url = quotedStatus.get('url') as string; + const text = state.compose.get('text') as string; + if (!text.includes(url)) { + const newText = text.trim() ? `${text}\n\n${url}` : url; + dispatch(changeCompose(newText)); + } + return visibility; + }, +); + export const changeUploadCompose = createDataLoadingThunk( 'compose/changeUpload', async ( @@ -122,7 +166,11 @@ export const quoteComposeByStatus = createAppThunk( false, ); - if (composeState.get('poll')) { + if (composeState.get('id')) { + dispatch(showAlert({ message: messages.quoteErrorEdit })); + } else if (composeState.get('privacy') === 'direct') { + dispatch(showAlert({ message: messages.quoteErrorPrivateMention })); + } else if (composeState.get('poll')) { dispatch(showAlert({ message: messages.quoteErrorPoll })); } else if ( composeState.get('is_uploading') || @@ -165,6 +213,61 @@ export const quoteComposeById = createAppThunk( }, ); +const composeStateForbidsLink = (composeState: RootState['compose']) => { + return ( + composeState.get('quoted_status_id') || + composeState.get('is_submitting') || + composeState.get('poll') || + composeState.get('is_uploading') || + composeState.get('id') || + composeState.get('privacy') === 'direct' + ); +}; + +export const pasteLinkCompose = createDataLoadingThunk( + 'compose/pasteLink', + async ({ url }: { url: string }) => { + return await apiGetSearch({ + q: url, + type: 'statuses', + resolve: true, + limit: 2, + }); + }, + (data, { dispatch, getState, requestId }) => { + const composeState = getState().compose; + + if ( + composeStateForbidsLink(composeState) || + composeState.get('fetching_link') !== requestId // Request has been cancelled + ) + return; + + dispatch(importFetchedStatuses(data.statuses)); + + if ( + data.statuses.length === 1 && + data.statuses[0] && + ['automatic', 'manual'].includes( + data.statuses[0].quote_approval?.current_user ?? 'denied', + ) + ) { + dispatch(quoteComposeById(data.statuses[0].id)); + } + }, + { + useLoadingBar: false, + condition: (_, { getState }) => + !getState().compose.get('fetching_link') && + !composeStateForbidsLink(getState().compose), + }, +); + +// Ideally this would cancel the action and the HTTP request, but this is good enough +export const cancelPasteLinkCompose = createAction( + 'compose/cancelPasteLinkCompose', +); + export const quoteComposeCancel = createAction('compose/quoteComposeCancel'); export const setComposeQuotePolicy = createAction( diff --git a/app/javascript/flavours/glitch/actions/importer/emoji.ts b/app/javascript/flavours/glitch/actions/importer/emoji.ts new file mode 100644 index 00000000000000..ef834ab50fe54d --- /dev/null +++ b/app/javascript/flavours/glitch/actions/importer/emoji.ts @@ -0,0 +1,22 @@ +import type { ApiCustomEmojiJSON } from '@/flavours/glitch/api_types/custom_emoji'; +import { loadCustomEmoji } from '@/flavours/glitch/features/emoji'; + +export async function importCustomEmoji(emojis: ApiCustomEmojiJSON[]) { + if (emojis.length === 0) { + return; + } + + // First, check if we already have them all. + const { searchCustomEmojisByShortcodes, clearEtag } = + await import('@/flavours/glitch/features/emoji/database'); + + const existingEmojis = await searchCustomEmojisByShortcodes( + emojis.map((emoji) => emoji.shortcode), + ); + + // If there's a mismatch, re-import all custom emojis. + if (existingEmojis.length < emojis.length) { + await clearEtag('custom'); + await loadCustomEmoji(); + } +} diff --git a/app/javascript/flavours/glitch/actions/importer/index.js b/app/javascript/flavours/glitch/actions/importer/index.js index f1aabe27474aa1..a25df5705e17a4 100644 --- a/app/javascript/flavours/glitch/actions/importer/index.js +++ b/app/javascript/flavours/glitch/actions/importer/index.js @@ -1,6 +1,7 @@ import { createPollFromServerJSON } from 'flavours/glitch/models/poll'; import { importAccounts } from './accounts'; +import { importCustomEmoji } from './emoji'; import { normalizeStatus } from './normalizer'; import { importPolls } from './polls'; @@ -39,6 +40,10 @@ export function importFetchedAccounts(accounts) { if (account.moved) { processAccount(account.moved); } + + if (account.emojis && account.username === account.acct) { + importCustomEmoji(account.emojis); + } } accounts.forEach(processAccount); @@ -46,11 +51,11 @@ export function importFetchedAccounts(accounts) { return importAccounts({ accounts: normalAccounts }); } -export function importFetchedStatus(status) { - return importFetchedStatuses([status]); +export function importFetchedStatus(status, options = {}) { + return importFetchedStatuses([status], options); } -export function importFetchedStatuses(statuses) { +export function importFetchedStatuses(statuses, options = {}) { return (dispatch, getState) => { const accounts = []; const normalStatuses = []; @@ -58,7 +63,7 @@ export function importFetchedStatuses(statuses) { const filters = []; function processStatus(status) { - pushUnique(normalStatuses, normalizeStatus(status, getState().getIn(['statuses', status.id]), getState().get('local_settings'))); + pushUnique(normalStatuses, normalizeStatus(status, getState().getIn(['statuses', status.id]), { ...options, settings: getState().get('local_settings') })); pushUnique(accounts, status.account); if (status.filtered) { @@ -80,6 +85,10 @@ export function importFetchedStatuses(statuses) { if (status.card) { status.card.authors.forEach(author => author.account && pushUnique(accounts, author.account)); } + + if (status.emojis && status.account.username === status.account.acct) { + importCustomEmoji(status.emojis); + } } statuses.forEach(processStatus); diff --git a/app/javascript/flavours/glitch/actions/importer/normalizer.js b/app/javascript/flavours/glitch/actions/importer/normalizer.js index e9972aa90c49e1..366375c3f634f3 100644 --- a/app/javascript/flavours/glitch/actions/importer/normalizer.js +++ b/app/javascript/flavours/glitch/actions/importer/normalizer.js @@ -1,10 +1,9 @@ import escapeTextContentForBrowser from 'escape-html'; -import { makeEmojiMap } from 'flavours/glitch/models/custom_emoji'; - -import emojify from '../../features/emoji/emoji'; import { autoHideCW } from '../../utils/content_warning'; +import { importCustomEmoji } from './emoji'; + const domParser = new DOMParser(); export function searchTextFromRawStatus (status) { @@ -30,9 +29,12 @@ function stripQuoteFallback(text) { return wrapper.innerHTML; } -export function normalizeStatus(status, normalOldStatus, settings) { +export function normalizeStatus(status, normalOldStatus, { settings, bogusQuotePolicy = false }) { const normalStatus = { ...status }; + if (bogusQuotePolicy) + normalStatus.quote_approval = null; + normalStatus.account = status.account.id; if (status.reblog && status.reblog.id) { @@ -80,11 +82,10 @@ export function normalizeStatus(status, normalOldStatus, settings) { } else { const spoilerText = normalStatus.spoiler_text || ''; const searchContent = ([spoilerText, status.content].concat((status.poll && status.poll.options) ? status.poll.options.map(option => option.title) : [])).concat(status.media_attachments.map(att => att.description)).join('\n\n').replace(//g, '\n').replace(/<\/p>

/g, '\n\n'); - const emojiMap = makeEmojiMap(normalStatus.emojis); normalStatus.search_index = domParser.parseFromString(searchContent, 'text/html').documentElement.textContent; - normalStatus.contentHtml = emojify(normalStatus.content, emojiMap); - normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(spoilerText), emojiMap); + normalStatus.contentHtml = normalStatus.content; + normalStatus.spoilerHtml = escapeTextContentForBrowser(spoilerText); normalStatus.hidden = (spoilerText.length > 0 || normalStatus.sensitive) && autoHideCW(settings, spoilerText); // Remove quote fallback link from the DOM so it doesn't mess with paragraph margins @@ -105,6 +106,8 @@ export function normalizeStatus(status, normalOldStatus, settings) { } if (normalOldStatus) { + normalStatus.quote_approval ||= normalOldStatus.get('quote_approval'); + const list = normalOldStatus.get('media_attachments'); if (normalStatus.media_attachments && list) { normalStatus.media_attachments.forEach(item => { @@ -120,14 +123,12 @@ export function normalizeStatus(status, normalOldStatus, settings) { } export function normalizeStatusTranslation(translation, status) { - const emojiMap = makeEmojiMap(status.get('emojis').toJS()); - const normalTranslation = { detected_source_language: translation.detected_source_language, language: translation.language, provider: translation.provider, - contentHtml: emojify(translation.content, emojiMap), - spoilerHtml: emojify(escapeTextContentForBrowser(translation.spoiler_text), emojiMap), + contentHtml: translation.content, + spoilerHtml: escapeTextContentForBrowser(translation.spoiler_text), spoiler_text: translation.spoiler_text, }; @@ -141,9 +142,12 @@ export function normalizeStatusTranslation(translation, status) { export function normalizeAnnouncement(announcement) { const normalAnnouncement = { ...announcement }; - const emojiMap = makeEmojiMap(normalAnnouncement.emojis); - normalAnnouncement.contentHtml = emojify(normalAnnouncement.content, emojiMap); + normalAnnouncement.contentHtml = normalAnnouncement.content; + + if (normalAnnouncement.emojis) { + importCustomEmoji(normalAnnouncement.emojis); + } return normalAnnouncement; } diff --git a/app/javascript/flavours/glitch/actions/search.ts b/app/javascript/flavours/glitch/actions/search.ts index d0c3e01c7701b2..ba11d8e79e88cf 100644 --- a/app/javascript/flavours/glitch/actions/search.ts +++ b/app/javascript/flavours/glitch/actions/search.ts @@ -147,7 +147,7 @@ export const hydrateSearch = createAppAsyncThunk( 'search/hydrate', (_args, { dispatch, getState }) => { const me = getState().meta.get('me') as string; - const history = searchHistory.get(me) as RecentSearch[] | null; + const history = searchHistory.get(me); if (history !== null) { dispatch(updateSearchHistory(history)); diff --git a/app/javascript/flavours/glitch/actions/statuses.js b/app/javascript/flavours/glitch/actions/statuses.js index bfb20dc4014a88..6eb5d904bc5f51 100644 --- a/app/javascript/flavours/glitch/actions/statuses.js +++ b/app/javascript/flavours/glitch/actions/statuses.js @@ -85,6 +85,8 @@ export function fetchStatus(id, { dispatch(fetchStatusSuccess(skipLoading)); }).catch(error => { dispatch(fetchStatusFail(id, error, skipLoading, parentQuotePostId)); + if (error.status === 404) + dispatch(deleteFromTimelines(id)); }); }; } @@ -204,8 +206,8 @@ export function deleteStatusFail(id, error) { }; } -export const updateStatus = status => dispatch => - dispatch(importFetchedStatus(status)); +export const updateStatus = (status, { bogusQuotePolicy }) => dispatch => + dispatch(importFetchedStatus(status, { bogusQuotePolicy })); export function muteStatus(id) { return (dispatch) => { diff --git a/app/javascript/flavours/glitch/actions/statuses_typed.ts b/app/javascript/flavours/glitch/actions/statuses_typed.ts index 4472cbad2540bc..039fbf3ac5110a 100644 --- a/app/javascript/flavours/glitch/actions/statuses_typed.ts +++ b/app/javascript/flavours/glitch/actions/statuses_typed.ts @@ -9,8 +9,9 @@ import { importFetchedStatuses } from './importer'; export const fetchContext = createDataLoadingThunk( 'status/context', - ({ statusId }: { statusId: string }) => apiGetContext(statusId), - ({ context, refresh }, { dispatch }) => { + ({ statusId }: { statusId: string; prefetchOnly?: boolean }) => + apiGetContext(statusId), + ({ context, refresh }, { dispatch, actionArg: { prefetchOnly = false } }) => { const statuses = context.ancestors.concat(context.descendants); dispatch(importFetchedStatuses(statuses)); @@ -18,6 +19,7 @@ export const fetchContext = createDataLoadingThunk( return { context, refresh, + prefetchOnly, }; }, ); @@ -26,6 +28,14 @@ export const completeContextRefresh = createAction<{ statusId: string }>( 'status/context/complete', ); +export const showPendingReplies = createAction<{ statusId: string }>( + 'status/context/showPendingReplies', +); + +export const clearPendingReplies = createAction<{ statusId: string }>( + 'status/context/clearPendingReplies', +); + export const setStatusQuotePolicy = createDataLoadingThunk( 'status/setQuotePolicy', ({ statusId, policy }: { statusId: string; policy: ApiQuotePolicy }) => { diff --git a/app/javascript/flavours/glitch/actions/store.js b/app/javascript/flavours/glitch/actions/store.js index b2d19575c4c5d6..2a45373c9e2545 100644 --- a/app/javascript/flavours/glitch/actions/store.js +++ b/app/javascript/flavours/glitch/actions/store.js @@ -37,7 +37,9 @@ export function hydrateStore(rawState) { dispatch(hydrateCompose()); dispatch(hydrateSearch()); - dispatch(importFetchedAccounts(Object.values(rawState.accounts))); + if (rawState.accounts) { + dispatch(importFetchedAccounts(Object.values(rawState.accounts))); + } dispatch(saveSettings()); }; } diff --git a/app/javascript/flavours/glitch/actions/streaming.js b/app/javascript/flavours/glitch/actions/streaming.js index 93bd7dc5f44098..f4bd0ff373264e 100644 --- a/app/javascript/flavours/glitch/actions/streaming.js +++ b/app/javascript/flavours/glitch/actions/streaming.js @@ -32,27 +32,38 @@ import { const randomUpTo = max => Math.floor(Math.random() * Math.floor(max)); +/** + * @typedef {import('flavours/glitch/store').AppDispatch} Dispatch + * @typedef {import('flavours/glitch/store').GetState} GetState + * @typedef {import('redux').UnknownAction} UnknownAction + * @typedef {function(Dispatch, GetState): Promise} FallbackFunction + */ + /** * @param {string} timelineId * @param {string} channelName * @param {Object.} params * @param {Object} options - * @param {function(Function, Function): Promise} [options.fallback] - * @param {function(): void} [options.fillGaps] + * @param {FallbackFunction} [options.fallback] + * @param {function(): UnknownAction} [options.fillGaps] * @param {function(object): boolean} [options.accept] * @returns {function(): void} */ export const connectTimelineStream = (timelineId, channelName, params = {}, options = {}) => { const { messages } = getLocale(); + // Public streams are currently not returning personalized quote policies + const bogusQuotePolicy = channelName.startsWith('public') || channelName.startsWith('hashtag'); + return connectStream(channelName, params, (dispatch, getState) => { + // @ts-ignore const locale = getState().getIn(['meta', 'locale']); // @ts-expect-error let pollingId; /** - * @param {function(Function, Function): Promise} fallback + * @param {FallbackFunction} fallback */ const useFallback = async fallback => { @@ -89,11 +100,11 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti switch (data.event) { case 'update': // @ts-expect-error - dispatch(updateTimeline(timelineId, JSON.parse(data.payload), options.accept)); + dispatch(updateTimeline(timelineId, JSON.parse(data.payload), { accept: options.accept, bogusQuotePolicy })); break; case 'status.update': // @ts-expect-error - dispatch(updateStatus(JSON.parse(data.payload))); + dispatch(updateStatus(JSON.parse(data.payload), { bogusQuotePolicy })); break; case 'delete': dispatch(deleteFromTimelines(data.payload)); @@ -132,7 +143,7 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti }; /** - * @param {Function} dispatch + * @param {Dispatch} dispatch */ async function refreshHomeTimelineAndNotification(dispatch) { await dispatch(expandHomeTimeline({ maxId: undefined })); @@ -151,7 +162,11 @@ async function refreshHomeTimelineAndNotification(dispatch) { * @returns {function(): void} */ export const connectUserStream = () => - connectTimelineStream('home', 'user', {}, { fallback: refreshHomeTimelineAndNotification, fillGaps: fillHomeTimelineGaps }); + connectTimelineStream('home', 'user', {}, { + fallback: refreshHomeTimelineAndNotification, + // @ts-expect-error + fillGaps: fillHomeTimelineGaps + }); /** * @param {Object} options @@ -159,7 +174,10 @@ export const connectUserStream = () => * @returns {function(): void} */ export const connectCommunityStream = ({ onlyMedia } = {}) => - connectTimelineStream(`community${onlyMedia ? ':media' : ''}`, `public:local${onlyMedia ? ':media' : ''}`, {}, { fillGaps: () => (fillCommunityTimelineGaps({ onlyMedia })) }); + connectTimelineStream(`community${onlyMedia ? ':media' : ''}`, `public:local${onlyMedia ? ':media' : ''}`, {}, { + // @ts-expect-error + fillGaps: () => (fillCommunityTimelineGaps({ onlyMedia })) + }); /** * @param {Object} options @@ -169,7 +187,10 @@ export const connectCommunityStream = ({ onlyMedia } = {}) => * @returns {function(): void} */ export const connectPublicStream = ({ onlyMedia, onlyRemote, allowLocalOnly } = {}) => - connectTimelineStream(`public${onlyRemote ? ':remote' : (allowLocalOnly ? ':allow_local_only' : '')}${onlyMedia ? ':media' : ''}`, `public${onlyRemote ? ':remote' : (allowLocalOnly ? ':allow_local_only' : '')}${onlyMedia ? ':media' : ''}`, {}, { fillGaps: () => fillPublicTimelineGaps({ onlyMedia, onlyRemote, allowLocalOnly }) }); + connectTimelineStream(`public${onlyRemote ? ':remote' : (allowLocalOnly ? ':allow_local_only' : '')}${onlyMedia ? ':media' : ''}`, `public${onlyRemote ? ':remote' : (allowLocalOnly ? ':allow_local_only' : '')}${onlyMedia ? ':media' : ''}`, {}, { + // @ts-expect-error + fillGaps: () => fillPublicTimelineGaps({ onlyMedia, onlyRemote, allowLocalOnly }) + }); /** * @param {string} columnId @@ -192,4 +213,7 @@ export const connectDirectStream = () => * @returns {function(): void} */ export const connectListStream = listId => - connectTimelineStream(`list:${listId}`, 'list', { list: listId }, { fillGaps: () => fillListTimelineGaps(listId) }); + connectTimelineStream(`list:${listId}`, 'list', { list: listId }, { + // @ts-expect-error + fillGaps: () => fillListTimelineGaps(listId) + }); diff --git a/app/javascript/flavours/glitch/actions/timelines.js b/app/javascript/flavours/glitch/actions/timelines.js index 1d5a696c92da5b..27643e991d032b 100644 --- a/app/javascript/flavours/glitch/actions/timelines.js +++ b/app/javascript/flavours/glitch/actions/timelines.js @@ -7,7 +7,7 @@ import { toServerSideType } from 'flavours/glitch/utils/filters'; import { importFetchedStatus, importFetchedStatuses } from './importer'; import { submitMarkers } from './markers'; -import {timelineDelete} from './timelines_typed'; +import { timelineDelete } from './timelines_typed'; export { disconnectTimeline } from './timelines_typed'; @@ -25,15 +25,21 @@ export const TIMELINE_CONNECT = 'TIMELINE_CONNECT'; export const TIMELINE_MARK_AS_PARTIAL = 'TIMELINE_MARK_AS_PARTIAL'; export const TIMELINE_INSERT = 'TIMELINE_INSERT'; +// When adding new special markers here, make sure to update TIMELINE_NON_STATUS_MARKERS in actions/timelines_typed.js export const TIMELINE_SUGGESTIONS = 'inline-follow-suggestions'; export const TIMELINE_GAP = null; +export const TIMELINE_NON_STATUS_MARKERS = [ + TIMELINE_GAP, + TIMELINE_SUGGESTIONS, +]; + export const loadPending = timeline => ({ type: TIMELINE_LOAD_PENDING, timeline, }); -export function updateTimeline(timeline, status, accept) { +export function updateTimeline(timeline, status, { accept = undefined, bogusQuotePolicy = false } = {}) { return (dispatch, getState) => { if (typeof accept === 'function' && !accept(status)) { return; @@ -55,7 +61,7 @@ export function updateTimeline(timeline, status, accept) { filtered = filters.length > 0; } - dispatch(importFetchedStatus(status)); + dispatch(importFetchedStatus(status, { bogusQuotePolicy })); dispatch({ type: TIMELINE_UPDATE, diff --git a/app/javascript/flavours/glitch/actions/timelines_typed.ts b/app/javascript/flavours/glitch/actions/timelines_typed.ts index 485b94ed524fd3..28cbf4058eaace 100644 --- a/app/javascript/flavours/glitch/actions/timelines_typed.ts +++ b/app/javascript/flavours/glitch/actions/timelines_typed.ts @@ -2,6 +2,12 @@ import { createAction } from '@reduxjs/toolkit'; import { usePendingItems as preferPendingItems } from 'flavours/glitch/initial_state'; +import { TIMELINE_NON_STATUS_MARKERS } from './timelines'; + +export function isNonStatusId(value: unknown) { + return TIMELINE_NON_STATUS_MARKERS.includes(value as string | null); +} + export const disconnectTimeline = createAction( 'timeline/disconnect', ({ timeline }: { timeline: string }) => ({ diff --git a/app/javascript/flavours/glitch/api/annual_report.ts b/app/javascript/flavours/glitch/api/annual_report.ts new file mode 100644 index 00000000000000..dc080035d49f8a --- /dev/null +++ b/app/javascript/flavours/glitch/api/annual_report.ts @@ -0,0 +1,38 @@ +import api, { apiRequestGet, getAsyncRefreshHeader } from '../api'; +import type { ApiAccountJSON } from '../api_types/accounts'; +import type { ApiStatusJSON } from '../api_types/statuses'; +import type { AnnualReport } from '../models/annual_report'; + +export type ApiAnnualReportState = + | 'available' + | 'generating' + | 'eligible' + | 'ineligible'; + +export const apiGetAnnualReportState = async (year: number) => { + const response = await api().get<{ state: ApiAnnualReportState }>( + `/api/v1/annual_reports/${year}/state`, + ); + + return { + state: response.data.state, + refresh: getAsyncRefreshHeader(response), + }; +}; + +export const apiRequestGenerateAnnualReport = async (year: number) => { + const response = await api().post(`/api/v1/annual_reports/${year}/generate`); + + return { + refresh: getAsyncRefreshHeader(response), + }; +}; + +export interface ApiAnnualReportResponse { + annual_reports: AnnualReport[]; + accounts: ApiAccountJSON[]; + statuses: ApiStatusJSON[]; +} + +export const apiGetAnnualReport = (year: number) => + apiRequestGet(`v1/annual_reports/${year}`); diff --git a/app/javascript/flavours/glitch/api_types/announcements.ts b/app/javascript/flavours/glitch/api_types/announcements.ts new file mode 100644 index 00000000000000..03e8922d8f189f --- /dev/null +++ b/app/javascript/flavours/glitch/api_types/announcements.ts @@ -0,0 +1,28 @@ +// See app/serializers/rest/announcement_serializer.rb + +import type { ApiCustomEmojiJSON } from './custom_emoji'; +import type { ApiMentionJSON, ApiStatusJSON, ApiTagJSON } from './statuses'; + +export interface ApiAnnouncementJSON { + id: string; + content: string; + starts_at: null | string; + ends_at: null | string; + all_day: boolean; + published_at: string; + updated_at: null | string; + read: boolean; + mentions: ApiMentionJSON[]; + statuses: ApiStatusJSON[]; + tags: ApiTagJSON[]; + emojis: ApiCustomEmojiJSON[]; + reactions: ApiAnnouncementReactionJSON[]; +} + +export interface ApiAnnouncementReactionJSON { + name: string; + count: number; + me: boolean; + url?: string; + static_url?: string; +} diff --git a/app/javascript/flavours/glitch/api_types/custom_emoji.ts b/app/javascript/flavours/glitch/api_types/custom_emoji.ts index 05144d6f68d0e8..099ef0b88b8f25 100644 --- a/app/javascript/flavours/glitch/api_types/custom_emoji.ts +++ b/app/javascript/flavours/glitch/api_types/custom_emoji.ts @@ -1,8 +1,9 @@ -// See app/serializers/rest/account_serializer.rb +// See app/serializers/rest/custom_emoji_serializer.rb export interface ApiCustomEmojiJSON { shortcode: string; static_url: string; url: string; category?: string; + featured?: boolean; visible_in_picker: boolean; } diff --git a/app/javascript/flavours/glitch/api_types/notifications.ts b/app/javascript/flavours/glitch/api_types/notifications.ts index c4556fa8e5577a..50b03cc109e3c7 100644 --- a/app/javascript/flavours/glitch/api_types/notifications.ts +++ b/app/javascript/flavours/glitch/api_types/notifications.ts @@ -102,8 +102,7 @@ export interface ApiAccountWarningJSON { appeal: unknown; } -interface ModerationWarningNotificationGroupJSON - extends BaseNotificationGroupJSON { +interface ModerationWarningNotificationGroupJSON extends BaseNotificationGroupJSON { type: 'moderation_warning'; moderation_warning: ApiAccountWarningJSON; } @@ -123,14 +122,12 @@ export interface ApiAccountRelationshipSeveranceEventJSON { created_at: string; } -interface AccountRelationshipSeveranceNotificationGroupJSON - extends BaseNotificationGroupJSON { +interface AccountRelationshipSeveranceNotificationGroupJSON extends BaseNotificationGroupJSON { type: 'severed_relationships'; event: ApiAccountRelationshipSeveranceEventJSON; } -interface AccountRelationshipSeveranceNotificationJSON - extends BaseNotificationJSON { +interface AccountRelationshipSeveranceNotificationJSON extends BaseNotificationJSON { type: 'severed_relationships'; event: ApiAccountRelationshipSeveranceEventJSON; } diff --git a/app/javascript/flavours/glitch/api_types/statuses.ts b/app/javascript/flavours/glitch/api_types/statuses.ts index 4ecb34bfe1bb6c..36468ce3bf981c 100644 --- a/app/javascript/flavours/glitch/api_types/statuses.ts +++ b/app/javascript/flavours/glitch/api_types/statuses.ts @@ -51,7 +51,7 @@ export interface ApiPreviewCardJSON { html: string; width: number; height: number; - image: string; + image: string | null; image_description: string; embed_url: string; blurhash: string; diff --git a/app/javascript/flavours/glitch/common.ts b/app/javascript/flavours/glitch/common.ts index e621a24e39fe46..33d2b5ad171f40 100644 --- a/app/javascript/flavours/glitch/common.ts +++ b/app/javascript/flavours/glitch/common.ts @@ -1,9 +1,5 @@ -import Rails from '@rails/ujs'; +import { setupLinkListeners } from './utils/links'; export function start() { - try { - Rails.start(); - } catch { - // If called twice - } + setupLinkListeners(); } diff --git a/app/javascript/flavours/glitch/components/account.tsx b/app/javascript/flavours/glitch/components/account/index.tsx similarity index 97% rename from app/javascript/flavours/glitch/components/account.tsx rename to app/javascript/flavours/glitch/components/account/index.tsx index 826d3c3ebb49b0..bcb84f2f4e8016 100644 --- a/app/javascript/flavours/glitch/components/account.tsx +++ b/app/javascript/flavours/glitch/components/account/index.tsx @@ -4,6 +4,7 @@ import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; import classNames from 'classnames'; +import { EmojiHTML } from '@/flavours/glitch/components/emoji/html'; import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; import { blockAccount, @@ -33,7 +34,7 @@ import { me } from 'flavours/glitch/initial_state'; import type { MenuItem } from 'flavours/glitch/models/dropdown_menu'; import { useAppSelector, useAppDispatch } from 'flavours/glitch/store'; -import { Permalink } from './permalink'; +import { Permalink } from '../permalink'; const messages = defineMessages({ follow: { id: 'account.follow', defaultMessage: 'Follow' }, @@ -333,9 +334,10 @@ export const Account: React.FC = ({ {account && withBio && (account.note.length > 0 ? ( -

) : (
diff --git a/app/javascript/flavours/glitch/components/account_bio.tsx b/app/javascript/flavours/glitch/components/account_bio.tsx index ea9cb2a6288b2a..6d4ab1ddd49b2c 100644 --- a/app/javascript/flavours/glitch/components/account_bio.tsx +++ b/app/javascript/flavours/glitch/components/account_bio.tsx @@ -1,10 +1,9 @@ -import { useCallback } from 'react'; +import classNames from 'classnames'; -import { useLinks } from 'flavours/glitch/hooks/useLinks'; - -import { EmojiHTML } from '../features/emoji/emoji_html'; import { useAppSelector } from '../store'; -import { isModernEmojiEnabled } from '../utils/environment'; + +import { EmojiHTML } from './emoji/html'; +import { useElementHandledLink } from './status/handled_link'; interface AccountBioProps { className: string; @@ -17,22 +16,16 @@ export const AccountBio: React.FC = ({ accountId, showDropdown = false, }) => { - const handleClick = useLinks(showDropdown); - const handleNodeChange = useCallback( - (node: HTMLDivElement | null) => { - if (!showDropdown || !node || node.childNodes.length === 0) { - return; - } - addDropdownToHashtags(node, accountId); - }, - [showDropdown, accountId], - ); + const htmlHandlers = useElementHandledLink({ + hashtagAccountId: showDropdown ? accountId : undefined, + }); + const note = useAppSelector((state) => { const account = state.accounts.get(accountId); if (!account) { return ''; } - return isModernEmojiEnabled() ? account.note : account.note_emojified; + return account.note_emojified; }); const extraEmojis = useAppSelector((state) => { const account = state.accounts.get(accountId); @@ -44,33 +37,11 @@ export const AccountBio: React.FC = ({ } return ( -
- -
+ ); }; - -function addDropdownToHashtags(node: HTMLElement | null, accountId: string) { - if (!node) { - return; - } - for (const childNode of node.childNodes) { - if (!(childNode instanceof HTMLElement)) { - continue; - } - if ( - childNode instanceof HTMLAnchorElement && - (childNode.classList.contains('hashtag') || - childNode.innerText.startsWith('#')) && - !childNode.dataset.menuHashtag - ) { - childNode.dataset.menuHashtag = accountId; - } else if (childNode.childNodes.length > 0) { - addDropdownToHashtags(childNode, accountId); - } - } -} diff --git a/app/javascript/flavours/glitch/components/account_fields.tsx b/app/javascript/flavours/glitch/components/account_fields.tsx index 768eb1fa4b343d..422dcc4b89fc75 100644 --- a/app/javascript/flavours/glitch/components/account_fields.tsx +++ b/app/javascript/flavours/glitch/components/account_fields.tsx @@ -1,42 +1,70 @@ +import { useIntl } from 'react-intl'; + import classNames from 'classnames'; import CheckIcon from '@/material-icons/400-24px/check.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; -import { useLinks } from 'flavours/glitch/hooks/useLinks'; import type { Account } from 'flavours/glitch/models/account'; -export const AccountFields: React.FC<{ - fields: Account['fields']; - limit: number; -}> = ({ fields, limit = -1 }) => { - const handleClick = useLinks(); +import { CustomEmojiProvider } from './emoji/context'; +import { EmojiHTML } from './emoji/html'; +import { useElementHandledLink } from './status/handled_link'; + +export const AccountFields: React.FC> = ({ + fields, + emojis, +}) => { + const intl = useIntl(); + const htmlHandlers = useElementHandledLink(); if (fields.size === 0) { return null; } return ( -
- {fields.take(limit).map((pair, i) => ( -
-
+ {fields.map((pair, i) => ( +
+ -
- {pair.get('verified_at') && ( - - )} - + {pair.verified_at && ( + + + + )}{' '} +
))} -
+ ); }; + +const dateFormatOptions: Intl.DateTimeFormatOptions = { + month: 'short', + day: 'numeric', + year: 'numeric', + hour: '2-digit', + minute: '2-digit', +}; diff --git a/app/javascript/flavours/glitch/components/alert/index.tsx b/app/javascript/flavours/glitch/components/alert/index.tsx index eb0abcb51887d7..3389c9d9fe97ff 100644 --- a/app/javascript/flavours/glitch/components/alert/index.tsx +++ b/app/javascript/flavours/glitch/components/alert/index.tsx @@ -49,7 +49,11 @@ export const Alert: React.FC<{ {hasAction && ( - )} diff --git a/app/javascript/flavours/glitch/components/alt_text_badge.tsx b/app/javascript/flavours/glitch/components/alt_text_badge.tsx index 9b3748b2ca63f3..be6f88bf83cd06 100644 --- a/app/javascript/flavours/glitch/components/alt_text_badge.tsx +++ b/app/javascript/flavours/glitch/components/alt_text_badge.tsx @@ -47,7 +47,7 @@ export const AltTextBadge: React.FC<{ description: string }> = ({ rootClose onHide={handleClose} show={open} - target={anchorRef.current} + target={anchorRef} placement='top-end' flip offset={offset} diff --git a/app/javascript/flavours/glitch/components/autosuggest_input.jsx b/app/javascript/flavours/glitch/components/autosuggest_input.jsx index f707a18e1d697f..9e342a353a169e 100644 --- a/app/javascript/flavours/glitch/components/autosuggest_input.jsx +++ b/app/javascript/flavours/glitch/components/autosuggest_input.jsx @@ -28,7 +28,7 @@ const textAtCursorMatchesToken = (str, caretPosition, searchTokens) => { return [null, null]; } - word = word.trim().toLowerCase(); + word = word.trim(); if (word.length > 0) { return [left + 1, word]; @@ -61,7 +61,7 @@ export default class AutosuggestInput extends ImmutablePureComponent { static defaultProps = { autoFocus: true, - searchTokens: ['@', ':', '#'], + searchTokens: ['@', '@', ':', '#', '#'], }; state = { @@ -159,8 +159,8 @@ export default class AutosuggestInput extends ImmutablePureComponent { this.input.focus(); }; - UNSAFE_componentWillReceiveProps (nextProps) { - if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden && this.state.focused) { + componentDidUpdate (prevProps) { + if (prevProps.suggestions !== this.props.suggestions && this.props.suggestions.size > 0 && this.state.suggestionsHidden && this.state.focused) { this.setState({ suggestionsHidden: false }); } } diff --git a/app/javascript/flavours/glitch/components/autosuggest_textarea.jsx b/app/javascript/flavours/glitch/components/autosuggest_textarea.jsx index de5accc4b287df..fae078da31b379 100644 --- a/app/javascript/flavours/glitch/components/autosuggest_textarea.jsx +++ b/app/javascript/flavours/glitch/components/autosuggest_textarea.jsx @@ -25,11 +25,11 @@ const textAtCursorMatchesToken = (str, caretPosition) => { word = str.slice(left, right + caretPosition); } - if (!word || word.trim().length < 3 || ['@', ':', '#'].indexOf(word[0]) === -1) { + if (!word || word.trim().length < 3 || ['@', '@', ':', '#', '#'].indexOf(word[0]) === -1) { return [null, null]; } - word = word.trim().toLowerCase(); + word = word.trim(); if (word.length > 0) { return [left + 1, word]; @@ -50,6 +50,7 @@ const AutosuggestTextarea = forwardRef(({ onKeyUp, onKeyDown, onPaste, + onDrop, onFocus, autoFocus = true, lang, @@ -150,12 +151,15 @@ const AutosuggestTextarea = forwardRef(({ }, [suggestions, onSuggestionSelected, textareaRef]); const handlePaste = useCallback((e) => { - if (e.clipboardData && e.clipboardData.files.length === 1) { - onPaste(e.clipboardData.files); - e.preventDefault(); - } + onPaste(e); }, [onPaste]); + const handleDrop = useCallback((e) => { + if (onDrop) { + onDrop(e); + } + }, [onDrop]); + // Show the suggestions again whenever they change and the textarea is focused useEffect(() => { if (suggestions.size > 0 && textareaRef.current === document.activeElement) { @@ -207,6 +211,7 @@ const AutosuggestTextarea = forwardRef(({ onFocus={handleFocus} onBlur={handleBlur} onPaste={handlePaste} + onDrop={handleDrop} dir='auto' aria-autocomplete='list' aria-label={placeholder} @@ -238,6 +243,7 @@ AutosuggestTextarea.propTypes = { onKeyUp: PropTypes.func, onKeyDown: PropTypes.func, onPaste: PropTypes.func.isRequired, + onDrop: PropTypes.func, onFocus:PropTypes.func, autoFocus: PropTypes.bool, lang: PropTypes.string, diff --git a/app/javascript/flavours/glitch/components/blurhash.tsx b/app/javascript/flavours/glitch/components/blurhash.tsx index 8e2a8af23e5937..b7331755b7f0dd 100644 --- a/app/javascript/flavours/glitch/components/blurhash.tsx +++ b/app/javascript/flavours/glitch/components/blurhash.tsx @@ -30,9 +30,12 @@ const Blurhash: React.FC = ({ try { const pixels = decode(hash, width, height); const ctx = canvas.getContext('2d'); - const imageData = new ImageData(pixels, width, height); + const imageData = ctx?.createImageData(width, height); + imageData?.data.set(pixels); - ctx?.putImageData(imageData, 0, 0); + if (imageData) { + ctx?.putImageData(imageData, 0, 0); + } } catch (err) { console.error('Blurhash decoding failure', { err, hash }); } diff --git a/app/javascript/flavours/glitch/components/button/index.tsx b/app/javascript/flavours/glitch/components/button/index.tsx index 4ef61e1e14b1fa..fb107c78e0047d 100644 --- a/app/javascript/flavours/glitch/components/button/index.tsx +++ b/app/javascript/flavours/glitch/components/button/index.tsx @@ -5,8 +5,10 @@ import classNames from 'classnames'; import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator'; -interface BaseProps - extends Omit, 'children'> { +interface BaseProps extends Omit< + React.ButtonHTMLAttributes, + 'children' +> { block?: boolean; secondary?: boolean; plain?: boolean; @@ -78,6 +80,7 @@ export const Button: React.FC = ({ aria-live={loading !== undefined ? 'polite' : undefined} onClick={handleClick} title={title} + // eslint-disable-next-line react/button-has-type -- set correctly via TS type={type} {...props} > diff --git a/app/javascript/flavours/glitch/components/carousel/carousel.stories.tsx b/app/javascript/flavours/glitch/components/carousel/carousel.stories.tsx new file mode 100644 index 00000000000000..5117bc08e3530d --- /dev/null +++ b/app/javascript/flavours/glitch/components/carousel/carousel.stories.tsx @@ -0,0 +1,126 @@ +import type { FC } from 'react'; + +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { fn, userEvent, expect } from 'storybook/test'; + +import type { CarouselProps } from './index'; +import { Carousel } from './index'; + +interface TestSlideProps { + id: number; + text: string; + color: string; +} + +const TestSlide: FC = ({ + active, + text, + color, +}) => ( +
+ {text} +
+); + +const slides: TestSlideProps[] = [ + { + id: 1, + text: 'first', + color: 'red', + }, + { + id: 2, + text: 'second', + color: 'pink', + }, + { + id: 3, + text: 'third', + color: 'orange', + }, +]; + +type StoryProps = Pick< + CarouselProps, + 'items' | 'renderItem' | 'emptyFallback' | 'onChangeSlide' +>; + +const meta = { + title: 'Components/Carousel', + args: { + items: slides, + renderItem(item, active) { + return ; + }, + onChangeSlide: fn(), + emptyFallback: 'No slides available', + }, + render(args) { + return ( + <> + + + + ); + }, + argTypes: { + emptyFallback: { + type: 'string', + }, + }, + tags: ['test'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + async play({ args, canvas }) { + const nextButton = await canvas.findByRole('button', { name: /next/i }); + const slides = await canvas.findAllByRole('group'); + await expect(slides).toHaveLength(slides.length); + + await userEvent.click(nextButton); + await expect(args.onChangeSlide).toHaveBeenCalledWith(1, slides[1]); + + await userEvent.click(nextButton); + await expect(args.onChangeSlide).toHaveBeenCalledWith(2, slides[2]); + + // Wrap around + await userEvent.click(nextButton); + await expect(args.onChangeSlide).toHaveBeenCalledWith(0, slides[0]); + }, +}; + +export const DifferentHeights: Story = { + args: { + items: slides.map((props, index) => ({ + ...props, + styles: { height: 100 + index * 100 }, + })), + }, +}; + +export const NoSlides: Story = { + args: { + items: [], + }, +}; diff --git a/app/javascript/flavours/glitch/components/carousel/index.tsx b/app/javascript/flavours/glitch/components/carousel/index.tsx new file mode 100644 index 00000000000000..bc287aa969132c --- /dev/null +++ b/app/javascript/flavours/glitch/components/carousel/index.tsx @@ -0,0 +1,244 @@ +import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react'; +import type { + ComponentPropsWithoutRef, + ComponentType, + ReactElement, + ReactNode, +} from 'react'; + +import type { MessageDescriptor } from 'react-intl'; +import { defineMessages, useIntl } from 'react-intl'; + +import classNames from 'classnames'; + +import { usePrevious } from '@dnd-kit/utilities'; +import { animated, useSpring } from '@react-spring/web'; +import { useDrag } from '@use-gesture/react'; + +import type { CarouselPaginationProps } from './pagination'; +import { CarouselPagination } from './pagination'; + +import './styles.scss'; + +const defaultMessages = defineMessages({ + previous: { id: 'lightbox.previous', defaultMessage: 'Previous' }, + next: { id: 'lightbox.next', defaultMessage: 'Next' }, + current: { + id: 'carousel.current', + defaultMessage: 'Slide {current, number} / {max, number}', + }, + slide: { + id: 'carousel.slide', + defaultMessage: 'Slide {current, number} of {max, number}', + }, +}); + +export type MessageKeys = keyof typeof defaultMessages; + +export interface CarouselSlideProps { + id: string | number; +} + +export type RenderSlideFn< + SlideProps extends CarouselSlideProps = CarouselSlideProps, +> = (item: SlideProps, active: boolean, index: number) => ReactElement; + +export interface CarouselProps< + SlideProps extends CarouselSlideProps = CarouselSlideProps, +> { + items: SlideProps[]; + renderItem: RenderSlideFn; + onChangeSlide?: (index: number, ref: Element) => void; + paginationComponent?: ComponentType | null; + paginationProps?: Partial; + messages?: Record; + emptyFallback?: ReactNode; + classNamePrefix?: string; + slideClassName?: string; +} + +export const Carousel = < + SlideProps extends CarouselSlideProps = CarouselSlideProps, +>({ + items, + renderItem, + onChangeSlide, + paginationComponent: Pagination = CarouselPagination, + paginationProps = {}, + messages = defaultMessages, + children, + emptyFallback = null, + className, + classNamePrefix = 'carousel', + slideClassName, + ...wrapperProps +}: CarouselProps & ComponentPropsWithoutRef<'div'>) => { + // Handle slide change + const [slideIndex, setSlideIndex] = useState(0); + const wrapperRef = useRef(null); + // Handle slide heights + const [currentSlideHeight, setCurrentSlideHeight] = useState( + () => wrapperRef.current?.scrollHeight ?? 0, + ); + const previousSlideHeight = usePrevious(currentSlideHeight); + const handleSlideChange = useCallback( + (direction: number) => { + setSlideIndex((prev) => { + const max = items.length - 1; + let newIndex = prev + direction; + if (newIndex < 0) { + newIndex = max; + } else if (newIndex > max) { + newIndex = 0; + } + + const slide = wrapperRef.current?.children[newIndex]; + if (slide) { + setCurrentSlideHeight(slide.scrollHeight); + if (slide instanceof HTMLElement) { + onChangeSlide?.(newIndex, slide); + } + } + + return newIndex; + }); + }, + [items.length, onChangeSlide], + ); + + const observerRef = useRef(null); + observerRef.current ??= new ResizeObserver(() => { + handleSlideChange(0); + }); + + const wrapperStyles = useSpring({ + x: `-${slideIndex * 100}%`, + height: currentSlideHeight, + // Don't animate from zero to the height of the initial slide + immediate: !previousSlideHeight, + }); + useLayoutEffect(() => { + // Update slide height when the component mounts + if (currentSlideHeight === 0) { + handleSlideChange(0); + } + }, [currentSlideHeight, handleSlideChange]); + + // Handle swiping animations + const bind = useDrag( + ({ swipe: [swipeX] }) => { + handleSlideChange(swipeX * -1); // Invert swipe as swiping left loads the next slide. + }, + { pointer: { capture: false } }, + ); + const handlePrev = useCallback(() => { + handleSlideChange(-1); + // We're focusing on the wrapper as the child slides can potentially be inert. + // Because of that, only the active slide can be focused anyway. + wrapperRef.current?.focus(); + }, [handleSlideChange]); + const handleNext = useCallback(() => { + handleSlideChange(1); + wrapperRef.current?.focus(); + }, [handleSlideChange]); + + const intl = useIntl(); + + if (items.length === 0) { + return emptyFallback; + } + + return ( +
+
+ {children} + {Pagination && items.length > 1 && ( + + )} +
+ + + {items.map((itemsProps, index) => ( + + item={itemsProps} + renderItem={renderItem} + observer={observerRef.current} + index={index} + key={`slide-${itemsProps.id}`} + className={classNames(`${classNamePrefix}__slide`, slideClassName, { + active: index === slideIndex, + })} + active={index === slideIndex} + /> + ))} + +
+ ); +}; + +type CarouselSlideWrapperProps = { + observer: ResizeObserver | null; + className: string; + active: boolean; + item: SlideProps; + index: number; +} & Pick, 'renderItem'>; + +const CarouselSlideWrapper = ({ + observer, + className, + active, + renderItem, + item, + index, +}: CarouselSlideWrapperProps) => { + const handleRef = useCallback( + (instance: HTMLDivElement | null) => { + if (observer && instance) { + observer.observe(instance); + } + }, + [observer], + ); + + const children = useMemo( + () => renderItem(item, active, index), + [renderItem, item, active, index], + ); + + return ( +
+ {children} +
+ ); +}; diff --git a/app/javascript/flavours/glitch/components/carousel/pagination.tsx b/app/javascript/flavours/glitch/components/carousel/pagination.tsx new file mode 100644 index 00000000000000..a2666f486fe2ab --- /dev/null +++ b/app/javascript/flavours/glitch/components/carousel/pagination.tsx @@ -0,0 +1,54 @@ +import type { FC, MouseEventHandler } from 'react'; + +import type { MessageDescriptor } from 'react-intl'; +import { useIntl } from 'react-intl'; + +import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react'; +import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'; + +import { IconButton } from '../icon_button'; + +import type { MessageKeys } from './index'; + +export interface CarouselPaginationProps { + onNext: MouseEventHandler; + onPrev: MouseEventHandler; + current: number; + max: number; + className?: string; + messages: Record; +} + +export const CarouselPagination: FC = ({ + onNext, + onPrev, + current, + max, + className = '', + messages, +}) => { + const intl = useIntl(); + return ( +
+ + + {intl.formatMessage(messages.current, { + current: current + 1, + max, + sr: (chunk) => {chunk}, + })} + + +
+ ); +}; diff --git a/app/javascript/flavours/glitch/components/carousel/styles.scss b/app/javascript/flavours/glitch/components/carousel/styles.scss new file mode 100644 index 00000000000000..bcd0bc7d3af76b --- /dev/null +++ b/app/javascript/flavours/glitch/components/carousel/styles.scss @@ -0,0 +1,28 @@ +.carousel { + gap: 16px; + overflow: hidden; + touch-action: pan-y; + + &__header { + padding: 8px 16px; + } + + &__pagination { + display: flex; + align-items: center; + justify-content: center; + gap: 4px; + } + + &__slides { + display: flex; + flex-wrap: nowrap; + align-items: start; + } + + &__slide { + flex: 0 0 100%; + width: 100%; + overflow: hidden; + } +} diff --git a/app/javascript/flavours/glitch/components/column_back_button.tsx b/app/javascript/flavours/glitch/components/column_back_button.tsx index c2afa788cb5d5b..89018c1853722d 100644 --- a/app/javascript/flavours/glitch/components/column_back_button.tsx +++ b/app/javascript/flavours/glitch/components/column_back_button.tsx @@ -30,7 +30,7 @@ export const ColumnBackButton: React.FC<{ onClick?: OnClickCallback }> = ({ const handleClick = useHandleClick(onClick); const component = ( - @@ -193,6 +196,7 @@ export const ColumnHeader: React.FC = ({ aria-label={intl.formatMessage(messages.moveRight)} className='icon-button column-header__setting-btn' onClick={handleMoveRight} + type='button' > @@ -203,6 +207,7 @@ export const ColumnHeader: React.FC = ({ @@ -248,9 +228,7 @@ export const DropdownMenu = ({ target={option.target ?? '_target'} data-method={option.method} rel='noopener' - ref={i === 0 ? handleFocusedItemRef : undefined} onClick={handleItemClick} - onKeyUp={handleItemKeyUp} data-index={i} > @@ -258,13 +236,7 @@ export const DropdownMenu = ({ ); } else { element = ( - + ); @@ -307,15 +279,7 @@ export const DropdownMenu = ({ })} > {items.map((option, i) => - renderItemMethod( - option, - i, - { - onClick: handleItemClick, - onKeyUp: handleItemKeyUp, - }, - i === 0 ? handleFocusedItemRef : undefined, - ), + renderItemMethod(option, i, handleItemClick), )} )} @@ -340,11 +304,12 @@ interface DropdownProps { */ scrollKey?: string; status?: ImmutableMap; + needsStatusRefresh?: boolean; forceDropdown?: boolean; renderItem?: RenderItemFn; renderHeader?: RenderHeaderFn; onOpen?: // Must use a union type for the full function as a union with void is not allowed. - | ((event: React.MouseEvent | React.KeyboardEvent) => void) + | ((event: React.MouseEvent | React.KeyboardEvent) => void) | ((event: React.MouseEvent | React.KeyboardEvent) => boolean); onItemClick?: ItemClickFn; } @@ -363,6 +328,7 @@ export const Dropdown = ({ placement = 'bottom', offset = [5, 5], status, + needsStatusRefresh, forceDropdown = false, renderItem, renderHeader, @@ -382,6 +348,7 @@ export const Dropdown = ({ const prefetchAccountId = status ? status.getIn(['account', 'id']) : undefined; + const statusId = status?.get('id') as string | undefined; const handleClose = useCallback(() => { if (buttonRef.current) { @@ -399,7 +366,7 @@ export const Dropdown = ({ }, [dispatch, currentId]); const handleItemClick = useCallback( - (e: React.MouseEvent | React.KeyboardEvent) => { + (e: React.MouseEvent) => { const i = Number(e.currentTarget.getAttribute('data-index')); const item = items?.[i]; @@ -420,10 +387,20 @@ export const Dropdown = ({ [handleClose, onItemClick, items], ); - const toggleDropdown = useCallback( - (e: React.MouseEvent | React.KeyboardEvent) => { - const { type } = e; + const isKeypressRef = useRef(false); + + const handleKeyDown = useCallback((e: React.KeyboardEvent) => { + if (e.key === ' ' || e.key === 'Enter') { + isKeypressRef.current = true; + } + }, []); + + const unsetIsKeypress = useCallback(() => { + isKeypressRef.current = false; + }, []); + const toggleDropdown = useCallback( + (e: React.MouseEvent) => { if (open) { handleClose(); } else { @@ -436,6 +413,15 @@ export const Dropdown = ({ dispatch(fetchRelationships([prefetchAccountId])); } + if (needsStatusRefresh && statusId) { + dispatch( + fetchStatus(statusId, { + forceFetch: true, + alsoFetchContext: false, + }), + ); + } + if (isUserTouching() && !forceDropdown) { dispatch( openModal({ @@ -450,10 +436,11 @@ export const Dropdown = ({ dispatch( openDropdownMenu({ id: currentId, - keyboard: type !== 'click', + keyboard: isKeypressRef.current, scrollKey, }), ); + isKeypressRef.current = false; } } }, @@ -468,6 +455,8 @@ export const Dropdown = ({ items, forceDropdown, handleClose, + statusId, + needsStatusRefresh, ], ); @@ -484,6 +473,9 @@ export const Dropdown = ({ const buttonProps = { disabled, onClick: toggleDropdown, + onKeyDown: handleKeyDown, + onKeyUp: unsetIsKeypress, + onBlur: unsetIsKeypress, 'aria-expanded': open, 'aria-controls': menuId, ref: buttonRef, diff --git a/app/javascript/flavours/glitch/components/edited_timestamp/index.tsx b/app/javascript/flavours/glitch/components/edited_timestamp/index.tsx index 2cc9219fb5d4df..368e71f8803a64 100644 --- a/app/javascript/flavours/glitch/components/edited_timestamp/index.tsx +++ b/app/javascript/flavours/glitch/components/edited_timestamp/index.tsx @@ -58,17 +58,7 @@ export const EditedTimestamp: React.FC<{ }, []); const renderItem = useCallback( - ( - item: HistoryItem, - index: number, - { - onClick, - onKeyUp, - }: { - onClick: React.MouseEventHandler; - onKeyUp: React.KeyboardEventHandler; - }, - ) => { + (item: HistoryItem, index: number, onClick: React.MouseEventHandler) => { const formattedDate = ( - @@ -118,7 +108,7 @@ export const EditedTimestamp: React.FC<{ onItemClick={handleItemClick} forceDropdown > - diff --git a/app/javascript/flavours/glitch/components/hashtag_bar.tsx b/app/javascript/flavours/glitch/components/hashtag_bar.tsx index 4f88385bef951f..a9dd6941a47e8a 100644 --- a/app/javascript/flavours/glitch/components/hashtag_bar.tsx +++ b/app/javascript/flavours/glitch/components/hashtag_bar.tsx @@ -33,7 +33,7 @@ function isNodeLinkHashtag(element: Node): element is HTMLLinkElement { return ( element instanceof HTMLAnchorElement && // it may be a starting with a hashtag - (element.textContent?.[0] === '#' || + (element.textContent.startsWith('#') || // or a # element.previousSibling?.textContent?.[ element.previousSibling.textContent.length - 1 @@ -235,7 +235,7 @@ const HashtagBar: React.FC<{ ))} {!expanded && hashtags.length > VISIBLE_HASHTAGS && ( -

+

This also has emoji: 🖤

`, + }, + argTypes: { + extraEmojis: { + table: { + disable: true, + }, + }, + onElement: { + table: { + disable: true, + }, + }, + onAttribute: { + table: { + disable: true, + }, + }, + }, + render(args) { + return ( + // Just for visual clarity in Storybook. + + ); + }, + // Force Twemoji to demonstrate emoji rendering. + parameters: { + state: { + meta: { + emoji_style: 'twemoji', + }, + }, + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + async play({ canvas }) { + const link = canvas.queryByRole('link'); + await expect(link).toBeInTheDocument(); + const button = canvas.queryByRole('button'); + await expect(button).not.toBeInTheDocument(); + }, +}; diff --git a/app/javascript/flavours/glitch/components/html_block/index.tsx b/app/javascript/flavours/glitch/components/html_block/index.tsx new file mode 100644 index 00000000000000..13212578809e47 --- /dev/null +++ b/app/javascript/flavours/glitch/components/html_block/index.tsx @@ -0,0 +1,30 @@ +import { useCallback } from 'react'; + +import type { OnElementHandler } from '@/flavours/glitch/utils/html'; +import { polymorphicForwardRef } from '@/types/polymorphic'; + +import type { EmojiHTMLProps } from '../emoji/html'; +import { EmojiHTML } from '../emoji/html'; +import { useElementHandledLink } from '../status/handled_link'; + +export const HTMLBlock = polymorphicForwardRef< + 'div', + EmojiHTMLProps & Parameters[0] +>( + ({ + onElement: onParentElement, + hrefToMention, + hashtagAccountId, + ...props + }) => { + const { onElement: onLinkElement } = useElementHandledLink({ + hrefToMention, + hashtagAccountId, + }); + const onElement: OnElementHandler = useCallback( + (...args) => onParentElement?.(...args) ?? onLinkElement(...args), + [onLinkElement, onParentElement], + ); + return ; + }, +); diff --git a/app/javascript/flavours/glitch/components/icon_button.tsx b/app/javascript/flavours/glitch/components/icon_button.tsx index 45ce9d5ff3c8d2..a3768feda4e53b 100644 --- a/app/javascript/flavours/glitch/components/icon_button.tsx +++ b/app/javascript/flavours/glitch/components/icon_button.tsx @@ -1,7 +1,9 @@ -import { useState, useEffect, useCallback, forwardRef } from 'react'; +import { useCallback, forwardRef } from 'react'; import classNames from 'classnames'; +import { usePrevious } from '../hooks/usePrevious'; + import { AnimatedNumber } from './animated_number'; import type { IconProp } from './icon'; import { Icon } from './icon'; @@ -59,23 +61,6 @@ export const IconButton = forwardRef( }, buttonRef, ) => { - const [activate, setActivate] = useState(false); - const [deactivate, setDeactivate] = useState(false); - - useEffect(() => { - if (!animate) { - return; - } - - if (activate && !active) { - setActivate(false); - setDeactivate(true); - } else if (!activate && active) { - setActivate(true); - setDeactivate(false); - } - }, [setActivate, setDeactivate, animate, active, activate]); - const handleClick: React.MouseEventHandler = useCallback( (e) => { e.preventDefault(); @@ -112,12 +97,15 @@ export const IconButton = forwardRef( ...(active ? activeStyle : {}), }; + const previousActive = usePrevious(active) ?? active; + const shouldAnimate = animate && active !== previousActive; + const classes = classNames(className, 'icon-button', { active, disabled, inverted, - activate, - deactivate, + activate: shouldAnimate && active, + deactivate: shouldAnimate && !active, overlayed: overlay, 'icon-button--with-counter': typeof counter !== 'undefined', }); diff --git a/app/javascript/flavours/glitch/components/learn_more_link.tsx b/app/javascript/flavours/glitch/components/learn_more_link.tsx index b5337794c952a2..8d22bb7a3ba053 100644 --- a/app/javascript/flavours/glitch/components/learn_more_link.tsx +++ b/app/javascript/flavours/glitch/components/learn_more_link.tsx @@ -23,6 +23,7 @@ export const LearnMoreLink: React.FC<{ children: React.ReactNode }> = ({ onClick={handleClick} aria-expanded={open} aria-controls={accessibilityId} + type='button' > = ({
{children}
- )} {!showResults && ( <> - {' '} ·{' '} @@ -186,7 +188,11 @@ export const Poll: React.FC = ({ pollId, disabled, status }) => { )} {showResults && !disabled && ( <> - {' '} ·{' '} @@ -234,12 +240,11 @@ const PollOption: React.FC = (props) => { let titleHtml = option.translation?.titleHtml ?? option.titleHtml; if (!titleHtml) { - const emojiMap = makeEmojiMap(poll.emojis); - titleHtml = emojify(escapeTextContentForBrowser(title), emojiMap); + titleHtml = escapeTextContentForBrowser(title); } return titleHtml; - }, [option, poll, title]); + }, [option, title]); // Handlers const handleOptionChange = useCallback(() => { @@ -305,10 +310,11 @@ const PollOption: React.FC = (props) => { )} - {!!voted && ( diff --git a/app/javascript/flavours/glitch/components/router.tsx b/app/javascript/flavours/glitch/components/router.tsx index 0175e1b44ffce9..f027d6cd3586ba 100644 --- a/app/javascript/flavours/glitch/components/router.tsx +++ b/app/javascript/flavours/glitch/components/router.tsx @@ -1,6 +1,7 @@ import type { PropsWithChildren } from 'react'; import type React from 'react'; +import type { useLocation } from 'react-router'; import { Router as OriginalRouter, useHistory } from 'react-router'; import type { @@ -18,7 +19,9 @@ interface MastodonLocationState { mastodonModalKey?: string; } -type LocationState = MastodonLocationState | null | undefined; +export type LocationState = MastodonLocationState | null | undefined; + +export type MastodonLocation = ReturnType>; type HistoryPath = Path | LocationDescriptor; diff --git a/app/javascript/flavours/glitch/components/scrollable_list.jsx b/app/javascript/flavours/glitch/components/scrollable_list.jsx index 836cc5ee20a6cf..a7c1dccb0ad51d 100644 --- a/app/javascript/flavours/glitch/components/scrollable_list.jsx +++ b/app/javascript/flavours/glitch/components/scrollable_list.jsx @@ -10,7 +10,7 @@ import { connect } from 'react-redux'; import { supportsPassiveEvents } from 'detect-passive-events'; import { throttle } from 'lodash'; -import ScrollContainer from 'flavours/glitch/containers/scroll_container'; +import { ScrollContainer } from 'flavours/glitch/containers/scroll_container'; import IntersectionObserverArticleContainer from '../containers/intersection_observer_article_container'; import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../features/ui/util/fullscreen'; @@ -399,7 +399,7 @@ class ScrollableList extends PureComponent { if (trackScroll) { return ( - + {scrollableArea} ); diff --git a/app/javascript/flavours/glitch/components/status.jsx b/app/javascript/flavours/glitch/components/status.jsx index 4362b704372f6c..246b47e9ca5f37 100644 --- a/app/javascript/flavours/glitch/components/status.jsx +++ b/app/javascript/flavours/glitch/components/status.jsx @@ -115,10 +115,11 @@ class Status extends ImmutablePureComponent { muted: PropTypes.bool, hidden: PropTypes.bool, unread: PropTypes.bool, + showActions: PropTypes.bool, prepend: PropTypes.string, withDismiss: PropTypes.bool, isQuotedPost: PropTypes.bool, - shouldHighlightOnMount: PropTypes.bool, + shouldHighlightOnMount: PropTypes.bool, getScrollPosition: PropTypes.func, updateScrollBottom: PropTypes.func, expanded: PropTypes.bool, @@ -465,7 +466,8 @@ class Status extends ImmutablePureComponent { onOpenMedia, notification, history, - isQuotedPost, + showActions = true, + isQuotedPost = false, ...other } = this.props; let attachments = null; @@ -634,7 +636,7 @@ class Status extends ImmutablePureComponent { } else if (status.get('card') && settings.get('inline_preview_cards') && !this.props.muted && !status.get('quote')) { media.push( , @@ -739,7 +741,7 @@ class Status extends ImmutablePureComponent { )} - {status.get('spoiler_text').length > 0 && } + {expanded && ( <> @@ -750,8 +752,6 @@ class Status extends ImmutablePureComponent { collapsible media={media} onCollapsedToggle={this.handleCollapsedToggle} - tagLinks={settings.get('tag_misleading_links')} - rewriteMentions={settings.get('rewrite_mentions')} {...statusContentProps} /> @@ -765,7 +765,7 @@ class Status extends ImmutablePureComponent { {/* This is a glitch-soc addition to have a placeholder */} {!expanded && } - {!isQuotedPost && + {(showActions && !isQuotedPost) && = ( - item, - index, - handlers, - focusRefCallback, -) => ( +const StandaloneBoostButton: FC = ({ status, counters }) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + + const statusState = useAppSelector((state) => + selectStatusState(state, status), + ); + const { title, meta, iconComponent, disabled } = useMemo( + () => boostItemState(statusState), + [statusState], + ); + + const handleClick: MouseEventHandler = useCallback( + (event) => { + if (statusState.isLoggedIn) { + dispatch(toggleReblog(status.get('id') as string, event.shiftKey)); + } else { + dispatch( + openModal({ + modalType: 'INTERACTION', + modalProps: { + accountId: status.getIn(['account', 'id']), + url: status.get('uri'), + }, + }), + ); + } + }, + [dispatch, status, statusState.isLoggedIn], + ); + + return ( + + ); +}; + +const renderMenuItem: RenderItemFn = (item, index, onClick) => ( ); @@ -46,7 +91,7 @@ interface ReblogButtonProps { type ActionMenuItemWithIcon = SomeRequired; -export const BoostButton: FC = ({ status, counters }) => { +const BoostOrQuoteMenu: FC = ({ status, counters }) => { const intl = useIntl(); const dispatch = useAppDispatch(); const statusState = useAppSelector((state) => @@ -67,6 +112,7 @@ export const BoostButton: FC = ({ status, counters }) => { const statusId = status.get('id') as string; const wasBoosted = !!status.get('reblogged'); + const quoteApproval = status.get('quote_approval'); const showLoginPrompt = useCallback(() => { dispatch( @@ -123,9 +169,16 @@ export const BoostButton: FC = ({ status, counters }) => { dispatch(toggleReblog(status.get('id'), true)); return false; } + + if (quoteApproval === null) { + dispatch( + fetchStatus(statusId, { forceFetch: true, alsoFetchContext: false }), + ); + } + return true; }, - [dispatch, isLoggedIn, showLoginPrompt, status], + [dispatch, isLoggedIn, showLoginPrompt, status, quoteApproval, statusId], ); return ( @@ -158,16 +211,10 @@ export const BoostButton: FC = ({ status, counters }) => { interface ReblogMenuItemProps { item: ActionMenuItem; index: number; - handlers: RenderItemFnHandlers; - focusRefCallback?: (c: HTMLAnchorElement | HTMLButtonElement | null) => void; + onClick: React.MouseEventHandler; } -const ReblogMenuItem: FC = ({ - index, - item, - handlers, - focusRefCallback, -}) => { +const ReblogMenuItem: FC = ({ index, item, onClick }) => { const { text, highlighted, disabled } = item; return ( @@ -178,13 +225,19 @@ const ReblogMenuItem: FC = ({ key={`${text}-${index}`} > ); }; + +// Switch between the standalone boost button or the +// "Boost or quote" menu based on the quickBoosting preference +export const BoostButton = quickBoosting + ? StandaloneBoostButton + : BoostOrQuoteMenu; diff --git a/app/javascript/flavours/glitch/components/status/handled_link.stories.tsx b/app/javascript/flavours/glitch/components/status/handled_link.stories.tsx new file mode 100644 index 00000000000000..e07d04b73071a2 --- /dev/null +++ b/app/javascript/flavours/glitch/components/status/handled_link.stories.tsx @@ -0,0 +1,106 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; + +import { HashtagMenuController } from '@/flavours/glitch/features/ui/components/hashtag_menu_controller'; +import { accountFactoryState } from '@/testing/factories'; + +import { HoverCardController } from '../hover_card_controller'; + +import type { HandledLinkProps } from './handled_link'; +import { HandledLink } from './handled_link'; + +type HandledLinkStoryProps = Pick< + HandledLinkProps, + 'href' | 'text' | 'prevText' +> & { + mentionAccount: 'local' | 'remote' | 'none'; + hashtagAccount: boolean; +}; + +const meta = { + title: 'Components/Status/HandledLink', + render({ mentionAccount, hashtagAccount, ...args }) { + let mention: HandledLinkProps['mention'] | undefined; + if (mentionAccount === 'local') { + mention = { id: '1', acct: 'testuser', username: 'testuser' }; + } else if (mentionAccount === 'remote') { + mention = { + id: '2', + acct: 'remoteuser@mastodon.social', + username: 'remoteuser', + }; + } + return ( + <> + + {args.text} + + + + + ); + }, + args: { + href: 'https://example.com/path/subpath?query=1#hash', + text: 'https://example.com', + mentionAccount: 'none', + hashtagAccount: false, + }, + argTypes: { + mentionAccount: { + control: { type: 'select' }, + options: ['local', 'remote', 'none'], + defaultValue: 'none', + }, + }, + parameters: { + state: { + accounts: { + '1': accountFactoryState({ id: '1', acct: 'hashtaguser' }), + }, + }, + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; + +export const Simple: Story = { + args: { + href: 'https://example.com/test', + }, +}; + +export const Hashtag: Story = { + args: { + text: '#example', + hashtagAccount: true, + }, +}; + +export const Mention: Story = { + args: { + text: '@user', + mentionAccount: 'local', + }, +}; + +export const InternalLink: Story = { + args: { + href: '/about', + text: 'About', + }, +}; + +export const InvalidURL: Story = { + args: { + href: 'ht!tp://invalid-url', + text: 'ht!tp://invalid-url -- invalid!', + }, +}; diff --git a/app/javascript/flavours/glitch/components/status/handled_link.tsx b/app/javascript/flavours/glitch/components/status/handled_link.tsx new file mode 100644 index 00000000000000..2b1a1bb7632800 --- /dev/null +++ b/app/javascript/flavours/glitch/components/status/handled_link.tsx @@ -0,0 +1,240 @@ +import { useCallback, useEffect, useRef } from 'react'; +import type { ComponentProps, FC } from 'react'; + +import classNames from 'classnames'; +import { Link } from 'react-router-dom'; + +import type { ApiMentionJSON } from '@/flavours/glitch/api_types/statuses'; +import { useAppSelector } from '@/flavours/glitch/store'; +import type { OnElementHandler } from '@/flavours/glitch/utils/html'; +import { decode as decodeIDNA } from 'flavours/glitch/utils/idna'; + +export interface HandledLinkProps { + href: string; + text: string; + prevText?: string; + hashtagAccountId?: string; + mention?: Pick; +} + +const textMatchesTarget = (text: string, origin: string, host: string) => { + return ( + text === origin || + text === host || + text.startsWith(origin + '/') || + text.startsWith(host + '/') || + 'www.' + text === host || + ('www.' + text).startsWith(host + '/') + ); +}; + +export const isLinkMisleading = (link: HTMLAnchorElement) => { + const linkTextParts: string[] = []; + + // Reconstruct visible text, as we do not have much control over how links + // from remote software look, and we can't rely on `innerText` because the + // `invisible` class does not set `display` to `none`. + + const walk = (node: Node) => { + if (node instanceof Text) { + linkTextParts.push(node.textContent); + } else if (node instanceof HTMLElement) { + if (node.classList.contains('invisible')) return; + for (const child of node.childNodes) { + walk(child); + } + } + }; + + walk(link); + + const linkText = linkTextParts.join(''); + const targetURL = new URL(link.href); + + if (targetURL.protocol === 'magnet:') { + return !linkText.startsWith('magnet:'); + } + + if (targetURL.protocol === 'xmpp:') { + return !( + linkText === targetURL.href || 'xmpp:' + linkText === targetURL.href + ); + } + + // The following may not work with international domain names + if ( + textMatchesTarget(linkText, targetURL.origin, targetURL.host) || + textMatchesTarget(linkText.toLowerCase(), targetURL.origin, targetURL.host) + ) { + return false; + } + + // The link hasn't been recognized, maybe it features an international domain name + const hostname = decodeIDNA(targetURL.hostname).normalize('NFKC'); + const host = targetURL.host.replace(targetURL.hostname, hostname); + const origin = targetURL.origin.replace(targetURL.host, host); + const text = linkText.normalize('NFKC'); + return !( + textMatchesTarget(text, origin, host) || + textMatchesTarget(text.toLowerCase(), origin, host) + ); +}; + +export const tagMisleadingLink = (link: HTMLAnchorElement) => { + try { + if (isLinkMisleading(link)) { + const url = new URL(link.href); + const tag = document.createElement('span'); + tag.classList.add('link-origin-tag'); + switch (url.protocol) { + case 'xmpp:': + tag.textContent = `[${url.href}]`; + break; + case 'magnet:': + tag.textContent = '(magnet)'; + break; + default: + tag.textContent = `[${url.host}]`; + } + link.insertAdjacentText('beforeend', ' '); + link.insertAdjacentElement('beforeend', tag); + } + } catch (e) { + // The URL is invalid, remove the href just to be safe + if (e instanceof TypeError) link.removeAttribute('href'); + } +}; + +export const HandledLink: FC> = ({ + href, + text, + prevText, + hashtagAccountId, + mention, + className, + children, + ...props +}) => { + const rewriteMentions = useAppSelector( + // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access + (state) => state.local_settings.get('rewrite_mentions', 'no') as string, + ); + const tagLinks = useAppSelector( + (state) => + // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access + state.local_settings.get('tag_misleading_links', false) as string, + ); + + const linkRef = useRef(null); + + useEffect(() => { + if (tagLinks && linkRef.current) tagMisleadingLink(linkRef.current); + }, [tagLinks]); + + // Handle hashtags + if ( + (text.startsWith('#') || + prevText?.endsWith('#') || + text.startsWith('#') || + prevText?.endsWith('#')) && + !text.includes('%') + ) { + const hashtag = text.slice(1).trim(); + + return ( + + {children} + + ); + } else if (mention) { + // glitch-soc feature to rewrite mentions + if (rewriteMentions !== 'no') { + return ( + + @ + + {rewriteMentions === 'acct' ? mention.acct : mention.username} + + + ); + } + + // Handle mentions + return ( + + {children} + + ); + } + + // Non-absolute paths treated as internal links. This shouldn't happen, but just in case. + if (href.startsWith('/')) { + return ( + + {children} + + ); + } + + return ( + + {children} + + ); +}; + +export const useElementHandledLink = ({ + hashtagAccountId, + hrefToMention, +}: { + hashtagAccountId?: string; + hrefToMention?: (href: string) => ApiMentionJSON | undefined; +} = {}) => { + const onElement = useCallback( + (element, { key, ...props }, children) => { + if (element instanceof HTMLAnchorElement) { + const mention = hrefToMention?.(element.href); + return ( + + {children} + + ); + } + return undefined; + }, + [hashtagAccountId, hrefToMention], + ); + return { onElement }; +}; diff --git a/app/javascript/flavours/glitch/components/status/intercept_status_clicks.tsx b/app/javascript/flavours/glitch/components/status/intercept_status_clicks.tsx new file mode 100644 index 00000000000000..b0dbc3c693848c --- /dev/null +++ b/app/javascript/flavours/glitch/components/status/intercept_status_clicks.tsx @@ -0,0 +1,45 @@ +import { useCallback, useRef } from 'react'; + +export const InterceptStatusClicks: React.FC<{ + onPreventedClick: ( + clickedArea: 'account' | 'post', + event: React.MouseEvent, + ) => void; + children: React.ReactNode; +}> = ({ onPreventedClick, children }) => { + const wrapperRef = useRef(null); + + const handleClick = useCallback( + (e: React.MouseEvent) => { + const clickTarget = e.target as Element; + const allowedElementsSelector = + '.video-player, .audio-player, .media-gallery, .content-warning'; + const allowedElements = wrapperRef.current?.querySelectorAll( + allowedElementsSelector, + ); + const isTargetClickAllowed = + allowedElements && + Array.from(allowedElements).some((element) => { + return clickTarget === element || element.contains(clickTarget); + }); + + if (!isTargetClickAllowed) { + e.preventDefault(); + e.stopPropagation(); + + const wasAccountAreaClicked = !!clickTarget.closest( + 'a.status__display-name', + ); + + onPreventedClick(wasAccountAreaClicked ? 'account' : 'post', e); + } + }, + [onPreventedClick], + ); + + return ( +
+ {children} +
+ ); +}; diff --git a/app/javascript/flavours/glitch/components/status_action_bar/index.jsx b/app/javascript/flavours/glitch/components/status_action_bar/index.jsx index 9284fb17f0e0d1..3f9287c2cd23ea 100644 --- a/app/javascript/flavours/glitch/components/status_action_bar/index.jsx +++ b/app/javascript/flavours/glitch/components/status_action_bar/index.jsx @@ -22,12 +22,13 @@ import { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import { Dropdown } from 'flavours/glitch/components/dropdown_menu'; -import { me } from '../../initial_state'; +import { me, quickBoosting } from '../../initial_state'; import { IconButton } from '../icon_button'; import { RelativeTimestamp } from '../relative_timestamp'; import { BoostButton } from '../status/boost_button'; import { RemoveQuoteHint } from './remove_quote_hint'; +import { quoteItemState, selectStatusState } from '../status/boost_button_utils'; const messages = defineMessages({ delete: { id: 'status.delete', defaultMessage: 'Delete' }, @@ -68,6 +69,7 @@ const mapStateToProps = (state, { status }) => { const quotedStatusId = status.getIn(['quote', 'quoted_status']); return ({ quotedAccountId: quotedStatusId ? state.getIn(['statuses', quotedStatusId, 'account']) : null, + statusQuoteState: selectStatusState(state, status), }); }; @@ -75,6 +77,7 @@ class StatusActionBar extends ImmutablePureComponent { static propTypes = { identity: identityContextPropShape, status: ImmutablePropTypes.map.isRequired, + statusQuoteState: PropTypes.object, quotedAccountId: PropTypes.string, contextType: PropTypes.string, onReply: PropTypes.func, @@ -122,6 +125,10 @@ class StatusActionBar extends ImmutablePureComponent { } }; + handleQuoteClick = () => { + this.props.onQuote(this.props.status); + }; + handleShareClick = () => { navigator.share({ url: this.props.status.get('url'), @@ -214,7 +221,7 @@ class StatusActionBar extends ImmutablePureComponent { }; render () { - const { status, quotedAccountId, contextType, intl, withDismiss, withCounters, showReplyCount, scrollKey } = this.props; + const { status, statusQuoteState, quotedAccountId, contextType, intl, withDismiss, withCounters, showReplyCount, scrollKey } = this.props; const { signedIn, permissions } = this.props.identity; const publicStatus = ['public', 'unlisted'].includes(status.get('visibility')); @@ -243,6 +250,19 @@ class StatusActionBar extends ImmutablePureComponent { menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed }); } + if (quickBoosting && signedIn) { + const quoteItem = quoteItemState(statusQuoteState); + menu.push(null); + menu.push({ + text: intl.formatMessage(quoteItem.title), + description: quoteItem.meta + ? intl.formatMessage(quoteItem.meta) + : undefined, + disabled: quoteItem.disabled, + action: this.handleQuoteClick, + }); + } + if (signedIn) { menu.push(null); @@ -354,6 +374,7 @@ class StatusActionBar extends ImmutablePureComponent { (null); const intl = useIntl(); - const { wasDismissed, dismiss } = useDismissableBannerState({ - id: DISMISSABLE_BANNER_ID, - }); + const { wasDismissed, dismiss } = useDismissible(DISMISSIBLE_BANNER_ID); const shouldShowHint = !wasDismissed && canShowHint; @@ -44,6 +44,7 @@ export const RemoveQuoteHint: React.FC<{ if (!firstHintId) { firstHintId = uniqueId; + // eslint-disable-next-line react-hooks/set-state-in-effect setIsOnlyHint(true); } @@ -64,8 +65,8 @@ export const RemoveQuoteHint: React.FC<{ flip offset={[12, 10]} placement='bottom-end' - target={anchorRef.current} - container={anchorRef.current} + target={anchorRef} + container={anchorRef} > {({ props, placement }) => (
), }} diff --git a/app/javascript/flavours/glitch/components/status_banner.tsx b/app/javascript/flavours/glitch/components/status_banner.tsx index e11b2c9279b761..b7f7289652d8be 100644 --- a/app/javascript/flavours/glitch/components/status_banner.tsx +++ b/app/javascript/flavours/glitch/components/status_banner.tsx @@ -3,6 +3,8 @@ import { useCallback, useRef, useId } from 'react'; import { FormattedMessage } from 'react-intl'; +import { AnimateEmojiProvider } from './emoji/context'; + export enum BannerVariant { Warning = 'warning', Filter = 'filter', @@ -34,8 +36,7 @@ export const StatusBanner: React.FC<{ return ( // Element clicks are passed on to button - // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions -
)} -
+ ); }; diff --git a/app/javascript/flavours/glitch/components/status_content.jsx b/app/javascript/flavours/glitch/components/status_content.jsx index fb52fd68cffb83..9f7b9cf4394f10 100644 --- a/app/javascript/flavours/glitch/components/status_content.jsx +++ b/app/javascript/flavours/glitch/components/status_content.jsx @@ -13,67 +13,12 @@ import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react' import { Icon } from 'flavours/glitch/components/icon'; import { Poll } from 'flavours/glitch/components/poll'; import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context'; -import { autoPlayGif, languages as preloadedLanguages } from 'flavours/glitch/initial_state'; -import { decode as decodeIDNA } from 'flavours/glitch/utils/idna'; -import { EmojiHTML } from '../features/emoji/emoji_html'; -import { isModernEmojiEnabled } from '../utils/environment'; +import { languages as preloadedLanguages } from 'flavours/glitch/initial_state'; -const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top) - -const textMatchesTarget = (text, origin, host) => { - return (text === origin || text === host - || text.startsWith(origin + '/') || text.startsWith(host + '/') - || 'www.' + text === host || ('www.' + text).startsWith(host + '/')); -}; - -const isLinkMisleading = (link) => { - let linkTextParts = []; - - // Reconstruct visible text, as we do not have much control over how links - // from remote software look, and we can't rely on `innerText` because the - // `invisible` class does not set `display` to `none`. - - const walk = (node) => { - switch (node.nodeType) { - case Node.TEXT_NODE: - linkTextParts.push(node.textContent); - break; - case Node.ELEMENT_NODE: { - if (node.classList.contains('invisible')) return; - const children = node.childNodes; - for (let i = 0; i < children.length; i++) { - walk(children[i]); - } - break; - } - } - }; - - walk(link); - - const linkText = linkTextParts.join(''); - const targetURL = new URL(link.href); - - if (targetURL.protocol === 'magnet:') { - return !linkText.startsWith('magnet:'); - } +import { EmojiHTML } from './emoji/html'; +import { HandledLink } from './status/handled_link'; - if (targetURL.protocol === 'xmpp:') { - return !(linkText === targetURL.href || 'xmpp:' + linkText === targetURL.href); - } - - // The following may not work with international domain names - if (textMatchesTarget(linkText, targetURL.origin, targetURL.host) || textMatchesTarget(linkText.toLowerCase(), targetURL.origin, targetURL.host)) { - return false; - } - - // The link hasn't been recognized, maybe it features an international domain name - const hostname = decodeIDNA(targetURL.hostname).normalize('NFKC'); - const host = targetURL.host.replace(targetURL.hostname, hostname); - const origin = targetURL.origin.replace(targetURL.host, host); - const text = linkText.normalize('NFKC'); - return !(textMatchesTarget(text, origin, host) || textMatchesTarget(text.toLowerCase(), origin, host)); -}; +const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top) /** * @@ -81,9 +26,6 @@ const isLinkMisleading = (link) => { * @returns {string} */ export function getStatusContent(status) { - if (isModernEmojiEnabled()) { - return status.getIn(['translation', 'content']) || status.get('content'); - } return status.getIn(['translation', 'contentHtml']) || status.get('contentHtml'); } @@ -128,6 +70,17 @@ const mapStateToProps = state => ({ languages: state.getIn(['server', 'translationLanguages', 'items']), }); +const compareUrls = (href1, href2) => { + try { + const url1 = new URL(href1); + const url2 = new URL(href2); + + return url1.origin === url2.origin && url1.pathname === url2.pathname && url1.search === url2.search; + } catch { + return false; + } +}; + class StatusContent extends PureComponent { static propTypes = { identity: identityContextPropShape, @@ -137,8 +90,6 @@ class StatusContent extends PureComponent { onClick: PropTypes.func, collapsible: PropTypes.bool, onCollapsedToggle: PropTypes.func, - tagLinks: PropTypes.bool, - rewriteMentions: PropTypes.string, languages: ImmutablePropTypes.map, intl: PropTypes.object, // from react-router @@ -147,83 +98,14 @@ class StatusContent extends PureComponent { history: PropTypes.object.isRequired }; - static defaultProps = { - tagLinks: true, - rewriteMentions: 'no', - }; - _updateStatusLinks () { const node = this.node; - const { tagLinks, rewriteMentions } = this.props; if (!node) { return; } const { status, onCollapsedToggle } = this.props; - const links = node.querySelectorAll('a'); - - let link, mention; - - for (var i = 0; i < links.length; ++i) { - link = links[i]; - - if (link.classList.contains('status-link')) { - continue; - } - - link.classList.add('status-link'); - - mention = this.props.status.get('mentions').find(item => link.href === item.get('url')); - - if (mention) { - link.addEventListener('click', this.onMentionClick.bind(this, mention), false); - link.setAttribute('title', `@${mention.get('acct')}`); - link.setAttribute('data-hover-card-account', mention.get('id')); - if (rewriteMentions !== 'no') { - while (link.firstChild) link.removeChild(link.firstChild); - link.appendChild(document.createTextNode('@')); - const acctSpan = document.createElement('span'); - acctSpan.textContent = rewriteMentions === 'acct' ? mention.get('acct') : mention.get('username'); - link.appendChild(acctSpan); - } - } else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) { - link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false); - link.setAttribute('data-menu-hashtag', this.props.status.getIn(['account', 'id'])); - } else { - link.setAttribute('title', link.href); - link.classList.add('unhandled-link'); - - link.setAttribute('target', '_blank'); - link.setAttribute('rel', 'noopener nofollow'); - - try { - if (tagLinks && isLinkMisleading(link)) { - // Add a tag besides the link to display its origin - - const url = new URL(link.href); - const tag = document.createElement('span'); - tag.classList.add('link-origin-tag'); - switch (url.protocol) { - case 'xmpp:': - tag.textContent = `[${url.href}]`; - break; - case 'magnet:': - tag.textContent = '(magnet)'; - break; - default: - tag.textContent = `[${url.host}]`; - } - link.insertAdjacentText('beforeend', ' '); - link.insertAdjacentElement('beforeend', tag); - } - } catch (e) { - // The URL is invalid, remove the href just to be safe - if (tagLinks && e instanceof TypeError) link.removeAttribute('href'); - } - } - } - if (status.get('collapsed', null) === null && onCollapsedToggle) { const { collapsible, onClick } = this.props; @@ -245,22 +127,6 @@ class StatusContent extends PureComponent { this._updateStatusLinks(); } - onMentionClick = (mention, e) => { - if (this.props.history && e.button === 0 && !(e.ctrlKey || e.metaKey)) { - e.preventDefault(); - this.props.history.push(`/@${mention.get('acct')}`); - } - }; - - onHashtagClick = (hashtag, e) => { - hashtag = hashtag.replace(/^#/, ''); - - if (this.props.history && e.button === 0 && !(e.ctrlKey || e.metaKey)) { - e.preventDefault(); - this.props.history.push(`/tags/${hashtag}`); - } - }; - handleMouseDown = (e) => { this.startXY = [e.clientX, e.clientY]; }; @@ -296,6 +162,27 @@ class StatusContent extends PureComponent { this.node = c; }; + handleElement = (element, { key, ...props }, children) => { + if (element instanceof HTMLAnchorElement) { + const mention = this.props.status.get('mentions').find(item => compareUrls(element.href, item.get('url'))); + return ( + + {children} + + ); + } else if (element.classList.contains('quote-inline')) { + return null; + } + return undefined; + } + render () { const { status, intl, statusContent } = this.props; @@ -340,6 +227,7 @@ class StatusContent extends PureComponent { lang={language} htmlString={content} extraEmojis={status.get('emojis')} + onElement={this.handleElement} /> {poll} @@ -357,6 +245,7 @@ class StatusContent extends PureComponent { lang={language} htmlString={content} extraEmojis={status.get('emojis')} + onElement={this.handleElement} /> {poll} diff --git a/app/javascript/flavours/glitch/components/status_list.jsx b/app/javascript/flavours/glitch/components/status_list.jsx index 14124a212d7e90..29310aff029701 100644 --- a/app/javascript/flavours/glitch/components/status_list.jsx +++ b/app/javascript/flavours/glitch/components/status_list.jsx @@ -64,9 +64,7 @@ export default class StatusList extends ImmutablePureComponent { switch(statusId) { case TIMELINE_SUGGESTIONS: return ( - + ); case TIMELINE_GAP: return ( diff --git a/app/javascript/flavours/glitch/components/status_quoted.tsx b/app/javascript/flavours/glitch/components/status_quoted.tsx index b50fec31d62c31..c3e07ecef501fe 100644 --- a/app/javascript/flavours/glitch/components/status_quoted.tsx +++ b/app/javascript/flavours/glitch/components/status_quoted.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useRef } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { FormattedMessage } from 'react-intl'; @@ -12,6 +12,7 @@ import type { Status } from 'flavours/glitch/models/status'; import type { RootState } from 'flavours/glitch/store'; import { useAppDispatch, useAppSelector } from 'flavours/glitch/store'; +import { fetchRelationships } from '../actions/accounts'; import { revealAccount } from '../actions/accounts_typed'; import { fetchStatus } from '../actions/statuses'; import { makeGetStatusWithExtraInfo } from '../selectors'; @@ -72,7 +73,63 @@ const LimitedAccountHint: React.FC<{ accountId: string }> = ({ accountId }) => { defaultMessage='This account has been hidden by the moderators of {domain}.' values={{ domain }} /> - + + ); +}; + +const FilteredQuote: React.FC<{ + reveal: VoidFunction; + quotedAccountId: string; + quoteState: string; +}> = ({ reveal, quotedAccountId, quoteState }) => { + const account = useAppSelector((state) => + quotedAccountId ? state.accounts.get(quotedAccountId) : undefined, + ); + + const quoteAuthorName = account?.acct; + const domain = quoteAuthorName?.split('@')[1]; + + let message; + + switch (quoteState) { + case 'blocked_account': + message = ( + + ); + break; + case 'blocked_domain': + message = ( + + ); + break; + case 'muted_account': + message = ( + + ); + } + + return ( + <> + {message} + @@ -154,6 +155,7 @@ export const DomainPill: React.FC<{ @@ -169,6 +171,7 @@ export const DomainPill: React.FC<{ diff --git a/app/javascript/flavours/glitch/features/account_timeline/components/account_header.tsx b/app/javascript/flavours/glitch/features/account_timeline/components/account_header.tsx index 163faf0a5143f2..a2d7901b654060 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/components/account_header.tsx +++ b/app/javascript/flavours/glitch/features/account_timeline/components/account_header.tsx @@ -7,8 +7,9 @@ import { Helmet } from 'react-helmet'; import { NavLink } from 'react-router-dom'; import { AccountBio } from '@/flavours/glitch/components/account_bio'; +import { AccountFields } from '@/flavours/glitch/components/account_fields'; import { DisplayName } from '@/flavours/glitch/components/display_name'; -import CheckIcon from '@/material-icons/400-24px/check.svg?react'; +import { AnimateEmojiProvider } from '@/flavours/glitch/components/emoji/context'; import LockIcon from '@/material-icons/400-24px/lock.svg?react'; import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; import NotificationsIcon from '@/material-icons/400-24px/notifications.svg?react'; @@ -37,7 +38,6 @@ import { AutomatedBadge, GroupBadge, } from 'flavours/glitch/components/badge'; -import { Button } from 'flavours/glitch/components/button'; import { CopyIconButton } from 'flavours/glitch/components/copy_icon_button'; import { Dropdown } from 'flavours/glitch/components/dropdown_menu'; import { FollowButton } from 'flavours/glitch/components/follow_button'; @@ -47,7 +47,6 @@ import { IconButton } from 'flavours/glitch/components/icon_button'; import { AccountNote } from 'flavours/glitch/features/account/components/account_note'; import { DomainPill } from 'flavours/glitch/features/account/components/domain_pill'; import FollowRequestNoteContainer from 'flavours/glitch/features/account/containers/follow_request_note_container'; -import { useLinks } from 'flavours/glitch/hooks/useLinks'; import { useIdentity } from 'flavours/glitch/identity_context'; import { autoPlayGif, @@ -190,14 +189,6 @@ const titleFromAccount = (account: Account) => { return `${prefix} (@${acct})`; }; -const dateFormatOptions: Intl.DateTimeFormatOptions = { - month: 'short', - day: 'numeric', - year: 'numeric', - hour: '2-digit', - minute: '2-digit', -}; - export const AccountHeader: React.FC<{ accountId: string; hideTabs?: boolean; @@ -210,7 +201,6 @@ export const AccountHeader: React.FC<{ state.relationships.get(accountId), ); const hidden = useAppSelector((state) => getAccountHidden(state, accountId)); - const handleLinkClick = useLinks(); const handleBlock = useCallback(() => { if (!account) { @@ -387,7 +377,7 @@ export const AccountHeader: React.FC<{ const isRemote = account?.acct !== account?.username; const remoteDomain = isRemote ? account?.acct.split('@')[1] : null; - const menu = useMemo(() => { + const menuItems = useMemo(() => { const arr: MenuItem[] = []; if (!account) { @@ -609,6 +599,15 @@ export const AccountHeader: React.FC<{ handleUnblockDomain, ]); + const menu = accountId !== me && ( + + ); + if (!account) { return null; } @@ -722,21 +721,16 @@ export const AccountHeader: React.FC<{ ); } - if (relationship?.blocking) { + const isMovedAndUnfollowedAccount = account.moved && !relationship?.following; + + if (!isMovedAndUnfollowedAccount) { actionBtn = ( -
+ diff --git a/app/javascript/flavours/glitch/features/alt_text_modal/index.tsx b/app/javascript/flavours/glitch/features/alt_text_modal/index.tsx index d6d61cf43cf7b6..6d1d9365f013c0 100644 --- a/app/javascript/flavours/glitch/features/alt_text_modal/index.tsx +++ b/app/javascript/flavours/glitch/features/alt_text_modal/index.tsx @@ -101,16 +101,17 @@ const Preview: React.FC<{ position: FocalPoint; onPositionChange: (arg0: FocalPoint) => void; }> = ({ mediaId, position, onPositionChange }) => { - const draggingRef = useRef(false); const nodeRef = useRef(null); + const [dragging, setDragging] = useState<'started' | 'moving' | null>(null); + const [x, y] = position; const style = useSpring({ to: { left: `${x * 100}%`, top: `${y * 100}%`, }, - immediate: draggingRef.current, + immediate: dragging === 'moving', }); const media = useAppSelector((state) => ( @@ -123,8 +124,6 @@ const Preview: React.FC<{ me ? state.accounts.get(me) : undefined, ); - const [dragging, setDragging] = useState(false); - const setRef = useCallback( (e: HTMLImageElement | HTMLVideoElement | null) => { nodeRef.current = e; @@ -140,20 +139,20 @@ const Preview: React.FC<{ const handleMouseMove = (e: MouseEvent) => { const { x, y } = getPointerPosition(nodeRef.current, e); - draggingRef.current = true; // This will disable the animation for quicker feedback, only do this if the mouse actually moves + + setDragging('moving'); // This will disable the animation for quicker feedback, only do this if the mouse actually moves onPositionChange([x, y]); }; const handleMouseUp = () => { - setDragging(false); - draggingRef.current = false; + setDragging(null); document.removeEventListener('mouseup', handleMouseUp); document.removeEventListener('mousemove', handleMouseMove); }; const { x, y } = getPointerPosition(nodeRef.current, e.nativeEvent); - setDragging(true); + setDragging('started'); onPositionChange([x, y]); document.addEventListener('mouseup', handleMouseUp); @@ -330,7 +329,7 @@ export const AltTextModal = forwardRef>( }); }, [dispatch, setIsSaving, mediaId, onClose, position, description]); - const handleKeyUp = useCallback( + const handleKeyDown = useCallback( (e: React.KeyboardEvent) => { if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { e.preventDefault(); @@ -457,7 +456,7 @@ export const AltTextModal = forwardRef>( id='description' value={isDetecting ? ' ' : description} onChange={handleDescriptionChange} - onKeyUp={handleKeyUp} + onKeyDown={handleKeyDown} lang={lang} placeholder={intl.formatMessage( type === 'audio' @@ -489,6 +488,7 @@ export const AltTextModal = forwardRef>( className='link-button' onClick={handleDetectClick} disabled={type !== 'image' || isDetecting} + type='button' > & { + reportState: AnnualReportAnnouncementProps['state']; + }, + AnyFunction // Remove any functions, as they can't meaningfully be controlled in Storybook. +>; + +const meta = { + title: 'Components/AnnualReport/Announcement', + args: { + reportState: 'eligible', + year: '2025', + }, + argTypes: { + reportState: { + control: { + type: 'select', + }, + options: ['eligible', 'generating', 'available'], + }, + }, + render({ reportState, ...args }: Props) { + return ( + + ); + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; + +export const Loading: Story = { + args: { + reportState: 'generating', + }, +}; + +export const WithData: Story = { + args: { + reportState: 'available', + }, +}; diff --git a/app/javascript/flavours/glitch/features/annual_report/announcement/index.tsx b/app/javascript/flavours/glitch/features/annual_report/announcement/index.tsx new file mode 100644 index 00000000000000..2ab11a9638715a --- /dev/null +++ b/app/javascript/flavours/glitch/features/annual_report/announcement/index.tsx @@ -0,0 +1,59 @@ +import { FormattedMessage } from 'react-intl'; + +import classNames from 'classnames'; + +import type { ApiAnnualReportState } from '@/flavours/glitch/api/annual_report'; +import { Button } from '@/flavours/glitch/components/button'; + +import styles from './styles.module.scss'; + +export interface AnnualReportAnnouncementProps { + year: string; + state: Exclude; + onRequestBuild: () => void; + onOpen?: () => void; // This is optional when inside the modal, as it won't be shown then. + onDismiss: () => void; +} + +export const AnnualReportAnnouncement: React.FC< + AnnualReportAnnouncementProps +> = ({ year, state, onRequestBuild, onOpen, onDismiss }) => { + return ( +
+ + + {state === 'available' ? ( + + ) : ( + + )} + {state === 'eligible' && ( + + )} +
+ ); +}; diff --git a/app/javascript/flavours/glitch/features/annual_report/announcement/styles.module.scss b/app/javascript/flavours/glitch/features/annual_report/announcement/styles.module.scss new file mode 100644 index 00000000000000..9ec62fa0fdbc0e --- /dev/null +++ b/app/javascript/flavours/glitch/features/annual_report/announcement/styles.module.scss @@ -0,0 +1,42 @@ +.wrapper { + display: flex; + flex-direction: column; + align-items: center; + padding: 40px 60px; + text-align: center; + font-size: 15px; + line-height: 1.5; + color: var(--color-text-primary); + background: var(--color-bg-primary); + background: + radial-gradient(at 40% 87%, #240c9a99 0, transparent 50%), + radial-gradient(at 19% 10%, #6b0c9a99 0, transparent 50%), + radial-gradient(at 90% 27%, #9a0c8299 0, transparent 50%), + radial-gradient(at 16% 95%, #1e948299 0, transparent 50%) + var(--color-bg-primary); + border-bottom: 1px solid var(--color-border-primary); + position: relative; + pointer-events: all; + + h2 { + font-size: 20px; + font-weight: 500; + line-height: 1.2; + margin-bottom: 8px; + } + + p { + margin-bottom: 20px; + } + + .closeButton { + position: absolute; + bottom: 8px; + right: 8px; + margin-inline: 0; + } + + :global(.modal-root__modal) & { + border-radius: 16px; + } +} diff --git a/app/javascript/flavours/glitch/features/annual_report/annual_report.stories.tsx b/app/javascript/flavours/glitch/features/annual_report/annual_report.stories.tsx new file mode 100644 index 00000000000000..3ccaceae6c5fd6 --- /dev/null +++ b/app/javascript/flavours/glitch/features/annual_report/annual_report.stories.tsx @@ -0,0 +1,99 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; + +import { + accountFactoryState, + annualReportFactory, + statusFactoryState, +} from '@/testing/factories'; + +import { AnnualReport } from '.'; + +const SAMPLE_HASHTAG = { + name: 'Mastodon', + count: 14, +}; + +const meta = { + title: 'Components/AnnualReport', + component: AnnualReport, + args: { + context: 'standalone', + }, + parameters: { + state: { + accounts: { + '1': accountFactoryState({ display_name: 'Freddie Fruitbat' }), + }, + statuses: { + '1': statusFactoryState(), + }, + annualReport: annualReportFactory({ + top_hashtag: SAMPLE_HASHTAG, + }), + }, + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Standalone: Story = { + args: { + context: 'standalone', + }, +}; + +export const InModal: Story = { + args: { + context: 'modal', + }, +}; + +export const ArchetypeOracle: Story = { + ...InModal, + parameters: { + state: { + annualReport: annualReportFactory({ + archetype: 'oracle', + top_hashtag: SAMPLE_HASHTAG, + }), + }, + }, +}; + +export const NoHashtag: Story = { + ...InModal, + parameters: { + state: { + annualReport: annualReportFactory({ + archetype: 'booster', + }), + }, + }, +}; + +export const NoNewPosts: Story = { + ...InModal, + parameters: { + state: { + annualReport: annualReportFactory({ + archetype: 'pollster', + top_hashtag: SAMPLE_HASHTAG, + without_posts: true, + }), + }, + }, +}; + +export const NoNewPostsNoHashtag: Story = { + ...InModal, + parameters: { + state: { + annualReport: annualReportFactory({ + archetype: 'replier', + without_posts: true, + }), + }, + }, +}; diff --git a/app/javascript/flavours/glitch/features/annual_report/archetype.tsx b/app/javascript/flavours/glitch/features/annual_report/archetype.tsx index 604f401039e1bb..743aa0c05c64c9 100644 --- a/app/javascript/flavours/glitch/features/annual_report/archetype.tsx +++ b/app/javascript/flavours/glitch/features/annual_report/archetype.tsx @@ -1,69 +1,224 @@ -import { FormattedMessage } from 'react-intl'; +import { useCallback, useRef, useState } from 'react'; +import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; + +import classNames from 'classnames'; + +import { Avatar } from '@/flavours/glitch/components/avatar'; +import { Button } from '@/flavours/glitch/components/button'; +import { DisplayName } from '@/flavours/glitch/components/display_name'; +import { me } from '@/flavours/glitch/initial_state'; +import type { Account } from '@/flavours/glitch/models/account'; +import type { + AnnualReport, + Archetype as ArchetypeData, +} from '@/flavours/glitch/models/annual_report'; +import { wrapstodonSettings } from '@/flavours/glitch/settings'; import booster from '@/images/archetypes/booster.png'; import lurker from '@/images/archetypes/lurker.png'; import oracle from '@/images/archetypes/oracle.png'; import pollster from '@/images/archetypes/pollster.png'; import replier from '@/images/archetypes/replier.png'; -import type { Archetype as ArchetypeData } from 'flavours/glitch/models/annual_report'; +import space_elements from '@/images/archetypes/space_elements.png'; + +import styles from './index.module.scss'; +import { ShareButton } from './share_button'; + +export const archetypeNames = defineMessages({ + booster: { + id: 'annual_report.summary.archetype.booster.name', + defaultMessage: 'The Archer', + }, + replier: { + id: 'annual_report.summary.archetype.replier.name', + defaultMessage: 'The Butterfly', + }, + pollster: { + id: 'annual_report.summary.archetype.pollster.name', + defaultMessage: 'The Wonderer', + }, + lurker: { + id: 'annual_report.summary.archetype.lurker.name', + defaultMessage: 'The Stoic', + }, + oracle: { + id: 'annual_report.summary.archetype.oracle.name', + defaultMessage: 'The Oracle', + }, +}); + +export const archetypeSelfDescriptions = defineMessages({ + booster: { + id: 'annual_report.summary.archetype.booster.desc_self', + defaultMessage: + 'You stayed on the hunt for posts to boost, amplifying other creators with perfect aim.', + }, + replier: { + id: 'annual_report.summary.archetype.replier.desc_self', + defaultMessage: + 'You frequently replied to other people’s posts, pollinating Mastodon with new discussions.', + }, + pollster: { + id: 'annual_report.summary.archetype.pollster.desc_self', + defaultMessage: + 'You created more polls than other post types, cultivating curiosity on Mastodon.', + }, + lurker: { + id: 'annual_report.summary.archetype.lurker.desc_self', + defaultMessage: + 'We know you were out there, somewhere, enjoying Mastodon in your own quiet way.', + }, + oracle: { + id: 'annual_report.summary.archetype.oracle.desc_self', + defaultMessage: + 'You created new posts more than replies, keeping Mastodon fresh and future-facing.', + }, +}); + +export const archetypePublicDescriptions = defineMessages({ + booster: { + id: 'annual_report.summary.archetype.booster.desc_public', + defaultMessage: + '{name} stayed on the hunt for posts to boost, amplifying other creators with perfect aim.', + }, + replier: { + id: 'annual_report.summary.archetype.replier.desc_public', + defaultMessage: + '{name} frequently replied to other people’s posts, pollinating Mastodon with new discussions.', + }, + pollster: { + id: 'annual_report.summary.archetype.pollster.desc_public', + defaultMessage: + '{name} created more polls than other post types, cultivating curiosity on Mastodon.', + }, + lurker: { + id: 'annual_report.summary.archetype.lurker.desc_public', + defaultMessage: + 'We know {name} was out there, somewhere, enjoying Mastodon in their own quiet way.', + }, + oracle: { + id: 'annual_report.summary.archetype.oracle.desc_public', + defaultMessage: + '{name} created new posts more than replies, keeping Mastodon fresh and future-facing.', + }, +}); + +const illustrations = { + booster, + replier, + pollster, + lurker, + oracle, +} as const; export const Archetype: React.FC<{ - data: ArchetypeData; -}> = ({ data }) => { - let illustration, label; + report: AnnualReport; + account?: Account; + context: 'modal' | 'standalone'; +}> = ({ report, account, context }) => { + const intl = useIntl(); + const wrapperRef = useRef(null); + const isSelfView = context === 'modal'; - switch (data) { - case 'booster': - illustration = booster; - label = ( - - ); - break; - case 'replier': - illustration = replier; - label = ( - - ); - break; - case 'pollster': - illustration = pollster; - label = ( - - ); - break; - case 'lurker': - illustration = lurker; - label = ( - - ); - break; - case 'oracle': - illustration = oracle; - label = ( - - ); - break; - } + const [isRevealed, setIsRevealed] = useState( + () => + !isSelfView || + (me ? (wrapstodonSettings.get(me)?.archetypeRevealed ?? false) : true), + ); + const reveal = useCallback(() => { + setIsRevealed(true); + if (me) { + wrapstodonSettings.set(me, { archetypeRevealed: true }); + } + wrapperRef.current?.focus(); + }, []); + + const archetype = report.data.archetype; + const descriptions = isSelfView + ? archetypeSelfDescriptions + : archetypePublicDescriptions; return ( -
-
{label}
- +
+
+ {account && ( + + )} +
+ +
+ +
+
+

+ {isSelfView ? ( + + ) : ( + , + }} + /> + )} +

+

+ {isRevealed ? ( + intl.formatMessage(archetypeNames[archetype]) + ) : ( + + )} +

+

+ {isRevealed ? ( + intl.formatMessage(descriptions[archetype], { + name: , + }) + ) : ( + + )} +

+
+ {!isRevealed && ( + + )} + {isRevealed && isSelfView && }
); }; diff --git a/app/javascript/flavours/glitch/features/annual_report/followers.tsx b/app/javascript/flavours/glitch/features/annual_report/followers.tsx index e5238705d797f0..b0f2216bc5bb65 100644 --- a/app/javascript/flavours/glitch/features/annual_report/followers.tsx +++ b/app/javascript/flavours/glitch/features/annual_report/followers.tsx @@ -1,68 +1,24 @@ import { FormattedMessage, FormattedNumber } from 'react-intl'; -import { Sparklines, SparklinesCurve } from 'react-sparklines'; +import classNames from 'classnames'; -import { ShortNumber } from 'flavours/glitch/components/short_number'; -import type { TimeSeriesMonth } from 'flavours/glitch/models/annual_report'; +import styles from './index.module.scss'; export const Followers: React.FC<{ - data: TimeSeriesMonth[]; - total?: number; -}> = ({ data, total }) => { - const change = data.reduce((sum, item) => sum + item.followers, 0); - - const cumulativeGraph = data.reduce( - (newData, item) => [ - ...newData, - item.followers + (newData[newData.length - 1] ?? 0), - ], - [0], - ); - + count: number; +}> = ({ count }) => { return ( -
- - - - - - - - - - - - - -
-
- {change > -1 ? '+' : '-'} - -
+
+
+ +
-
- - - -
- }} - /> -
-
+
+
); diff --git a/app/javascript/flavours/glitch/features/annual_report/highlighted_post.tsx b/app/javascript/flavours/glitch/features/annual_report/highlighted_post.tsx index 6d23e5deb65426..9e03c7e327d5f2 100644 --- a/app/javascript/flavours/glitch/features/annual_report/highlighted_post.tsx +++ b/app/javascript/flavours/glitch/features/annual_report/highlighted_post.tsx @@ -1,106 +1,102 @@ /* eslint-disable @typescript-eslint/no-unsafe-return, @typescript-eslint/no-explicit-any, - @typescript-eslint/no-unsafe-assignment */ + @typescript-eslint/no-unsafe-assignment, + @typescript-eslint/no-unsafe-member-access, + @typescript-eslint/no-unsafe-call */ +import type { ComponentPropsWithoutRef } from 'react'; import { useCallback } from 'react'; import { FormattedMessage } from 'react-intl'; -import { DisplayName } from '@/flavours/glitch/components/display_name'; -import { toggleStatusSpoilers } from 'flavours/glitch/actions/statuses'; -import { DetailedStatus } from 'flavours/glitch/features/status/components/detailed_status'; -import { me } from 'flavours/glitch/initial_state'; +import classNames from 'classnames'; + +import { InterceptStatusClicks } from 'flavours/glitch/components/status/intercept_status_clicks'; +import { StatusQuoteManager } from 'flavours/glitch/components/status_quoted'; import type { TopStatuses } from 'flavours/glitch/models/annual_report'; -import { - makeGetStatus, - makeGetPictureInPicture, -} from 'flavours/glitch/selectors'; -import { useAppSelector, useAppDispatch } from 'flavours/glitch/store'; +import { makeGetStatus } from 'flavours/glitch/selectors'; +import { useAppSelector } from 'flavours/glitch/store'; + +import styles from './index.module.scss'; const getStatus = makeGetStatus() as unknown as (arg0: any, arg1: any) => any; -const getPictureInPicture = makeGetPictureInPicture() as unknown as ( - arg0: any, - arg1: any, -) => any; export const HighlightedPost: React.FC<{ data: TopStatuses; -}> = ({ data }) => { - let statusId, label; + context: 'modal' | 'standalone'; +}> = ({ data, context }) => { + const { by_reblogs, by_favourites, by_replies } = data; + + const statusId = by_reblogs || by_favourites || by_replies; + + const status = useAppSelector((state) => + statusId ? getStatus(state, { id: statusId }) : undefined, + ); - if (data.by_reblogs) { - statusId = data.by_reblogs; + const handleClick = useCallback< + ComponentPropsWithoutRef['onPreventedClick'] + >( + (clickedArea) => { + const link: string = + clickedArea === 'account' + ? status.getIn(['account', 'url']) + : status.get('url'); + + if (context === 'standalone') { + window.location.href = link; + } else { + window.open(link, '_blank'); + } + }, + [status, context], + ); + + if (!status) { + return
; + } + + let label; + if (by_reblogs) { label = ( ); - } else if (data.by_favourites) { - statusId = data.by_favourites; + } else if (by_favourites) { label = ( ); } else { - statusId = data.by_replies; label = ( ); } - const dispatch = useAppDispatch(); - const domain = useAppSelector((state) => state.meta.get('domain')); - const status = useAppSelector((state) => - statusId ? getStatus(state, { id: statusId }) : undefined, - ); - const pictureInPicture = useAppSelector((state) => - statusId ? getPictureInPicture(state, { id: statusId }) : undefined, - ); - const account = useAppSelector((state) => - me ? state.accounts.get(me) : undefined, - ); - - const handleToggleHidden = useCallback(() => { - dispatch(toggleStatusSpoilers(statusId)); - }, [dispatch, statusId]); - - if (!status) { - return ( -
- ); - } - - const displayName = ( - - - , - }} - /> - - {label} - - ); - return ( -
- +
+
+

+ +

+ {context === 'modal' &&

{label}

} +
+ + + +
); }; diff --git a/app/javascript/flavours/glitch/features/annual_report/index.module.scss b/app/javascript/flavours/glitch/features/annual_report/index.module.scss new file mode 100644 index 00000000000000..024518d72f75fe --- /dev/null +++ b/app/javascript/flavours/glitch/features/annual_report/index.module.scss @@ -0,0 +1,351 @@ +$mobile-breakpoint: 540px; + +@font-face { + font-family: silkscreen-wrapstodon; + src: url('@/fonts/silkscreen-wrapstodon/silkscreen-regular.woff2') + format('woff2'); + font-weight: normal; + font-display: swap; + font-style: normal; +} + +.modalWrapper { + box-sizing: border-box; + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + padding: 40px; + overflow-y: auto; + scrollbar-color: var(--color-text-secondary) var(--color-bg-secondary); + + @media (width < $mobile-breakpoint) { + padding: 0; + } + + .loading-indicator .circular-progress { + color: var(--lime); + } +} + +.closeButton { + --default-icon-color: var(--color-bg-primary); + --default-bg-color: var(--color-text-primary); + --hover-icon-color: var(--color-bg-primary); + --hover-bg-color: var(--color-text-primary); + --corner-distance: 18px; + + position: absolute; + top: var(--corner-distance); + right: var(--corner-distance); + padding: 8px; + border-radius: 100%; + + @media (width < $mobile-breakpoint) { + --corner-distance: 16px; + + padding: 4px; + } +} + +.wrapper { + --gradient-strength: 0.4; + + box-sizing: border-box; + position: relative; + max-width: 600px; + padding: 24px; + padding-top: 40px; + contain: layout; + flex: 0 0 auto; + pointer-events: all; + color: var(--color-text-primary); + background: var(--color-bg-primary); + background: + radial-gradient( + at 10% 27%, + rgba(83, 12, 154, var(--gradient-strength)) 0, + transparent 50% + ), + radial-gradient( + at 91% 10%, + rgba(30, 24, 223, var(--gradient-strength)) 0, + transparent 25% + ), + radial-gradient( + at 10% 91%, + rgba(22, 218, 228, var(--gradient-strength)) 0, + transparent 40% + ), + radial-gradient( + at 75% 87%, + rgba(37, 31, 217, var(--gradient-strength)) 0, + transparent 20% + ), + radial-gradient( + at 84% 60%, + rgba(95, 30, 148, var(--gradient-strength)) 0, + transparent 40% + ) + var(--color-bg-primary); + border-radius: 40px; + + @media (width < $mobile-breakpoint) { + padding-inline: 12px; + padding-bottom: 12px; + border-radius: 0; + } +} + +.header { + margin-bottom: 18px; + text-align: center; + + h1 { + font-family: silkscreen-wrapstodon, monospace; + font-size: 28px; + line-height: 1; + margin-bottom: 4px; + padding-inline: 40px; // Prevent overlap with close button + + @media (width < $mobile-breakpoint) { + font-size: 22px; + margin-bottom: 4px; + } + } + + p { + font-size: 14px; + line-height: 1.5; + } +} + +.stack { + --grid-spacing: 12px; + + display: grid; + gap: var(--grid-spacing); +} + +.box { + position: relative; + padding: 24px; + border-radius: 16px; + background: rgb(from var(--color-bg-primary) r g b / 60%); + box-shadow: inset 0 0 0 1px rgb(from var(--color-text-primary) r g b / 40%); + + &::before, + &::after { + content: ''; + position: absolute; + inset-inline: 0; + display: block; + height: 1px; + background-image: linear-gradient( + to right, + transparent, + white, + transparent + ); + } + + &::before { + top: 0; + } + + &::after { + bottom: 0; + } +} + +.content { + display: flex; + flex-direction: column; + justify-content: center; + gap: 8px; + font-size: 14px; + text-align: center; + text-wrap: balance; + + &.comfortable { + gap: 12px; + } +} + +.title { + text-transform: uppercase; + color: var(--color-text-brand-soft); + font-weight: 500; + + &:last-child { + margin-bottom: -3px; + } +} + +.statLarge { + font-size: 24px; + font-weight: 500; + overflow-wrap: break-word; +} + +.statExtraLarge { + font-size: 32px; + font-weight: 500; + line-height: 1; + overflow-wrap: break-word; + + @media (width < $mobile-breakpoint) { + font-size: 24px; + } +} + +.mostBoostedPost { + padding: 0; + padding-top: 24px; + overflow: hidden; +} + +.statsGrid { + display: grid; + gap: var(--grid-spacing); + grid-template-columns: 1fr 2fr; + grid-template-areas: + 'followers hashtag' + 'new-posts hashtag'; + + @media (width < $mobile-breakpoint) { + grid-template-columns: 1fr 1fr; + grid-template-areas: + 'followers new-posts' + 'hashtag hashtag'; + } + + &:empty { + display: none; + } + + &.onlyHashtag { + grid-template-columns: 1fr; + grid-template-areas: 'hashtag'; + } + + &.noHashtag { + grid-template-columns: 1fr 1fr; + grid-template-areas: 'followers new-posts'; + } + + &.singleNumber { + grid-template-columns: 1fr 2fr; + grid-template-areas: 'number hashtag'; + + @media (width < $mobile-breakpoint) { + grid-template-areas: + 'number number' + 'hashtag hashtag'; + } + } + + &.singleNumber.noHashtag { + grid-template-columns: 1fr; + grid-template-areas: 'number'; + } +} + +.followers { + grid-area: followers; + + .singleNumber & { + grid-area: number; + } +} + +.newPosts { + grid-area: new-posts; + + .singleNumber & { + grid-area: number; + } +} + +.mostUsedHashtag { + grid-area: hashtag; + padding-block: 24px; +} + +.archetype { + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; + + p { + max-width: 460px; + } +} + +.archetypeArtboard { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + align-self: center; + width: 180px; + padding-top: 40px; +} + +.archetypeAvatar { + position: absolute; + top: 7px; + left: 4px; + border-radius: 100%; + overflow: hidden; +} + +.archetypeIllustrationWrapper { + position: relative; + width: 92px; + aspect-ratio: 1; + overflow: hidden; + border-radius: 100%; + + &::after { + content: ''; + display: block; + position: absolute; + inset: 0; + border-radius: inherit; + box-shadow: inset -10px -4px 15px #00000080; + } +} + +.archetypeIllustration { + width: 100%; +} + +.blurredImage { + filter: blur(10px); +} + +.archetypePlanetRing { + position: absolute; + top: 0; + left: 0; + mix-blend-mode: screen; +} + +.shareButtonWrapper { + display: flex; + flex-direction: column; + gap: 10px; +} + +.secondaryShareButton { + // Extra selector is needed to override color + &:global(.button) { + color: var(--color-text-primary); + } +} + +.navItemBadge { + background: var(--color-bg-brand-soft); +} diff --git a/app/javascript/flavours/glitch/features/annual_report/index.tsx b/app/javascript/flavours/glitch/features/annual_report/index.tsx index 0c737934b4bea1..5345cbdb725f1a 100644 --- a/app/javascript/flavours/glitch/features/annual_report/index.tsx +++ b/app/javascript/flavours/glitch/features/annual_report/index.tsx @@ -1,98 +1,124 @@ -import { useState, useEffect } from 'react'; +import { useCallback, useEffect, useState } from 'react'; +import type { FC } from 'react'; -import { FormattedMessage } from 'react-intl'; +import { useIntl } from 'react-intl'; +import { useLocation } from 'react-router'; + +import classNames from 'classnames/bind'; + +import { closeModal } from '@/flavours/glitch/actions/modal'; +import { IconButton } from '@/flavours/glitch/components/icon_button'; +import { LoadingIndicator } from '@/flavours/glitch/components/loading_indicator'; +import { getReport } from '@/flavours/glitch/reducers/slices/annual_report'; import { - importFetchedStatuses, - importFetchedAccounts, -} from 'flavours/glitch/actions/importer'; -import { apiRequestGet, apiRequestPost } from 'flavours/glitch/api'; -import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator'; -import { me } from 'flavours/glitch/initial_state'; -import type { Account } from 'flavours/glitch/models/account'; -import type { AnnualReport as AnnualReportData } from 'flavours/glitch/models/annual_report'; -import type { Status } from 'flavours/glitch/models/status'; -import { useAppSelector, useAppDispatch } from 'flavours/glitch/store'; + createAppSelector, + useAppDispatch, + useAppSelector, +} from '@/flavours/glitch/store'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { Archetype } from './archetype'; import { Followers } from './followers'; import { HighlightedPost } from './highlighted_post'; +import styles from './index.module.scss'; import { MostUsedHashtag } from './most_used_hashtag'; import { NewPosts } from './new_posts'; -import { Percentile } from './percentile'; - -interface AnnualReportResponse { - annual_reports: AnnualReportData[]; - accounts: Account[]; - statuses: Status[]; -} - -export const AnnualReport: React.FC<{ - year: string; -}> = ({ year }) => { - const [response, setResponse] = useState(null); - const [loading, setLoading] = useState(false); - const currentAccount = useAppSelector((state) => - me ? state.accounts.get(me) : undefined, - ); + +const moduleClassNames = classNames.bind(styles); + +export const accountSelector = createAppSelector( + [(state) => state.accounts, (state) => state.annualReport.report], + (accounts, report) => { + if (report?.schema_version === 2) { + return accounts.get(report.account_id); + } + return undefined; + }, +); + +export const AnnualReport: FC<{ context?: 'modal' | 'standalone' }> = ({ + context = 'standalone', +}) => { + const intl = useIntl(); const dispatch = useAppDispatch(); + const report = useAppSelector((state) => state.annualReport.report); + const account = useAppSelector(accountSelector); + const needsReport = !report; // Make into boolean to avoid object comparison in deps. useEffect(() => { - setLoading(true); + if (needsReport) { + void dispatch(getReport()); + } + }, [dispatch, needsReport]); - apiRequestGet(`v1/annual_reports/${year}`) - .then((data) => { - dispatch(importFetchedStatuses(data.statuses)); - dispatch(importFetchedAccounts(data.accounts)); + const close = useCallback(() => { + dispatch(closeModal({ modalType: 'ANNUAL_REPORT', ignoreFocus: false })); + }, [dispatch]); - setResponse(data); - setLoading(false); - - return apiRequestPost(`v1/annual_reports/${year}/read`); - }) - .catch(() => { - setLoading(false); - }); - }, [dispatch, year, setResponse, setLoading]); + // Close modal when navigating away from within + const { pathname } = useLocation(); + const [initialPathname] = useState(pathname); + useEffect(() => { + if (pathname !== initialPathname) { + close(); + } + }, [pathname, initialPathname, close]); - if (loading) { + if (needsReport) { return ; } - const report = response?.annual_reports[0]; + const newPostCount = report.data.time_series.reduce( + (sum, item) => sum + item.statuses, + 0, + ); + + const newFollowerCount = + context === 'modal' && + report.data.time_series.reduce((sum, item) => sum + item.followers, 0); - if (!report) { - return null; - } + const topHashtag = report.data.top_hashtags[0]; return ( -
-
-

- -

-

- +

+

Wrapstodon {report.year}

+ {account &&

@{account.acct}

} + {context === 'modal' && ( + -

+ )}
-
- - - - - - +
+ +
+ {!!newFollowerCount && } + {!!newPostCount && } + {topHashtag && ( + + )} +
+
); diff --git a/app/javascript/flavours/glitch/features/annual_report/modal.tsx b/app/javascript/flavours/glitch/features/annual_report/modal.tsx new file mode 100644 index 00000000000000..29034de6b9114b --- /dev/null +++ b/app/javascript/flavours/glitch/features/annual_report/modal.tsx @@ -0,0 +1,85 @@ +import type { MouseEventHandler } from 'react'; +import { useCallback, useEffect } from 'react'; + +import classNames from 'classnames'; + +import { closeModal } from '@/flavours/glitch/actions/modal'; +import { generateReport } from '@/flavours/glitch/reducers/slices/annual_report'; +import { useAppDispatch, useAppSelector } from '@/flavours/glitch/store'; + +import { AnnualReport } from '.'; +import { AnnualReportAnnouncement } from './announcement'; +import styles from './index.module.scss'; + +const AnnualReportModal: React.FC<{ + onChangeBackgroundColor: (color: string) => void; +}> = ({ onChangeBackgroundColor }) => { + useEffect(() => { + onChangeBackgroundColor('var(--color-bg-media-base)'); + }, [onChangeBackgroundColor]); + + const { state, year } = useAppSelector((state) => state.annualReport); + + const showAnnouncement = year && state && state !== 'available'; + + const dispatch = useAppDispatch(); + + const handleBuildRequest = useCallback(() => { + void dispatch(generateReport()); + }, [dispatch]); + + const handleClose = useCallback(() => { + dispatch(closeModal({ modalType: 'ANNUAL_REPORT', ignoreFocus: false })); + }, [dispatch]); + + const handleCloseModal: MouseEventHandler = useCallback( + (e) => { + if (e.target === e.currentTarget) { + handleClose(); + } + }, + [handleClose], + ); + + // Auto-close if ineligible + useEffect(() => { + if (state === 'ineligible') { + handleClose(); + } + }, [handleClose, state]); + + if (state === 'ineligible') { + // Not sure how you got here, but don't show anything. + return null; + } + + return ( + // It's fine not to provide a keyboard handler here since there is a global + // [Esc] key listener that will close open modals. + // This onClick handler is needed since the modalWrapper styles overlap the + // default modal backdrop, preventing clicks to pass through. + // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions +
+ {!showAnnouncement ? ( + + ) : ( + + )} +
+ ); +}; + +// eslint-disable-next-line import/no-default-export +export default AnnualReportModal; diff --git a/app/javascript/flavours/glitch/features/annual_report/most_used_hashtag.tsx b/app/javascript/flavours/glitch/features/annual_report/most_used_hashtag.tsx index 6bf7493960d2fb..46f17fff9b4ab9 100644 --- a/app/javascript/flavours/glitch/features/annual_report/most_used_hashtag.tsx +++ b/app/javascript/flavours/glitch/features/annual_report/most_used_hashtag.tsx @@ -1,30 +1,50 @@ import { FormattedMessage } from 'react-intl'; +import classNames from 'classnames'; + +import { DisplayName } from '@/flavours/glitch/components/display_name'; +import type { Account } from '@/flavours/glitch/models/account'; import type { NameAndCount } from 'flavours/glitch/models/annual_report'; -export const MostUsedHashtag: React.FC<{ - data: NameAndCount[]; -}> = ({ data }) => { - const hashtag = data[0]; +import styles from './index.module.scss'; +export const MostUsedHashtag: React.FC<{ + hashtag: NameAndCount; + context: 'modal' | 'standalone'; + account?: Account; +}> = ({ hashtag, context, account }) => { return ( -
-
- {hashtag ? ( - <>#{hashtag.name} - ) : ( - - )} -
-
+
+
+ +
#{hashtag.name}
+ +

+ {context === 'modal' && ( + + )} + {context !== 'modal' && account && ( + , + }} + /> + )} +

); }; diff --git a/app/javascript/flavours/glitch/features/annual_report/nav_item.tsx b/app/javascript/flavours/glitch/features/annual_report/nav_item.tsx new file mode 100644 index 00000000000000..8ac98266129ad8 --- /dev/null +++ b/app/javascript/flavours/glitch/features/annual_report/nav_item.tsx @@ -0,0 +1,53 @@ +import { useCallback } from 'react'; +import type { FC } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import classNames from 'classnames'; + +import { openModal } from '@/flavours/glitch/actions/modal'; +import { Icon } from '@/flavours/glitch/components/icon'; +import { + createAppSelector, + useAppDispatch, + useAppSelector, +} from '@/flavours/glitch/store'; +import IconPlanet from '@/images/icons/icon_planet.svg?react'; + +import classes from './index.module.scss'; + +const selectReportModalOpen = createAppSelector( + [(state) => state.modal.getIn(['stack', 0, 'modalType'])], + (modalType) => modalType === 'ANNUAL_REPORT', +); + +export const AnnualReportNavItem: FC = () => { + const { state, year } = useAppSelector((state) => state.annualReport); + const active = useAppSelector(selectReportModalOpen); + + const dispatch = useAppDispatch(); + const handleClick = useCallback(() => { + dispatch(openModal({ modalType: 'ANNUAL_REPORT', modalProps: {} })); + }, [dispatch]); + + if (!year || !state || state === 'ineligible') { + return null; + } + + return ( + + ); +}; diff --git a/app/javascript/flavours/glitch/features/annual_report/new_posts.tsx b/app/javascript/flavours/glitch/features/annual_report/new_posts.tsx index 4ca286debb68d5..9a265f0b9d47a2 100644 --- a/app/javascript/flavours/glitch/features/annual_report/new_posts.tsx +++ b/app/javascript/flavours/glitch/features/annual_report/new_posts.tsx @@ -1,51 +1,23 @@ import { FormattedNumber, FormattedMessage } from 'react-intl'; -import ChatBubbleIcon from '@/material-icons/400-24px/chat_bubble.svg?react'; -import type { TimeSeriesMonth } from 'flavours/glitch/models/annual_report'; +import classNames from 'classnames'; -export const NewPosts: React.FC<{ - data: TimeSeriesMonth[]; -}> = ({ data }) => { - const posts = data.reduce((sum, item) => sum + item.statuses, 0); +import styles from './index.module.scss'; +export const NewPosts: React.FC<{ + count: number; +}> = ({ count }) => { return ( -
- - - - - - - - - - - -
- +
+
+
-
+ +
diff --git a/app/javascript/flavours/glitch/features/annual_report/share_button.tsx b/app/javascript/flavours/glitch/features/annual_report/share_button.tsx new file mode 100644 index 00000000000000..58ea88933605b3 --- /dev/null +++ b/app/javascript/flavours/glitch/features/annual_report/share_button.tsx @@ -0,0 +1,96 @@ +import { useCallback } from 'react'; +import type { FC } from 'react'; + +import { defineMessages, useIntl } from 'react-intl'; + +import { showAlert } from '@/flavours/glitch/actions/alerts'; +import { resetCompose, focusCompose } from '@/flavours/glitch/actions/compose'; +import { closeModal } from '@/flavours/glitch/actions/modal'; +import { Button } from '@/flavours/glitch/components/button'; +import type { AnnualReport as AnnualReportData } from '@/flavours/glitch/models/annual_report'; +import { useAppDispatch } from '@/flavours/glitch/store'; + +import { archetypeNames } from './archetype'; +import styles from './index.module.scss'; + +const messages = defineMessages({ + share_message: { + id: 'annual_report.summary.share_message', + defaultMessage: 'I got the {archetype} archetype!', + }, + share_on_mastodon: { + id: 'annual_report.summary.share_on_mastodon', + defaultMessage: 'Share on Mastodon', + }, + share_elsewhere: { + id: 'annual_report.summary.share_elsewhere', + defaultMessage: 'Share elsewhere', + }, + copy_link: { + id: 'annual_report.summary.copy_link', + defaultMessage: 'Copy link', + }, + copied: { + id: 'copy_icon_button.copied', + defaultMessage: 'Copied to clipboard', + }, +}); + +export const ShareButton: FC<{ report: AnnualReportData }> = ({ report }) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + + const handleShareClick = useCallback(() => { + // Generate the share message. + const archetypeName = intl.formatMessage( + archetypeNames[report.data.archetype], + ); + const shareLines = [ + intl.formatMessage(messages.share_message, { + archetype: archetypeName, + }), + ]; + // Share URL is only available for schema version 2. + if (report.schema_version === 2 && report.share_url) { + shareLines.push(report.share_url); + } + shareLines.push(`#Wrapstodon${report.year}`); + + // Reset the composer and focus it with the share message, then close the modal. + dispatch(resetCompose()); + dispatch(focusCompose(shareLines.join('\n\n'))); + dispatch(closeModal({ modalType: 'ANNUAL_REPORT', ignoreFocus: false })); + }, [report, intl, dispatch]); + + const supportsNativeShare = 'share' in navigator; + + const handleSecondaryShare = useCallback(() => { + if (report.schema_version === 2 && report.share_url) { + if (supportsNativeShare) { + void navigator.share({ + url: report.share_url, + }); + } else { + void navigator.clipboard.writeText(report.share_url); + dispatch(showAlert({ message: messages.copied })); + } + } + }, [report, supportsNativeShare, dispatch]); + + return ( +
+
+ ); +}; diff --git a/app/javascript/flavours/glitch/features/annual_report/shared_page.module.scss b/app/javascript/flavours/glitch/features/annual_report/shared_page.module.scss new file mode 100644 index 00000000000000..ea3ea471b90153 --- /dev/null +++ b/app/javascript/flavours/glitch/features/annual_report/shared_page.module.scss @@ -0,0 +1,58 @@ +$mobile-breakpoint: 540px; + +.wrapper { + box-sizing: border-box; + max-width: 600px; + margin-inline: auto; + padding: 40px 10px; + + @media (width < $mobile-breakpoint) { + padding-top: 0; + padding-inline: 0; + } +} + +.footer { + display: flex; + flex-direction: column; + align-items: center; + gap: 1.8rem; + margin-top: 2rem; + font-size: 16px; + line-height: 1.4; + text-align: center; + color: var(--color-text-secondary); + + strong { + font-weight: 600; + } + + a:any-link { + color: inherit; + text-decoration: underline; + text-underline-offset: 0.2em; + } + + a:hover { + color: var(--color-text-primary); + } +} + +.logo { + width: 2rem; + opacity: 0.6; +} + +.footerSection { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5rem; +} + +.linkList { + list-style: none; + display: flex; + flex-wrap: wrap; + gap: 12px; +} diff --git a/app/javascript/flavours/glitch/features/annual_report/shared_page.tsx b/app/javascript/flavours/glitch/features/annual_report/shared_page.tsx new file mode 100644 index 00000000000000..634f5c2d120075 --- /dev/null +++ b/app/javascript/flavours/glitch/features/annual_report/shared_page.tsx @@ -0,0 +1,68 @@ +import type { FC } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import { DisplayName } from '@/flavours/glitch/components/display_name'; +import { IconLogo } from '@/flavours/glitch/components/logo'; +import { useAppSelector } from '@/flavours/glitch/store'; + +import { AnnualReport, accountSelector } from './index'; +import classes from './shared_page.module.scss'; + +export const WrapstodonSharedPage: FC = () => { + const account = useAppSelector(accountSelector); + const domain = useAppSelector((state) => state.meta.get('domain') as string); + return ( +
+ +
+
+ + + +
+ +
+ , + domain: {domain}, + }} + tagName='p' + /> + + + +
+
+
+ ); +}; diff --git a/app/javascript/flavours/glitch/features/annual_report/timeline.tsx b/app/javascript/flavours/glitch/features/annual_report/timeline.tsx new file mode 100644 index 00000000000000..a56ac379e66758 --- /dev/null +++ b/app/javascript/flavours/glitch/features/annual_report/timeline.tsx @@ -0,0 +1,41 @@ +import { useCallback } from 'react'; +import type { FC } from 'react'; + +import { openModal } from '@/flavours/glitch/actions/modal'; +import { useDismissible } from '@/flavours/glitch/hooks/useDismissible'; +import { generateReport } from '@/flavours/glitch/reducers/slices/annual_report'; +import { useAppDispatch, useAppSelector } from '@/flavours/glitch/store'; + +import { AnnualReportAnnouncement } from './announcement'; + +export const AnnualReportTimeline: FC = () => { + const { state, year } = useAppSelector((state) => state.annualReport); + + const dispatch = useAppDispatch(); + const handleBuildRequest = useCallback(() => { + void dispatch(generateReport()); + }, [dispatch]); + + const { wasDismissed, dismiss } = useDismissible( + `annual_report_announcement_${year}`, + ); + + const handleOpen = useCallback(() => { + dispatch(openModal({ modalType: 'ANNUAL_REPORT', modalProps: {} })); + dismiss(); + }, [dismiss, dispatch]); + + if (!year || wasDismissed || !state || state === 'ineligible') { + return null; + } + + return ( + + ); +}; diff --git a/app/javascript/flavours/glitch/features/audio/index.tsx b/app/javascript/flavours/glitch/features/audio/index.tsx index a8e1550f84a664..0a1a054bdd88d1 100644 --- a/app/javascript/flavours/glitch/features/audio/index.tsx +++ b/app/javascript/flavours/glitch/features/audio/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useCallback, useState, useId } from 'react'; +import { useEffect, useRef, useCallback, useState } from 'react'; import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; @@ -22,6 +22,8 @@ import { useAudioVisualizer } from 'flavours/glitch/hooks/useAudioVisualizer'; import { displayMedia, useBlurhash } from 'flavours/glitch/initial_state'; import { playerSettings } from 'flavours/glitch/settings'; +import { AudioVisualizer } from './visualizer'; + const messages = defineMessages({ play: { id: 'video.play', defaultMessage: 'Play' }, pause: { id: 'video.pause', defaultMessage: 'Pause' }, @@ -39,8 +41,8 @@ const persistVolume = (volume: number, muted: boolean) => { }; const restoreVolume = (audio: HTMLAudioElement) => { - const volume = (playerSettings.get('volume') as number | undefined) ?? 0.5; - const muted = (playerSettings.get('muted') as boolean | undefined) ?? false; + const volume = playerSettings.get('volume') ?? 0.5; + const muted = playerSettings.get('muted') ?? false; audio.volume = volume; audio.muted = muted; @@ -116,7 +118,6 @@ export const Audio: React.FC<{ const seekRef = useRef(null); const volumeRef = useRef(null); const hoverTimeoutRef = useRef | null>(); - const accessibilityId = useId(); const { audioContextRef, sourceRef, gainNodeRef, playAudio, pauseAudio } = useAudioContext({ audioElementRef: audioRef }); @@ -538,19 +539,6 @@ export const Audio: React.FC<{ [togglePlay, toggleMute], ); - const springForBand0 = useSpring({ - to: { r: 50 + (frequencyBands[0] ?? 0) * 10 }, - config: config.wobbly, - }); - const springForBand1 = useSpring({ - to: { r: 50 + (frequencyBands[1] ?? 0) * 10 }, - config: config.wobbly, - }); - const springForBand2 = useSpring({ - to: { r: 50 + (frequencyBands[2] ?? 0) * 10 }, - config: config.wobbly, - }); - const progress = Math.min((currentTime / loadedDuration) * 100, 100); const effectivelyMuted = muted || volume === 0; @@ -641,81 +629,7 @@ export const Audio: React.FC<{
- - - - - - - - - - - - - - - - - - - - - - + - - - - - + + + + + +
); } @@ -134,12 +139,12 @@ class ModifierPicker extends PureComponent { this.props.onClose(); }; - render () { + render() { const { active, modifier } = this.props; return (
- +
); @@ -291,7 +296,6 @@ class EmojiPickerMenuImpl extends PureComponent { notFound={notFoundFn} autoFocus={this.state.readyToFocus} emojiTooltip - native={useSystemEmojiFont} /> text.length > 20; + const LanguageDropdownMenu: React.FC<{ value: string; guess?: string; @@ -375,18 +377,32 @@ export const LanguageDropdown: React.FC = () => { ); useEffect(() => { - if (text.length > 20) { + if (isTextLongEnoughForGuess(text)) { debouncedGuess(text, setGuess); } else { debouncedGuess.cancel(); - setGuess(''); } }, [text, setGuess]); + // Keeping track of the previous render's text length here + // to be able to reset the guess when the text length drops + // below the threshold needed to make a guess + const [wasLongText, setWasLongText] = useState(() => + isTextLongEnoughForGuess(text), + ); + if (wasLongText !== isTextLongEnoughForGuess(text)) { + setWasLongText(isTextLongEnoughForGuess(text)); + + if (wasLongText) { + setGuess(''); + } + } + return ( -
+ <>
)} -
+ ); }; diff --git a/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.jsx b/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.jsx deleted file mode 100644 index d481b030a810e0..00000000000000 --- a/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.jsx +++ /dev/null @@ -1,158 +0,0 @@ -import PropTypes from 'prop-types'; -import { PureComponent } from 'react'; - -import { injectIntl, defineMessages } from 'react-intl'; - -import classNames from 'classnames'; - -import Overlay from 'react-overlays/Overlay'; - -import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react'; -import LockIcon from '@/material-icons/400-24px/lock.svg?react'; -import PublicIcon from '@/material-icons/400-24px/public.svg?react'; -import QuietTimeIcon from '@/material-icons/400-24px/quiet_time.svg?react'; -import { DropdownSelector } from 'flavours/glitch/components/dropdown_selector'; -import { Icon } from 'flavours/glitch/components/icon'; - -export const messages = defineMessages({ - public_short: { id: 'privacy.public.short', defaultMessage: 'Public' }, - public_long: { id: 'privacy.public.long', defaultMessage: 'Anyone on and off Mastodon' }, - unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Quiet public' }, - unlisted_long: { id: 'privacy.unlisted.long', defaultMessage: 'Hidden from Mastodon search results, trending, and public timelines' }, - private_short: { id: 'privacy.private.short', defaultMessage: 'Followers' }, - private_long: { id: 'privacy.private.long', defaultMessage: 'Only your followers' }, - direct_short: { id: 'privacy.direct.short', defaultMessage: 'Specific people' }, - direct_long: { id: 'privacy.direct.long', defaultMessage: 'Everyone mentioned in the post' }, - change_privacy: { id: 'privacy.change', defaultMessage: 'Change post privacy' }, - unlisted_extra: { id: 'privacy.unlisted.additional', defaultMessage: 'This behaves exactly like public, except the post will not appear in live feeds or hashtags, explore, or Mastodon search, even if you are opted-in account-wide.' }, -}); - -class PrivacyDropdown extends PureComponent { - - static propTypes = { - value: PropTypes.string.isRequired, - onChange: PropTypes.func.isRequired, - noDirect: PropTypes.bool, - container: PropTypes.func, - disabled: PropTypes.bool, - intl: PropTypes.object.isRequired, - }; - - state = { - open: false, - placement: 'bottom', - }; - - handleToggle = () => { - if (this.state.open && this.activeElement) { - this.activeElement.focus({ preventScroll: true }); - } - - this.setState({ open: !this.state.open }); - }; - - handleKeyDown = e => { - switch(e.key) { - case 'Escape': - this.handleClose(); - break; - } - }; - - handleMouseDown = () => { - if (!this.state.open) { - this.activeElement = document.activeElement; - } - }; - - handleButtonKeyDown = (e) => { - switch(e.key) { - case ' ': - case 'Enter': - this.handleMouseDown(); - break; - } - }; - - handleClose = () => { - if (this.state.open && this.activeElement) { - this.activeElement.focus({ preventScroll: true }); - } - this.setState({ open: false }); - }; - - handleChange = value => { - this.props.onChange(value); - }; - - UNSAFE_componentWillMount () { - const { intl: { formatMessage } } = this.props; - - this.options = [ - { icon: 'globe', iconComponent: PublicIcon, value: 'public', text: formatMessage(messages.public_short), meta: formatMessage(messages.public_long) }, - { icon: 'unlock', iconComponent: QuietTimeIcon, value: 'unlisted', text: formatMessage(messages.unlisted_short), meta: formatMessage(messages.unlisted_long), extra: formatMessage(messages.unlisted_extra) }, - { icon: 'lock', iconComponent: LockIcon, value: 'private', text: formatMessage(messages.private_short), meta: formatMessage(messages.private_long) }, - ]; - - if (!this.props.noDirect) { - this.options.push( - { icon: 'at', iconComponent: AlternateEmailIcon, value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) }, - ); - } - } - - setTargetRef = c => { - this.target = c; - }; - - findTarget = () => { - return this.target; - }; - - handleOverlayEnter = (state) => { - this.setState({ placement: state.placement }); - }; - - render () { - const { value, container, disabled, intl } = this.props; - const { open, placement } = this.state; - - const valueOption = this.options.find(item => item.value === value); - - return ( -
- - - - {({ props, placement }) => ( -
-
- -
-
- )} -
-
- ); - } - -} - -export default injectIntl(PrivacyDropdown); diff --git a/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.tsx b/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.tsx new file mode 100644 index 00000000000000..ae15d01dcebf50 --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.tsx @@ -0,0 +1,199 @@ +import { useCallback, useRef, useState } from 'react'; + +import { defineMessages, useIntl } from 'react-intl'; + +import classNames from 'classnames'; + +import type { OverlayProps } from 'react-overlays/Overlay'; +import Overlay from 'react-overlays/Overlay'; + +import type { StatusVisibility } from '@/flavours/glitch/api_types/statuses'; +import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react'; +import LockIcon from '@/material-icons/400-24px/lock.svg?react'; +import PublicIcon from '@/material-icons/400-24px/public.svg?react'; +import QuietTimeIcon from '@/material-icons/400-24px/quiet_time.svg?react'; +import { DropdownSelector } from 'flavours/glitch/components/dropdown_selector'; +import { Icon } from 'flavours/glitch/components/icon'; + +export const messages = defineMessages({ + public_short: { id: 'privacy.public.short', defaultMessage: 'Public' }, + public_long: { + id: 'privacy.public.long', + defaultMessage: 'Anyone on and off Mastodon', + }, + unlisted_short: { + id: 'privacy.unlisted.short', + defaultMessage: 'Quiet public', + }, + unlisted_long: { + id: 'privacy.unlisted.long', + defaultMessage: + 'Hidden from Mastodon search results, trending, and public timelines', + }, + private_short: { id: 'privacy.private.short', defaultMessage: 'Followers' }, + private_long: { + id: 'privacy.private.long', + defaultMessage: 'Only your followers', + }, + direct_short: { + id: 'privacy.direct.short', + defaultMessage: 'Specific people', + }, + direct_long: { + id: 'privacy.direct.long', + defaultMessage: 'Everyone mentioned in the post', + }, + change_privacy: { + id: 'privacy.change', + defaultMessage: 'Change post privacy', + }, + unlisted_extra: { + id: 'privacy.unlisted.additional', + defaultMessage: + 'This behaves exactly like public, except the post will not appear in live feeds or hashtags, explore, or Mastodon search, even if you are opted-in account-wide.', + }, +}); + +interface PrivacyDropdownProps { + value: StatusVisibility; + onChange: (value: StatusVisibility) => void; + noDirect?: boolean; + container?: OverlayProps['container']; + disabled?: boolean; +} + +const PrivacyDropdown: React.FC = ({ + value, + onChange, + noDirect, + container, + disabled, +}) => { + const intl = useIntl(); + const overlayTargetRef = useRef(null); + const previousFocusTargetRef = useRef(null); + const [isOpen, setIsOpen] = useState(false); + + const handleClose = useCallback(() => { + if (isOpen && previousFocusTargetRef.current) { + previousFocusTargetRef.current.focus({ preventScroll: true }); + } + setIsOpen(false); + }, [isOpen]); + + const handleToggle = useCallback(() => { + if (isOpen) { + handleClose(); + } + setIsOpen((prev) => !prev); + }, [handleClose, isOpen]); + + const registerPreviousFocusTarget = useCallback(() => { + if (!isOpen) { + previousFocusTargetRef.current = document.activeElement as HTMLElement; + } + }, [isOpen]); + + const handleButtonKeyDown = useCallback( + (e: React.KeyboardEvent) => { + if ([' ', 'Enter'].includes(e.key)) { + registerPreviousFocusTarget(); + } + }, + [registerPreviousFocusTarget], + ); + + const options = [ + { + icon: 'globe', + iconComponent: PublicIcon, + value: 'public', + text: intl.formatMessage(messages.public_short), + meta: intl.formatMessage(messages.public_long), + }, + { + icon: 'unlock', + iconComponent: QuietTimeIcon, + value: 'unlisted', + text: intl.formatMessage(messages.unlisted_short), + meta: intl.formatMessage(messages.unlisted_long), + extra: intl.formatMessage(messages.unlisted_extra), + }, + { + icon: 'lock', + iconComponent: LockIcon, + value: 'private', + text: intl.formatMessage(messages.private_short), + meta: intl.formatMessage(messages.private_long), + }, + ]; + + if (!noDirect) { + options.push({ + icon: 'at', + iconComponent: AlternateEmailIcon, + value: 'direct', + text: intl.formatMessage(messages.direct_short), + meta: intl.formatMessage(messages.direct_long), + }); + } + + const selectedOption = + options.find((item) => item.value === value) ?? options.at(0); + + return ( +
+ + + + {({ props, placement }) => ( +
+
+ +
+
+ )} +
+
+ ); +}; + +// eslint-disable-next-line import/no-default-export +export default PrivacyDropdown; diff --git a/app/javascript/flavours/glitch/features/compose/components/quote_placeholder.tsx b/app/javascript/flavours/glitch/features/compose/components/quote_placeholder.tsx new file mode 100644 index 00000000000000..18e131ec5e1287 --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/components/quote_placeholder.tsx @@ -0,0 +1,48 @@ +import { useCallback } from 'react'; +import type { FC } from 'react'; + +import { defineMessages, useIntl } from 'react-intl'; + +import { cancelPasteLinkCompose } from '@/flavours/glitch/actions/compose_typed'; +import { useAppDispatch } from '@/flavours/glitch/store'; +import CancelFillIcon from '@/material-icons/400-24px/cancel-fill.svg?react'; +import { DisplayName } from 'flavours/glitch/components/display_name'; +import { IconButton } from 'flavours/glitch/components/icon_button'; +import { Skeleton } from 'flavours/glitch/components/skeleton'; + +const messages = defineMessages({ + quote_cancel: { id: 'status.quote.cancel', defaultMessage: 'Cancel quote' }, +}); + +export const QuotePlaceholder: FC = () => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + const handleQuoteCancel = useCallback(() => { + dispatch(cancelPasteLinkCompose()); + }, [dispatch]); + + return ( +
+
+
+
+ +
+
+ +
+ +
+
+ +
+
+
+ ); +}; diff --git a/app/javascript/flavours/glitch/features/compose/components/quoted_post.tsx b/app/javascript/flavours/glitch/features/compose/components/quoted_post.tsx index db95c58cb96076..a59ec923bbb3ff 100644 --- a/app/javascript/flavours/glitch/features/compose/components/quoted_post.tsx +++ b/app/javascript/flavours/glitch/features/compose/components/quoted_post.tsx @@ -7,11 +7,17 @@ import { quoteComposeCancel } from '@/flavours/glitch/actions/compose_typed'; import { QuotedStatus } from '@/flavours/glitch/components/status_quoted'; import { useAppDispatch, useAppSelector } from '@/flavours/glitch/store'; +import { QuotePlaceholder } from './quote_placeholder'; + export const ComposeQuotedStatus: FC = () => { const quotedStatusId = useAppSelector( (state) => state.compose.get('quoted_status_id') as string | null, ); + const isFetchingLink = useAppSelector( + (state) => !!state.compose.get('fetching_link'), + ); + const isEditing = useAppSelector((state) => !!state.compose.get('id')); const quote = useMemo( @@ -30,7 +36,9 @@ export const ComposeQuotedStatus: FC = () => { dispatch(quoteComposeCancel()); }, [dispatch]); - if (!quote) { + if (isFetchingLink && !quote) { + return ; + } else if (!quote) { return null; } diff --git a/app/javascript/flavours/glitch/features/compose/components/reply_indicator.jsx b/app/javascript/flavours/glitch/features/compose/components/reply_indicator.jsx index d88fb5ef9327f5..048b23c7759394 100644 --- a/app/javascript/flavours/glitch/features/compose/components/reply_indicator.jsx +++ b/app/javascript/flavours/glitch/features/compose/components/reply_indicator.jsx @@ -34,9 +34,7 @@ export const ReplyIndicator = () => { {(status.get('poll') || status.get('media_attachments').size > 0) && ( diff --git a/app/javascript/flavours/glitch/features/compose/components/search.tsx b/app/javascript/flavours/glitch/features/compose/components/search.tsx index 862b6c451e81cb..f4eeaacf20f273 100644 --- a/app/javascript/flavours/glitch/features/compose/components/search.tsx +++ b/app/javascript/flavours/glitch/features/compose/components/search.tsx @@ -1,4 +1,11 @@ -import { useCallback, useState, useRef, useEffect } from 'react'; +import { + useCallback, + useState, + useRef, + useEffect, + useMemo, + useId, +} from 'react'; import { defineMessages, @@ -97,183 +104,197 @@ export const Search: React.FC<{ const [expanded, setExpanded] = useState(false); const [selectedOption, setSelectedOption] = useState(-1); const [quickActions, setQuickActions] = useState([]); - useEffect(() => { - setValue(initialValue ?? ''); - setQuickActions([]); - }, [initialValue]); - const searchOptions: SearchOption[] = []; const unfocus = useCallback(() => { document.querySelector('.ui')?.parentElement?.focus(); setExpanded(false); }, []); - if (searchEnabled) { - searchOptions.push( - { - key: 'prompt-has', - label: ( - <> - has:{' '} - - - ), - action: (e) => { - e.preventDefault(); - insertText('has:'); + const insertText = useCallback((text: string) => { + setValue((currentValue) => { + if (currentValue === '') { + return text; + } else if (currentValue.endsWith(' ')) { + return `${currentValue}${text}`; + } else { + return `${currentValue} ${text}`; + } + }); + }, []); + + const searchOptions = useMemo(() => { + if (!searchEnabled) { + return []; + } else { + const options: SearchOption[] = [ + { + key: 'prompt-has', + label: ( + <> + has:{' '} + + + ), + action: (e) => { + e.preventDefault(); + insertText('has:'); + }, }, - }, - { - key: 'prompt-is', - label: ( - <> - is:{' '} - - - ), - action: (e) => { - e.preventDefault(); - insertText('is:'); + { + key: 'prompt-is', + label: ( + <> + is:{' '} + + + ), + action: (e) => { + e.preventDefault(); + insertText('is:'); + }, }, - }, - { - key: 'prompt-language', - label: ( - <> - language:{' '} - - - ), - action: (e) => { - e.preventDefault(); - insertText('language:'); + { + key: 'prompt-language', + label: ( + <> + language:{' '} + + + ), + action: (e) => { + e.preventDefault(); + insertText('language:'); + }, }, - }, - { - key: 'prompt-from', - label: ( - <> - from:{' '} - - - ), - action: (e) => { - e.preventDefault(); - insertText('from:'); + { + key: 'prompt-from', + label: ( + <> + from:{' '} + + + ), + action: (e) => { + e.preventDefault(); + insertText('from:'); + }, }, - }, - { - key: 'prompt-before', - label: ( - <> - before:{' '} - - - ), - action: (e) => { - e.preventDefault(); - insertText('before:'); + { + key: 'prompt-before', + label: ( + <> + before:{' '} + + + ), + action: (e) => { + e.preventDefault(); + insertText('before:'); + }, }, - }, - { - key: 'prompt-during', - label: ( - <> - during:{' '} - - - ), - action: (e) => { - e.preventDefault(); - insertText('during:'); + { + key: 'prompt-during', + label: ( + <> + during:{' '} + + + ), + action: (e) => { + e.preventDefault(); + insertText('during:'); + }, }, - }, - { - key: 'prompt-after', - label: ( - <> - after:{' '} - - - ), - action: (e) => { - e.preventDefault(); - insertText('after:'); + { + key: 'prompt-after', + label: ( + <> + after:{' '} + + + ), + action: (e) => { + e.preventDefault(); + insertText('after:'); + }, }, - }, - { - key: 'prompt-in', - label: ( - <> - in:{' '} - - - ), - action: (e) => { - e.preventDefault(); - insertText('in:'); + { + key: 'prompt-in', + label: ( + <> + in:{' '} + + + ), + action: (e) => { + e.preventDefault(); + insertText('in:'); + }, }, - }, - ); - } - - const recentOptions: SearchOption[] = recent.map((search) => ({ - key: `${search.type}/${search.q}`, - label: labelForRecentSearch(search), - action: () => { - setValue(search.q); - - if (search.type === 'account') { - history.push(`/@${search.q}`); - } else if (search.type === 'hashtag') { - history.push(`/tags/${search.q}`); - } else { - const queryParams = new URLSearchParams({ q: search.q }); - if (search.type) queryParams.set('type', search.type); - history.push({ pathname: '/search', search: queryParams.toString() }); - } - - unfocus(); - }, - forget: (e) => { - e.stopPropagation(); - void dispatch(forgetSearchResult(search)); - }, - })); + ]; + return options; + } + }, [insertText]); + + const recentOptions: SearchOption[] = useMemo( + () => + recent.map((search) => ({ + key: `${search.type}/${search.q}`, + label: labelForRecentSearch(search), + action: () => { + setValue(search.q); + + if (search.type === 'account') { + history.push(`/@${search.q}`); + } else if (search.type === 'hashtag') { + history.push(`/tags/${search.q}`); + } else { + const queryParams = new URLSearchParams({ q: search.q }); + if (search.type) queryParams.set('type', search.type); + history.push({ + pathname: '/search', + search: queryParams.toString(), + }); + } - const navigableOptions = hasValue - ? quickActions.concat(searchOptions) - : recentOptions.concat(quickActions, searchOptions); + unfocus(); + }, + forget: (e) => { + e.stopPropagation(); + void dispatch(forgetSearchResult(search)); + }, + })), + [dispatch, history, recent, unfocus], + ); - const insertText = (text: string) => { - setValue((currentValue) => { - if (currentValue === '') { - return text; - } else if (currentValue.endsWith(' ')) { - return `${currentValue}${text}`; - } else { - return `${currentValue} ${text}`; - } - }); - }; + const navigableOptions: SearchOption[] = useMemo( + () => + hasValue + ? quickActions.concat(searchOptions) + : recentOptions.concat(quickActions, searchOptions), + [hasValue, quickActions, recentOptions, searchOptions], + ); const submit = useCallback( (q: string, type?: SearchType) => { @@ -418,12 +439,17 @@ export const Search: React.FC<{ switch (e.key) { case 'Escape': e.preventDefault(); - unfocus(); + searchInputRef.current?.focus(); + setExpanded(false); break; case 'ArrowDown': e.preventDefault(); + if (!expanded) { + setExpanded(true); + } + if (navigableOptions.length > 0) { setSelectedOption( Math.min(selectedOption + 1, navigableOptions.length - 1), @@ -462,10 +488,10 @@ export const Search: React.FC<{ break; } }, - [unfocus, navigableOptions, selectedOption, submit, value], + [expanded, navigableOptions, selectedOption, submit, value], ); - const handleFocus = useCallback(() => { + const handleInputFocus = useCallback(() => { setExpanded(true); setSelectedOption(-1); @@ -481,10 +507,16 @@ export const Search: React.FC<{ } }, [setExpanded, setSelectedOption, singleColumn]); - const handleBlur = useCallback(() => { + const handleInputBlur = useCallback(() => { setSelectedOption(-1); }, [setSelectedOption]); + const getOptionFocusHandler = useCallback((index: number) => { + return () => { + setSelectedOption(index); + }; + }, []); + const formRef = useRef(null); useEffect(() => { @@ -512,6 +544,8 @@ export const Search: React.FC<{ return () => null; }, [expanded]); + const searchOptionsHeading = useId(); + return (
-
+ {/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions */} +
{!hasValue && ( <>

@@ -551,13 +592,18 @@ export const Search: React.FC<{ tabIndex={0} role='button' onMouseDown={action} + onFocus={getOptionFocusHandler(i)} className={classNames( 'search__popout__menu__item search__popout__menu__item--flex', { selected: selectedOption === i }, )} > {label} -

@@ -588,9 +634,11 @@ export const Search: React.FC<{ @@ -599,7 +647,7 @@ export const Search: React.FC<{ )} -

+

- {searchOptions.map(({ key, label, action }, i) => ( - - ))} + {searchOptions.map(({ key, label, action }, i) => { + const currentIndex = (quickActions.length || recent.length) + i; + return ( + + ); + })}

) : (
diff --git a/app/javascript/flavours/glitch/features/compose/components/upload.tsx b/app/javascript/flavours/glitch/features/compose/components/upload.tsx index 0ef9894428b73d..815f744b094388 100644 --- a/app/javascript/flavours/glitch/features/compose/components/upload.tsx +++ b/app/javascript/flavours/glitch/features/compose/components/upload.tsx @@ -10,6 +10,7 @@ import { useSortable } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import CloseIcon from '@/material-icons/400-20px/close.svg?react'; +import SoundIcon from '@/material-icons/400-24px/audio.svg?react'; import EditIcon from '@/material-icons/400-24px/edit.svg?react'; import WarningIcon from '@/material-icons/400-24px/warning.svg?react'; import { undoUploadCompose } from 'flavours/glitch/actions/compose'; @@ -17,7 +18,18 @@ import { openModal } from 'flavours/glitch/actions/modal'; import { Blurhash } from 'flavours/glitch/components/blurhash'; import { Icon } from 'flavours/glitch/components/icon'; import type { MediaAttachment } from 'flavours/glitch/models/media_attachment'; -import { useAppDispatch, useAppSelector } from 'flavours/glitch/store'; +import { + createAppSelector, + useAppDispatch, + useAppSelector, +} from 'flavours/glitch/store'; + +import { AudioVisualizer } from '../../audio/visualizer'; + +const selectUserAvatar = createAppSelector( + [(state) => state.accounts, (state) => state.meta.get('me') as string], + (accounts, myId) => accounts.get(myId)?.avatar_static, +); export const Upload: React.FC<{ id: string; @@ -38,6 +50,7 @@ export const Upload: React.FC<{ const sensitive = useAppSelector( (state) => state.compose.get('sensitive') as boolean, ); + const userAvatar = useAppSelector(selectUserAvatar); const handleUndoClick = useCallback(() => { dispatch(undoUploadCompose(id)); @@ -67,6 +80,8 @@ export const Upload: React.FC<{ transform: CSS.Transform.toString(transform), transition, }; + const preview_url = media.get('preview_url') as string | null; + const blurhash = media.get('blurhash') as string | null; return (
- {sensitive && ( - + {sensitive && blurhash && ( + + )} + {!sensitive && !preview_url && ( +
+ + +
)}
diff --git a/app/javascript/flavours/glitch/features/compose/components/visibility_button.tsx b/app/javascript/flavours/glitch/features/compose/components/visibility_button.tsx index 1e6462ecd33b76..07f4815e02c0a2 100644 --- a/app/javascript/flavours/glitch/features/compose/components/visibility_button.tsx +++ b/app/javascript/flavours/glitch/features/compose/components/visibility_button.tsx @@ -5,8 +5,10 @@ import { defineMessages, useIntl } from 'react-intl'; import classNames from 'classnames'; -import { changeComposeVisibility } from '@/flavours/glitch/actions/compose'; -import { setComposeQuotePolicy } from '@/flavours/glitch/actions/compose_typed'; +import { + changeComposeVisibility, + setComposeQuotePolicy, +} from '@/flavours/glitch/actions/compose_typed'; import { openModal } from '@/flavours/glitch/actions/modal'; import type { ApiQuotePolicy } from '@/flavours/glitch/api_types/quotes'; import type { StatusVisibility } from '@/flavours/glitch/api_types/statuses'; diff --git a/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js b/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js index 239f2f8a3d0a5b..a8a9f30432f979 100644 --- a/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js +++ b/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js @@ -10,11 +10,16 @@ import { insertEmojiCompose, uploadCompose, } from 'flavours/glitch/actions/compose'; +import { pasteLinkCompose } from 'flavours/glitch/actions/compose_typed'; import { openModal } from 'flavours/glitch/actions/modal'; +import { PRIVATE_QUOTE_MODAL_ID } from 'flavours/glitch/features/ui/components/confirmation_modals/private_quote_notify'; +import { me } from 'flavours/glitch/initial_state'; import { privacyPreference } from 'flavours/glitch/utils/privacy_preference'; import ComposeForm from '../components/compose_form'; +const urlLikeRegex = /^https?:\/\/[^\s]+\/[^\s]+$/i; + const sideArmPrivacy = state => { const inReplyTo = state.getIn(['compose', 'in_reply_to']); const replyPrivacy = inReplyTo ? state.getIn(['statuses', inReplyTo, 'visibility']) : null; @@ -32,6 +37,23 @@ const sideArmPrivacy = state => { return sideArmPrivacy || sideArmBasePrivacy; }; +const processPasteOrDrop = (transfer, e, dispatch) => { + if (transfer && transfer.files.length === 1) { + dispatch(uploadCompose(transfer.files)); + e.preventDefault(); + } else if (transfer && transfer.files.length === 0) { + const data = transfer.getData('text/plain'); + if (!data.match(urlLikeRegex)) return; + + try { + const url = new URL(data); + dispatch(pasteLinkCompose({ url })); + } catch { + return; + } + } +}; + const mapStateToProps = state => ({ text: state.getIn(['compose', 'text']), suggestions: state.getIn(['compose', 'suggestions']), @@ -49,6 +71,11 @@ const mapStateToProps = state => ({ isUploading: state.getIn(['compose', 'is_uploading']), anyMedia: state.getIn(['compose', 'media_attachments']).size > 0, missingAltText: state.getIn(['compose', 'media_attachments']).some(media => ['image', 'gifv'].includes(media.get('type')) && (media.get('description') ?? '').length === 0), + quoteToPrivate: + !!state.getIn(['compose', 'quoted_status_id']) + && state.getIn(['compose', 'privacy']) === 'private' + && state.getIn(['statuses', state.getIn(['compose', 'quoted_status_id']), 'account']) !== me + && !state.getIn(['settings', 'dismissed_banners', PRIVATE_QUOTE_MODAL_ID]), isInReply: state.getIn(['compose', 'in_reply_to']) !== null, lang: state.getIn(['compose', 'language']), sideArm: sideArmPrivacy(state), @@ -62,12 +89,17 @@ const mapDispatchToProps = (dispatch, props) => ({ dispatch(changeCompose(text)); }, - onSubmit (missingAltText, overridePrivacy = null) { + onSubmit ({ missingAltText, quoteToPrivate, overridePrivacy = null }) { if (missingAltText) { dispatch(openModal({ modalType: 'CONFIRM_MISSING_ALT_TEXT', modalProps: { overridePrivacy }, })); + } else if (quoteToPrivate) { + dispatch(openModal({ + modalType: 'CONFIRM_PRIVATE_QUOTE_NOTIFY', + modalProps: {}, + })); } else { dispatch(submitCompose(overridePrivacy, (status) => { if (props.redirectOnSuccess) { @@ -93,8 +125,12 @@ const mapDispatchToProps = (dispatch, props) => ({ dispatch(changeComposeSpoilerText(checked)); }, - onPaste (files) { - dispatch(uploadCompose(files)); + onPaste (e) { + processPasteOrDrop(e.clipboardData, e, dispatch); + }, + + onDrop (e) { + processPasteOrDrop(e.dataTransfer, e, dispatch); }, onPickEmoji (position, data, needsSpace) { diff --git a/app/javascript/flavours/glitch/features/compose/containers/privacy_dropdown_container.js b/app/javascript/flavours/glitch/features/compose/containers/privacy_dropdown_container.js deleted file mode 100644 index 6d3eef13aa237d..00000000000000 --- a/app/javascript/flavours/glitch/features/compose/containers/privacy_dropdown_container.js +++ /dev/null @@ -1,20 +0,0 @@ -import { connect } from 'react-redux'; - -import { changeComposeVisibility } from '../../../actions/compose'; -import { openModal, closeModal } from '../../../actions/modal'; -import { isUserTouching } from '../../../is_mobile'; -import PrivacyDropdown from '../components/privacy_dropdown'; - -const mapStateToProps = state => ({ - value: state.getIn(['compose', 'privacy']), -}); - -const mapDispatchToProps = dispatch => ({ - - onChange (value) { - dispatch(changeComposeVisibility(value)); - }, - -}); - -export default connect(mapStateToProps, mapDispatchToProps)(PrivacyDropdown); diff --git a/app/javascript/flavours/glitch/features/compose/index.tsx b/app/javascript/flavours/glitch/features/compose/index.tsx index 77922f5a22db14..3eb689dd8ad19a 100644 --- a/app/javascript/flavours/glitch/features/compose/index.tsx +++ b/app/javascript/flavours/glitch/features/compose/index.tsx @@ -55,6 +55,11 @@ type ColumnMap = ImmutableMap<'id' | 'uuid' | 'params', string>; const glitchProbability = 1 - 0.0420215528; const totalElefriends = 3; +const pickRandomFriend = () => + Math.random() < glitchProbability + ? Math.floor(Math.random() * totalElefriends) + : totalElefriends; + const Compose: React.FC<{ multiColumn: boolean }> = ({ multiColumn }) => { const intl = useIntl(); const dispatch = useAppDispatch(); @@ -75,11 +80,7 @@ const Compose: React.FC<{ multiColumn: boolean }> = ({ multiColumn }) => { false, ) as boolean, ); - const [elefriend, setElefriend] = useState( - Math.random() < glitchProbability - ? Math.floor(Math.random() * totalElefriends) - : totalElefriends, - ); + const [elefriend, setElefriend] = useState(pickRandomFriend()); useEffect(() => { dispatch(mountCompose()); diff --git a/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.jsx b/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.jsx index cf35e286208351..e7fac1a5db1bac 100644 --- a/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.jsx +++ b/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.jsx @@ -25,6 +25,7 @@ import StatusContent from 'flavours/glitch/components/status_content'; import { Dropdown } from 'flavours/glitch/components/dropdown_menu'; import { makeGetStatus } from 'flavours/glitch/selectors'; import { LinkedDisplayName } from '@/flavours/glitch/components/display_name'; +import { AnimateEmojiProvider } from '@/flavours/glitch/components/emoji/context'; const messages = defineMessages({ more: { id: 'status.more', defaultMessage: 'More' }, @@ -144,9 +145,9 @@ export const Conversation = ({ conversation, scrollKey }) => { {unread && }
-
+ {names} }} /> -
+
= ({ accountId }) => { - const intl = useIntl(); const account = useAppSelector((s) => getAccount(s, accountId)); - const dispatch = useAppDispatch(); - - const handleFollow = useCallback(() => { - if (!account) return; - - if ( - account.getIn(['relationship', 'following']) || - account.getIn(['relationship', 'requested']) - ) { - dispatch( - openModal({ modalType: 'CONFIRM_UNFOLLOW', modalProps: { account } }), - ); - } else { - dispatch(followAccount(account.get('id'))); - } - }, [account, dispatch]); - - const handleBlock = useCallback(() => { - if (account?.relationship?.blocking) { - dispatch(unblockAccount(account.get('id'))); - } - }, [account, dispatch]); - - const handleMute = useCallback(() => { - if (account?.relationship?.muting) { - dispatch(unmuteAccount(account.get('id'))); - } - }, [account, dispatch]); - - const handleEditProfile = useCallback(() => { - window.open('/settings/profile', '_blank'); - }, []); if (!account) return null; - let actionBtn; - - if (me !== account.get('id')) { - if (!account.get('relationship')) { - // Wait until the relationship is loaded - actionBtn = ''; - } else if (account.getIn(['relationship', 'requested'])) { - actionBtn = ( -
); diff --git a/app/javascript/flavours/glitch/features/directory/index.tsx b/app/javascript/flavours/glitch/features/directory/index.tsx index 689f924b8f76c5..3b402bd3917337 100644 --- a/app/javascript/flavours/glitch/features/directory/index.tsx +++ b/app/javascript/flavours/glitch/features/directory/index.tsx @@ -24,7 +24,7 @@ import { ColumnHeader } from 'flavours/glitch/components/column_header'; import { LoadMore } from 'flavours/glitch/components/load_more'; import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator'; import { RadioButton } from 'flavours/glitch/components/radio_button'; -import ScrollContainer from 'flavours/glitch/containers/scroll_container'; +import { ScrollContainer } from 'flavours/glitch/containers/scroll_container'; import { useSearchParam } from 'flavours/glitch/hooks/useSearchParam'; import { useAppDispatch, useAppSelector } from 'flavours/glitch/store'; @@ -209,7 +209,6 @@ export const Directory: React.FC<{ /> {multiColumn && !pinned ? ( - // @ts-expect-error ScrollContainer is not properly typed yet {scrollableArea} diff --git a/app/javascript/flavours/glitch/features/domain_blocks/index.tsx b/app/javascript/flavours/glitch/features/domain_blocks/index.tsx index ba581119ddea12..f444ccd407a6a8 100644 --- a/app/javascript/flavours/glitch/features/domain_blocks/index.tsx +++ b/app/javascript/flavours/glitch/features/domain_blocks/index.tsx @@ -19,14 +19,12 @@ const messages = defineMessages({ const Blocks: React.FC<{ multiColumn: boolean }> = ({ multiColumn }) => { const intl = useIntl(); const [domains, setDomains] = useState([]); - const [loading, setLoading] = useState(false); + const [loading, setLoading] = useState(true); const [next, setNext] = useState(); const hasMore = !!next; const columnRef = useRef(null); useEffect(() => { - setLoading(true); - void apiGetDomainBlocks() .then(({ domains, links }) => { const next = links.refs.find((link) => link.rel === 'next'); @@ -40,7 +38,7 @@ const Blocks: React.FC<{ multiColumn: boolean }> = ({ multiColumn }) => { .catch(() => { setLoading(false); }); - }, [setLoading, setDomains, setNext]); + }, []); const handleLoadMore = useCallback(() => { setLoading(true); diff --git a/app/javascript/flavours/glitch/features/emoji/constants.ts b/app/javascript/flavours/glitch/features/emoji/constants.ts index 09022371b2248c..e02663c9d84082 100644 --- a/app/javascript/flavours/glitch/features/emoji/constants.ts +++ b/app/javascript/flavours/glitch/features/emoji/constants.ts @@ -23,7 +23,9 @@ export const EMOJI_MODE_TWEMOJI = 'twemoji'; export const EMOJI_TYPE_UNICODE = 'unicode'; export const EMOJI_TYPE_CUSTOM = 'custom'; -export const EMOJI_STATE_MISSING = 'missing'; +export const EMOJI_DB_NAME_SHORTCODES = 'shortcodes'; + +export const EMOJI_DB_SHORTCODE_TEST = '2122'; // 2122 is the trademark sign, which we know has shortcodes in all datasets. export const EMOJIS_WITH_DARK_BORDER = [ '🎱', // 1F3B1 @@ -118,3 +120,29 @@ export const EMOJIS_WITH_LIGHT_BORDER = [ '🪽', // 1FAE8 '🪿', // 1FABF ]; + +export const EMOJIS_REQUIRING_INVERSION_IN_LIGHT_MODE = [ + '⛓️', // 26D3-FE0F +]; + +export const EMOJIS_REQUIRING_INVERSION_IN_DARK_MODE = [ + '🔜', // 1F51C + '🔙', // 1F519 + '🔛', // 1F51B + '🔝', // 1F51D + '🔚', // 1F51A + '©️', // 00A9 FE0F + '➰', // 27B0 + '💱', // 1F4B1 + '✔️', // 2714 FE0F + '➗', // 2797 + '💲', // 1F4B2 + '➖', // 2796 + '✖️', // 2716 FE0F + '➕', // 2795 + '®️', // 00AE FE0F + '🕷️', // 1F577 FE0F + '📞', // 1F4DE + '™️', // 2122 FE0F + '〰️', // 3030 FE0F +]; diff --git a/app/javascript/flavours/glitch/features/emoji/database.test.ts b/app/javascript/flavours/glitch/features/emoji/database.test.ts index 0689fd7c542d7f..6b6ea952b74cdc 100644 --- a/app/javascript/flavours/glitch/features/emoji/database.test.ts +++ b/app/javascript/flavours/glitch/features/emoji/database.test.ts @@ -1,7 +1,8 @@ import { IDBFactory } from 'fake-indexeddb'; -import { unicodeEmojiFactory } from '@/testing/factories'; +import { customEmojiFactory, unicodeEmojiFactory } from '@/testing/factories'; +import { EMOJI_DB_SHORTCODE_TEST } from './constants'; import { putEmojiData, loadEmojiByHexcode, @@ -9,6 +10,11 @@ import { searchEmojisByTag, testClear, testGet, + putCustomEmojiData, + putLegacyShortcodes, + loadLegacyShortcodesByShortcode, + loadLatestEtag, + putLatestEtag, } from './database'; describe('emoji database', () => { @@ -16,6 +22,7 @@ describe('emoji database', () => { testClear(); indexedDB = new IDBFactory(); }); + describe('putEmojiData', () => { test('adds to loaded locales', async () => { const { loadedLocales } = await testGet(); @@ -33,6 +40,44 @@ describe('emoji database', () => { }); }); + describe('putCustomEmojiData', () => { + test('loads custom emoji into indexedDB', async () => { + const { db } = await testGet(); + await putCustomEmojiData({ emojis: [customEmojiFactory()] }); + await expect(db.get('custom', 'custom')).resolves.toEqual( + customEmojiFactory(), + ); + }); + + test('clears existing custom emoji if specified', async () => { + const { db } = await testGet(); + await putCustomEmojiData({ + emojis: [customEmojiFactory({ shortcode: 'emoji1' })], + }); + await putCustomEmojiData({ + emojis: [customEmojiFactory({ shortcode: 'emoji2' })], + clear: true, + }); + await expect(db.get('custom', 'emoji1')).resolves.toBeUndefined(); + await expect(db.get('custom', 'emoji2')).resolves.toEqual( + customEmojiFactory({ shortcode: 'emoji2' }), + ); + }); + }); + + describe('putLegacyShortcodes', () => { + test('loads shortcodes into indexedDB', async () => { + const { db } = await testGet(); + await putLegacyShortcodes({ + test_hexcode: ['shortcode1', 'shortcode2'], + }); + await expect(db.get('shortcodes', 'test_hexcode')).resolves.toEqual({ + hexcode: 'test_hexcode', + shortcodes: ['shortcode1', 'shortcode2'], + }); + }); + }); + describe('loadEmojiByHexcode', () => { test('throws if the locale is not loaded', async () => { await expect(loadEmojiByHexcode('en', 'test')).rejects.toThrowError( @@ -136,4 +181,58 @@ describe('emoji database', () => { expect(actual).toHaveLength(0); }); }); + + describe('loadLegacyShortcodesByShortcode', () => { + const data = { + hexcode: 'test_hexcode', + shortcodes: ['shortcode1', 'shortcode2'], + }; + + beforeEach(async () => { + await putLegacyShortcodes({ + [data.hexcode]: data.shortcodes, + }); + }); + + test('retrieves the shortcodes', async () => { + await expect( + loadLegacyShortcodesByShortcode('shortcode1'), + ).resolves.toEqual(data); + await expect( + loadLegacyShortcodesByShortcode('shortcode2'), + ).resolves.toEqual(data); + }); + }); + + describe('loadLatestEtag', () => { + beforeEach(async () => { + await putLatestEtag('etag', 'en'); + await putEmojiData([unicodeEmojiFactory()], 'en'); + await putLatestEtag('fr-etag', 'fr'); + }); + + test('retrieves the etag for loaded locale', async () => { + await putEmojiData( + [unicodeEmojiFactory({ hexcode: EMOJI_DB_SHORTCODE_TEST })], + 'en', + ); + const etag = await loadLatestEtag('en'); + expect(etag).toBe('etag'); + }); + + test('returns null if locale has no shortcodes', async () => { + const etag = await loadLatestEtag('en'); + expect(etag).toBeNull(); + }); + + test('returns null if locale not loaded', async () => { + const etag = await loadLatestEtag('de'); + expect(etag).toBeNull(); + }); + + test('returns null if locale has no data', async () => { + const etag = await loadLatestEtag('fr'); + expect(etag).toBeNull(); + }); + }); }); diff --git a/app/javascript/flavours/glitch/features/emoji/database.ts b/app/javascript/flavours/glitch/features/emoji/database.ts index 0e8ada1d0e05a9..fe4010a861d994 100644 --- a/app/javascript/flavours/glitch/features/emoji/database.ts +++ b/app/javascript/flavours/glitch/features/emoji/database.ts @@ -1,14 +1,11 @@ import { SUPPORTED_LOCALES } from 'emojibase'; -import type { Locale } from 'emojibase'; +import type { Locale, ShortcodesDataset } from 'emojibase'; import type { DBSchema, IDBPDatabase } from 'idb'; import { openDB } from 'idb'; +import { EMOJI_DB_SHORTCODE_TEST } from './constants'; import { toSupportedLocale, toSupportedLocaleOrCustom } from './locale'; -import type { - CustomEmojiData, - UnicodeEmojiData, - LocaleOrCustom, -} from './types'; +import type { CustomEmojiData, UnicodeEmojiData, EtagTypes } from './types'; import { emojiLogger } from './utils'; interface EmojiDB extends LocaleTables, DBSchema { @@ -19,8 +16,19 @@ interface EmojiDB extends LocaleTables, DBSchema { category: string; }; }; + shortcodes: { + key: string; + value: { + hexcode: string; + shortcodes: string[]; + }; + indexes: { + hexcode: string; + shortcodes: string[]; + }; + }; etags: { - key: LocaleOrCustom; + key: EtagTypes; value: string; }; } @@ -33,13 +41,14 @@ interface LocaleTable { label: string; order: number; tags: string[]; + shortcodes: string[]; }; } type LocaleTables = Record; type Database = IDBPDatabase; -const SCHEMA_VERSION = 1; +const SCHEMA_VERSION = 2; const loadedLocales = new Set(); @@ -52,28 +61,76 @@ const loadDB = (() => { // Actually load the DB. async function initDB() { const db = await openDB('mastodon-emoji', SCHEMA_VERSION, { - upgrade(database) { - const customTable = database.createObjectStore('custom', { - keyPath: 'shortcode', - autoIncrement: false, - }); - customTable.createIndex('category', 'category'); + upgrade(database, oldVersion, newVersion, trx) { + if (!database.objectStoreNames.contains('custom')) { + const customTable = database.createObjectStore('custom', { + keyPath: 'shortcode', + autoIncrement: false, + }); + customTable.createIndex('category', 'category'); + } - database.createObjectStore('etags'); + if (!database.objectStoreNames.contains('etags')) { + database.createObjectStore('etags'); + } for (const locale of SUPPORTED_LOCALES) { - const localeTable = database.createObjectStore(locale, { + if (!database.objectStoreNames.contains(locale)) { + const localeTable = database.createObjectStore(locale, { + keyPath: 'hexcode', + autoIncrement: false, + }); + localeTable.createIndex('group', 'group'); + localeTable.createIndex('label', 'label'); + localeTable.createIndex('order', 'order'); + localeTable.createIndex('tags', 'tags', { multiEntry: true }); + localeTable.createIndex('shortcodes', 'shortcodes', { + multiEntry: true, + }); + } + // Added in version 2. + const localeTable = trx.objectStore(locale); + if (!localeTable.indexNames.contains('shortcodes')) { + localeTable.createIndex('shortcodes', 'shortcodes', { + multiEntry: true, + }); + } + } + + if (!database.objectStoreNames.contains('shortcodes')) { + const shortcodeTable = database.createObjectStore('shortcodes', { keyPath: 'hexcode', autoIncrement: false, }); - localeTable.createIndex('group', 'group'); - localeTable.createIndex('label', 'label'); - localeTable.createIndex('order', 'order'); - localeTable.createIndex('tags', 'tags', { multiEntry: true }); + shortcodeTable.createIndex('hexcode', 'hexcode'); + shortcodeTable.createIndex('shortcodes', 'shortcodes', { + multiEntry: true, + }); } + + log( + 'Upgraded emoji database from version %d to %d', + oldVersion, + newVersion, + ); + }, + blocked(currentVersion, blockedVersion) { + log( + 'Emoji database upgrade from version %d to %d is blocked', + currentVersion, + blockedVersion, + ); + }, + blocking(currentVersion, blockedVersion) { + log( + 'Emoji database upgrade from version %d is blocking upgrade to %d', + currentVersion, + blockedVersion, + ); }, }); await syncLocales(db); + log('Loaded database version %d', db.version); return db; } @@ -100,17 +157,52 @@ export async function putEmojiData(emojis: UnicodeEmojiData[], locale: Locale) { await trx.done; } -export async function putCustomEmojiData(emojis: CustomEmojiData[]) { +export async function putCustomEmojiData({ + emojis, + clear = false, +}: { + emojis: CustomEmojiData[]; + clear?: boolean; +}) { const db = await loadDB(); const trx = db.transaction('custom', 'readwrite'); + + // When importing from the API, clear everything first. + if (clear) { + await trx.store.clear(); + log('Cleared existing custom emojis in database'); + } + await Promise.all(emojis.map((emoji) => trx.store.put(emoji))); await trx.done; + + log('Imported %d custom emojis into database', emojis.length); } -export async function putLatestEtag(etag: string, localeString: string) { +export async function putLegacyShortcodes(shortcodes: ShortcodesDataset) { + const db = await loadDB(); + const trx = db.transaction('shortcodes', 'readwrite'); + await Promise.all( + Object.entries(shortcodes).map(([hexcode, codes]) => + trx.store.put({ + hexcode, + shortcodes: Array.isArray(codes) ? codes : [codes], + }), + ), + ); + await trx.done; +} + +export async function putLatestEtag(etag: string, name: EtagTypes) { + const db = await loadDB(); + await db.put('etags', etag, name); +} + +export async function clearEtag(localeString: string) { const locale = toSupportedLocaleOrCustom(localeString); const db = await loadDB(); - await db.put('etags', etag, locale); + await db.delete('etags', locale); + log('Cleared etag for %s', locale); } export async function loadEmojiByHexcode( @@ -161,6 +253,15 @@ export async function searchCustomEmojisByShortcodes(shortcodes: string[]) { return results.filter((emoji) => shortcodes.includes(emoji.shortcode)); } +export async function loadLegacyShortcodesByShortcode(shortcode: string) { + const db = await loadDB(); + return db.getFromIndex( + 'shortcodes', + 'shortcodes', + IDBKeyRange.only(shortcode), + ); +} + export async function loadLatestEtag(localeString: string) { const locale = toSupportedLocaleOrCustom(localeString); const db = await loadDB(); @@ -168,6 +269,15 @@ export async function loadLatestEtag(localeString: string) { if (!rowCount) { return null; // No data for this locale, return null even if there is an etag. } + + // Check if shortcodes exist for the given Unicode locale. + if (locale !== 'custom') { + const result = await db.get(locale, EMOJI_DB_SHORTCODE_TEST); + if (!result?.shortcodes) { + return null; + } + } + const etag = await db.get('etags', locale); return etag ?? null; } @@ -197,11 +307,18 @@ function toLoadedLocale(localeString: string) { log(`Locale ${locale} is different from provided ${localeString}`); } if (!loadedLocales.has(locale)) { - throw new Error(`Locale ${locale} is not loaded in emoji database`); + throw new LocaleNotLoadedError(locale); } return locale; } +export class LocaleNotLoadedError extends Error { + constructor(locale: Locale) { + super(`Locale ${locale} is not loaded in emoji database`); + this.name = 'LocaleNotLoadedError'; + } +} + async function hasLocale(locale: Locale, db: Database): Promise { if (loadedLocales.has(locale)) { return true; diff --git a/app/javascript/flavours/glitch/features/emoji/emoji.js b/app/javascript/flavours/glitch/features/emoji/emoji.js index 55fc382a5def90..b7ea36d3dfcc6c 100644 --- a/app/javascript/flavours/glitch/features/emoji/emoji.js +++ b/app/javascript/flavours/glitch/features/emoji/emoji.js @@ -1,8 +1,9 @@ import Trie from 'substring-trie'; +import { getUserTheme, isDarkMode } from '@/flavours/glitch/utils/theme'; import { assetHost } from 'flavours/glitch/utils/config'; -import { autoPlayGif, useSystemEmojiFont } from '../../initial_state'; +import { autoPlayGif } from '../../initial_state'; import { unicodeMapping } from './emoji_unicode_mapping_light'; @@ -39,13 +40,13 @@ const emojifyTextNode = (node, customEmojis) => { for (;;) { let unicode_emoji; - // Skip to the next potential emoji to replace (either custom emoji or custom emoji :shortcode:) + // Skip to the next potential emoji to replace (either custom emoji or custom emoji :shortcode: if (customEmojis === null) { - while (i < str.length && (useSystemEmojiFont || !(unicode_emoji = trie.search(str.slice(i))))) { + while (i < str.length && !(unicode_emoji = trie.search(str.slice(i)))) { i += str.codePointAt(i) < 65536 ? 1 : 2; } } else { - while (i < str.length && str[i] !== ':' && (useSystemEmojiFont || !(unicode_emoji = trie.search(str.slice(i))))) { + while (i < str.length && str[i] !== ':' && !(unicode_emoji = trie.search(str.slice(i)))) { i += str.codePointAt(i) < 65536 ? 1 : 2; } } @@ -97,9 +98,9 @@ const emojifyTextNode = (node, customEmojis) => { const { filename, shortCode } = unicodeMapping[unicode_emoji]; const title = shortCode ? `:${shortCode}:` : ''; - const isSystemTheme = !!document.body?.classList.contains('theme-system'); + const isSystemTheme = getUserTheme() === 'system'; - const theme = (isSystemTheme || document.body?.classList.contains('theme-mastodon-light')) ? 'light' : 'dark'; + const theme = (isSystemTheme || !isDarkMode()) ? 'light' : 'dark'; const imageFilename = emojiFilename(filename, theme); @@ -148,6 +149,12 @@ const emojifyNode = (node, customEmojis) => { } }; +/** + * Legacy emoji processing function. + * @param {string} str + * @param {object} customEmojis + * @returns {string} + */ const emojify = (str, customEmojis = {}) => { const wrapper = document.createElement('div'); wrapper.innerHTML = str; diff --git a/app/javascript/flavours/glitch/features/emoji/emoji_compressed.mjs b/app/javascript/flavours/glitch/features/emoji/emoji_compressed.mjs index bab2f6bfca2c18..b9526446e15b46 100644 --- a/app/javascript/flavours/glitch/features/emoji/emoji_compressed.mjs +++ b/app/javascript/flavours/glitch/features/emoji/emoji_compressed.mjs @@ -14,8 +14,7 @@ import { uncompress as emojiMartUncompress } from 'emoji-mart/dist/utils/data'; import data from './emoji_data.json'; import emojiMap from './emoji_map.json'; -import { unicodeToFilename } from './unicode_to_filename'; -import { unicodeToUnifiedName } from './unicode_to_unified_name'; +import { unicodeToFilename, unicodeToUnifiedName } from './unicode_utils'; emojiMartUncompress(data); diff --git a/app/javascript/flavours/glitch/features/emoji/emoji_data.json b/app/javascript/flavours/glitch/features/emoji/emoji_data.json index 4d7a48692bec81..7ef1c9838b2849 100644 --- a/app/javascript/flavours/glitch/features/emoji/emoji_data.json +++ b/app/javascript/flavours/glitch/features/emoji/emoji_data.json @@ -1 +1 @@ -{"compressed":true,"categories":[{"id":"people","name":"Smileys & People","emojis":["grinning","smiley","smile","grin","laughing","sweat_smile","rolling_on_the_floor_laughing","joy","slightly_smiling_face","upside_down_face","melting_face","wink","blush","innocent","smiling_face_with_3_hearts","heart_eyes","star-struck","kissing_heart","kissing","relaxed","kissing_closed_eyes","kissing_smiling_eyes","smiling_face_with_tear","yum","stuck_out_tongue","stuck_out_tongue_winking_eye","zany_face","stuck_out_tongue_closed_eyes","money_mouth_face","hugging_face","face_with_hand_over_mouth","face_with_open_eyes_and_hand_over_mouth","face_with_peeking_eye","shushing_face","thinking_face","saluting_face","zipper_mouth_face","face_with_raised_eyebrow","neutral_face","expressionless","no_mouth","dotted_line_face","face_in_clouds","smirk","unamused","face_with_rolling_eyes","grimacing","face_exhaling","lying_face","shaking_face","head_shaking_horizontally","head_shaking_vertically","relieved","pensive","sleepy","drooling_face","sleeping","mask","face_with_thermometer","face_with_head_bandage","nauseated_face","face_vomiting","sneezing_face","hot_face","cold_face","woozy_face","dizzy_face","face_with_spiral_eyes","exploding_head","face_with_cowboy_hat","partying_face","disguised_face","sunglasses","nerd_face","face_with_monocle","confused","face_with_diagonal_mouth","worried","slightly_frowning_face","white_frowning_face","open_mouth","hushed","astonished","flushed","pleading_face","face_holding_back_tears","frowning","anguished","fearful","cold_sweat","disappointed_relieved","cry","sob","scream","confounded","persevere","disappointed","sweat","weary","tired_face","yawning_face","triumph","rage","angry","face_with_symbols_on_mouth","smiling_imp","imp","skull","skull_and_crossbones","hankey","clown_face","japanese_ogre","japanese_goblin","ghost","alien","space_invader","robot_face","smiley_cat","smile_cat","joy_cat","heart_eyes_cat","smirk_cat","kissing_cat","scream_cat","crying_cat_face","pouting_cat","see_no_evil","hear_no_evil","speak_no_evil","wave","raised_back_of_hand","raised_hand_with_fingers_splayed","hand","spock-hand","rightwards_hand","leftwards_hand","palm_down_hand","palm_up_hand","leftwards_pushing_hand","rightwards_pushing_hand","ok_hand","pinched_fingers","pinching_hand","v","crossed_fingers","hand_with_index_finger_and_thumb_crossed","i_love_you_hand_sign","the_horns","call_me_hand","point_left","point_right","point_up_2","middle_finger","point_down","point_up","index_pointing_at_the_viewer","+1","-1","fist","facepunch","left-facing_fist","right-facing_fist","clap","raised_hands","heart_hands","open_hands","palms_up_together","handshake","pray","writing_hand","nail_care","selfie","muscle","mechanical_arm","mechanical_leg","leg","foot","ear","ear_with_hearing_aid","nose","brain","anatomical_heart","lungs","tooth","bone","eyes","eye","tongue","lips","biting_lip","baby","child","boy","girl","adult","person_with_blond_hair","man","bearded_person","man_with_beard","woman_with_beard","red_haired_man","curly_haired_man","white_haired_man","bald_man","woman","red_haired_woman","red_haired_person","curly_haired_woman","curly_haired_person","white_haired_woman","white_haired_person","bald_woman","bald_person","blond-haired-woman","blond-haired-man","older_adult","older_man","older_woman","person_frowning","man-frowning","woman-frowning","person_with_pouting_face","man-pouting","woman-pouting","no_good","man-gesturing-no","woman-gesturing-no","ok_woman","man-gesturing-ok","woman-gesturing-ok","information_desk_person","man-tipping-hand","woman-tipping-hand","raising_hand","man-raising-hand","woman-raising-hand","deaf_person","deaf_man","deaf_woman","bow","man-bowing","woman-bowing","face_palm","man-facepalming","woman-facepalming","shrug","man-shrugging","woman-shrugging","health_worker","male-doctor","female-doctor","student","male-student","female-student","teacher","male-teacher","female-teacher","judge","male-judge","female-judge","farmer","male-farmer","female-farmer","cook","male-cook","female-cook","mechanic","male-mechanic","female-mechanic","factory_worker","male-factory-worker","female-factory-worker","office_worker","male-office-worker","female-office-worker","scientist","male-scientist","female-scientist","technologist","male-technologist","female-technologist","singer","male-singer","female-singer","artist","male-artist","female-artist","pilot","male-pilot","female-pilot","astronaut","male-astronaut","female-astronaut","firefighter","male-firefighter","female-firefighter","cop","male-police-officer","female-police-officer","sleuth_or_spy","male-detective","female-detective","guardsman","male-guard","female-guard","ninja","construction_worker","male-construction-worker","female-construction-worker","person_with_crown","prince","princess","man_with_turban","man-wearing-turban","woman-wearing-turban","man_with_gua_pi_mao","person_with_headscarf","person_in_tuxedo","man_in_tuxedo","woman_in_tuxedo","bride_with_veil","man_with_veil","woman_with_veil","pregnant_woman","pregnant_man","pregnant_person","breast-feeding","woman_feeding_baby","man_feeding_baby","person_feeding_baby","angel","santa","mrs_claus","mx_claus","superhero","male_superhero","female_superhero","supervillain","male_supervillain","female_supervillain","mage","male_mage","female_mage","fairy","male_fairy","female_fairy","vampire","male_vampire","female_vampire","merperson","merman","mermaid","elf","male_elf","female_elf","genie","male_genie","female_genie","zombie","male_zombie","female_zombie","troll","massage","man-getting-massage","woman-getting-massage","haircut","man-getting-haircut","woman-getting-haircut","walking","man-walking","woman-walking","person_walking_facing_right","woman_walking_facing_right","man_walking_facing_right","standing_person","man_standing","woman_standing","kneeling_person","man_kneeling","woman_kneeling","person_kneeling_facing_right","woman_kneeling_facing_right","man_kneeling_facing_right","person_with_probing_cane","person_with_white_cane_facing_right","man_with_probing_cane","man_with_white_cane_facing_right","woman_with_probing_cane","woman_with_white_cane_facing_right","person_in_motorized_wheelchair","person_in_motorized_wheelchair_facing_right","man_in_motorized_wheelchair","man_in_motorized_wheelchair_facing_right","woman_in_motorized_wheelchair","woman_in_motorized_wheelchair_facing_right","person_in_manual_wheelchair","person_in_manual_wheelchair_facing_right","man_in_manual_wheelchair","man_in_manual_wheelchair_facing_right","woman_in_manual_wheelchair","woman_in_manual_wheelchair_facing_right","runner","man-running","woman-running","person_running_facing_right","woman_running_facing_right","man_running_facing_right","dancer","man_dancing","man_in_business_suit_levitating","dancers","men-with-bunny-ears-partying","women-with-bunny-ears-partying","person_in_steamy_room","man_in_steamy_room","woman_in_steamy_room","person_climbing","man_climbing","woman_climbing","fencer","horse_racing","skier","snowboarder","golfer","man-golfing","woman-golfing","surfer","man-surfing","woman-surfing","rowboat","man-rowing-boat","woman-rowing-boat","swimmer","man-swimming","woman-swimming","person_with_ball","man-bouncing-ball","woman-bouncing-ball","weight_lifter","man-lifting-weights","woman-lifting-weights","bicyclist","man-biking","woman-biking","mountain_bicyclist","man-mountain-biking","woman-mountain-biking","person_doing_cartwheel","man-cartwheeling","woman-cartwheeling","wrestlers","man-wrestling","woman-wrestling","water_polo","man-playing-water-polo","woman-playing-water-polo","handball","man-playing-handball","woman-playing-handball","juggling","man-juggling","woman-juggling","person_in_lotus_position","man_in_lotus_position","woman_in_lotus_position","bath","sleeping_accommodation","people_holding_hands","two_women_holding_hands","man_and_woman_holding_hands","two_men_holding_hands","couplekiss","woman-kiss-man","man-kiss-man","woman-kiss-woman","couple_with_heart","woman-heart-man","man-heart-man","woman-heart-woman","man-woman-boy","man-woman-girl","man-woman-girl-boy","man-woman-boy-boy","man-woman-girl-girl","man-man-boy","man-man-girl","man-man-girl-boy","man-man-boy-boy","man-man-girl-girl","woman-woman-boy","woman-woman-girl","woman-woman-girl-boy","woman-woman-boy-boy","woman-woman-girl-girl","man-boy","man-boy-boy","man-girl","man-girl-boy","man-girl-girl","woman-boy","woman-boy-boy","woman-girl","woman-girl-boy","woman-girl-girl","speaking_head_in_silhouette","bust_in_silhouette","busts_in_silhouette","people_hugging","family","family_adult_adult_child","family_adult_adult_child_child","family_adult_child","family_adult_child_child","footprints","love_letter","cupid","gift_heart","sparkling_heart","heartpulse","heartbeat","revolving_hearts","two_hearts","heart_decoration","heavy_heart_exclamation_mark_ornament","broken_heart","heart_on_fire","mending_heart","heart","pink_heart","orange_heart","yellow_heart","green_heart","blue_heart","light_blue_heart","purple_heart","brown_heart","black_heart","grey_heart","white_heart","kiss","100","anger","boom","dizzy","sweat_drops","dash","hole","speech_balloon","eye-in-speech-bubble","left_speech_bubble","right_anger_bubble","thought_balloon","zzz"]},{"id":"nature","name":"Animals & Nature","emojis":["monkey_face","monkey","gorilla","orangutan","dog","dog2","guide_dog","service_dog","poodle","wolf","fox_face","raccoon","cat","cat2","black_cat","lion_face","tiger","tiger2","leopard","horse","moose","donkey","racehorse","unicorn_face","zebra_face","deer","bison","cow","ox","water_buffalo","cow2","pig","pig2","boar","pig_nose","ram","sheep","goat","dromedary_camel","camel","llama","giraffe_face","elephant","mammoth","rhinoceros","hippopotamus","mouse","mouse2","rat","hamster","rabbit","rabbit2","chipmunk","beaver","hedgehog","bat","bear","polar_bear","koala","panda_face","sloth","otter","skunk","kangaroo","badger","feet","turkey","chicken","rooster","hatching_chick","baby_chick","hatched_chick","bird","penguin","dove_of_peace","eagle","duck","swan","owl","dodo","feather","flamingo","peacock","parrot","wing","black_bird","goose","phoenix","frog","crocodile","turtle","lizard","snake","dragon_face","dragon","sauropod","t-rex","whale","whale2","dolphin","seal","fish","tropical_fish","blowfish","shark","octopus","shell","coral","jellyfish","snail","butterfly","bug","ant","bee","beetle","ladybug","cricket","cockroach","spider","spider_web","scorpion","mosquito","fly","worm","microbe","bouquet","cherry_blossom","white_flower","lotus","rosette","rose","wilted_flower","hibiscus","sunflower","blossom","tulip","hyacinth","seedling","potted_plant","evergreen_tree","deciduous_tree","palm_tree","cactus","ear_of_rice","herb","shamrock","four_leaf_clover","maple_leaf","fallen_leaf","leaves","empty_nest","nest_with_eggs","mushroom"]},{"id":"foods","name":"Food & Drink","emojis":["grapes","melon","watermelon","tangerine","lemon","lime","banana","pineapple","mango","apple","green_apple","pear","peach","cherries","strawberry","blueberries","kiwifruit","tomato","olive","coconut","avocado","eggplant","potato","carrot","corn","hot_pepper","bell_pepper","cucumber","leafy_green","broccoli","garlic","onion","peanuts","beans","chestnut","ginger_root","pea_pod","brown_mushroom","bread","croissant","baguette_bread","flatbread","pretzel","bagel","pancakes","waffle","cheese_wedge","meat_on_bone","poultry_leg","cut_of_meat","bacon","hamburger","fries","pizza","hotdog","sandwich","taco","burrito","tamale","stuffed_flatbread","falafel","egg","fried_egg","shallow_pan_of_food","stew","fondue","bowl_with_spoon","green_salad","popcorn","butter","salt","canned_food","bento","rice_cracker","rice_ball","rice","curry","ramen","spaghetti","sweet_potato","oden","sushi","fried_shrimp","fish_cake","moon_cake","dango","dumpling","fortune_cookie","takeout_box","crab","lobster","shrimp","squid","oyster","icecream","shaved_ice","ice_cream","doughnut","cookie","birthday","cake","cupcake","pie","chocolate_bar","candy","lollipop","custard","honey_pot","baby_bottle","glass_of_milk","coffee","teapot","tea","sake","champagne","wine_glass","cocktail","tropical_drink","beer","beers","clinking_glasses","tumbler_glass","pouring_liquid","cup_with_straw","bubble_tea","beverage_box","mate_drink","ice_cube","chopsticks","knife_fork_plate","fork_and_knife","spoon","hocho","jar","amphora"]},{"id":"activity","name":"Activities","emojis":["jack_o_lantern","christmas_tree","fireworks","sparkler","firecracker","sparkles","balloon","tada","confetti_ball","tanabata_tree","bamboo","dolls","flags","wind_chime","rice_scene","red_envelope","ribbon","gift","reminder_ribbon","admission_tickets","ticket","medal","trophy","sports_medal","first_place_medal","second_place_medal","third_place_medal","soccer","baseball","softball","basketball","volleyball","football","rugby_football","tennis","flying_disc","bowling","cricket_bat_and_ball","field_hockey_stick_and_ball","ice_hockey_stick_and_puck","lacrosse","table_tennis_paddle_and_ball","badminton_racquet_and_shuttlecock","boxing_glove","martial_arts_uniform","goal_net","golf","ice_skate","fishing_pole_and_fish","diving_mask","running_shirt_with_sash","ski","sled","curling_stone","dart","yo-yo","kite","gun","8ball","crystal_ball","magic_wand","video_game","joystick","slot_machine","game_die","jigsaw","teddy_bear","pinata","mirror_ball","nesting_dolls","spades","hearts","diamonds","clubs","chess_pawn","black_joker","mahjong","flower_playing_cards","performing_arts","frame_with_picture","art","thread","sewing_needle","yarn","knot"]},{"id":"places","name":"Travel & Places","emojis":["earth_africa","earth_americas","earth_asia","globe_with_meridians","world_map","japan","compass","snow_capped_mountain","mountain","volcano","mount_fuji","camping","beach_with_umbrella","desert","desert_island","national_park","stadium","classical_building","building_construction","bricks","rock","wood","hut","house_buildings","derelict_house_building","house","house_with_garden","office","post_office","european_post_office","hospital","bank","hotel","love_hotel","convenience_store","school","department_store","factory","japanese_castle","european_castle","wedding","tokyo_tower","statue_of_liberty","church","mosque","hindu_temple","synagogue","shinto_shrine","kaaba","fountain","tent","foggy","night_with_stars","cityscape","sunrise_over_mountains","sunrise","city_sunset","city_sunrise","bridge_at_night","hotsprings","carousel_horse","playground_slide","ferris_wheel","roller_coaster","barber","circus_tent","steam_locomotive","railway_car","bullettrain_side","bullettrain_front","train2","metro","light_rail","station","tram","monorail","mountain_railway","train","bus","oncoming_bus","trolleybus","minibus","ambulance","fire_engine","police_car","oncoming_police_car","taxi","oncoming_taxi","car","oncoming_automobile","blue_car","pickup_truck","truck","articulated_lorry","tractor","racing_car","racing_motorcycle","motor_scooter","manual_wheelchair","motorized_wheelchair","auto_rickshaw","bike","scooter","skateboard","roller_skate","busstop","motorway","railway_track","oil_drum","fuelpump","wheel","rotating_light","traffic_light","vertical_traffic_light","octagonal_sign","construction","anchor","ring_buoy","boat","canoe","speedboat","passenger_ship","ferry","motor_boat","ship","airplane","small_airplane","airplane_departure","airplane_arriving","parachute","seat","helicopter","suspension_railway","mountain_cableway","aerial_tramway","satellite","rocket","flying_saucer","bellhop_bell","luggage","hourglass","hourglass_flowing_sand","watch","alarm_clock","stopwatch","timer_clock","mantelpiece_clock","clock12","clock1230","clock1","clock130","clock2","clock230","clock3","clock330","clock4","clock430","clock5","clock530","clock6","clock630","clock7","clock730","clock8","clock830","clock9","clock930","clock10","clock1030","clock11","clock1130","new_moon","waxing_crescent_moon","first_quarter_moon","moon","full_moon","waning_gibbous_moon","last_quarter_moon","waning_crescent_moon","crescent_moon","new_moon_with_face","first_quarter_moon_with_face","last_quarter_moon_with_face","thermometer","sunny","full_moon_with_face","sun_with_face","ringed_planet","star","star2","stars","milky_way","cloud","partly_sunny","thunder_cloud_and_rain","mostly_sunny","barely_sunny","partly_sunny_rain","rain_cloud","snow_cloud","lightning","tornado","fog","wind_blowing_face","cyclone","rainbow","closed_umbrella","umbrella","umbrella_with_rain_drops","umbrella_on_ground","zap","snowflake","snowman","snowman_without_snow","comet","fire","droplet","ocean"]},{"id":"objects","name":"Objects","emojis":["eyeglasses","dark_sunglasses","goggles","lab_coat","safety_vest","necktie","shirt","jeans","scarf","gloves","coat","socks","dress","kimono","sari","one-piece_swimsuit","briefs","shorts","bikini","womans_clothes","folding_hand_fan","purse","handbag","pouch","shopping_bags","school_satchel","thong_sandal","mans_shoe","athletic_shoe","hiking_boot","womans_flat_shoe","high_heel","sandal","ballet_shoes","boot","hair_pick","crown","womans_hat","tophat","mortar_board","billed_cap","military_helmet","helmet_with_white_cross","prayer_beads","lipstick","ring","gem","mute","speaker","sound","loud_sound","loudspeaker","mega","postal_horn","bell","no_bell","musical_score","musical_note","notes","studio_microphone","level_slider","control_knobs","microphone","headphones","radio","saxophone","accordion","guitar","musical_keyboard","trumpet","violin","banjo","drum_with_drumsticks","long_drum","maracas","flute","iphone","calling","phone","telephone_receiver","pager","fax","battery","low_battery","electric_plug","computer","desktop_computer","printer","keyboard","three_button_mouse","trackball","minidisc","floppy_disk","cd","dvd","abacus","movie_camera","film_frames","film_projector","clapper","tv","camera","camera_with_flash","video_camera","vhs","mag","mag_right","candle","bulb","flashlight","izakaya_lantern","diya_lamp","notebook_with_decorative_cover","closed_book","book","green_book","blue_book","orange_book","books","notebook","ledger","page_with_curl","scroll","page_facing_up","newspaper","rolled_up_newspaper","bookmark_tabs","bookmark","label","moneybag","coin","yen","dollar","euro","pound","money_with_wings","credit_card","receipt","chart","email","e-mail","incoming_envelope","envelope_with_arrow","outbox_tray","inbox_tray","package","mailbox","mailbox_closed","mailbox_with_mail","mailbox_with_no_mail","postbox","ballot_box_with_ballot","pencil2","black_nib","lower_left_fountain_pen","lower_left_ballpoint_pen","lower_left_paintbrush","lower_left_crayon","memo","briefcase","file_folder","open_file_folder","card_index_dividers","date","calendar","spiral_note_pad","spiral_calendar_pad","card_index","chart_with_upwards_trend","chart_with_downwards_trend","bar_chart","clipboard","pushpin","round_pushpin","paperclip","linked_paperclips","straight_ruler","triangular_ruler","scissors","card_file_box","file_cabinet","wastebasket","lock","unlock","lock_with_ink_pen","closed_lock_with_key","key","old_key","hammer","axe","pick","hammer_and_pick","hammer_and_wrench","dagger_knife","crossed_swords","bomb","boomerang","bow_and_arrow","shield","carpentry_saw","wrench","screwdriver","nut_and_bolt","gear","compression","scales","probing_cane","link","broken_chain","chains","hook","toolbox","magnet","ladder","alembic","test_tube","petri_dish","dna","microscope","telescope","satellite_antenna","syringe","drop_of_blood","pill","adhesive_bandage","crutch","stethoscope","x-ray","door","elevator","mirror","window","bed","couch_and_lamp","chair","toilet","plunger","shower","bathtub","mouse_trap","razor","lotion_bottle","safety_pin","broom","basket","roll_of_paper","bucket","soap","bubbles","toothbrush","sponge","fire_extinguisher","shopping_trolley","smoking","coffin","headstone","funeral_urn","nazar_amulet","hamsa","moyai","placard","identification_card"]},{"id":"symbols","name":"Symbols","emojis":["atm","put_litter_in_its_place","potable_water","wheelchair","mens","womens","restroom","baby_symbol","wc","passport_control","customs","baggage_claim","left_luggage","warning","children_crossing","no_entry","no_entry_sign","no_bicycles","no_smoking","do_not_litter","non-potable_water","no_pedestrians","no_mobile_phones","underage","radioactive_sign","biohazard_sign","arrow_up","arrow_upper_right","arrow_right","arrow_lower_right","arrow_down","arrow_lower_left","arrow_left","arrow_upper_left","arrow_up_down","left_right_arrow","leftwards_arrow_with_hook","arrow_right_hook","arrow_heading_up","arrow_heading_down","arrows_clockwise","arrows_counterclockwise","back","end","on","soon","top","place_of_worship","atom_symbol","om_symbol","star_of_david","wheel_of_dharma","yin_yang","latin_cross","orthodox_cross","star_and_crescent","peace_symbol","menorah_with_nine_branches","six_pointed_star","khanda","aries","taurus","gemini","cancer","leo","virgo","libra","scorpius","sagittarius","capricorn","aquarius","pisces","ophiuchus","twisted_rightwards_arrows","repeat","repeat_one","arrow_forward","fast_forward","black_right_pointing_double_triangle_with_vertical_bar","black_right_pointing_triangle_with_double_vertical_bar","arrow_backward","rewind","black_left_pointing_double_triangle_with_vertical_bar","arrow_up_small","arrow_double_up","arrow_down_small","arrow_double_down","double_vertical_bar","black_square_for_stop","black_circle_for_record","eject","cinema","low_brightness","high_brightness","signal_strength","wireless","vibration_mode","mobile_phone_off","female_sign","male_sign","transgender_symbol","heavy_multiplication_x","heavy_plus_sign","heavy_minus_sign","heavy_division_sign","heavy_equals_sign","infinity","bangbang","interrobang","question","grey_question","grey_exclamation","exclamation","wavy_dash","currency_exchange","heavy_dollar_sign","medical_symbol","recycle","fleur_de_lis","trident","name_badge","beginner","o","white_check_mark","ballot_box_with_check","heavy_check_mark","x","negative_squared_cross_mark","curly_loop","loop","part_alternation_mark","eight_spoked_asterisk","eight_pointed_black_star","sparkle","copyright","registered","tm","hash","keycap_star","zero","one","two","three","four","five","six","seven","eight","nine","keycap_ten","capital_abcd","abcd","1234","symbols","abc","a","ab","b","cl","cool","free","information_source","id","m","new","ng","o2","ok","parking","sos","up","vs","koko","sa","u6708","u6709","u6307","ideograph_advantage","u5272","u7121","u7981","accept","u7533","u5408","u7a7a","congratulations","secret","u55b6","u6e80","red_circle","large_orange_circle","large_yellow_circle","large_green_circle","large_blue_circle","large_purple_circle","large_brown_circle","black_circle","white_circle","large_red_square","large_orange_square","large_yellow_square","large_green_square","large_blue_square","large_purple_square","large_brown_square","black_large_square","white_large_square","black_medium_square","white_medium_square","black_medium_small_square","white_medium_small_square","black_small_square","white_small_square","large_orange_diamond","large_blue_diamond","small_orange_diamond","small_blue_diamond","small_red_triangle","small_red_triangle_down","diamond_shape_with_a_dot_inside","radio_button","white_square_button","black_square_button"]},{"id":"flags","name":"Flags","emojis":["checkered_flag","triangular_flag_on_post","crossed_flags","waving_black_flag","waving_white_flag","rainbow-flag","transgender_flag","pirate_flag","flag-ac","flag-ad","flag-ae","flag-af","flag-ag","flag-ai","flag-al","flag-am","flag-ao","flag-aq","flag-ar","flag-as","flag-at","flag-au","flag-aw","flag-ax","flag-az","flag-ba","flag-bb","flag-bd","flag-be","flag-bf","flag-bg","flag-bh","flag-bi","flag-bj","flag-bl","flag-bm","flag-bn","flag-bo","flag-bq","flag-br","flag-bs","flag-bt","flag-bv","flag-bw","flag-by","flag-bz","flag-ca","flag-cc","flag-cd","flag-cf","flag-cg","flag-ch","flag-ci","flag-ck","flag-cl","flag-cm","cn","flag-co","flag-cp","flag-cr","flag-cu","flag-cv","flag-cw","flag-cx","flag-cy","flag-cz","de","flag-dg","flag-dj","flag-dk","flag-dm","flag-do","flag-dz","flag-ea","flag-ec","flag-ee","flag-eg","flag-eh","flag-er","es","flag-et","flag-eu","flag-fi","flag-fj","flag-fk","flag-fm","flag-fo","fr","flag-ga","gb","flag-gd","flag-ge","flag-gf","flag-gg","flag-gh","flag-gi","flag-gl","flag-gm","flag-gn","flag-gp","flag-gq","flag-gr","flag-gs","flag-gt","flag-gu","flag-gw","flag-gy","flag-hk","flag-hm","flag-hn","flag-hr","flag-ht","flag-hu","flag-ic","flag-id","flag-ie","flag-il","flag-im","flag-in","flag-io","flag-iq","flag-ir","flag-is","it","flag-je","flag-jm","flag-jo","jp","flag-ke","flag-kg","flag-kh","flag-ki","flag-km","flag-kn","flag-kp","kr","flag-kw","flag-ky","flag-kz","flag-la","flag-lb","flag-lc","flag-li","flag-lk","flag-lr","flag-ls","flag-lt","flag-lu","flag-lv","flag-ly","flag-ma","flag-mc","flag-md","flag-me","flag-mf","flag-mg","flag-mh","flag-mk","flag-ml","flag-mm","flag-mn","flag-mo","flag-mp","flag-mq","flag-mr","flag-ms","flag-mt","flag-mu","flag-mv","flag-mw","flag-mx","flag-my","flag-mz","flag-na","flag-nc","flag-ne","flag-nf","flag-ng","flag-ni","flag-nl","flag-no","flag-np","flag-nr","flag-nu","flag-nz","flag-om","flag-pa","flag-pe","flag-pf","flag-pg","flag-ph","flag-pk","flag-pl","flag-pm","flag-pn","flag-pr","flag-ps","flag-pt","flag-pw","flag-py","flag-qa","flag-re","flag-ro","flag-rs","ru","flag-rw","flag-sa","flag-sb","flag-sc","flag-sd","flag-se","flag-sg","flag-sh","flag-si","flag-sj","flag-sk","flag-sl","flag-sm","flag-sn","flag-so","flag-sr","flag-ss","flag-st","flag-sv","flag-sx","flag-sy","flag-sz","flag-ta","flag-tc","flag-td","flag-tf","flag-tg","flag-th","flag-tj","flag-tk","flag-tl","flag-tm","flag-tn","flag-to","flag-tr","flag-tt","flag-tv","flag-tw","flag-tz","flag-ua","flag-ug","flag-um","flag-un","us","flag-uy","flag-uz","flag-va","flag-vc","flag-ve","flag-vg","flag-vi","flag-vn","flag-vu","flag-wf","flag-ws","flag-xk","flag-ye","flag-yt","flag-za","flag-zm","flag-zw","flag-england","flag-scotland","flag-wales"]}],"emojis":{"grinning":{"a":"Grinning Face","b":"1F600","f":true,"k":[32,46],"j":["grinning_face","face","smile","happy","joy",":D","grin"],"m":":D"},"smiley":{"a":"Smiling Face with Open Mouth","b":"1F603","f":true,"k":[32,49],"j":["grinning_face_with_big_eyes","face","happy","joy","haha",":D",":)","smile","funny"],"l":["=)","=-)"],"m":":)"},"smile":{"a":"Smiling Face with Open Mouth and Smiling Eyes","b":"1F604","f":true,"k":[32,50],"j":["grinning_face_with_smiling_eyes","face","happy","joy","funny","haha","laugh","like",":D",":)"],"l":["C:","c:",":D",":-D"],"m":":)"},"grin":{"a":"Grinning Face with Smiling Eyes","b":"1F601","f":true,"k":[32,47],"j":["beaming_face_with_smiling_eyes","face","happy","smile","joy","kawaii"]},"laughing":{"a":"Smiling Face with Open Mouth and Tightly-Closed Eyes","b":"1F606","f":true,"k":[32,52],"j":["grinning_squinting_face","happy","joy","lol","satisfied","haha","face","glad","XD","laugh"],"l":[":>",":->"]},"sweat_smile":{"a":"Smiling Face with Open Mouth and Cold Sweat","b":"1F605","f":true,"k":[32,51],"j":["grinning_face_with_sweat","face","hot","happy","laugh","sweat","smile","relief"]},"rolling_on_the_floor_laughing":{"a":"Rolling On the Floor Laughing","b":"1F923","f":true,"k":[40,54],"j":["face","rolling","floor","laughing","lol","haha","rofl"]},"joy":{"a":"Face with Tears Of Joy","b":"1F602","f":true,"k":[32,48],"j":["face_with_tears_of_joy","face","cry","tears","weep","happy","happytears","haha"]},"slightly_smiling_face":{"a":"Slightly Smiling Face","b":"1F642","f":true,"k":[33,55],"j":["face","smile"],"l":[":)","(:",":-)"]},"upside_down_face":{"a":"Upside-Down Face","b":"1F643","f":true,"k":[33,56],"j":["face","flipped","silly","smile"]},"melting_face":{"a":"Melting Face","b":"1FAE0","f":true,"k":[56,30],"j":["melting face","hot","heat"]},"wink":{"a":"Winking Face","b":"1F609","f":true,"k":[32,55],"j":["winking_face","face","happy","mischievous","secret",";)","smile","eye"],"l":[";)",";-)"],"m":";)"},"blush":{"a":"Smiling Face with Smiling Eyes","b":"1F60A","f":true,"k":[32,56],"j":["smiling_face_with_smiling_eyes","face","smile","happy","flushed","crush","embarrassed","shy","joy"],"m":":)"},"innocent":{"a":"Smiling Face with Halo","b":"1F607","f":true,"k":[32,53],"j":["smiling_face_with_halo","face","angel","heaven","halo"]},"smiling_face_with_3_hearts":{"a":"Smiling Face with Smiling Eyes and Three Hearts","b":"1F970","f":true,"k":[44,32],"j":["smiling_face_with_hearts","face","love","like","affection","valentines","infatuation","crush","hearts","adore"]},"heart_eyes":{"a":"Smiling Face with Heart-Shaped Eyes","b":"1F60D","f":true,"k":[32,59],"j":["smiling_face_with_heart_eyes","face","love","like","affection","valentines","infatuation","crush","heart"]},"star-struck":{"a":"Grinning Face with Star Eyes","b":"1F929","f":true,"k":[41,15],"j":["star_struck","face","smile","starry","eyes","grinning"]},"kissing_heart":{"a":"Face Throwing a Kiss","b":"1F618","f":true,"k":[33,8],"j":["face_blowing_a_kiss","face","love","like","affection","valentines","infatuation","kiss"],"l":[":*",":-*"]},"kissing":{"a":"Kissing Face","b":"1F617","f":true,"k":[33,7],"j":["kissing_face","love","like","face","3","valentines","infatuation","kiss"]},"relaxed":{"a":"White Smiling Face","b":"263A-FE0F","f":true,"k":[58,33],"c":"263A"},"kissing_closed_eyes":{"a":"Kissing Face with Closed Eyes","b":"1F61A","f":true,"k":[33,10],"j":["kissing_face_with_closed_eyes","face","love","like","affection","valentines","infatuation","kiss"]},"kissing_smiling_eyes":{"a":"Kissing Face with Smiling Eyes","b":"1F619","f":true,"k":[33,9],"j":["kissing_face_with_smiling_eyes","face","affection","valentines","infatuation","kiss"]},"smiling_face_with_tear":{"a":"Smiling Face with Tear","b":"1F972","f":true,"k":[44,34],"j":["smiling face with tear","sad","cry","pretend"]},"yum":{"a":"Face Savouring Delicious Food","b":"1F60B","f":true,"k":[32,57],"j":["face_savoring_food","happy","joy","tongue","smile","face","silly","yummy","nom","delicious","savouring"]},"stuck_out_tongue":{"a":"Face with Stuck-Out Tongue","b":"1F61B","f":true,"k":[33,11],"j":["face_with_tongue","face","prank","childish","playful","mischievous","smile","tongue"],"l":[":p",":-p",":P",":-P",":b",":-b"],"m":":p"},"stuck_out_tongue_winking_eye":{"a":"Face with Stuck-Out Tongue and Winking Eye","b":"1F61C","f":true,"k":[33,12],"j":["winking_face_with_tongue","face","prank","childish","playful","mischievous","smile","wink","tongue"],"l":[";p",";-p",";b",";-b",";P",";-P"],"m":";p"},"zany_face":{"a":"Grinning Face with One Large and One Small Eye","b":"1F92A","f":true,"k":[41,16],"j":["face","goofy","crazy"]},"stuck_out_tongue_closed_eyes":{"a":"Face with Stuck-Out Tongue and Tightly-Closed Eyes","b":"1F61D","f":true,"k":[33,13],"j":["squinting_face_with_tongue","face","prank","playful","mischievous","smile","tongue"]},"money_mouth_face":{"a":"Money-Mouth Face","b":"1F911","f":true,"k":[39,38],"j":["face","rich","dollar","money"]},"hugging_face":{"a":"Hugging Face","b":"1F917","f":true,"k":[39,44],"j":["face","smile","hug"]},"face_with_hand_over_mouth":{"a":"Smiling Face with Smiling Eyes and Hand Covering Mouth","b":"1F92D","f":true,"k":[41,19],"j":["face","whoops","shock","surprise"]},"face_with_open_eyes_and_hand_over_mouth":{"a":"Face with Open Eyes and Hand Over Mouth","b":"1FAE2","f":true,"k":[56,32],"j":["face with open eyes and hand over mouth","silence","secret","shock","surprise"]},"face_with_peeking_eye":{"a":"Face with Peeking Eye","b":"1FAE3","f":true,"k":[56,33],"j":["face with peeking eye","scared","frightening","embarrassing","shy"]},"shushing_face":{"a":"Face with Finger Covering Closed Lips","b":"1F92B","f":true,"k":[41,17],"j":["face","quiet","shhh"]},"thinking_face":{"a":"Thinking Face","b":"1F914","f":true,"k":[39,41],"j":["face","hmmm","think","consider"]},"saluting_face":{"a":"Saluting Face","b":"1FAE1","f":true,"k":[56,31],"j":["saluting face","respect","salute"]},"zipper_mouth_face":{"a":"Zipper-Mouth Face","b":"1F910","f":true,"k":[39,37],"j":["face","sealed","zipper","secret"]},"face_with_raised_eyebrow":{"a":"Face with One Eyebrow Raised","b":"1F928","f":true,"k":[41,14],"j":["face","distrust","scepticism","disapproval","disbelief","surprise","suspicious"]},"neutral_face":{"a":"Neutral Face","b":"1F610","f":true,"k":[33,0],"j":["indifference","meh",":|","neutral"],"l":[":|",":-|"]},"expressionless":{"a":"Expressionless Face","b":"1F611","f":true,"k":[33,1],"j":["expressionless_face","face","indifferent","-_-","meh","deadpan"]},"no_mouth":{"a":"Face Without Mouth","b":"1F636","f":true,"k":[33,41],"j":["face_without_mouth","face"]},"dotted_line_face":{"a":"Dotted Line Face","b":"1FAE5","f":true,"k":[56,35],"j":["dotted line face","invisible","lonely","isolation","depression"]},"face_in_clouds":{"a":"Face In Clouds","b":"1F636-200D-1F32B-FE0F","f":true,"k":[33,40],"c":"1F636-200D-1F32B","j":["face_without_mouth","face"]},"smirk":{"a":"Smirking Face","b":"1F60F","f":true,"k":[32,61],"j":["smirking_face","face","smile","mean","prank","smug","sarcasm"]},"unamused":{"a":"Unamused Face","b":"1F612","f":true,"k":[33,2],"j":["unamused_face","indifference","bored","straight face","serious","sarcasm","unimpressed","skeptical","dubious","ugh","side_eye"],"m":":("},"face_with_rolling_eyes":{"a":"Face with Rolling Eyes","b":"1F644","f":true,"k":[33,57],"j":["face","eyeroll","frustrated"]},"grimacing":{"a":"Grimacing Face","b":"1F62C","f":true,"k":[33,28],"j":["grimacing_face","face","grimace","teeth"]},"face_exhaling":{"a":"Face Exhaling","b":"1F62E-200D-1F4A8","f":true,"k":[33,30],"j":["face_with_open_mouth","face","surprise","impressed","wow","whoa",":O"]},"lying_face":{"a":"Lying Face","b":"1F925","f":true,"k":[40,56],"j":["face","lie","pinocchio"]},"shaking_face":{"a":"Shaking Face","b":"1FAE8","f":true,"k":[56,38],"j":["shaking face","dizzy","shock","blurry","earthquake"]},"head_shaking_horizontally":{"a":"Head Shaking Horizontally","b":"1F642-200D-2194-FE0F","f":true,"k":[33,53],"c":"1F642-200D-2194","j":["slightly_smiling_face","face","smile"]},"head_shaking_vertically":{"a":"Head Shaking Vertically","b":"1F642-200D-2195-FE0F","f":true,"k":[33,54],"c":"1F642-200D-2195","j":["slightly_smiling_face","face","smile"]},"relieved":{"a":"Relieved Face","b":"1F60C","f":true,"k":[32,58],"j":["relieved_face","face","relaxed","phew","massage","happiness"]},"pensive":{"a":"Pensive Face","b":"1F614","f":true,"k":[33,4],"j":["pensive_face","face","sad","depressed","upset"]},"sleepy":{"a":"Sleepy Face","b":"1F62A","f":true,"k":[33,26],"j":["sleepy_face","face","tired","rest","nap"]},"drooling_face":{"a":"Drooling Face","b":"1F924","f":true,"k":[40,55],"j":["face"]},"sleeping":{"a":"Sleeping Face","b":"1F634","f":true,"k":[33,37],"j":["sleeping_face","face","tired","sleepy","night","zzz"]},"mask":{"a":"Face with Medical Mask","b":"1F637","f":true,"k":[33,42],"j":["face_with_medical_mask","face","sick","ill","disease","covid"]},"face_with_thermometer":{"a":"Face with Thermometer","b":"1F912","f":true,"k":[39,39],"j":["sick","temperature","thermometer","cold","fever","covid"]},"face_with_head_bandage":{"a":"Face with Head-Bandage","b":"1F915","f":true,"k":[39,42],"j":["injured","clumsy","bandage","hurt"]},"nauseated_face":{"a":"Nauseated Face","b":"1F922","f":true,"k":[40,53],"j":["face","vomit","gross","green","sick","throw up","ill"]},"face_vomiting":{"a":"Face with Open Mouth Vomiting","b":"1F92E","f":true,"k":[41,20],"j":["face","sick"]},"sneezing_face":{"a":"Sneezing Face","b":"1F927","f":true,"k":[41,13],"j":["face","gesundheit","sneeze","sick","allergy"]},"hot_face":{"a":"Overheated Face","b":"1F975","f":true,"k":[44,37],"j":["face","feverish","heat","red","sweating"]},"cold_face":{"a":"Freezing Face","b":"1F976","f":true,"k":[44,38],"j":["face","blue","freezing","frozen","frostbite","icicles"]},"woozy_face":{"a":"Face with Uneven Eyes and Wavy Mouth","b":"1F974","f":true,"k":[44,36],"j":["face","dizzy","intoxicated","tipsy","wavy"]},"dizzy_face":{"a":"Dizzy Face","b":"1F635","f":true,"k":[33,39],"j":["spent","unconscious","xox","dizzy"]},"face_with_spiral_eyes":{"a":"Face with Spiral Eyes","b":"1F635-200D-1F4AB","f":true,"k":[33,38],"j":["dizzy_face","spent","unconscious","xox","dizzy"]},"exploding_head":{"a":"Shocked Face with Exploding Head","b":"1F92F","f":true,"k":[41,21],"j":["face","shocked","mind","blown"]},"face_with_cowboy_hat":{"a":"Face with Cowboy Hat","b":"1F920","f":true,"k":[40,51],"j":["cowboy_hat_face","face","cowgirl","hat"]},"partying_face":{"a":"Face with Party Horn and Party Hat","b":"1F973","f":true,"k":[44,35],"j":["face","celebration","woohoo"]},"disguised_face":{"a":"Disguised Face","b":"1F978","f":true,"k":[44,45],"j":["disguised face","pretent","brows","glasses","moustache"]},"sunglasses":{"a":"Smiling Face with Sunglasses","b":"1F60E","f":true,"k":[32,60],"j":["smiling_face_with_sunglasses","face","cool","smile","summer","beach","sunglass"],"l":["8)"]},"nerd_face":{"a":"Nerd Face","b":"1F913","f":true,"k":[39,40],"j":["face","nerdy","geek","dork"]},"face_with_monocle":{"a":"Face with Monocle","b":"1F9D0","f":true,"k":[47,61],"j":["face","stuffy","wealthy"]},"confused":{"a":"Confused Face","b":"1F615","f":true,"k":[33,5],"j":["confused_face","face","indifference","huh","weird","hmmm",":/"],"l":[":\\",":-\\",":/",":-/"]},"face_with_diagonal_mouth":{"a":"Face with Diagonal Mouth","b":"1FAE4","f":true,"k":[56,34],"j":["face with diagonal mouth","skeptic","confuse","frustrated","indifferent"]},"worried":{"a":"Worried Face","b":"1F61F","f":true,"k":[33,15],"j":["worried_face","face","concern","nervous",":("]},"slightly_frowning_face":{"a":"Slightly Frowning Face","b":"1F641","f":true,"k":[33,52],"j":["face","frowning","disappointed","sad","upset"]},"white_frowning_face":{"a":"Frowning Face","b":"2639-FE0F","f":true,"k":[58,32],"c":"2639"},"open_mouth":{"a":"Face with Open Mouth","b":"1F62E","f":true,"k":[33,31],"j":["face_with_open_mouth","face","surprise","impressed","wow","whoa",":O"],"l":[":o",":-o",":O",":-O"]},"hushed":{"a":"Hushed Face","b":"1F62F","f":true,"k":[33,32],"j":["hushed_face","face","woo","shh"]},"astonished":{"a":"Astonished Face","b":"1F632","f":true,"k":[33,35],"j":["astonished_face","face","xox","surprised","poisoned"]},"flushed":{"a":"Flushed Face","b":"1F633","f":true,"k":[33,36],"j":["flushed_face","face","blush","shy","flattered"]},"pleading_face":{"a":"Face with Pleading Eyes","b":"1F97A","f":true,"k":[44,47],"j":["face","begging","mercy","cry","tears","sad","grievance"]},"face_holding_back_tears":{"a":"Face Holding Back Tears","b":"1F979","f":true,"k":[44,46],"j":["face holding back tears","touched","gratitude","cry"]},"frowning":{"a":"Frowning Face with Open Mouth","b":"1F626","f":true,"k":[33,22],"j":["frowning_face_with_open_mouth","face","aw","what"]},"anguished":{"a":"Anguished Face","b":"1F627","f":true,"k":[33,23],"j":["anguished_face","face","stunned","nervous"],"l":["D:"]},"fearful":{"a":"Fearful Face","b":"1F628","f":true,"k":[33,24],"j":["fearful_face","face","scared","terrified","nervous"]},"cold_sweat":{"a":"Face with Open Mouth and Cold Sweat","b":"1F630","f":true,"k":[33,33],"j":["anxious_face_with_sweat","face","nervous","sweat"]},"disappointed_relieved":{"a":"Disappointed But Relieved Face","b":"1F625","f":true,"k":[33,21],"j":["sad_but_relieved_face","face","phew","sweat","nervous"]},"cry":{"a":"Crying Face","b":"1F622","f":true,"k":[33,18],"j":["crying_face","face","tears","sad","depressed","upset",":'("],"l":[":'("],"m":":'("},"sob":{"a":"Loudly Crying Face","b":"1F62D","f":true,"k":[33,29],"j":["loudly_crying_face","sobbing","face","cry","tears","sad","upset","depressed"],"m":":'("},"scream":{"a":"Face Screaming In Fear","b":"1F631","f":true,"k":[33,34],"j":["face_screaming_in_fear","face","munch","scared","omg"]},"confounded":{"a":"Confounded Face","b":"1F616","f":true,"k":[33,6],"j":["confounded_face","face","confused","sick","unwell","oops",":S"]},"persevere":{"a":"Persevering Face","b":"1F623","f":true,"k":[33,19],"j":["persevering_face","face","sick","no","upset","oops"]},"disappointed":{"a":"Disappointed Face","b":"1F61E","f":true,"k":[33,14],"j":["disappointed_face","face","sad","upset","depressed",":("],"l":["):",":(",":-("],"m":":("},"sweat":{"a":"Face with Cold Sweat","b":"1F613","f":true,"k":[33,3],"j":["downcast_face_with_sweat","face","hot","sad","tired","exercise"]},"weary":{"a":"Weary Face","b":"1F629","f":true,"k":[33,25],"j":["weary_face","face","tired","sleepy","sad","frustrated","upset"]},"tired_face":{"a":"Tired Face","b":"1F62B","f":true,"k":[33,27],"j":["sick","whine","upset","frustrated"]},"yawning_face":{"a":"Yawning Face","b":"1F971","f":true,"k":[44,33],"j":["tired","sleepy"]},"triumph":{"a":"Face with Look Of Triumph","b":"1F624","f":true,"k":[33,20],"j":["face_with_steam_from_nose","face","gas","phew","proud","pride"]},"rage":{"a":"Pouting Face","b":"1F621","f":true,"k":[33,17],"j":["pouting_face","angry","mad","hate","despise"]},"angry":{"a":"Angry Face","b":"1F620","f":true,"k":[33,16],"j":["angry_face","mad","face","annoyed","frustrated"],"l":[">:(",">:-("]},"face_with_symbols_on_mouth":{"a":"Serious Face with Symbols Covering Mouth","b":"1F92C","f":true,"k":[41,18],"j":["face","swearing","cursing","cussing","profanity","expletive"]},"smiling_imp":{"a":"Smiling Face with Horns","b":"1F608","f":true,"k":[32,54],"j":["smiling_face_with_horns","devil","horns"]},"imp":{"a":"Imp","b":"1F47F","f":true,"k":[25,41],"j":["angry_face_with_horns","devil","angry","horns"]},"skull":{"a":"Skull","b":"1F480","f":true,"k":[25,42],"j":["dead","skeleton","creepy","death","dead"]},"skull_and_crossbones":{"a":"Skull and Crossbones","b":"2620-FE0F","f":true,"k":[58,24],"c":"2620"},"hankey":{"a":"Pile Of Poo","b":"1F4A9","f":true,"k":[28,25],"j":["pile_of_poo","shitface","fail","turd","shit"]},"clown_face":{"a":"Clown Face","b":"1F921","f":true,"k":[40,52],"j":["face"]},"japanese_ogre":{"a":"Japanese Ogre","b":"1F479","f":true,"k":[25,30],"j":["ogre","monster","red","mask","halloween","scary","creepy","devil","demon"]},"japanese_goblin":{"a":"Japanese Goblin","b":"1F47A","f":true,"k":[25,31],"j":["goblin","red","evil","mask","monster","scary","creepy"]},"ghost":{"a":"Ghost","b":"1F47B","f":true,"k":[25,32],"j":["halloween","spooky","scary"]},"alien":{"a":"Extraterrestrial Alien","b":"1F47D","f":true,"k":[25,39],"j":["UFO","paul","weird","outer_space"]},"space_invader":{"a":"Alien Monster","b":"1F47E","f":true,"k":[25,40],"j":["alien_monster","game","arcade","play"]},"robot_face":{"a":"Robot Face","b":"1F916","f":true,"k":[39,43],"j":["robot","computer","machine","bot"]},"smiley_cat":{"a":"Smiling Cat Face with Open Mouth","b":"1F63A","f":true,"k":[33,45],"j":["grinning_cat","animal","cats","happy","smile"]},"smile_cat":{"a":"Grinning Cat Face with Smiling Eyes","b":"1F638","f":true,"k":[33,43],"j":["grinning_cat_with_smiling_eyes","animal","cats","smile"]},"joy_cat":{"a":"Cat Face with Tears Of Joy","b":"1F639","f":true,"k":[33,44],"j":["cat_with_tears_of_joy","animal","cats","haha","happy","tears"]},"heart_eyes_cat":{"a":"Smiling Cat Face with Heart-Shaped Eyes","b":"1F63B","f":true,"k":[33,46],"j":["smiling_cat_with_heart_eyes","animal","love","like","affection","cats","valentines","heart"]},"smirk_cat":{"a":"Cat Face with Wry Smile","b":"1F63C","f":true,"k":[33,47],"j":["cat_with_wry_smile","animal","cats","smirk"]},"kissing_cat":{"a":"Kissing Cat Face with Closed Eyes","b":"1F63D","f":true,"k":[33,48],"j":["animal","cats","kiss"]},"scream_cat":{"a":"Weary Cat Face","b":"1F640","f":true,"k":[33,51],"j":["weary_cat","animal","cats","munch","scared","scream"]},"crying_cat_face":{"a":"Crying Cat Face","b":"1F63F","f":true,"k":[33,50],"j":["crying_cat","animal","tears","weep","sad","cats","upset","cry"]},"pouting_cat":{"a":"Pouting Cat Face","b":"1F63E","f":true,"k":[33,49],"j":["animal","cats"]},"see_no_evil":{"a":"See-No-Evil Monkey","b":"1F648","f":true,"k":[34,50],"j":["see_no_evil_monkey","monkey","animal","nature","haha"]},"hear_no_evil":{"a":"Hear-No-Evil Monkey","b":"1F649","f":true,"k":[34,51],"j":["hear_no_evil_monkey","animal","monkey","nature"]},"speak_no_evil":{"a":"Speak-No-Evil Monkey","b":"1F64A","f":true,"k":[34,52],"j":["speak_no_evil_monkey","monkey","animal","nature","omg"]},"love_letter":{"a":"Love Letter","b":"1F48C","f":true,"k":[27,8],"j":["email","like","affection","envelope","valentines"]},"cupid":{"a":"Heart with Arrow","b":"1F498","f":true,"k":[28,8],"j":["heart_with_arrow","love","like","heart","affection","valentines"]},"gift_heart":{"a":"Heart with Ribbon","b":"1F49D","f":true,"k":[28,13],"j":["heart_with_ribbon","love","valentines"]},"sparkling_heart":{"a":"Sparkling Heart","b":"1F496","f":true,"k":[28,6],"j":["love","like","affection","valentines"]},"heartpulse":{"a":"Growing Heart","b":"1F497","f":true,"k":[28,7],"j":["growing_heart","like","love","affection","valentines","pink"]},"heartbeat":{"a":"Beating Heart","b":"1F493","f":true,"k":[28,3],"j":["beating_heart","love","like","affection","valentines","pink","heart"]},"revolving_hearts":{"a":"Revolving Hearts","b":"1F49E","f":true,"k":[28,14],"j":["love","like","affection","valentines"]},"two_hearts":{"a":"Two Hearts","b":"1F495","f":true,"k":[28,5],"j":["love","like","affection","valentines","heart"]},"heart_decoration":{"a":"Heart Decoration","b":"1F49F","f":true,"k":[28,15],"j":["purple-square","love","like"]},"heavy_heart_exclamation_mark_ornament":{"a":"Heart Exclamation","b":"2763-FE0F","f":true,"k":[60,35],"c":"2763"},"broken_heart":{"a":"Broken Heart","b":"1F494","f":true,"k":[28,4],"j":["sad","sorry","break","heart","heartbreak"],"l":["",":->"]},"sweat_smile":{"a":"Smiling Face with Open Mouth and Cold Sweat","b":"1F605","f":true,"k":[32,52],"j":["grinning_face_with_sweat","face","hot","happy","laugh","sweat","smile","relief"]},"rolling_on_the_floor_laughing":{"a":"Rolling On the Floor Laughing","b":"1F923","f":true,"k":[40,55],"j":["face","rolling","floor","laughing","lol","haha","rofl"]},"joy":{"a":"Face with Tears Of Joy","b":"1F602","f":true,"k":[32,49],"j":["face_with_tears_of_joy","face","cry","tears","weep","happy","happytears","haha"]},"slightly_smiling_face":{"a":"Slightly Smiling Face","b":"1F642","f":true,"k":[33,56],"j":["face","smile"],"l":[":)","(:",":-)"]},"upside_down_face":{"a":"Upside-Down Face","b":"1F643","f":true,"k":[33,57],"j":["face","flipped","silly","smile"]},"melting_face":{"a":"Melting Face","b":"1FAE0","f":true,"k":[56,37],"j":["melting face","hot","heat"]},"wink":{"a":"Winking Face","b":"1F609","f":true,"k":[32,56],"j":["winking_face","face","happy","mischievous","secret",";)","smile","eye"],"l":[";)",";-)"],"m":";)"},"blush":{"a":"Smiling Face with Smiling Eyes","b":"1F60A","f":true,"k":[32,57],"j":["smiling_face_with_smiling_eyes","face","smile","happy","flushed","crush","embarrassed","shy","joy"],"m":":)"},"innocent":{"a":"Smiling Face with Halo","b":"1F607","f":true,"k":[32,54],"j":["smiling_face_with_halo","face","angel","heaven","halo"]},"smiling_face_with_3_hearts":{"a":"Smiling Face with Smiling Eyes and Three Hearts","b":"1F970","f":true,"k":[44,33],"j":["smiling_face_with_hearts","face","love","like","affection","valentines","infatuation","crush","hearts","adore"]},"heart_eyes":{"a":"Smiling Face with Heart-Shaped Eyes","b":"1F60D","f":true,"k":[32,60],"j":["smiling_face_with_heart_eyes","face","love","like","affection","valentines","infatuation","crush","heart"]},"star-struck":{"a":"Grinning Face with Star Eyes","b":"1F929","f":true,"k":[41,16],"j":["star_struck","face","smile","starry","eyes","grinning"]},"kissing_heart":{"a":"Face Throwing a Kiss","b":"1F618","f":true,"k":[33,9],"j":["face_blowing_a_kiss","face","love","like","affection","valentines","infatuation","kiss"],"l":[":*",":-*"]},"kissing":{"a":"Kissing Face","b":"1F617","f":true,"k":[33,8],"j":["kissing_face","love","like","face","3","valentines","infatuation","kiss"]},"relaxed":{"a":"White Smiling Face","b":"263A-FE0F","f":true,"k":[58,41],"c":"263A"},"kissing_closed_eyes":{"a":"Kissing Face with Closed Eyes","b":"1F61A","f":true,"k":[33,11],"j":["kissing_face_with_closed_eyes","face","love","like","affection","valentines","infatuation","kiss"]},"kissing_smiling_eyes":{"a":"Kissing Face with Smiling Eyes","b":"1F619","f":true,"k":[33,10],"j":["kissing_face_with_smiling_eyes","face","affection","valentines","infatuation","kiss"]},"smiling_face_with_tear":{"a":"Smiling Face with Tear","b":"1F972","f":true,"k":[44,35],"j":["smiling face with tear","sad","cry","pretend"]},"yum":{"a":"Face Savouring Delicious Food","b":"1F60B","f":true,"k":[32,58],"j":["face_savoring_food","happy","joy","tongue","smile","face","silly","yummy","nom","delicious","savouring"]},"stuck_out_tongue":{"a":"Face with Stuck-Out Tongue","b":"1F61B","f":true,"k":[33,12],"j":["face_with_tongue","face","prank","childish","playful","mischievous","smile","tongue"],"l":[":p",":-p",":P",":-P",":b",":-b"],"m":":p"},"stuck_out_tongue_winking_eye":{"a":"Face with Stuck-Out Tongue and Winking Eye","b":"1F61C","f":true,"k":[33,13],"j":["winking_face_with_tongue","face","prank","childish","playful","mischievous","smile","wink","tongue"],"l":[";p",";-p",";b",";-b",";P",";-P"],"m":";p"},"zany_face":{"a":"Grinning Face with One Large and One Small Eye","b":"1F92A","f":true,"k":[41,17],"j":["face","goofy","crazy"]},"stuck_out_tongue_closed_eyes":{"a":"Face with Stuck-Out Tongue and Tightly-Closed Eyes","b":"1F61D","f":true,"k":[33,14],"j":["squinting_face_with_tongue","face","prank","playful","mischievous","smile","tongue"]},"money_mouth_face":{"a":"Money-Mouth Face","b":"1F911","f":true,"k":[39,39],"j":["face","rich","dollar","money"]},"hugging_face":{"a":"Hugging Face","b":"1F917","f":true,"k":[39,45],"j":["face","smile","hug"]},"face_with_hand_over_mouth":{"a":"Smiling Face with Smiling Eyes and Hand Covering Mouth","b":"1F92D","f":true,"k":[41,20],"j":["face","whoops","shock","surprise"]},"face_with_open_eyes_and_hand_over_mouth":{"a":"Face with Open Eyes and Hand Over Mouth","b":"1FAE2","f":true,"k":[56,39],"j":["face with open eyes and hand over mouth","silence","secret","shock","surprise"]},"face_with_peeking_eye":{"a":"Face with Peeking Eye","b":"1FAE3","f":true,"k":[56,40],"j":["face with peeking eye","scared","frightening","embarrassing","shy"]},"shushing_face":{"a":"Face with Finger Covering Closed Lips","b":"1F92B","f":true,"k":[41,18],"j":["face","quiet","shhh"]},"thinking_face":{"a":"Thinking Face","b":"1F914","f":true,"k":[39,42],"j":["face","hmmm","think","consider"]},"saluting_face":{"a":"Saluting Face","b":"1FAE1","f":true,"k":[56,38],"j":["saluting face","respect","salute"]},"zipper_mouth_face":{"a":"Zipper-Mouth Face","b":"1F910","f":true,"k":[39,38],"j":["face","sealed","zipper","secret"]},"face_with_raised_eyebrow":{"a":"Face with One Eyebrow Raised","b":"1F928","f":true,"k":[41,15],"j":["face","distrust","scepticism","disapproval","disbelief","surprise","suspicious"]},"neutral_face":{"a":"Neutral Face","b":"1F610","f":true,"k":[33,1],"j":["indifference","meh",":|","neutral"],"l":[":|",":-|"]},"expressionless":{"a":"Expressionless Face","b":"1F611","f":true,"k":[33,2],"j":["expressionless_face","face","indifferent","-_-","meh","deadpan"]},"no_mouth":{"a":"Face Without Mouth","b":"1F636","f":true,"k":[33,42],"j":["face_without_mouth","face"]},"dotted_line_face":{"a":"Dotted Line Face","b":"1FAE5","f":true,"k":[56,42],"j":["dotted line face","invisible","lonely","isolation","depression"]},"face_in_clouds":{"a":"Face In Clouds","b":"1F636-200D-1F32B-FE0F","f":true,"k":[33,41],"c":"1F636-200D-1F32B","j":["face_without_mouth","face"]},"smirk":{"a":"Smirking Face","b":"1F60F","f":true,"k":[33,0],"j":["smirking_face","face","smile","mean","prank","smug","sarcasm"]},"unamused":{"a":"Unamused Face","b":"1F612","f":true,"k":[33,3],"j":["unamused_face","indifference","bored","straight face","serious","sarcasm","unimpressed","skeptical","dubious","ugh","side_eye"],"m":":("},"face_with_rolling_eyes":{"a":"Face with Rolling Eyes","b":"1F644","f":true,"k":[33,58],"j":["face","eyeroll","frustrated"]},"grimacing":{"a":"Grimacing Face","b":"1F62C","f":true,"k":[33,29],"j":["grimacing_face","face","grimace","teeth"]},"face_exhaling":{"a":"Face Exhaling","b":"1F62E-200D-1F4A8","f":true,"k":[33,31],"j":["face_with_open_mouth","face","surprise","impressed","wow","whoa",":O"]},"lying_face":{"a":"Lying Face","b":"1F925","f":true,"k":[40,57],"j":["face","lie","pinocchio"]},"shaking_face":{"a":"Shaking Face","b":"1FAE8","f":true,"k":[56,45],"j":["shaking face","dizzy","shock","blurry","earthquake"]},"head_shaking_horizontally":{"a":"Head Shaking Horizontally","b":"1F642-200D-2194-FE0F","f":true,"k":[33,54],"c":"1F642-200D-2194","j":["slightly_smiling_face","face","smile"]},"head_shaking_vertically":{"a":"Head Shaking Vertically","b":"1F642-200D-2195-FE0F","f":true,"k":[33,55],"c":"1F642-200D-2195","j":["slightly_smiling_face","face","smile"]},"relieved":{"a":"Relieved Face","b":"1F60C","f":true,"k":[32,59],"j":["relieved_face","face","relaxed","phew","massage","happiness"]},"pensive":{"a":"Pensive Face","b":"1F614","f":true,"k":[33,5],"j":["pensive_face","face","sad","depressed","upset"]},"sleepy":{"a":"Sleepy Face","b":"1F62A","f":true,"k":[33,27],"j":["sleepy_face","face","tired","rest","nap"]},"drooling_face":{"a":"Drooling Face","b":"1F924","f":true,"k":[40,56],"j":["face"]},"sleeping":{"a":"Sleeping Face","b":"1F634","f":true,"k":[33,38],"j":["sleeping_face","face","tired","sleepy","night","zzz"]},"face_with_bags_under_eyes":{"a":"Face with Bags Under Eyes","b":"1FAE9","f":true,"k":[56,46]},"mask":{"a":"Face with Medical Mask","b":"1F637","f":true,"k":[33,43],"j":["face_with_medical_mask","face","sick","ill","disease","covid"]},"face_with_thermometer":{"a":"Face with Thermometer","b":"1F912","f":true,"k":[39,40],"j":["sick","temperature","thermometer","cold","fever","covid"]},"face_with_head_bandage":{"a":"Face with Head-Bandage","b":"1F915","f":true,"k":[39,43],"j":["injured","clumsy","bandage","hurt"]},"nauseated_face":{"a":"Nauseated Face","b":"1F922","f":true,"k":[40,54],"j":["face","vomit","gross","green","sick","throw up","ill"]},"face_vomiting":{"a":"Face with Open Mouth Vomiting","b":"1F92E","f":true,"k":[41,21],"j":["face","sick"]},"sneezing_face":{"a":"Sneezing Face","b":"1F927","f":true,"k":[41,14],"j":["face","gesundheit","sneeze","sick","allergy"]},"hot_face":{"a":"Overheated Face","b":"1F975","f":true,"k":[44,38],"j":["face","feverish","heat","red","sweating"]},"cold_face":{"a":"Freezing Face","b":"1F976","f":true,"k":[44,39],"j":["face","blue","freezing","frozen","frostbite","icicles"]},"woozy_face":{"a":"Face with Uneven Eyes and Wavy Mouth","b":"1F974","f":true,"k":[44,37],"j":["face","dizzy","intoxicated","tipsy","wavy"]},"dizzy_face":{"a":"Dizzy Face","b":"1F635","f":true,"k":[33,40],"j":["spent","unconscious","xox","dizzy"]},"face_with_spiral_eyes":{"a":"Face with Spiral Eyes","b":"1F635-200D-1F4AB","f":true,"k":[33,39],"j":["dizzy_face","spent","unconscious","xox","dizzy"]},"exploding_head":{"a":"Shocked Face with Exploding Head","b":"1F92F","f":true,"k":[41,22],"j":["face","shocked","mind","blown"]},"face_with_cowboy_hat":{"a":"Face with Cowboy Hat","b":"1F920","f":true,"k":[40,52],"j":["cowboy_hat_face","face","cowgirl","hat"]},"partying_face":{"a":"Face with Party Horn and Party Hat","b":"1F973","f":true,"k":[44,36],"j":["face","celebration","woohoo"]},"disguised_face":{"a":"Disguised Face","b":"1F978","f":true,"k":[44,46],"j":["disguised face","pretent","brows","glasses","moustache"]},"sunglasses":{"a":"Smiling Face with Sunglasses","b":"1F60E","f":true,"k":[32,61],"j":["smiling_face_with_sunglasses","face","cool","smile","summer","beach","sunglass"],"l":["8)"]},"nerd_face":{"a":"Nerd Face","b":"1F913","f":true,"k":[39,41],"j":["face","nerdy","geek","dork"]},"face_with_monocle":{"a":"Face with Monocle","b":"1F9D0","f":true,"k":[48,0],"j":["face","stuffy","wealthy"]},"confused":{"a":"Confused Face","b":"1F615","f":true,"k":[33,6],"j":["confused_face","face","indifference","huh","weird","hmmm",":/"],"l":[":\\",":-\\",":/",":-/"]},"face_with_diagonal_mouth":{"a":"Face with Diagonal Mouth","b":"1FAE4","f":true,"k":[56,41],"j":["face with diagonal mouth","skeptic","confuse","frustrated","indifferent"]},"worried":{"a":"Worried Face","b":"1F61F","f":true,"k":[33,16],"j":["worried_face","face","concern","nervous",":("]},"slightly_frowning_face":{"a":"Slightly Frowning Face","b":"1F641","f":true,"k":[33,53],"j":["face","frowning","disappointed","sad","upset"]},"white_frowning_face":{"a":"Frowning Face","b":"2639-FE0F","f":true,"k":[58,40],"c":"2639"},"open_mouth":{"a":"Face with Open Mouth","b":"1F62E","f":true,"k":[33,32],"j":["face_with_open_mouth","face","surprise","impressed","wow","whoa",":O"],"l":[":o",":-o",":O",":-O"]},"hushed":{"a":"Hushed Face","b":"1F62F","f":true,"k":[33,33],"j":["hushed_face","face","woo","shh"]},"astonished":{"a":"Astonished Face","b":"1F632","f":true,"k":[33,36],"j":["astonished_face","face","xox","surprised","poisoned"]},"flushed":{"a":"Flushed Face","b":"1F633","f":true,"k":[33,37],"j":["flushed_face","face","blush","shy","flattered"]},"pleading_face":{"a":"Face with Pleading Eyes","b":"1F97A","f":true,"k":[44,48],"j":["face","begging","mercy","cry","tears","sad","grievance"]},"face_holding_back_tears":{"a":"Face Holding Back Tears","b":"1F979","f":true,"k":[44,47],"j":["face holding back tears","touched","gratitude","cry"]},"frowning":{"a":"Frowning Face with Open Mouth","b":"1F626","f":true,"k":[33,23],"j":["frowning_face_with_open_mouth","face","aw","what"]},"anguished":{"a":"Anguished Face","b":"1F627","f":true,"k":[33,24],"j":["anguished_face","face","stunned","nervous"],"l":["D:"]},"fearful":{"a":"Fearful Face","b":"1F628","f":true,"k":[33,25],"j":["fearful_face","face","scared","terrified","nervous"]},"cold_sweat":{"a":"Face with Open Mouth and Cold Sweat","b":"1F630","f":true,"k":[33,34],"j":["anxious_face_with_sweat","face","nervous","sweat"]},"disappointed_relieved":{"a":"Disappointed But Relieved Face","b":"1F625","f":true,"k":[33,22],"j":["sad_but_relieved_face","face","phew","sweat","nervous"]},"cry":{"a":"Crying Face","b":"1F622","f":true,"k":[33,19],"j":["crying_face","face","tears","sad","depressed","upset",":'("],"l":[":'("],"m":":'("},"sob":{"a":"Loudly Crying Face","b":"1F62D","f":true,"k":[33,30],"j":["loudly_crying_face","sobbing","face","cry","tears","sad","upset","depressed"],"m":":'("},"scream":{"a":"Face Screaming In Fear","b":"1F631","f":true,"k":[33,35],"j":["face_screaming_in_fear","face","munch","scared","omg"]},"confounded":{"a":"Confounded Face","b":"1F616","f":true,"k":[33,7],"j":["confounded_face","face","confused","sick","unwell","oops",":S"]},"persevere":{"a":"Persevering Face","b":"1F623","f":true,"k":[33,20],"j":["persevering_face","face","sick","no","upset","oops"]},"disappointed":{"a":"Disappointed Face","b":"1F61E","f":true,"k":[33,15],"j":["disappointed_face","face","sad","upset","depressed",":("],"l":["):",":(",":-("],"m":":("},"sweat":{"a":"Face with Cold Sweat","b":"1F613","f":true,"k":[33,4],"j":["downcast_face_with_sweat","face","hot","sad","tired","exercise"]},"weary":{"a":"Weary Face","b":"1F629","f":true,"k":[33,26],"j":["weary_face","face","tired","sleepy","sad","frustrated","upset"]},"tired_face":{"a":"Tired Face","b":"1F62B","f":true,"k":[33,28],"j":["sick","whine","upset","frustrated"]},"yawning_face":{"a":"Yawning Face","b":"1F971","f":true,"k":[44,34],"j":["tired","sleepy"]},"triumph":{"a":"Face with Look Of Triumph","b":"1F624","f":true,"k":[33,21],"j":["face_with_steam_from_nose","face","gas","phew","proud","pride"]},"rage":{"a":"Pouting Face","b":"1F621","f":true,"k":[33,18],"j":["pouting_face","angry","mad","hate","despise"]},"angry":{"a":"Angry Face","b":"1F620","f":true,"k":[33,17],"j":["angry_face","mad","face","annoyed","frustrated"],"l":[">:(",">:-("]},"face_with_symbols_on_mouth":{"a":"Serious Face with Symbols Covering Mouth","b":"1F92C","f":true,"k":[41,19],"j":["face","swearing","cursing","cussing","profanity","expletive"]},"smiling_imp":{"a":"Smiling Face with Horns","b":"1F608","f":true,"k":[32,55],"j":["smiling_face_with_horns","devil","horns"]},"imp":{"a":"Imp","b":"1F47F","f":true,"k":[25,42],"j":["angry_face_with_horns","devil","angry","horns"]},"skull":{"a":"Skull","b":"1F480","f":true,"k":[25,43],"j":["dead","skeleton","creepy","death","dead"]},"skull_and_crossbones":{"a":"Skull and Crossbones","b":"2620-FE0F","f":true,"k":[58,32],"c":"2620"},"hankey":{"a":"Pile Of Poo","b":"1F4A9","f":true,"k":[28,26],"j":["pile_of_poo","shitface","fail","turd","shit"]},"clown_face":{"a":"Clown Face","b":"1F921","f":true,"k":[40,53],"j":["face"]},"japanese_ogre":{"a":"Japanese Ogre","b":"1F479","f":true,"k":[25,31],"j":["ogre","monster","red","mask","halloween","scary","creepy","devil","demon"]},"japanese_goblin":{"a":"Japanese Goblin","b":"1F47A","f":true,"k":[25,32],"j":["goblin","red","evil","mask","monster","scary","creepy"]},"ghost":{"a":"Ghost","b":"1F47B","f":true,"k":[25,33],"j":["halloween","spooky","scary"]},"alien":{"a":"Extraterrestrial Alien","b":"1F47D","f":true,"k":[25,40],"j":["UFO","paul","weird","outer_space"]},"space_invader":{"a":"Alien Monster","b":"1F47E","f":true,"k":[25,41],"j":["alien_monster","game","arcade","play"]},"robot_face":{"a":"Robot Face","b":"1F916","f":true,"k":[39,44],"j":["robot","computer","machine","bot"]},"smiley_cat":{"a":"Smiling Cat Face with Open Mouth","b":"1F63A","f":true,"k":[33,46],"j":["grinning_cat","animal","cats","happy","smile"]},"smile_cat":{"a":"Grinning Cat Face with Smiling Eyes","b":"1F638","f":true,"k":[33,44],"j":["grinning_cat_with_smiling_eyes","animal","cats","smile"]},"joy_cat":{"a":"Cat Face with Tears Of Joy","b":"1F639","f":true,"k":[33,45],"j":["cat_with_tears_of_joy","animal","cats","haha","happy","tears"]},"heart_eyes_cat":{"a":"Smiling Cat Face with Heart-Shaped Eyes","b":"1F63B","f":true,"k":[33,47],"j":["smiling_cat_with_heart_eyes","animal","love","like","affection","cats","valentines","heart"]},"smirk_cat":{"a":"Cat Face with Wry Smile","b":"1F63C","f":true,"k":[33,48],"j":["cat_with_wry_smile","animal","cats","smirk"]},"kissing_cat":{"a":"Kissing Cat Face with Closed Eyes","b":"1F63D","f":true,"k":[33,49],"j":["animal","cats","kiss"]},"scream_cat":{"a":"Weary Cat Face","b":"1F640","f":true,"k":[33,52],"j":["weary_cat","animal","cats","munch","scared","scream"]},"crying_cat_face":{"a":"Crying Cat Face","b":"1F63F","f":true,"k":[33,51],"j":["crying_cat","animal","tears","weep","sad","cats","upset","cry"]},"pouting_cat":{"a":"Pouting Cat Face","b":"1F63E","f":true,"k":[33,50],"j":["animal","cats"]},"see_no_evil":{"a":"See-No-Evil Monkey","b":"1F648","f":true,"k":[34,51],"j":["see_no_evil_monkey","monkey","animal","nature","haha"]},"hear_no_evil":{"a":"Hear-No-Evil Monkey","b":"1F649","f":true,"k":[34,52],"j":["hear_no_evil_monkey","animal","monkey","nature"]},"speak_no_evil":{"a":"Speak-No-Evil Monkey","b":"1F64A","f":true,"k":[34,53],"j":["speak_no_evil_monkey","monkey","animal","nature","omg"]},"love_letter":{"a":"Love Letter","b":"1F48C","f":true,"k":[27,9],"j":["email","like","affection","envelope","valentines"]},"cupid":{"a":"Heart with Arrow","b":"1F498","f":true,"k":[28,9],"j":["heart_with_arrow","love","like","heart","affection","valentines"]},"gift_heart":{"a":"Heart with Ribbon","b":"1F49D","f":true,"k":[28,14],"j":["heart_with_ribbon","love","valentines"]},"sparkling_heart":{"a":"Sparkling Heart","b":"1F496","f":true,"k":[28,7],"j":["love","like","affection","valentines"]},"heartpulse":{"a":"Growing Heart","b":"1F497","f":true,"k":[28,8],"j":["growing_heart","like","love","affection","valentines","pink"]},"heartbeat":{"a":"Beating Heart","b":"1F493","f":true,"k":[28,4],"j":["beating_heart","love","like","affection","valentines","pink","heart"]},"revolving_hearts":{"a":"Revolving Hearts","b":"1F49E","f":true,"k":[28,15],"j":["love","like","affection","valentines"]},"two_hearts":{"a":"Two Hearts","b":"1F495","f":true,"k":[28,6],"j":["love","like","affection","valentines","heart"]},"heart_decoration":{"a":"Heart Decoration","b":"1F49F","f":true,"k":[28,16],"j":["purple-square","love","like"]},"heavy_heart_exclamation_mark_ornament":{"a":"Heart Exclamation","b":"2763-FE0F","f":true,"k":[60,43],"c":"2763"},"broken_heart":{"a":"Broken Heart","b":"1F494","f":true,"k":[28,5],"j":["sad","sorry","break","heart","heartbreak"],"l":[" = Omit< - ComponentPropsWithoutRef, - 'dangerouslySetInnerHTML' | 'className' -> & { - htmlString: string; - extraEmojis?: CustomEmojiMapArg; - as?: Element; - shallow?: boolean; - className?: string; -}; - -export const ModernEmojiHTML = ({ - extraEmojis, - htmlString, - as: Wrapper = 'div', // Rename for syntax highlighting - shallow, - className = '', - ...props -}: EmojiHTMLProps) => { - const emojifiedHtml = useEmojify({ - text: htmlString, - extraEmojis, - deep: !shallow, - }); - - if (emojifiedHtml === null) { - return null; - } - - return ( - - ); -}; - -export const EmojiHTML = ( - props: EmojiHTMLProps, -) => { - if (isModernEmojiEnabled()) { - return ; - } - const { as: asElement, htmlString, extraEmojis, className, ...rest } = props; - const Wrapper = asElement ?? 'div'; - return ( - - ); -}; diff --git a/app/javascript/flavours/glitch/features/emoji/emoji_map.json b/app/javascript/flavours/glitch/features/emoji/emoji_map.json index 6e09bd0f7b16a5..c4c91bc72f6017 100644 --- a/app/javascript/flavours/glitch/features/emoji/emoji_map.json +++ b/app/javascript/flavours/glitch/features/emoji/emoji_map.json @@ -1 +1 @@ -{"😀":"1f600","😃":"1f603","😄":"1f604","😁":"1f601","😆":"1f606","😅":"1f605","🤣":"1f923","😂":"1f602","🙂":"1f642","🙃":"1f643","🫠":"1fae0","😉":"1f609","😊":"1f60a","😇":"1f607","🥰":"1f970","😍":"1f60d","🤩":"1f929","😘":"1f618","😗":"1f617","☺":"263a","😚":"1f61a","😙":"1f619","🥲":"1f972","😋":"1f60b","😛":"1f61b","😜":"1f61c","🤪":"1f92a","😝":"1f61d","🤑":"1f911","🤗":"1f917","🤭":"1f92d","🫢":"1fae2","🫣":"1fae3","🤫":"1f92b","🤔":"1f914","🫡":"1fae1","🤐":"1f910","🤨":"1f928","😐":"1f610","😑":"1f611","😶":"1f636","🫥":"1fae5","😏":"1f60f","😒":"1f612","🙄":"1f644","😬":"1f62c","🤥":"1f925","🫨":"1fae8","😌":"1f60c","😔":"1f614","😪":"1f62a","🤤":"1f924","😴":"1f634","😷":"1f637","🤒":"1f912","🤕":"1f915","🤢":"1f922","🤮":"1f92e","🤧":"1f927","🥵":"1f975","🥶":"1f976","🥴":"1f974","😵":"1f635","🤯":"1f92f","🤠":"1f920","🥳":"1f973","🥸":"1f978","😎":"1f60e","🤓":"1f913","🧐":"1f9d0","😕":"1f615","🫤":"1fae4","😟":"1f61f","🙁":"1f641","☹":"2639","😮":"1f62e","😯":"1f62f","😲":"1f632","😳":"1f633","🥺":"1f97a","🥹":"1f979","😦":"1f626","😧":"1f627","😨":"1f628","😰":"1f630","😥":"1f625","😢":"1f622","😭":"1f62d","😱":"1f631","😖":"1f616","😣":"1f623","😞":"1f61e","😓":"1f613","😩":"1f629","😫":"1f62b","🥱":"1f971","😤":"1f624","😡":"1f621","😠":"1f620","🤬":"1f92c","😈":"1f608","👿":"1f47f","💀":"1f480","☠":"2620","💩":"1f4a9","🤡":"1f921","👹":"1f479","👺":"1f47a","👻":"1f47b","👽":"1f47d","👾":"1f47e","🤖":"1f916","😺":"1f63a","😸":"1f638","😹":"1f639","😻":"1f63b","😼":"1f63c","😽":"1f63d","🙀":"1f640","😿":"1f63f","😾":"1f63e","🙈":"1f648","🙉":"1f649","🙊":"1f64a","💌":"1f48c","💘":"1f498","💝":"1f49d","💖":"1f496","💗":"1f497","💓":"1f493","💞":"1f49e","💕":"1f495","💟":"1f49f","❣":"2763","💔":"1f494","❤":"2764","🩷":"1fa77","🧡":"1f9e1","💛":"1f49b","💚":"1f49a","💙":"1f499","🩵":"1fa75","💜":"1f49c","🤎":"1f90e","🖤":"1f5a4","🩶":"1fa76","🤍":"1f90d","💋":"1f48b","💯":"1f4af","💢":"1f4a2","💥":"1f4a5","💫":"1f4ab","💦":"1f4a6","💨":"1f4a8","🕳":"1f573","💬":"1f4ac","🗨":"1f5e8","🗯":"1f5ef","💭":"1f4ad","💤":"1f4a4","👋":"1f44b","🤚":"1f91a","🖐":"1f590","✋":"270b","🖖":"1f596","🫱":"1faf1","🫲":"1faf2","🫳":"1faf3","🫴":"1faf4","🫷":"1faf7","🫸":"1faf8","👌":"1f44c","🤌":"1f90c","🤏":"1f90f","✌":"270c","🤞":"1f91e","🫰":"1faf0","🤟":"1f91f","🤘":"1f918","🤙":"1f919","👈":"1f448","👉":"1f449","👆":"1f446","🖕":"1f595","👇":"1f447","☝":"261d","🫵":"1faf5","👍":"1f44d","👎":"1f44e","✊":"270a","👊":"1f44a","🤛":"1f91b","🤜":"1f91c","👏":"1f44f","🙌":"1f64c","🫶":"1faf6","👐":"1f450","🤲":"1f932","🤝":"1f91d","🙏":"1f64f","✍":"270d","💅":"1f485","🤳":"1f933","💪":"1f4aa","🦾":"1f9be","🦿":"1f9bf","🦵":"1f9b5","🦶":"1f9b6","👂":"1f442","🦻":"1f9bb","👃":"1f443","🧠":"1f9e0","🫀":"1fac0","🫁":"1fac1","🦷":"1f9b7","🦴":"1f9b4","👀":"1f440","👁":"1f441","👅":"1f445","👄":"1f444","🫦":"1fae6","👶":"1f476","🧒":"1f9d2","👦":"1f466","👧":"1f467","🧑":"1f9d1","👱":"1f471","👨":"1f468","🧔":"1f9d4","👩":"1f469","🧓":"1f9d3","👴":"1f474","👵":"1f475","🙍":"1f64d","🙎":"1f64e","🙅":"1f645","🙆":"1f646","💁":"1f481","🙋":"1f64b","🧏":"1f9cf","🙇":"1f647","🤦":"1f926","🤷":"1f937","👮":"1f46e","🕵":"1f575","💂":"1f482","🥷":"1f977","👷":"1f477","🫅":"1fac5","🤴":"1f934","👸":"1f478","👳":"1f473","👲":"1f472","🧕":"1f9d5","🤵":"1f935","👰":"1f470","🤰":"1f930","🫃":"1fac3","🫄":"1fac4","🤱":"1f931","👼":"1f47c","🎅":"1f385","🤶":"1f936","🦸":"1f9b8","🦹":"1f9b9","🧙":"1f9d9","🧚":"1f9da","🧛":"1f9db","🧜":"1f9dc","🧝":"1f9dd","🧞":"1f9de","🧟":"1f9df","🧌":"1f9cc","💆":"1f486","💇":"1f487","🚶":"1f6b6","🧍":"1f9cd","🧎":"1f9ce","🏃":"1f3c3","💃":"1f483","🕺":"1f57a","🕴":"1f574","👯":"1f46f","🧖":"1f9d6","🧗":"1f9d7","🤺":"1f93a","🏇":"1f3c7","⛷":"26f7","🏂":"1f3c2","🏌":"1f3cc","🏄":"1f3c4","🚣":"1f6a3","🏊":"1f3ca","⛹":"26f9","🏋":"1f3cb","🚴":"1f6b4","🚵":"1f6b5","🤸":"1f938","🤼":"1f93c","🤽":"1f93d","🤾":"1f93e","🤹":"1f939","🧘":"1f9d8","🛀":"1f6c0","🛌":"1f6cc","👭":"1f46d","👫":"1f46b","👬":"1f46c","💏":"1f48f","💑":"1f491","🗣":"1f5e3","👤":"1f464","👥":"1f465","🫂":"1fac2","👪":"1f46a","👣":"1f463","🏻":"1f463","🏼":"1f463","🏽":"1f463","🏾":"1f463","🏿":"1f463","🦰":"1f463","🦱":"1f463","🦳":"1f463","🦲":"1f463","🐵":"1f435","🐒":"1f412","🦍":"1f98d","🦧":"1f9a7","🐶":"1f436","🐕":"1f415","🦮":"1f9ae","🐩":"1f429","🐺":"1f43a","🦊":"1f98a","🦝":"1f99d","🐱":"1f431","🐈":"1f408","🦁":"1f981","🐯":"1f42f","🐅":"1f405","🐆":"1f406","🐴":"1f434","🫎":"1face","🫏":"1facf","🐎":"1f40e","🦄":"1f984","🦓":"1f993","🦌":"1f98c","🦬":"1f9ac","🐮":"1f42e","🐂":"1f402","🐃":"1f403","🐄":"1f404","🐷":"1f437","🐖":"1f416","🐗":"1f417","🐽":"1f43d","🐏":"1f40f","🐑":"1f411","🐐":"1f410","🐪":"1f42a","🐫":"1f42b","🦙":"1f999","🦒":"1f992","🐘":"1f418","🦣":"1f9a3","🦏":"1f98f","🦛":"1f99b","🐭":"1f42d","🐁":"1f401","🐀":"1f400","🐹":"1f439","🐰":"1f430","🐇":"1f407","🐿":"1f43f","🦫":"1f9ab","🦔":"1f994","🦇":"1f987","🐻":"1f43b","🐨":"1f428","🐼":"1f43c","🦥":"1f9a5","🦦":"1f9a6","🦨":"1f9a8","🦘":"1f998","🦡":"1f9a1","🐾":"1f43e","🦃":"1f983","🐔":"1f414","🐓":"1f413","🐣":"1f423","🐤":"1f424","🐥":"1f425","🐦":"1f426","🐧":"1f427","🕊":"1f54a","🦅":"1f985","🦆":"1f986","🦢":"1f9a2","🦉":"1f989","🦤":"1f9a4","🪶":"1fab6","🦩":"1f9a9","🦚":"1f99a","🦜":"1f99c","🪽":"1fabd","🪿":"1fabf","🐸":"1f438","🐊":"1f40a","🐢":"1f422","🦎":"1f98e","🐍":"1f40d","🐲":"1f432","🐉":"1f409","🦕":"1f995","🦖":"1f996","🐳":"1f433","🐋":"1f40b","🐬":"1f42c","🦭":"1f9ad","🐟":"1f41f","🐠":"1f420","🐡":"1f421","🦈":"1f988","🐙":"1f419","🐚":"1f41a","🪸":"1fab8","🪼":"1fabc","🐌":"1f40c","🦋":"1f98b","🐛":"1f41b","🐜":"1f41c","🐝":"1f41d","🪲":"1fab2","🐞":"1f41e","🦗":"1f997","🪳":"1fab3","🕷":"1f577","🕸":"1f578","🦂":"1f982","🦟":"1f99f","🪰":"1fab0","🪱":"1fab1","🦠":"1f9a0","💐":"1f490","🌸":"1f338","💮":"1f4ae","🪷":"1fab7","🏵":"1f3f5","🌹":"1f339","🥀":"1f940","🌺":"1f33a","🌻":"1f33b","🌼":"1f33c","🌷":"1f337","🪻":"1fabb","🌱":"1f331","🪴":"1fab4","🌲":"1f332","🌳":"1f333","🌴":"1f334","🌵":"1f335","🌾":"1f33e","🌿":"1f33f","☘":"2618","🍀":"1f340","🍁":"1f341","🍂":"1f342","🍃":"1f343","🪹":"1fab9","🪺":"1faba","🍄":"1f344","🍇":"1f347","🍈":"1f348","🍉":"1f349","🍊":"1f34a","🍋":"1f34b","🍌":"1f34c","🍍":"1f34d","🥭":"1f96d","🍎":"1f34e","🍏":"1f34f","🍐":"1f350","🍑":"1f351","🍒":"1f352","🍓":"1f353","🫐":"1fad0","🥝":"1f95d","🍅":"1f345","🫒":"1fad2","🥥":"1f965","🥑":"1f951","🍆":"1f346","🥔":"1f954","🥕":"1f955","🌽":"1f33d","🌶":"1f336","🫑":"1fad1","🥒":"1f952","🥬":"1f96c","🥦":"1f966","🧄":"1f9c4","🧅":"1f9c5","🥜":"1f95c","🫘":"1fad8","🌰":"1f330","🫚":"1fada","🫛":"1fadb","🍞":"1f35e","🥐":"1f950","🥖":"1f956","🫓":"1fad3","🥨":"1f968","🥯":"1f96f","🥞":"1f95e","🧇":"1f9c7","🧀":"1f9c0","🍖":"1f356","🍗":"1f357","🥩":"1f969","🥓":"1f953","🍔":"1f354","🍟":"1f35f","🍕":"1f355","🌭":"1f32d","🥪":"1f96a","🌮":"1f32e","🌯":"1f32f","🫔":"1fad4","🥙":"1f959","🧆":"1f9c6","🥚":"1f95a","🍳":"1f373","🥘":"1f958","🍲":"1f372","🫕":"1fad5","🥣":"1f963","🥗":"1f957","🍿":"1f37f","🧈":"1f9c8","🧂":"1f9c2","🥫":"1f96b","🍱":"1f371","🍘":"1f358","🍙":"1f359","🍚":"1f35a","🍛":"1f35b","🍜":"1f35c","🍝":"1f35d","🍠":"1f360","🍢":"1f362","🍣":"1f363","🍤":"1f364","🍥":"1f365","🥮":"1f96e","🍡":"1f361","🥟":"1f95f","🥠":"1f960","🥡":"1f961","🦀":"1f980","🦞":"1f99e","🦐":"1f990","🦑":"1f991","🦪":"1f9aa","🍦":"1f366","🍧":"1f367","🍨":"1f368","🍩":"1f369","🍪":"1f36a","🎂":"1f382","🍰":"1f370","🧁":"1f9c1","🥧":"1f967","🍫":"1f36b","🍬":"1f36c","🍭":"1f36d","🍮":"1f36e","🍯":"1f36f","🍼":"1f37c","🥛":"1f95b","☕":"2615","🫖":"1fad6","🍵":"1f375","🍶":"1f376","🍾":"1f37e","🍷":"1f377","🍸":"1f378","🍹":"1f379","🍺":"1f37a","🍻":"1f37b","🥂":"1f942","🥃":"1f943","🫗":"1fad7","🥤":"1f964","🧋":"1f9cb","🧃":"1f9c3","🧉":"1f9c9","🧊":"1f9ca","🥢":"1f962","🍽":"1f37d","🍴":"1f374","🥄":"1f944","🔪":"1f52a","🫙":"1fad9","🏺":"1f3fa","🌍":"1f30d","🌎":"1f30e","🌏":"1f30f","🌐":"1f310","🗺":"1f5fa","🗾":"1f5fe","🧭":"1f9ed","🏔":"1f3d4","⛰":"26f0","🌋":"1f30b","🗻":"1f5fb","🏕":"1f3d5","🏖":"1f3d6","🏜":"1f3dc","🏝":"1f3dd","🏞":"1f3de","🏟":"1f3df","🏛":"1f3db","🏗":"1f3d7","🧱":"1f9f1","🪨":"1faa8","🪵":"1fab5","🛖":"1f6d6","🏘":"1f3d8","🏚":"1f3da","🏠":"1f3e0","🏡":"1f3e1","🏢":"1f3e2","🏣":"1f3e3","🏤":"1f3e4","🏥":"1f3e5","🏦":"1f3e6","🏨":"1f3e8","🏩":"1f3e9","🏪":"1f3ea","🏫":"1f3eb","🏬":"1f3ec","🏭":"1f3ed","🏯":"1f3ef","🏰":"1f3f0","💒":"1f492","🗼":"1f5fc","🗽":"1f5fd","⛪":"26ea","🕌":"1f54c","🛕":"1f6d5","🕍":"1f54d","⛩":"26e9","🕋":"1f54b","⛲":"26f2","⛺":"26fa","🌁":"1f301","🌃":"1f303","🏙":"1f3d9","🌄":"1f304","🌅":"1f305","🌆":"1f306","🌇":"1f307","🌉":"1f309","♨":"2668","🎠":"1f3a0","🛝":"1f6dd","🎡":"1f3a1","🎢":"1f3a2","💈":"1f488","🎪":"1f3aa","🚂":"1f682","🚃":"1f683","🚄":"1f684","🚅":"1f685","🚆":"1f686","🚇":"1f687","🚈":"1f688","🚉":"1f689","🚊":"1f68a","🚝":"1f69d","🚞":"1f69e","🚋":"1f68b","🚌":"1f68c","🚍":"1f68d","🚎":"1f68e","🚐":"1f690","🚑":"1f691","🚒":"1f692","🚓":"1f693","🚔":"1f694","🚕":"1f695","🚖":"1f696","🚗":"1f697","🚘":"1f698","🚙":"1f699","🛻":"1f6fb","🚚":"1f69a","🚛":"1f69b","🚜":"1f69c","🏎":"1f3ce","🏍":"1f3cd","🛵":"1f6f5","🦽":"1f9bd","🦼":"1f9bc","🛺":"1f6fa","🚲":"1f6b2","🛴":"1f6f4","🛹":"1f6f9","🛼":"1f6fc","🚏":"1f68f","🛣":"1f6e3","🛤":"1f6e4","🛢":"1f6e2","⛽":"26fd","🛞":"1f6de","🚨":"1f6a8","🚥":"1f6a5","🚦":"1f6a6","🛑":"1f6d1","🚧":"1f6a7","⚓":"2693","🛟":"1f6df","⛵":"26f5","🛶":"1f6f6","🚤":"1f6a4","🛳":"1f6f3","⛴":"26f4","🛥":"1f6e5","🚢":"1f6a2","✈":"2708","🛩":"1f6e9","🛫":"1f6eb","🛬":"1f6ec","🪂":"1fa82","💺":"1f4ba","🚁":"1f681","🚟":"1f69f","🚠":"1f6a0","🚡":"1f6a1","🛰":"1f6f0","🚀":"1f680","🛸":"1f6f8","🛎":"1f6ce","🧳":"1f9f3","⌛":"231b","⏳":"23f3","⌚":"231a","⏰":"23f0","⏱":"23f1","⏲":"23f2","🕰":"1f570","🕛":"1f55b","🕧":"1f567","🕐":"1f550","🕜":"1f55c","🕑":"1f551","🕝":"1f55d","🕒":"1f552","🕞":"1f55e","🕓":"1f553","🕟":"1f55f","🕔":"1f554","🕠":"1f560","🕕":"1f555","🕡":"1f561","🕖":"1f556","🕢":"1f562","🕗":"1f557","🕣":"1f563","🕘":"1f558","🕤":"1f564","🕙":"1f559","🕥":"1f565","🕚":"1f55a","🕦":"1f566","🌑":"1f311","🌒":"1f312","🌓":"1f313","🌔":"1f314","🌕":"1f315","🌖":"1f316","🌗":"1f317","🌘":"1f318","🌙":"1f319","🌚":"1f31a","🌛":"1f31b","🌜":"1f31c","🌡":"1f321","☀":"2600","🌝":"1f31d","🌞":"1f31e","🪐":"1fa90","⭐":"2b50","🌟":"1f31f","🌠":"1f320","🌌":"1f30c","☁":"2601","⛅":"26c5","⛈":"26c8","🌤":"1f324","🌥":"1f325","🌦":"1f326","🌧":"1f327","🌨":"1f328","🌩":"1f329","🌪":"1f32a","🌫":"1f32b","🌬":"1f32c","🌀":"1f300","🌈":"1f308","🌂":"1f302","☂":"2602","☔":"2614","⛱":"26f1","⚡":"26a1","❄":"2744","☃":"2603","⛄":"26c4","☄":"2604","🔥":"1f525","💧":"1f4a7","🌊":"1f30a","🎃":"1f383","🎄":"1f384","🎆":"1f386","🎇":"1f387","🧨":"1f9e8","✨":"2728","🎈":"1f388","🎉":"1f389","🎊":"1f38a","🎋":"1f38b","🎍":"1f38d","🎎":"1f38e","🎏":"1f38f","🎐":"1f390","🎑":"1f391","🧧":"1f9e7","🎀":"1f380","🎁":"1f381","🎗":"1f397","🎟":"1f39f","🎫":"1f3ab","🎖":"1f396","🏆":"1f3c6","🏅":"1f3c5","🥇":"1f947","🥈":"1f948","🥉":"1f949","⚽":"26bd","⚾":"26be","🥎":"1f94e","🏀":"1f3c0","🏐":"1f3d0","🏈":"1f3c8","🏉":"1f3c9","🎾":"1f3be","🥏":"1f94f","🎳":"1f3b3","🏏":"1f3cf","🏑":"1f3d1","🏒":"1f3d2","🥍":"1f94d","🏓":"1f3d3","🏸":"1f3f8","🥊":"1f94a","🥋":"1f94b","🥅":"1f945","⛳":"26f3","⛸":"26f8","🎣":"1f3a3","🤿":"1f93f","🎽":"1f3bd","🎿":"1f3bf","🛷":"1f6f7","🥌":"1f94c","🎯":"1f3af","🪀":"1fa80","🪁":"1fa81","🔫":"1f52b","🎱":"1f3b1","🔮":"1f52e","🪄":"1fa84","🎮":"1f3ae","🕹":"1f579","🎰":"1f3b0","🎲":"1f3b2","🧩":"1f9e9","🧸":"1f9f8","🪅":"1fa85","🪩":"1faa9","🪆":"1fa86","♠":"2660","♥":"2665","♦":"2666","♣":"2663","♟":"265f","🃏":"1f0cf","🀄":"1f004","🎴":"1f3b4","🎭":"1f3ad","🖼":"1f5bc","🎨":"1f3a8","🧵":"1f9f5","🪡":"1faa1","🧶":"1f9f6","🪢":"1faa2","👓":"1f453","🕶":"1f576","🥽":"1f97d","🥼":"1f97c","🦺":"1f9ba","👔":"1f454","👕":"1f455","👖":"1f456","🧣":"1f9e3","🧤":"1f9e4","🧥":"1f9e5","🧦":"1f9e6","👗":"1f457","👘":"1f458","🥻":"1f97b","🩱":"1fa71","🩲":"1fa72","🩳":"1fa73","👙":"1f459","👚":"1f45a","🪭":"1faad","👛":"1f45b","👜":"1f45c","👝":"1f45d","🛍":"1f6cd","🎒":"1f392","🩴":"1fa74","👞":"1f45e","👟":"1f45f","🥾":"1f97e","🥿":"1f97f","👠":"1f460","👡":"1f461","🩰":"1fa70","👢":"1f462","🪮":"1faae","👑":"1f451","👒":"1f452","🎩":"1f3a9","🎓":"1f393","🧢":"1f9e2","🪖":"1fa96","⛑":"26d1","📿":"1f4ff","💄":"1f484","💍":"1f48d","💎":"1f48e","🔇":"1f507","🔈":"1f508","🔉":"1f509","🔊":"1f50a","📢":"1f4e2","📣":"1f4e3","📯":"1f4ef","🔔":"1f514","🔕":"1f515","🎼":"1f3bc","🎵":"1f3b5","🎶":"1f3b6","🎙":"1f399","🎚":"1f39a","🎛":"1f39b","🎤":"1f3a4","🎧":"1f3a7","📻":"1f4fb","🎷":"1f3b7","🪗":"1fa97","🎸":"1f3b8","🎹":"1f3b9","🎺":"1f3ba","🎻":"1f3bb","🪕":"1fa95","🥁":"1f941","🪘":"1fa98","🪇":"1fa87","🪈":"1fa88","📱":"1f4f1","📲":"1f4f2","☎":"260e","📞":"1f4de","📟":"1f4df","📠":"1f4e0","🔋":"1f50b","🪫":"1faab","🔌":"1f50c","💻":"1f4bb","🖥":"1f5a5","🖨":"1f5a8","⌨":"2328","🖱":"1f5b1","🖲":"1f5b2","💽":"1f4bd","💾":"1f4be","💿":"1f4bf","📀":"1f4c0","🧮":"1f9ee","🎥":"1f3a5","🎞":"1f39e","📽":"1f4fd","🎬":"1f3ac","📺":"1f4fa","📷":"1f4f7","📸":"1f4f8","📹":"1f4f9","📼":"1f4fc","🔍":"1f50d","🔎":"1f50e","🕯":"1f56f","💡":"1f4a1","🔦":"1f526","🏮":"1f3ee","🪔":"1fa94","📔":"1f4d4","📕":"1f4d5","📖":"1f4d6","📗":"1f4d7","📘":"1f4d8","📙":"1f4d9","📚":"1f4da","📓":"1f4d3","📒":"1f4d2","📃":"1f4c3","📜":"1f4dc","📄":"1f4c4","📰":"1f4f0","🗞":"1f5de","📑":"1f4d1","🔖":"1f516","🏷":"1f3f7","💰":"1f4b0","🪙":"1fa99","💴":"1f4b4","💵":"1f4b5","💶":"1f4b6","💷":"1f4b7","💸":"1f4b8","💳":"1f4b3","🧾":"1f9fe","💹":"1f4b9","✉":"2709","📧":"1f4e7","📨":"1f4e8","📩":"1f4e9","📤":"1f4e4","📥":"1f4e5","📦":"1f4e6","📫":"1f4eb","📪":"1f4ea","📬":"1f4ec","📭":"1f4ed","📮":"1f4ee","🗳":"1f5f3","✏":"270f","✒":"2712","🖋":"1f58b","🖊":"1f58a","🖌":"1f58c","🖍":"1f58d","📝":"1f4dd","💼":"1f4bc","📁":"1f4c1","📂":"1f4c2","🗂":"1f5c2","📅":"1f4c5","📆":"1f4c6","🗒":"1f5d2","🗓":"1f5d3","📇":"1f4c7","📈":"1f4c8","📉":"1f4c9","📊":"1f4ca","📋":"1f4cb","📌":"1f4cc","📍":"1f4cd","📎":"1f4ce","🖇":"1f587","📏":"1f4cf","📐":"1f4d0","✂":"2702","🗃":"1f5c3","🗄":"1f5c4","🗑":"1f5d1","🔒":"1f512","🔓":"1f513","🔏":"1f50f","🔐":"1f510","🔑":"1f511","🗝":"1f5dd","🔨":"1f528","🪓":"1fa93","⛏":"26cf","⚒":"2692","🛠":"1f6e0","🗡":"1f5e1","⚔":"2694","💣":"1f4a3","🪃":"1fa83","🏹":"1f3f9","🛡":"1f6e1","🪚":"1fa9a","🔧":"1f527","🪛":"1fa9b","🔩":"1f529","⚙":"2699","🗜":"1f5dc","⚖":"2696","🦯":"1f9af","🔗":"1f517","⛓":"26d3","🪝":"1fa9d","🧰":"1f9f0","🧲":"1f9f2","🪜":"1fa9c","⚗":"2697","🧪":"1f9ea","🧫":"1f9eb","🧬":"1f9ec","🔬":"1f52c","🔭":"1f52d","📡":"1f4e1","💉":"1f489","🩸":"1fa78","💊":"1f48a","🩹":"1fa79","🩼":"1fa7c","🩺":"1fa7a","🩻":"1fa7b","🚪":"1f6aa","🛗":"1f6d7","🪞":"1fa9e","🪟":"1fa9f","🛏":"1f6cf","🛋":"1f6cb","🪑":"1fa91","🚽":"1f6bd","🪠":"1faa0","🚿":"1f6bf","🛁":"1f6c1","🪤":"1faa4","🪒":"1fa92","🧴":"1f9f4","🧷":"1f9f7","🧹":"1f9f9","🧺":"1f9fa","🧻":"1f9fb","🪣":"1faa3","🧼":"1f9fc","🫧":"1fae7","🪥":"1faa5","🧽":"1f9fd","🧯":"1f9ef","🛒":"1f6d2","🚬":"1f6ac","⚰":"26b0","🪦":"1faa6","⚱":"26b1","🧿":"1f9ff","🪬":"1faac","🗿":"1f5ff","🪧":"1faa7","🪪":"1faaa","🏧":"1f3e7","🚮":"1f6ae","🚰":"1f6b0","♿":"267f","🚹":"1f6b9","🚺":"1f6ba","🚻":"1f6bb","🚼":"1f6bc","🚾":"1f6be","🛂":"1f6c2","🛃":"1f6c3","🛄":"1f6c4","🛅":"1f6c5","⚠":"26a0","🚸":"1f6b8","⛔":"26d4","🚫":"1f6ab","🚳":"1f6b3","🚭":"1f6ad","🚯":"1f6af","🚱":"1f6b1","🚷":"1f6b7","📵":"1f4f5","🔞":"1f51e","☢":"2622","☣":"2623","⬆":"2b06","↗":"2197","➡":"27a1","↘":"2198","⬇":"2b07","↙":"2199","⬅":"2b05","↖":"2196","↕":"2195","↔":"2194","↩":"21a9","↪":"21aa","⤴":"2934","⤵":"2935","🔃":"1f503","🔄":"1f504","🔙":"1f519","🔚":"1f51a","🔛":"1f51b","🔜":"1f51c","🔝":"1f51d","🛐":"1f6d0","⚛":"269b","🕉":"1f549","✡":"2721","☸":"2638","☯":"262f","✝":"271d","☦":"2626","☪":"262a","☮":"262e","🕎":"1f54e","🔯":"1f52f","🪯":"1faaf","♈":"2648","♉":"2649","♊":"264a","♋":"264b","♌":"264c","♍":"264d","♎":"264e","♏":"264f","♐":"2650","♑":"2651","♒":"2652","♓":"2653","⛎":"26ce","🔀":"1f500","🔁":"1f501","🔂":"1f502","▶":"25b6","⏩":"23e9","⏭":"23ed","⏯":"23ef","◀":"25c0","⏪":"23ea","⏮":"23ee","🔼":"1f53c","⏫":"23eb","🔽":"1f53d","⏬":"23ec","⏸":"23f8","⏹":"23f9","⏺":"23fa","⏏":"23cf","🎦":"1f3a6","🔅":"1f505","🔆":"1f506","📶":"1f4f6","🛜":"1f6dc","📳":"1f4f3","📴":"1f4f4","♀":"2640","♂":"2642","⚧":"26a7","✖":"2716","➕":"2795","➖":"2796","➗":"2797","🟰":"1f7f0","♾":"267e","‼":"203c","⁉":"2049","❓":"2753","❔":"2754","❕":"2755","❗":"2757","〰":"3030","💱":"1f4b1","💲":"1f4b2","⚕":"2695","♻":"267b","⚜":"269c","🔱":"1f531","📛":"1f4db","🔰":"1f530","⭕":"2b55","✅":"2705","☑":"2611","✔":"2714","❌":"274c","❎":"274e","➰":"27b0","➿":"27bf","〽":"303d","✳":"2733","✴":"2734","❇":"2747","©":"a9","®":"ae","™":"2122","🔟":"1f51f","🔠":"1f520","🔡":"1f521","🔢":"1f522","🔣":"1f523","🔤":"1f524","🅰":"1f170","🆎":"1f18e","🅱":"1f171","🆑":"1f191","🆒":"1f192","🆓":"1f193","ℹ":"2139","🆔":"1f194","Ⓜ":"24c2","🆕":"1f195","🆖":"1f196","🅾":"1f17e","🆗":"1f197","🅿":"1f17f","🆘":"1f198","🆙":"1f199","🆚":"1f19a","🈁":"1f201","🈂":"1f202","🈷":"1f237","🈶":"1f236","🈯":"1f22f","🉐":"1f250","🈹":"1f239","🈚":"1f21a","🈲":"1f232","🉑":"1f251","🈸":"1f238","🈴":"1f234","🈳":"1f233","㊗":"3297","㊙":"3299","🈺":"1f23a","🈵":"1f235","🔴":"1f534","🟠":"1f7e0","🟡":"1f7e1","🟢":"1f7e2","🔵":"1f535","🟣":"1f7e3","🟤":"1f7e4","⚫":"26ab","⚪":"26aa","🟥":"1f7e5","🟧":"1f7e7","🟨":"1f7e8","🟩":"1f7e9","🟦":"1f7e6","🟪":"1f7ea","🟫":"1f7eb","⬛":"2b1b","⬜":"2b1c","◼":"25fc","◻":"25fb","◾":"25fe","◽":"25fd","▪":"25aa","▫":"25ab","🔶":"1f536","🔷":"1f537","🔸":"1f538","🔹":"1f539","🔺":"1f53a","🔻":"1f53b","💠":"1f4a0","🔘":"1f518","🔳":"1f533","🔲":"1f532","🏁":"1f3c1","🚩":"1f6a9","🎌":"1f38c","🏴":"1f3f4","🏳":"1f3f3","☺️":"263a","☹️":"2639","☠️":"2620","❣️":"2763","❤️":"2764","🕳️":"1f573","🗨️":"1f5e8","🗯️":"1f5ef","👋🏻":"1f44b-1f3fb","👋🏼":"1f44b-1f3fc","👋🏽":"1f44b-1f3fd","👋🏾":"1f44b-1f3fe","👋🏿":"1f44b-1f3ff","🤚🏻":"1f91a-1f3fb","🤚🏼":"1f91a-1f3fc","🤚🏽":"1f91a-1f3fd","🤚🏾":"1f91a-1f3fe","🤚🏿":"1f91a-1f3ff","🖐️":"1f590","🖐🏻":"1f590-1f3fb","🖐🏼":"1f590-1f3fc","🖐🏽":"1f590-1f3fd","🖐🏾":"1f590-1f3fe","🖐🏿":"1f590-1f3ff","✋🏻":"270b-1f3fb","✋🏼":"270b-1f3fc","✋🏽":"270b-1f3fd","✋🏾":"270b-1f3fe","✋🏿":"270b-1f3ff","🖖🏻":"1f596-1f3fb","🖖🏼":"1f596-1f3fc","🖖🏽":"1f596-1f3fd","🖖🏾":"1f596-1f3fe","🖖🏿":"1f596-1f3ff","🫱🏻":"1faf1-1f3fb","🫱🏼":"1faf1-1f3fc","🫱🏽":"1faf1-1f3fd","🫱🏾":"1faf1-1f3fe","🫱🏿":"1faf1-1f3ff","🫲🏻":"1faf2-1f3fb","🫲🏼":"1faf2-1f3fc","🫲🏽":"1faf2-1f3fd","🫲🏾":"1faf2-1f3fe","🫲🏿":"1faf2-1f3ff","🫳🏻":"1faf3-1f3fb","🫳🏼":"1faf3-1f3fc","🫳🏽":"1faf3-1f3fd","🫳🏾":"1faf3-1f3fe","🫳🏿":"1faf3-1f3ff","🫴🏻":"1faf4-1f3fb","🫴🏼":"1faf4-1f3fc","🫴🏽":"1faf4-1f3fd","🫴🏾":"1faf4-1f3fe","🫴🏿":"1faf4-1f3ff","🫷🏻":"1faf7-1f3fb","🫷🏼":"1faf7-1f3fc","🫷🏽":"1faf7-1f3fd","🫷🏾":"1faf7-1f3fe","🫷🏿":"1faf7-1f3ff","🫸🏻":"1faf8-1f3fb","🫸🏼":"1faf8-1f3fc","🫸🏽":"1faf8-1f3fd","🫸🏾":"1faf8-1f3fe","🫸🏿":"1faf8-1f3ff","👌🏻":"1f44c-1f3fb","👌🏼":"1f44c-1f3fc","👌🏽":"1f44c-1f3fd","👌🏾":"1f44c-1f3fe","👌🏿":"1f44c-1f3ff","🤌🏻":"1f90c-1f3fb","🤌🏼":"1f90c-1f3fc","🤌🏽":"1f90c-1f3fd","🤌🏾":"1f90c-1f3fe","🤌🏿":"1f90c-1f3ff","🤏🏻":"1f90f-1f3fb","🤏🏼":"1f90f-1f3fc","🤏🏽":"1f90f-1f3fd","🤏🏾":"1f90f-1f3fe","🤏🏿":"1f90f-1f3ff","✌️":"270c","✌🏻":"270c-1f3fb","✌🏼":"270c-1f3fc","✌🏽":"270c-1f3fd","✌🏾":"270c-1f3fe","✌🏿":"270c-1f3ff","🤞🏻":"1f91e-1f3fb","🤞🏼":"1f91e-1f3fc","🤞🏽":"1f91e-1f3fd","🤞🏾":"1f91e-1f3fe","🤞🏿":"1f91e-1f3ff","🫰🏻":"1faf0-1f3fb","🫰🏼":"1faf0-1f3fc","🫰🏽":"1faf0-1f3fd","🫰🏾":"1faf0-1f3fe","🫰🏿":"1faf0-1f3ff","🤟🏻":"1f91f-1f3fb","🤟🏼":"1f91f-1f3fc","🤟🏽":"1f91f-1f3fd","🤟🏾":"1f91f-1f3fe","🤟🏿":"1f91f-1f3ff","🤘🏻":"1f918-1f3fb","🤘🏼":"1f918-1f3fc","🤘🏽":"1f918-1f3fd","🤘🏾":"1f918-1f3fe","🤘🏿":"1f918-1f3ff","🤙🏻":"1f919-1f3fb","🤙🏼":"1f919-1f3fc","🤙🏽":"1f919-1f3fd","🤙🏾":"1f919-1f3fe","🤙🏿":"1f919-1f3ff","👈🏻":"1f448-1f3fb","👈🏼":"1f448-1f3fc","👈🏽":"1f448-1f3fd","👈🏾":"1f448-1f3fe","👈🏿":"1f448-1f3ff","👉🏻":"1f449-1f3fb","👉🏼":"1f449-1f3fc","👉🏽":"1f449-1f3fd","👉🏾":"1f449-1f3fe","👉🏿":"1f449-1f3ff","👆🏻":"1f446-1f3fb","👆🏼":"1f446-1f3fc","👆🏽":"1f446-1f3fd","👆🏾":"1f446-1f3fe","👆🏿":"1f446-1f3ff","🖕🏻":"1f595-1f3fb","🖕🏼":"1f595-1f3fc","🖕🏽":"1f595-1f3fd","🖕🏾":"1f595-1f3fe","🖕🏿":"1f595-1f3ff","👇🏻":"1f447-1f3fb","👇🏼":"1f447-1f3fc","👇🏽":"1f447-1f3fd","👇🏾":"1f447-1f3fe","👇🏿":"1f447-1f3ff","☝️":"261d","☝🏻":"261d-1f3fb","☝🏼":"261d-1f3fc","☝🏽":"261d-1f3fd","☝🏾":"261d-1f3fe","☝🏿":"261d-1f3ff","🫵🏻":"1faf5-1f3fb","🫵🏼":"1faf5-1f3fc","🫵🏽":"1faf5-1f3fd","🫵🏾":"1faf5-1f3fe","🫵🏿":"1faf5-1f3ff","👍🏻":"1f44d-1f3fb","👍🏼":"1f44d-1f3fc","👍🏽":"1f44d-1f3fd","👍🏾":"1f44d-1f3fe","👍🏿":"1f44d-1f3ff","👎🏻":"1f44e-1f3fb","👎🏼":"1f44e-1f3fc","👎🏽":"1f44e-1f3fd","👎🏾":"1f44e-1f3fe","👎🏿":"1f44e-1f3ff","✊🏻":"270a-1f3fb","✊🏼":"270a-1f3fc","✊🏽":"270a-1f3fd","✊🏾":"270a-1f3fe","✊🏿":"270a-1f3ff","👊🏻":"1f44a-1f3fb","👊🏼":"1f44a-1f3fc","👊🏽":"1f44a-1f3fd","👊🏾":"1f44a-1f3fe","👊🏿":"1f44a-1f3ff","🤛🏻":"1f91b-1f3fb","🤛🏼":"1f91b-1f3fc","🤛🏽":"1f91b-1f3fd","🤛🏾":"1f91b-1f3fe","🤛🏿":"1f91b-1f3ff","🤜🏻":"1f91c-1f3fb","🤜🏼":"1f91c-1f3fc","🤜🏽":"1f91c-1f3fd","🤜🏾":"1f91c-1f3fe","🤜🏿":"1f91c-1f3ff","👏🏻":"1f44f-1f3fb","👏🏼":"1f44f-1f3fc","👏🏽":"1f44f-1f3fd","👏🏾":"1f44f-1f3fe","👏🏿":"1f44f-1f3ff","🙌🏻":"1f64c-1f3fb","🙌🏼":"1f64c-1f3fc","🙌🏽":"1f64c-1f3fd","🙌🏾":"1f64c-1f3fe","🙌🏿":"1f64c-1f3ff","🫶🏻":"1faf6-1f3fb","🫶🏼":"1faf6-1f3fc","🫶🏽":"1faf6-1f3fd","🫶🏾":"1faf6-1f3fe","🫶🏿":"1faf6-1f3ff","👐🏻":"1f450-1f3fb","👐🏼":"1f450-1f3fc","👐🏽":"1f450-1f3fd","👐🏾":"1f450-1f3fe","👐🏿":"1f450-1f3ff","🤲🏻":"1f932-1f3fb","🤲🏼":"1f932-1f3fc","🤲🏽":"1f932-1f3fd","🤲🏾":"1f932-1f3fe","🤲🏿":"1f932-1f3ff","🤝🏻":"1f91d-1f3fb","🤝🏼":"1f91d-1f3fc","🤝🏽":"1f91d-1f3fd","🤝🏾":"1f91d-1f3fe","🤝🏿":"1f91d-1f3ff","🙏🏻":"1f64f-1f3fb","🙏🏼":"1f64f-1f3fc","🙏🏽":"1f64f-1f3fd","🙏🏾":"1f64f-1f3fe","🙏🏿":"1f64f-1f3ff","✍️":"270d","✍🏻":"270d-1f3fb","✍🏼":"270d-1f3fc","✍🏽":"270d-1f3fd","✍🏾":"270d-1f3fe","✍🏿":"270d-1f3ff","💅🏻":"1f485-1f3fb","💅🏼":"1f485-1f3fc","💅🏽":"1f485-1f3fd","💅🏾":"1f485-1f3fe","💅🏿":"1f485-1f3ff","🤳🏻":"1f933-1f3fb","🤳🏼":"1f933-1f3fc","🤳🏽":"1f933-1f3fd","🤳🏾":"1f933-1f3fe","🤳🏿":"1f933-1f3ff","💪🏻":"1f4aa-1f3fb","💪🏼":"1f4aa-1f3fc","💪🏽":"1f4aa-1f3fd","💪🏾":"1f4aa-1f3fe","💪🏿":"1f4aa-1f3ff","🦵🏻":"1f9b5-1f3fb","🦵🏼":"1f9b5-1f3fc","🦵🏽":"1f9b5-1f3fd","🦵🏾":"1f9b5-1f3fe","🦵🏿":"1f9b5-1f3ff","🦶🏻":"1f9b6-1f3fb","🦶🏼":"1f9b6-1f3fc","🦶🏽":"1f9b6-1f3fd","🦶🏾":"1f9b6-1f3fe","🦶🏿":"1f9b6-1f3ff","👂🏻":"1f442-1f3fb","👂🏼":"1f442-1f3fc","👂🏽":"1f442-1f3fd","👂🏾":"1f442-1f3fe","👂🏿":"1f442-1f3ff","🦻🏻":"1f9bb-1f3fb","🦻🏼":"1f9bb-1f3fc","🦻🏽":"1f9bb-1f3fd","🦻🏾":"1f9bb-1f3fe","🦻🏿":"1f9bb-1f3ff","👃🏻":"1f443-1f3fb","👃🏼":"1f443-1f3fc","👃🏽":"1f443-1f3fd","👃🏾":"1f443-1f3fe","👃🏿":"1f443-1f3ff","👁️":"1f441","👶🏻":"1f476-1f3fb","👶🏼":"1f476-1f3fc","👶🏽":"1f476-1f3fd","👶🏾":"1f476-1f3fe","👶🏿":"1f476-1f3ff","🧒🏻":"1f9d2-1f3fb","🧒🏼":"1f9d2-1f3fc","🧒🏽":"1f9d2-1f3fd","🧒🏾":"1f9d2-1f3fe","🧒🏿":"1f9d2-1f3ff","👦🏻":"1f466-1f3fb","👦🏼":"1f466-1f3fc","👦🏽":"1f466-1f3fd","👦🏾":"1f466-1f3fe","👦🏿":"1f466-1f3ff","👧🏻":"1f467-1f3fb","👧🏼":"1f467-1f3fc","👧🏽":"1f467-1f3fd","👧🏾":"1f467-1f3fe","👧🏿":"1f467-1f3ff","🧑🏻":"1f9d1-1f3fb","🧑🏼":"1f9d1-1f3fc","🧑🏽":"1f9d1-1f3fd","🧑🏾":"1f9d1-1f3fe","🧑🏿":"1f9d1-1f3ff","👱🏻":"1f471-1f3fb","👱🏼":"1f471-1f3fc","👱🏽":"1f471-1f3fd","👱🏾":"1f471-1f3fe","👱🏿":"1f471-1f3ff","👨🏻":"1f468-1f3fb","👨🏼":"1f468-1f3fc","👨🏽":"1f468-1f3fd","👨🏾":"1f468-1f3fe","👨🏿":"1f468-1f3ff","🧔🏻":"1f9d4-1f3fb","🧔🏼":"1f9d4-1f3fc","🧔🏽":"1f9d4-1f3fd","🧔🏾":"1f9d4-1f3fe","🧔🏿":"1f9d4-1f3ff","👩🏻":"1f469-1f3fb","👩🏼":"1f469-1f3fc","👩🏽":"1f469-1f3fd","👩🏾":"1f469-1f3fe","👩🏿":"1f469-1f3ff","🧓🏻":"1f9d3-1f3fb","🧓🏼":"1f9d3-1f3fc","🧓🏽":"1f9d3-1f3fd","🧓🏾":"1f9d3-1f3fe","🧓🏿":"1f9d3-1f3ff","👴🏻":"1f474-1f3fb","👴🏼":"1f474-1f3fc","👴🏽":"1f474-1f3fd","👴🏾":"1f474-1f3fe","👴🏿":"1f474-1f3ff","👵🏻":"1f475-1f3fb","👵🏼":"1f475-1f3fc","👵🏽":"1f475-1f3fd","👵🏾":"1f475-1f3fe","👵🏿":"1f475-1f3ff","🙍🏻":"1f64d-1f3fb","🙍🏼":"1f64d-1f3fc","🙍🏽":"1f64d-1f3fd","🙍🏾":"1f64d-1f3fe","🙍🏿":"1f64d-1f3ff","🙎🏻":"1f64e-1f3fb","🙎🏼":"1f64e-1f3fc","🙎🏽":"1f64e-1f3fd","🙎🏾":"1f64e-1f3fe","🙎🏿":"1f64e-1f3ff","🙅🏻":"1f645-1f3fb","🙅🏼":"1f645-1f3fc","🙅🏽":"1f645-1f3fd","🙅🏾":"1f645-1f3fe","🙅🏿":"1f645-1f3ff","🙆🏻":"1f646-1f3fb","🙆🏼":"1f646-1f3fc","🙆🏽":"1f646-1f3fd","🙆🏾":"1f646-1f3fe","🙆🏿":"1f646-1f3ff","💁🏻":"1f481-1f3fb","💁🏼":"1f481-1f3fc","💁🏽":"1f481-1f3fd","💁🏾":"1f481-1f3fe","💁🏿":"1f481-1f3ff","🙋🏻":"1f64b-1f3fb","🙋🏼":"1f64b-1f3fc","🙋🏽":"1f64b-1f3fd","🙋🏾":"1f64b-1f3fe","🙋🏿":"1f64b-1f3ff","🧏🏻":"1f9cf-1f3fb","🧏🏼":"1f9cf-1f3fc","🧏🏽":"1f9cf-1f3fd","🧏🏾":"1f9cf-1f3fe","🧏🏿":"1f9cf-1f3ff","🙇🏻":"1f647-1f3fb","🙇🏼":"1f647-1f3fc","🙇🏽":"1f647-1f3fd","🙇🏾":"1f647-1f3fe","🙇🏿":"1f647-1f3ff","🤦🏻":"1f926-1f3fb","🤦🏼":"1f926-1f3fc","🤦🏽":"1f926-1f3fd","🤦🏾":"1f926-1f3fe","🤦🏿":"1f926-1f3ff","🤷🏻":"1f937-1f3fb","🤷🏼":"1f937-1f3fc","🤷🏽":"1f937-1f3fd","🤷🏾":"1f937-1f3fe","🤷🏿":"1f937-1f3ff","👮🏻":"1f46e-1f3fb","👮🏼":"1f46e-1f3fc","👮🏽":"1f46e-1f3fd","👮🏾":"1f46e-1f3fe","👮🏿":"1f46e-1f3ff","🕵️":"1f575","🕵🏻":"1f575-1f3fb","🕵🏼":"1f575-1f3fc","🕵🏽":"1f575-1f3fd","🕵🏾":"1f575-1f3fe","🕵🏿":"1f575-1f3ff","💂🏻":"1f482-1f3fb","💂🏼":"1f482-1f3fc","💂🏽":"1f482-1f3fd","💂🏾":"1f482-1f3fe","💂🏿":"1f482-1f3ff","🥷🏻":"1f977-1f3fb","🥷🏼":"1f977-1f3fc","🥷🏽":"1f977-1f3fd","🥷🏾":"1f977-1f3fe","🥷🏿":"1f977-1f3ff","👷🏻":"1f477-1f3fb","👷🏼":"1f477-1f3fc","👷🏽":"1f477-1f3fd","👷🏾":"1f477-1f3fe","👷🏿":"1f477-1f3ff","🫅🏻":"1fac5-1f3fb","🫅🏼":"1fac5-1f3fc","🫅🏽":"1fac5-1f3fd","🫅🏾":"1fac5-1f3fe","🫅🏿":"1fac5-1f3ff","🤴🏻":"1f934-1f3fb","🤴🏼":"1f934-1f3fc","🤴🏽":"1f934-1f3fd","🤴🏾":"1f934-1f3fe","🤴🏿":"1f934-1f3ff","👸🏻":"1f478-1f3fb","👸🏼":"1f478-1f3fc","👸🏽":"1f478-1f3fd","👸🏾":"1f478-1f3fe","👸🏿":"1f478-1f3ff","👳🏻":"1f473-1f3fb","👳🏼":"1f473-1f3fc","👳🏽":"1f473-1f3fd","👳🏾":"1f473-1f3fe","👳🏿":"1f473-1f3ff","👲🏻":"1f472-1f3fb","👲🏼":"1f472-1f3fc","👲🏽":"1f472-1f3fd","👲🏾":"1f472-1f3fe","👲🏿":"1f472-1f3ff","🧕🏻":"1f9d5-1f3fb","🧕🏼":"1f9d5-1f3fc","🧕🏽":"1f9d5-1f3fd","🧕🏾":"1f9d5-1f3fe","🧕🏿":"1f9d5-1f3ff","🤵🏻":"1f935-1f3fb","🤵🏼":"1f935-1f3fc","🤵🏽":"1f935-1f3fd","🤵🏾":"1f935-1f3fe","🤵🏿":"1f935-1f3ff","👰🏻":"1f470-1f3fb","👰🏼":"1f470-1f3fc","👰🏽":"1f470-1f3fd","👰🏾":"1f470-1f3fe","👰🏿":"1f470-1f3ff","🤰🏻":"1f930-1f3fb","🤰🏼":"1f930-1f3fc","🤰🏽":"1f930-1f3fd","🤰🏾":"1f930-1f3fe","🤰🏿":"1f930-1f3ff","🫃🏻":"1fac3-1f3fb","🫃🏼":"1fac3-1f3fc","🫃🏽":"1fac3-1f3fd","🫃🏾":"1fac3-1f3fe","🫃🏿":"1fac3-1f3ff","🫄🏻":"1fac4-1f3fb","🫄🏼":"1fac4-1f3fc","🫄🏽":"1fac4-1f3fd","🫄🏾":"1fac4-1f3fe","🫄🏿":"1fac4-1f3ff","🤱🏻":"1f931-1f3fb","🤱🏼":"1f931-1f3fc","🤱🏽":"1f931-1f3fd","🤱🏾":"1f931-1f3fe","🤱🏿":"1f931-1f3ff","👼🏻":"1f47c-1f3fb","👼🏼":"1f47c-1f3fc","👼🏽":"1f47c-1f3fd","👼🏾":"1f47c-1f3fe","👼🏿":"1f47c-1f3ff","🎅🏻":"1f385-1f3fb","🎅🏼":"1f385-1f3fc","🎅🏽":"1f385-1f3fd","🎅🏾":"1f385-1f3fe","🎅🏿":"1f385-1f3ff","🤶🏻":"1f936-1f3fb","🤶🏼":"1f936-1f3fc","🤶🏽":"1f936-1f3fd","🤶🏾":"1f936-1f3fe","🤶🏿":"1f936-1f3ff","🦸🏻":"1f9b8-1f3fb","🦸🏼":"1f9b8-1f3fc","🦸🏽":"1f9b8-1f3fd","🦸🏾":"1f9b8-1f3fe","🦸🏿":"1f9b8-1f3ff","🦹🏻":"1f9b9-1f3fb","🦹🏼":"1f9b9-1f3fc","🦹🏽":"1f9b9-1f3fd","🦹🏾":"1f9b9-1f3fe","🦹🏿":"1f9b9-1f3ff","🧙🏻":"1f9d9-1f3fb","🧙🏼":"1f9d9-1f3fc","🧙🏽":"1f9d9-1f3fd","🧙🏾":"1f9d9-1f3fe","🧙🏿":"1f9d9-1f3ff","🧚🏻":"1f9da-1f3fb","🧚🏼":"1f9da-1f3fc","🧚🏽":"1f9da-1f3fd","🧚🏾":"1f9da-1f3fe","🧚🏿":"1f9da-1f3ff","🧛🏻":"1f9db-1f3fb","🧛🏼":"1f9db-1f3fc","🧛🏽":"1f9db-1f3fd","🧛🏾":"1f9db-1f3fe","🧛🏿":"1f9db-1f3ff","🧜🏻":"1f9dc-1f3fb","🧜🏼":"1f9dc-1f3fc","🧜🏽":"1f9dc-1f3fd","🧜🏾":"1f9dc-1f3fe","🧜🏿":"1f9dc-1f3ff","🧝🏻":"1f9dd-1f3fb","🧝🏼":"1f9dd-1f3fc","🧝🏽":"1f9dd-1f3fd","🧝🏾":"1f9dd-1f3fe","🧝🏿":"1f9dd-1f3ff","💆🏻":"1f486-1f3fb","💆🏼":"1f486-1f3fc","💆🏽":"1f486-1f3fd","💆🏾":"1f486-1f3fe","💆🏿":"1f486-1f3ff","💇🏻":"1f487-1f3fb","💇🏼":"1f487-1f3fc","💇🏽":"1f487-1f3fd","💇🏾":"1f487-1f3fe","💇🏿":"1f487-1f3ff","🚶🏻":"1f6b6-1f3fb","🚶🏼":"1f6b6-1f3fc","🚶🏽":"1f6b6-1f3fd","🚶🏾":"1f6b6-1f3fe","🚶🏿":"1f6b6-1f3ff","🧍🏻":"1f9cd-1f3fb","🧍🏼":"1f9cd-1f3fc","🧍🏽":"1f9cd-1f3fd","🧍🏾":"1f9cd-1f3fe","🧍🏿":"1f9cd-1f3ff","🧎🏻":"1f9ce-1f3fb","🧎🏼":"1f9ce-1f3fc","🧎🏽":"1f9ce-1f3fd","🧎🏾":"1f9ce-1f3fe","🧎🏿":"1f9ce-1f3ff","🏃🏻":"1f3c3-1f3fb","🏃🏼":"1f3c3-1f3fc","🏃🏽":"1f3c3-1f3fd","🏃🏾":"1f3c3-1f3fe","🏃🏿":"1f3c3-1f3ff","💃🏻":"1f483-1f3fb","💃🏼":"1f483-1f3fc","💃🏽":"1f483-1f3fd","💃🏾":"1f483-1f3fe","💃🏿":"1f483-1f3ff","🕺🏻":"1f57a-1f3fb","🕺🏼":"1f57a-1f3fc","🕺🏽":"1f57a-1f3fd","🕺🏾":"1f57a-1f3fe","🕺🏿":"1f57a-1f3ff","🕴️":"1f574","🕴🏻":"1f574-1f3fb","🕴🏼":"1f574-1f3fc","🕴🏽":"1f574-1f3fd","🕴🏾":"1f574-1f3fe","🕴🏿":"1f574-1f3ff","🧖🏻":"1f9d6-1f3fb","🧖🏼":"1f9d6-1f3fc","🧖🏽":"1f9d6-1f3fd","🧖🏾":"1f9d6-1f3fe","🧖🏿":"1f9d6-1f3ff","🧗🏻":"1f9d7-1f3fb","🧗🏼":"1f9d7-1f3fc","🧗🏽":"1f9d7-1f3fd","🧗🏾":"1f9d7-1f3fe","🧗🏿":"1f9d7-1f3ff","🏇🏻":"1f3c7-1f3fb","🏇🏼":"1f3c7-1f3fc","🏇🏽":"1f3c7-1f3fd","🏇🏾":"1f3c7-1f3fe","🏇🏿":"1f3c7-1f3ff","⛷️":"26f7","🏂🏻":"1f3c2-1f3fb","🏂🏼":"1f3c2-1f3fc","🏂🏽":"1f3c2-1f3fd","🏂🏾":"1f3c2-1f3fe","🏂🏿":"1f3c2-1f3ff","🏌️":"1f3cc","🏌🏻":"1f3cc-1f3fb","🏌🏼":"1f3cc-1f3fc","🏌🏽":"1f3cc-1f3fd","🏌🏾":"1f3cc-1f3fe","🏌🏿":"1f3cc-1f3ff","🏄🏻":"1f3c4-1f3fb","🏄🏼":"1f3c4-1f3fc","🏄🏽":"1f3c4-1f3fd","🏄🏾":"1f3c4-1f3fe","🏄🏿":"1f3c4-1f3ff","🚣🏻":"1f6a3-1f3fb","🚣🏼":"1f6a3-1f3fc","🚣🏽":"1f6a3-1f3fd","🚣🏾":"1f6a3-1f3fe","🚣🏿":"1f6a3-1f3ff","🏊🏻":"1f3ca-1f3fb","🏊🏼":"1f3ca-1f3fc","🏊🏽":"1f3ca-1f3fd","🏊🏾":"1f3ca-1f3fe","🏊🏿":"1f3ca-1f3ff","⛹️":"26f9","⛹🏻":"26f9-1f3fb","⛹🏼":"26f9-1f3fc","⛹🏽":"26f9-1f3fd","⛹🏾":"26f9-1f3fe","⛹🏿":"26f9-1f3ff","🏋️":"1f3cb","🏋🏻":"1f3cb-1f3fb","🏋🏼":"1f3cb-1f3fc","🏋🏽":"1f3cb-1f3fd","🏋🏾":"1f3cb-1f3fe","🏋🏿":"1f3cb-1f3ff","🚴🏻":"1f6b4-1f3fb","🚴🏼":"1f6b4-1f3fc","🚴🏽":"1f6b4-1f3fd","🚴🏾":"1f6b4-1f3fe","🚴🏿":"1f6b4-1f3ff","🚵🏻":"1f6b5-1f3fb","🚵🏼":"1f6b5-1f3fc","🚵🏽":"1f6b5-1f3fd","🚵🏾":"1f6b5-1f3fe","🚵🏿":"1f6b5-1f3ff","🤸🏻":"1f938-1f3fb","🤸🏼":"1f938-1f3fc","🤸🏽":"1f938-1f3fd","🤸🏾":"1f938-1f3fe","🤸🏿":"1f938-1f3ff","🤽🏻":"1f93d-1f3fb","🤽🏼":"1f93d-1f3fc","🤽🏽":"1f93d-1f3fd","🤽🏾":"1f93d-1f3fe","🤽🏿":"1f93d-1f3ff","🤾🏻":"1f93e-1f3fb","🤾🏼":"1f93e-1f3fc","🤾🏽":"1f93e-1f3fd","🤾🏾":"1f93e-1f3fe","🤾🏿":"1f93e-1f3ff","🤹🏻":"1f939-1f3fb","🤹🏼":"1f939-1f3fc","🤹🏽":"1f939-1f3fd","🤹🏾":"1f939-1f3fe","🤹🏿":"1f939-1f3ff","🧘🏻":"1f9d8-1f3fb","🧘🏼":"1f9d8-1f3fc","🧘🏽":"1f9d8-1f3fd","🧘🏾":"1f9d8-1f3fe","🧘🏿":"1f9d8-1f3ff","🛀🏻":"1f6c0-1f3fb","🛀🏼":"1f6c0-1f3fc","🛀🏽":"1f6c0-1f3fd","🛀🏾":"1f6c0-1f3fe","🛀🏿":"1f6c0-1f3ff","🛌🏻":"1f6cc-1f3fb","🛌🏼":"1f6cc-1f3fc","🛌🏽":"1f6cc-1f3fd","🛌🏾":"1f6cc-1f3fe","🛌🏿":"1f6cc-1f3ff","👭🏻":"1f46d-1f3fb","👭🏼":"1f46d-1f3fc","👭🏽":"1f46d-1f3fd","👭🏾":"1f46d-1f3fe","👭🏿":"1f46d-1f3ff","👫🏻":"1f46b-1f3fb","👫🏼":"1f46b-1f3fc","👫🏽":"1f46b-1f3fd","👫🏾":"1f46b-1f3fe","👫🏿":"1f46b-1f3ff","👬🏻":"1f46c-1f3fb","👬🏼":"1f46c-1f3fc","👬🏽":"1f46c-1f3fd","👬🏾":"1f46c-1f3fe","👬🏿":"1f46c-1f3ff","💏🏻":"1f48f-1f3fb","💏🏼":"1f48f-1f3fc","💏🏽":"1f48f-1f3fd","💏🏾":"1f48f-1f3fe","💏🏿":"1f48f-1f3ff","💑🏻":"1f491-1f3fb","💑🏼":"1f491-1f3fc","💑🏽":"1f491-1f3fd","💑🏾":"1f491-1f3fe","💑🏿":"1f491-1f3ff","🗣️":"1f5e3","🐿️":"1f43f","🕊️":"1f54a","🕷️":"1f577","🕸️":"1f578","🏵️":"1f3f5","☘️":"2618","🌶️":"1f336","🍽️":"1f37d","🗺️":"1f5fa","🏔️":"1f3d4","⛰️":"26f0","🏕️":"1f3d5","🏖️":"1f3d6","🏜️":"1f3dc","🏝️":"1f3dd","🏞️":"1f3de","🏟️":"1f3df","🏛️":"1f3db","🏗️":"1f3d7","🏘️":"1f3d8","🏚️":"1f3da","⛩️":"26e9","🏙️":"1f3d9","♨️":"2668","🏎️":"1f3ce","🏍️":"1f3cd","🛣️":"1f6e3","🛤️":"1f6e4","🛢️":"1f6e2","🛳️":"1f6f3","⛴️":"26f4","🛥️":"1f6e5","✈️":"2708","🛩️":"1f6e9","🛰️":"1f6f0","🛎️":"1f6ce","⏱️":"23f1","⏲️":"23f2","🕰️":"1f570","🌡️":"1f321","☀️":"2600","☁️":"2601","⛈️":"26c8","🌤️":"1f324","🌥️":"1f325","🌦️":"1f326","🌧️":"1f327","🌨️":"1f328","🌩️":"1f329","🌪️":"1f32a","🌫️":"1f32b","🌬️":"1f32c","☂️":"2602","⛱️":"26f1","❄️":"2744","☃️":"2603","☄️":"2604","🎗️":"1f397","🎟️":"1f39f","🎖️":"1f396","⛸️":"26f8","🕹️":"1f579","♠️":"2660","♥️":"2665","♦️":"2666","♣️":"2663","♟️":"265f","🖼️":"1f5bc","🕶️":"1f576","🛍️":"1f6cd","⛑️":"26d1","🎙️":"1f399","🎚️":"1f39a","🎛️":"1f39b","☎️":"260e","🖥️":"1f5a5","🖨️":"1f5a8","⌨️":"2328","🖱️":"1f5b1","🖲️":"1f5b2","🎞️":"1f39e","📽️":"1f4fd","🕯️":"1f56f","🗞️":"1f5de","🏷️":"1f3f7","✉️":"2709","🗳️":"1f5f3","✏️":"270f","✒️":"2712","🖋️":"1f58b","🖊️":"1f58a","🖌️":"1f58c","🖍️":"1f58d","🗂️":"1f5c2","🗒️":"1f5d2","🗓️":"1f5d3","🖇️":"1f587","✂️":"2702","🗃️":"1f5c3","🗄️":"1f5c4","🗑️":"1f5d1","🗝️":"1f5dd","⛏️":"26cf","⚒️":"2692","🛠️":"1f6e0","🗡️":"1f5e1","⚔️":"2694","🛡️":"1f6e1","⚙️":"2699","🗜️":"1f5dc","⚖️":"2696","⛓️":"26d3","⚗️":"2697","🛏️":"1f6cf","🛋️":"1f6cb","⚰️":"26b0","⚱️":"26b1","⚠️":"26a0","☢️":"2622","☣️":"2623","⬆️":"2b06","↗️":"2197","➡️":"27a1","↘️":"2198","⬇️":"2b07","↙️":"2199","⬅️":"2b05","↖️":"2196","↕️":"2195","↔️":"2194","↩️":"21a9","↪️":"21aa","⤴️":"2934","⤵️":"2935","⚛️":"269b","🕉️":"1f549","✡️":"2721","☸️":"2638","☯️":"262f","✝️":"271d","☦️":"2626","☪️":"262a","☮️":"262e","▶️":"25b6","⏭️":"23ed","⏯️":"23ef","◀️":"25c0","⏮️":"23ee","⏸️":"23f8","⏹️":"23f9","⏺️":"23fa","⏏️":"23cf","♀️":"2640","♂️":"2642","⚧️":"26a7","✖️":"2716","♾️":"267e","‼️":"203c","⁉️":"2049","〰️":"3030","⚕️":"2695","♻️":"267b","⚜️":"269c","☑️":"2611","✔️":"2714","〽️":"303d","✳️":"2733","✴️":"2734","❇️":"2747","©️":"a9","®️":"ae","™️":"2122","#⃣":"23-20e3","*⃣":"2a-20e3","0⃣":"30-20e3","1⃣":"31-20e3","2⃣":"32-20e3","3⃣":"33-20e3","4⃣":"34-20e3","5⃣":"35-20e3","6⃣":"36-20e3","7⃣":"37-20e3","8⃣":"38-20e3","9⃣":"39-20e3","🅰️":"1f170","🅱️":"1f171","ℹ️":"2139","Ⓜ️":"24c2","🅾️":"1f17e","🅿️":"1f17f","🈂️":"1f202","🈷️":"1f237","㊗️":"3297","㊙️":"3299","◼️":"25fc","◻️":"25fb","▪️":"25aa","▫️":"25ab","🏳️":"1f3f3","🇦🇨":"1f1e6-1f1e8","🇦🇩":"1f1e6-1f1e9","🇦🇪":"1f1e6-1f1ea","🇦🇫":"1f1e6-1f1eb","🇦🇬":"1f1e6-1f1ec","🇦🇮":"1f1e6-1f1ee","🇦🇱":"1f1e6-1f1f1","🇦🇲":"1f1e6-1f1f2","🇦🇴":"1f1e6-1f1f4","🇦🇶":"1f1e6-1f1f6","🇦🇷":"1f1e6-1f1f7","🇦🇸":"1f1e6-1f1f8","🇦🇹":"1f1e6-1f1f9","🇦🇺":"1f1e6-1f1fa","🇦🇼":"1f1e6-1f1fc","🇦🇽":"1f1e6-1f1fd","🇦🇿":"1f1e6-1f1ff","🇧🇦":"1f1e7-1f1e6","🇧🇧":"1f1e7-1f1e7","🇧🇩":"1f1e7-1f1e9","🇧🇪":"1f1e7-1f1ea","🇧🇫":"1f1e7-1f1eb","🇧🇬":"1f1e7-1f1ec","🇧🇭":"1f1e7-1f1ed","🇧🇮":"1f1e7-1f1ee","🇧🇯":"1f1e7-1f1ef","🇧🇱":"1f1e7-1f1f1","🇧🇲":"1f1e7-1f1f2","🇧🇳":"1f1e7-1f1f3","🇧🇴":"1f1e7-1f1f4","🇧🇶":"1f1e7-1f1f6","🇧🇷":"1f1e7-1f1f7","🇧🇸":"1f1e7-1f1f8","🇧🇹":"1f1e7-1f1f9","🇧🇻":"1f1e7-1f1fb","🇧🇼":"1f1e7-1f1fc","🇧🇾":"1f1e7-1f1fe","🇧🇿":"1f1e7-1f1ff","🇨🇦":"1f1e8-1f1e6","🇨🇨":"1f1e8-1f1e8","🇨🇩":"1f1e8-1f1e9","🇨🇫":"1f1e8-1f1eb","🇨🇬":"1f1e8-1f1ec","🇨🇭":"1f1e8-1f1ed","🇨🇮":"1f1e8-1f1ee","🇨🇰":"1f1e8-1f1f0","🇨🇱":"1f1e8-1f1f1","🇨🇲":"1f1e8-1f1f2","🇨🇳":"1f1e8-1f1f3","🇨🇴":"1f1e8-1f1f4","🇨🇵":"1f1e8-1f1f5","🇨🇷":"1f1e8-1f1f7","🇨🇺":"1f1e8-1f1fa","🇨🇻":"1f1e8-1f1fb","🇨🇼":"1f1e8-1f1fc","🇨🇽":"1f1e8-1f1fd","🇨🇾":"1f1e8-1f1fe","🇨🇿":"1f1e8-1f1ff","🇩🇪":"1f1e9-1f1ea","🇩🇬":"1f1e9-1f1ec","🇩🇯":"1f1e9-1f1ef","🇩🇰":"1f1e9-1f1f0","🇩🇲":"1f1e9-1f1f2","🇩🇴":"1f1e9-1f1f4","🇩🇿":"1f1e9-1f1ff","🇪🇦":"1f1ea-1f1e6","🇪🇨":"1f1ea-1f1e8","🇪🇪":"1f1ea-1f1ea","🇪🇬":"1f1ea-1f1ec","🇪🇭":"1f1ea-1f1ed","🇪🇷":"1f1ea-1f1f7","🇪🇸":"1f1ea-1f1f8","🇪🇹":"1f1ea-1f1f9","🇪🇺":"1f1ea-1f1fa","🇫🇮":"1f1eb-1f1ee","🇫🇯":"1f1eb-1f1ef","🇫🇰":"1f1eb-1f1f0","🇫🇲":"1f1eb-1f1f2","🇫🇴":"1f1eb-1f1f4","🇫🇷":"1f1eb-1f1f7","🇬🇦":"1f1ec-1f1e6","🇬🇧":"1f1ec-1f1e7","🇬🇩":"1f1ec-1f1e9","🇬🇪":"1f1ec-1f1ea","🇬🇫":"1f1ec-1f1eb","🇬🇬":"1f1ec-1f1ec","🇬🇭":"1f1ec-1f1ed","🇬🇮":"1f1ec-1f1ee","🇬🇱":"1f1ec-1f1f1","🇬🇲":"1f1ec-1f1f2","🇬🇳":"1f1ec-1f1f3","🇬🇵":"1f1ec-1f1f5","🇬🇶":"1f1ec-1f1f6","🇬🇷":"1f1ec-1f1f7","🇬🇸":"1f1ec-1f1f8","🇬🇹":"1f1ec-1f1f9","🇬🇺":"1f1ec-1f1fa","🇬🇼":"1f1ec-1f1fc","🇬🇾":"1f1ec-1f1fe","🇭🇰":"1f1ed-1f1f0","🇭🇲":"1f1ed-1f1f2","🇭🇳":"1f1ed-1f1f3","🇭🇷":"1f1ed-1f1f7","🇭🇹":"1f1ed-1f1f9","🇭🇺":"1f1ed-1f1fa","🇮🇨":"1f1ee-1f1e8","🇮🇩":"1f1ee-1f1e9","🇮🇪":"1f1ee-1f1ea","🇮🇱":"1f1ee-1f1f1","🇮🇲":"1f1ee-1f1f2","🇮🇳":"1f1ee-1f1f3","🇮🇴":"1f1ee-1f1f4","🇮🇶":"1f1ee-1f1f6","🇮🇷":"1f1ee-1f1f7","🇮🇸":"1f1ee-1f1f8","🇮🇹":"1f1ee-1f1f9","🇯🇪":"1f1ef-1f1ea","🇯🇲":"1f1ef-1f1f2","🇯🇴":"1f1ef-1f1f4","🇯🇵":"1f1ef-1f1f5","🇰🇪":"1f1f0-1f1ea","🇰🇬":"1f1f0-1f1ec","🇰🇭":"1f1f0-1f1ed","🇰🇮":"1f1f0-1f1ee","🇰🇲":"1f1f0-1f1f2","🇰🇳":"1f1f0-1f1f3","🇰🇵":"1f1f0-1f1f5","🇰🇷":"1f1f0-1f1f7","🇰🇼":"1f1f0-1f1fc","🇰🇾":"1f1f0-1f1fe","🇰🇿":"1f1f0-1f1ff","🇱🇦":"1f1f1-1f1e6","🇱🇧":"1f1f1-1f1e7","🇱🇨":"1f1f1-1f1e8","🇱🇮":"1f1f1-1f1ee","🇱🇰":"1f1f1-1f1f0","🇱🇷":"1f1f1-1f1f7","🇱🇸":"1f1f1-1f1f8","🇱🇹":"1f1f1-1f1f9","🇱🇺":"1f1f1-1f1fa","🇱🇻":"1f1f1-1f1fb","🇱🇾":"1f1f1-1f1fe","🇲🇦":"1f1f2-1f1e6","🇲🇨":"1f1f2-1f1e8","🇲🇩":"1f1f2-1f1e9","🇲🇪":"1f1f2-1f1ea","🇲🇫":"1f1f2-1f1eb","🇲🇬":"1f1f2-1f1ec","🇲🇭":"1f1f2-1f1ed","🇲🇰":"1f1f2-1f1f0","🇲🇱":"1f1f2-1f1f1","🇲🇲":"1f1f2-1f1f2","🇲🇳":"1f1f2-1f1f3","🇲🇴":"1f1f2-1f1f4","🇲🇵":"1f1f2-1f1f5","🇲🇶":"1f1f2-1f1f6","🇲🇷":"1f1f2-1f1f7","🇲🇸":"1f1f2-1f1f8","🇲🇹":"1f1f2-1f1f9","🇲🇺":"1f1f2-1f1fa","🇲🇻":"1f1f2-1f1fb","🇲🇼":"1f1f2-1f1fc","🇲🇽":"1f1f2-1f1fd","🇲🇾":"1f1f2-1f1fe","🇲🇿":"1f1f2-1f1ff","🇳🇦":"1f1f3-1f1e6","🇳🇨":"1f1f3-1f1e8","🇳🇪":"1f1f3-1f1ea","🇳🇫":"1f1f3-1f1eb","🇳🇬":"1f1f3-1f1ec","🇳🇮":"1f1f3-1f1ee","🇳🇱":"1f1f3-1f1f1","🇳🇴":"1f1f3-1f1f4","🇳🇵":"1f1f3-1f1f5","🇳🇷":"1f1f3-1f1f7","🇳🇺":"1f1f3-1f1fa","🇳🇿":"1f1f3-1f1ff","🇴🇲":"1f1f4-1f1f2","🇵🇦":"1f1f5-1f1e6","🇵🇪":"1f1f5-1f1ea","🇵🇫":"1f1f5-1f1eb","🇵🇬":"1f1f5-1f1ec","🇵🇭":"1f1f5-1f1ed","🇵🇰":"1f1f5-1f1f0","🇵🇱":"1f1f5-1f1f1","🇵🇲":"1f1f5-1f1f2","🇵🇳":"1f1f5-1f1f3","🇵🇷":"1f1f5-1f1f7","🇵🇸":"1f1f5-1f1f8","🇵🇹":"1f1f5-1f1f9","🇵🇼":"1f1f5-1f1fc","🇵🇾":"1f1f5-1f1fe","🇶🇦":"1f1f6-1f1e6","🇷🇪":"1f1f7-1f1ea","🇷🇴":"1f1f7-1f1f4","🇷🇸":"1f1f7-1f1f8","🇷🇺":"1f1f7-1f1fa","🇷🇼":"1f1f7-1f1fc","🇸🇦":"1f1f8-1f1e6","🇸🇧":"1f1f8-1f1e7","🇸🇨":"1f1f8-1f1e8","🇸🇩":"1f1f8-1f1e9","🇸🇪":"1f1f8-1f1ea","🇸🇬":"1f1f8-1f1ec","🇸🇭":"1f1f8-1f1ed","🇸🇮":"1f1f8-1f1ee","🇸🇯":"1f1f8-1f1ef","🇸🇰":"1f1f8-1f1f0","🇸🇱":"1f1f8-1f1f1","🇸🇲":"1f1f8-1f1f2","🇸🇳":"1f1f8-1f1f3","🇸🇴":"1f1f8-1f1f4","🇸🇷":"1f1f8-1f1f7","🇸🇸":"1f1f8-1f1f8","🇸🇹":"1f1f8-1f1f9","🇸🇻":"1f1f8-1f1fb","🇸🇽":"1f1f8-1f1fd","🇸🇾":"1f1f8-1f1fe","🇸🇿":"1f1f8-1f1ff","🇹🇦":"1f1f9-1f1e6","🇹🇨":"1f1f9-1f1e8","🇹🇩":"1f1f9-1f1e9","🇹🇫":"1f1f9-1f1eb","🇹🇬":"1f1f9-1f1ec","🇹🇭":"1f1f9-1f1ed","🇹🇯":"1f1f9-1f1ef","🇹🇰":"1f1f9-1f1f0","🇹🇱":"1f1f9-1f1f1","🇹🇲":"1f1f9-1f1f2","🇹🇳":"1f1f9-1f1f3","🇹🇴":"1f1f9-1f1f4","🇹🇷":"1f1f9-1f1f7","🇹🇹":"1f1f9-1f1f9","🇹🇻":"1f1f9-1f1fb","🇹🇼":"1f1f9-1f1fc","🇹🇿":"1f1f9-1f1ff","🇺🇦":"1f1fa-1f1e6","🇺🇬":"1f1fa-1f1ec","🇺🇲":"1f1fa-1f1f2","🇺🇳":"1f1fa-1f1f3","🇺🇸":"1f1fa-1f1f8","🇺🇾":"1f1fa-1f1fe","🇺🇿":"1f1fa-1f1ff","🇻🇦":"1f1fb-1f1e6","🇻🇨":"1f1fb-1f1e8","🇻🇪":"1f1fb-1f1ea","🇻🇬":"1f1fb-1f1ec","🇻🇮":"1f1fb-1f1ee","🇻🇳":"1f1fb-1f1f3","🇻🇺":"1f1fb-1f1fa","🇼🇫":"1f1fc-1f1eb","🇼🇸":"1f1fc-1f1f8","🇽🇰":"1f1fd-1f1f0","🇾🇪":"1f1fe-1f1ea","🇾🇹":"1f1fe-1f1f9","🇿🇦":"1f1ff-1f1e6","🇿🇲":"1f1ff-1f1f2","🇿🇼":"1f1ff-1f1fc","😶‍🌫":"1f636-200d-1f32b-fe0f","😮‍💨":"1f62e-200d-1f4a8","🙂‍↔":"1f642-200d-2194-fe0f","🙂‍↕":"1f642-200d-2195-fe0f","😵‍💫":"1f635-200d-1f4ab","❤‍🔥":"2764-fe0f-200d-1f525","❤‍🩹":"2764-fe0f-200d-1fa79","👁‍🗨":"1f441-200d-1f5e8","🧔‍♂":"1f9d4-200d-2642-fe0f","🧔‍♀":"1f9d4-200d-2640-fe0f","👨‍🦰":"1f468-200d-1f9b0","👨‍🦱":"1f468-200d-1f9b1","👨‍🦳":"1f468-200d-1f9b3","👨‍🦲":"1f468-200d-1f9b2","👩‍🦰":"1f469-200d-1f9b0","🧑‍🦰":"1f9d1-200d-1f9b0","👩‍🦱":"1f469-200d-1f9b1","🧑‍🦱":"1f9d1-200d-1f9b1","👩‍🦳":"1f469-200d-1f9b3","🧑‍🦳":"1f9d1-200d-1f9b3","👩‍🦲":"1f469-200d-1f9b2","🧑‍🦲":"1f9d1-200d-1f9b2","👱‍♀":"1f471-200d-2640-fe0f","👱‍♂":"1f471-200d-2642-fe0f","🙍‍♂":"1f64d-200d-2642-fe0f","🙍‍♀":"1f64d-200d-2640-fe0f","🙎‍♂":"1f64e-200d-2642-fe0f","🙎‍♀":"1f64e-200d-2640-fe0f","🙅‍♂":"1f645-200d-2642-fe0f","🙅‍♀":"1f645-200d-2640-fe0f","🙆‍♂":"1f646-200d-2642-fe0f","🙆‍♀":"1f646-200d-2640-fe0f","💁‍♂":"1f481-200d-2642-fe0f","💁‍♀":"1f481-200d-2640-fe0f","🙋‍♂":"1f64b-200d-2642-fe0f","🙋‍♀":"1f64b-200d-2640-fe0f","🧏‍♂":"1f9cf-200d-2642-fe0f","🧏‍♀":"1f9cf-200d-2640-fe0f","🙇‍♂":"1f647-200d-2642-fe0f","🙇‍♀":"1f647-200d-2640-fe0f","🤦‍♂":"1f926-200d-2642-fe0f","🤦‍♀":"1f926-200d-2640-fe0f","🤷‍♂":"1f937-200d-2642-fe0f","🤷‍♀":"1f937-200d-2640-fe0f","🧑‍⚕":"1f9d1-200d-2695-fe0f","👨‍⚕":"1f468-200d-2695-fe0f","👩‍⚕":"1f469-200d-2695-fe0f","🧑‍🎓":"1f9d1-200d-1f393","👨‍🎓":"1f468-200d-1f393","👩‍🎓":"1f469-200d-1f393","🧑‍🏫":"1f9d1-200d-1f3eb","👨‍🏫":"1f468-200d-1f3eb","👩‍🏫":"1f469-200d-1f3eb","🧑‍⚖":"1f9d1-200d-2696-fe0f","👨‍⚖":"1f468-200d-2696-fe0f","👩‍⚖":"1f469-200d-2696-fe0f","🧑‍🌾":"1f9d1-200d-1f33e","👨‍🌾":"1f468-200d-1f33e","👩‍🌾":"1f469-200d-1f33e","🧑‍🍳":"1f9d1-200d-1f373","👨‍🍳":"1f468-200d-1f373","👩‍🍳":"1f469-200d-1f373","🧑‍🔧":"1f9d1-200d-1f527","👨‍🔧":"1f468-200d-1f527","👩‍🔧":"1f469-200d-1f527","🧑‍🏭":"1f9d1-200d-1f3ed","👨‍🏭":"1f468-200d-1f3ed","👩‍🏭":"1f469-200d-1f3ed","🧑‍💼":"1f9d1-200d-1f4bc","👨‍💼":"1f468-200d-1f4bc","👩‍💼":"1f469-200d-1f4bc","🧑‍🔬":"1f9d1-200d-1f52c","👨‍🔬":"1f468-200d-1f52c","👩‍🔬":"1f469-200d-1f52c","🧑‍💻":"1f9d1-200d-1f4bb","👨‍💻":"1f468-200d-1f4bb","👩‍💻":"1f469-200d-1f4bb","🧑‍🎤":"1f9d1-200d-1f3a4","👨‍🎤":"1f468-200d-1f3a4","👩‍🎤":"1f469-200d-1f3a4","🧑‍🎨":"1f9d1-200d-1f3a8","👨‍🎨":"1f468-200d-1f3a8","👩‍🎨":"1f469-200d-1f3a8","🧑‍✈":"1f9d1-200d-2708-fe0f","👨‍✈":"1f468-200d-2708-fe0f","👩‍✈":"1f469-200d-2708-fe0f","🧑‍🚀":"1f9d1-200d-1f680","👨‍🚀":"1f468-200d-1f680","👩‍🚀":"1f469-200d-1f680","🧑‍🚒":"1f9d1-200d-1f692","👨‍🚒":"1f468-200d-1f692","👩‍🚒":"1f469-200d-1f692","👮‍♂":"1f46e-200d-2642-fe0f","👮‍♀":"1f46e-200d-2640-fe0f","🕵‍♂":"1f575-fe0f-200d-2642-fe0f","🕵‍♀":"1f575-fe0f-200d-2640-fe0f","💂‍♂":"1f482-200d-2642-fe0f","💂‍♀":"1f482-200d-2640-fe0f","👷‍♂":"1f477-200d-2642-fe0f","👷‍♀":"1f477-200d-2640-fe0f","👳‍♂":"1f473-200d-2642-fe0f","👳‍♀":"1f473-200d-2640-fe0f","🤵‍♂":"1f935-200d-2642-fe0f","🤵‍♀":"1f935-200d-2640-fe0f","👰‍♂":"1f470-200d-2642-fe0f","👰‍♀":"1f470-200d-2640-fe0f","👩‍🍼":"1f469-200d-1f37c","👨‍🍼":"1f468-200d-1f37c","🧑‍🍼":"1f9d1-200d-1f37c","🧑‍🎄":"1f9d1-200d-1f384","🦸‍♂":"1f9b8-200d-2642-fe0f","🦸‍♀":"1f9b8-200d-2640-fe0f","🦹‍♂":"1f9b9-200d-2642-fe0f","🦹‍♀":"1f9b9-200d-2640-fe0f","🧙‍♂":"1f9d9-200d-2642-fe0f","🧙‍♀":"1f9d9-200d-2640-fe0f","🧚‍♂":"1f9da-200d-2642-fe0f","🧚‍♀":"1f9da-200d-2640-fe0f","🧛‍♂":"1f9db-200d-2642-fe0f","🧛‍♀":"1f9db-200d-2640-fe0f","🧜‍♂":"1f9dc-200d-2642-fe0f","🧜‍♀":"1f9dc-200d-2640-fe0f","🧝‍♂":"1f9dd-200d-2642-fe0f","🧝‍♀":"1f9dd-200d-2640-fe0f","🧞‍♂":"1f9de-200d-2642-fe0f","🧞‍♀":"1f9de-200d-2640-fe0f","🧟‍♂":"1f9df-200d-2642-fe0f","🧟‍♀":"1f9df-200d-2640-fe0f","💆‍♂":"1f486-200d-2642-fe0f","💆‍♀":"1f486-200d-2640-fe0f","💇‍♂":"1f487-200d-2642-fe0f","💇‍♀":"1f487-200d-2640-fe0f","🚶‍♂":"1f6b6-200d-2642-fe0f","🚶‍♀":"1f6b6-200d-2640-fe0f","🚶‍➡":"1f6b6-200d-27a1-fe0f","🧍‍♂":"1f9cd-200d-2642-fe0f","🧍‍♀":"1f9cd-200d-2640-fe0f","🧎‍♂":"1f9ce-200d-2642-fe0f","🧎‍♀":"1f9ce-200d-2640-fe0f","🧎‍➡":"1f9ce-200d-27a1-fe0f","🧑‍🦯":"1f9d1-200d-1f9af","👨‍🦯":"1f468-200d-1f9af","👩‍🦯":"1f469-200d-1f9af","🧑‍🦼":"1f9d1-200d-1f9bc","👨‍🦼":"1f468-200d-1f9bc","👩‍🦼":"1f469-200d-1f9bc","🧑‍🦽":"1f9d1-200d-1f9bd","👨‍🦽":"1f468-200d-1f9bd","👩‍🦽":"1f469-200d-1f9bd","🏃‍♂":"1f3c3-200d-2642-fe0f","🏃‍♀":"1f3c3-200d-2640-fe0f","🏃‍➡":"1f3c3-200d-27a1-fe0f","👯‍♂":"1f46f-200d-2642-fe0f","👯‍♀":"1f46f-200d-2640-fe0f","🧖‍♂":"1f9d6-200d-2642-fe0f","🧖‍♀":"1f9d6-200d-2640-fe0f","🧗‍♂":"1f9d7-200d-2642-fe0f","🧗‍♀":"1f9d7-200d-2640-fe0f","🏌‍♂":"1f3cc-fe0f-200d-2642-fe0f","🏌‍♀":"1f3cc-fe0f-200d-2640-fe0f","🏄‍♂":"1f3c4-200d-2642-fe0f","🏄‍♀":"1f3c4-200d-2640-fe0f","🚣‍♂":"1f6a3-200d-2642-fe0f","🚣‍♀":"1f6a3-200d-2640-fe0f","🏊‍♂":"1f3ca-200d-2642-fe0f","🏊‍♀":"1f3ca-200d-2640-fe0f","⛹‍♂":"26f9-fe0f-200d-2642-fe0f","⛹‍♀":"26f9-fe0f-200d-2640-fe0f","🏋‍♂":"1f3cb-fe0f-200d-2642-fe0f","🏋‍♀":"1f3cb-fe0f-200d-2640-fe0f","🚴‍♂":"1f6b4-200d-2642-fe0f","🚴‍♀":"1f6b4-200d-2640-fe0f","🚵‍♂":"1f6b5-200d-2642-fe0f","🚵‍♀":"1f6b5-200d-2640-fe0f","🤸‍♂":"1f938-200d-2642-fe0f","🤸‍♀":"1f938-200d-2640-fe0f","🤼‍♂":"1f93c-200d-2642-fe0f","🤼‍♀":"1f93c-200d-2640-fe0f","🤽‍♂":"1f93d-200d-2642-fe0f","🤽‍♀":"1f93d-200d-2640-fe0f","🤾‍♂":"1f93e-200d-2642-fe0f","🤾‍♀":"1f93e-200d-2640-fe0f","🤹‍♂":"1f939-200d-2642-fe0f","🤹‍♀":"1f939-200d-2640-fe0f","🧘‍♂":"1f9d8-200d-2642-fe0f","🧘‍♀":"1f9d8-200d-2640-fe0f","👨‍👦":"1f468-200d-1f466","👨‍👧":"1f468-200d-1f467","👩‍👦":"1f469-200d-1f466","👩‍👧":"1f469-200d-1f467","🧑‍🧒":"1f9d1-200d-1f9d2","🐕‍🦺":"1f415-200d-1f9ba","🐈‍⬛":"1f408-200d-2b1b","🐻‍❄":"1f43b-200d-2744-fe0f","🐦‍⬛":"1f426-200d-2b1b","🐦‍🔥":"1f426-200d-1f525","🍋‍🟩":"1f34b-200d-1f7e9","🍄‍🟫":"1f344-200d-1f7eb","⛓‍💥":"26d3-fe0f-200d-1f4a5","#️⃣":"23-20e3","*️⃣":"2a-20e3","0️⃣":"30-20e3","1️⃣":"31-20e3","2️⃣":"32-20e3","3️⃣":"33-20e3","4️⃣":"34-20e3","5️⃣":"35-20e3","6️⃣":"36-20e3","7️⃣":"37-20e3","8️⃣":"38-20e3","9️⃣":"39-20e3","🏳‍🌈":"1f3f3-fe0f-200d-1f308","🏳‍⚧":"1f3f3-fe0f-200d-26a7-fe0f","🏴‍☠":"1f3f4-200d-2620-fe0f","😶‍🌫️":"1f636-200d-1f32b-fe0f","🙂‍↔️":"1f642-200d-2194-fe0f","🙂‍↕️":"1f642-200d-2195-fe0f","❤️‍🔥":"2764-fe0f-200d-1f525","❤️‍🩹":"2764-fe0f-200d-1fa79","👁‍🗨️":"1f441-200d-1f5e8","👁️‍🗨":"1f441-200d-1f5e8","🧔‍♂️":"1f9d4-200d-2642-fe0f","🧔🏻‍♂":"1f9d4-1f3fb-200d-2642-fe0f","🧔🏼‍♂":"1f9d4-1f3fc-200d-2642-fe0f","🧔🏽‍♂":"1f9d4-1f3fd-200d-2642-fe0f","🧔🏾‍♂":"1f9d4-1f3fe-200d-2642-fe0f","🧔🏿‍♂":"1f9d4-1f3ff-200d-2642-fe0f","🧔‍♀️":"1f9d4-200d-2640-fe0f","🧔🏻‍♀":"1f9d4-1f3fb-200d-2640-fe0f","🧔🏼‍♀":"1f9d4-1f3fc-200d-2640-fe0f","🧔🏽‍♀":"1f9d4-1f3fd-200d-2640-fe0f","🧔🏾‍♀":"1f9d4-1f3fe-200d-2640-fe0f","🧔🏿‍♀":"1f9d4-1f3ff-200d-2640-fe0f","👨🏻‍🦰":"1f468-1f3fb-200d-1f9b0","👨🏼‍🦰":"1f468-1f3fc-200d-1f9b0","👨🏽‍🦰":"1f468-1f3fd-200d-1f9b0","👨🏾‍🦰":"1f468-1f3fe-200d-1f9b0","👨🏿‍🦰":"1f468-1f3ff-200d-1f9b0","👨🏻‍🦱":"1f468-1f3fb-200d-1f9b1","👨🏼‍🦱":"1f468-1f3fc-200d-1f9b1","👨🏽‍🦱":"1f468-1f3fd-200d-1f9b1","👨🏾‍🦱":"1f468-1f3fe-200d-1f9b1","👨🏿‍🦱":"1f468-1f3ff-200d-1f9b1","👨🏻‍🦳":"1f468-1f3fb-200d-1f9b3","👨🏼‍🦳":"1f468-1f3fc-200d-1f9b3","👨🏽‍🦳":"1f468-1f3fd-200d-1f9b3","👨🏾‍🦳":"1f468-1f3fe-200d-1f9b3","👨🏿‍🦳":"1f468-1f3ff-200d-1f9b3","👨🏻‍🦲":"1f468-1f3fb-200d-1f9b2","👨🏼‍🦲":"1f468-1f3fc-200d-1f9b2","👨🏽‍🦲":"1f468-1f3fd-200d-1f9b2","👨🏾‍🦲":"1f468-1f3fe-200d-1f9b2","👨🏿‍🦲":"1f468-1f3ff-200d-1f9b2","👩🏻‍🦰":"1f469-1f3fb-200d-1f9b0","👩🏼‍🦰":"1f469-1f3fc-200d-1f9b0","👩🏽‍🦰":"1f469-1f3fd-200d-1f9b0","👩🏾‍🦰":"1f469-1f3fe-200d-1f9b0","👩🏿‍🦰":"1f469-1f3ff-200d-1f9b0","🧑🏻‍🦰":"1f9d1-1f3fb-200d-1f9b0","🧑🏼‍🦰":"1f9d1-1f3fc-200d-1f9b0","🧑🏽‍🦰":"1f9d1-1f3fd-200d-1f9b0","🧑🏾‍🦰":"1f9d1-1f3fe-200d-1f9b0","🧑🏿‍🦰":"1f9d1-1f3ff-200d-1f9b0","👩🏻‍🦱":"1f469-1f3fb-200d-1f9b1","👩🏼‍🦱":"1f469-1f3fc-200d-1f9b1","👩🏽‍🦱":"1f469-1f3fd-200d-1f9b1","👩🏾‍🦱":"1f469-1f3fe-200d-1f9b1","👩🏿‍🦱":"1f469-1f3ff-200d-1f9b1","🧑🏻‍🦱":"1f9d1-1f3fb-200d-1f9b1","🧑🏼‍🦱":"1f9d1-1f3fc-200d-1f9b1","🧑🏽‍🦱":"1f9d1-1f3fd-200d-1f9b1","🧑🏾‍🦱":"1f9d1-1f3fe-200d-1f9b1","🧑🏿‍🦱":"1f9d1-1f3ff-200d-1f9b1","👩🏻‍🦳":"1f469-1f3fb-200d-1f9b3","👩🏼‍🦳":"1f469-1f3fc-200d-1f9b3","👩🏽‍🦳":"1f469-1f3fd-200d-1f9b3","👩🏾‍🦳":"1f469-1f3fe-200d-1f9b3","👩🏿‍🦳":"1f469-1f3ff-200d-1f9b3","🧑🏻‍🦳":"1f9d1-1f3fb-200d-1f9b3","🧑🏼‍🦳":"1f9d1-1f3fc-200d-1f9b3","🧑🏽‍🦳":"1f9d1-1f3fd-200d-1f9b3","🧑🏾‍🦳":"1f9d1-1f3fe-200d-1f9b3","🧑🏿‍🦳":"1f9d1-1f3ff-200d-1f9b3","👩🏻‍🦲":"1f469-1f3fb-200d-1f9b2","👩🏼‍🦲":"1f469-1f3fc-200d-1f9b2","👩🏽‍🦲":"1f469-1f3fd-200d-1f9b2","👩🏾‍🦲":"1f469-1f3fe-200d-1f9b2","👩🏿‍🦲":"1f469-1f3ff-200d-1f9b2","🧑🏻‍🦲":"1f9d1-1f3fb-200d-1f9b2","🧑🏼‍🦲":"1f9d1-1f3fc-200d-1f9b2","🧑🏽‍🦲":"1f9d1-1f3fd-200d-1f9b2","🧑🏾‍🦲":"1f9d1-1f3fe-200d-1f9b2","🧑🏿‍🦲":"1f9d1-1f3ff-200d-1f9b2","👱‍♀️":"1f471-200d-2640-fe0f","👱🏻‍♀":"1f471-1f3fb-200d-2640-fe0f","👱🏼‍♀":"1f471-1f3fc-200d-2640-fe0f","👱🏽‍♀":"1f471-1f3fd-200d-2640-fe0f","👱🏾‍♀":"1f471-1f3fe-200d-2640-fe0f","👱🏿‍♀":"1f471-1f3ff-200d-2640-fe0f","👱‍♂️":"1f471-200d-2642-fe0f","👱🏻‍♂":"1f471-1f3fb-200d-2642-fe0f","👱🏼‍♂":"1f471-1f3fc-200d-2642-fe0f","👱🏽‍♂":"1f471-1f3fd-200d-2642-fe0f","👱🏾‍♂":"1f471-1f3fe-200d-2642-fe0f","👱🏿‍♂":"1f471-1f3ff-200d-2642-fe0f","🙍‍♂️":"1f64d-200d-2642-fe0f","🙍🏻‍♂":"1f64d-1f3fb-200d-2642-fe0f","🙍🏼‍♂":"1f64d-1f3fc-200d-2642-fe0f","🙍🏽‍♂":"1f64d-1f3fd-200d-2642-fe0f","🙍🏾‍♂":"1f64d-1f3fe-200d-2642-fe0f","🙍🏿‍♂":"1f64d-1f3ff-200d-2642-fe0f","🙍‍♀️":"1f64d-200d-2640-fe0f","🙍🏻‍♀":"1f64d-1f3fb-200d-2640-fe0f","🙍🏼‍♀":"1f64d-1f3fc-200d-2640-fe0f","🙍🏽‍♀":"1f64d-1f3fd-200d-2640-fe0f","🙍🏾‍♀":"1f64d-1f3fe-200d-2640-fe0f","🙍🏿‍♀":"1f64d-1f3ff-200d-2640-fe0f","🙎‍♂️":"1f64e-200d-2642-fe0f","🙎🏻‍♂":"1f64e-1f3fb-200d-2642-fe0f","🙎🏼‍♂":"1f64e-1f3fc-200d-2642-fe0f","🙎🏽‍♂":"1f64e-1f3fd-200d-2642-fe0f","🙎🏾‍♂":"1f64e-1f3fe-200d-2642-fe0f","🙎🏿‍♂":"1f64e-1f3ff-200d-2642-fe0f","🙎‍♀️":"1f64e-200d-2640-fe0f","🙎🏻‍♀":"1f64e-1f3fb-200d-2640-fe0f","🙎🏼‍♀":"1f64e-1f3fc-200d-2640-fe0f","🙎🏽‍♀":"1f64e-1f3fd-200d-2640-fe0f","🙎🏾‍♀":"1f64e-1f3fe-200d-2640-fe0f","🙎🏿‍♀":"1f64e-1f3ff-200d-2640-fe0f","🙅‍♂️":"1f645-200d-2642-fe0f","🙅🏻‍♂":"1f645-1f3fb-200d-2642-fe0f","🙅🏼‍♂":"1f645-1f3fc-200d-2642-fe0f","🙅🏽‍♂":"1f645-1f3fd-200d-2642-fe0f","🙅🏾‍♂":"1f645-1f3fe-200d-2642-fe0f","🙅🏿‍♂":"1f645-1f3ff-200d-2642-fe0f","🙅‍♀️":"1f645-200d-2640-fe0f","🙅🏻‍♀":"1f645-1f3fb-200d-2640-fe0f","🙅🏼‍♀":"1f645-1f3fc-200d-2640-fe0f","🙅🏽‍♀":"1f645-1f3fd-200d-2640-fe0f","🙅🏾‍♀":"1f645-1f3fe-200d-2640-fe0f","🙅🏿‍♀":"1f645-1f3ff-200d-2640-fe0f","🙆‍♂️":"1f646-200d-2642-fe0f","🙆🏻‍♂":"1f646-1f3fb-200d-2642-fe0f","🙆🏼‍♂":"1f646-1f3fc-200d-2642-fe0f","🙆🏽‍♂":"1f646-1f3fd-200d-2642-fe0f","🙆🏾‍♂":"1f646-1f3fe-200d-2642-fe0f","🙆🏿‍♂":"1f646-1f3ff-200d-2642-fe0f","🙆‍♀️":"1f646-200d-2640-fe0f","🙆🏻‍♀":"1f646-1f3fb-200d-2640-fe0f","🙆🏼‍♀":"1f646-1f3fc-200d-2640-fe0f","🙆🏽‍♀":"1f646-1f3fd-200d-2640-fe0f","🙆🏾‍♀":"1f646-1f3fe-200d-2640-fe0f","🙆🏿‍♀":"1f646-1f3ff-200d-2640-fe0f","💁‍♂️":"1f481-200d-2642-fe0f","💁🏻‍♂":"1f481-1f3fb-200d-2642-fe0f","💁🏼‍♂":"1f481-1f3fc-200d-2642-fe0f","💁🏽‍♂":"1f481-1f3fd-200d-2642-fe0f","💁🏾‍♂":"1f481-1f3fe-200d-2642-fe0f","💁🏿‍♂":"1f481-1f3ff-200d-2642-fe0f","💁‍♀️":"1f481-200d-2640-fe0f","💁🏻‍♀":"1f481-1f3fb-200d-2640-fe0f","💁🏼‍♀":"1f481-1f3fc-200d-2640-fe0f","💁🏽‍♀":"1f481-1f3fd-200d-2640-fe0f","💁🏾‍♀":"1f481-1f3fe-200d-2640-fe0f","💁🏿‍♀":"1f481-1f3ff-200d-2640-fe0f","🙋‍♂️":"1f64b-200d-2642-fe0f","🙋🏻‍♂":"1f64b-1f3fb-200d-2642-fe0f","🙋🏼‍♂":"1f64b-1f3fc-200d-2642-fe0f","🙋🏽‍♂":"1f64b-1f3fd-200d-2642-fe0f","🙋🏾‍♂":"1f64b-1f3fe-200d-2642-fe0f","🙋🏿‍♂":"1f64b-1f3ff-200d-2642-fe0f","🙋‍♀️":"1f64b-200d-2640-fe0f","🙋🏻‍♀":"1f64b-1f3fb-200d-2640-fe0f","🙋🏼‍♀":"1f64b-1f3fc-200d-2640-fe0f","🙋🏽‍♀":"1f64b-1f3fd-200d-2640-fe0f","🙋🏾‍♀":"1f64b-1f3fe-200d-2640-fe0f","🙋🏿‍♀":"1f64b-1f3ff-200d-2640-fe0f","🧏‍♂️":"1f9cf-200d-2642-fe0f","🧏🏻‍♂":"1f9cf-1f3fb-200d-2642-fe0f","🧏🏼‍♂":"1f9cf-1f3fc-200d-2642-fe0f","🧏🏽‍♂":"1f9cf-1f3fd-200d-2642-fe0f","🧏🏾‍♂":"1f9cf-1f3fe-200d-2642-fe0f","🧏🏿‍♂":"1f9cf-1f3ff-200d-2642-fe0f","🧏‍♀️":"1f9cf-200d-2640-fe0f","🧏🏻‍♀":"1f9cf-1f3fb-200d-2640-fe0f","🧏🏼‍♀":"1f9cf-1f3fc-200d-2640-fe0f","🧏🏽‍♀":"1f9cf-1f3fd-200d-2640-fe0f","🧏🏾‍♀":"1f9cf-1f3fe-200d-2640-fe0f","🧏🏿‍♀":"1f9cf-1f3ff-200d-2640-fe0f","🙇‍♂️":"1f647-200d-2642-fe0f","🙇🏻‍♂":"1f647-1f3fb-200d-2642-fe0f","🙇🏼‍♂":"1f647-1f3fc-200d-2642-fe0f","🙇🏽‍♂":"1f647-1f3fd-200d-2642-fe0f","🙇🏾‍♂":"1f647-1f3fe-200d-2642-fe0f","🙇🏿‍♂":"1f647-1f3ff-200d-2642-fe0f","🙇‍♀️":"1f647-200d-2640-fe0f","🙇🏻‍♀":"1f647-1f3fb-200d-2640-fe0f","🙇🏼‍♀":"1f647-1f3fc-200d-2640-fe0f","🙇🏽‍♀":"1f647-1f3fd-200d-2640-fe0f","🙇🏾‍♀":"1f647-1f3fe-200d-2640-fe0f","🙇🏿‍♀":"1f647-1f3ff-200d-2640-fe0f","🤦‍♂️":"1f926-200d-2642-fe0f","🤦🏻‍♂":"1f926-1f3fb-200d-2642-fe0f","🤦🏼‍♂":"1f926-1f3fc-200d-2642-fe0f","🤦🏽‍♂":"1f926-1f3fd-200d-2642-fe0f","🤦🏾‍♂":"1f926-1f3fe-200d-2642-fe0f","🤦🏿‍♂":"1f926-1f3ff-200d-2642-fe0f","🤦‍♀️":"1f926-200d-2640-fe0f","🤦🏻‍♀":"1f926-1f3fb-200d-2640-fe0f","🤦🏼‍♀":"1f926-1f3fc-200d-2640-fe0f","🤦🏽‍♀":"1f926-1f3fd-200d-2640-fe0f","🤦🏾‍♀":"1f926-1f3fe-200d-2640-fe0f","🤦🏿‍♀":"1f926-1f3ff-200d-2640-fe0f","🤷‍♂️":"1f937-200d-2642-fe0f","🤷🏻‍♂":"1f937-1f3fb-200d-2642-fe0f","🤷🏼‍♂":"1f937-1f3fc-200d-2642-fe0f","🤷🏽‍♂":"1f937-1f3fd-200d-2642-fe0f","🤷🏾‍♂":"1f937-1f3fe-200d-2642-fe0f","🤷🏿‍♂":"1f937-1f3ff-200d-2642-fe0f","🤷‍♀️":"1f937-200d-2640-fe0f","🤷🏻‍♀":"1f937-1f3fb-200d-2640-fe0f","🤷🏼‍♀":"1f937-1f3fc-200d-2640-fe0f","🤷🏽‍♀":"1f937-1f3fd-200d-2640-fe0f","🤷🏾‍♀":"1f937-1f3fe-200d-2640-fe0f","🤷🏿‍♀":"1f937-1f3ff-200d-2640-fe0f","🧑‍⚕️":"1f9d1-200d-2695-fe0f","🧑🏻‍⚕":"1f9d1-1f3fb-200d-2695-fe0f","🧑🏼‍⚕":"1f9d1-1f3fc-200d-2695-fe0f","🧑🏽‍⚕":"1f9d1-1f3fd-200d-2695-fe0f","🧑🏾‍⚕":"1f9d1-1f3fe-200d-2695-fe0f","🧑🏿‍⚕":"1f9d1-1f3ff-200d-2695-fe0f","👨‍⚕️":"1f468-200d-2695-fe0f","👨🏻‍⚕":"1f468-1f3fb-200d-2695-fe0f","👨🏼‍⚕":"1f468-1f3fc-200d-2695-fe0f","👨🏽‍⚕":"1f468-1f3fd-200d-2695-fe0f","👨🏾‍⚕":"1f468-1f3fe-200d-2695-fe0f","👨🏿‍⚕":"1f468-1f3ff-200d-2695-fe0f","👩‍⚕️":"1f469-200d-2695-fe0f","👩🏻‍⚕":"1f469-1f3fb-200d-2695-fe0f","👩🏼‍⚕":"1f469-1f3fc-200d-2695-fe0f","👩🏽‍⚕":"1f469-1f3fd-200d-2695-fe0f","👩🏾‍⚕":"1f469-1f3fe-200d-2695-fe0f","👩🏿‍⚕":"1f469-1f3ff-200d-2695-fe0f","🧑🏻‍🎓":"1f9d1-1f3fb-200d-1f393","🧑🏼‍🎓":"1f9d1-1f3fc-200d-1f393","🧑🏽‍🎓":"1f9d1-1f3fd-200d-1f393","🧑🏾‍🎓":"1f9d1-1f3fe-200d-1f393","🧑🏿‍🎓":"1f9d1-1f3ff-200d-1f393","👨🏻‍🎓":"1f468-1f3fb-200d-1f393","👨🏼‍🎓":"1f468-1f3fc-200d-1f393","👨🏽‍🎓":"1f468-1f3fd-200d-1f393","👨🏾‍🎓":"1f468-1f3fe-200d-1f393","👨🏿‍🎓":"1f468-1f3ff-200d-1f393","👩🏻‍🎓":"1f469-1f3fb-200d-1f393","👩🏼‍🎓":"1f469-1f3fc-200d-1f393","👩🏽‍🎓":"1f469-1f3fd-200d-1f393","👩🏾‍🎓":"1f469-1f3fe-200d-1f393","👩🏿‍🎓":"1f469-1f3ff-200d-1f393","🧑🏻‍🏫":"1f9d1-1f3fb-200d-1f3eb","🧑🏼‍🏫":"1f9d1-1f3fc-200d-1f3eb","🧑🏽‍🏫":"1f9d1-1f3fd-200d-1f3eb","🧑🏾‍🏫":"1f9d1-1f3fe-200d-1f3eb","🧑🏿‍🏫":"1f9d1-1f3ff-200d-1f3eb","👨🏻‍🏫":"1f468-1f3fb-200d-1f3eb","👨🏼‍🏫":"1f468-1f3fc-200d-1f3eb","👨🏽‍🏫":"1f468-1f3fd-200d-1f3eb","👨🏾‍🏫":"1f468-1f3fe-200d-1f3eb","👨🏿‍🏫":"1f468-1f3ff-200d-1f3eb","👩🏻‍🏫":"1f469-1f3fb-200d-1f3eb","👩🏼‍🏫":"1f469-1f3fc-200d-1f3eb","👩🏽‍🏫":"1f469-1f3fd-200d-1f3eb","👩🏾‍🏫":"1f469-1f3fe-200d-1f3eb","👩🏿‍🏫":"1f469-1f3ff-200d-1f3eb","🧑‍⚖️":"1f9d1-200d-2696-fe0f","🧑🏻‍⚖":"1f9d1-1f3fb-200d-2696-fe0f","🧑🏼‍⚖":"1f9d1-1f3fc-200d-2696-fe0f","🧑🏽‍⚖":"1f9d1-1f3fd-200d-2696-fe0f","🧑🏾‍⚖":"1f9d1-1f3fe-200d-2696-fe0f","🧑🏿‍⚖":"1f9d1-1f3ff-200d-2696-fe0f","👨‍⚖️":"1f468-200d-2696-fe0f","👨🏻‍⚖":"1f468-1f3fb-200d-2696-fe0f","👨🏼‍⚖":"1f468-1f3fc-200d-2696-fe0f","👨🏽‍⚖":"1f468-1f3fd-200d-2696-fe0f","👨🏾‍⚖":"1f468-1f3fe-200d-2696-fe0f","👨🏿‍⚖":"1f468-1f3ff-200d-2696-fe0f","👩‍⚖️":"1f469-200d-2696-fe0f","👩🏻‍⚖":"1f469-1f3fb-200d-2696-fe0f","👩🏼‍⚖":"1f469-1f3fc-200d-2696-fe0f","👩🏽‍⚖":"1f469-1f3fd-200d-2696-fe0f","👩🏾‍⚖":"1f469-1f3fe-200d-2696-fe0f","👩🏿‍⚖":"1f469-1f3ff-200d-2696-fe0f","🧑🏻‍🌾":"1f9d1-1f3fb-200d-1f33e","🧑🏼‍🌾":"1f9d1-1f3fc-200d-1f33e","🧑🏽‍🌾":"1f9d1-1f3fd-200d-1f33e","🧑🏾‍🌾":"1f9d1-1f3fe-200d-1f33e","🧑🏿‍🌾":"1f9d1-1f3ff-200d-1f33e","👨🏻‍🌾":"1f468-1f3fb-200d-1f33e","👨🏼‍🌾":"1f468-1f3fc-200d-1f33e","👨🏽‍🌾":"1f468-1f3fd-200d-1f33e","👨🏾‍🌾":"1f468-1f3fe-200d-1f33e","👨🏿‍🌾":"1f468-1f3ff-200d-1f33e","👩🏻‍🌾":"1f469-1f3fb-200d-1f33e","👩🏼‍🌾":"1f469-1f3fc-200d-1f33e","👩🏽‍🌾":"1f469-1f3fd-200d-1f33e","👩🏾‍🌾":"1f469-1f3fe-200d-1f33e","👩🏿‍🌾":"1f469-1f3ff-200d-1f33e","🧑🏻‍🍳":"1f9d1-1f3fb-200d-1f373","🧑🏼‍🍳":"1f9d1-1f3fc-200d-1f373","🧑🏽‍🍳":"1f9d1-1f3fd-200d-1f373","🧑🏾‍🍳":"1f9d1-1f3fe-200d-1f373","🧑🏿‍🍳":"1f9d1-1f3ff-200d-1f373","👨🏻‍🍳":"1f468-1f3fb-200d-1f373","👨🏼‍🍳":"1f468-1f3fc-200d-1f373","👨🏽‍🍳":"1f468-1f3fd-200d-1f373","👨🏾‍🍳":"1f468-1f3fe-200d-1f373","👨🏿‍🍳":"1f468-1f3ff-200d-1f373","👩🏻‍🍳":"1f469-1f3fb-200d-1f373","👩🏼‍🍳":"1f469-1f3fc-200d-1f373","👩🏽‍🍳":"1f469-1f3fd-200d-1f373","👩🏾‍🍳":"1f469-1f3fe-200d-1f373","👩🏿‍🍳":"1f469-1f3ff-200d-1f373","🧑🏻‍🔧":"1f9d1-1f3fb-200d-1f527","🧑🏼‍🔧":"1f9d1-1f3fc-200d-1f527","🧑🏽‍🔧":"1f9d1-1f3fd-200d-1f527","🧑🏾‍🔧":"1f9d1-1f3fe-200d-1f527","🧑🏿‍🔧":"1f9d1-1f3ff-200d-1f527","👨🏻‍🔧":"1f468-1f3fb-200d-1f527","👨🏼‍🔧":"1f468-1f3fc-200d-1f527","👨🏽‍🔧":"1f468-1f3fd-200d-1f527","👨🏾‍🔧":"1f468-1f3fe-200d-1f527","👨🏿‍🔧":"1f468-1f3ff-200d-1f527","👩🏻‍🔧":"1f469-1f3fb-200d-1f527","👩🏼‍🔧":"1f469-1f3fc-200d-1f527","👩🏽‍🔧":"1f469-1f3fd-200d-1f527","👩🏾‍🔧":"1f469-1f3fe-200d-1f527","👩🏿‍🔧":"1f469-1f3ff-200d-1f527","🧑🏻‍🏭":"1f9d1-1f3fb-200d-1f3ed","🧑🏼‍🏭":"1f9d1-1f3fc-200d-1f3ed","🧑🏽‍🏭":"1f9d1-1f3fd-200d-1f3ed","🧑🏾‍🏭":"1f9d1-1f3fe-200d-1f3ed","🧑🏿‍🏭":"1f9d1-1f3ff-200d-1f3ed","👨🏻‍🏭":"1f468-1f3fb-200d-1f3ed","👨🏼‍🏭":"1f468-1f3fc-200d-1f3ed","👨🏽‍🏭":"1f468-1f3fd-200d-1f3ed","👨🏾‍🏭":"1f468-1f3fe-200d-1f3ed","👨🏿‍🏭":"1f468-1f3ff-200d-1f3ed","👩🏻‍🏭":"1f469-1f3fb-200d-1f3ed","👩🏼‍🏭":"1f469-1f3fc-200d-1f3ed","👩🏽‍🏭":"1f469-1f3fd-200d-1f3ed","👩🏾‍🏭":"1f469-1f3fe-200d-1f3ed","👩🏿‍🏭":"1f469-1f3ff-200d-1f3ed","🧑🏻‍💼":"1f9d1-1f3fb-200d-1f4bc","🧑🏼‍💼":"1f9d1-1f3fc-200d-1f4bc","🧑🏽‍💼":"1f9d1-1f3fd-200d-1f4bc","🧑🏾‍💼":"1f9d1-1f3fe-200d-1f4bc","🧑🏿‍💼":"1f9d1-1f3ff-200d-1f4bc","👨🏻‍💼":"1f468-1f3fb-200d-1f4bc","👨🏼‍💼":"1f468-1f3fc-200d-1f4bc","👨🏽‍💼":"1f468-1f3fd-200d-1f4bc","👨🏾‍💼":"1f468-1f3fe-200d-1f4bc","👨🏿‍💼":"1f468-1f3ff-200d-1f4bc","👩🏻‍💼":"1f469-1f3fb-200d-1f4bc","👩🏼‍💼":"1f469-1f3fc-200d-1f4bc","👩🏽‍💼":"1f469-1f3fd-200d-1f4bc","👩🏾‍💼":"1f469-1f3fe-200d-1f4bc","👩🏿‍💼":"1f469-1f3ff-200d-1f4bc","🧑🏻‍🔬":"1f9d1-1f3fb-200d-1f52c","🧑🏼‍🔬":"1f9d1-1f3fc-200d-1f52c","🧑🏽‍🔬":"1f9d1-1f3fd-200d-1f52c","🧑🏾‍🔬":"1f9d1-1f3fe-200d-1f52c","🧑🏿‍🔬":"1f9d1-1f3ff-200d-1f52c","👨🏻‍🔬":"1f468-1f3fb-200d-1f52c","👨🏼‍🔬":"1f468-1f3fc-200d-1f52c","👨🏽‍🔬":"1f468-1f3fd-200d-1f52c","👨🏾‍🔬":"1f468-1f3fe-200d-1f52c","👨🏿‍🔬":"1f468-1f3ff-200d-1f52c","👩🏻‍🔬":"1f469-1f3fb-200d-1f52c","👩🏼‍🔬":"1f469-1f3fc-200d-1f52c","👩🏽‍🔬":"1f469-1f3fd-200d-1f52c","👩🏾‍🔬":"1f469-1f3fe-200d-1f52c","👩🏿‍🔬":"1f469-1f3ff-200d-1f52c","🧑🏻‍💻":"1f9d1-1f3fb-200d-1f4bb","🧑🏼‍💻":"1f9d1-1f3fc-200d-1f4bb","🧑🏽‍💻":"1f9d1-1f3fd-200d-1f4bb","🧑🏾‍💻":"1f9d1-1f3fe-200d-1f4bb","🧑🏿‍💻":"1f9d1-1f3ff-200d-1f4bb","👨🏻‍💻":"1f468-1f3fb-200d-1f4bb","👨🏼‍💻":"1f468-1f3fc-200d-1f4bb","👨🏽‍💻":"1f468-1f3fd-200d-1f4bb","👨🏾‍💻":"1f468-1f3fe-200d-1f4bb","👨🏿‍💻":"1f468-1f3ff-200d-1f4bb","👩🏻‍💻":"1f469-1f3fb-200d-1f4bb","👩🏼‍💻":"1f469-1f3fc-200d-1f4bb","👩🏽‍💻":"1f469-1f3fd-200d-1f4bb","👩🏾‍💻":"1f469-1f3fe-200d-1f4bb","👩🏿‍💻":"1f469-1f3ff-200d-1f4bb","🧑🏻‍🎤":"1f9d1-1f3fb-200d-1f3a4","🧑🏼‍🎤":"1f9d1-1f3fc-200d-1f3a4","🧑🏽‍🎤":"1f9d1-1f3fd-200d-1f3a4","🧑🏾‍🎤":"1f9d1-1f3fe-200d-1f3a4","🧑🏿‍🎤":"1f9d1-1f3ff-200d-1f3a4","👨🏻‍🎤":"1f468-1f3fb-200d-1f3a4","👨🏼‍🎤":"1f468-1f3fc-200d-1f3a4","👨🏽‍🎤":"1f468-1f3fd-200d-1f3a4","👨🏾‍🎤":"1f468-1f3fe-200d-1f3a4","👨🏿‍🎤":"1f468-1f3ff-200d-1f3a4","👩🏻‍🎤":"1f469-1f3fb-200d-1f3a4","👩🏼‍🎤":"1f469-1f3fc-200d-1f3a4","👩🏽‍🎤":"1f469-1f3fd-200d-1f3a4","👩🏾‍🎤":"1f469-1f3fe-200d-1f3a4","👩🏿‍🎤":"1f469-1f3ff-200d-1f3a4","🧑🏻‍🎨":"1f9d1-1f3fb-200d-1f3a8","🧑🏼‍🎨":"1f9d1-1f3fc-200d-1f3a8","🧑🏽‍🎨":"1f9d1-1f3fd-200d-1f3a8","🧑🏾‍🎨":"1f9d1-1f3fe-200d-1f3a8","🧑🏿‍🎨":"1f9d1-1f3ff-200d-1f3a8","👨🏻‍🎨":"1f468-1f3fb-200d-1f3a8","👨🏼‍🎨":"1f468-1f3fc-200d-1f3a8","👨🏽‍🎨":"1f468-1f3fd-200d-1f3a8","👨🏾‍🎨":"1f468-1f3fe-200d-1f3a8","👨🏿‍🎨":"1f468-1f3ff-200d-1f3a8","👩🏻‍🎨":"1f469-1f3fb-200d-1f3a8","👩🏼‍🎨":"1f469-1f3fc-200d-1f3a8","👩🏽‍🎨":"1f469-1f3fd-200d-1f3a8","👩🏾‍🎨":"1f469-1f3fe-200d-1f3a8","👩🏿‍🎨":"1f469-1f3ff-200d-1f3a8","🧑‍✈️":"1f9d1-200d-2708-fe0f","🧑🏻‍✈":"1f9d1-1f3fb-200d-2708-fe0f","🧑🏼‍✈":"1f9d1-1f3fc-200d-2708-fe0f","🧑🏽‍✈":"1f9d1-1f3fd-200d-2708-fe0f","🧑🏾‍✈":"1f9d1-1f3fe-200d-2708-fe0f","🧑🏿‍✈":"1f9d1-1f3ff-200d-2708-fe0f","👨‍✈️":"1f468-200d-2708-fe0f","👨🏻‍✈":"1f468-1f3fb-200d-2708-fe0f","👨🏼‍✈":"1f468-1f3fc-200d-2708-fe0f","👨🏽‍✈":"1f468-1f3fd-200d-2708-fe0f","👨🏾‍✈":"1f468-1f3fe-200d-2708-fe0f","👨🏿‍✈":"1f468-1f3ff-200d-2708-fe0f","👩‍✈️":"1f469-200d-2708-fe0f","👩🏻‍✈":"1f469-1f3fb-200d-2708-fe0f","👩🏼‍✈":"1f469-1f3fc-200d-2708-fe0f","👩🏽‍✈":"1f469-1f3fd-200d-2708-fe0f","👩🏾‍✈":"1f469-1f3fe-200d-2708-fe0f","👩🏿‍✈":"1f469-1f3ff-200d-2708-fe0f","🧑🏻‍🚀":"1f9d1-1f3fb-200d-1f680","🧑🏼‍🚀":"1f9d1-1f3fc-200d-1f680","🧑🏽‍🚀":"1f9d1-1f3fd-200d-1f680","🧑🏾‍🚀":"1f9d1-1f3fe-200d-1f680","🧑🏿‍🚀":"1f9d1-1f3ff-200d-1f680","👨🏻‍🚀":"1f468-1f3fb-200d-1f680","👨🏼‍🚀":"1f468-1f3fc-200d-1f680","👨🏽‍🚀":"1f468-1f3fd-200d-1f680","👨🏾‍🚀":"1f468-1f3fe-200d-1f680","👨🏿‍🚀":"1f468-1f3ff-200d-1f680","👩🏻‍🚀":"1f469-1f3fb-200d-1f680","👩🏼‍🚀":"1f469-1f3fc-200d-1f680","👩🏽‍🚀":"1f469-1f3fd-200d-1f680","👩🏾‍🚀":"1f469-1f3fe-200d-1f680","👩🏿‍🚀":"1f469-1f3ff-200d-1f680","🧑🏻‍🚒":"1f9d1-1f3fb-200d-1f692","🧑🏼‍🚒":"1f9d1-1f3fc-200d-1f692","🧑🏽‍🚒":"1f9d1-1f3fd-200d-1f692","🧑🏾‍🚒":"1f9d1-1f3fe-200d-1f692","🧑🏿‍🚒":"1f9d1-1f3ff-200d-1f692","👨🏻‍🚒":"1f468-1f3fb-200d-1f692","👨🏼‍🚒":"1f468-1f3fc-200d-1f692","👨🏽‍🚒":"1f468-1f3fd-200d-1f692","👨🏾‍🚒":"1f468-1f3fe-200d-1f692","👨🏿‍🚒":"1f468-1f3ff-200d-1f692","👩🏻‍🚒":"1f469-1f3fb-200d-1f692","👩🏼‍🚒":"1f469-1f3fc-200d-1f692","👩🏽‍🚒":"1f469-1f3fd-200d-1f692","👩🏾‍🚒":"1f469-1f3fe-200d-1f692","👩🏿‍🚒":"1f469-1f3ff-200d-1f692","👮‍♂️":"1f46e-200d-2642-fe0f","👮🏻‍♂":"1f46e-1f3fb-200d-2642-fe0f","👮🏼‍♂":"1f46e-1f3fc-200d-2642-fe0f","👮🏽‍♂":"1f46e-1f3fd-200d-2642-fe0f","👮🏾‍♂":"1f46e-1f3fe-200d-2642-fe0f","👮🏿‍♂":"1f46e-1f3ff-200d-2642-fe0f","👮‍♀️":"1f46e-200d-2640-fe0f","👮🏻‍♀":"1f46e-1f3fb-200d-2640-fe0f","👮🏼‍♀":"1f46e-1f3fc-200d-2640-fe0f","👮🏽‍♀":"1f46e-1f3fd-200d-2640-fe0f","👮🏾‍♀":"1f46e-1f3fe-200d-2640-fe0f","👮🏿‍♀":"1f46e-1f3ff-200d-2640-fe0f","🕵‍♂️":"1f575-fe0f-200d-2642-fe0f","🕵️‍♂":"1f575-fe0f-200d-2642-fe0f","🕵🏻‍♂":"1f575-1f3fb-200d-2642-fe0f","🕵🏼‍♂":"1f575-1f3fc-200d-2642-fe0f","🕵🏽‍♂":"1f575-1f3fd-200d-2642-fe0f","🕵🏾‍♂":"1f575-1f3fe-200d-2642-fe0f","🕵🏿‍♂":"1f575-1f3ff-200d-2642-fe0f","🕵‍♀️":"1f575-fe0f-200d-2640-fe0f","🕵️‍♀":"1f575-fe0f-200d-2640-fe0f","🕵🏻‍♀":"1f575-1f3fb-200d-2640-fe0f","🕵🏼‍♀":"1f575-1f3fc-200d-2640-fe0f","🕵🏽‍♀":"1f575-1f3fd-200d-2640-fe0f","🕵🏾‍♀":"1f575-1f3fe-200d-2640-fe0f","🕵🏿‍♀":"1f575-1f3ff-200d-2640-fe0f","💂‍♂️":"1f482-200d-2642-fe0f","💂🏻‍♂":"1f482-1f3fb-200d-2642-fe0f","💂🏼‍♂":"1f482-1f3fc-200d-2642-fe0f","💂🏽‍♂":"1f482-1f3fd-200d-2642-fe0f","💂🏾‍♂":"1f482-1f3fe-200d-2642-fe0f","💂🏿‍♂":"1f482-1f3ff-200d-2642-fe0f","💂‍♀️":"1f482-200d-2640-fe0f","💂🏻‍♀":"1f482-1f3fb-200d-2640-fe0f","💂🏼‍♀":"1f482-1f3fc-200d-2640-fe0f","💂🏽‍♀":"1f482-1f3fd-200d-2640-fe0f","💂🏾‍♀":"1f482-1f3fe-200d-2640-fe0f","💂🏿‍♀":"1f482-1f3ff-200d-2640-fe0f","👷‍♂️":"1f477-200d-2642-fe0f","👷🏻‍♂":"1f477-1f3fb-200d-2642-fe0f","👷🏼‍♂":"1f477-1f3fc-200d-2642-fe0f","👷🏽‍♂":"1f477-1f3fd-200d-2642-fe0f","👷🏾‍♂":"1f477-1f3fe-200d-2642-fe0f","👷🏿‍♂":"1f477-1f3ff-200d-2642-fe0f","👷‍♀️":"1f477-200d-2640-fe0f","👷🏻‍♀":"1f477-1f3fb-200d-2640-fe0f","👷🏼‍♀":"1f477-1f3fc-200d-2640-fe0f","👷🏽‍♀":"1f477-1f3fd-200d-2640-fe0f","👷🏾‍♀":"1f477-1f3fe-200d-2640-fe0f","👷🏿‍♀":"1f477-1f3ff-200d-2640-fe0f","👳‍♂️":"1f473-200d-2642-fe0f","👳🏻‍♂":"1f473-1f3fb-200d-2642-fe0f","👳🏼‍♂":"1f473-1f3fc-200d-2642-fe0f","👳🏽‍♂":"1f473-1f3fd-200d-2642-fe0f","👳🏾‍♂":"1f473-1f3fe-200d-2642-fe0f","👳🏿‍♂":"1f473-1f3ff-200d-2642-fe0f","👳‍♀️":"1f473-200d-2640-fe0f","👳🏻‍♀":"1f473-1f3fb-200d-2640-fe0f","👳🏼‍♀":"1f473-1f3fc-200d-2640-fe0f","👳🏽‍♀":"1f473-1f3fd-200d-2640-fe0f","👳🏾‍♀":"1f473-1f3fe-200d-2640-fe0f","👳🏿‍♀":"1f473-1f3ff-200d-2640-fe0f","🤵‍♂️":"1f935-200d-2642-fe0f","🤵🏻‍♂":"1f935-1f3fb-200d-2642-fe0f","🤵🏼‍♂":"1f935-1f3fc-200d-2642-fe0f","🤵🏽‍♂":"1f935-1f3fd-200d-2642-fe0f","🤵🏾‍♂":"1f935-1f3fe-200d-2642-fe0f","🤵🏿‍♂":"1f935-1f3ff-200d-2642-fe0f","🤵‍♀️":"1f935-200d-2640-fe0f","🤵🏻‍♀":"1f935-1f3fb-200d-2640-fe0f","🤵🏼‍♀":"1f935-1f3fc-200d-2640-fe0f","🤵🏽‍♀":"1f935-1f3fd-200d-2640-fe0f","🤵🏾‍♀":"1f935-1f3fe-200d-2640-fe0f","🤵🏿‍♀":"1f935-1f3ff-200d-2640-fe0f","👰‍♂️":"1f470-200d-2642-fe0f","👰🏻‍♂":"1f470-1f3fb-200d-2642-fe0f","👰🏼‍♂":"1f470-1f3fc-200d-2642-fe0f","👰🏽‍♂":"1f470-1f3fd-200d-2642-fe0f","👰🏾‍♂":"1f470-1f3fe-200d-2642-fe0f","👰🏿‍♂":"1f470-1f3ff-200d-2642-fe0f","👰‍♀️":"1f470-200d-2640-fe0f","👰🏻‍♀":"1f470-1f3fb-200d-2640-fe0f","👰🏼‍♀":"1f470-1f3fc-200d-2640-fe0f","👰🏽‍♀":"1f470-1f3fd-200d-2640-fe0f","👰🏾‍♀":"1f470-1f3fe-200d-2640-fe0f","👰🏿‍♀":"1f470-1f3ff-200d-2640-fe0f","👩🏻‍🍼":"1f469-1f3fb-200d-1f37c","👩🏼‍🍼":"1f469-1f3fc-200d-1f37c","👩🏽‍🍼":"1f469-1f3fd-200d-1f37c","👩🏾‍🍼":"1f469-1f3fe-200d-1f37c","👩🏿‍🍼":"1f469-1f3ff-200d-1f37c","👨🏻‍🍼":"1f468-1f3fb-200d-1f37c","👨🏼‍🍼":"1f468-1f3fc-200d-1f37c","👨🏽‍🍼":"1f468-1f3fd-200d-1f37c","👨🏾‍🍼":"1f468-1f3fe-200d-1f37c","👨🏿‍🍼":"1f468-1f3ff-200d-1f37c","🧑🏻‍🍼":"1f9d1-1f3fb-200d-1f37c","🧑🏼‍🍼":"1f9d1-1f3fc-200d-1f37c","🧑🏽‍🍼":"1f9d1-1f3fd-200d-1f37c","🧑🏾‍🍼":"1f9d1-1f3fe-200d-1f37c","🧑🏿‍🍼":"1f9d1-1f3ff-200d-1f37c","🧑🏻‍🎄":"1f9d1-1f3fb-200d-1f384","🧑🏼‍🎄":"1f9d1-1f3fc-200d-1f384","🧑🏽‍🎄":"1f9d1-1f3fd-200d-1f384","🧑🏾‍🎄":"1f9d1-1f3fe-200d-1f384","🧑🏿‍🎄":"1f9d1-1f3ff-200d-1f384","🦸‍♂️":"1f9b8-200d-2642-fe0f","🦸🏻‍♂":"1f9b8-1f3fb-200d-2642-fe0f","🦸🏼‍♂":"1f9b8-1f3fc-200d-2642-fe0f","🦸🏽‍♂":"1f9b8-1f3fd-200d-2642-fe0f","🦸🏾‍♂":"1f9b8-1f3fe-200d-2642-fe0f","🦸🏿‍♂":"1f9b8-1f3ff-200d-2642-fe0f","🦸‍♀️":"1f9b8-200d-2640-fe0f","🦸🏻‍♀":"1f9b8-1f3fb-200d-2640-fe0f","🦸🏼‍♀":"1f9b8-1f3fc-200d-2640-fe0f","🦸🏽‍♀":"1f9b8-1f3fd-200d-2640-fe0f","🦸🏾‍♀":"1f9b8-1f3fe-200d-2640-fe0f","🦸🏿‍♀":"1f9b8-1f3ff-200d-2640-fe0f","🦹‍♂️":"1f9b9-200d-2642-fe0f","🦹🏻‍♂":"1f9b9-1f3fb-200d-2642-fe0f","🦹🏼‍♂":"1f9b9-1f3fc-200d-2642-fe0f","🦹🏽‍♂":"1f9b9-1f3fd-200d-2642-fe0f","🦹🏾‍♂":"1f9b9-1f3fe-200d-2642-fe0f","🦹🏿‍♂":"1f9b9-1f3ff-200d-2642-fe0f","🦹‍♀️":"1f9b9-200d-2640-fe0f","🦹🏻‍♀":"1f9b9-1f3fb-200d-2640-fe0f","🦹🏼‍♀":"1f9b9-1f3fc-200d-2640-fe0f","🦹🏽‍♀":"1f9b9-1f3fd-200d-2640-fe0f","🦹🏾‍♀":"1f9b9-1f3fe-200d-2640-fe0f","🦹🏿‍♀":"1f9b9-1f3ff-200d-2640-fe0f","🧙‍♂️":"1f9d9-200d-2642-fe0f","🧙🏻‍♂":"1f9d9-1f3fb-200d-2642-fe0f","🧙🏼‍♂":"1f9d9-1f3fc-200d-2642-fe0f","🧙🏽‍♂":"1f9d9-1f3fd-200d-2642-fe0f","🧙🏾‍♂":"1f9d9-1f3fe-200d-2642-fe0f","🧙🏿‍♂":"1f9d9-1f3ff-200d-2642-fe0f","🧙‍♀️":"1f9d9-200d-2640-fe0f","🧙🏻‍♀":"1f9d9-1f3fb-200d-2640-fe0f","🧙🏼‍♀":"1f9d9-1f3fc-200d-2640-fe0f","🧙🏽‍♀":"1f9d9-1f3fd-200d-2640-fe0f","🧙🏾‍♀":"1f9d9-1f3fe-200d-2640-fe0f","🧙🏿‍♀":"1f9d9-1f3ff-200d-2640-fe0f","🧚‍♂️":"1f9da-200d-2642-fe0f","🧚🏻‍♂":"1f9da-1f3fb-200d-2642-fe0f","🧚🏼‍♂":"1f9da-1f3fc-200d-2642-fe0f","🧚🏽‍♂":"1f9da-1f3fd-200d-2642-fe0f","🧚🏾‍♂":"1f9da-1f3fe-200d-2642-fe0f","🧚🏿‍♂":"1f9da-1f3ff-200d-2642-fe0f","🧚‍♀️":"1f9da-200d-2640-fe0f","🧚🏻‍♀":"1f9da-1f3fb-200d-2640-fe0f","🧚🏼‍♀":"1f9da-1f3fc-200d-2640-fe0f","🧚🏽‍♀":"1f9da-1f3fd-200d-2640-fe0f","🧚🏾‍♀":"1f9da-1f3fe-200d-2640-fe0f","🧚🏿‍♀":"1f9da-1f3ff-200d-2640-fe0f","🧛‍♂️":"1f9db-200d-2642-fe0f","🧛🏻‍♂":"1f9db-1f3fb-200d-2642-fe0f","🧛🏼‍♂":"1f9db-1f3fc-200d-2642-fe0f","🧛🏽‍♂":"1f9db-1f3fd-200d-2642-fe0f","🧛🏾‍♂":"1f9db-1f3fe-200d-2642-fe0f","🧛🏿‍♂":"1f9db-1f3ff-200d-2642-fe0f","🧛‍♀️":"1f9db-200d-2640-fe0f","🧛🏻‍♀":"1f9db-1f3fb-200d-2640-fe0f","🧛🏼‍♀":"1f9db-1f3fc-200d-2640-fe0f","🧛🏽‍♀":"1f9db-1f3fd-200d-2640-fe0f","🧛🏾‍♀":"1f9db-1f3fe-200d-2640-fe0f","🧛🏿‍♀":"1f9db-1f3ff-200d-2640-fe0f","🧜‍♂️":"1f9dc-200d-2642-fe0f","🧜🏻‍♂":"1f9dc-1f3fb-200d-2642-fe0f","🧜🏼‍♂":"1f9dc-1f3fc-200d-2642-fe0f","🧜🏽‍♂":"1f9dc-1f3fd-200d-2642-fe0f","🧜🏾‍♂":"1f9dc-1f3fe-200d-2642-fe0f","🧜🏿‍♂":"1f9dc-1f3ff-200d-2642-fe0f","🧜‍♀️":"1f9dc-200d-2640-fe0f","🧜🏻‍♀":"1f9dc-1f3fb-200d-2640-fe0f","🧜🏼‍♀":"1f9dc-1f3fc-200d-2640-fe0f","🧜🏽‍♀":"1f9dc-1f3fd-200d-2640-fe0f","🧜🏾‍♀":"1f9dc-1f3fe-200d-2640-fe0f","🧜🏿‍♀":"1f9dc-1f3ff-200d-2640-fe0f","🧝‍♂️":"1f9dd-200d-2642-fe0f","🧝🏻‍♂":"1f9dd-1f3fb-200d-2642-fe0f","🧝🏼‍♂":"1f9dd-1f3fc-200d-2642-fe0f","🧝🏽‍♂":"1f9dd-1f3fd-200d-2642-fe0f","🧝🏾‍♂":"1f9dd-1f3fe-200d-2642-fe0f","🧝🏿‍♂":"1f9dd-1f3ff-200d-2642-fe0f","🧝‍♀️":"1f9dd-200d-2640-fe0f","🧝🏻‍♀":"1f9dd-1f3fb-200d-2640-fe0f","🧝🏼‍♀":"1f9dd-1f3fc-200d-2640-fe0f","🧝🏽‍♀":"1f9dd-1f3fd-200d-2640-fe0f","🧝🏾‍♀":"1f9dd-1f3fe-200d-2640-fe0f","🧝🏿‍♀":"1f9dd-1f3ff-200d-2640-fe0f","🧞‍♂️":"1f9de-200d-2642-fe0f","🧞‍♀️":"1f9de-200d-2640-fe0f","🧟‍♂️":"1f9df-200d-2642-fe0f","🧟‍♀️":"1f9df-200d-2640-fe0f","💆‍♂️":"1f486-200d-2642-fe0f","💆🏻‍♂":"1f486-1f3fb-200d-2642-fe0f","💆🏼‍♂":"1f486-1f3fc-200d-2642-fe0f","💆🏽‍♂":"1f486-1f3fd-200d-2642-fe0f","💆🏾‍♂":"1f486-1f3fe-200d-2642-fe0f","💆🏿‍♂":"1f486-1f3ff-200d-2642-fe0f","💆‍♀️":"1f486-200d-2640-fe0f","💆🏻‍♀":"1f486-1f3fb-200d-2640-fe0f","💆🏼‍♀":"1f486-1f3fc-200d-2640-fe0f","💆🏽‍♀":"1f486-1f3fd-200d-2640-fe0f","💆🏾‍♀":"1f486-1f3fe-200d-2640-fe0f","💆🏿‍♀":"1f486-1f3ff-200d-2640-fe0f","💇‍♂️":"1f487-200d-2642-fe0f","💇🏻‍♂":"1f487-1f3fb-200d-2642-fe0f","💇🏼‍♂":"1f487-1f3fc-200d-2642-fe0f","💇🏽‍♂":"1f487-1f3fd-200d-2642-fe0f","💇🏾‍♂":"1f487-1f3fe-200d-2642-fe0f","💇🏿‍♂":"1f487-1f3ff-200d-2642-fe0f","💇‍♀️":"1f487-200d-2640-fe0f","💇🏻‍♀":"1f487-1f3fb-200d-2640-fe0f","💇🏼‍♀":"1f487-1f3fc-200d-2640-fe0f","💇🏽‍♀":"1f487-1f3fd-200d-2640-fe0f","💇🏾‍♀":"1f487-1f3fe-200d-2640-fe0f","💇🏿‍♀":"1f487-1f3ff-200d-2640-fe0f","🚶‍♂️":"1f6b6-200d-2642-fe0f","🚶🏻‍♂":"1f6b6-1f3fb-200d-2642-fe0f","🚶🏼‍♂":"1f6b6-1f3fc-200d-2642-fe0f","🚶🏽‍♂":"1f6b6-1f3fd-200d-2642-fe0f","🚶🏾‍♂":"1f6b6-1f3fe-200d-2642-fe0f","🚶🏿‍♂":"1f6b6-1f3ff-200d-2642-fe0f","🚶‍♀️":"1f6b6-200d-2640-fe0f","🚶🏻‍♀":"1f6b6-1f3fb-200d-2640-fe0f","🚶🏼‍♀":"1f6b6-1f3fc-200d-2640-fe0f","🚶🏽‍♀":"1f6b6-1f3fd-200d-2640-fe0f","🚶🏾‍♀":"1f6b6-1f3fe-200d-2640-fe0f","🚶🏿‍♀":"1f6b6-1f3ff-200d-2640-fe0f","🚶‍➡️":"1f6b6-200d-27a1-fe0f","🚶🏻‍➡":"1f6b6-1f3fb-200d-27a1-fe0f","🚶🏼‍➡":"1f6b6-1f3fc-200d-27a1-fe0f","🚶🏽‍➡":"1f6b6-1f3fd-200d-27a1-fe0f","🚶🏾‍➡":"1f6b6-1f3fe-200d-27a1-fe0f","🚶🏿‍➡":"1f6b6-1f3ff-200d-27a1-fe0f","🧍‍♂️":"1f9cd-200d-2642-fe0f","🧍🏻‍♂":"1f9cd-1f3fb-200d-2642-fe0f","🧍🏼‍♂":"1f9cd-1f3fc-200d-2642-fe0f","🧍🏽‍♂":"1f9cd-1f3fd-200d-2642-fe0f","🧍🏾‍♂":"1f9cd-1f3fe-200d-2642-fe0f","🧍🏿‍♂":"1f9cd-1f3ff-200d-2642-fe0f","🧍‍♀️":"1f9cd-200d-2640-fe0f","🧍🏻‍♀":"1f9cd-1f3fb-200d-2640-fe0f","🧍🏼‍♀":"1f9cd-1f3fc-200d-2640-fe0f","🧍🏽‍♀":"1f9cd-1f3fd-200d-2640-fe0f","🧍🏾‍♀":"1f9cd-1f3fe-200d-2640-fe0f","🧍🏿‍♀":"1f9cd-1f3ff-200d-2640-fe0f","🧎‍♂️":"1f9ce-200d-2642-fe0f","🧎🏻‍♂":"1f9ce-1f3fb-200d-2642-fe0f","🧎🏼‍♂":"1f9ce-1f3fc-200d-2642-fe0f","🧎🏽‍♂":"1f9ce-1f3fd-200d-2642-fe0f","🧎🏾‍♂":"1f9ce-1f3fe-200d-2642-fe0f","🧎🏿‍♂":"1f9ce-1f3ff-200d-2642-fe0f","🧎‍♀️":"1f9ce-200d-2640-fe0f","🧎🏻‍♀":"1f9ce-1f3fb-200d-2640-fe0f","🧎🏼‍♀":"1f9ce-1f3fc-200d-2640-fe0f","🧎🏽‍♀":"1f9ce-1f3fd-200d-2640-fe0f","🧎🏾‍♀":"1f9ce-1f3fe-200d-2640-fe0f","🧎🏿‍♀":"1f9ce-1f3ff-200d-2640-fe0f","🧎‍➡️":"1f9ce-200d-27a1-fe0f","🧎🏻‍➡":"1f9ce-1f3fb-200d-27a1-fe0f","🧎🏼‍➡":"1f9ce-1f3fc-200d-27a1-fe0f","🧎🏽‍➡":"1f9ce-1f3fd-200d-27a1-fe0f","🧎🏾‍➡":"1f9ce-1f3fe-200d-27a1-fe0f","🧎🏿‍➡":"1f9ce-1f3ff-200d-27a1-fe0f","🧑🏻‍🦯":"1f9d1-1f3fb-200d-1f9af","🧑🏼‍🦯":"1f9d1-1f3fc-200d-1f9af","🧑🏽‍🦯":"1f9d1-1f3fd-200d-1f9af","🧑🏾‍🦯":"1f9d1-1f3fe-200d-1f9af","🧑🏿‍🦯":"1f9d1-1f3ff-200d-1f9af","👨🏻‍🦯":"1f468-1f3fb-200d-1f9af","👨🏼‍🦯":"1f468-1f3fc-200d-1f9af","👨🏽‍🦯":"1f468-1f3fd-200d-1f9af","👨🏾‍🦯":"1f468-1f3fe-200d-1f9af","👨🏿‍🦯":"1f468-1f3ff-200d-1f9af","👩🏻‍🦯":"1f469-1f3fb-200d-1f9af","👩🏼‍🦯":"1f469-1f3fc-200d-1f9af","👩🏽‍🦯":"1f469-1f3fd-200d-1f9af","👩🏾‍🦯":"1f469-1f3fe-200d-1f9af","👩🏿‍🦯":"1f469-1f3ff-200d-1f9af","🧑🏻‍🦼":"1f9d1-1f3fb-200d-1f9bc","🧑🏼‍🦼":"1f9d1-1f3fc-200d-1f9bc","🧑🏽‍🦼":"1f9d1-1f3fd-200d-1f9bc","🧑🏾‍🦼":"1f9d1-1f3fe-200d-1f9bc","🧑🏿‍🦼":"1f9d1-1f3ff-200d-1f9bc","👨🏻‍🦼":"1f468-1f3fb-200d-1f9bc","👨🏼‍🦼":"1f468-1f3fc-200d-1f9bc","👨🏽‍🦼":"1f468-1f3fd-200d-1f9bc","👨🏾‍🦼":"1f468-1f3fe-200d-1f9bc","👨🏿‍🦼":"1f468-1f3ff-200d-1f9bc","👩🏻‍🦼":"1f469-1f3fb-200d-1f9bc","👩🏼‍🦼":"1f469-1f3fc-200d-1f9bc","👩🏽‍🦼":"1f469-1f3fd-200d-1f9bc","👩🏾‍🦼":"1f469-1f3fe-200d-1f9bc","👩🏿‍🦼":"1f469-1f3ff-200d-1f9bc","🧑🏻‍🦽":"1f9d1-1f3fb-200d-1f9bd","🧑🏼‍🦽":"1f9d1-1f3fc-200d-1f9bd","🧑🏽‍🦽":"1f9d1-1f3fd-200d-1f9bd","🧑🏾‍🦽":"1f9d1-1f3fe-200d-1f9bd","🧑🏿‍🦽":"1f9d1-1f3ff-200d-1f9bd","👨🏻‍🦽":"1f468-1f3fb-200d-1f9bd","👨🏼‍🦽":"1f468-1f3fc-200d-1f9bd","👨🏽‍🦽":"1f468-1f3fd-200d-1f9bd","👨🏾‍🦽":"1f468-1f3fe-200d-1f9bd","👨🏿‍🦽":"1f468-1f3ff-200d-1f9bd","👩🏻‍🦽":"1f469-1f3fb-200d-1f9bd","👩🏼‍🦽":"1f469-1f3fc-200d-1f9bd","👩🏽‍🦽":"1f469-1f3fd-200d-1f9bd","👩🏾‍🦽":"1f469-1f3fe-200d-1f9bd","👩🏿‍🦽":"1f469-1f3ff-200d-1f9bd","🏃‍♂️":"1f3c3-200d-2642-fe0f","🏃🏻‍♂":"1f3c3-1f3fb-200d-2642-fe0f","🏃🏼‍♂":"1f3c3-1f3fc-200d-2642-fe0f","🏃🏽‍♂":"1f3c3-1f3fd-200d-2642-fe0f","🏃🏾‍♂":"1f3c3-1f3fe-200d-2642-fe0f","🏃🏿‍♂":"1f3c3-1f3ff-200d-2642-fe0f","🏃‍♀️":"1f3c3-200d-2640-fe0f","🏃🏻‍♀":"1f3c3-1f3fb-200d-2640-fe0f","🏃🏼‍♀":"1f3c3-1f3fc-200d-2640-fe0f","🏃🏽‍♀":"1f3c3-1f3fd-200d-2640-fe0f","🏃🏾‍♀":"1f3c3-1f3fe-200d-2640-fe0f","🏃🏿‍♀":"1f3c3-1f3ff-200d-2640-fe0f","🏃‍➡️":"1f3c3-200d-27a1-fe0f","🏃🏻‍➡":"1f3c3-1f3fb-200d-27a1-fe0f","🏃🏼‍➡":"1f3c3-1f3fc-200d-27a1-fe0f","🏃🏽‍➡":"1f3c3-1f3fd-200d-27a1-fe0f","🏃🏾‍➡":"1f3c3-1f3fe-200d-27a1-fe0f","🏃🏿‍➡":"1f3c3-1f3ff-200d-27a1-fe0f","👯‍♂️":"1f46f-200d-2642-fe0f","👯‍♀️":"1f46f-200d-2640-fe0f","🧖‍♂️":"1f9d6-200d-2642-fe0f","🧖🏻‍♂":"1f9d6-1f3fb-200d-2642-fe0f","🧖🏼‍♂":"1f9d6-1f3fc-200d-2642-fe0f","🧖🏽‍♂":"1f9d6-1f3fd-200d-2642-fe0f","🧖🏾‍♂":"1f9d6-1f3fe-200d-2642-fe0f","🧖🏿‍♂":"1f9d6-1f3ff-200d-2642-fe0f","🧖‍♀️":"1f9d6-200d-2640-fe0f","🧖🏻‍♀":"1f9d6-1f3fb-200d-2640-fe0f","🧖🏼‍♀":"1f9d6-1f3fc-200d-2640-fe0f","🧖🏽‍♀":"1f9d6-1f3fd-200d-2640-fe0f","🧖🏾‍♀":"1f9d6-1f3fe-200d-2640-fe0f","🧖🏿‍♀":"1f9d6-1f3ff-200d-2640-fe0f","🧗‍♂️":"1f9d7-200d-2642-fe0f","🧗🏻‍♂":"1f9d7-1f3fb-200d-2642-fe0f","🧗🏼‍♂":"1f9d7-1f3fc-200d-2642-fe0f","🧗🏽‍♂":"1f9d7-1f3fd-200d-2642-fe0f","🧗🏾‍♂":"1f9d7-1f3fe-200d-2642-fe0f","🧗🏿‍♂":"1f9d7-1f3ff-200d-2642-fe0f","🧗‍♀️":"1f9d7-200d-2640-fe0f","🧗🏻‍♀":"1f9d7-1f3fb-200d-2640-fe0f","🧗🏼‍♀":"1f9d7-1f3fc-200d-2640-fe0f","🧗🏽‍♀":"1f9d7-1f3fd-200d-2640-fe0f","🧗🏾‍♀":"1f9d7-1f3fe-200d-2640-fe0f","🧗🏿‍♀":"1f9d7-1f3ff-200d-2640-fe0f","🏌‍♂️":"1f3cc-fe0f-200d-2642-fe0f","🏌️‍♂":"1f3cc-fe0f-200d-2642-fe0f","🏌🏻‍♂":"1f3cc-1f3fb-200d-2642-fe0f","🏌🏼‍♂":"1f3cc-1f3fc-200d-2642-fe0f","🏌🏽‍♂":"1f3cc-1f3fd-200d-2642-fe0f","🏌🏾‍♂":"1f3cc-1f3fe-200d-2642-fe0f","🏌🏿‍♂":"1f3cc-1f3ff-200d-2642-fe0f","🏌‍♀️":"1f3cc-fe0f-200d-2640-fe0f","🏌️‍♀":"1f3cc-fe0f-200d-2640-fe0f","🏌🏻‍♀":"1f3cc-1f3fb-200d-2640-fe0f","🏌🏼‍♀":"1f3cc-1f3fc-200d-2640-fe0f","🏌🏽‍♀":"1f3cc-1f3fd-200d-2640-fe0f","🏌🏾‍♀":"1f3cc-1f3fe-200d-2640-fe0f","🏌🏿‍♀":"1f3cc-1f3ff-200d-2640-fe0f","🏄‍♂️":"1f3c4-200d-2642-fe0f","🏄🏻‍♂":"1f3c4-1f3fb-200d-2642-fe0f","🏄🏼‍♂":"1f3c4-1f3fc-200d-2642-fe0f","🏄🏽‍♂":"1f3c4-1f3fd-200d-2642-fe0f","🏄🏾‍♂":"1f3c4-1f3fe-200d-2642-fe0f","🏄🏿‍♂":"1f3c4-1f3ff-200d-2642-fe0f","🏄‍♀️":"1f3c4-200d-2640-fe0f","🏄🏻‍♀":"1f3c4-1f3fb-200d-2640-fe0f","🏄🏼‍♀":"1f3c4-1f3fc-200d-2640-fe0f","🏄🏽‍♀":"1f3c4-1f3fd-200d-2640-fe0f","🏄🏾‍♀":"1f3c4-1f3fe-200d-2640-fe0f","🏄🏿‍♀":"1f3c4-1f3ff-200d-2640-fe0f","🚣‍♂️":"1f6a3-200d-2642-fe0f","🚣🏻‍♂":"1f6a3-1f3fb-200d-2642-fe0f","🚣🏼‍♂":"1f6a3-1f3fc-200d-2642-fe0f","🚣🏽‍♂":"1f6a3-1f3fd-200d-2642-fe0f","🚣🏾‍♂":"1f6a3-1f3fe-200d-2642-fe0f","🚣🏿‍♂":"1f6a3-1f3ff-200d-2642-fe0f","🚣‍♀️":"1f6a3-200d-2640-fe0f","🚣🏻‍♀":"1f6a3-1f3fb-200d-2640-fe0f","🚣🏼‍♀":"1f6a3-1f3fc-200d-2640-fe0f","🚣🏽‍♀":"1f6a3-1f3fd-200d-2640-fe0f","🚣🏾‍♀":"1f6a3-1f3fe-200d-2640-fe0f","🚣🏿‍♀":"1f6a3-1f3ff-200d-2640-fe0f","🏊‍♂️":"1f3ca-200d-2642-fe0f","🏊🏻‍♂":"1f3ca-1f3fb-200d-2642-fe0f","🏊🏼‍♂":"1f3ca-1f3fc-200d-2642-fe0f","🏊🏽‍♂":"1f3ca-1f3fd-200d-2642-fe0f","🏊🏾‍♂":"1f3ca-1f3fe-200d-2642-fe0f","🏊🏿‍♂":"1f3ca-1f3ff-200d-2642-fe0f","🏊‍♀️":"1f3ca-200d-2640-fe0f","🏊🏻‍♀":"1f3ca-1f3fb-200d-2640-fe0f","🏊🏼‍♀":"1f3ca-1f3fc-200d-2640-fe0f","🏊🏽‍♀":"1f3ca-1f3fd-200d-2640-fe0f","🏊🏾‍♀":"1f3ca-1f3fe-200d-2640-fe0f","🏊🏿‍♀":"1f3ca-1f3ff-200d-2640-fe0f","⛹‍♂️":"26f9-fe0f-200d-2642-fe0f","⛹️‍♂":"26f9-fe0f-200d-2642-fe0f","⛹🏻‍♂":"26f9-1f3fb-200d-2642-fe0f","⛹🏼‍♂":"26f9-1f3fc-200d-2642-fe0f","⛹🏽‍♂":"26f9-1f3fd-200d-2642-fe0f","⛹🏾‍♂":"26f9-1f3fe-200d-2642-fe0f","⛹🏿‍♂":"26f9-1f3ff-200d-2642-fe0f","⛹‍♀️":"26f9-fe0f-200d-2640-fe0f","⛹️‍♀":"26f9-fe0f-200d-2640-fe0f","⛹🏻‍♀":"26f9-1f3fb-200d-2640-fe0f","⛹🏼‍♀":"26f9-1f3fc-200d-2640-fe0f","⛹🏽‍♀":"26f9-1f3fd-200d-2640-fe0f","⛹🏾‍♀":"26f9-1f3fe-200d-2640-fe0f","⛹🏿‍♀":"26f9-1f3ff-200d-2640-fe0f","🏋‍♂️":"1f3cb-fe0f-200d-2642-fe0f","🏋️‍♂":"1f3cb-fe0f-200d-2642-fe0f","🏋🏻‍♂":"1f3cb-1f3fb-200d-2642-fe0f","🏋🏼‍♂":"1f3cb-1f3fc-200d-2642-fe0f","🏋🏽‍♂":"1f3cb-1f3fd-200d-2642-fe0f","🏋🏾‍♂":"1f3cb-1f3fe-200d-2642-fe0f","🏋🏿‍♂":"1f3cb-1f3ff-200d-2642-fe0f","🏋‍♀️":"1f3cb-fe0f-200d-2640-fe0f","🏋️‍♀":"1f3cb-fe0f-200d-2640-fe0f","🏋🏻‍♀":"1f3cb-1f3fb-200d-2640-fe0f","🏋🏼‍♀":"1f3cb-1f3fc-200d-2640-fe0f","🏋🏽‍♀":"1f3cb-1f3fd-200d-2640-fe0f","🏋🏾‍♀":"1f3cb-1f3fe-200d-2640-fe0f","🏋🏿‍♀":"1f3cb-1f3ff-200d-2640-fe0f","🚴‍♂️":"1f6b4-200d-2642-fe0f","🚴🏻‍♂":"1f6b4-1f3fb-200d-2642-fe0f","🚴🏼‍♂":"1f6b4-1f3fc-200d-2642-fe0f","🚴🏽‍♂":"1f6b4-1f3fd-200d-2642-fe0f","🚴🏾‍♂":"1f6b4-1f3fe-200d-2642-fe0f","🚴🏿‍♂":"1f6b4-1f3ff-200d-2642-fe0f","🚴‍♀️":"1f6b4-200d-2640-fe0f","🚴🏻‍♀":"1f6b4-1f3fb-200d-2640-fe0f","🚴🏼‍♀":"1f6b4-1f3fc-200d-2640-fe0f","🚴🏽‍♀":"1f6b4-1f3fd-200d-2640-fe0f","🚴🏾‍♀":"1f6b4-1f3fe-200d-2640-fe0f","🚴🏿‍♀":"1f6b4-1f3ff-200d-2640-fe0f","🚵‍♂️":"1f6b5-200d-2642-fe0f","🚵🏻‍♂":"1f6b5-1f3fb-200d-2642-fe0f","🚵🏼‍♂":"1f6b5-1f3fc-200d-2642-fe0f","🚵🏽‍♂":"1f6b5-1f3fd-200d-2642-fe0f","🚵🏾‍♂":"1f6b5-1f3fe-200d-2642-fe0f","🚵🏿‍♂":"1f6b5-1f3ff-200d-2642-fe0f","🚵‍♀️":"1f6b5-200d-2640-fe0f","🚵🏻‍♀":"1f6b5-1f3fb-200d-2640-fe0f","🚵🏼‍♀":"1f6b5-1f3fc-200d-2640-fe0f","🚵🏽‍♀":"1f6b5-1f3fd-200d-2640-fe0f","🚵🏾‍♀":"1f6b5-1f3fe-200d-2640-fe0f","🚵🏿‍♀":"1f6b5-1f3ff-200d-2640-fe0f","🤸‍♂️":"1f938-200d-2642-fe0f","🤸🏻‍♂":"1f938-1f3fb-200d-2642-fe0f","🤸🏼‍♂":"1f938-1f3fc-200d-2642-fe0f","🤸🏽‍♂":"1f938-1f3fd-200d-2642-fe0f","🤸🏾‍♂":"1f938-1f3fe-200d-2642-fe0f","🤸🏿‍♂":"1f938-1f3ff-200d-2642-fe0f","🤸‍♀️":"1f938-200d-2640-fe0f","🤸🏻‍♀":"1f938-1f3fb-200d-2640-fe0f","🤸🏼‍♀":"1f938-1f3fc-200d-2640-fe0f","🤸🏽‍♀":"1f938-1f3fd-200d-2640-fe0f","🤸🏾‍♀":"1f938-1f3fe-200d-2640-fe0f","🤸🏿‍♀":"1f938-1f3ff-200d-2640-fe0f","🤼‍♂️":"1f93c-200d-2642-fe0f","🤼‍♀️":"1f93c-200d-2640-fe0f","🤽‍♂️":"1f93d-200d-2642-fe0f","🤽🏻‍♂":"1f93d-1f3fb-200d-2642-fe0f","🤽🏼‍♂":"1f93d-1f3fc-200d-2642-fe0f","🤽🏽‍♂":"1f93d-1f3fd-200d-2642-fe0f","🤽🏾‍♂":"1f93d-1f3fe-200d-2642-fe0f","🤽🏿‍♂":"1f93d-1f3ff-200d-2642-fe0f","🤽‍♀️":"1f93d-200d-2640-fe0f","🤽🏻‍♀":"1f93d-1f3fb-200d-2640-fe0f","🤽🏼‍♀":"1f93d-1f3fc-200d-2640-fe0f","🤽🏽‍♀":"1f93d-1f3fd-200d-2640-fe0f","🤽🏾‍♀":"1f93d-1f3fe-200d-2640-fe0f","🤽🏿‍♀":"1f93d-1f3ff-200d-2640-fe0f","🤾‍♂️":"1f93e-200d-2642-fe0f","🤾🏻‍♂":"1f93e-1f3fb-200d-2642-fe0f","🤾🏼‍♂":"1f93e-1f3fc-200d-2642-fe0f","🤾🏽‍♂":"1f93e-1f3fd-200d-2642-fe0f","🤾🏾‍♂":"1f93e-1f3fe-200d-2642-fe0f","🤾🏿‍♂":"1f93e-1f3ff-200d-2642-fe0f","🤾‍♀️":"1f93e-200d-2640-fe0f","🤾🏻‍♀":"1f93e-1f3fb-200d-2640-fe0f","🤾🏼‍♀":"1f93e-1f3fc-200d-2640-fe0f","🤾🏽‍♀":"1f93e-1f3fd-200d-2640-fe0f","🤾🏾‍♀":"1f93e-1f3fe-200d-2640-fe0f","🤾🏿‍♀":"1f93e-1f3ff-200d-2640-fe0f","🤹‍♂️":"1f939-200d-2642-fe0f","🤹🏻‍♂":"1f939-1f3fb-200d-2642-fe0f","🤹🏼‍♂":"1f939-1f3fc-200d-2642-fe0f","🤹🏽‍♂":"1f939-1f3fd-200d-2642-fe0f","🤹🏾‍♂":"1f939-1f3fe-200d-2642-fe0f","🤹🏿‍♂":"1f939-1f3ff-200d-2642-fe0f","🤹‍♀️":"1f939-200d-2640-fe0f","🤹🏻‍♀":"1f939-1f3fb-200d-2640-fe0f","🤹🏼‍♀":"1f939-1f3fc-200d-2640-fe0f","🤹🏽‍♀":"1f939-1f3fd-200d-2640-fe0f","🤹🏾‍♀":"1f939-1f3fe-200d-2640-fe0f","🤹🏿‍♀":"1f939-1f3ff-200d-2640-fe0f","🧘‍♂️":"1f9d8-200d-2642-fe0f","🧘🏻‍♂":"1f9d8-1f3fb-200d-2642-fe0f","🧘🏼‍♂":"1f9d8-1f3fc-200d-2642-fe0f","🧘🏽‍♂":"1f9d8-1f3fd-200d-2642-fe0f","🧘🏾‍♂":"1f9d8-1f3fe-200d-2642-fe0f","🧘🏿‍♂":"1f9d8-1f3ff-200d-2642-fe0f","🧘‍♀️":"1f9d8-200d-2640-fe0f","🧘🏻‍♀":"1f9d8-1f3fb-200d-2640-fe0f","🧘🏼‍♀":"1f9d8-1f3fc-200d-2640-fe0f","🧘🏽‍♀":"1f9d8-1f3fd-200d-2640-fe0f","🧘🏾‍♀":"1f9d8-1f3fe-200d-2640-fe0f","🧘🏿‍♀":"1f9d8-1f3ff-200d-2640-fe0f","🐻‍❄️":"1f43b-200d-2744-fe0f","⛓️‍💥":"26d3-fe0f-200d-1f4a5","🏳️‍🌈":"1f3f3-fe0f-200d-1f308","🏳‍⚧️":"1f3f3-fe0f-200d-26a7-fe0f","🏳️‍⚧":"1f3f3-fe0f-200d-26a7-fe0f","🏴‍☠️":"1f3f4-200d-2620-fe0f","👁️‍🗨️":"1f441-200d-1f5e8","🫱🏻‍🫲🏼":"1faf1-1f3fb-200d-1faf2-1f3fc","🫱🏻‍🫲🏽":"1faf1-1f3fb-200d-1faf2-1f3fd","🫱🏻‍🫲🏾":"1faf1-1f3fb-200d-1faf2-1f3fe","🫱🏻‍🫲🏿":"1faf1-1f3fb-200d-1faf2-1f3ff","🫱🏼‍🫲🏻":"1faf1-1f3fc-200d-1faf2-1f3fb","🫱🏼‍🫲🏽":"1faf1-1f3fc-200d-1faf2-1f3fd","🫱🏼‍🫲🏾":"1faf1-1f3fc-200d-1faf2-1f3fe","🫱🏼‍🫲🏿":"1faf1-1f3fc-200d-1faf2-1f3ff","🫱🏽‍🫲🏻":"1faf1-1f3fd-200d-1faf2-1f3fb","🫱🏽‍🫲🏼":"1faf1-1f3fd-200d-1faf2-1f3fc","🫱🏽‍🫲🏾":"1faf1-1f3fd-200d-1faf2-1f3fe","🫱🏽‍🫲🏿":"1faf1-1f3fd-200d-1faf2-1f3ff","🫱🏾‍🫲🏻":"1faf1-1f3fe-200d-1faf2-1f3fb","🫱🏾‍🫲🏼":"1faf1-1f3fe-200d-1faf2-1f3fc","🫱🏾‍🫲🏽":"1faf1-1f3fe-200d-1faf2-1f3fd","🫱🏾‍🫲🏿":"1faf1-1f3fe-200d-1faf2-1f3ff","🫱🏿‍🫲🏻":"1faf1-1f3ff-200d-1faf2-1f3fb","🫱🏿‍🫲🏼":"1faf1-1f3ff-200d-1faf2-1f3fc","🫱🏿‍🫲🏽":"1faf1-1f3ff-200d-1faf2-1f3fd","🫱🏿‍🫲🏾":"1faf1-1f3ff-200d-1faf2-1f3fe","🧔🏻‍♂️":"1f9d4-1f3fb-200d-2642-fe0f","🧔🏼‍♂️":"1f9d4-1f3fc-200d-2642-fe0f","🧔🏽‍♂️":"1f9d4-1f3fd-200d-2642-fe0f","🧔🏾‍♂️":"1f9d4-1f3fe-200d-2642-fe0f","🧔🏿‍♂️":"1f9d4-1f3ff-200d-2642-fe0f","🧔🏻‍♀️":"1f9d4-1f3fb-200d-2640-fe0f","🧔🏼‍♀️":"1f9d4-1f3fc-200d-2640-fe0f","🧔🏽‍♀️":"1f9d4-1f3fd-200d-2640-fe0f","🧔🏾‍♀️":"1f9d4-1f3fe-200d-2640-fe0f","🧔🏿‍♀️":"1f9d4-1f3ff-200d-2640-fe0f","👱🏻‍♀️":"1f471-1f3fb-200d-2640-fe0f","👱🏼‍♀️":"1f471-1f3fc-200d-2640-fe0f","👱🏽‍♀️":"1f471-1f3fd-200d-2640-fe0f","👱🏾‍♀️":"1f471-1f3fe-200d-2640-fe0f","👱🏿‍♀️":"1f471-1f3ff-200d-2640-fe0f","👱🏻‍♂️":"1f471-1f3fb-200d-2642-fe0f","👱🏼‍♂️":"1f471-1f3fc-200d-2642-fe0f","👱🏽‍♂️":"1f471-1f3fd-200d-2642-fe0f","👱🏾‍♂️":"1f471-1f3fe-200d-2642-fe0f","👱🏿‍♂️":"1f471-1f3ff-200d-2642-fe0f","🙍🏻‍♂️":"1f64d-1f3fb-200d-2642-fe0f","🙍🏼‍♂️":"1f64d-1f3fc-200d-2642-fe0f","🙍🏽‍♂️":"1f64d-1f3fd-200d-2642-fe0f","🙍🏾‍♂️":"1f64d-1f3fe-200d-2642-fe0f","🙍🏿‍♂️":"1f64d-1f3ff-200d-2642-fe0f","🙍🏻‍♀️":"1f64d-1f3fb-200d-2640-fe0f","🙍🏼‍♀️":"1f64d-1f3fc-200d-2640-fe0f","🙍🏽‍♀️":"1f64d-1f3fd-200d-2640-fe0f","🙍🏾‍♀️":"1f64d-1f3fe-200d-2640-fe0f","🙍🏿‍♀️":"1f64d-1f3ff-200d-2640-fe0f","🙎🏻‍♂️":"1f64e-1f3fb-200d-2642-fe0f","🙎🏼‍♂️":"1f64e-1f3fc-200d-2642-fe0f","🙎🏽‍♂️":"1f64e-1f3fd-200d-2642-fe0f","🙎🏾‍♂️":"1f64e-1f3fe-200d-2642-fe0f","🙎🏿‍♂️":"1f64e-1f3ff-200d-2642-fe0f","🙎🏻‍♀️":"1f64e-1f3fb-200d-2640-fe0f","🙎🏼‍♀️":"1f64e-1f3fc-200d-2640-fe0f","🙎🏽‍♀️":"1f64e-1f3fd-200d-2640-fe0f","🙎🏾‍♀️":"1f64e-1f3fe-200d-2640-fe0f","🙎🏿‍♀️":"1f64e-1f3ff-200d-2640-fe0f","🙅🏻‍♂️":"1f645-1f3fb-200d-2642-fe0f","🙅🏼‍♂️":"1f645-1f3fc-200d-2642-fe0f","🙅🏽‍♂️":"1f645-1f3fd-200d-2642-fe0f","🙅🏾‍♂️":"1f645-1f3fe-200d-2642-fe0f","🙅🏿‍♂️":"1f645-1f3ff-200d-2642-fe0f","🙅🏻‍♀️":"1f645-1f3fb-200d-2640-fe0f","🙅🏼‍♀️":"1f645-1f3fc-200d-2640-fe0f","🙅🏽‍♀️":"1f645-1f3fd-200d-2640-fe0f","🙅🏾‍♀️":"1f645-1f3fe-200d-2640-fe0f","🙅🏿‍♀️":"1f645-1f3ff-200d-2640-fe0f","🙆🏻‍♂️":"1f646-1f3fb-200d-2642-fe0f","🙆🏼‍♂️":"1f646-1f3fc-200d-2642-fe0f","🙆🏽‍♂️":"1f646-1f3fd-200d-2642-fe0f","🙆🏾‍♂️":"1f646-1f3fe-200d-2642-fe0f","🙆🏿‍♂️":"1f646-1f3ff-200d-2642-fe0f","🙆🏻‍♀️":"1f646-1f3fb-200d-2640-fe0f","🙆🏼‍♀️":"1f646-1f3fc-200d-2640-fe0f","🙆🏽‍♀️":"1f646-1f3fd-200d-2640-fe0f","🙆🏾‍♀️":"1f646-1f3fe-200d-2640-fe0f","🙆🏿‍♀️":"1f646-1f3ff-200d-2640-fe0f","💁🏻‍♂️":"1f481-1f3fb-200d-2642-fe0f","💁🏼‍♂️":"1f481-1f3fc-200d-2642-fe0f","💁🏽‍♂️":"1f481-1f3fd-200d-2642-fe0f","💁🏾‍♂️":"1f481-1f3fe-200d-2642-fe0f","💁🏿‍♂️":"1f481-1f3ff-200d-2642-fe0f","💁🏻‍♀️":"1f481-1f3fb-200d-2640-fe0f","💁🏼‍♀️":"1f481-1f3fc-200d-2640-fe0f","💁🏽‍♀️":"1f481-1f3fd-200d-2640-fe0f","💁🏾‍♀️":"1f481-1f3fe-200d-2640-fe0f","💁🏿‍♀️":"1f481-1f3ff-200d-2640-fe0f","🙋🏻‍♂️":"1f64b-1f3fb-200d-2642-fe0f","🙋🏼‍♂️":"1f64b-1f3fc-200d-2642-fe0f","🙋🏽‍♂️":"1f64b-1f3fd-200d-2642-fe0f","🙋🏾‍♂️":"1f64b-1f3fe-200d-2642-fe0f","🙋🏿‍♂️":"1f64b-1f3ff-200d-2642-fe0f","🙋🏻‍♀️":"1f64b-1f3fb-200d-2640-fe0f","🙋🏼‍♀️":"1f64b-1f3fc-200d-2640-fe0f","🙋🏽‍♀️":"1f64b-1f3fd-200d-2640-fe0f","🙋🏾‍♀️":"1f64b-1f3fe-200d-2640-fe0f","🙋🏿‍♀️":"1f64b-1f3ff-200d-2640-fe0f","🧏🏻‍♂️":"1f9cf-1f3fb-200d-2642-fe0f","🧏🏼‍♂️":"1f9cf-1f3fc-200d-2642-fe0f","🧏🏽‍♂️":"1f9cf-1f3fd-200d-2642-fe0f","🧏🏾‍♂️":"1f9cf-1f3fe-200d-2642-fe0f","🧏🏿‍♂️":"1f9cf-1f3ff-200d-2642-fe0f","🧏🏻‍♀️":"1f9cf-1f3fb-200d-2640-fe0f","🧏🏼‍♀️":"1f9cf-1f3fc-200d-2640-fe0f","🧏🏽‍♀️":"1f9cf-1f3fd-200d-2640-fe0f","🧏🏾‍♀️":"1f9cf-1f3fe-200d-2640-fe0f","🧏🏿‍♀️":"1f9cf-1f3ff-200d-2640-fe0f","🙇🏻‍♂️":"1f647-1f3fb-200d-2642-fe0f","🙇🏼‍♂️":"1f647-1f3fc-200d-2642-fe0f","🙇🏽‍♂️":"1f647-1f3fd-200d-2642-fe0f","🙇🏾‍♂️":"1f647-1f3fe-200d-2642-fe0f","🙇🏿‍♂️":"1f647-1f3ff-200d-2642-fe0f","🙇🏻‍♀️":"1f647-1f3fb-200d-2640-fe0f","🙇🏼‍♀️":"1f647-1f3fc-200d-2640-fe0f","🙇🏽‍♀️":"1f647-1f3fd-200d-2640-fe0f","🙇🏾‍♀️":"1f647-1f3fe-200d-2640-fe0f","🙇🏿‍♀️":"1f647-1f3ff-200d-2640-fe0f","🤦🏻‍♂️":"1f926-1f3fb-200d-2642-fe0f","🤦🏼‍♂️":"1f926-1f3fc-200d-2642-fe0f","🤦🏽‍♂️":"1f926-1f3fd-200d-2642-fe0f","🤦🏾‍♂️":"1f926-1f3fe-200d-2642-fe0f","🤦🏿‍♂️":"1f926-1f3ff-200d-2642-fe0f","🤦🏻‍♀️":"1f926-1f3fb-200d-2640-fe0f","🤦🏼‍♀️":"1f926-1f3fc-200d-2640-fe0f","🤦🏽‍♀️":"1f926-1f3fd-200d-2640-fe0f","🤦🏾‍♀️":"1f926-1f3fe-200d-2640-fe0f","🤦🏿‍♀️":"1f926-1f3ff-200d-2640-fe0f","🤷🏻‍♂️":"1f937-1f3fb-200d-2642-fe0f","🤷🏼‍♂️":"1f937-1f3fc-200d-2642-fe0f","🤷🏽‍♂️":"1f937-1f3fd-200d-2642-fe0f","🤷🏾‍♂️":"1f937-1f3fe-200d-2642-fe0f","🤷🏿‍♂️":"1f937-1f3ff-200d-2642-fe0f","🤷🏻‍♀️":"1f937-1f3fb-200d-2640-fe0f","🤷🏼‍♀️":"1f937-1f3fc-200d-2640-fe0f","🤷🏽‍♀️":"1f937-1f3fd-200d-2640-fe0f","🤷🏾‍♀️":"1f937-1f3fe-200d-2640-fe0f","🤷🏿‍♀️":"1f937-1f3ff-200d-2640-fe0f","🧑🏻‍⚕️":"1f9d1-1f3fb-200d-2695-fe0f","🧑🏼‍⚕️":"1f9d1-1f3fc-200d-2695-fe0f","🧑🏽‍⚕️":"1f9d1-1f3fd-200d-2695-fe0f","🧑🏾‍⚕️":"1f9d1-1f3fe-200d-2695-fe0f","🧑🏿‍⚕️":"1f9d1-1f3ff-200d-2695-fe0f","👨🏻‍⚕️":"1f468-1f3fb-200d-2695-fe0f","👨🏼‍⚕️":"1f468-1f3fc-200d-2695-fe0f","👨🏽‍⚕️":"1f468-1f3fd-200d-2695-fe0f","👨🏾‍⚕️":"1f468-1f3fe-200d-2695-fe0f","👨🏿‍⚕️":"1f468-1f3ff-200d-2695-fe0f","👩🏻‍⚕️":"1f469-1f3fb-200d-2695-fe0f","👩🏼‍⚕️":"1f469-1f3fc-200d-2695-fe0f","👩🏽‍⚕️":"1f469-1f3fd-200d-2695-fe0f","👩🏾‍⚕️":"1f469-1f3fe-200d-2695-fe0f","👩🏿‍⚕️":"1f469-1f3ff-200d-2695-fe0f","🧑🏻‍⚖️":"1f9d1-1f3fb-200d-2696-fe0f","🧑🏼‍⚖️":"1f9d1-1f3fc-200d-2696-fe0f","🧑🏽‍⚖️":"1f9d1-1f3fd-200d-2696-fe0f","🧑🏾‍⚖️":"1f9d1-1f3fe-200d-2696-fe0f","🧑🏿‍⚖️":"1f9d1-1f3ff-200d-2696-fe0f","👨🏻‍⚖️":"1f468-1f3fb-200d-2696-fe0f","👨🏼‍⚖️":"1f468-1f3fc-200d-2696-fe0f","👨🏽‍⚖️":"1f468-1f3fd-200d-2696-fe0f","👨🏾‍⚖️":"1f468-1f3fe-200d-2696-fe0f","👨🏿‍⚖️":"1f468-1f3ff-200d-2696-fe0f","👩🏻‍⚖️":"1f469-1f3fb-200d-2696-fe0f","👩🏼‍⚖️":"1f469-1f3fc-200d-2696-fe0f","👩🏽‍⚖️":"1f469-1f3fd-200d-2696-fe0f","👩🏾‍⚖️":"1f469-1f3fe-200d-2696-fe0f","👩🏿‍⚖️":"1f469-1f3ff-200d-2696-fe0f","🧑🏻‍✈️":"1f9d1-1f3fb-200d-2708-fe0f","🧑🏼‍✈️":"1f9d1-1f3fc-200d-2708-fe0f","🧑🏽‍✈️":"1f9d1-1f3fd-200d-2708-fe0f","🧑🏾‍✈️":"1f9d1-1f3fe-200d-2708-fe0f","🧑🏿‍✈️":"1f9d1-1f3ff-200d-2708-fe0f","👨🏻‍✈️":"1f468-1f3fb-200d-2708-fe0f","👨🏼‍✈️":"1f468-1f3fc-200d-2708-fe0f","👨🏽‍✈️":"1f468-1f3fd-200d-2708-fe0f","👨🏾‍✈️":"1f468-1f3fe-200d-2708-fe0f","👨🏿‍✈️":"1f468-1f3ff-200d-2708-fe0f","👩🏻‍✈️":"1f469-1f3fb-200d-2708-fe0f","👩🏼‍✈️":"1f469-1f3fc-200d-2708-fe0f","👩🏽‍✈️":"1f469-1f3fd-200d-2708-fe0f","👩🏾‍✈️":"1f469-1f3fe-200d-2708-fe0f","👩🏿‍✈️":"1f469-1f3ff-200d-2708-fe0f","👮🏻‍♂️":"1f46e-1f3fb-200d-2642-fe0f","👮🏼‍♂️":"1f46e-1f3fc-200d-2642-fe0f","👮🏽‍♂️":"1f46e-1f3fd-200d-2642-fe0f","👮🏾‍♂️":"1f46e-1f3fe-200d-2642-fe0f","👮🏿‍♂️":"1f46e-1f3ff-200d-2642-fe0f","👮🏻‍♀️":"1f46e-1f3fb-200d-2640-fe0f","👮🏼‍♀️":"1f46e-1f3fc-200d-2640-fe0f","👮🏽‍♀️":"1f46e-1f3fd-200d-2640-fe0f","👮🏾‍♀️":"1f46e-1f3fe-200d-2640-fe0f","👮🏿‍♀️":"1f46e-1f3ff-200d-2640-fe0f","🕵️‍♂️":"1f575-fe0f-200d-2642-fe0f","🕵🏻‍♂️":"1f575-1f3fb-200d-2642-fe0f","🕵🏼‍♂️":"1f575-1f3fc-200d-2642-fe0f","🕵🏽‍♂️":"1f575-1f3fd-200d-2642-fe0f","🕵🏾‍♂️":"1f575-1f3fe-200d-2642-fe0f","🕵🏿‍♂️":"1f575-1f3ff-200d-2642-fe0f","🕵️‍♀️":"1f575-fe0f-200d-2640-fe0f","🕵🏻‍♀️":"1f575-1f3fb-200d-2640-fe0f","🕵🏼‍♀️":"1f575-1f3fc-200d-2640-fe0f","🕵🏽‍♀️":"1f575-1f3fd-200d-2640-fe0f","🕵🏾‍♀️":"1f575-1f3fe-200d-2640-fe0f","🕵🏿‍♀️":"1f575-1f3ff-200d-2640-fe0f","💂🏻‍♂️":"1f482-1f3fb-200d-2642-fe0f","💂🏼‍♂️":"1f482-1f3fc-200d-2642-fe0f","💂🏽‍♂️":"1f482-1f3fd-200d-2642-fe0f","💂🏾‍♂️":"1f482-1f3fe-200d-2642-fe0f","💂🏿‍♂️":"1f482-1f3ff-200d-2642-fe0f","💂🏻‍♀️":"1f482-1f3fb-200d-2640-fe0f","💂🏼‍♀️":"1f482-1f3fc-200d-2640-fe0f","💂🏽‍♀️":"1f482-1f3fd-200d-2640-fe0f","💂🏾‍♀️":"1f482-1f3fe-200d-2640-fe0f","💂🏿‍♀️":"1f482-1f3ff-200d-2640-fe0f","👷🏻‍♂️":"1f477-1f3fb-200d-2642-fe0f","👷🏼‍♂️":"1f477-1f3fc-200d-2642-fe0f","👷🏽‍♂️":"1f477-1f3fd-200d-2642-fe0f","👷🏾‍♂️":"1f477-1f3fe-200d-2642-fe0f","👷🏿‍♂️":"1f477-1f3ff-200d-2642-fe0f","👷🏻‍♀️":"1f477-1f3fb-200d-2640-fe0f","👷🏼‍♀️":"1f477-1f3fc-200d-2640-fe0f","👷🏽‍♀️":"1f477-1f3fd-200d-2640-fe0f","👷🏾‍♀️":"1f477-1f3fe-200d-2640-fe0f","👷🏿‍♀️":"1f477-1f3ff-200d-2640-fe0f","👳🏻‍♂️":"1f473-1f3fb-200d-2642-fe0f","👳🏼‍♂️":"1f473-1f3fc-200d-2642-fe0f","👳🏽‍♂️":"1f473-1f3fd-200d-2642-fe0f","👳🏾‍♂️":"1f473-1f3fe-200d-2642-fe0f","👳🏿‍♂️":"1f473-1f3ff-200d-2642-fe0f","👳🏻‍♀️":"1f473-1f3fb-200d-2640-fe0f","👳🏼‍♀️":"1f473-1f3fc-200d-2640-fe0f","👳🏽‍♀️":"1f473-1f3fd-200d-2640-fe0f","👳🏾‍♀️":"1f473-1f3fe-200d-2640-fe0f","👳🏿‍♀️":"1f473-1f3ff-200d-2640-fe0f","🤵🏻‍♂️":"1f935-1f3fb-200d-2642-fe0f","🤵🏼‍♂️":"1f935-1f3fc-200d-2642-fe0f","🤵🏽‍♂️":"1f935-1f3fd-200d-2642-fe0f","🤵🏾‍♂️":"1f935-1f3fe-200d-2642-fe0f","🤵🏿‍♂️":"1f935-1f3ff-200d-2642-fe0f","🤵🏻‍♀️":"1f935-1f3fb-200d-2640-fe0f","🤵🏼‍♀️":"1f935-1f3fc-200d-2640-fe0f","🤵🏽‍♀️":"1f935-1f3fd-200d-2640-fe0f","🤵🏾‍♀️":"1f935-1f3fe-200d-2640-fe0f","🤵🏿‍♀️":"1f935-1f3ff-200d-2640-fe0f","👰🏻‍♂️":"1f470-1f3fb-200d-2642-fe0f","👰🏼‍♂️":"1f470-1f3fc-200d-2642-fe0f","👰🏽‍♂️":"1f470-1f3fd-200d-2642-fe0f","👰🏾‍♂️":"1f470-1f3fe-200d-2642-fe0f","👰🏿‍♂️":"1f470-1f3ff-200d-2642-fe0f","👰🏻‍♀️":"1f470-1f3fb-200d-2640-fe0f","👰🏼‍♀️":"1f470-1f3fc-200d-2640-fe0f","👰🏽‍♀️":"1f470-1f3fd-200d-2640-fe0f","👰🏾‍♀️":"1f470-1f3fe-200d-2640-fe0f","👰🏿‍♀️":"1f470-1f3ff-200d-2640-fe0f","🦸🏻‍♂️":"1f9b8-1f3fb-200d-2642-fe0f","🦸🏼‍♂️":"1f9b8-1f3fc-200d-2642-fe0f","🦸🏽‍♂️":"1f9b8-1f3fd-200d-2642-fe0f","🦸🏾‍♂️":"1f9b8-1f3fe-200d-2642-fe0f","🦸🏿‍♂️":"1f9b8-1f3ff-200d-2642-fe0f","🦸🏻‍♀️":"1f9b8-1f3fb-200d-2640-fe0f","🦸🏼‍♀️":"1f9b8-1f3fc-200d-2640-fe0f","🦸🏽‍♀️":"1f9b8-1f3fd-200d-2640-fe0f","🦸🏾‍♀️":"1f9b8-1f3fe-200d-2640-fe0f","🦸🏿‍♀️":"1f9b8-1f3ff-200d-2640-fe0f","🦹🏻‍♂️":"1f9b9-1f3fb-200d-2642-fe0f","🦹🏼‍♂️":"1f9b9-1f3fc-200d-2642-fe0f","🦹🏽‍♂️":"1f9b9-1f3fd-200d-2642-fe0f","🦹🏾‍♂️":"1f9b9-1f3fe-200d-2642-fe0f","🦹🏿‍♂️":"1f9b9-1f3ff-200d-2642-fe0f","🦹🏻‍♀️":"1f9b9-1f3fb-200d-2640-fe0f","🦹🏼‍♀️":"1f9b9-1f3fc-200d-2640-fe0f","🦹🏽‍♀️":"1f9b9-1f3fd-200d-2640-fe0f","🦹🏾‍♀️":"1f9b9-1f3fe-200d-2640-fe0f","🦹🏿‍♀️":"1f9b9-1f3ff-200d-2640-fe0f","🧙🏻‍♂️":"1f9d9-1f3fb-200d-2642-fe0f","🧙🏼‍♂️":"1f9d9-1f3fc-200d-2642-fe0f","🧙🏽‍♂️":"1f9d9-1f3fd-200d-2642-fe0f","🧙🏾‍♂️":"1f9d9-1f3fe-200d-2642-fe0f","🧙🏿‍♂️":"1f9d9-1f3ff-200d-2642-fe0f","🧙🏻‍♀️":"1f9d9-1f3fb-200d-2640-fe0f","🧙🏼‍♀️":"1f9d9-1f3fc-200d-2640-fe0f","🧙🏽‍♀️":"1f9d9-1f3fd-200d-2640-fe0f","🧙🏾‍♀️":"1f9d9-1f3fe-200d-2640-fe0f","🧙🏿‍♀️":"1f9d9-1f3ff-200d-2640-fe0f","🧚🏻‍♂️":"1f9da-1f3fb-200d-2642-fe0f","🧚🏼‍♂️":"1f9da-1f3fc-200d-2642-fe0f","🧚🏽‍♂️":"1f9da-1f3fd-200d-2642-fe0f","🧚🏾‍♂️":"1f9da-1f3fe-200d-2642-fe0f","🧚🏿‍♂️":"1f9da-1f3ff-200d-2642-fe0f","🧚🏻‍♀️":"1f9da-1f3fb-200d-2640-fe0f","🧚🏼‍♀️":"1f9da-1f3fc-200d-2640-fe0f","🧚🏽‍♀️":"1f9da-1f3fd-200d-2640-fe0f","🧚🏾‍♀️":"1f9da-1f3fe-200d-2640-fe0f","🧚🏿‍♀️":"1f9da-1f3ff-200d-2640-fe0f","🧛🏻‍♂️":"1f9db-1f3fb-200d-2642-fe0f","🧛🏼‍♂️":"1f9db-1f3fc-200d-2642-fe0f","🧛🏽‍♂️":"1f9db-1f3fd-200d-2642-fe0f","🧛🏾‍♂️":"1f9db-1f3fe-200d-2642-fe0f","🧛🏿‍♂️":"1f9db-1f3ff-200d-2642-fe0f","🧛🏻‍♀️":"1f9db-1f3fb-200d-2640-fe0f","🧛🏼‍♀️":"1f9db-1f3fc-200d-2640-fe0f","🧛🏽‍♀️":"1f9db-1f3fd-200d-2640-fe0f","🧛🏾‍♀️":"1f9db-1f3fe-200d-2640-fe0f","🧛🏿‍♀️":"1f9db-1f3ff-200d-2640-fe0f","🧜🏻‍♂️":"1f9dc-1f3fb-200d-2642-fe0f","🧜🏼‍♂️":"1f9dc-1f3fc-200d-2642-fe0f","🧜🏽‍♂️":"1f9dc-1f3fd-200d-2642-fe0f","🧜🏾‍♂️":"1f9dc-1f3fe-200d-2642-fe0f","🧜🏿‍♂️":"1f9dc-1f3ff-200d-2642-fe0f","🧜🏻‍♀️":"1f9dc-1f3fb-200d-2640-fe0f","🧜🏼‍♀️":"1f9dc-1f3fc-200d-2640-fe0f","🧜🏽‍♀️":"1f9dc-1f3fd-200d-2640-fe0f","🧜🏾‍♀️":"1f9dc-1f3fe-200d-2640-fe0f","🧜🏿‍♀️":"1f9dc-1f3ff-200d-2640-fe0f","🧝🏻‍♂️":"1f9dd-1f3fb-200d-2642-fe0f","🧝🏼‍♂️":"1f9dd-1f3fc-200d-2642-fe0f","🧝🏽‍♂️":"1f9dd-1f3fd-200d-2642-fe0f","🧝🏾‍♂️":"1f9dd-1f3fe-200d-2642-fe0f","🧝🏿‍♂️":"1f9dd-1f3ff-200d-2642-fe0f","🧝🏻‍♀️":"1f9dd-1f3fb-200d-2640-fe0f","🧝🏼‍♀️":"1f9dd-1f3fc-200d-2640-fe0f","🧝🏽‍♀️":"1f9dd-1f3fd-200d-2640-fe0f","🧝🏾‍♀️":"1f9dd-1f3fe-200d-2640-fe0f","🧝🏿‍♀️":"1f9dd-1f3ff-200d-2640-fe0f","💆🏻‍♂️":"1f486-1f3fb-200d-2642-fe0f","💆🏼‍♂️":"1f486-1f3fc-200d-2642-fe0f","💆🏽‍♂️":"1f486-1f3fd-200d-2642-fe0f","💆🏾‍♂️":"1f486-1f3fe-200d-2642-fe0f","💆🏿‍♂️":"1f486-1f3ff-200d-2642-fe0f","💆🏻‍♀️":"1f486-1f3fb-200d-2640-fe0f","💆🏼‍♀️":"1f486-1f3fc-200d-2640-fe0f","💆🏽‍♀️":"1f486-1f3fd-200d-2640-fe0f","💆🏾‍♀️":"1f486-1f3fe-200d-2640-fe0f","💆🏿‍♀️":"1f486-1f3ff-200d-2640-fe0f","💇🏻‍♂️":"1f487-1f3fb-200d-2642-fe0f","💇🏼‍♂️":"1f487-1f3fc-200d-2642-fe0f","💇🏽‍♂️":"1f487-1f3fd-200d-2642-fe0f","💇🏾‍♂️":"1f487-1f3fe-200d-2642-fe0f","💇🏿‍♂️":"1f487-1f3ff-200d-2642-fe0f","💇🏻‍♀️":"1f487-1f3fb-200d-2640-fe0f","💇🏼‍♀️":"1f487-1f3fc-200d-2640-fe0f","💇🏽‍♀️":"1f487-1f3fd-200d-2640-fe0f","💇🏾‍♀️":"1f487-1f3fe-200d-2640-fe0f","💇🏿‍♀️":"1f487-1f3ff-200d-2640-fe0f","🚶🏻‍♂️":"1f6b6-1f3fb-200d-2642-fe0f","🚶🏼‍♂️":"1f6b6-1f3fc-200d-2642-fe0f","🚶🏽‍♂️":"1f6b6-1f3fd-200d-2642-fe0f","🚶🏾‍♂️":"1f6b6-1f3fe-200d-2642-fe0f","🚶🏿‍♂️":"1f6b6-1f3ff-200d-2642-fe0f","🚶🏻‍♀️":"1f6b6-1f3fb-200d-2640-fe0f","🚶🏼‍♀️":"1f6b6-1f3fc-200d-2640-fe0f","🚶🏽‍♀️":"1f6b6-1f3fd-200d-2640-fe0f","🚶🏾‍♀️":"1f6b6-1f3fe-200d-2640-fe0f","🚶🏿‍♀️":"1f6b6-1f3ff-200d-2640-fe0f","🚶🏻‍➡️":"1f6b6-1f3fb-200d-27a1-fe0f","🚶🏼‍➡️":"1f6b6-1f3fc-200d-27a1-fe0f","🚶🏽‍➡️":"1f6b6-1f3fd-200d-27a1-fe0f","🚶🏾‍➡️":"1f6b6-1f3fe-200d-27a1-fe0f","🚶🏿‍➡️":"1f6b6-1f3ff-200d-27a1-fe0f","🚶‍♀‍➡":"1f6b6-200d-2640-fe0f-200d-27a1-fe0f","🚶‍♂‍➡":"1f6b6-200d-2642-fe0f-200d-27a1-fe0f","🧍🏻‍♂️":"1f9cd-1f3fb-200d-2642-fe0f","🧍🏼‍♂️":"1f9cd-1f3fc-200d-2642-fe0f","🧍🏽‍♂️":"1f9cd-1f3fd-200d-2642-fe0f","🧍🏾‍♂️":"1f9cd-1f3fe-200d-2642-fe0f","🧍🏿‍♂️":"1f9cd-1f3ff-200d-2642-fe0f","🧍🏻‍♀️":"1f9cd-1f3fb-200d-2640-fe0f","🧍🏼‍♀️":"1f9cd-1f3fc-200d-2640-fe0f","🧍🏽‍♀️":"1f9cd-1f3fd-200d-2640-fe0f","🧍🏾‍♀️":"1f9cd-1f3fe-200d-2640-fe0f","🧍🏿‍♀️":"1f9cd-1f3ff-200d-2640-fe0f","🧎🏻‍♂️":"1f9ce-1f3fb-200d-2642-fe0f","🧎🏼‍♂️":"1f9ce-1f3fc-200d-2642-fe0f","🧎🏽‍♂️":"1f9ce-1f3fd-200d-2642-fe0f","🧎🏾‍♂️":"1f9ce-1f3fe-200d-2642-fe0f","🧎🏿‍♂️":"1f9ce-1f3ff-200d-2642-fe0f","🧎🏻‍♀️":"1f9ce-1f3fb-200d-2640-fe0f","🧎🏼‍♀️":"1f9ce-1f3fc-200d-2640-fe0f","🧎🏽‍♀️":"1f9ce-1f3fd-200d-2640-fe0f","🧎🏾‍♀️":"1f9ce-1f3fe-200d-2640-fe0f","🧎🏿‍♀️":"1f9ce-1f3ff-200d-2640-fe0f","🧎🏻‍➡️":"1f9ce-1f3fb-200d-27a1-fe0f","🧎🏼‍➡️":"1f9ce-1f3fc-200d-27a1-fe0f","🧎🏽‍➡️":"1f9ce-1f3fd-200d-27a1-fe0f","🧎🏾‍➡️":"1f9ce-1f3fe-200d-27a1-fe0f","🧎🏿‍➡️":"1f9ce-1f3ff-200d-27a1-fe0f","🧎‍♀‍➡":"1f9ce-200d-2640-fe0f-200d-27a1-fe0f","🧎‍♂‍➡":"1f9ce-200d-2642-fe0f-200d-27a1-fe0f","🧑‍🦯‍➡":"1f9d1-200d-1f9af-200d-27a1-fe0f","👨‍🦯‍➡":"1f468-200d-1f9af-200d-27a1-fe0f","👩‍🦯‍➡":"1f469-200d-1f9af-200d-27a1-fe0f","🧑‍🦼‍➡":"1f9d1-200d-1f9bc-200d-27a1-fe0f","👨‍🦼‍➡":"1f468-200d-1f9bc-200d-27a1-fe0f","👩‍🦼‍➡":"1f469-200d-1f9bc-200d-27a1-fe0f","🧑‍🦽‍➡":"1f9d1-200d-1f9bd-200d-27a1-fe0f","👨‍🦽‍➡":"1f468-200d-1f9bd-200d-27a1-fe0f","👩‍🦽‍➡":"1f469-200d-1f9bd-200d-27a1-fe0f","🏃🏻‍♂️":"1f3c3-1f3fb-200d-2642-fe0f","🏃🏼‍♂️":"1f3c3-1f3fc-200d-2642-fe0f","🏃🏽‍♂️":"1f3c3-1f3fd-200d-2642-fe0f","🏃🏾‍♂️":"1f3c3-1f3fe-200d-2642-fe0f","🏃🏿‍♂️":"1f3c3-1f3ff-200d-2642-fe0f","🏃🏻‍♀️":"1f3c3-1f3fb-200d-2640-fe0f","🏃🏼‍♀️":"1f3c3-1f3fc-200d-2640-fe0f","🏃🏽‍♀️":"1f3c3-1f3fd-200d-2640-fe0f","🏃🏾‍♀️":"1f3c3-1f3fe-200d-2640-fe0f","🏃🏿‍♀️":"1f3c3-1f3ff-200d-2640-fe0f","🏃🏻‍➡️":"1f3c3-1f3fb-200d-27a1-fe0f","🏃🏼‍➡️":"1f3c3-1f3fc-200d-27a1-fe0f","🏃🏽‍➡️":"1f3c3-1f3fd-200d-27a1-fe0f","🏃🏾‍➡️":"1f3c3-1f3fe-200d-27a1-fe0f","🏃🏿‍➡️":"1f3c3-1f3ff-200d-27a1-fe0f","🏃‍♀‍➡":"1f3c3-200d-2640-fe0f-200d-27a1-fe0f","🏃‍♂‍➡":"1f3c3-200d-2642-fe0f-200d-27a1-fe0f","🧖🏻‍♂️":"1f9d6-1f3fb-200d-2642-fe0f","🧖🏼‍♂️":"1f9d6-1f3fc-200d-2642-fe0f","🧖🏽‍♂️":"1f9d6-1f3fd-200d-2642-fe0f","🧖🏾‍♂️":"1f9d6-1f3fe-200d-2642-fe0f","🧖🏿‍♂️":"1f9d6-1f3ff-200d-2642-fe0f","🧖🏻‍♀️":"1f9d6-1f3fb-200d-2640-fe0f","🧖🏼‍♀️":"1f9d6-1f3fc-200d-2640-fe0f","🧖🏽‍♀️":"1f9d6-1f3fd-200d-2640-fe0f","🧖🏾‍♀️":"1f9d6-1f3fe-200d-2640-fe0f","🧖🏿‍♀️":"1f9d6-1f3ff-200d-2640-fe0f","🧗🏻‍♂️":"1f9d7-1f3fb-200d-2642-fe0f","🧗🏼‍♂️":"1f9d7-1f3fc-200d-2642-fe0f","🧗🏽‍♂️":"1f9d7-1f3fd-200d-2642-fe0f","🧗🏾‍♂️":"1f9d7-1f3fe-200d-2642-fe0f","🧗🏿‍♂️":"1f9d7-1f3ff-200d-2642-fe0f","🧗🏻‍♀️":"1f9d7-1f3fb-200d-2640-fe0f","🧗🏼‍♀️":"1f9d7-1f3fc-200d-2640-fe0f","🧗🏽‍♀️":"1f9d7-1f3fd-200d-2640-fe0f","🧗🏾‍♀️":"1f9d7-1f3fe-200d-2640-fe0f","🧗🏿‍♀️":"1f9d7-1f3ff-200d-2640-fe0f","🏌️‍♂️":"1f3cc-fe0f-200d-2642-fe0f","🏌🏻‍♂️":"1f3cc-1f3fb-200d-2642-fe0f","🏌🏼‍♂️":"1f3cc-1f3fc-200d-2642-fe0f","🏌🏽‍♂️":"1f3cc-1f3fd-200d-2642-fe0f","🏌🏾‍♂️":"1f3cc-1f3fe-200d-2642-fe0f","🏌🏿‍♂️":"1f3cc-1f3ff-200d-2642-fe0f","🏌️‍♀️":"1f3cc-fe0f-200d-2640-fe0f","🏌🏻‍♀️":"1f3cc-1f3fb-200d-2640-fe0f","🏌🏼‍♀️":"1f3cc-1f3fc-200d-2640-fe0f","🏌🏽‍♀️":"1f3cc-1f3fd-200d-2640-fe0f","🏌🏾‍♀️":"1f3cc-1f3fe-200d-2640-fe0f","🏌🏿‍♀️":"1f3cc-1f3ff-200d-2640-fe0f","🏄🏻‍♂️":"1f3c4-1f3fb-200d-2642-fe0f","🏄🏼‍♂️":"1f3c4-1f3fc-200d-2642-fe0f","🏄🏽‍♂️":"1f3c4-1f3fd-200d-2642-fe0f","🏄🏾‍♂️":"1f3c4-1f3fe-200d-2642-fe0f","🏄🏿‍♂️":"1f3c4-1f3ff-200d-2642-fe0f","🏄🏻‍♀️":"1f3c4-1f3fb-200d-2640-fe0f","🏄🏼‍♀️":"1f3c4-1f3fc-200d-2640-fe0f","🏄🏽‍♀️":"1f3c4-1f3fd-200d-2640-fe0f","🏄🏾‍♀️":"1f3c4-1f3fe-200d-2640-fe0f","🏄🏿‍♀️":"1f3c4-1f3ff-200d-2640-fe0f","🚣🏻‍♂️":"1f6a3-1f3fb-200d-2642-fe0f","🚣🏼‍♂️":"1f6a3-1f3fc-200d-2642-fe0f","🚣🏽‍♂️":"1f6a3-1f3fd-200d-2642-fe0f","🚣🏾‍♂️":"1f6a3-1f3fe-200d-2642-fe0f","🚣🏿‍♂️":"1f6a3-1f3ff-200d-2642-fe0f","🚣🏻‍♀️":"1f6a3-1f3fb-200d-2640-fe0f","🚣🏼‍♀️":"1f6a3-1f3fc-200d-2640-fe0f","🚣🏽‍♀️":"1f6a3-1f3fd-200d-2640-fe0f","🚣🏾‍♀️":"1f6a3-1f3fe-200d-2640-fe0f","🚣🏿‍♀️":"1f6a3-1f3ff-200d-2640-fe0f","🏊🏻‍♂️":"1f3ca-1f3fb-200d-2642-fe0f","🏊🏼‍♂️":"1f3ca-1f3fc-200d-2642-fe0f","🏊🏽‍♂️":"1f3ca-1f3fd-200d-2642-fe0f","🏊🏾‍♂️":"1f3ca-1f3fe-200d-2642-fe0f","🏊🏿‍♂️":"1f3ca-1f3ff-200d-2642-fe0f","🏊🏻‍♀️":"1f3ca-1f3fb-200d-2640-fe0f","🏊🏼‍♀️":"1f3ca-1f3fc-200d-2640-fe0f","🏊🏽‍♀️":"1f3ca-1f3fd-200d-2640-fe0f","🏊🏾‍♀️":"1f3ca-1f3fe-200d-2640-fe0f","🏊🏿‍♀️":"1f3ca-1f3ff-200d-2640-fe0f","⛹️‍♂️":"26f9-fe0f-200d-2642-fe0f","⛹🏻‍♂️":"26f9-1f3fb-200d-2642-fe0f","⛹🏼‍♂️":"26f9-1f3fc-200d-2642-fe0f","⛹🏽‍♂️":"26f9-1f3fd-200d-2642-fe0f","⛹🏾‍♂️":"26f9-1f3fe-200d-2642-fe0f","⛹🏿‍♂️":"26f9-1f3ff-200d-2642-fe0f","⛹️‍♀️":"26f9-fe0f-200d-2640-fe0f","⛹🏻‍♀️":"26f9-1f3fb-200d-2640-fe0f","⛹🏼‍♀️":"26f9-1f3fc-200d-2640-fe0f","⛹🏽‍♀️":"26f9-1f3fd-200d-2640-fe0f","⛹🏾‍♀️":"26f9-1f3fe-200d-2640-fe0f","⛹🏿‍♀️":"26f9-1f3ff-200d-2640-fe0f","🏋️‍♂️":"1f3cb-fe0f-200d-2642-fe0f","🏋🏻‍♂️":"1f3cb-1f3fb-200d-2642-fe0f","🏋🏼‍♂️":"1f3cb-1f3fc-200d-2642-fe0f","🏋🏽‍♂️":"1f3cb-1f3fd-200d-2642-fe0f","🏋🏾‍♂️":"1f3cb-1f3fe-200d-2642-fe0f","🏋🏿‍♂️":"1f3cb-1f3ff-200d-2642-fe0f","🏋️‍♀️":"1f3cb-fe0f-200d-2640-fe0f","🏋🏻‍♀️":"1f3cb-1f3fb-200d-2640-fe0f","🏋🏼‍♀️":"1f3cb-1f3fc-200d-2640-fe0f","🏋🏽‍♀️":"1f3cb-1f3fd-200d-2640-fe0f","🏋🏾‍♀️":"1f3cb-1f3fe-200d-2640-fe0f","🏋🏿‍♀️":"1f3cb-1f3ff-200d-2640-fe0f","🚴🏻‍♂️":"1f6b4-1f3fb-200d-2642-fe0f","🚴🏼‍♂️":"1f6b4-1f3fc-200d-2642-fe0f","🚴🏽‍♂️":"1f6b4-1f3fd-200d-2642-fe0f","🚴🏾‍♂️":"1f6b4-1f3fe-200d-2642-fe0f","🚴🏿‍♂️":"1f6b4-1f3ff-200d-2642-fe0f","🚴🏻‍♀️":"1f6b4-1f3fb-200d-2640-fe0f","🚴🏼‍♀️":"1f6b4-1f3fc-200d-2640-fe0f","🚴🏽‍♀️":"1f6b4-1f3fd-200d-2640-fe0f","🚴🏾‍♀️":"1f6b4-1f3fe-200d-2640-fe0f","🚴🏿‍♀️":"1f6b4-1f3ff-200d-2640-fe0f","🚵🏻‍♂️":"1f6b5-1f3fb-200d-2642-fe0f","🚵🏼‍♂️":"1f6b5-1f3fc-200d-2642-fe0f","🚵🏽‍♂️":"1f6b5-1f3fd-200d-2642-fe0f","🚵🏾‍♂️":"1f6b5-1f3fe-200d-2642-fe0f","🚵🏿‍♂️":"1f6b5-1f3ff-200d-2642-fe0f","🚵🏻‍♀️":"1f6b5-1f3fb-200d-2640-fe0f","🚵🏼‍♀️":"1f6b5-1f3fc-200d-2640-fe0f","🚵🏽‍♀️":"1f6b5-1f3fd-200d-2640-fe0f","🚵🏾‍♀️":"1f6b5-1f3fe-200d-2640-fe0f","🚵🏿‍♀️":"1f6b5-1f3ff-200d-2640-fe0f","🤸🏻‍♂️":"1f938-1f3fb-200d-2642-fe0f","🤸🏼‍♂️":"1f938-1f3fc-200d-2642-fe0f","🤸🏽‍♂️":"1f938-1f3fd-200d-2642-fe0f","🤸🏾‍♂️":"1f938-1f3fe-200d-2642-fe0f","🤸🏿‍♂️":"1f938-1f3ff-200d-2642-fe0f","🤸🏻‍♀️":"1f938-1f3fb-200d-2640-fe0f","🤸🏼‍♀️":"1f938-1f3fc-200d-2640-fe0f","🤸🏽‍♀️":"1f938-1f3fd-200d-2640-fe0f","🤸🏾‍♀️":"1f938-1f3fe-200d-2640-fe0f","🤸🏿‍♀️":"1f938-1f3ff-200d-2640-fe0f","🤽🏻‍♂️":"1f93d-1f3fb-200d-2642-fe0f","🤽🏼‍♂️":"1f93d-1f3fc-200d-2642-fe0f","🤽🏽‍♂️":"1f93d-1f3fd-200d-2642-fe0f","🤽🏾‍♂️":"1f93d-1f3fe-200d-2642-fe0f","🤽🏿‍♂️":"1f93d-1f3ff-200d-2642-fe0f","🤽🏻‍♀️":"1f93d-1f3fb-200d-2640-fe0f","🤽🏼‍♀️":"1f93d-1f3fc-200d-2640-fe0f","🤽🏽‍♀️":"1f93d-1f3fd-200d-2640-fe0f","🤽🏾‍♀️":"1f93d-1f3fe-200d-2640-fe0f","🤽🏿‍♀️":"1f93d-1f3ff-200d-2640-fe0f","🤾🏻‍♂️":"1f93e-1f3fb-200d-2642-fe0f","🤾🏼‍♂️":"1f93e-1f3fc-200d-2642-fe0f","🤾🏽‍♂️":"1f93e-1f3fd-200d-2642-fe0f","🤾🏾‍♂️":"1f93e-1f3fe-200d-2642-fe0f","🤾🏿‍♂️":"1f93e-1f3ff-200d-2642-fe0f","🤾🏻‍♀️":"1f93e-1f3fb-200d-2640-fe0f","🤾🏼‍♀️":"1f93e-1f3fc-200d-2640-fe0f","🤾🏽‍♀️":"1f93e-1f3fd-200d-2640-fe0f","🤾🏾‍♀️":"1f93e-1f3fe-200d-2640-fe0f","🤾🏿‍♀️":"1f93e-1f3ff-200d-2640-fe0f","🤹🏻‍♂️":"1f939-1f3fb-200d-2642-fe0f","🤹🏼‍♂️":"1f939-1f3fc-200d-2642-fe0f","🤹🏽‍♂️":"1f939-1f3fd-200d-2642-fe0f","🤹🏾‍♂️":"1f939-1f3fe-200d-2642-fe0f","🤹🏿‍♂️":"1f939-1f3ff-200d-2642-fe0f","🤹🏻‍♀️":"1f939-1f3fb-200d-2640-fe0f","🤹🏼‍♀️":"1f939-1f3fc-200d-2640-fe0f","🤹🏽‍♀️":"1f939-1f3fd-200d-2640-fe0f","🤹🏾‍♀️":"1f939-1f3fe-200d-2640-fe0f","🤹🏿‍♀️":"1f939-1f3ff-200d-2640-fe0f","🧘🏻‍♂️":"1f9d8-1f3fb-200d-2642-fe0f","🧘🏼‍♂️":"1f9d8-1f3fc-200d-2642-fe0f","🧘🏽‍♂️":"1f9d8-1f3fd-200d-2642-fe0f","🧘🏾‍♂️":"1f9d8-1f3fe-200d-2642-fe0f","🧘🏿‍♂️":"1f9d8-1f3ff-200d-2642-fe0f","🧘🏻‍♀️":"1f9d8-1f3fb-200d-2640-fe0f","🧘🏼‍♀️":"1f9d8-1f3fc-200d-2640-fe0f","🧘🏽‍♀️":"1f9d8-1f3fd-200d-2640-fe0f","🧘🏾‍♀️":"1f9d8-1f3fe-200d-2640-fe0f","🧘🏿‍♀️":"1f9d8-1f3ff-200d-2640-fe0f","🧑‍🤝‍🧑":"1f9d1-200d-1f91d-200d-1f9d1","👩‍❤‍👨":"1f469-200d-2764-fe0f-200d-1f468","👨‍❤‍👨":"1f468-200d-2764-fe0f-200d-1f468","👩‍❤‍👩":"1f469-200d-2764-fe0f-200d-1f469","👨‍👩‍👦":"1f468-200d-1f469-200d-1f466","👨‍👩‍👧":"1f468-200d-1f469-200d-1f467","👨‍👨‍👦":"1f468-200d-1f468-200d-1f466","👨‍👨‍👧":"1f468-200d-1f468-200d-1f467","👩‍👩‍👦":"1f469-200d-1f469-200d-1f466","👩‍👩‍👧":"1f469-200d-1f469-200d-1f467","👨‍👦‍👦":"1f468-200d-1f466-200d-1f466","👨‍👧‍👦":"1f468-200d-1f467-200d-1f466","👨‍👧‍👧":"1f468-200d-1f467-200d-1f467","👩‍👦‍👦":"1f469-200d-1f466-200d-1f466","👩‍👧‍👦":"1f469-200d-1f467-200d-1f466","👩‍👧‍👧":"1f469-200d-1f467-200d-1f467","🧑‍🧑‍🧒":"1f9d1-200d-1f9d1-200d-1f9d2","🧑‍🧒‍🧒":"1f9d1-200d-1f9d2-200d-1f9d2","🏳️‍⚧️":"1f3f3-fe0f-200d-26a7-fe0f","🚶‍♀‍➡️":"1f6b6-200d-2640-fe0f-200d-27a1-fe0f","🚶‍♀️‍➡":"1f6b6-200d-2640-fe0f-200d-27a1-fe0f","🚶🏻‍♀‍➡":"1f6b6-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🚶🏼‍♀‍➡":"1f6b6-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🚶🏽‍♀‍➡":"1f6b6-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🚶🏾‍♀‍➡":"1f6b6-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","🚶🏿‍♀‍➡":"1f6b6-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🚶‍♂‍➡️":"1f6b6-200d-2642-fe0f-200d-27a1-fe0f","🚶‍♂️‍➡":"1f6b6-200d-2642-fe0f-200d-27a1-fe0f","🚶🏻‍♂‍➡":"1f6b6-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🚶🏼‍♂‍➡":"1f6b6-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🚶🏽‍♂‍➡":"1f6b6-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🚶🏾‍♂‍➡":"1f6b6-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🚶🏿‍♂‍➡":"1f6b6-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","🧎‍♀‍➡️":"1f9ce-200d-2640-fe0f-200d-27a1-fe0f","🧎‍♀️‍➡":"1f9ce-200d-2640-fe0f-200d-27a1-fe0f","🧎🏻‍♀‍➡":"1f9ce-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🧎🏼‍♀‍➡":"1f9ce-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🧎🏽‍♀‍➡":"1f9ce-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🧎🏾‍♀‍➡":"1f9ce-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","🧎🏿‍♀‍➡":"1f9ce-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🧎‍♂‍➡️":"1f9ce-200d-2642-fe0f-200d-27a1-fe0f","🧎‍♂️‍➡":"1f9ce-200d-2642-fe0f-200d-27a1-fe0f","🧎🏻‍♂‍➡":"1f9ce-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🧎🏼‍♂‍➡":"1f9ce-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🧎🏽‍♂‍➡":"1f9ce-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🧎🏾‍♂‍➡":"1f9ce-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🧎🏿‍♂‍➡":"1f9ce-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","🧑‍🦯‍➡️":"1f9d1-200d-1f9af-200d-27a1-fe0f","🧑🏻‍🦯‍➡":"1f9d1-1f3fb-200d-1f9af-200d-27a1-fe0f","🧑🏼‍🦯‍➡":"1f9d1-1f3fc-200d-1f9af-200d-27a1-fe0f","🧑🏽‍🦯‍➡":"1f9d1-1f3fd-200d-1f9af-200d-27a1-fe0f","🧑🏾‍🦯‍➡":"1f9d1-1f3fe-200d-1f9af-200d-27a1-fe0f","🧑🏿‍🦯‍➡":"1f9d1-1f3ff-200d-1f9af-200d-27a1-fe0f","👨‍🦯‍➡️":"1f468-200d-1f9af-200d-27a1-fe0f","👨🏻‍🦯‍➡":"1f468-1f3fb-200d-1f9af-200d-27a1-fe0f","👨🏼‍🦯‍➡":"1f468-1f3fc-200d-1f9af-200d-27a1-fe0f","👨🏽‍🦯‍➡":"1f468-1f3fd-200d-1f9af-200d-27a1-fe0f","👨🏾‍🦯‍➡":"1f468-1f3fe-200d-1f9af-200d-27a1-fe0f","👨🏿‍🦯‍➡":"1f468-1f3ff-200d-1f9af-200d-27a1-fe0f","👩‍🦯‍➡️":"1f469-200d-1f9af-200d-27a1-fe0f","👩🏻‍🦯‍➡":"1f469-1f3fb-200d-1f9af-200d-27a1-fe0f","👩🏼‍🦯‍➡":"1f469-1f3fc-200d-1f9af-200d-27a1-fe0f","👩🏽‍🦯‍➡":"1f469-1f3fd-200d-1f9af-200d-27a1-fe0f","👩🏾‍🦯‍➡":"1f469-1f3fe-200d-1f9af-200d-27a1-fe0f","👩🏿‍🦯‍➡":"1f469-1f3ff-200d-1f9af-200d-27a1-fe0f","🧑‍🦼‍➡️":"1f9d1-200d-1f9bc-200d-27a1-fe0f","🧑🏻‍🦼‍➡":"1f9d1-1f3fb-200d-1f9bc-200d-27a1-fe0f","🧑🏼‍🦼‍➡":"1f9d1-1f3fc-200d-1f9bc-200d-27a1-fe0f","🧑🏽‍🦼‍➡":"1f9d1-1f3fd-200d-1f9bc-200d-27a1-fe0f","🧑🏾‍🦼‍➡":"1f9d1-1f3fe-200d-1f9bc-200d-27a1-fe0f","🧑🏿‍🦼‍➡":"1f9d1-1f3ff-200d-1f9bc-200d-27a1-fe0f","👨‍🦼‍➡️":"1f468-200d-1f9bc-200d-27a1-fe0f","👨🏻‍🦼‍➡":"1f468-1f3fb-200d-1f9bc-200d-27a1-fe0f","👨🏼‍🦼‍➡":"1f468-1f3fc-200d-1f9bc-200d-27a1-fe0f","👨🏽‍🦼‍➡":"1f468-1f3fd-200d-1f9bc-200d-27a1-fe0f","👨🏾‍🦼‍➡":"1f468-1f3fe-200d-1f9bc-200d-27a1-fe0f","👨🏿‍🦼‍➡":"1f468-1f3ff-200d-1f9bc-200d-27a1-fe0f","👩‍🦼‍➡️":"1f469-200d-1f9bc-200d-27a1-fe0f","👩🏻‍🦼‍➡":"1f469-1f3fb-200d-1f9bc-200d-27a1-fe0f","👩🏼‍🦼‍➡":"1f469-1f3fc-200d-1f9bc-200d-27a1-fe0f","👩🏽‍🦼‍➡":"1f469-1f3fd-200d-1f9bc-200d-27a1-fe0f","👩🏾‍🦼‍➡":"1f469-1f3fe-200d-1f9bc-200d-27a1-fe0f","👩🏿‍🦼‍➡":"1f469-1f3ff-200d-1f9bc-200d-27a1-fe0f","🧑‍🦽‍➡️":"1f9d1-200d-1f9bd-200d-27a1-fe0f","🧑🏻‍🦽‍➡":"1f9d1-1f3fb-200d-1f9bd-200d-27a1-fe0f","🧑🏼‍🦽‍➡":"1f9d1-1f3fc-200d-1f9bd-200d-27a1-fe0f","🧑🏽‍🦽‍➡":"1f9d1-1f3fd-200d-1f9bd-200d-27a1-fe0f","🧑🏾‍🦽‍➡":"1f9d1-1f3fe-200d-1f9bd-200d-27a1-fe0f","🧑🏿‍🦽‍➡":"1f9d1-1f3ff-200d-1f9bd-200d-27a1-fe0f","👨‍🦽‍➡️":"1f468-200d-1f9bd-200d-27a1-fe0f","👨🏻‍🦽‍➡":"1f468-1f3fb-200d-1f9bd-200d-27a1-fe0f","👨🏼‍🦽‍➡":"1f468-1f3fc-200d-1f9bd-200d-27a1-fe0f","👨🏽‍🦽‍➡":"1f468-1f3fd-200d-1f9bd-200d-27a1-fe0f","👨🏾‍🦽‍➡":"1f468-1f3fe-200d-1f9bd-200d-27a1-fe0f","👨🏿‍🦽‍➡":"1f468-1f3ff-200d-1f9bd-200d-27a1-fe0f","👩‍🦽‍➡️":"1f469-200d-1f9bd-200d-27a1-fe0f","👩🏻‍🦽‍➡":"1f469-1f3fb-200d-1f9bd-200d-27a1-fe0f","👩🏼‍🦽‍➡":"1f469-1f3fc-200d-1f9bd-200d-27a1-fe0f","👩🏽‍🦽‍➡":"1f469-1f3fd-200d-1f9bd-200d-27a1-fe0f","👩🏾‍🦽‍➡":"1f469-1f3fe-200d-1f9bd-200d-27a1-fe0f","👩🏿‍🦽‍➡":"1f469-1f3ff-200d-1f9bd-200d-27a1-fe0f","🏃‍♀‍➡️":"1f3c3-200d-2640-fe0f-200d-27a1-fe0f","🏃‍♀️‍➡":"1f3c3-200d-2640-fe0f-200d-27a1-fe0f","🏃🏻‍♀‍➡":"1f3c3-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🏃🏼‍♀‍➡":"1f3c3-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🏃🏽‍♀‍➡":"1f3c3-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🏃🏾‍♀‍➡":"1f3c3-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","🏃🏿‍♀‍➡":"1f3c3-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🏃‍♂‍➡️":"1f3c3-200d-2642-fe0f-200d-27a1-fe0f","🏃‍♂️‍➡":"1f3c3-200d-2642-fe0f-200d-27a1-fe0f","🏃🏻‍♂‍➡":"1f3c3-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🏃🏼‍♂‍➡":"1f3c3-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🏃🏽‍♂‍➡":"1f3c3-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🏃🏾‍♂‍➡":"1f3c3-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🏃🏿‍♂‍➡":"1f3c3-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","👩‍❤️‍👨":"1f469-200d-2764-fe0f-200d-1f468","👨‍❤️‍👨":"1f468-200d-2764-fe0f-200d-1f468","👩‍❤️‍👩":"1f469-200d-2764-fe0f-200d-1f469","🚶‍♀️‍➡️":"1f6b6-200d-2640-fe0f-200d-27a1-fe0f","🚶🏻‍♀‍➡️":"1f6b6-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🚶🏻‍♀️‍➡":"1f6b6-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🚶🏼‍♀‍➡️":"1f6b6-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🚶🏼‍♀️‍➡":"1f6b6-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🚶🏽‍♀‍➡️":"1f6b6-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🚶🏽‍♀️‍➡":"1f6b6-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🚶🏾‍♀‍➡️":"1f6b6-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","🚶🏾‍♀️‍➡":"1f6b6-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","🚶🏿‍♀‍➡️":"1f6b6-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🚶🏿‍♀️‍➡":"1f6b6-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🚶‍♂️‍➡️":"1f6b6-200d-2642-fe0f-200d-27a1-fe0f","🚶🏻‍♂‍➡️":"1f6b6-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🚶🏻‍♂️‍➡":"1f6b6-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🚶🏼‍♂‍➡️":"1f6b6-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🚶🏼‍♂️‍➡":"1f6b6-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🚶🏽‍♂‍➡️":"1f6b6-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🚶🏽‍♂️‍➡":"1f6b6-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🚶🏾‍♂‍➡️":"1f6b6-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🚶🏾‍♂️‍➡":"1f6b6-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🚶🏿‍♂‍➡️":"1f6b6-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","🚶🏿‍♂️‍➡":"1f6b6-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","🧎‍♀️‍➡️":"1f9ce-200d-2640-fe0f-200d-27a1-fe0f","🧎🏻‍♀‍➡️":"1f9ce-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🧎🏻‍♀️‍➡":"1f9ce-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🧎🏼‍♀‍➡️":"1f9ce-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🧎🏼‍♀️‍➡":"1f9ce-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🧎🏽‍♀‍➡️":"1f9ce-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🧎🏽‍♀️‍➡":"1f9ce-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🧎🏾‍♀‍➡️":"1f9ce-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","🧎🏾‍♀️‍➡":"1f9ce-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","🧎🏿‍♀‍➡️":"1f9ce-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🧎🏿‍♀️‍➡":"1f9ce-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🧎‍♂️‍➡️":"1f9ce-200d-2642-fe0f-200d-27a1-fe0f","🧎🏻‍♂‍➡️":"1f9ce-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🧎🏻‍♂️‍➡":"1f9ce-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🧎🏼‍♂‍➡️":"1f9ce-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🧎🏼‍♂️‍➡":"1f9ce-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🧎🏽‍♂‍➡️":"1f9ce-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🧎🏽‍♂️‍➡":"1f9ce-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🧎🏾‍♂‍➡️":"1f9ce-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🧎🏾‍♂️‍➡":"1f9ce-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🧎🏿‍♂‍➡️":"1f9ce-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","🧎🏿‍♂️‍➡":"1f9ce-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","🧑🏻‍🦯‍➡️":"1f9d1-1f3fb-200d-1f9af-200d-27a1-fe0f","🧑🏼‍🦯‍➡️":"1f9d1-1f3fc-200d-1f9af-200d-27a1-fe0f","🧑🏽‍🦯‍➡️":"1f9d1-1f3fd-200d-1f9af-200d-27a1-fe0f","🧑🏾‍🦯‍➡️":"1f9d1-1f3fe-200d-1f9af-200d-27a1-fe0f","🧑🏿‍🦯‍➡️":"1f9d1-1f3ff-200d-1f9af-200d-27a1-fe0f","👨🏻‍🦯‍➡️":"1f468-1f3fb-200d-1f9af-200d-27a1-fe0f","👨🏼‍🦯‍➡️":"1f468-1f3fc-200d-1f9af-200d-27a1-fe0f","👨🏽‍🦯‍➡️":"1f468-1f3fd-200d-1f9af-200d-27a1-fe0f","👨🏾‍🦯‍➡️":"1f468-1f3fe-200d-1f9af-200d-27a1-fe0f","👨🏿‍🦯‍➡️":"1f468-1f3ff-200d-1f9af-200d-27a1-fe0f","👩🏻‍🦯‍➡️":"1f469-1f3fb-200d-1f9af-200d-27a1-fe0f","👩🏼‍🦯‍➡️":"1f469-1f3fc-200d-1f9af-200d-27a1-fe0f","👩🏽‍🦯‍➡️":"1f469-1f3fd-200d-1f9af-200d-27a1-fe0f","👩🏾‍🦯‍➡️":"1f469-1f3fe-200d-1f9af-200d-27a1-fe0f","👩🏿‍🦯‍➡️":"1f469-1f3ff-200d-1f9af-200d-27a1-fe0f","🧑🏻‍🦼‍➡️":"1f9d1-1f3fb-200d-1f9bc-200d-27a1-fe0f","🧑🏼‍🦼‍➡️":"1f9d1-1f3fc-200d-1f9bc-200d-27a1-fe0f","🧑🏽‍🦼‍➡️":"1f9d1-1f3fd-200d-1f9bc-200d-27a1-fe0f","🧑🏾‍🦼‍➡️":"1f9d1-1f3fe-200d-1f9bc-200d-27a1-fe0f","🧑🏿‍🦼‍➡️":"1f9d1-1f3ff-200d-1f9bc-200d-27a1-fe0f","👨🏻‍🦼‍➡️":"1f468-1f3fb-200d-1f9bc-200d-27a1-fe0f","👨🏼‍🦼‍➡️":"1f468-1f3fc-200d-1f9bc-200d-27a1-fe0f","👨🏽‍🦼‍➡️":"1f468-1f3fd-200d-1f9bc-200d-27a1-fe0f","👨🏾‍🦼‍➡️":"1f468-1f3fe-200d-1f9bc-200d-27a1-fe0f","👨🏿‍🦼‍➡️":"1f468-1f3ff-200d-1f9bc-200d-27a1-fe0f","👩🏻‍🦼‍➡️":"1f469-1f3fb-200d-1f9bc-200d-27a1-fe0f","👩🏼‍🦼‍➡️":"1f469-1f3fc-200d-1f9bc-200d-27a1-fe0f","👩🏽‍🦼‍➡️":"1f469-1f3fd-200d-1f9bc-200d-27a1-fe0f","👩🏾‍🦼‍➡️":"1f469-1f3fe-200d-1f9bc-200d-27a1-fe0f","👩🏿‍🦼‍➡️":"1f469-1f3ff-200d-1f9bc-200d-27a1-fe0f","🧑🏻‍🦽‍➡️":"1f9d1-1f3fb-200d-1f9bd-200d-27a1-fe0f","🧑🏼‍🦽‍➡️":"1f9d1-1f3fc-200d-1f9bd-200d-27a1-fe0f","🧑🏽‍🦽‍➡️":"1f9d1-1f3fd-200d-1f9bd-200d-27a1-fe0f","🧑🏾‍🦽‍➡️":"1f9d1-1f3fe-200d-1f9bd-200d-27a1-fe0f","🧑🏿‍🦽‍➡️":"1f9d1-1f3ff-200d-1f9bd-200d-27a1-fe0f","👨🏻‍🦽‍➡️":"1f468-1f3fb-200d-1f9bd-200d-27a1-fe0f","👨🏼‍🦽‍➡️":"1f468-1f3fc-200d-1f9bd-200d-27a1-fe0f","👨🏽‍🦽‍➡️":"1f468-1f3fd-200d-1f9bd-200d-27a1-fe0f","👨🏾‍🦽‍➡️":"1f468-1f3fe-200d-1f9bd-200d-27a1-fe0f","👨🏿‍🦽‍➡️":"1f468-1f3ff-200d-1f9bd-200d-27a1-fe0f","👩🏻‍🦽‍➡️":"1f469-1f3fb-200d-1f9bd-200d-27a1-fe0f","👩🏼‍🦽‍➡️":"1f469-1f3fc-200d-1f9bd-200d-27a1-fe0f","👩🏽‍🦽‍➡️":"1f469-1f3fd-200d-1f9bd-200d-27a1-fe0f","👩🏾‍🦽‍➡️":"1f469-1f3fe-200d-1f9bd-200d-27a1-fe0f","👩🏿‍🦽‍➡️":"1f469-1f3ff-200d-1f9bd-200d-27a1-fe0f","🏃‍♀️‍➡️":"1f3c3-200d-2640-fe0f-200d-27a1-fe0f","🏃🏻‍♀‍➡️":"1f3c3-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🏃🏻‍♀️‍➡":"1f3c3-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🏃🏼‍♀‍➡️":"1f3c3-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🏃🏼‍♀️‍➡":"1f3c3-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🏃🏽‍♀‍➡️":"1f3c3-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🏃🏽‍♀️‍➡":"1f3c3-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🏃🏾‍♀‍➡️":"1f3c3-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","🏃🏾‍♀️‍➡":"1f3c3-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","🏃🏿‍♀‍➡️":"1f3c3-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🏃🏿‍♀️‍➡":"1f3c3-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🏃‍♂️‍➡️":"1f3c3-200d-2642-fe0f-200d-27a1-fe0f","🏃🏻‍♂‍➡️":"1f3c3-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🏃🏻‍♂️‍➡":"1f3c3-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🏃🏼‍♂‍➡️":"1f3c3-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🏃🏼‍♂️‍➡":"1f3c3-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🏃🏽‍♂‍➡️":"1f3c3-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🏃🏽‍♂️‍➡":"1f3c3-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🏃🏾‍♂‍➡️":"1f3c3-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🏃🏾‍♂️‍➡":"1f3c3-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🏃🏿‍♂‍➡️":"1f3c3-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","🏃🏿‍♂️‍➡":"1f3c3-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","🧑🏻‍🤝‍🧑🏻":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fb","🧑🏻‍🤝‍🧑🏼":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fc","🧑🏻‍🤝‍🧑🏽":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fd","🧑🏻‍🤝‍🧑🏾":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fe","🧑🏻‍🤝‍🧑🏿":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3ff","🧑🏼‍🤝‍🧑🏻":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fb","🧑🏼‍🤝‍🧑🏼":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fc","🧑🏼‍🤝‍🧑🏽":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fd","🧑🏼‍🤝‍🧑🏾":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fe","🧑🏼‍🤝‍🧑🏿":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3ff","🧑🏽‍🤝‍🧑🏻":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fb","🧑🏽‍🤝‍🧑🏼":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fc","🧑🏽‍🤝‍🧑🏽":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fd","🧑🏽‍🤝‍🧑🏾":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fe","🧑🏽‍🤝‍🧑🏿":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3ff","🧑🏾‍🤝‍🧑🏻":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fb","🧑🏾‍🤝‍🧑🏼":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fc","🧑🏾‍🤝‍🧑🏽":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fd","🧑🏾‍🤝‍🧑🏾":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fe","🧑🏾‍🤝‍🧑🏿":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3ff","🧑🏿‍🤝‍🧑🏻":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fb","🧑🏿‍🤝‍🧑🏼":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fc","🧑🏿‍🤝‍🧑🏽":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fd","🧑🏿‍🤝‍🧑🏾":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fe","🧑🏿‍🤝‍🧑🏿":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3ff","👩🏻‍🤝‍👩🏼":"1f469-1f3fb-200d-1f91d-200d-1f469-1f3fc","👩🏻‍🤝‍👩🏽":"1f469-1f3fb-200d-1f91d-200d-1f469-1f3fd","👩🏻‍🤝‍👩🏾":"1f469-1f3fb-200d-1f91d-200d-1f469-1f3fe","👩🏻‍🤝‍👩🏿":"1f469-1f3fb-200d-1f91d-200d-1f469-1f3ff","👩🏼‍🤝‍👩🏻":"1f469-1f3fc-200d-1f91d-200d-1f469-1f3fb","👩🏼‍🤝‍👩🏽":"1f469-1f3fc-200d-1f91d-200d-1f469-1f3fd","👩🏼‍🤝‍👩🏾":"1f469-1f3fc-200d-1f91d-200d-1f469-1f3fe","👩🏼‍🤝‍👩🏿":"1f469-1f3fc-200d-1f91d-200d-1f469-1f3ff","👩🏽‍🤝‍👩🏻":"1f469-1f3fd-200d-1f91d-200d-1f469-1f3fb","👩🏽‍🤝‍👩🏼":"1f469-1f3fd-200d-1f91d-200d-1f469-1f3fc","👩🏽‍🤝‍👩🏾":"1f469-1f3fd-200d-1f91d-200d-1f469-1f3fe","👩🏽‍🤝‍👩🏿":"1f469-1f3fd-200d-1f91d-200d-1f469-1f3ff","👩🏾‍🤝‍👩🏻":"1f469-1f3fe-200d-1f91d-200d-1f469-1f3fb","👩🏾‍🤝‍👩🏼":"1f469-1f3fe-200d-1f91d-200d-1f469-1f3fc","👩🏾‍🤝‍👩🏽":"1f469-1f3fe-200d-1f91d-200d-1f469-1f3fd","👩🏾‍🤝‍👩🏿":"1f469-1f3fe-200d-1f91d-200d-1f469-1f3ff","👩🏿‍🤝‍👩🏻":"1f469-1f3ff-200d-1f91d-200d-1f469-1f3fb","👩🏿‍🤝‍👩🏼":"1f469-1f3ff-200d-1f91d-200d-1f469-1f3fc","👩🏿‍🤝‍👩🏽":"1f469-1f3ff-200d-1f91d-200d-1f469-1f3fd","👩🏿‍🤝‍👩🏾":"1f469-1f3ff-200d-1f91d-200d-1f469-1f3fe","👩🏻‍🤝‍👨🏼":"1f469-1f3fb-200d-1f91d-200d-1f468-1f3fc","👩🏻‍🤝‍👨🏽":"1f469-1f3fb-200d-1f91d-200d-1f468-1f3fd","👩🏻‍🤝‍👨🏾":"1f469-1f3fb-200d-1f91d-200d-1f468-1f3fe","👩🏻‍🤝‍👨🏿":"1f469-1f3fb-200d-1f91d-200d-1f468-1f3ff","👩🏼‍🤝‍👨🏻":"1f469-1f3fc-200d-1f91d-200d-1f468-1f3fb","👩🏼‍🤝‍👨🏽":"1f469-1f3fc-200d-1f91d-200d-1f468-1f3fd","👩🏼‍🤝‍👨🏾":"1f469-1f3fc-200d-1f91d-200d-1f468-1f3fe","👩🏼‍🤝‍👨🏿":"1f469-1f3fc-200d-1f91d-200d-1f468-1f3ff","👩🏽‍🤝‍👨🏻":"1f469-1f3fd-200d-1f91d-200d-1f468-1f3fb","👩🏽‍🤝‍👨🏼":"1f469-1f3fd-200d-1f91d-200d-1f468-1f3fc","👩🏽‍🤝‍👨🏾":"1f469-1f3fd-200d-1f91d-200d-1f468-1f3fe","👩🏽‍🤝‍👨🏿":"1f469-1f3fd-200d-1f91d-200d-1f468-1f3ff","👩🏾‍🤝‍👨🏻":"1f469-1f3fe-200d-1f91d-200d-1f468-1f3fb","👩🏾‍🤝‍👨🏼":"1f469-1f3fe-200d-1f91d-200d-1f468-1f3fc","👩🏾‍🤝‍👨🏽":"1f469-1f3fe-200d-1f91d-200d-1f468-1f3fd","👩🏾‍🤝‍👨🏿":"1f469-1f3fe-200d-1f91d-200d-1f468-1f3ff","👩🏿‍🤝‍👨🏻":"1f469-1f3ff-200d-1f91d-200d-1f468-1f3fb","👩🏿‍🤝‍👨🏼":"1f469-1f3ff-200d-1f91d-200d-1f468-1f3fc","👩🏿‍🤝‍👨🏽":"1f469-1f3ff-200d-1f91d-200d-1f468-1f3fd","👩🏿‍🤝‍👨🏾":"1f469-1f3ff-200d-1f91d-200d-1f468-1f3fe","👨🏻‍🤝‍👨🏼":"1f468-1f3fb-200d-1f91d-200d-1f468-1f3fc","👨🏻‍🤝‍👨🏽":"1f468-1f3fb-200d-1f91d-200d-1f468-1f3fd","👨🏻‍🤝‍👨🏾":"1f468-1f3fb-200d-1f91d-200d-1f468-1f3fe","👨🏻‍🤝‍👨🏿":"1f468-1f3fb-200d-1f91d-200d-1f468-1f3ff","👨🏼‍🤝‍👨🏻":"1f468-1f3fc-200d-1f91d-200d-1f468-1f3fb","👨🏼‍🤝‍👨🏽":"1f468-1f3fc-200d-1f91d-200d-1f468-1f3fd","👨🏼‍🤝‍👨🏾":"1f468-1f3fc-200d-1f91d-200d-1f468-1f3fe","👨🏼‍🤝‍👨🏿":"1f468-1f3fc-200d-1f91d-200d-1f468-1f3ff","👨🏽‍🤝‍👨🏻":"1f468-1f3fd-200d-1f91d-200d-1f468-1f3fb","👨🏽‍🤝‍👨🏼":"1f468-1f3fd-200d-1f91d-200d-1f468-1f3fc","👨🏽‍🤝‍👨🏾":"1f468-1f3fd-200d-1f91d-200d-1f468-1f3fe","👨🏽‍🤝‍👨🏿":"1f468-1f3fd-200d-1f91d-200d-1f468-1f3ff","👨🏾‍🤝‍👨🏻":"1f468-1f3fe-200d-1f91d-200d-1f468-1f3fb","👨🏾‍🤝‍👨🏼":"1f468-1f3fe-200d-1f91d-200d-1f468-1f3fc","👨🏾‍🤝‍👨🏽":"1f468-1f3fe-200d-1f91d-200d-1f468-1f3fd","👨🏾‍🤝‍👨🏿":"1f468-1f3fe-200d-1f91d-200d-1f468-1f3ff","👨🏿‍🤝‍👨🏻":"1f468-1f3ff-200d-1f91d-200d-1f468-1f3fb","👨🏿‍🤝‍👨🏼":"1f468-1f3ff-200d-1f91d-200d-1f468-1f3fc","👨🏿‍🤝‍👨🏽":"1f468-1f3ff-200d-1f91d-200d-1f468-1f3fd","👨🏿‍🤝‍👨🏾":"1f468-1f3ff-200d-1f91d-200d-1f468-1f3fe","👩‍❤‍💋‍👨":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f468","👨‍❤‍💋‍👨":"1f468-200d-2764-fe0f-200d-1f48b-200d-1f468","👩‍❤‍💋‍👩":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f469","🧑🏻‍❤‍🧑🏼":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fc","🧑🏻‍❤‍🧑🏽":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fd","🧑🏻‍❤‍🧑🏾":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fe","🧑🏻‍❤‍🧑🏿":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3ff","🧑🏼‍❤‍🧑🏻":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fb","🧑🏼‍❤‍🧑🏽":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fd","🧑🏼‍❤‍🧑🏾":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fe","🧑🏼‍❤‍🧑🏿":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3ff","🧑🏽‍❤‍🧑🏻":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fb","🧑🏽‍❤‍🧑🏼":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fc","🧑🏽‍❤‍🧑🏾":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fe","🧑🏽‍❤‍🧑🏿":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3ff","🧑🏾‍❤‍🧑🏻":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fb","🧑🏾‍❤‍🧑🏼":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fc","🧑🏾‍❤‍🧑🏽":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fd","🧑🏾‍❤‍🧑🏿":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3ff","🧑🏿‍❤‍🧑🏻":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fb","🧑🏿‍❤‍🧑🏼":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fc","🧑🏿‍❤‍🧑🏽":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fd","🧑🏿‍❤‍🧑🏾":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fe","👩🏻‍❤‍👨🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb","👩🏻‍❤‍👨🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fc","👩🏻‍❤‍👨🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fd","👩🏻‍❤‍👨🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fe","👩🏻‍❤‍👨🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3ff","👩🏼‍❤‍👨🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fb","👩🏼‍❤‍👨🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc","👩🏼‍❤‍👨🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fd","👩🏼‍❤‍👨🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fe","👩🏼‍❤‍👨🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3ff","👩🏽‍❤‍👨🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fb","👩🏽‍❤‍👨🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fc","👩🏽‍❤‍👨🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd","👩🏽‍❤‍👨🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fe","👩🏽‍❤‍👨🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3ff","👩🏾‍❤‍👨🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fb","👩🏾‍❤‍👨🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fc","👩🏾‍❤‍👨🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fd","👩🏾‍❤‍👨🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe","👩🏾‍❤‍👨🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3ff","👩🏿‍❤‍👨🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fb","👩🏿‍❤‍👨🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fc","👩🏿‍❤‍👨🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fd","👩🏿‍❤‍👨🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fe","👩🏿‍❤‍👨🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff","👨🏻‍❤‍👨🏻":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb","👨🏻‍❤‍👨🏼":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fc","👨🏻‍❤‍👨🏽":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fd","👨🏻‍❤‍👨🏾":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fe","👨🏻‍❤‍👨🏿":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3ff","👨🏼‍❤‍👨🏻":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fb","👨🏼‍❤‍👨🏼":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc","👨🏼‍❤‍👨🏽":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fd","👨🏼‍❤‍👨🏾":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fe","👨🏼‍❤‍👨🏿":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3ff","👨🏽‍❤‍👨🏻":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fb","👨🏽‍❤‍👨🏼":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fc","👨🏽‍❤‍👨🏽":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd","👨🏽‍❤‍👨🏾":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fe","👨🏽‍❤‍👨🏿":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3ff","👨🏾‍❤‍👨🏻":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fb","👨🏾‍❤‍👨🏼":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fc","👨🏾‍❤‍👨🏽":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fd","👨🏾‍❤‍👨🏾":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe","👨🏾‍❤‍👨🏿":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3ff","👨🏿‍❤‍👨🏻":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fb","👨🏿‍❤‍👨🏼":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fc","👨🏿‍❤‍👨🏽":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fd","👨🏿‍❤‍👨🏾":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fe","👨🏿‍❤‍👨🏿":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff","👩🏻‍❤‍👩🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fb","👩🏻‍❤‍👩🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fc","👩🏻‍❤‍👩🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fd","👩🏻‍❤‍👩🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fe","👩🏻‍❤‍👩🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3ff","👩🏼‍❤‍👩🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fb","👩🏼‍❤‍👩🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fc","👩🏼‍❤‍👩🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fd","👩🏼‍❤‍👩🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fe","👩🏼‍❤‍👩🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3ff","👩🏽‍❤‍👩🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fb","👩🏽‍❤‍👩🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fc","👩🏽‍❤‍👩🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fd","👩🏽‍❤‍👩🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fe","👩🏽‍❤‍👩🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3ff","👩🏾‍❤‍👩🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fb","👩🏾‍❤‍👩🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fc","👩🏾‍❤‍👩🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fd","👩🏾‍❤‍👩🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fe","👩🏾‍❤‍👩🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3ff","👩🏿‍❤‍👩🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fb","👩🏿‍❤‍👩🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fc","👩🏿‍❤‍👩🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fd","👩🏿‍❤‍👩🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fe","👩🏿‍❤‍👩🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3ff","👨‍👩‍👧‍👦":"1f468-200d-1f469-200d-1f467-200d-1f466","👨‍👩‍👦‍👦":"1f468-200d-1f469-200d-1f466-200d-1f466","👨‍👩‍👧‍👧":"1f468-200d-1f469-200d-1f467-200d-1f467","👨‍👨‍👧‍👦":"1f468-200d-1f468-200d-1f467-200d-1f466","👨‍👨‍👦‍👦":"1f468-200d-1f468-200d-1f466-200d-1f466","👨‍👨‍👧‍👧":"1f468-200d-1f468-200d-1f467-200d-1f467","👩‍👩‍👧‍👦":"1f469-200d-1f469-200d-1f467-200d-1f466","👩‍👩‍👦‍👦":"1f469-200d-1f469-200d-1f466-200d-1f466","👩‍👩‍👧‍👧":"1f469-200d-1f469-200d-1f467-200d-1f467","🧑‍🧑‍🧒‍🧒":"1f9d1-200d-1f9d1-200d-1f9d2-200d-1f9d2","🏴󠁧󠁢󠁥󠁮󠁧󠁿":"1f3f4-e0067-e0062-e0065-e006e-e0067-e007f","🏴󠁧󠁢󠁳󠁣󠁴󠁿":"1f3f4-e0067-e0062-e0073-e0063-e0074-e007f","🏴󠁧󠁢󠁷󠁬󠁳󠁿":"1f3f4-e0067-e0062-e0077-e006c-e0073-e007f","🚶🏻‍♀️‍➡️":"1f6b6-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🚶🏼‍♀️‍➡️":"1f6b6-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🚶🏽‍♀️‍➡️":"1f6b6-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🚶🏾‍♀️‍➡️":"1f6b6-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","🚶🏿‍♀️‍➡️":"1f6b6-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🚶🏻‍♂️‍➡️":"1f6b6-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🚶🏼‍♂️‍➡️":"1f6b6-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🚶🏽‍♂️‍➡️":"1f6b6-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🚶🏾‍♂️‍➡️":"1f6b6-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🚶🏿‍♂️‍➡️":"1f6b6-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","🧎🏻‍♀️‍➡️":"1f9ce-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🧎🏼‍♀️‍➡️":"1f9ce-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🧎🏽‍♀️‍➡️":"1f9ce-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🧎🏾‍♀️‍➡️":"1f9ce-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","🧎🏿‍♀️‍➡️":"1f9ce-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🧎🏻‍♂️‍➡️":"1f9ce-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🧎🏼‍♂️‍➡️":"1f9ce-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🧎🏽‍♂️‍➡️":"1f9ce-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🧎🏾‍♂️‍➡️":"1f9ce-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🧎🏿‍♂️‍➡️":"1f9ce-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","🏃🏻‍♀️‍➡️":"1f3c3-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🏃🏼‍♀️‍➡️":"1f3c3-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🏃🏽‍♀️‍➡️":"1f3c3-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🏃🏾‍♀️‍➡️":"1f3c3-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","🏃🏿‍♀️‍➡️":"1f3c3-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🏃🏻‍♂️‍➡️":"1f3c3-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🏃🏼‍♂️‍➡️":"1f3c3-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🏃🏽‍♂️‍➡️":"1f3c3-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🏃🏾‍♂️‍➡️":"1f3c3-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🏃🏿‍♂️‍➡️":"1f3c3-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","👩‍❤️‍💋‍👨":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f468","👨‍❤️‍💋‍👨":"1f468-200d-2764-fe0f-200d-1f48b-200d-1f468","👩‍❤️‍💋‍👩":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f469","🧑🏻‍❤️‍🧑🏼":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fc","🧑🏻‍❤️‍🧑🏽":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fd","🧑🏻‍❤️‍🧑🏾":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fe","🧑🏻‍❤️‍🧑🏿":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3ff","🧑🏼‍❤️‍🧑🏻":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fb","🧑🏼‍❤️‍🧑🏽":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fd","🧑🏼‍❤️‍🧑🏾":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fe","🧑🏼‍❤️‍🧑🏿":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3ff","🧑🏽‍❤️‍🧑🏻":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fb","🧑🏽‍❤️‍🧑🏼":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fc","🧑🏽‍❤️‍🧑🏾":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fe","🧑🏽‍❤️‍🧑🏿":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3ff","🧑🏾‍❤️‍🧑🏻":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fb","🧑🏾‍❤️‍🧑🏼":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fc","🧑🏾‍❤️‍🧑🏽":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fd","🧑🏾‍❤️‍🧑🏿":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3ff","🧑🏿‍❤️‍🧑🏻":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fb","🧑🏿‍❤️‍🧑🏼":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fc","🧑🏿‍❤️‍🧑🏽":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fd","🧑🏿‍❤️‍🧑🏾":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fe","👩🏻‍❤️‍👨🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb","👩🏻‍❤️‍👨🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fc","👩🏻‍❤️‍👨🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fd","👩🏻‍❤️‍👨🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fe","👩🏻‍❤️‍👨🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3ff","👩🏼‍❤️‍👨🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fb","👩🏼‍❤️‍👨🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc","👩🏼‍❤️‍👨🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fd","👩🏼‍❤️‍👨🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fe","👩🏼‍❤️‍👨🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3ff","👩🏽‍❤️‍👨🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fb","👩🏽‍❤️‍👨🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fc","👩🏽‍❤️‍👨🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd","👩🏽‍❤️‍👨🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fe","👩🏽‍❤️‍👨🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3ff","👩🏾‍❤️‍👨🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fb","👩🏾‍❤️‍👨🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fc","👩🏾‍❤️‍👨🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fd","👩🏾‍❤️‍👨🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe","👩🏾‍❤️‍👨🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3ff","👩🏿‍❤️‍👨🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fb","👩🏿‍❤️‍👨🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fc","👩🏿‍❤️‍👨🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fd","👩🏿‍❤️‍👨🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fe","👩🏿‍❤️‍👨🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff","👨🏻‍❤️‍👨🏻":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb","👨🏻‍❤️‍👨🏼":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fc","👨🏻‍❤️‍👨🏽":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fd","👨🏻‍❤️‍👨🏾":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fe","👨🏻‍❤️‍👨🏿":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3ff","👨🏼‍❤️‍👨🏻":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fb","👨🏼‍❤️‍👨🏼":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc","👨🏼‍❤️‍👨🏽":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fd","👨🏼‍❤️‍👨🏾":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fe","👨🏼‍❤️‍👨🏿":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3ff","👨🏽‍❤️‍👨🏻":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fb","👨🏽‍❤️‍👨🏼":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fc","👨🏽‍❤️‍👨🏽":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd","👨🏽‍❤️‍👨🏾":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fe","👨🏽‍❤️‍👨🏿":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3ff","👨🏾‍❤️‍👨🏻":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fb","👨🏾‍❤️‍👨🏼":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fc","👨🏾‍❤️‍👨🏽":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fd","👨🏾‍❤️‍👨🏾":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe","👨🏾‍❤️‍👨🏿":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3ff","👨🏿‍❤️‍👨🏻":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fb","👨🏿‍❤️‍👨🏼":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fc","👨🏿‍❤️‍👨🏽":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fd","👨🏿‍❤️‍👨🏾":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fe","👨🏿‍❤️‍👨🏿":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff","👩🏻‍❤️‍👩🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fb","👩🏻‍❤️‍👩🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fc","👩🏻‍❤️‍👩🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fd","👩🏻‍❤️‍👩🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fe","👩🏻‍❤️‍👩🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3ff","👩🏼‍❤️‍👩🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fb","👩🏼‍❤️‍👩🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fc","👩🏼‍❤️‍👩🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fd","👩🏼‍❤️‍👩🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fe","👩🏼‍❤️‍👩🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3ff","👩🏽‍❤️‍👩🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fb","👩🏽‍❤️‍👩🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fc","👩🏽‍❤️‍👩🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fd","👩🏽‍❤️‍👩🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fe","👩🏽‍❤️‍👩🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3ff","👩🏾‍❤️‍👩🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fb","👩🏾‍❤️‍👩🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fc","👩🏾‍❤️‍👩🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fd","👩🏾‍❤️‍👩🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fe","👩🏾‍❤️‍👩🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3ff","👩🏿‍❤️‍👩🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fb","👩🏿‍❤️‍👩🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fc","👩🏿‍❤️‍👩🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fd","👩🏿‍❤️‍👩🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fe","👩🏿‍❤️‍👩🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3ff","🧑🏻‍❤‍💋‍🧑🏼":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","🧑🏻‍❤‍💋‍🧑🏽":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","🧑🏻‍❤‍💋‍🧑🏾":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","🧑🏻‍❤‍💋‍🧑🏿":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","🧑🏼‍❤‍💋‍🧑🏻":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","🧑🏼‍❤‍💋‍🧑🏽":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","🧑🏼‍❤‍💋‍🧑🏾":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","🧑🏼‍❤‍💋‍🧑🏿":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","🧑🏽‍❤‍💋‍🧑🏻":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","🧑🏽‍❤‍💋‍🧑🏼":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","🧑🏽‍❤‍💋‍🧑🏾":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","🧑🏽‍❤‍💋‍🧑🏿":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","🧑🏾‍❤‍💋‍🧑🏻":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","🧑🏾‍❤‍💋‍🧑🏼":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","🧑🏾‍❤‍💋‍🧑🏽":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","🧑🏾‍❤‍💋‍🧑🏿":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","🧑🏿‍❤‍💋‍🧑🏻":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","🧑🏿‍❤‍💋‍🧑🏼":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","🧑🏿‍❤‍💋‍🧑🏽":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","🧑🏿‍❤‍💋‍🧑🏾":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","👩🏻‍❤‍💋‍👨🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏻‍❤‍💋‍👨🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏻‍❤‍💋‍👨🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏻‍❤‍💋‍👨🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏻‍❤‍💋‍👨🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏼‍❤‍💋‍👨🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏼‍❤‍💋‍👨🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏼‍❤‍💋‍👨🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏼‍❤‍💋‍👨🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏼‍❤‍💋‍👨🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏽‍❤‍💋‍👨🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏽‍❤‍💋‍👨🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏽‍❤‍💋‍👨🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏽‍❤‍💋‍👨🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏽‍❤‍💋‍👨🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏾‍❤‍💋‍👨🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏾‍❤‍💋‍👨🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏾‍❤‍💋‍👨🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏾‍❤‍💋‍👨🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏾‍❤‍💋‍👨🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏿‍❤‍💋‍👨🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏿‍❤‍💋‍👨🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏿‍❤‍💋‍👨🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏿‍❤‍💋‍👨🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏿‍❤‍💋‍👨🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏻‍❤‍💋‍👨🏻":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏻‍❤‍💋‍👨🏼":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏻‍❤‍💋‍👨🏽":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏻‍❤‍💋‍👨🏾":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏻‍❤‍💋‍👨🏿":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏼‍❤‍💋‍👨🏻":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏼‍❤‍💋‍👨🏼":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏼‍❤‍💋‍👨🏽":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏼‍❤‍💋‍👨🏾":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏼‍❤‍💋‍👨🏿":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏽‍❤‍💋‍👨🏻":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏽‍❤‍💋‍👨🏼":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏽‍❤‍💋‍👨🏽":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏽‍❤‍💋‍👨🏾":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏽‍❤‍💋‍👨🏿":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏾‍❤‍💋‍👨🏻":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏾‍❤‍💋‍👨🏼":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏾‍❤‍💋‍👨🏽":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏾‍❤‍💋‍👨🏾":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏾‍❤‍💋‍👨🏿":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏿‍❤‍💋‍👨🏻":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏿‍❤‍💋‍👨🏼":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏿‍❤‍💋‍👨🏽":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏿‍❤‍💋‍👨🏾":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏿‍❤‍💋‍👨🏿":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏻‍❤‍💋‍👩🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏻‍❤‍💋‍👩🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏻‍❤‍💋‍👩🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏻‍❤‍💋‍👩🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏻‍❤‍💋‍👩🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏼‍❤‍💋‍👩🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏼‍❤‍💋‍👩🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏼‍❤‍💋‍👩🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏼‍❤‍💋‍👩🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏼‍❤‍💋‍👩🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏽‍❤‍💋‍👩🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏽‍❤‍💋‍👩🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏽‍❤‍💋‍👩🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏽‍❤‍💋‍👩🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏽‍❤‍💋‍👩🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏾‍❤‍💋‍👩🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏾‍❤‍💋‍👩🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏾‍❤‍💋‍👩🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏾‍❤‍💋‍👩🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏾‍❤‍💋‍👩🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏿‍❤‍💋‍👩🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏿‍❤‍💋‍👩🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏿‍❤‍💋‍👩🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏿‍❤‍💋‍👩🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏿‍❤‍💋‍👩🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","🧑🏻‍❤️‍💋‍🧑🏼":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","🧑🏻‍❤️‍💋‍🧑🏽":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","🧑🏻‍❤️‍💋‍🧑🏾":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","🧑🏻‍❤️‍💋‍🧑🏿":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","🧑🏼‍❤️‍💋‍🧑🏻":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","🧑🏼‍❤️‍💋‍🧑🏽":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","🧑🏼‍❤️‍💋‍🧑🏾":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","🧑🏼‍❤️‍💋‍🧑🏿":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","🧑🏽‍❤️‍💋‍🧑🏻":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","🧑🏽‍❤️‍💋‍🧑🏼":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","🧑🏽‍❤️‍💋‍🧑🏾":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","🧑🏽‍❤️‍💋‍🧑🏿":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","🧑🏾‍❤️‍💋‍🧑🏻":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","🧑🏾‍❤️‍💋‍🧑🏼":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","🧑🏾‍❤️‍💋‍🧑🏽":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","🧑🏾‍❤️‍💋‍🧑🏿":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","🧑🏿‍❤️‍💋‍🧑🏻":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","🧑🏿‍❤️‍💋‍🧑🏼":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","🧑🏿‍❤️‍💋‍🧑🏽":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","🧑🏿‍❤️‍💋‍🧑🏾":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","👩🏻‍❤️‍💋‍👨🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏻‍❤️‍💋‍👨🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏻‍❤️‍💋‍👨🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏻‍❤️‍💋‍👨🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏻‍❤️‍💋‍👨🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏼‍❤️‍💋‍👨🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏼‍❤️‍💋‍👨🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏼‍❤️‍💋‍👨🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏼‍❤️‍💋‍👨🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏼‍❤️‍💋‍👨🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏽‍❤️‍💋‍👨🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏽‍❤️‍💋‍👨🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏽‍❤️‍💋‍👨🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏽‍❤️‍💋‍👨🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏽‍❤️‍💋‍👨🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏾‍❤️‍💋‍👨🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏾‍❤️‍💋‍👨🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏾‍❤️‍💋‍👨🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏾‍❤️‍💋‍👨🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏾‍❤️‍💋‍👨🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏿‍❤️‍💋‍👨🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏿‍❤️‍💋‍👨🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏿‍❤️‍💋‍👨🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏿‍❤️‍💋‍👨🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏿‍❤️‍💋‍👨🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏻‍❤️‍💋‍👨🏻":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏻‍❤️‍💋‍👨🏼":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏻‍❤️‍💋‍👨🏽":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏻‍❤️‍💋‍👨🏾":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏻‍❤️‍💋‍👨🏿":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏼‍❤️‍💋‍👨🏻":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏼‍❤️‍💋‍👨🏼":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏼‍❤️‍💋‍👨🏽":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏼‍❤️‍💋‍👨🏾":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏼‍❤️‍💋‍👨🏿":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏽‍❤️‍💋‍👨🏻":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏽‍❤️‍💋‍👨🏼":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏽‍❤️‍💋‍👨🏽":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏽‍❤️‍💋‍👨🏾":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏽‍❤️‍💋‍👨🏿":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏾‍❤️‍💋‍👨🏻":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏾‍❤️‍💋‍👨🏼":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏾‍❤️‍💋‍👨🏽":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏾‍❤️‍💋‍👨🏾":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏾‍❤️‍💋‍👨🏿":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏿‍❤️‍💋‍👨🏻":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏿‍❤️‍💋‍👨🏼":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏿‍❤️‍💋‍👨🏽":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏿‍❤️‍💋‍👨🏾":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏿‍❤️‍💋‍👨🏿":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏻‍❤️‍💋‍👩🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏻‍❤️‍💋‍👩🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏻‍❤️‍💋‍👩🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏻‍❤️‍💋‍👩🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏻‍❤️‍💋‍👩🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏼‍❤️‍💋‍👩🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏼‍❤️‍💋‍👩🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏼‍❤️‍💋‍👩🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏼‍❤️‍💋‍👩🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏼‍❤️‍💋‍👩🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏽‍❤️‍💋‍👩🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏽‍❤️‍💋‍👩🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏽‍❤️‍💋‍👩🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏽‍❤️‍💋‍👩🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏽‍❤️‍💋‍👩🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏾‍❤️‍💋‍👩🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏾‍❤️‍💋‍👩🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏾‍❤️‍💋‍👩🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏾‍❤️‍💋‍👩🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏾‍❤️‍💋‍👩🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏿‍❤️‍💋‍👩🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏿‍❤️‍💋‍👩🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏿‍❤️‍💋‍👩🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏿‍❤️‍💋‍👩🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏿‍❤️‍💋‍👩🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff"} \ No newline at end of file +{"🩵":"1fa75","🦌":"1f98c","🦓":"1f993","🦄":"1f984","🐎":"1f40e","🫏":"1facf","🫎":"1face","🐴":"1f434","🐆":"1f406","🐅":"1f405","🐯":"1f42f","🦁":"1f981","🐈":"1f408","💜":"1f49c","🐱":"1f431","🦝":"1f99d","🦊":"1f98a","🐺":"1f43a","🐩":"1f429","🦮":"1f9ae","🐕":"1f415","🐶":"1f436","🦧":"1f9a7","🦍":"1f98d","🏄":"1f3c4","🆗":"1f197","🅾":"1f17e","🤷":"1f937","🫥":"1fae5","🆖":"1f196","🆕":"1f195","Ⓜ":"24c2","🆔":"1f194","ℹ":"2139","🆓":"1f193","🆒":"1f192","🆑":"1f191","🅱":"1f171","🆎":"1f18e","🅰":"1f170","🔤":"1f524","🔣":"1f523","🔢":"1f522","🔡":"1f521","🔠":"1f520","🔟":"1f51f","😃":"1f603","😏":"1f60f","™":"2122","®":"ae","©":"a9","❇":"2747","✴":"2734","✳":"2733","😒":"1f612","〽":"303d","➿":"27bf","➰":"27b0","❎":"274e","❌":"274c","✔":"2714","☑":"2611","🙄":"1f644","✅":"2705","⭕":"2b55","🔰":"1f530","📛":"1f4db","😬":"1f62c","🔱":"1f531","⚜":"269c","♻":"267b","😄":"1f604","⚕":"2695","💲":"1f4b2","💱":"1f4b1","〰":"3030","🤥":"1f925","❗":"2757","❕":"2755","❔":"2754","❓":"2753","🫨":"1fae8","⁉":"2049","‼":"203c","♾":"267e","🟰":"1f7f0","➗":"2797","➖":"2796","➕":"2795","✖":"2716","😁":"1f601","⚧":"26a7","♂":"2642","♀":"2640","📴":"1f4f4","📳":"1f4f3","🛜":"1f6dc","📶":"1f4f6","🔆":"1f506","🔅":"1f505","🎦":"1f3a6","⏏":"23cf","⏺":"23fa","⏹":"23f9","⏸":"23f8","😆":"1f606","⏬":"23ec","🔽":"1f53d","⏫":"23eb","🔼":"1f53c","⏮":"23ee","⏪":"23ea","◀":"25c0","⏯":"23ef","😌":"1f60c","⏭":"23ed","⏩":"23e9","▶":"25b6","😔":"1f614","🔂":"1f502","🔁":"1f501","🔀":"1f500","⛎":"26ce","😪":"1f62a","♓":"2653","♒":"2652","♑":"2651","♐":"2650","♏":"264f","🤤":"1f924","♎":"264e","♍":"264d","♌":"264c","♋":"264b","♊":"264a","😴":"1f634","♉":"2649","♈":"2648","🪯":"1faaf","🔯":"1f52f","🕎":"1f54e","😷":"1f637","☮":"262e","☪":"262a","☦":"2626","🤒":"1f912","✝":"271d","☯":"262f","🤕":"1f915","☸":"2638","✡":"2721","🕉":"1f549","🤢":"1f922","⚛":"269b","🛐":"1f6d0","🔝":"1f51d","🤮":"1f92e","🔜":"1f51c","🔛":"1f51b","🔚":"1f51a","🔙":"1f519","🔄":"1f504","🤧":"1f927","🔃":"1f503","⤵":"2935","⤴":"2934","🥵":"1f975","↪":"21aa","↩":"21a9","↔":"2194","🥶":"1f976","↕":"2195","↖":"2196","🥴":"1f974","⬅":"2b05","↙":"2199","⬇":"2b07","😵":"1f635","↘":"2198","➡":"27a1","😅":"1f605","↗":"2197","⬆":"2b06","☣":"2623","🤯":"1f92f","☢":"2622","🔞":"1f51e","📵":"1f4f5","🤠":"1f920","🚷":"1f6b7","🚱":"1f6b1","🚯":"1f6af","🚭":"1f6ad","🚳":"1f6b3","🥳":"1f973","🚫":"1f6ab","⛔":"26d4","🚸":"1f6b8","⚠":"26a0","🥸":"1f978","🛅":"1f6c5","🛄":"1f6c4","🛃":"1f6c3","🛂":"1f6c2","🚾":"1f6be","😎":"1f60e","🚼":"1f6bc","🚻":"1f6bb","🚺":"1f6ba","🚹":"1f6b9","♿":"267f","🤓":"1f913","🚰":"1f6b0","🚮":"1f6ae","🏧":"1f3e7","🪪":"1faaa","🪧":"1faa7","🧐":"1f9d0","🗿":"1f5ff","🪬":"1faac","🧿":"1f9ff","⚱":"26b1","😕":"1f615","🪦":"1faa6","⚰":"26b0","🚬":"1f6ac","🛒":"1f6d2","🫤":"1fae4","🧯":"1f9ef","🧽":"1f9fd","🪥":"1faa5","🫧":"1fae7","🧼":"1f9fc","😟":"1f61f","🪣":"1faa3","🧻":"1f9fb","🧺":"1f9fa","🧹":"1f9f9","🧷":"1f9f7","🙁":"1f641","🧴":"1f9f4","🪒":"1fa92","🪤":"1faa4","🛁":"1f6c1","🚿":"1f6bf","🪠":"1faa0","🚽":"1f6bd","🪑":"1fa91","🛋":"1f6cb","🛏":"1f6cf","🪟":"1fa9f","🪞":"1fa9e","🛗":"1f6d7","🚪":"1f6aa","🩻":"1fa7b","☹":"2639","🩺":"1fa7a","🩼":"1fa7c","🩹":"1fa79","💊":"1f48a","🩸":"1fa78","💉":"1f489","📡":"1f4e1","🔭":"1f52d","🔬":"1f52c","🧬":"1f9ec","🧫":"1f9eb","😮":"1f62e","🧪":"1f9ea","⚗":"2697","🪜":"1fa9c","🧲":"1f9f2","🧰":"1f9f0","🪝":"1fa9d","⛓":"26d3","🔗":"1f517","😯":"1f62f","🦯":"1f9af","⚖":"2696","🗜":"1f5dc","😲":"1f632","⚙":"2699","🔩":"1f529","🪛":"1fa9b","🔧":"1f527","😳":"1f633","🪚":"1fa9a","🛡":"1f6e1","🏹":"1f3f9","🪃":"1fa83","🥺":"1f97a","💣":"1f4a3","⚔":"2694","🗡":"1f5e1","🥹":"1f979","🛠":"1f6e0","⚒":"2692","⛏":"26cf","😦":"1f626","🪓":"1fa93","🔨":"1f528","🗝":"1f5dd","👮":"1f46e","🔑":"1f511","😧":"1f627","🔐":"1f510","🔏":"1f50f","🔓":"1f513","🔒":"1f512","🗑":"1f5d1","🗄":"1f5c4","🗃":"1f5c3","✂":"2702","😨":"1f628","📐":"1f4d0","📏":"1f4cf","🖇":"1f587","📎":"1f4ce","📍":"1f4cd","📌":"1f4cc","📋":"1f4cb","📊":"1f4ca","🕵":"1f575","📉":"1f4c9","📈":"1f4c8","📇":"1f4c7","😰":"1f630","🗓":"1f5d3","🗒":"1f5d2","📆":"1f4c6","📅":"1f4c5","🗂":"1f5c2","📂":"1f4c2","📁":"1f4c1","💼":"1f4bc","📝":"1f4dd","🖍":"1f58d","😥":"1f625","🖌":"1f58c","🖊":"1f58a","🖋":"1f58b","✒":"2712","✏":"270f","💂":"1f482","😢":"1f622","🗳":"1f5f3","📮":"1f4ee","📭":"1f4ed","📬":"1f4ec","📪":"1f4ea","📫":"1f4eb","📦":"1f4e6","📥":"1f4e5","📤":"1f4e4","📩":"1f4e9","😭":"1f62d","📨":"1f4e8","📧":"1f4e7","✉":"2709","💹":"1f4b9","🧾":"1f9fe","💳":"1f4b3","💸":"1f4b8","💷":"1f4b7","💶":"1f4b6","🥷":"1f977","👷":"1f477","💵":"1f4b5","😱":"1f631","💴":"1f4b4","🪙":"1fa99","💰":"1f4b0","🏷":"1f3f7","🔖":"1f516","📑":"1f4d1","🗞":"1f5de","📰":"1f4f0","📄":"1f4c4","😖":"1f616","📜":"1f4dc","📃":"1f4c3","📒":"1f4d2","📓":"1f4d3","📚":"1f4da","📙":"1f4d9","📘":"1f4d8","📗":"1f4d7","📖":"1f4d6","📕":"1f4d5","🫅":"1fac5","🤴":"1f934","👸":"1f478","👳":"1f473","📔":"1f4d4","😣":"1f623","🪔":"1fa94","🏮":"1f3ee","🔦":"1f526","💡":"1f4a1","🕯":"1f56f","🔎":"1f50e","🔍":"1f50d","📼":"1f4fc","📹":"1f4f9","📸":"1f4f8","😞":"1f61e","📷":"1f4f7","📺":"1f4fa","🎬":"1f3ac","📽":"1f4fd","🎞":"1f39e","🎥":"1f3a5","🧮":"1f9ee","📀":"1f4c0","👲":"1f472","🧕":"1f9d5","🤵":"1f935","💿":"1f4bf","😓":"1f613","💾":"1f4be","💽":"1f4bd","🖲":"1f5b2","🖱":"1f5b1","⌨":"2328","🖨":"1f5a8","🖥":"1f5a5","😩":"1f629","💻":"1f4bb","🔌":"1f50c","🪫":"1faab","🔋":"1f50b","📠":"1f4e0","📟":"1f4df","📞":"1f4de","☎":"260e","👰":"1f470","📲":"1f4f2","😫":"1f62b","📱":"1f4f1","🪈":"1fa88","🪇":"1fa87","🪘":"1fa98","🥁":"1f941","🪕":"1fa95","🎻":"1f3bb","🎺":"1f3ba","🎹":"1f3b9","🎸":"1f3b8","🪗":"1fa97","🥱":"1f971","🎷":"1f3b7","📻":"1f4fb","🎧":"1f3a7","🎤":"1f3a4","🎛":"1f39b","🎚":"1f39a","🎙":"1f399","🤰":"1f930","🫃":"1fac3","🫄":"1fac4","🤱":"1f931","😤":"1f624","🎶":"1f3b6","🎵":"1f3b5","🎼":"1f3bc","🔕":"1f515","🔔":"1f514","😡":"1f621","📯":"1f4ef","📣":"1f4e3","📢":"1f4e2","🔊":"1f50a","🔉":"1f509","😠":"1f620","🔈":"1f508","🔇":"1f507","💎":"1f48e","💍":"1f48d","💄":"1f484","👼":"1f47c","🎅":"1f385","🤶":"1f936","🤬":"1f92c","📿":"1f4ff","⛑":"26d1","🪖":"1fa96","🧢":"1f9e2","🦸":"1f9b8","🎓":"1f393","😈":"1f608","🎩":"1f3a9","👒":"1f452","👑":"1f451","🪮":"1faae","👢":"1f462","🩰":"1fa70","👡":"1f461","👠":"1f460","🥿":"1f97f","🥾":"1f97e","👟":"1f45f","👿":"1f47f","👞":"1f45e","🩴":"1fa74","🎒":"1f392","🛍":"1f6cd","👝":"1f45d","👜":"1f45c","👛":"1f45b","🪭":"1faad","👚":"1f45a","🦹":"1f9b9","👙":"1f459","💀":"1f480","🩳":"1fa73","🩲":"1fa72","🩱":"1fa71","🥻":"1f97b","👘":"1f458","👗":"1f457","🧦":"1f9e6","🧥":"1f9e5","🧤":"1f9e4","🧣":"1f9e3","👖":"1f456","👕":"1f455","👔":"1f454","🦺":"1f9ba","🥼":"1f97c","🥽":"1f97d","🕶":"1f576","👓":"1f453","🪢":"1faa2","🧶":"1f9f6","🧙":"1f9d9","🪡":"1faa1","☠":"2620","🧵":"1f9f5","🎨":"1f3a8","🖼":"1f5bc","🎭":"1f3ad","🎴":"1f3b4","🀄":"1f004","🃏":"1f0cf","♟":"265f","♣":"2663","💩":"1f4a9","♦":"2666","♥":"2665","♠":"2660","🪆":"1fa86","🪩":"1faa9","🪅":"1fa85","🧚":"1f9da","🧸":"1f9f8","🤡":"1f921","🧩":"1f9e9","🎲":"1f3b2","🎰":"1f3b0","🕹":"1f579","🎮":"1f3ae","🪄":"1fa84","🔮":"1f52e","🎱":"1f3b1","🔫":"1f52b","🪁":"1fa81","👹":"1f479","🪀":"1fa80","🎯":"1f3af","🥌":"1f94c","🛷":"1f6f7","🎿":"1f3bf","🎽":"1f3bd","🤿":"1f93f","🎣":"1f3a3","⛸":"26f8","🧛":"1f9db","⛳":"26f3","👺":"1f47a","🥅":"1f945","🥋":"1f94b","🥊":"1f94a","🏸":"1f3f8","🏓":"1f3d3","🥍":"1f94d","🏒":"1f3d2","🏑":"1f3d1","🏏":"1f3cf","🎳":"1f3b3","🥏":"1f94f","👻":"1f47b","🎾":"1f3be","🏉":"1f3c9","🏈":"1f3c8","🏐":"1f3d0","🏀":"1f3c0","🥎":"1f94e","⚾":"26be","⚽":"26bd","🥉":"1f949","🥈":"1f948","🧜":"1f9dc","🥇":"1f947","👽":"1f47d","🏅":"1f3c5","🏆":"1f3c6","🎖":"1f396","🎫":"1f3ab","🎟":"1f39f","🎗":"1f397","🎁":"1f381","🎀":"1f380","👾":"1f47e","🧧":"1f9e7","🎑":"1f391","🎐":"1f390","🎏":"1f38f","🎎":"1f38e","🎍":"1f38d","🎋":"1f38b","🎊":"1f38a","🎉":"1f389","🎈":"1f388","🧝":"1f9dd","✨":"2728","🤖":"1f916","🧨":"1f9e8","🎇":"1f387","🎆":"1f386","🎄":"1f384","🎃":"1f383","🌊":"1f30a","💧":"1f4a7","🔥":"1f525","☄":"2604","⛄":"26c4","😺":"1f63a","☃":"2603","❄":"2744","⚡":"26a1","⛱":"26f1","☔":"2614","☂":"2602","🧞":"1f9de","🌂":"1f302","😸":"1f638","🌈":"1f308","😹":"1f639","🧟":"1f9df","🌀":"1f300","😻":"1f63b","🌬":"1f32c","😼":"1f63c","🧌":"1f9cc","💆":"1f486","😽":"1f63d","🌫":"1f32b","🌪":"1f32a","🌩":"1f329","🌨":"1f328","🌧":"1f327","🌦":"1f326","🙀":"1f640","🌥":"1f325","🌤":"1f324","⛈":"26c8","⛅":"26c5","☁":"2601","💇":"1f487","🌌":"1f30c","😿":"1f63f","🌠":"1f320","🌟":"1f31f","⭐":"2b50","🪐":"1fa90","🌞":"1f31e","🌝":"1f31d","☀":"2600","🌡":"1f321","🌜":"1f31c","😾":"1f63e","🌛":"1f31b","🌚":"1f31a","🌙":"1f319","🌘":"1f318","🌗":"1f317","🌖":"1f316","🌕":"1f315","🌔":"1f314","🌓":"1f313","🌒":"1f312","🚶":"1f6b6","🌑":"1f311","🙈":"1f648","🕦":"1f566","🕚":"1f55a","🕥":"1f565","🕙":"1f559","🕤":"1f564","🕘":"1f558","🕣":"1f563","🕗":"1f557","🕢":"1f562","🕖":"1f556","🕡":"1f561","🙉":"1f649","🕕":"1f555","🕠":"1f560","🕔":"1f554","🕟":"1f55f","🕓":"1f553","🕞":"1f55e","🕒":"1f552","🕝":"1f55d","🕑":"1f551","🕜":"1f55c","🕐":"1f550","🙊":"1f64a","🕧":"1f567","🕛":"1f55b","🕰":"1f570","⏲":"23f2","⏱":"23f1","⏰":"23f0","⌚":"231a","⏳":"23f3","⌛":"231b","🧳":"1f9f3","🛎":"1f6ce","🛸":"1f6f8","🚀":"1f680","🛰":"1f6f0","🚡":"1f6a1","🚠":"1f6a0","🚟":"1f69f","🚁":"1f681","💺":"1f4ba","🪂":"1fa82","🛬":"1f6ec","🛫":"1f6eb","🛩":"1f6e9","✈":"2708","🚢":"1f6a2","🛥":"1f6e5","⛴":"26f4","🛳":"1f6f3","🚤":"1f6a4","🛶":"1f6f6","⛵":"26f5","🛟":"1f6df","⚓":"2693","🚧":"1f6a7","🛑":"1f6d1","🚦":"1f6a6","🚥":"1f6a5","🚨":"1f6a8","🛞":"1f6de","⛽":"26fd","🛢":"1f6e2","🛤":"1f6e4","🛣":"1f6e3","🚏":"1f68f","🛼":"1f6fc","🧍":"1f9cd","🛹":"1f6f9","💌":"1f48c","🛴":"1f6f4","🚲":"1f6b2","🛺":"1f6fa","🦼":"1f9bc","🦽":"1f9bd","🛵":"1f6f5","🏍":"1f3cd","🏎":"1f3ce","🚜":"1f69c","💘":"1f498","🚛":"1f69b","🚚":"1f69a","🛻":"1f6fb","🚙":"1f699","🚘":"1f698","🚗":"1f697","🚖":"1f696","🚕":"1f695","🚔":"1f694","🚓":"1f693","🧎":"1f9ce","🚒":"1f692","💝":"1f49d","🚑":"1f691","🚐":"1f690","🚎":"1f68e","🚍":"1f68d","🚌":"1f68c","🚋":"1f68b","🚞":"1f69e","🚝":"1f69d","🚊":"1f68a","🚉":"1f689","🚈":"1f688","💖":"1f496","🚇":"1f687","🚆":"1f686","🚅":"1f685","🚄":"1f684","🚃":"1f683","🚂":"1f682","🎪":"1f3aa","💈":"1f488","🎢":"1f3a2","🎡":"1f3a1","🛝":"1f6dd","💗":"1f497","🎠":"1f3a0","♨":"2668","🌉":"1f309","🌇":"1f307","🌆":"1f306","🌅":"1f305","🌄":"1f304","🏙":"1f3d9","🌃":"1f303","🌁":"1f301","⛺":"26fa","⛲":"26f2","🕋":"1f54b","⛩":"26e9","🕍":"1f54d","🛕":"1f6d5","🕌":"1f54c","⛪":"26ea","🗽":"1f5fd","🗼":"1f5fc","💒":"1f492","🏰":"1f3f0","🏯":"1f3ef","🏭":"1f3ed","🏬":"1f3ec","🏫":"1f3eb","🏪":"1f3ea","🏩":"1f3e9","🏨":"1f3e8","🏦":"1f3e6","🏥":"1f3e5","🏤":"1f3e4","🏣":"1f3e3","🏢":"1f3e2","🏡":"1f3e1","🏠":"1f3e0","🏚":"1f3da","🏘":"1f3d8","🛖":"1f6d6","🪵":"1fab5","🪨":"1faa8","🧱":"1f9f1","🏗":"1f3d7","🏛":"1f3db","🏟":"1f3df","🏞":"1f3de","🏝":"1f3dd","🏜":"1f3dc","💓":"1f493","🏖":"1f3d6","🏕":"1f3d5","🗻":"1f5fb","🌋":"1f30b","⛰":"26f0","🏔":"1f3d4","🧭":"1f9ed","🗾":"1f5fe","🗺":"1f5fa","🌐":"1f310","🌏":"1f30f","💞":"1f49e","🌎":"1f30e","🌍":"1f30d","🏺":"1f3fa","🫙":"1fad9","🔪":"1f52a","🥄":"1f944","🍴":"1f374","🍽":"1f37d","🥢":"1f962","🧊":"1f9ca","🧉":"1f9c9","🧃":"1f9c3","🧋":"1f9cb","🥤":"1f964","🫗":"1fad7","🥃":"1f943","💕":"1f495","🥂":"1f942","🍻":"1f37b","🍺":"1f37a","🍹":"1f379","🍸":"1f378","🍷":"1f377","🍾":"1f37e","🍶":"1f376","🍵":"1f375","🫖":"1fad6","☕":"2615","🥛":"1f95b","🍼":"1f37c","🍯":"1f36f","🍮":"1f36e","🍭":"1f36d","🍬":"1f36c","💟":"1f49f","🍫":"1f36b","🥧":"1f967","🧁":"1f9c1","🍰":"1f370","🎂":"1f382","🍪":"1f36a","🍩":"1f369","🍨":"1f368","🍧":"1f367","🍦":"1f366","🥡":"1f961","🥠":"1f960","🥟":"1f95f","🍡":"1f361","🥮":"1f96e","🍥":"1f365","🍤":"1f364","🍣":"1f363","🍢":"1f362","🍠":"1f360","🍝":"1f35d","🍜":"1f35c","🍛":"1f35b","🍚":"1f35a","🍙":"1f359","🍘":"1f358","🍱":"1f371","🥫":"1f96b","🧂":"1f9c2","🧈":"1f9c8","🍿":"1f37f","🥗":"1f957","🥣":"1f963","🫕":"1fad5","❣":"2763","🍲":"1f372","🥘":"1f958","🍳":"1f373","🥚":"1f95a","🧆":"1f9c6","🥙":"1f959","🫔":"1fad4","🌯":"1f32f","🌮":"1f32e","🥪":"1f96a","🌭":"1f32d","🍕":"1f355","🍟":"1f35f","🍔":"1f354","🥓":"1f953","🥩":"1f969","🍗":"1f357","💔":"1f494","🍖":"1f356","🧀":"1f9c0","🧇":"1f9c7","🥞":"1f95e","🥯":"1f96f","🥨":"1f968","🫓":"1fad3","🥖":"1f956","🥐":"1f950","🍞":"1f35e","🫛":"1fadb","🫚":"1fada","🌰":"1f330","🫘":"1fad8","🥜":"1f95c","🧅":"1f9c5","🧄":"1f9c4","🥦":"1f966","🥬":"1f96c","🥒":"1f952","🫑":"1fad1","🌶":"1f336","🌽":"1f33d","🥕":"1f955","🥔":"1f954","🍆":"1f346","🥑":"1f951","🥥":"1f965","🫒":"1fad2","🍅":"1f345","🥝":"1f95d","🫐":"1fad0","🍓":"1f353","🤣":"1f923","🍒":"1f352","🍑":"1f351","🍐":"1f350","🍏":"1f34f","🍎":"1f34e","🥭":"1f96d","🍍":"1f34d","🍌":"1f34c","🍋":"1f34b","🍊":"1f34a","🍉":"1f349","🍈":"1f348","🍇":"1f347","🍄":"1f344","🪺":"1faba","🪹":"1fab9","🍃":"1f343","🏃":"1f3c3","🍂":"1f342","🍁":"1f341","🍀":"1f340","☘":"2618","🌿":"1f33f","🌾":"1f33e","🌵":"1f335","🌴":"1f334","🌳":"1f333","🌲":"1f332","🪴":"1fab4","😂":"1f602","🌱":"1f331","🪻":"1fabb","🌷":"1f337","🌼":"1f33c","🌻":"1f33b","🌺":"1f33a","🥀":"1f940","🌹":"1f339","🏵":"1f3f5","🪷":"1fab7","💮":"1f4ae","🌸":"1f338","💐":"1f490","🦠":"1f9a0","🪱":"1fab1","🪰":"1fab0","🦟":"1f99f","🦂":"1f982","🕸":"1f578","🕷":"1f577","🪳":"1fab3","🦗":"1f997","🐞":"1f41e","🪲":"1fab2","🐝":"1f41d","🐜":"1f41c","🐛":"1f41b","🦋":"1f98b","🐌":"1f40c","🦪":"1f9aa","🦑":"1f991","🦐":"1f990","🦞":"1f99e","🦀":"1f980","🪼":"1fabc","🪸":"1fab8","🐚":"1f41a","🐙":"1f419","🦈":"1f988","🐡":"1f421","🐠":"1f420","🐟":"1f41f","🦭":"1f9ad","🐬":"1f42c","🐋":"1f40b","🐳":"1f433","🦖":"1f996","🦕":"1f995","🐉":"1f409","🐲":"1f432","🐍":"1f40d","🦎":"1f98e","🐢":"1f422","🐊":"1f40a","🐸":"1f438","🪿":"1fabf","🪽":"1fabd","🦜":"1f99c","🦚":"1f99a","🦩":"1f9a9","🪶":"1fab6","🦤":"1f9a4","🦉":"1f989","🦢":"1f9a2","🦆":"1f986","🦅":"1f985","💃":"1f483","🕺":"1f57a","🕴":"1f574","👯":"1f46f","🕊":"1f54a","❤":"2764","🩷":"1fa77","🧖":"1f9d6","🐧":"1f427","🧡":"1f9e1","🐦":"1f426","🐥":"1f425","🐤":"1f424","🐣":"1f423","🐓":"1f413","🐔":"1f414","🦃":"1f983","🐾":"1f43e","🦡":"1f9a1","🦘":"1f998","🦨":"1f9a8","💛":"1f49b","🦦":"1f9a6","🦥":"1f9a5","🐼":"1f43c","🐨":"1f428","🐻":"1f43b","🦇":"1f987","🦔":"1f994","🦫":"1f9ab","🐿":"1f43f","🧗":"1f9d7","🐇":"1f407","💚":"1f49a","🐰":"1f430","🐹":"1f439","🐀":"1f400","🐁":"1f401","🐭":"1f42d","🦛":"1f99b","🦏":"1f98f","🦣":"1f9a3","🐘":"1f418","🦒":"1f992","🦙":"1f999","💙":"1f499","🐫":"1f42b","🐪":"1f42a","🐐":"1f410","🐑":"1f411","🐏":"1f40f","🐽":"1f43d","🐗":"1f417","🐖":"1f416","🐷":"1f437","🐄":"1f404","🤺":"1f93a","🏇":"1f3c7","⛷":"26f7","🏂":"1f3c2","🏌":"1f3cc","🐃":"1f403","🐂":"1f402","🐮":"1f42e","🦬":"1f9ac","🐒":"1f412","🤎":"1f90e","🐵":"1f435","🦲":"1f3fb","🦳":"1f3fb","🦱":"1f3fb","🦰":"1f3fb","🏿":"1f3fb","🏾":"1f3fb","🏽":"1f3fb","🏼":"1f3fb","🏻":"1f3fb","🫆":"1f3fb","🖤":"1f5a4","👣":"1f463","👪":"1f46a","🫂":"1fac2","👥":"1f465","👤":"1f464","🗣":"1f5e3","🚣":"1f6a3","🩶":"1fa76","💑":"1f491","💏":"1f48f","🤍":"1f90d","👬":"1f46c","👫":"1f46b","🏊":"1f3ca","💋":"1f48b","👭":"1f46d","🛌":"1f6cc","💯":"1f4af","🛀":"1f6c0","⛹":"26f9","🧘":"1f9d8","💢":"1f4a2","🤹":"1f939","🤾":"1f93e","💥":"1f4a5","🤽":"1f93d","🤼":"1f93c","🤸":"1f938","🏋":"1f3cb","💫":"1f4ab","🚵":"1f6b5","🚴":"1f6b4","💦":"1f4a6","💨":"1f4a8","🕳":"1f573","💬":"1f4ac","🙂":"1f642","🗨":"1f5e8","🗯":"1f5ef","💭":"1f4ad","💤":"1f4a4","👋":"1f44b","🤚":"1f91a","🖐":"1f590","✋":"270b","🖖":"1f596","🫱":"1faf1","🫲":"1faf2","🫳":"1faf3","🫴":"1faf4","🫷":"1faf7","🫸":"1faf8","👌":"1f44c","🤌":"1f90c","🤏":"1f90f","✌":"270c","🤞":"1f91e","🫰":"1faf0","🤟":"1f91f","🤘":"1f918","🤙":"1f919","👈":"1f448","👉":"1f449","👆":"1f446","🖕":"1f595","👇":"1f447","☝":"261d","🫵":"1faf5","👍":"1f44d","👎":"1f44e","✊":"270a","👊":"1f44a","🤛":"1f91b","🤜":"1f91c","👏":"1f44f","🙌":"1f64c","🫶":"1faf6","👐":"1f450","🤲":"1f932","🤝":"1f91d","🙏":"1f64f","✍":"270d","💅":"1f485","🤳":"1f933","💪":"1f4aa","🦾":"1f9be","🦿":"1f9bf","🦵":"1f9b5","🦶":"1f9b6","👂":"1f442","🦻":"1f9bb","👃":"1f443","🧠":"1f9e0","🫀":"1fac0","🫁":"1fac1","🦷":"1f9b7","🦴":"1f9b4","👀":"1f440","👁":"1f441","👅":"1f445","👄":"1f444","🫦":"1fae6","👶":"1f476","🧒":"1f9d2","👦":"1f466","👧":"1f467","🧑":"1f9d1","👱":"1f471","👨":"1f468","🧔":"1f9d4","🙃":"1f643","🫠":"1fae0","😉":"1f609","😊":"1f60a","😇":"1f607","🥰":"1f970","👩":"1f469","😍":"1f60d","🤩":"1f929","😀":"1f600","😘":"1f618","😗":"1f617","☺":"263a","😚":"1f61a","😙":"1f619","🥲":"1f972","🧓":"1f9d3","👴":"1f474","👵":"1f475","🙍":"1f64d","😋":"1f60b","😛":"1f61b","🙎":"1f64e","😜":"1f61c","🤪":"1f92a","🙅":"1f645","😝":"1f61d","🤑":"1f911","🙆":"1f646","🤗":"1f917","🤭":"1f92d","💁":"1f481","🫢":"1fae2","🫣":"1fae3","🙋":"1f64b","🤫":"1f92b","🤔":"1f914","🏳":"1f3f3","🏴":"1f3f4","🎌":"1f38c","🚩":"1f6a9","🏁":"1f3c1","🧏":"1f9cf","🔲":"1f532","🫡":"1fae1","🔳":"1f533","🔘":"1f518","💠":"1f4a0","🔻":"1f53b","🔺":"1f53a","🔹":"1f539","🔸":"1f538","🔷":"1f537","🔶":"1f536","▫":"25ab","🤐":"1f910","▪":"25aa","◽":"25fd","◾":"25fe","◻":"25fb","◼":"25fc","⬜":"2b1c","⬛":"2b1b","🙇":"1f647","🟫":"1f7eb","🤨":"1f928","🟪":"1f7ea","🟦":"1f7e6","🟩":"1f7e9","🟨":"1f7e8","🟧":"1f7e7","🟥":"1f7e5","⚪":"26aa","⚫":"26ab","🟤":"1f7e4","🟣":"1f7e3","🔵":"1f535","😐":"1f610","🟢":"1f7e2","🟡":"1f7e1","🟠":"1f7e0","🔴":"1f534","🈵":"1f235","🈺":"1f23a","㊙":"3299","㊗":"3297","🤦":"1f926","🈳":"1f233","😑":"1f611","🈴":"1f234","🈸":"1f238","🉑":"1f251","🈲":"1f232","🈚":"1f21a","🈹":"1f239","🉐":"1f250","🈯":"1f22f","🈶":"1f236","🈷":"1f237","😶":"1f636","🈂":"1f202","🈁":"1f201","🆚":"1f19a","🆙":"1f199","🆘":"1f198","🅿":"1f17f","☯️":"262f","🏄🏼":"1f3c4-1f3fc","🏄🏽":"1f3c4-1f3fd","🏄🏾":"1f3c4-1f3fe","🏄🏿":"1f3c4-1f3ff","🧚🏿":"1f9da-1f3ff","🕯️":"1f56f","☸️":"2638","☁️":"2601","✡️":"2721","🛢️":"1f6e2","💇🏻":"1f487-1f3fb","🕉️":"1f549","💇🏼":"1f487-1f3fc","⚛️":"269b","💇🏽":"1f487-1f3fd","💇🏾":"1f487-1f3fe","🕹️":"1f579","💇🏿":"1f487-1f3ff","💃🏽":"1f483-1f3fd","🛤️":"1f6e4","📽️":"1f4fd","💃🏾":"1f483-1f3fe","🎞️":"1f39e","🛣️":"1f6e3","🗣️":"1f5e3","💑🏿":"1f491-1f3ff","💑🏾":"1f491-1f3fe","💑🏽":"1f491-1f3fd","💃🏿":"1f483-1f3ff","🚣🏻":"1f6a3-1f3fb","🚣🏼":"1f6a3-1f3fc","🚣🏽":"1f6a3-1f3fd","🚣🏾":"1f6a3-1f3fe","🚣🏿":"1f6a3-1f3ff","💑🏼":"1f491-1f3fc","⤵️":"2935","💑🏻":"1f491-1f3fb","🏇🏻":"1f3c7-1f3fb","💏🏿":"1f48f-1f3ff","💏🏾":"1f48f-1f3fe","💏🏽":"1f48f-1f3fd","💏🏼":"1f48f-1f3fc","💏🏻":"1f48f-1f3fb","⤴️":"2934","👬🏿":"1f46c-1f3ff","👬🏾":"1f46c-1f3fe","👬🏽":"1f46c-1f3fd","🕺🏻":"1f57a-1f3fb","👬🏼":"1f46c-1f3fc","👬🏻":"1f46c-1f3fb","👲🏻":"1f472-1f3fb","👫🏿":"1f46b-1f3ff","👫🏾":"1f46b-1f3fe","👫🏽":"1f46b-1f3fd","👫🏼":"1f46b-1f3fc","👫🏻":"1f46b-1f3fb","↪️":"21aa","👭🏿":"1f46d-1f3ff","👲🏼":"1f472-1f3fc","🏊🏻":"1f3ca-1f3fb","🏊🏼":"1f3ca-1f3fc","🏊🏽":"1f3ca-1f3fd","🏊🏾":"1f3ca-1f3fe","🏊🏿":"1f3ca-1f3ff","👭🏾":"1f46d-1f3fe","↩️":"21a9","👭🏽":"1f46d-1f3fd","👭🏼":"1f46d-1f3fc","👭🏻":"1f46d-1f3fb","👲🏽":"1f472-1f3fd","🛌🏿":"1f6cc-1f3ff","🛌🏾":"1f6cc-1f3fe","🛌🏽":"1f6cc-1f3fd","🛌🏼":"1f6cc-1f3fc","🛌🏻":"1f6cc-1f3fb","👲🏾":"1f472-1f3fe","🛀🏿":"1f6c0-1f3ff","↔️":"2194","🛀🏾":"1f6c0-1f3fe","🛀🏽":"1f6c0-1f3fd","🛀🏼":"1f6c0-1f3fc","🛀🏻":"1f6c0-1f3fb","👲🏿":"1f472-1f3ff","🧘🏿":"1f9d8-1f3ff","🧘🏾":"1f9d8-1f3fe","🧘🏽":"1f9d8-1f3fd","🧘🏼":"1f9d8-1f3fc","🧘🏻":"1f9d8-1f3fb","⛹️":"26f9","↕️":"2195","⛹🏻":"26f9-1f3fb","⛹🏼":"26f9-1f3fc","⛹🏽":"26f9-1f3fd","⛹🏾":"26f9-1f3fe","⛹🏿":"26f9-1f3ff","🧍🏻":"1f9cd-1f3fb","🤹🏿":"1f939-1f3ff","🤹🏾":"1f939-1f3fe","↖️":"2196","🤹🏽":"1f939-1f3fd","🤹🏼":"1f939-1f3fc","🤹🏻":"1f939-1f3fb","🧕🏻":"1f9d5-1f3fb","🤾🏿":"1f93e-1f3ff","🤾🏾":"1f93e-1f3fe","🤾🏽":"1f93e-1f3fd","🤾🏼":"1f93e-1f3fc","🤾🏻":"1f93e-1f3fb","🧕🏼":"1f9d5-1f3fc","🤽🏿":"1f93d-1f3ff","🤽🏾":"1f93d-1f3fe","🤽🏽":"1f93d-1f3fd","⬅️":"2b05","🤽🏼":"1f93d-1f3fc","🤽🏻":"1f93d-1f3fb","🧕🏽":"1f9d5-1f3fd","↙️":"2199","🤸🏿":"1f938-1f3ff","🤸🏾":"1f938-1f3fe","🤸🏽":"1f938-1f3fd","🤸🏼":"1f938-1f3fc","🤸🏻":"1f938-1f3fb","🧕🏾":"1f9d5-1f3fe","🏋️":"1f3cb","🧕🏿":"1f9d5-1f3ff","🏋🏻":"1f3cb-1f3fb","🏋🏼":"1f3cb-1f3fc","🏋🏽":"1f3cb-1f3fd","🏋🏾":"1f3cb-1f3fe","🏋🏿":"1f3cb-1f3ff","🚵🏿":"1f6b5-1f3ff","🚵🏾":"1f6b5-1f3fe","🚵🏽":"1f6b5-1f3fd","⬇️":"2b07","🚵🏼":"1f6b5-1f3fc","🚵🏻":"1f6b5-1f3fb","🧍🏼":"1f9cd-1f3fc","🚴🏿":"1f6b4-1f3ff","🚴🏾":"1f6b4-1f3fe","🚴🏽":"1f6b4-1f3fd","🚴🏼":"1f6b4-1f3fc","🚴🏻":"1f6b4-1f3fb","↘️":"2198","🤵🏻":"1f935-1f3fb","➡️":"27a1","🕳️":"1f573","🤵🏼":"1f935-1f3fc","🤵🏽":"1f935-1f3fd","🇽🇰":"1f1fd-1f1f0","🇼🇸":"1f1fc-1f1f8","🇼🇫":"1f1fc-1f1eb","↗️":"2197","🗨️":"1f5e8","🤵🏾":"1f935-1f3fe","🗯️":"1f5ef","⬆️":"2b06","🤵🏿":"1f935-1f3ff","☀️":"2600","☣️":"2623","👋🏻":"1f44b-1f3fb","👋🏼":"1f44b-1f3fc","👋🏽":"1f44b-1f3fd","👋🏾":"1f44b-1f3fe","👋🏿":"1f44b-1f3ff","🧍🏽":"1f9cd-1f3fd","🤚🏻":"1f91a-1f3fb","🤚🏼":"1f91a-1f3fc","🤚🏽":"1f91a-1f3fd","🤚🏾":"1f91a-1f3fe","🤚🏿":"1f91a-1f3ff","🖐️":"1f590","☢️":"2622","🖐🏻":"1f590-1f3fb","🖐🏼":"1f590-1f3fc","🖐🏽":"1f590-1f3fd","🖐🏾":"1f590-1f3fe","🖐🏿":"1f590-1f3ff","🌡️":"1f321","✋🏻":"270b-1f3fb","✋🏼":"270b-1f3fc","✋🏽":"270b-1f3fd","✋🏾":"270b-1f3fe","✋🏿":"270b-1f3ff","🧍🏾":"1f9cd-1f3fe","🖖🏻":"1f596-1f3fb","🖖🏼":"1f596-1f3fc","🖖🏽":"1f596-1f3fd","🖖🏾":"1f596-1f3fe","🖖🏿":"1f596-1f3ff","🧍🏿":"1f9cd-1f3ff","🫱🏻":"1faf1-1f3fb","🫱🏼":"1faf1-1f3fc","🫱🏽":"1faf1-1f3fd","🫱🏾":"1faf1-1f3fe","🫱🏿":"1faf1-1f3ff","🖲️":"1f5b2","🫲🏻":"1faf2-1f3fb","🫲🏼":"1faf2-1f3fc","🫲🏽":"1faf2-1f3fd","🫲🏾":"1faf2-1f3fe","🫲🏿":"1faf2-1f3ff","🕺🏼":"1f57a-1f3fc","🫳🏻":"1faf3-1f3fb","🫳🏼":"1faf3-1f3fc","🫳🏽":"1faf3-1f3fd","🫳🏾":"1faf3-1f3fe","🫳🏿":"1faf3-1f3ff","🖱️":"1f5b1","🫴🏻":"1faf4-1f3fb","🫴🏼":"1faf4-1f3fc","🫴🏽":"1faf4-1f3fd","🫴🏾":"1faf4-1f3fe","🫴🏿":"1faf4-1f3ff","⛸️":"26f8","🫷🏻":"1faf7-1f3fb","🫷🏼":"1faf7-1f3fc","🫷🏽":"1faf7-1f3fd","🫷🏾":"1faf7-1f3fe","🫷🏿":"1faf7-1f3ff","⌨️":"2328","🫸🏻":"1faf8-1f3fb","🫸🏼":"1faf8-1f3fc","🫸🏽":"1faf8-1f3fd","🫸🏾":"1faf8-1f3fe","🫸🏿":"1faf8-1f3ff","🕺🏽":"1f57a-1f3fd","👌🏻":"1f44c-1f3fb","👌🏼":"1f44c-1f3fc","👌🏽":"1f44c-1f3fd","👌🏾":"1f44c-1f3fe","👌🏿":"1f44c-1f3ff","🖨️":"1f5a8","🤌🏻":"1f90c-1f3fb","🤌🏼":"1f90c-1f3fc","🤌🏽":"1f90c-1f3fd","🤌🏾":"1f90c-1f3fe","🤌🏿":"1f90c-1f3ff","🧛🏻":"1f9db-1f3fb","🤏🏻":"1f90f-1f3fb","🤏🏼":"1f90f-1f3fc","🤏🏽":"1f90f-1f3fd","🤏🏾":"1f90f-1f3fe","🤏🏿":"1f90f-1f3ff","✌️":"270c","🧛🏼":"1f9db-1f3fc","✌🏻":"270c-1f3fb","✌🏼":"270c-1f3fc","✌🏽":"270c-1f3fd","✌🏾":"270c-1f3fe","✌🏿":"270c-1f3ff","🖥️":"1f5a5","🤞🏻":"1f91e-1f3fb","🤞🏼":"1f91e-1f3fc","🤞🏽":"1f91e-1f3fd","🤞🏾":"1f91e-1f3fe","🤞🏿":"1f91e-1f3ff","⚠️":"26a0","🫰🏻":"1faf0-1f3fb","🫰🏼":"1faf0-1f3fc","🫰🏽":"1faf0-1f3fd","🫰🏾":"1faf0-1f3fe","🫰🏿":"1faf0-1f3ff","🧛🏽":"1f9db-1f3fd","🤟🏻":"1f91f-1f3fb","🤟🏼":"1f91f-1f3fc","🤟🏽":"1f91f-1f3fd","🤟🏾":"1f91f-1f3fe","🤟🏿":"1f91f-1f3ff","🧛🏾":"1f9db-1f3fe","🤘🏻":"1f918-1f3fb","🤘🏼":"1f918-1f3fc","🤘🏽":"1f918-1f3fd","🤘🏾":"1f918-1f3fe","🤘🏿":"1f918-1f3ff","🧛🏿":"1f9db-1f3ff","🤙🏻":"1f919-1f3fb","🤙🏼":"1f919-1f3fc","🤙🏽":"1f919-1f3fd","🤙🏾":"1f919-1f3fe","🤙🏿":"1f919-1f3ff","🕺🏾":"1f57a-1f3fe","👈🏻":"1f448-1f3fb","👈🏼":"1f448-1f3fc","👈🏽":"1f448-1f3fd","👈🏾":"1f448-1f3fe","👈🏿":"1f448-1f3ff","🕺🏿":"1f57a-1f3ff","👉🏻":"1f449-1f3fb","👉🏼":"1f449-1f3fc","👉🏽":"1f449-1f3fd","👉🏾":"1f449-1f3fe","👉🏿":"1f449-1f3ff","🏃🏻":"1f3c3-1f3fb","👆🏻":"1f446-1f3fb","👆🏼":"1f446-1f3fc","👆🏽":"1f446-1f3fd","👆🏾":"1f446-1f3fe","👆🏿":"1f446-1f3ff","🏃🏼":"1f3c3-1f3fc","🖕🏻":"1f595-1f3fb","🖕🏼":"1f595-1f3fc","🖕🏽":"1f595-1f3fd","🖕🏾":"1f595-1f3fe","🖕🏿":"1f595-1f3ff","🏃🏽":"1f3c3-1f3fd","👇🏻":"1f447-1f3fb","👇🏼":"1f447-1f3fc","👇🏽":"1f447-1f3fd","👇🏾":"1f447-1f3fe","👇🏿":"1f447-1f3ff","☝️":"261d","☎️":"260e","☝🏻":"261d-1f3fb","☝🏼":"261d-1f3fc","☝🏽":"261d-1f3fd","☝🏾":"261d-1f3fe","☝🏿":"261d-1f3ff","🏃🏾":"1f3c3-1f3fe","🫵🏻":"1faf5-1f3fb","🫵🏼":"1faf5-1f3fc","🫵🏽":"1faf5-1f3fd","🫵🏾":"1faf5-1f3fe","🫵🏿":"1faf5-1f3ff","👰🏻":"1f470-1f3fb","👍🏻":"1f44d-1f3fb","👍🏼":"1f44d-1f3fc","👍🏽":"1f44d-1f3fd","👍🏾":"1f44d-1f3fe","👍🏿":"1f44d-1f3ff","👰🏼":"1f470-1f3fc","👎🏻":"1f44e-1f3fb","👎🏼":"1f44e-1f3fc","👎🏽":"1f44e-1f3fd","👎🏾":"1f44e-1f3fe","👎🏿":"1f44e-1f3ff","👰🏽":"1f470-1f3fd","✊🏻":"270a-1f3fb","✊🏼":"270a-1f3fc","✊🏽":"270a-1f3fd","✊🏾":"270a-1f3fe","✊🏿":"270a-1f3ff","👰🏾":"1f470-1f3fe","👊🏻":"1f44a-1f3fb","👊🏼":"1f44a-1f3fc","👊🏽":"1f44a-1f3fd","👊🏾":"1f44a-1f3fe","👊🏿":"1f44a-1f3ff","👰🏿":"1f470-1f3ff","🤛🏻":"1f91b-1f3fb","🤛🏼":"1f91b-1f3fc","🤛🏽":"1f91b-1f3fd","🤛🏾":"1f91b-1f3fe","🤛🏿":"1f91b-1f3ff","🏃🏿":"1f3c3-1f3ff","🤜🏻":"1f91c-1f3fb","🤜🏼":"1f91c-1f3fc","🤜🏽":"1f91c-1f3fd","🤜🏾":"1f91c-1f3fe","🤜🏿":"1f91c-1f3ff","🏍️":"1f3cd","👏🏻":"1f44f-1f3fb","👏🏼":"1f44f-1f3fc","👏🏽":"1f44f-1f3fd","👏🏾":"1f44f-1f3fe","👏🏿":"1f44f-1f3ff","🕴️":"1f574","🙌🏻":"1f64c-1f3fb","🙌🏼":"1f64c-1f3fc","🙌🏽":"1f64c-1f3fd","🙌🏾":"1f64c-1f3fe","🙌🏿":"1f64c-1f3ff","🚶🏻":"1f6b6-1f3fb","🫶🏻":"1faf6-1f3fb","🫶🏼":"1faf6-1f3fc","🫶🏽":"1faf6-1f3fd","🫶🏾":"1faf6-1f3fe","🫶🏿":"1faf6-1f3ff","🚶🏼":"1f6b6-1f3fc","👐🏻":"1f450-1f3fb","👐🏼":"1f450-1f3fc","👐🏽":"1f450-1f3fd","👐🏾":"1f450-1f3fe","👐🏿":"1f450-1f3ff","🚶🏽":"1f6b6-1f3fd","🤲🏻":"1f932-1f3fb","🤲🏼":"1f932-1f3fc","🤲🏽":"1f932-1f3fd","🤲🏾":"1f932-1f3fe","🤲🏿":"1f932-1f3ff","🚶🏾":"1f6b6-1f3fe","🤝🏻":"1f91d-1f3fb","🤝🏼":"1f91d-1f3fc","🤝🏽":"1f91d-1f3fd","🤝🏾":"1f91d-1f3fe","🤝🏿":"1f91d-1f3ff","🇻🇺":"1f1fb-1f1fa","🇻🇳":"1f1fb-1f1f3","🇻🇮":"1f1fb-1f1ee","🇻🇬":"1f1fb-1f1ec","🇻🇪":"1f1fb-1f1ea","🇻🇨":"1f1fb-1f1e8","🇻🇦":"1f1fb-1f1e6","🇺🇿":"1f1fa-1f1ff","🇺🇾":"1f1fa-1f1fe","🇺🇸":"1f1fa-1f1f8","🇺🇳":"1f1fa-1f1f3","🇺🇲":"1f1fa-1f1f2","🇺🇬":"1f1fa-1f1ec","🇺🇦":"1f1fa-1f1e6","🇹🇿":"1f1f9-1f1ff","🇹🇼":"1f1f9-1f1fc","🇹🇻":"1f1f9-1f1fb","🇹🇹":"1f1f9-1f1f9","🇹🇷":"1f1f9-1f1f7","🇹🇴":"1f1f9-1f1f4","🚶🏿":"1f6b6-1f3ff","🙏🏻":"1f64f-1f3fb","🙏🏼":"1f64f-1f3fc","🙏🏽":"1f64f-1f3fd","🙏🏾":"1f64f-1f3fe","🙏🏿":"1f64f-1f3ff","✍️":"270d","⚱️":"26b1","✍🏻":"270d-1f3fb","✍🏼":"270d-1f3fc","✍🏽":"270d-1f3fd","✍🏾":"270d-1f3fe","✍🏿":"270d-1f3ff","🏎️":"1f3ce","💅🏻":"1f485-1f3fb","💅🏼":"1f485-1f3fc","💅🏽":"1f485-1f3fd","💅🏾":"1f485-1f3fe","💅🏿":"1f485-1f3ff","🇾🇪":"1f1fe-1f1ea","🤳🏻":"1f933-1f3fb","🤳🏼":"1f933-1f3fc","🤳🏽":"1f933-1f3fd","🤳🏾":"1f933-1f3fe","🤳🏿":"1f933-1f3ff","🏇🏼":"1f3c7-1f3fc","💪🏻":"1f4aa-1f3fb","💪🏼":"1f4aa-1f3fc","💪🏽":"1f4aa-1f3fd","💪🏾":"1f4aa-1f3fe","💪🏿":"1f4aa-1f3ff","⚰️":"26b0","🕴🏻":"1f574-1f3fb","🏚️":"1f3da","🦵🏻":"1f9b5-1f3fb","🦵🏼":"1f9b5-1f3fc","🦵🏽":"1f9b5-1f3fd","🦵🏾":"1f9b5-1f3fe","🦵🏿":"1f9b5-1f3ff","🕴🏼":"1f574-1f3fc","🦶🏻":"1f9b6-1f3fb","🦶🏼":"1f9b6-1f3fc","🦶🏽":"1f9b6-1f3fd","🦶🏾":"1f9b6-1f3fe","🦶🏿":"1f9b6-1f3ff","🏘️":"1f3d8","👂🏻":"1f442-1f3fb","👂🏼":"1f442-1f3fc","👂🏽":"1f442-1f3fd","👂🏾":"1f442-1f3fe","👂🏿":"1f442-1f3ff","☘️":"2618","🦻🏻":"1f9bb-1f3fb","🦻🏼":"1f9bb-1f3fc","🦻🏽":"1f9bb-1f3fd","🦻🏾":"1f9bb-1f3fe","🦻🏿":"1f9bb-1f3ff","🕴🏽":"1f574-1f3fd","👃🏻":"1f443-1f3fb","👃🏼":"1f443-1f3fc","👃🏽":"1f443-1f3fd","👃🏾":"1f443-1f3fe","👃🏿":"1f443-1f3ff","🕴🏾":"1f574-1f3fe","🕴🏿":"1f574-1f3ff","🎛️":"1f39b","🧜🏻":"1f9dc-1f3fb","🎚️":"1f39a","🧜🏼":"1f9dc-1f3fc","👁️":"1f441","🎙️":"1f399","🧜🏽":"1f9dc-1f3fd","🤰🏻":"1f930-1f3fb","🤰🏼":"1f930-1f3fc","🤰🏽":"1f930-1f3fd","👶🏻":"1f476-1f3fb","👶🏼":"1f476-1f3fc","👶🏽":"1f476-1f3fd","👶🏾":"1f476-1f3fe","👶🏿":"1f476-1f3ff","🤰🏾":"1f930-1f3fe","🧒🏻":"1f9d2-1f3fb","🧒🏼":"1f9d2-1f3fc","🧒🏽":"1f9d2-1f3fd","🧒🏾":"1f9d2-1f3fe","🧒🏿":"1f9d2-1f3ff","🤰🏿":"1f930-1f3ff","👦🏻":"1f466-1f3fb","👦🏼":"1f466-1f3fc","👦🏽":"1f466-1f3fd","👦🏾":"1f466-1f3fe","👦🏿":"1f466-1f3ff","🧜🏾":"1f9dc-1f3fe","👧🏻":"1f467-1f3fb","👧🏼":"1f467-1f3fc","👧🏽":"1f467-1f3fd","👧🏾":"1f467-1f3fe","👧🏿":"1f467-1f3ff","🫃🏻":"1fac3-1f3fb","🧑🏻":"1f9d1-1f3fb","🧑🏼":"1f9d1-1f3fc","🧑🏽":"1f9d1-1f3fd","🧑🏾":"1f9d1-1f3fe","🧑🏿":"1f9d1-1f3ff","☹️":"2639","👱🏻":"1f471-1f3fb","👱🏼":"1f471-1f3fc","👱🏽":"1f471-1f3fd","👱🏾":"1f471-1f3fe","👱🏿":"1f471-1f3ff","🫃🏼":"1fac3-1f3fc","👨🏻":"1f468-1f3fb","👨🏼":"1f468-1f3fc","👨🏽":"1f468-1f3fd","👨🏾":"1f468-1f3fe","👨🏿":"1f468-1f3ff","🫃🏽":"1fac3-1f3fd","🧔🏻":"1f9d4-1f3fb","🧔🏼":"1f9d4-1f3fc","🧔🏽":"1f9d4-1f3fd","🧔🏾":"1f9d4-1f3fe","🧔🏿":"1f9d4-1f3ff","🇹🇳":"1f1f9-1f1f3","🫃🏾":"1fac3-1f3fe","🇹🇲":"1f1f9-1f1f2","🇹🇱":"1f1f9-1f1f1","🇹🇰":"1f1f9-1f1f0","🇹🇯":"1f1f9-1f1ef","🇹🇭":"1f1f9-1f1ed","🇹🇬":"1f1f9-1f1ec","🇹🇫":"1f1f9-1f1eb","🇹🇩":"1f1f9-1f1e9","🇹🇨":"1f1f9-1f1e8","🇹🇦":"1f1f9-1f1e6","🇸🇿":"1f1f8-1f1ff","🛋️":"1f6cb","🇸🇾":"1f1f8-1f1fe","🇸🇽":"1f1f8-1f1fd","🇸🇻":"1f1f8-1f1fb","🇸🇹":"1f1f8-1f1f9","🇸🇸":"1f1f8-1f1f8","🇸🇷":"1f1f8-1f1f7","🇸🇴":"1f1f8-1f1f4","🇸🇳":"1f1f8-1f1f3","🇸🇲":"1f1f8-1f1f2","🇸🇱":"1f1f8-1f1f1","🫃🏿":"1fac3-1f3ff","🇸🇰":"1f1f8-1f1f0","🇸🇯":"1f1f8-1f1ef","🇸🇮":"1f1f8-1f1ee","🇸🇭":"1f1f8-1f1ed","🇸🇬":"1f1f8-1f1ec","🛏️":"1f6cf","🇸🇪":"1f1f8-1f1ea","🇸🇩":"1f1f8-1f1e9","🇸🇨":"1f1f8-1f1e8","🇸🇧":"1f1f8-1f1e7","🇸🇦":"1f1f8-1f1e6","🧜🏿":"1f9dc-1f3ff","🇷🇼":"1f1f7-1f1fc","🇷🇺":"1f1f7-1f1fa","🇷🇸":"1f1f7-1f1f8","🇷🇴":"1f1f7-1f1f4","🇷🇪":"1f1f7-1f1ea","🫄🏻":"1fac4-1f3fb","🇶🇦":"1f1f6-1f1e6","🇵🇾":"1f1f5-1f1fe","🇵🇼":"1f1f5-1f1fc","🇵🇹":"1f1f5-1f1f9","🇵🇸":"1f1f5-1f1f8","🫄🏼":"1fac4-1f3fc","👩🏻":"1f469-1f3fb","👩🏼":"1f469-1f3fc","👩🏽":"1f469-1f3fd","👩🏾":"1f469-1f3fe","👩🏿":"1f469-1f3ff","🫄🏽":"1fac4-1f3fd","🇵🇷":"1f1f5-1f1f7","🇵🇳":"1f1f5-1f1f3","🇵🇲":"1f1f5-1f1f2","🇵🇱":"1f1f5-1f1f1","🇵🇰":"1f1f5-1f1f0","🫄🏾":"1fac4-1f3fe","🇵🇭":"1f1f5-1f1ed","🇵🇬":"1f1f5-1f1ec","🇵🇫":"1f1f5-1f1eb","🇵🇪":"1f1f5-1f1ea","🇵🇦":"1f1f5-1f1e6","🫄🏿":"1fac4-1f3ff","🇴🇲":"1f1f4-1f1f2","🇳🇿":"1f1f3-1f1ff","🇳🇺":"1f1f3-1f1fa","🇳🇷":"1f1f3-1f1f7","🇳🇵":"1f1f3-1f1f5","🏇🏽":"1f3c7-1f3fd","🇳🇴":"1f1f3-1f1f4","🇳🇱":"1f1f3-1f1f1","🇳🇮":"1f1f3-1f1ee","🇳🇬":"1f1f3-1f1ec","🇳🇫":"1f1f3-1f1eb","🤱🏻":"1f931-1f3fb","🇳🇪":"1f1f3-1f1ea","🇳🇨":"1f1f3-1f1e8","🇳🇦":"1f1f3-1f1e6","🇲🇿":"1f1f2-1f1ff","🇲🇾":"1f1f2-1f1fe","☺️":"263a","🇲🇽":"1f1f2-1f1fd","🇲🇼":"1f1f2-1f1fc","🇲🇻":"1f1f2-1f1fb","🇲🇺":"1f1f2-1f1fa","🇲🇹":"1f1f2-1f1f9","🤱🏼":"1f931-1f3fc","🇲🇸":"1f1f2-1f1f8","🇲🇷":"1f1f2-1f1f7","🇲🇶":"1f1f2-1f1f6","🇲🇵":"1f1f2-1f1f5","🇲🇴":"1f1f2-1f1f4","🤱🏽":"1f931-1f3fd","🇲🇳":"1f1f2-1f1f3","🇲🇲":"1f1f2-1f1f2","🇲🇱":"1f1f2-1f1f1","🇲🇰":"1f1f2-1f1f0","🇲🇭":"1f1f2-1f1ed","🇲🇬":"1f1f2-1f1ec","🤱🏾":"1f931-1f3fe","🇲🇫":"1f1f2-1f1eb","🇲🇪":"1f1f2-1f1ea","🇲🇩":"1f1f2-1f1e9","🇲🇨":"1f1f2-1f1e8","🇲🇦":"1f1f2-1f1e6","🇱🇾":"1f1f1-1f1fe","🇱🇻":"1f1f1-1f1fb","🇱🇺":"1f1f1-1f1fa","🇱🇹":"1f1f1-1f1f9","🇱🇸":"1f1f1-1f1f8","🇱🇷":"1f1f1-1f1f7","🤱🏿":"1f931-1f3ff","🇱🇰":"1f1f1-1f1f0","🇱🇮":"1f1f1-1f1ee","🇱🇨":"1f1f1-1f1e8","🇱🇧":"1f1f1-1f1e7","🇱🇦":"1f1f1-1f1e6","🇰🇿":"1f1f0-1f1ff","🇰🇾":"1f1f0-1f1fe","🇰🇼":"1f1f0-1f1fc","🇰🇷":"1f1f0-1f1f7","🇰🇵":"1f1f0-1f1f5","🏗️":"1f3d7","🧓🏻":"1f9d3-1f3fb","🧓🏼":"1f9d3-1f3fc","🧓🏽":"1f9d3-1f3fd","🧓🏾":"1f9d3-1f3fe","🧓🏿":"1f9d3-1f3ff","🏇🏾":"1f3c7-1f3fe","👴🏻":"1f474-1f3fb","👴🏼":"1f474-1f3fc","👴🏽":"1f474-1f3fd","👴🏾":"1f474-1f3fe","👴🏿":"1f474-1f3ff","🧎🏻":"1f9ce-1f3fb","👵🏻":"1f475-1f3fb","👵🏼":"1f475-1f3fc","👵🏽":"1f475-1f3fd","👵🏾":"1f475-1f3fe","👵🏿":"1f475-1f3ff","🧎🏼":"1f9ce-1f3fc","🙍🏻":"1f64d-1f3fb","🙍🏼":"1f64d-1f3fc","🙍🏽":"1f64d-1f3fd","🙍🏾":"1f64d-1f3fe","🙍🏿":"1f64d-1f3ff","🇰🇳":"1f1f0-1f1f3","🎖️":"1f396","🇰🇲":"1f1f0-1f1f2","🇰🇮":"1f1f0-1f1ee","🇰🇭":"1f1f0-1f1ed","🇰🇬":"1f1f0-1f1ec","🇰🇪":"1f1f0-1f1ea","🇯🇵":"1f1ef-1f1f5","🇯🇴":"1f1ef-1f1f4","🇯🇲":"1f1ef-1f1f2","🇯🇪":"1f1ef-1f1ea","🇮🇹":"1f1ee-1f1f9","🇮🇸":"1f1ee-1f1f8","🧎🏽":"1f9ce-1f3fd","🇮🇷":"1f1ee-1f1f7","🇮🇶":"1f1ee-1f1f6","🇮🇴":"1f1ee-1f1f4","🇮🇳":"1f1ee-1f1f3","🇮🇲":"1f1ee-1f1f2","🇮🇱":"1f1ee-1f1f1","🇮🇪":"1f1ee-1f1ea","🇮🇩":"1f1ee-1f1e9","🇮🇨":"1f1ee-1f1e8","🇭🇺":"1f1ed-1f1fa","🧎🏾":"1f9ce-1f3fe","🙎🏻":"1f64e-1f3fb","🙎🏼":"1f64e-1f3fc","🙎🏽":"1f64e-1f3fd","🙎🏾":"1f64e-1f3fe","🙎🏿":"1f64e-1f3ff","🇭🇹":"1f1ed-1f1f9","🎟️":"1f39f","🇭🇷":"1f1ed-1f1f7","🇭🇳":"1f1ed-1f1f3","🇭🇲":"1f1ed-1f1f2","🇭🇰":"1f1ed-1f1f0","🇬🇾":"1f1ec-1f1fe","🇬🇼":"1f1ec-1f1fc","🇬🇺":"1f1ec-1f1fa","🇬🇹":"1f1ec-1f1f9","🇬🇸":"1f1ec-1f1f8","🇬🇷":"1f1ec-1f1f7","🇬🇶":"1f1ec-1f1f6","⚗️":"2697","🇬🇵":"1f1ec-1f1f5","🇬🇳":"1f1ec-1f1f3","🇬🇲":"1f1ec-1f1f2","🇬🇱":"1f1ec-1f1f1","🇬🇮":"1f1ec-1f1ee","🇬🇭":"1f1ec-1f1ed","🇬🇬":"1f1ec-1f1ec","🇬🇫":"1f1ec-1f1eb","🇬🇪":"1f1ec-1f1ea","🇬🇩":"1f1ec-1f1e9","🧎🏿":"1f9ce-1f3ff","🙅🏻":"1f645-1f3fb","🙅🏼":"1f645-1f3fc","🙅🏽":"1f645-1f3fd","🙅🏾":"1f645-1f3fe","🙅🏿":"1f645-1f3ff","🇬🇧":"1f1ec-1f1e7","🎗️":"1f397","🇬🇦":"1f1ec-1f1e6","🇫🇷":"1f1eb-1f1f7","🇫🇴":"1f1eb-1f1f4","🇫🇲":"1f1eb-1f1f2","🇫🇰":"1f1eb-1f1f0","🇫🇯":"1f1eb-1f1ef","🇫🇮":"1f1eb-1f1ee","🇪🇺":"1f1ea-1f1fa","🇪🇹":"1f1ea-1f1f9","🇪🇸":"1f1ea-1f1f8","🇪🇷":"1f1ea-1f1f7","🏛️":"1f3db","🇪🇭":"1f1ea-1f1ed","🇪🇬":"1f1ea-1f1ec","🇪🇪":"1f1ea-1f1ea","🇪🇨":"1f1ea-1f1e8","🇪🇦":"1f1ea-1f1e6","🇩🇿":"1f1e9-1f1ff","🇩🇴":"1f1e9-1f1f4","🇩🇲":"1f1e9-1f1f2","🇩🇰":"1f1e9-1f1f0","🇩🇯":"1f1e9-1f1ef","🏇🏿":"1f3c7-1f3ff","🙆🏻":"1f646-1f3fb","🙆🏼":"1f646-1f3fc","🙆🏽":"1f646-1f3fd","🙆🏾":"1f646-1f3fe","🙆🏿":"1f646-1f3ff","🇩🇬":"1f1e9-1f1ec","🏟️":"1f3df","🇩🇪":"1f1e9-1f1ea","🇨🇿":"1f1e8-1f1ff","🇨🇾":"1f1e8-1f1fe","🇨🇽":"1f1e8-1f1fd","🇨🇼":"1f1e8-1f1fc","🇨🇻":"1f1e8-1f1fb","🇨🇺":"1f1e8-1f1fa","🇨🇷":"1f1e8-1f1f7","🇨🇵":"1f1e8-1f1f5","🇨🇴":"1f1e8-1f1f4","🇨🇳":"1f1e8-1f1f3","⛓️":"26d3","🇨🇲":"1f1e8-1f1f2","🇨🇱":"1f1e8-1f1f1","🇨🇰":"1f1e8-1f1f0","🇨🇮":"1f1e8-1f1ee","🇨🇭":"1f1e8-1f1ed","🇨🇬":"1f1e8-1f1ec","🇨🇫":"1f1e8-1f1eb","🇨🇩":"1f1e8-1f1e9","🇨🇨":"1f1e8-1f1e8","🇨🇦":"1f1e8-1f1e6","🕊️":"1f54a","💁🏻":"1f481-1f3fb","💁🏼":"1f481-1f3fc","💁🏽":"1f481-1f3fd","💁🏾":"1f481-1f3fe","💁🏿":"1f481-1f3ff","🇧🇿":"1f1e7-1f1ff","🏞️":"1f3de","🇧🇾":"1f1e7-1f1fe","🇧🇼":"1f1e7-1f1fc","🇧🇻":"1f1e7-1f1fb","🇧🇹":"1f1e7-1f1f9","🇧🇸":"1f1e7-1f1f8","🇧🇷":"1f1e7-1f1f7","🇧🇶":"1f1e7-1f1f6","🇧🇴":"1f1e7-1f1f4","🇧🇳":"1f1e7-1f1f3","🇧🇲":"1f1e7-1f1f2","🇧🇱":"1f1e7-1f1f1","⛷️":"26f7","🇧🇯":"1f1e7-1f1ef","🇧🇮":"1f1e7-1f1ee","🇧🇭":"1f1e7-1f1ed","🇧🇬":"1f1e7-1f1ec","🇧🇫":"1f1e7-1f1eb","🇧🇪":"1f1e7-1f1ea","🇧🇩":"1f1e7-1f1e9","🇧🇧":"1f1e7-1f1e7","🇧🇦":"1f1e7-1f1e6","🇦🇿":"1f1e6-1f1ff","🏝️":"1f3dd","🙋🏻":"1f64b-1f3fb","🙋🏼":"1f64b-1f3fc","🙋🏽":"1f64b-1f3fd","🙋🏾":"1f64b-1f3fe","🙋🏿":"1f64b-1f3ff","🇦🇽":"1f1e6-1f1fd","⚖️":"2696","🇦🇼":"1f1e6-1f1fc","🇦🇺":"1f1e6-1f1fa","🇦🇹":"1f1e6-1f1f9","🇦🇸":"1f1e6-1f1f8","🇦🇷":"1f1e6-1f1f7","🇦🇶":"1f1e6-1f1f6","🇦🇴":"1f1e6-1f1f4","🇦🇲":"1f1e6-1f1f2","🇦🇱":"1f1e6-1f1f1","🇦🇮":"1f1e6-1f1ee","🇦🇬":"1f1e6-1f1ec","🏌🏽":"1f3cc-1f3fd","🇦🇫":"1f1e6-1f1eb","🇦🇪":"1f1e6-1f1ea","🇦🇩":"1f1e6-1f1e9","🇦🇨":"1f1e6-1f1e8","🗜️":"1f5dc","🏳️":"1f3f3","🧖🏻":"1f9d6-1f3fb","👼🏻":"1f47c-1f3fb","⚙️":"2699","👼🏼":"1f47c-1f3fc","👼🏽":"1f47c-1f3fd","🧏🏻":"1f9cf-1f3fb","🧏🏼":"1f9cf-1f3fc","🧏🏽":"1f9cf-1f3fd","🧏🏾":"1f9cf-1f3fe","🧏🏿":"1f9cf-1f3ff","👼🏾":"1f47c-1f3fe","👼🏿":"1f47c-1f3ff","🏜️":"1f3dc","🎅🏻":"1f385-1f3fb","🛡️":"1f6e1","🎅🏼":"1f385-1f3fc","🎅🏽":"1f385-1f3fd","🎅🏾":"1f385-1f3fe","🎅🏿":"1f385-1f3ff","🕰️":"1f570","⚔️":"2694","🤶🏻":"1f936-1f3fb","▫️":"25ab","🗡️":"1f5e1","🤶🏼":"1f936-1f3fc","▪️":"25aa","🤶🏽":"1f936-1f3fd","🛠️":"1f6e0","🤶🏾":"1f936-1f3fe","◻️":"25fb","⚒️":"2692","◼️":"25fc","🤶🏿":"1f936-1f3ff","🇾🇹":"1f1fe-1f1f9","⛏️":"26cf","🙇🏻":"1f647-1f3fb","🙇🏼":"1f647-1f3fc","🙇🏽":"1f647-1f3fd","🙇🏾":"1f647-1f3fe","🙇🏿":"1f647-1f3ff","⏲️":"23f2","🏖️":"1f3d6","⛑️":"26d1","🗝️":"1f5dd","🧝🏻":"1f9dd-1f3fb","👮🏻":"1f46e-1f3fb","👮🏼":"1f46e-1f3fc","👮🏽":"1f46e-1f3fd","👮🏾":"1f46e-1f3fe","👮🏿":"1f46e-1f3ff","🧝🏼":"1f9dd-1f3fc","🧝🏽":"1f9dd-1f3fd","🦸🏻":"1f9b8-1f3fb","🦸🏼":"1f9b8-1f3fc","🦸🏽":"1f9b8-1f3fd","🦸🏾":"1f9b8-1f3fe","🦸🏿":"1f9b8-1f3ff","🗑️":"1f5d1","🧝🏾":"1f9dd-1f3fe","🗄️":"1f5c4","🧝🏿":"1f9dd-1f3ff","㊙️":"3299","🗃️":"1f5c3","㊗️":"3297","⏱️":"23f1","🤦🏻":"1f926-1f3fb","🤦🏼":"1f926-1f3fc","🤦🏽":"1f926-1f3fd","🤦🏾":"1f926-1f3fe","🤦🏿":"1f926-1f3ff","🧖🏼":"1f9d6-1f3fc","✂️":"2702","🏕️":"1f3d5","🧖🏽":"1f9d6-1f3fd","🧖🏾":"1f9d6-1f3fe","🖇️":"1f587","🧖🏿":"1f9d6-1f3ff","⛰️":"26f0","🛎️":"1f6ce","❣️":"2763","🏔️":"1f3d4","🕵️":"1f575","🈷️":"1f237","🐿️":"1f43f","🕵🏻":"1f575-1f3fb","🈂️":"1f202","🕵🏼":"1f575-1f3fc","🕵🏽":"1f575-1f3fd","🕵🏾":"1f575-1f3fe","🕵🏿":"1f575-1f3ff","☄️":"2604","🅿️":"1f17f","🛰️":"1f6f0","🌶️":"1f336","🏌🏾":"1f3cc-1f3fe","🤷🏻":"1f937-1f3fb","🤷🏼":"1f937-1f3fc","🤷🏽":"1f937-1f3fd","🤷🏾":"1f937-1f3fe","🤷🏿":"1f937-1f3ff","🅾️":"1f17e","☃️":"2603","🗓️":"1f5d3","🛍️":"1f6cd","🗒️":"1f5d2","Ⓜ️":"24c2","🗺️":"1f5fa","❄️":"2744","ℹ️":"2139","🧗🏻":"1f9d7-1f3fb","🗂️":"1f5c2","🏵️":"1f3f5","⛱️":"26f1","🇿🇼":"1f1ff-1f1fc","🅱️":"1f171","🧗🏼":"1f9d7-1f3fc","🦹🏻":"1f9b9-1f3fb","🅰️":"1f170","🦹🏼":"1f9b9-1f3fc","🦹🏽":"1f9b9-1f3fd","🖍️":"1f58d","🦹🏾":"1f9b9-1f3fe","🖌️":"1f58c","🦹🏿":"1f9b9-1f3ff","9⃣":"39-20e3","🖊️":"1f58a","8⃣":"38-20e3","7⃣":"37-20e3","6⃣":"36-20e3","5⃣":"35-20e3","4⃣":"34-20e3","3⃣":"33-20e3","2⃣":"32-20e3","1⃣":"31-20e3","0⃣":"30-20e3","*⃣":"2a-20e3","#⃣":"23-20e3","❤️":"2764","🖋️":"1f58b","™️":"2122","☂️":"2602","®️":"ae","✒️":"2712","©️":"a9","♨️":"2668","❇️":"2747","🧗🏽":"1f9d7-1f3fd","✴️":"2734","💂🏻":"1f482-1f3fb","💂🏼":"1f482-1f3fc","✳️":"2733","💂🏽":"1f482-1f3fd","〽️":"303d","💂🏾":"1f482-1f3fe","💂🏿":"1f482-1f3ff","✏️":"270f","🛩️":"1f6e9","🧗🏾":"1f9d7-1f3fe","✔️":"2714","🗳️":"1f5f3","✈️":"2708","☑️":"2611","🧗🏿":"1f9d7-1f3ff","🏂🏻":"1f3c2-1f3fb","🛥️":"1f6e5","🏂🏼":"1f3c2-1f3fc","⛴️":"26f4","🏂🏽":"1f3c2-1f3fd","☠️":"2620","⚜️":"269c","🛳️":"1f6f3","♻️":"267b","💆🏻":"1f486-1f3fb","💆🏼":"1f486-1f3fc","⚕️":"2695","💆🏽":"1f486-1f3fd","💆🏾":"1f486-1f3fe","✉️":"2709","💆🏿":"1f486-1f3ff","〰️":"3030","🕶️":"1f576","🌬️":"1f32c","🏙️":"1f3d9","🏂🏾":"1f3c2-1f3fe","🌫️":"1f32b","🧙🏻":"1f9d9-1f3fb","⁉️":"2049","🥷🏻":"1f977-1f3fb","‼️":"203c","🥷🏼":"1f977-1f3fc","🇿🇲":"1f1ff-1f1f2","♾️":"267e","🥷🏽":"1f977-1f3fd","🥷🏾":"1f977-1f3fe","🥷🏿":"1f977-1f3ff","🧙🏼":"1f9d9-1f3fc","👷🏻":"1f477-1f3fb","👷🏼":"1f477-1f3fc","✖️":"2716","👷🏽":"1f477-1f3fd","⚧️":"26a7","👷🏾":"1f477-1f3fe","♂️":"2642","👷🏿":"1f477-1f3ff","♀️":"2640","🧙🏽":"1f9d9-1f3fd","🧙🏾":"1f9d9-1f3fe","🧙🏿":"1f9d9-1f3ff","🍽️":"1f37d","🇿🇦":"1f1ff-1f1e6","🌪️":"1f32a","🏂🏿":"1f3c2-1f3ff","🏷️":"1f3f7","🌩️":"1f329","⏏️":"23cf","🏌️":"1f3cc","⏺️":"23fa","🖼️":"1f5bc","⏹️":"23f9","🗞️":"1f5de","⏸️":"23f8","🌨️":"1f328","🕸️":"1f578","🌧️":"1f327","🏌🏿":"1f3cc-1f3ff","⛩️":"26e9","♟️":"265f","⏮️":"23ee","🌦️":"1f326","🕷️":"1f577","◀️":"25c0","♣️":"2663","🌥️":"1f325","⏯️":"23ef","♦️":"2666","⏭️":"23ed","🏌🏻":"1f3cc-1f3fb","♥️":"2665","🌤️":"1f324","▶️":"25b6","🫅🏻":"1fac5-1f3fb","🫅🏼":"1fac5-1f3fc","🫅🏽":"1fac5-1f3fd","🫅🏾":"1fac5-1f3fe","🫅🏿":"1fac5-1f3ff","♠️":"2660","🤴🏻":"1f934-1f3fb","🤴🏼":"1f934-1f3fc","🤴🏽":"1f934-1f3fd","🤴🏾":"1f934-1f3fe","🤴🏿":"1f934-1f3ff","🏌🏼":"1f3cc-1f3fc","👸🏻":"1f478-1f3fb","👸🏼":"1f478-1f3fc","👸🏽":"1f478-1f3fd","👸🏾":"1f478-1f3fe","👸🏿":"1f478-1f3ff","⛈️":"26c8","👳🏻":"1f473-1f3fb","👳🏼":"1f473-1f3fc","👳🏽":"1f473-1f3fd","👳🏾":"1f473-1f3fe","👳🏿":"1f473-1f3ff","💃🏻":"1f483-1f3fb","☮️":"262e","💃🏼":"1f483-1f3fc","☪️":"262a","🧚🏻":"1f9da-1f3fb","🧚🏼":"1f9da-1f3fc","☦️":"2626","🧚🏽":"1f9da-1f3fd","✝️":"271d","🧚🏾":"1f9da-1f3fe","🏄🏻":"1f3c4-1f3fb","👩‍🦱":"1f469-200d-1f9b1","😶‍🌫":"1f636-200d-1f32b-fe0f","😮‍💨":"1f62e-200d-1f4a8","🙂‍↔":"1f642-200d-2194-fe0f","🙂‍↕":"1f642-200d-2195-fe0f","😵‍💫":"1f635-200d-1f4ab","❤‍🔥":"2764-fe0f-200d-1f525","❤‍🩹":"2764-fe0f-200d-1fa79","👁‍🗨":"1f441-200d-1f5e8","🧔‍♂":"1f9d4-200d-2642-fe0f","🧔‍♀":"1f9d4-200d-2640-fe0f","👨‍🦰":"1f468-200d-1f9b0","👨‍🦱":"1f468-200d-1f9b1","👨‍🦳":"1f468-200d-1f9b3","👨‍🦲":"1f468-200d-1f9b2","👩‍🦰":"1f469-200d-1f9b0","🧑‍🦰":"1f9d1-200d-1f9b0","🧑‍🦱":"1f9d1-200d-1f9b1","👩‍🦳":"1f469-200d-1f9b3","🧑‍🦳":"1f9d1-200d-1f9b3","👩‍🦲":"1f469-200d-1f9b2","🧑‍🦲":"1f9d1-200d-1f9b2","👱‍♀":"1f471-200d-2640-fe0f","👱‍♂":"1f471-200d-2642-fe0f","🙍‍♂":"1f64d-200d-2642-fe0f","🙍‍♀":"1f64d-200d-2640-fe0f","🙎‍♂":"1f64e-200d-2642-fe0f","🙎‍♀":"1f64e-200d-2640-fe0f","🙅‍♂":"1f645-200d-2642-fe0f","🙅‍♀":"1f645-200d-2640-fe0f","🙆‍♂":"1f646-200d-2642-fe0f","🙆‍♀":"1f646-200d-2640-fe0f","💁‍♂":"1f481-200d-2642-fe0f","💁‍♀":"1f481-200d-2640-fe0f","🙋‍♂":"1f64b-200d-2642-fe0f","🙋‍♀":"1f64b-200d-2640-fe0f","🧏‍♂":"1f9cf-200d-2642-fe0f","🧏‍♀":"1f9cf-200d-2640-fe0f","🙇‍♂":"1f647-200d-2642-fe0f","🙇‍♀":"1f647-200d-2640-fe0f","🤦‍♂":"1f926-200d-2642-fe0f","🤦‍♀":"1f926-200d-2640-fe0f","🤷‍♂":"1f937-200d-2642-fe0f","🤷‍♀":"1f937-200d-2640-fe0f","🧑‍⚕":"1f9d1-200d-2695-fe0f","👨‍⚕":"1f468-200d-2695-fe0f","👩‍⚕":"1f469-200d-2695-fe0f","🧑‍🎓":"1f9d1-200d-1f393","👨‍🎓":"1f468-200d-1f393","👩‍🎓":"1f469-200d-1f393","🧑‍🏫":"1f9d1-200d-1f3eb","👨‍🏫":"1f468-200d-1f3eb","👩‍🏫":"1f469-200d-1f3eb","🧑‍⚖":"1f9d1-200d-2696-fe0f","👨‍⚖":"1f468-200d-2696-fe0f","👩‍⚖":"1f469-200d-2696-fe0f","🧑‍🌾":"1f9d1-200d-1f33e","👨‍🌾":"1f468-200d-1f33e","👩‍🌾":"1f469-200d-1f33e","🧑‍🍳":"1f9d1-200d-1f373","👨‍🍳":"1f468-200d-1f373","👩‍🍳":"1f469-200d-1f373","🧑‍🔧":"1f9d1-200d-1f527","👨‍🔧":"1f468-200d-1f527","👩‍🔧":"1f469-200d-1f527","🧑‍🏭":"1f9d1-200d-1f3ed","👨‍🏭":"1f468-200d-1f3ed","👩‍🏭":"1f469-200d-1f3ed","🧑‍💼":"1f9d1-200d-1f4bc","👨‍💼":"1f468-200d-1f4bc","👩‍💼":"1f469-200d-1f4bc","🧑‍🔬":"1f9d1-200d-1f52c","👨‍🔬":"1f468-200d-1f52c","👩‍🔬":"1f469-200d-1f52c","🧑‍💻":"1f9d1-200d-1f4bb","👨‍💻":"1f468-200d-1f4bb","👩‍💻":"1f469-200d-1f4bb","🧑‍🎤":"1f9d1-200d-1f3a4","👨‍🎤":"1f468-200d-1f3a4","👩‍🎤":"1f469-200d-1f3a4","🧑‍🎨":"1f9d1-200d-1f3a8","👨‍🎨":"1f468-200d-1f3a8","👩‍🎨":"1f469-200d-1f3a8","🧑‍✈":"1f9d1-200d-2708-fe0f","👨‍✈":"1f468-200d-2708-fe0f","👩‍✈":"1f469-200d-2708-fe0f","🧑‍🚀":"1f9d1-200d-1f680","👨‍🚀":"1f468-200d-1f680","👩‍🚀":"1f469-200d-1f680","🧑‍🚒":"1f9d1-200d-1f692","👨‍🚒":"1f468-200d-1f692","👩‍🚒":"1f469-200d-1f692","👮‍♂":"1f46e-200d-2642-fe0f","👮‍♀":"1f46e-200d-2640-fe0f","🕵‍♂":"1f575-fe0f-200d-2642-fe0f","🕵‍♀":"1f575-fe0f-200d-2640-fe0f","💂‍♂":"1f482-200d-2642-fe0f","💂‍♀":"1f482-200d-2640-fe0f","👷‍♂":"1f477-200d-2642-fe0f","👷‍♀":"1f477-200d-2640-fe0f","👳‍♂":"1f473-200d-2642-fe0f","👳‍♀":"1f473-200d-2640-fe0f","🤵‍♂":"1f935-200d-2642-fe0f","🤵‍♀":"1f935-200d-2640-fe0f","👰‍♂":"1f470-200d-2642-fe0f","👰‍♀":"1f470-200d-2640-fe0f","👩‍🍼":"1f469-200d-1f37c","👨‍🍼":"1f468-200d-1f37c","🧑‍🍼":"1f9d1-200d-1f37c","🧑‍🎄":"1f9d1-200d-1f384","🦸‍♂":"1f9b8-200d-2642-fe0f","🦸‍♀":"1f9b8-200d-2640-fe0f","🦹‍♂":"1f9b9-200d-2642-fe0f","🦹‍♀":"1f9b9-200d-2640-fe0f","🧙‍♂":"1f9d9-200d-2642-fe0f","🧙‍♀":"1f9d9-200d-2640-fe0f","🧚‍♂":"1f9da-200d-2642-fe0f","🧚‍♀":"1f9da-200d-2640-fe0f","🧛‍♂":"1f9db-200d-2642-fe0f","🧛‍♀":"1f9db-200d-2640-fe0f","🧜‍♂":"1f9dc-200d-2642-fe0f","🧜‍♀":"1f9dc-200d-2640-fe0f","🧝‍♂":"1f9dd-200d-2642-fe0f","🧝‍♀":"1f9dd-200d-2640-fe0f","🧞‍♂":"1f9de-200d-2642-fe0f","🧞‍♀":"1f9de-200d-2640-fe0f","🧟‍♂":"1f9df-200d-2642-fe0f","🧟‍♀":"1f9df-200d-2640-fe0f","💆‍♂":"1f486-200d-2642-fe0f","💆‍♀":"1f486-200d-2640-fe0f","💇‍♂":"1f487-200d-2642-fe0f","💇‍♀":"1f487-200d-2640-fe0f","🚶‍♂":"1f6b6-200d-2642-fe0f","🚶‍♀":"1f6b6-200d-2640-fe0f","🚶‍➡":"1f6b6-200d-27a1-fe0f","🧍‍♂":"1f9cd-200d-2642-fe0f","🧍‍♀":"1f9cd-200d-2640-fe0f","🧎‍♂":"1f9ce-200d-2642-fe0f","🧎‍♀":"1f9ce-200d-2640-fe0f","🧎‍➡":"1f9ce-200d-27a1-fe0f","🧑‍🦯":"1f9d1-200d-1f9af","👨‍🦯":"1f468-200d-1f9af","👩‍🦯":"1f469-200d-1f9af","🧑‍🦼":"1f9d1-200d-1f9bc","👨‍🦼":"1f468-200d-1f9bc","👩‍🦼":"1f469-200d-1f9bc","🧑‍🦽":"1f9d1-200d-1f9bd","👨‍🦽":"1f468-200d-1f9bd","👩‍🦽":"1f469-200d-1f9bd","🏃‍♂":"1f3c3-200d-2642-fe0f","🏃‍♀":"1f3c3-200d-2640-fe0f","🏃‍➡":"1f3c3-200d-27a1-fe0f","👯‍♂":"1f46f-200d-2642-fe0f","👯‍♀":"1f46f-200d-2640-fe0f","🧖‍♂":"1f9d6-200d-2642-fe0f","🧖‍♀":"1f9d6-200d-2640-fe0f","🧗‍♂":"1f9d7-200d-2642-fe0f","🧗‍♀":"1f9d7-200d-2640-fe0f","🏌‍♂":"1f3cc-fe0f-200d-2642-fe0f","🏌‍♀":"1f3cc-fe0f-200d-2640-fe0f","🏄‍♂":"1f3c4-200d-2642-fe0f","🏄‍♀":"1f3c4-200d-2640-fe0f","🚣‍♂":"1f6a3-200d-2642-fe0f","🚣‍♀":"1f6a3-200d-2640-fe0f","🏊‍♂":"1f3ca-200d-2642-fe0f","🏊‍♀":"1f3ca-200d-2640-fe0f","⛹‍♂":"26f9-fe0f-200d-2642-fe0f","⛹‍♀":"26f9-fe0f-200d-2640-fe0f","🏋‍♂":"1f3cb-fe0f-200d-2642-fe0f","🏋‍♀":"1f3cb-fe0f-200d-2640-fe0f","🚴‍♂":"1f6b4-200d-2642-fe0f","🚴‍♀":"1f6b4-200d-2640-fe0f","🚵‍♂":"1f6b5-200d-2642-fe0f","🚵‍♀":"1f6b5-200d-2640-fe0f","🤸‍♂":"1f938-200d-2642-fe0f","🤸‍♀":"1f938-200d-2640-fe0f","🤼‍♂":"1f93c-200d-2642-fe0f","🤼‍♀":"1f93c-200d-2640-fe0f","🤽‍♂":"1f93d-200d-2642-fe0f","🤽‍♀":"1f93d-200d-2640-fe0f","🤾‍♂":"1f93e-200d-2642-fe0f","🤾‍♀":"1f93e-200d-2640-fe0f","🤹‍♂":"1f939-200d-2642-fe0f","🤹‍♀":"1f939-200d-2640-fe0f","🧘‍♂":"1f9d8-200d-2642-fe0f","🧘‍♀":"1f9d8-200d-2640-fe0f","👨‍👦":"1f468-200d-1f466","👨‍👧":"1f468-200d-1f467","👩‍👦":"1f469-200d-1f466","👩‍👧":"1f469-200d-1f467","🧑‍🧒":"1f9d1-200d-1f9d2","🐕‍🦺":"1f415-200d-1f9ba","🐈‍⬛":"1f408-200d-2b1b","🐻‍❄":"1f43b-200d-2744-fe0f","🐦‍⬛":"1f426-200d-2b1b","🐦‍🔥":"1f426-200d-1f525","🍋‍🟩":"1f34b-200d-1f7e9","🍄‍🟫":"1f344-200d-1f7eb","⛓‍💥":"26d3-fe0f-200d-1f4a5","#️⃣":"23-20e3","*️⃣":"2a-20e3","0️⃣":"30-20e3","1️⃣":"31-20e3","2️⃣":"32-20e3","3️⃣":"33-20e3","4️⃣":"34-20e3","5️⃣":"35-20e3","6️⃣":"36-20e3","7️⃣":"37-20e3","8️⃣":"38-20e3","9️⃣":"39-20e3","🏳‍🌈":"1f3f3-fe0f-200d-1f308","🏳‍⚧":"1f3f3-fe0f-200d-26a7-fe0f","🏴‍☠":"1f3f4-200d-2620-fe0f","👨🏾‍🍼":"1f468-1f3fe-200d-1f37c","🧑🏻‍🍼":"1f9d1-1f3fb-200d-1f37c","🧑🏼‍🍼":"1f9d1-1f3fc-200d-1f37c","🧑🏽‍🍼":"1f9d1-1f3fd-200d-1f37c","🧑🏾‍🍼":"1f9d1-1f3fe-200d-1f37c","🧑🏿‍🍼":"1f9d1-1f3ff-200d-1f37c","🧑🏻‍🎄":"1f9d1-1f3fb-200d-1f384","🤽🏽‍♀":"1f93d-1f3fd-200d-2640-fe0f","🧑🏼‍🎄":"1f9d1-1f3fc-200d-1f384","👨🏿‍🦼":"1f468-1f3ff-200d-1f9bc","👨🏾‍🦼":"1f468-1f3fe-200d-1f9bc","👨🏽‍🦼":"1f468-1f3fd-200d-1f9bc","👨🏼‍🦼":"1f468-1f3fc-200d-1f9bc","👨🏻‍🦼":"1f468-1f3fb-200d-1f9bc","🧑🏽‍🎄":"1f9d1-1f3fd-200d-1f384","🧑🏾‍🎄":"1f9d1-1f3fe-200d-1f384","🧑🏿‍🎄":"1f9d1-1f3ff-200d-1f384","🦸‍♂️":"1f9b8-200d-2642-fe0f","🦸🏻‍♂":"1f9b8-1f3fb-200d-2642-fe0f","🦸🏼‍♂":"1f9b8-1f3fc-200d-2642-fe0f","🦸🏽‍♂":"1f9b8-1f3fd-200d-2642-fe0f","🦸🏾‍♂":"1f9b8-1f3fe-200d-2642-fe0f","🦸🏿‍♂":"1f9b8-1f3ff-200d-2642-fe0f","🦸‍♀️":"1f9b8-200d-2640-fe0f","🤸🏼‍♂":"1f938-1f3fc-200d-2642-fe0f","🦸🏻‍♀":"1f9b8-1f3fb-200d-2640-fe0f","🧑🏿‍🦼":"1f9d1-1f3ff-200d-1f9bc","🧑🏾‍🦼":"1f9d1-1f3fe-200d-1f9bc","🧑🏽‍🦼":"1f9d1-1f3fd-200d-1f9bc","🧑🏼‍🦼":"1f9d1-1f3fc-200d-1f9bc","🧑🏻‍🦼":"1f9d1-1f3fb-200d-1f9bc","🦸🏼‍♀":"1f9b8-1f3fc-200d-2640-fe0f","🦸🏽‍♀":"1f9b8-1f3fd-200d-2640-fe0f","🦸🏾‍♀":"1f9b8-1f3fe-200d-2640-fe0f","🦸🏿‍♀":"1f9b8-1f3ff-200d-2640-fe0f","🦹‍♂️":"1f9b9-200d-2642-fe0f","🦹🏻‍♂":"1f9b9-1f3fb-200d-2642-fe0f","🦹🏼‍♂":"1f9b9-1f3fc-200d-2642-fe0f","🦹🏽‍♂":"1f9b9-1f3fd-200d-2642-fe0f","🦹🏾‍♂":"1f9b9-1f3fe-200d-2642-fe0f","🦹🏿‍♂":"1f9b9-1f3ff-200d-2642-fe0f","🚴🏿‍♀":"1f6b4-1f3ff-200d-2640-fe0f","🦹‍♀️":"1f9b9-200d-2640-fe0f","👩🏿‍🦯":"1f469-1f3ff-200d-1f9af","👩🏾‍🦯":"1f469-1f3fe-200d-1f9af","👩🏽‍🦯":"1f469-1f3fd-200d-1f9af","👩🏼‍🦯":"1f469-1f3fc-200d-1f9af","👩🏻‍🦯":"1f469-1f3fb-200d-1f9af","🦹🏻‍♀":"1f9b9-1f3fb-200d-2640-fe0f","🦹🏼‍♀":"1f9b9-1f3fc-200d-2640-fe0f","🦹🏽‍♀":"1f9b9-1f3fd-200d-2640-fe0f","🦹🏾‍♀":"1f9b9-1f3fe-200d-2640-fe0f","🦹🏿‍♀":"1f9b9-1f3ff-200d-2640-fe0f","🧙‍♂️":"1f9d9-200d-2642-fe0f","🧙🏻‍♂":"1f9d9-1f3fb-200d-2642-fe0f","🧙🏼‍♂":"1f9d9-1f3fc-200d-2642-fe0f","🧙🏽‍♂":"1f9d9-1f3fd-200d-2642-fe0f","🧙🏾‍♂":"1f9d9-1f3fe-200d-2642-fe0f","🚴🏾‍♀":"1f6b4-1f3fe-200d-2640-fe0f","🧙🏿‍♂":"1f9d9-1f3ff-200d-2642-fe0f","👨🏿‍🦯":"1f468-1f3ff-200d-1f9af","👨🏾‍🦯":"1f468-1f3fe-200d-1f9af","👨🏽‍🦯":"1f468-1f3fd-200d-1f9af","👨🏼‍🦯":"1f468-1f3fc-200d-1f9af","👨🏻‍🦯":"1f468-1f3fb-200d-1f9af","🧙‍♀️":"1f9d9-200d-2640-fe0f","🧙🏻‍♀":"1f9d9-1f3fb-200d-2640-fe0f","🧙🏼‍♀":"1f9d9-1f3fc-200d-2640-fe0f","🧙🏽‍♀":"1f9d9-1f3fd-200d-2640-fe0f","🧙🏾‍♀":"1f9d9-1f3fe-200d-2640-fe0f","🧙🏿‍♀":"1f9d9-1f3ff-200d-2640-fe0f","🧚‍♂️":"1f9da-200d-2642-fe0f","🧚🏻‍♂":"1f9da-1f3fb-200d-2642-fe0f","🧚🏼‍♂":"1f9da-1f3fc-200d-2642-fe0f","🧚🏽‍♂":"1f9da-1f3fd-200d-2642-fe0f","🚴🏽‍♀":"1f6b4-1f3fd-200d-2640-fe0f","🧚🏾‍♂":"1f9da-1f3fe-200d-2642-fe0f","🧑🏿‍🦯":"1f9d1-1f3ff-200d-1f9af","🧑🏾‍🦯":"1f9d1-1f3fe-200d-1f9af","🧑🏽‍🦯":"1f9d1-1f3fd-200d-1f9af","🧑🏼‍🦯":"1f9d1-1f3fc-200d-1f9af","🧑🏻‍🦯":"1f9d1-1f3fb-200d-1f9af","🧚🏿‍♂":"1f9da-1f3ff-200d-2642-fe0f","🧚‍♀️":"1f9da-200d-2640-fe0f","🧚🏻‍♀":"1f9da-1f3fb-200d-2640-fe0f","🧚🏼‍♀":"1f9da-1f3fc-200d-2640-fe0f","🧚🏽‍♀":"1f9da-1f3fd-200d-2640-fe0f","🧚🏾‍♀":"1f9da-1f3fe-200d-2640-fe0f","🧚🏿‍♀":"1f9da-1f3ff-200d-2640-fe0f","🧛‍♂️":"1f9db-200d-2642-fe0f","🧛🏻‍♂":"1f9db-1f3fb-200d-2642-fe0f","🧛🏼‍♂":"1f9db-1f3fc-200d-2642-fe0f","🧛🏽‍♂":"1f9db-1f3fd-200d-2642-fe0f","🧛🏾‍♂":"1f9db-1f3fe-200d-2642-fe0f","🧛🏿‍♂":"1f9db-1f3ff-200d-2642-fe0f","🧛‍♀️":"1f9db-200d-2640-fe0f","🧛🏻‍♀":"1f9db-1f3fb-200d-2640-fe0f","🧛🏼‍♀":"1f9db-1f3fc-200d-2640-fe0f","🧛🏽‍♀":"1f9db-1f3fd-200d-2640-fe0f","🧛🏾‍♀":"1f9db-1f3fe-200d-2640-fe0f","🧛🏿‍♀":"1f9db-1f3ff-200d-2640-fe0f","🧜‍♂️":"1f9dc-200d-2642-fe0f","🚴🏼‍♀":"1f6b4-1f3fc-200d-2640-fe0f","🧜🏻‍♂":"1f9dc-1f3fb-200d-2642-fe0f","🧜🏼‍♂":"1f9dc-1f3fc-200d-2642-fe0f","🧜🏽‍♂":"1f9dc-1f3fd-200d-2642-fe0f","🧜🏾‍♂":"1f9dc-1f3fe-200d-2642-fe0f","🧜🏿‍♂":"1f9dc-1f3ff-200d-2642-fe0f","🧜‍♀️":"1f9dc-200d-2640-fe0f","🧜🏻‍♀":"1f9dc-1f3fb-200d-2640-fe0f","🧜🏼‍♀":"1f9dc-1f3fc-200d-2640-fe0f","🧜🏽‍♀":"1f9dc-1f3fd-200d-2640-fe0f","🧜🏾‍♀":"1f9dc-1f3fe-200d-2640-fe0f","🧜🏿‍♀":"1f9dc-1f3ff-200d-2640-fe0f","🧝‍♂️":"1f9dd-200d-2642-fe0f","🧝🏻‍♂":"1f9dd-1f3fb-200d-2642-fe0f","🧝🏼‍♂":"1f9dd-1f3fc-200d-2642-fe0f","🧝🏽‍♂":"1f9dd-1f3fd-200d-2642-fe0f","🧝🏾‍♂":"1f9dd-1f3fe-200d-2642-fe0f","🧝🏿‍♂":"1f9dd-1f3ff-200d-2642-fe0f","🧝‍♀️":"1f9dd-200d-2640-fe0f","🧝🏻‍♀":"1f9dd-1f3fb-200d-2640-fe0f","🧝🏼‍♀":"1f9dd-1f3fc-200d-2640-fe0f","🧝🏽‍♀":"1f9dd-1f3fd-200d-2640-fe0f","🧝🏾‍♀":"1f9dd-1f3fe-200d-2640-fe0f","🧝🏿‍♀":"1f9dd-1f3ff-200d-2640-fe0f","🚴🏻‍♀":"1f6b4-1f3fb-200d-2640-fe0f","🧞‍♂️":"1f9de-200d-2642-fe0f","🧞‍♀️":"1f9de-200d-2640-fe0f","🧟‍♂️":"1f9df-200d-2642-fe0f","🧎🏿‍➡":"1f9ce-1f3ff-200d-27a1-fe0f","🧔‍♀️":"1f9d4-200d-2640-fe0f","🧎🏾‍➡":"1f9ce-1f3fe-200d-27a1-fe0f","🚴‍♀️":"1f6b4-200d-2640-fe0f","🧎🏽‍➡":"1f9ce-1f3fd-200d-27a1-fe0f","🚴🏿‍♂":"1f6b4-1f3ff-200d-2642-fe0f","🧎🏼‍➡":"1f9ce-1f3fc-200d-27a1-fe0f","🚴🏾‍♂":"1f6b4-1f3fe-200d-2642-fe0f","🧎🏻‍➡":"1f9ce-1f3fb-200d-27a1-fe0f","🚴🏽‍♂":"1f6b4-1f3fd-200d-2642-fe0f","🧎‍➡️":"1f9ce-200d-27a1-fe0f","🧎🏿‍♀":"1f9ce-1f3ff-200d-2640-fe0f","🚴🏼‍♂":"1f6b4-1f3fc-200d-2642-fe0f","🧎🏾‍♀":"1f9ce-1f3fe-200d-2640-fe0f","🚴🏻‍♂":"1f6b4-1f3fb-200d-2642-fe0f","🧎🏽‍♀":"1f9ce-1f3fd-200d-2640-fe0f","🚴‍♂️":"1f6b4-200d-2642-fe0f","🧎🏼‍♀":"1f9ce-1f3fc-200d-2640-fe0f","🏋🏼‍♂":"1f3cb-1f3fc-200d-2642-fe0f","🧎🏻‍♀":"1f9ce-1f3fb-200d-2640-fe0f","🏋🏽‍♂":"1f3cb-1f3fd-200d-2642-fe0f","🧎‍♀️":"1f9ce-200d-2640-fe0f","🧎🏿‍♂":"1f9ce-1f3ff-200d-2642-fe0f","🏋🏾‍♂":"1f3cb-1f3fe-200d-2642-fe0f","🧎🏾‍♂":"1f9ce-1f3fe-200d-2642-fe0f","🏋🏿‍♀":"1f3cb-1f3ff-200d-2640-fe0f","🧎🏽‍♂":"1f9ce-1f3fd-200d-2642-fe0f","🏋🏾‍♀":"1f3cb-1f3fe-200d-2640-fe0f","🧎🏼‍♂":"1f9ce-1f3fc-200d-2642-fe0f","🏋🏽‍♀":"1f3cb-1f3fd-200d-2640-fe0f","🧎🏻‍♂":"1f9ce-1f3fb-200d-2642-fe0f","🏋🏼‍♀":"1f3cb-1f3fc-200d-2640-fe0f","🧎‍♂️":"1f9ce-200d-2642-fe0f","🧍🏿‍♀":"1f9cd-1f3ff-200d-2640-fe0f","🏋🏻‍♀":"1f3cb-1f3fb-200d-2640-fe0f","🧍🏾‍♀":"1f9cd-1f3fe-200d-2640-fe0f","🧔🏻‍♀":"1f9d4-1f3fb-200d-2640-fe0f","🧍🏽‍♀":"1f9cd-1f3fd-200d-2640-fe0f","🏋️‍♀":"1f3cb-fe0f-200d-2640-fe0f","🧍🏼‍♀":"1f9cd-1f3fc-200d-2640-fe0f","🏋‍♀️":"1f3cb-fe0f-200d-2640-fe0f","🧍🏻‍♀":"1f9cd-1f3fb-200d-2640-fe0f","🏋🏿‍♂":"1f3cb-1f3ff-200d-2642-fe0f","🧍‍♀️":"1f9cd-200d-2640-fe0f","🧍🏿‍♂":"1f9cd-1f3ff-200d-2642-fe0f","🏊🏼‍♂":"1f3ca-1f3fc-200d-2642-fe0f","🧍🏾‍♂":"1f9cd-1f3fe-200d-2642-fe0f","🤽🏾‍♀":"1f93d-1f3fe-200d-2640-fe0f","🧍🏽‍♂":"1f9cd-1f3fd-200d-2642-fe0f","🧔🏼‍♀":"1f9d4-1f3fc-200d-2640-fe0f","🧍🏼‍♂":"1f9cd-1f3fc-200d-2642-fe0f","🧔🏽‍♀":"1f9d4-1f3fd-200d-2640-fe0f","🧍🏻‍♂":"1f9cd-1f3fb-200d-2642-fe0f","🧔🏾‍♀":"1f9d4-1f3fe-200d-2640-fe0f","🧍‍♂️":"1f9cd-200d-2642-fe0f","🧟‍♀️":"1f9df-200d-2640-fe0f","💆‍♂️":"1f486-200d-2642-fe0f","💆🏻‍♂":"1f486-1f3fb-200d-2642-fe0f","💆🏼‍♂":"1f486-1f3fc-200d-2642-fe0f","💆🏽‍♂":"1f486-1f3fd-200d-2642-fe0f","💆🏾‍♂":"1f486-1f3fe-200d-2642-fe0f","💆🏿‍♂":"1f486-1f3ff-200d-2642-fe0f","💆‍♀️":"1f486-200d-2640-fe0f","💆🏻‍♀":"1f486-1f3fb-200d-2640-fe0f","💆🏼‍♀":"1f486-1f3fc-200d-2640-fe0f","💆🏽‍♀":"1f486-1f3fd-200d-2640-fe0f","💆🏾‍♀":"1f486-1f3fe-200d-2640-fe0f","💆🏿‍♀":"1f486-1f3ff-200d-2640-fe0f","💇‍♂️":"1f487-200d-2642-fe0f","💇🏻‍♂":"1f487-1f3fb-200d-2642-fe0f","💇🏼‍♂":"1f487-1f3fc-200d-2642-fe0f","💇🏽‍♂":"1f487-1f3fd-200d-2642-fe0f","💇🏾‍♂":"1f487-1f3fe-200d-2642-fe0f","💇🏿‍♂":"1f487-1f3ff-200d-2642-fe0f","💇‍♀️":"1f487-200d-2640-fe0f","🧔🏿‍♀":"1f9d4-1f3ff-200d-2640-fe0f","💇🏻‍♀":"1f487-1f3fb-200d-2640-fe0f","💇🏼‍♀":"1f487-1f3fc-200d-2640-fe0f","💇🏽‍♀":"1f487-1f3fd-200d-2640-fe0f","💇🏾‍♀":"1f487-1f3fe-200d-2640-fe0f","💇🏿‍♀":"1f487-1f3ff-200d-2640-fe0f","🚶‍♂️":"1f6b6-200d-2642-fe0f","🚶🏻‍♂":"1f6b6-1f3fb-200d-2642-fe0f","🚶🏼‍♂":"1f6b6-1f3fc-200d-2642-fe0f","🚶🏽‍♂":"1f6b6-1f3fd-200d-2642-fe0f","🚶🏾‍♂":"1f6b6-1f3fe-200d-2642-fe0f","🚶🏿‍♂":"1f6b6-1f3ff-200d-2642-fe0f","🚶‍♀️":"1f6b6-200d-2640-fe0f","🚶🏻‍♀":"1f6b6-1f3fb-200d-2640-fe0f","🚶🏼‍♀":"1f6b6-1f3fc-200d-2640-fe0f","🚶🏽‍♀":"1f6b6-1f3fd-200d-2640-fe0f","🚶🏾‍♀":"1f6b6-1f3fe-200d-2640-fe0f","🚶🏿‍♀":"1f6b6-1f3ff-200d-2640-fe0f","🚶‍➡️":"1f6b6-200d-27a1-fe0f","🚶🏻‍➡":"1f6b6-1f3fb-200d-27a1-fe0f","🚶🏼‍➡":"1f6b6-1f3fc-200d-27a1-fe0f","🚶🏽‍➡":"1f6b6-1f3fd-200d-27a1-fe0f","🚶🏾‍➡":"1f6b6-1f3fe-200d-27a1-fe0f","🚶🏿‍➡":"1f6b6-1f3ff-200d-27a1-fe0f","👨🏻‍🦰":"1f468-1f3fb-200d-1f9b0","🏊🏻‍♂":"1f3ca-1f3fb-200d-2642-fe0f","👨🏼‍🦰":"1f468-1f3fc-200d-1f9b0","👨🏽‍🦰":"1f468-1f3fd-200d-1f9b0","👨🏾‍🦰":"1f468-1f3fe-200d-1f9b0","👨🏿‍🦰":"1f468-1f3ff-200d-1f9b0","👨🏻‍🦱":"1f468-1f3fb-200d-1f9b1","🏋️‍♂":"1f3cb-fe0f-200d-2642-fe0f","👨🏼‍🦱":"1f468-1f3fc-200d-1f9b1","👨🏽‍🦱":"1f468-1f3fd-200d-1f9b1","👨🏾‍🦱":"1f468-1f3fe-200d-1f9b1","👨🏿‍🦱":"1f468-1f3ff-200d-1f9b1","👨🏻‍🦳":"1f468-1f3fb-200d-1f9b3","🏊‍♂️":"1f3ca-200d-2642-fe0f","👨🏼‍🦳":"1f468-1f3fc-200d-1f9b3","👨🏽‍🦳":"1f468-1f3fd-200d-1f9b3","👨🏾‍🦳":"1f468-1f3fe-200d-1f9b3","👨🏿‍🦳":"1f468-1f3ff-200d-1f9b3","👨🏻‍🦲":"1f468-1f3fb-200d-1f9b2","🚣🏿‍♀":"1f6a3-1f3ff-200d-2640-fe0f","🤽🏿‍♀":"1f93d-1f3ff-200d-2640-fe0f","🚣🏾‍♀":"1f6a3-1f3fe-200d-2640-fe0f","👨🏼‍🦲":"1f468-1f3fc-200d-1f9b2","👨🏽‍🦲":"1f468-1f3fd-200d-1f9b2","👨🏾‍🦲":"1f468-1f3fe-200d-1f9b2","👨🏿‍🦲":"1f468-1f3ff-200d-1f9b2","👩🏻‍🦰":"1f469-1f3fb-200d-1f9b0","⛹🏿‍♂":"26f9-1f3ff-200d-2642-fe0f","👩🏼‍🦰":"1f469-1f3fc-200d-1f9b0","👩🏽‍🦰":"1f469-1f3fd-200d-1f9b0","👩🏾‍🦰":"1f469-1f3fe-200d-1f9b0","👩🏿‍🦰":"1f469-1f3ff-200d-1f9b0","🧑🏻‍🦰":"1f9d1-1f3fb-200d-1f9b0","🚣🏽‍♀":"1f6a3-1f3fd-200d-2640-fe0f","🧑🏼‍🦰":"1f9d1-1f3fc-200d-1f9b0","🧑🏽‍🦰":"1f9d1-1f3fd-200d-1f9b0","🧑🏾‍🦰":"1f9d1-1f3fe-200d-1f9b0","🧑🏿‍🦰":"1f9d1-1f3ff-200d-1f9b0","👩🏻‍🦱":"1f469-1f3fb-200d-1f9b1","🤸🏽‍♂":"1f938-1f3fd-200d-2642-fe0f","👩🏼‍🦱":"1f469-1f3fc-200d-1f9b1","👩🏽‍🦱":"1f469-1f3fd-200d-1f9b1","👩🏾‍🦱":"1f469-1f3fe-200d-1f9b1","👩🏿‍🦱":"1f469-1f3ff-200d-1f9b1","🧑🏻‍🦱":"1f9d1-1f3fb-200d-1f9b1","🚣🏼‍♀":"1f6a3-1f3fc-200d-2640-fe0f","⛹🏾‍♂":"26f9-1f3fe-200d-2642-fe0f","🚣🏻‍♀":"1f6a3-1f3fb-200d-2640-fe0f","🧑🏼‍🦱":"1f9d1-1f3fc-200d-1f9b1","🧑🏽‍🦱":"1f9d1-1f3fd-200d-1f9b1","🧑🏾‍🦱":"1f9d1-1f3fe-200d-1f9b1","🧑🏿‍🦱":"1f9d1-1f3ff-200d-1f9b1","👩🏻‍🦳":"1f469-1f3fb-200d-1f9b3","🚵🏻‍♂":"1f6b5-1f3fb-200d-2642-fe0f","👩🏼‍🦳":"1f469-1f3fc-200d-1f9b3","👩🏽‍🦳":"1f469-1f3fd-200d-1f9b3","👩🏾‍🦳":"1f469-1f3fe-200d-1f9b3","👩🏿‍🦳":"1f469-1f3ff-200d-1f9b3","🧑🏻‍🦳":"1f9d1-1f3fb-200d-1f9b3","🚣‍♀️":"1f6a3-200d-2640-fe0f","🧑🏼‍🦳":"1f9d1-1f3fc-200d-1f9b3","🧑🏽‍🦳":"1f9d1-1f3fd-200d-1f9b3","🧑🏾‍🦳":"1f9d1-1f3fe-200d-1f9b3","🧑🏿‍🦳":"1f9d1-1f3ff-200d-1f9b3","👩🏻‍🦲":"1f469-1f3fb-200d-1f9b2","🚣🏿‍♂":"1f6a3-1f3ff-200d-2642-fe0f","👩🏼‍🦲":"1f469-1f3fc-200d-1f9b2","👩🏽‍🦲":"1f469-1f3fd-200d-1f9b2","👩🏾‍🦲":"1f469-1f3fe-200d-1f9b2","👩🏿‍🦲":"1f469-1f3ff-200d-1f9b2","🧑🏻‍🦲":"1f9d1-1f3fb-200d-1f9b2","⛹🏽‍♂":"26f9-1f3fd-200d-2642-fe0f","🚣🏾‍♂":"1f6a3-1f3fe-200d-2642-fe0f","🤸🏾‍♂":"1f938-1f3fe-200d-2642-fe0f","🚣🏽‍♂":"1f6a3-1f3fd-200d-2642-fe0f","🤾‍♂️":"1f93e-200d-2642-fe0f","🚣🏼‍♂":"1f6a3-1f3fc-200d-2642-fe0f","🧔🏻‍♂":"1f9d4-1f3fb-200d-2642-fe0f","🧑🏼‍🦲":"1f9d1-1f3fc-200d-1f9b2","🧑🏽‍🦲":"1f9d1-1f3fd-200d-1f9b2","🧑🏾‍🦲":"1f9d1-1f3fe-200d-1f9b2","🧑🏿‍🦲":"1f9d1-1f3ff-200d-1f9b2","👱‍♀️":"1f471-200d-2640-fe0f","👱🏻‍♀":"1f471-1f3fb-200d-2640-fe0f","👱🏼‍♀":"1f471-1f3fc-200d-2640-fe0f","👱🏽‍♀":"1f471-1f3fd-200d-2640-fe0f","👱🏾‍♀":"1f471-1f3fe-200d-2640-fe0f","👱🏿‍♀":"1f471-1f3ff-200d-2640-fe0f","👱‍♂️":"1f471-200d-2642-fe0f","👱🏻‍♂":"1f471-1f3fb-200d-2642-fe0f","👱🏼‍♂":"1f471-1f3fc-200d-2642-fe0f","👱🏽‍♂":"1f471-1f3fd-200d-2642-fe0f","👱🏾‍♂":"1f471-1f3fe-200d-2642-fe0f","👱🏿‍♂":"1f471-1f3ff-200d-2642-fe0f","🙍‍♂️":"1f64d-200d-2642-fe0f","🙍🏻‍♂":"1f64d-1f3fb-200d-2642-fe0f","🙍🏼‍♂":"1f64d-1f3fc-200d-2642-fe0f","🙍🏽‍♂":"1f64d-1f3fd-200d-2642-fe0f","🙍🏾‍♂":"1f64d-1f3fe-200d-2642-fe0f","🙍🏿‍♂":"1f64d-1f3ff-200d-2642-fe0f","🙍‍♀️":"1f64d-200d-2640-fe0f","🙍🏻‍♀":"1f64d-1f3fb-200d-2640-fe0f","🙍🏼‍♀":"1f64d-1f3fc-200d-2640-fe0f","🙍🏽‍♀":"1f64d-1f3fd-200d-2640-fe0f","🙍🏾‍♀":"1f64d-1f3fe-200d-2640-fe0f","🙍🏿‍♀":"1f64d-1f3ff-200d-2640-fe0f","🙎‍♂️":"1f64e-200d-2642-fe0f","🙎🏻‍♂":"1f64e-1f3fb-200d-2642-fe0f","🙎🏼‍♂":"1f64e-1f3fc-200d-2642-fe0f","🙎🏽‍♂":"1f64e-1f3fd-200d-2642-fe0f","🙎🏾‍♂":"1f64e-1f3fe-200d-2642-fe0f","🙎🏿‍♂":"1f64e-1f3ff-200d-2642-fe0f","🙎‍♀️":"1f64e-200d-2640-fe0f","🙎🏻‍♀":"1f64e-1f3fb-200d-2640-fe0f","🙎🏼‍♀":"1f64e-1f3fc-200d-2640-fe0f","🙎🏽‍♀":"1f64e-1f3fd-200d-2640-fe0f","🙎🏾‍♀":"1f64e-1f3fe-200d-2640-fe0f","🙎🏿‍♀":"1f64e-1f3ff-200d-2640-fe0f","🙅‍♂️":"1f645-200d-2642-fe0f","🙅🏻‍♂":"1f645-1f3fb-200d-2642-fe0f","🙅🏼‍♂":"1f645-1f3fc-200d-2642-fe0f","🙅🏽‍♂":"1f645-1f3fd-200d-2642-fe0f","🙅🏾‍♂":"1f645-1f3fe-200d-2642-fe0f","🙅🏿‍♂":"1f645-1f3ff-200d-2642-fe0f","🙅‍♀️":"1f645-200d-2640-fe0f","🙅🏻‍♀":"1f645-1f3fb-200d-2640-fe0f","🙅🏼‍♀":"1f645-1f3fc-200d-2640-fe0f","🙅🏽‍♀":"1f645-1f3fd-200d-2640-fe0f","🙅🏾‍♀":"1f645-1f3fe-200d-2640-fe0f","🙅🏿‍♀":"1f645-1f3ff-200d-2640-fe0f","🙆‍♂️":"1f646-200d-2642-fe0f","🙆🏻‍♂":"1f646-1f3fb-200d-2642-fe0f","🙆🏼‍♂":"1f646-1f3fc-200d-2642-fe0f","🙆🏽‍♂":"1f646-1f3fd-200d-2642-fe0f","🙆🏾‍♂":"1f646-1f3fe-200d-2642-fe0f","🙆🏿‍♂":"1f646-1f3ff-200d-2642-fe0f","🙆‍♀️":"1f646-200d-2640-fe0f","🙆🏻‍♀":"1f646-1f3fb-200d-2640-fe0f","🙆🏼‍♀":"1f646-1f3fc-200d-2640-fe0f","🙆🏽‍♀":"1f646-1f3fd-200d-2640-fe0f","🙆🏾‍♀":"1f646-1f3fe-200d-2640-fe0f","🙆🏿‍♀":"1f646-1f3ff-200d-2640-fe0f","💁‍♂️":"1f481-200d-2642-fe0f","💁🏻‍♂":"1f481-1f3fb-200d-2642-fe0f","💁🏼‍♂":"1f481-1f3fc-200d-2642-fe0f","💁🏽‍♂":"1f481-1f3fd-200d-2642-fe0f","💁🏾‍♂":"1f481-1f3fe-200d-2642-fe0f","💁🏿‍♂":"1f481-1f3ff-200d-2642-fe0f","💁‍♀️":"1f481-200d-2640-fe0f","💁🏻‍♀":"1f481-1f3fb-200d-2640-fe0f","💁🏼‍♀":"1f481-1f3fc-200d-2640-fe0f","💁🏽‍♀":"1f481-1f3fd-200d-2640-fe0f","💁🏾‍♀":"1f481-1f3fe-200d-2640-fe0f","💁🏿‍♀":"1f481-1f3ff-200d-2640-fe0f","🙋‍♂️":"1f64b-200d-2642-fe0f","🙋🏻‍♂":"1f64b-1f3fb-200d-2642-fe0f","🙋🏼‍♂":"1f64b-1f3fc-200d-2642-fe0f","🙋🏽‍♂":"1f64b-1f3fd-200d-2642-fe0f","🙋🏾‍♂":"1f64b-1f3fe-200d-2642-fe0f","🙋🏿‍♂":"1f64b-1f3ff-200d-2642-fe0f","🙋‍♀️":"1f64b-200d-2640-fe0f","🙋🏻‍♀":"1f64b-1f3fb-200d-2640-fe0f","🙋🏼‍♀":"1f64b-1f3fc-200d-2640-fe0f","🏴‍☠️":"1f3f4-200d-2620-fe0f","🏳️‍⚧":"1f3f3-fe0f-200d-26a7-fe0f","🏳‍⚧️":"1f3f3-fe0f-200d-26a7-fe0f","🏳️‍🌈":"1f3f3-fe0f-200d-1f308","🙋🏽‍♀":"1f64b-1f3fd-200d-2640-fe0f","🙋🏾‍♀":"1f64b-1f3fe-200d-2640-fe0f","🙋🏿‍♀":"1f64b-1f3ff-200d-2640-fe0f","🧏‍♂️":"1f9cf-200d-2642-fe0f","🧏🏻‍♂":"1f9cf-1f3fb-200d-2642-fe0f","🧏🏼‍♂":"1f9cf-1f3fc-200d-2642-fe0f","🧏🏽‍♂":"1f9cf-1f3fd-200d-2642-fe0f","🧏🏾‍♂":"1f9cf-1f3fe-200d-2642-fe0f","🧏🏿‍♂":"1f9cf-1f3ff-200d-2642-fe0f","🧏‍♀️":"1f9cf-200d-2640-fe0f","🧏🏻‍♀":"1f9cf-1f3fb-200d-2640-fe0f","🧏🏼‍♀":"1f9cf-1f3fc-200d-2640-fe0f","🧏🏽‍♀":"1f9cf-1f3fd-200d-2640-fe0f","🧏🏾‍♀":"1f9cf-1f3fe-200d-2640-fe0f","🧏🏿‍♀":"1f9cf-1f3ff-200d-2640-fe0f","🙇‍♂️":"1f647-200d-2642-fe0f","🙇🏻‍♂":"1f647-1f3fb-200d-2642-fe0f","🙇🏼‍♂":"1f647-1f3fc-200d-2642-fe0f","🙇🏽‍♂":"1f647-1f3fd-200d-2642-fe0f","🙇🏾‍♂":"1f647-1f3fe-200d-2642-fe0f","🙇🏿‍♂":"1f647-1f3ff-200d-2642-fe0f","🙇‍♀️":"1f647-200d-2640-fe0f","🙇🏻‍♀":"1f647-1f3fb-200d-2640-fe0f","🙇🏼‍♀":"1f647-1f3fc-200d-2640-fe0f","🙇🏽‍♀":"1f647-1f3fd-200d-2640-fe0f","🙇🏾‍♀":"1f647-1f3fe-200d-2640-fe0f","🙇🏿‍♀":"1f647-1f3ff-200d-2640-fe0f","🤦‍♂️":"1f926-200d-2642-fe0f","🤦🏻‍♂":"1f926-1f3fb-200d-2642-fe0f","🤦🏼‍♂":"1f926-1f3fc-200d-2642-fe0f","🤦🏽‍♂":"1f926-1f3fd-200d-2642-fe0f","🤦🏾‍♂":"1f926-1f3fe-200d-2642-fe0f","🤦🏿‍♂":"1f926-1f3ff-200d-2642-fe0f","🤦‍♀️":"1f926-200d-2640-fe0f","🤦🏻‍♀":"1f926-1f3fb-200d-2640-fe0f","🤦🏼‍♀":"1f926-1f3fc-200d-2640-fe0f","🤦🏽‍♀":"1f926-1f3fd-200d-2640-fe0f","🤦🏾‍♀":"1f926-1f3fe-200d-2640-fe0f","🤦🏿‍♀":"1f926-1f3ff-200d-2640-fe0f","🤷‍♂️":"1f937-200d-2642-fe0f","🤷🏻‍♂":"1f937-1f3fb-200d-2642-fe0f","🤷🏼‍♂":"1f937-1f3fc-200d-2642-fe0f","🤷🏽‍♂":"1f937-1f3fd-200d-2642-fe0f","🤷🏾‍♂":"1f937-1f3fe-200d-2642-fe0f","🤷🏿‍♂":"1f937-1f3ff-200d-2642-fe0f","🤷‍♀️":"1f937-200d-2640-fe0f","🤷🏻‍♀":"1f937-1f3fb-200d-2640-fe0f","🤷🏼‍♀":"1f937-1f3fc-200d-2640-fe0f","🤷🏽‍♀":"1f937-1f3fd-200d-2640-fe0f","🤷🏾‍♀":"1f937-1f3fe-200d-2640-fe0f","🤷🏿‍♀":"1f937-1f3ff-200d-2640-fe0f","🧑‍⚕️":"1f9d1-200d-2695-fe0f","😶‍🌫️":"1f636-200d-1f32b-fe0f","🙂‍↔️":"1f642-200d-2194-fe0f","🧑🏻‍⚕":"1f9d1-1f3fb-200d-2695-fe0f","🙂‍↕️":"1f642-200d-2195-fe0f","❤️‍🔥":"2764-fe0f-200d-1f525","🧑🏼‍⚕":"1f9d1-1f3fc-200d-2695-fe0f","❤️‍🩹":"2764-fe0f-200d-1fa79","🧑🏽‍⚕":"1f9d1-1f3fd-200d-2695-fe0f","👁‍🗨️":"1f441-200d-1f5e8","👁️‍🗨":"1f441-200d-1f5e8","🧑🏾‍⚕":"1f9d1-1f3fe-200d-2695-fe0f","🧑🏿‍⚕":"1f9d1-1f3ff-200d-2695-fe0f","👨‍⚕️":"1f468-200d-2695-fe0f","👨🏻‍⚕":"1f468-1f3fb-200d-2695-fe0f","👨🏼‍⚕":"1f468-1f3fc-200d-2695-fe0f","👨🏽‍⚕":"1f468-1f3fd-200d-2695-fe0f","👨🏾‍⚕":"1f468-1f3fe-200d-2695-fe0f","👨🏿‍⚕":"1f468-1f3ff-200d-2695-fe0f","👩‍⚕️":"1f469-200d-2695-fe0f","👩🏻‍⚕":"1f469-1f3fb-200d-2695-fe0f","👩🏼‍⚕":"1f469-1f3fc-200d-2695-fe0f","👩🏽‍⚕":"1f469-1f3fd-200d-2695-fe0f","👩🏾‍⚕":"1f469-1f3fe-200d-2695-fe0f","👩🏿‍⚕":"1f469-1f3ff-200d-2695-fe0f","🧑🏻‍🎓":"1f9d1-1f3fb-200d-1f393","🧑🏼‍🎓":"1f9d1-1f3fc-200d-1f393","🧑🏽‍🎓":"1f9d1-1f3fd-200d-1f393","🧑🏾‍🎓":"1f9d1-1f3fe-200d-1f393","🧑🏿‍🎓":"1f9d1-1f3ff-200d-1f393","👨🏻‍🎓":"1f468-1f3fb-200d-1f393","👨🏼‍🎓":"1f468-1f3fc-200d-1f393","👨🏽‍🎓":"1f468-1f3fd-200d-1f393","👨🏾‍🎓":"1f468-1f3fe-200d-1f393","👨🏿‍🎓":"1f468-1f3ff-200d-1f393","👩🏻‍🎓":"1f469-1f3fb-200d-1f393","👩🏼‍🎓":"1f469-1f3fc-200d-1f393","👩🏽‍🎓":"1f469-1f3fd-200d-1f393","👩🏾‍🎓":"1f469-1f3fe-200d-1f393","👩🏿‍🎓":"1f469-1f3ff-200d-1f393","🧑🏻‍🏫":"1f9d1-1f3fb-200d-1f3eb","🧑🏼‍🏫":"1f9d1-1f3fc-200d-1f3eb","🧑🏽‍🏫":"1f9d1-1f3fd-200d-1f3eb","🧑🏾‍🏫":"1f9d1-1f3fe-200d-1f3eb","🧑🏿‍🏫":"1f9d1-1f3ff-200d-1f3eb","👨🏻‍🏫":"1f468-1f3fb-200d-1f3eb","👨🏼‍🏫":"1f468-1f3fc-200d-1f3eb","👨🏽‍🏫":"1f468-1f3fd-200d-1f3eb","👨🏾‍🏫":"1f468-1f3fe-200d-1f3eb","👨🏿‍🏫":"1f468-1f3ff-200d-1f3eb","👩🏻‍🏫":"1f469-1f3fb-200d-1f3eb","👩🏼‍🏫":"1f469-1f3fc-200d-1f3eb","👩🏽‍🏫":"1f469-1f3fd-200d-1f3eb","👩🏾‍🏫":"1f469-1f3fe-200d-1f3eb","👩🏿‍🏫":"1f469-1f3ff-200d-1f3eb","🧑‍⚖️":"1f9d1-200d-2696-fe0f","🚣🏻‍♂":"1f6a3-1f3fb-200d-2642-fe0f","🚵🏻‍♀":"1f6b5-1f3fb-200d-2640-fe0f","🚣‍♂️":"1f6a3-200d-2642-fe0f","🏄🏿‍♀":"1f3c4-1f3ff-200d-2640-fe0f","🤾🏻‍♂":"1f93e-1f3fb-200d-2642-fe0f","🏄🏾‍♀":"1f3c4-1f3fe-200d-2640-fe0f","🧑🏻‍⚖":"1f9d1-1f3fb-200d-2696-fe0f","🧑🏼‍⚖":"1f9d1-1f3fc-200d-2696-fe0f","🧑🏽‍⚖":"1f9d1-1f3fd-200d-2696-fe0f","🧑🏾‍⚖":"1f9d1-1f3fe-200d-2696-fe0f","🧑🏿‍⚖":"1f9d1-1f3ff-200d-2696-fe0f","👨‍⚖️":"1f468-200d-2696-fe0f","👨🏻‍⚖":"1f468-1f3fb-200d-2696-fe0f","👨🏼‍⚖":"1f468-1f3fc-200d-2696-fe0f","👨🏽‍⚖":"1f468-1f3fd-200d-2696-fe0f","👨🏾‍⚖":"1f468-1f3fe-200d-2696-fe0f","👨🏿‍⚖":"1f468-1f3ff-200d-2696-fe0f","👩‍⚖️":"1f469-200d-2696-fe0f","👩🏻‍⚖":"1f469-1f3fb-200d-2696-fe0f","👩🏼‍⚖":"1f469-1f3fc-200d-2696-fe0f","👩🏽‍⚖":"1f469-1f3fd-200d-2696-fe0f","👩🏾‍⚖":"1f469-1f3fe-200d-2696-fe0f","👩🏿‍⚖":"1f469-1f3ff-200d-2696-fe0f","🧑🏻‍🌾":"1f9d1-1f3fb-200d-1f33e","🧑🏼‍🌾":"1f9d1-1f3fc-200d-1f33e","🧑🏽‍🌾":"1f9d1-1f3fd-200d-1f33e","🧑🏾‍🌾":"1f9d1-1f3fe-200d-1f33e","🧑🏿‍🌾":"1f9d1-1f3ff-200d-1f33e","👨🏻‍🌾":"1f468-1f3fb-200d-1f33e","👨🏼‍🌾":"1f468-1f3fc-200d-1f33e","👨🏽‍🌾":"1f468-1f3fd-200d-1f33e","👨🏾‍🌾":"1f468-1f3fe-200d-1f33e","👨🏿‍🌾":"1f468-1f3ff-200d-1f33e","👩🏻‍🌾":"1f469-1f3fb-200d-1f33e","👩🏼‍🌾":"1f469-1f3fc-200d-1f33e","👩🏽‍🌾":"1f469-1f3fd-200d-1f33e","👩🏾‍🌾":"1f469-1f3fe-200d-1f33e","👩🏿‍🌾":"1f469-1f3ff-200d-1f33e","🧑🏻‍🍳":"1f9d1-1f3fb-200d-1f373","🧑🏼‍🍳":"1f9d1-1f3fc-200d-1f373","🧑🏽‍🍳":"1f9d1-1f3fd-200d-1f373","🧑🏾‍🍳":"1f9d1-1f3fe-200d-1f373","🧑🏿‍🍳":"1f9d1-1f3ff-200d-1f373","👨🏻‍🍳":"1f468-1f3fb-200d-1f373","👨🏼‍🍳":"1f468-1f3fc-200d-1f373","👨🏽‍🍳":"1f468-1f3fd-200d-1f373","👨🏾‍🍳":"1f468-1f3fe-200d-1f373","🤸🏿‍♂":"1f938-1f3ff-200d-2642-fe0f","👨🏿‍🍳":"1f468-1f3ff-200d-1f373","👩🏻‍🍳":"1f469-1f3fb-200d-1f373","👩🏼‍🍳":"1f469-1f3fc-200d-1f373","👩🏽‍🍳":"1f469-1f3fd-200d-1f373","👩🏾‍🍳":"1f469-1f3fe-200d-1f373","👩🏿‍🍳":"1f469-1f3ff-200d-1f373","🧑🏻‍🔧":"1f9d1-1f3fb-200d-1f527","🧑🏼‍🔧":"1f9d1-1f3fc-200d-1f527","🧑🏽‍🔧":"1f9d1-1f3fd-200d-1f527","🧑🏾‍🔧":"1f9d1-1f3fe-200d-1f527","🧑🏿‍🔧":"1f9d1-1f3ff-200d-1f527","👨🏻‍🔧":"1f468-1f3fb-200d-1f527","👨🏼‍🔧":"1f468-1f3fc-200d-1f527","👨🏽‍🔧":"1f468-1f3fd-200d-1f527","👨🏾‍🔧":"1f468-1f3fe-200d-1f527","👨🏿‍🔧":"1f468-1f3ff-200d-1f527","👩🏻‍🔧":"1f469-1f3fb-200d-1f527","👩🏼‍🔧":"1f469-1f3fc-200d-1f527","👩🏽‍🔧":"1f469-1f3fd-200d-1f527","👩🏾‍🔧":"1f469-1f3fe-200d-1f527","👩🏿‍🔧":"1f469-1f3ff-200d-1f527","🧑🏻‍🏭":"1f9d1-1f3fb-200d-1f3ed","🧑🏼‍🏭":"1f9d1-1f3fc-200d-1f3ed","🧑🏽‍🏭":"1f9d1-1f3fd-200d-1f3ed","🧑🏾‍🏭":"1f9d1-1f3fe-200d-1f3ed","🧑🏿‍🏭":"1f9d1-1f3ff-200d-1f3ed","👨🏻‍🏭":"1f468-1f3fb-200d-1f3ed","👨🏼‍🏭":"1f468-1f3fc-200d-1f3ed","👨🏽‍🏭":"1f468-1f3fd-200d-1f3ed","👨🏾‍🏭":"1f468-1f3fe-200d-1f3ed","👨🏿‍🏭":"1f468-1f3ff-200d-1f3ed","👩🏻‍🏭":"1f469-1f3fb-200d-1f3ed","👩🏼‍🏭":"1f469-1f3fc-200d-1f3ed","👩🏽‍🏭":"1f469-1f3fd-200d-1f3ed","👩🏾‍🏭":"1f469-1f3fe-200d-1f3ed","👩🏿‍🏭":"1f469-1f3ff-200d-1f3ed","🧑🏻‍💼":"1f9d1-1f3fb-200d-1f4bc","🧑🏼‍💼":"1f9d1-1f3fc-200d-1f4bc","🧑🏽‍💼":"1f9d1-1f3fd-200d-1f4bc","🧑🏾‍💼":"1f9d1-1f3fe-200d-1f4bc","🧑🏿‍💼":"1f9d1-1f3ff-200d-1f4bc","👨🏻‍💼":"1f468-1f3fb-200d-1f4bc","👨🏼‍💼":"1f468-1f3fc-200d-1f4bc","👨🏽‍💼":"1f468-1f3fd-200d-1f4bc","👨🏾‍💼":"1f468-1f3fe-200d-1f4bc","👨🏿‍💼":"1f468-1f3ff-200d-1f4bc","👩🏻‍💼":"1f469-1f3fb-200d-1f4bc","👩🏼‍💼":"1f469-1f3fc-200d-1f4bc","👩🏽‍💼":"1f469-1f3fd-200d-1f4bc","👩🏾‍💼":"1f469-1f3fe-200d-1f4bc","👩🏿‍💼":"1f469-1f3ff-200d-1f4bc","🤾🏼‍♂":"1f93e-1f3fc-200d-2642-fe0f","🧑🏻‍🔬":"1f9d1-1f3fb-200d-1f52c","🧑🏼‍🔬":"1f9d1-1f3fc-200d-1f52c","🧑🏽‍🔬":"1f9d1-1f3fd-200d-1f52c","🧑🏾‍🔬":"1f9d1-1f3fe-200d-1f52c","🧑🏿‍🔬":"1f9d1-1f3ff-200d-1f52c","👨🏻‍🔬":"1f468-1f3fb-200d-1f52c","👨🏼‍🔬":"1f468-1f3fc-200d-1f52c","👨🏽‍🔬":"1f468-1f3fd-200d-1f52c","👨🏾‍🔬":"1f468-1f3fe-200d-1f52c","👨🏿‍🔬":"1f468-1f3ff-200d-1f52c","👩🏻‍🔬":"1f469-1f3fb-200d-1f52c","👩🏼‍🔬":"1f469-1f3fc-200d-1f52c","👩🏽‍🔬":"1f469-1f3fd-200d-1f52c","👩🏾‍🔬":"1f469-1f3fe-200d-1f52c","👩🏿‍🔬":"1f469-1f3ff-200d-1f52c","🧑🏻‍💻":"1f9d1-1f3fb-200d-1f4bb","🧑🏼‍💻":"1f9d1-1f3fc-200d-1f4bb","🧑🏽‍💻":"1f9d1-1f3fd-200d-1f4bb","🧑🏾‍💻":"1f9d1-1f3fe-200d-1f4bb","🧑🏿‍💻":"1f9d1-1f3ff-200d-1f4bb","👨🏻‍💻":"1f468-1f3fb-200d-1f4bb","👨🏼‍💻":"1f468-1f3fc-200d-1f4bb","👨🏽‍💻":"1f468-1f3fd-200d-1f4bb","👨🏾‍💻":"1f468-1f3fe-200d-1f4bb","👨🏿‍💻":"1f468-1f3ff-200d-1f4bb","👩🏻‍💻":"1f469-1f3fb-200d-1f4bb","👩🏼‍💻":"1f469-1f3fc-200d-1f4bb","👩🏽‍💻":"1f469-1f3fd-200d-1f4bb","👩🏾‍💻":"1f469-1f3fe-200d-1f4bb","👩🏿‍💻":"1f469-1f3ff-200d-1f4bb","🧑🏻‍🎤":"1f9d1-1f3fb-200d-1f3a4","🧑🏼‍🎤":"1f9d1-1f3fc-200d-1f3a4","🧑🏽‍🎤":"1f9d1-1f3fd-200d-1f3a4","🧑🏾‍🎤":"1f9d1-1f3fe-200d-1f3a4","🧑🏿‍🎤":"1f9d1-1f3ff-200d-1f3a4","👨🏻‍🎤":"1f468-1f3fb-200d-1f3a4","👨🏼‍🎤":"1f468-1f3fc-200d-1f3a4","👨🏽‍🎤":"1f468-1f3fd-200d-1f3a4","👨🏾‍🎤":"1f468-1f3fe-200d-1f3a4","👨🏿‍🎤":"1f468-1f3ff-200d-1f3a4","👩🏻‍🎤":"1f469-1f3fb-200d-1f3a4","👩🏼‍🎤":"1f469-1f3fc-200d-1f3a4","👩🏽‍🎤":"1f469-1f3fd-200d-1f3a4","👩🏾‍🎤":"1f469-1f3fe-200d-1f3a4","👩🏿‍🎤":"1f469-1f3ff-200d-1f3a4","🧑🏻‍🎨":"1f9d1-1f3fb-200d-1f3a8","🧑🏼‍🎨":"1f9d1-1f3fc-200d-1f3a8","🧑🏽‍🎨":"1f9d1-1f3fd-200d-1f3a8","🧑🏾‍🎨":"1f9d1-1f3fe-200d-1f3a8","🧑🏿‍🎨":"1f9d1-1f3ff-200d-1f3a8","👨🏻‍🎨":"1f468-1f3fb-200d-1f3a8","🤸‍♀️":"1f938-200d-2640-fe0f","👨🏼‍🎨":"1f468-1f3fc-200d-1f3a8","👨🏽‍🎨":"1f468-1f3fd-200d-1f3a8","👨🏾‍🎨":"1f468-1f3fe-200d-1f3a8","👨🏿‍🎨":"1f468-1f3ff-200d-1f3a8","👩🏻‍🎨":"1f469-1f3fb-200d-1f3a8","👩🏼‍🎨":"1f469-1f3fc-200d-1f3a8","👩🏽‍🎨":"1f469-1f3fd-200d-1f3a8","👩🏾‍🎨":"1f469-1f3fe-200d-1f3a8","👩🏿‍🎨":"1f469-1f3ff-200d-1f3a8","🧑‍✈️":"1f9d1-200d-2708-fe0f","🧑🏻‍✈":"1f9d1-1f3fb-200d-2708-fe0f","🧑🏼‍✈":"1f9d1-1f3fc-200d-2708-fe0f","🧑🏽‍✈":"1f9d1-1f3fd-200d-2708-fe0f","🧑🏾‍✈":"1f9d1-1f3fe-200d-2708-fe0f","🧑🏿‍✈":"1f9d1-1f3ff-200d-2708-fe0f","👨‍✈️":"1f468-200d-2708-fe0f","👨🏻‍✈":"1f468-1f3fb-200d-2708-fe0f","👨🏼‍✈":"1f468-1f3fc-200d-2708-fe0f","👨🏽‍✈":"1f468-1f3fd-200d-2708-fe0f","👨🏾‍✈":"1f468-1f3fe-200d-2708-fe0f","👨🏿‍✈":"1f468-1f3ff-200d-2708-fe0f","👩‍✈️":"1f469-200d-2708-fe0f","👩🏻‍✈":"1f469-1f3fb-200d-2708-fe0f","👩🏼‍✈":"1f469-1f3fc-200d-2708-fe0f","👩🏽‍✈":"1f469-1f3fd-200d-2708-fe0f","👩🏾‍✈":"1f469-1f3fe-200d-2708-fe0f","⛓️‍💥":"26d3-fe0f-200d-1f4a5","👩🏿‍✈":"1f469-1f3ff-200d-2708-fe0f","🧑🏻‍🚀":"1f9d1-1f3fb-200d-1f680","🧑🏼‍🚀":"1f9d1-1f3fc-200d-1f680","🧑🏽‍🚀":"1f9d1-1f3fd-200d-1f680","🧑🏾‍🚀":"1f9d1-1f3fe-200d-1f680","🧑🏿‍🚀":"1f9d1-1f3ff-200d-1f680","👨🏻‍🚀":"1f468-1f3fb-200d-1f680","👨🏼‍🚀":"1f468-1f3fc-200d-1f680","👨🏽‍🚀":"1f468-1f3fd-200d-1f680","👨🏾‍🚀":"1f468-1f3fe-200d-1f680","👨🏿‍🚀":"1f468-1f3ff-200d-1f680","👩🏻‍🚀":"1f469-1f3fb-200d-1f680","👩🏼‍🚀":"1f469-1f3fc-200d-1f680","👩🏽‍🚀":"1f469-1f3fd-200d-1f680","👩🏾‍🚀":"1f469-1f3fe-200d-1f680","👩🏿‍🚀":"1f469-1f3ff-200d-1f680","🧑🏻‍🚒":"1f9d1-1f3fb-200d-1f692","🧑🏼‍🚒":"1f9d1-1f3fc-200d-1f692","🧑🏽‍🚒":"1f9d1-1f3fd-200d-1f692","🧑🏾‍🚒":"1f9d1-1f3fe-200d-1f692","🧑🏿‍🚒":"1f9d1-1f3ff-200d-1f692","👨🏻‍🚒":"1f468-1f3fb-200d-1f692","👨🏼‍🚒":"1f468-1f3fc-200d-1f692","🤾🏽‍♂":"1f93e-1f3fd-200d-2642-fe0f","🏋‍♂️":"1f3cb-fe0f-200d-2642-fe0f","👨🏽‍🚒":"1f468-1f3fd-200d-1f692","👨🏾‍🚒":"1f468-1f3fe-200d-1f692","👨🏿‍🚒":"1f468-1f3ff-200d-1f692","🤾🏾‍♂":"1f93e-1f3fe-200d-2642-fe0f","🚵🏼‍♀":"1f6b5-1f3fc-200d-2640-fe0f","👩🏻‍🚒":"1f469-1f3fb-200d-1f692","👩🏼‍🚒":"1f469-1f3fc-200d-1f692","👩🏽‍🚒":"1f469-1f3fd-200d-1f692","🤾🏿‍♂":"1f93e-1f3ff-200d-2642-fe0f","🤾‍♀️":"1f93e-200d-2640-fe0f","👩🏾‍🚒":"1f469-1f3fe-200d-1f692","👩🏿‍🚒":"1f469-1f3ff-200d-1f692","👮‍♂️":"1f46e-200d-2642-fe0f","🤸🏻‍♀":"1f938-1f3fb-200d-2640-fe0f","🚵🏼‍♂":"1f6b5-1f3fc-200d-2642-fe0f","🤾🏻‍♀":"1f93e-1f3fb-200d-2640-fe0f","🤸🏼‍♀":"1f938-1f3fc-200d-2640-fe0f","🤾🏼‍♀":"1f93e-1f3fc-200d-2640-fe0f","🚵🏽‍♀":"1f6b5-1f3fd-200d-2640-fe0f","🤾🏽‍♀":"1f93e-1f3fd-200d-2640-fe0f","🤸🏽‍♀":"1f938-1f3fd-200d-2640-fe0f","🤾🏾‍♀":"1f93e-1f3fe-200d-2640-fe0f","🏋🏻‍♂":"1f3cb-1f3fb-200d-2642-fe0f","🤾🏿‍♀":"1f93e-1f3ff-200d-2640-fe0f","🏄🏽‍♀":"1f3c4-1f3fd-200d-2640-fe0f","⛹🏼‍♂":"26f9-1f3fc-200d-2642-fe0f","🏄🏼‍♀":"1f3c4-1f3fc-200d-2640-fe0f","🤸🏾‍♀":"1f938-1f3fe-200d-2640-fe0f","🏄🏻‍♀":"1f3c4-1f3fb-200d-2640-fe0f","⛹🏻‍♂":"26f9-1f3fb-200d-2642-fe0f","👮🏻‍♂":"1f46e-1f3fb-200d-2642-fe0f","🚵🏾‍♀":"1f6b5-1f3fe-200d-2640-fe0f","⛹️‍♂":"26f9-fe0f-200d-2642-fe0f","⛹‍♂️":"26f9-fe0f-200d-2642-fe0f","🏄‍♀️":"1f3c4-200d-2640-fe0f","🏄🏿‍♂":"1f3c4-1f3ff-200d-2642-fe0f","🤹‍♂️":"1f939-200d-2642-fe0f","🏄🏾‍♂":"1f3c4-1f3fe-200d-2642-fe0f","🧔‍♂️":"1f9d4-200d-2642-fe0f","🏄🏽‍♂":"1f3c4-1f3fd-200d-2642-fe0f","🤸🏿‍♀":"1f938-1f3ff-200d-2640-fe0f","🏄🏼‍♂":"1f3c4-1f3fc-200d-2642-fe0f","🤹🏻‍♂":"1f939-1f3fb-200d-2642-fe0f","🏄🏻‍♂":"1f3c4-1f3fb-200d-2642-fe0f","⛹🏼‍♀":"26f9-1f3fc-200d-2640-fe0f","🏄‍♂️":"1f3c4-200d-2642-fe0f","🏌🏿‍♀":"1f3cc-1f3ff-200d-2640-fe0f","🤹🏼‍♂":"1f939-1f3fc-200d-2642-fe0f","🏌🏾‍♀":"1f3cc-1f3fe-200d-2640-fe0f","🤼‍♂️":"1f93c-200d-2642-fe0f","🏌🏽‍♀":"1f3cc-1f3fd-200d-2640-fe0f","🤹🏽‍♂":"1f939-1f3fd-200d-2642-fe0f","🧔🏽‍♂":"1f9d4-1f3fd-200d-2642-fe0f","🏌🏼‍♀":"1f3cc-1f3fc-200d-2640-fe0f","🤹🏾‍♂":"1f939-1f3fe-200d-2642-fe0f","🏌🏻‍♀":"1f3cc-1f3fb-200d-2640-fe0f","🤼‍♀️":"1f93c-200d-2640-fe0f","🏌️‍♀":"1f3cc-fe0f-200d-2640-fe0f","🤹🏿‍♂":"1f939-1f3ff-200d-2642-fe0f","🏌‍♀️":"1f3cc-fe0f-200d-2640-fe0f","🤹‍♀️":"1f939-200d-2640-fe0f","🏌🏿‍♂":"1f3cc-1f3ff-200d-2642-fe0f","🚵🏽‍♂":"1f6b5-1f3fd-200d-2642-fe0f","🏌🏾‍♂":"1f3cc-1f3fe-200d-2642-fe0f","🚵🏿‍♀":"1f6b5-1f3ff-200d-2640-fe0f","🏌🏽‍♂":"1f3cc-1f3fd-200d-2642-fe0f","🤹🏻‍♀":"1f939-1f3fb-200d-2640-fe0f","🏌🏼‍♂":"1f3cc-1f3fc-200d-2642-fe0f","⛹🏻‍♀":"26f9-1f3fb-200d-2640-fe0f","🏌🏻‍♂":"1f3cc-1f3fb-200d-2642-fe0f","🤹🏼‍♀":"1f939-1f3fc-200d-2640-fe0f","🏌️‍♂":"1f3cc-fe0f-200d-2642-fe0f","🏌‍♂️":"1f3cc-fe0f-200d-2642-fe0f","⛹🏿‍♀":"26f9-1f3ff-200d-2640-fe0f","🧗🏿‍♀":"1f9d7-1f3ff-200d-2640-fe0f","🤹🏽‍♀":"1f939-1f3fd-200d-2640-fe0f","🧗🏾‍♀":"1f9d7-1f3fe-200d-2640-fe0f","⛹️‍♀":"26f9-fe0f-200d-2640-fe0f","🧗🏽‍♀":"1f9d7-1f3fd-200d-2640-fe0f","🤹🏾‍♀":"1f939-1f3fe-200d-2640-fe0f","🧗🏼‍♀":"1f9d7-1f3fc-200d-2640-fe0f","⛹‍♀️":"26f9-fe0f-200d-2640-fe0f","🧗🏻‍♀":"1f9d7-1f3fb-200d-2640-fe0f","🤹🏿‍♀":"1f939-1f3ff-200d-2640-fe0f","🧗‍♀️":"1f9d7-200d-2640-fe0f","🧗🏿‍♂":"1f9d7-1f3ff-200d-2642-fe0f","🚵‍♂️":"1f6b5-200d-2642-fe0f","🧗🏾‍♂":"1f9d7-1f3fe-200d-2642-fe0f","🏊🏿‍♀":"1f3ca-1f3ff-200d-2640-fe0f","🧗🏽‍♂":"1f9d7-1f3fd-200d-2642-fe0f","🤽‍♂️":"1f93d-200d-2642-fe0f","🧗🏼‍♂":"1f9d7-1f3fc-200d-2642-fe0f","🏊🏾‍♀":"1f3ca-1f3fe-200d-2640-fe0f","🧗🏻‍♂":"1f9d7-1f3fb-200d-2642-fe0f","🧔🏼‍♂":"1f9d4-1f3fc-200d-2642-fe0f","🧗‍♂️":"1f9d7-200d-2642-fe0f","🧖🏿‍♀":"1f9d6-1f3ff-200d-2640-fe0f","🏊🏽‍♀":"1f3ca-1f3fd-200d-2640-fe0f","🧖🏾‍♀":"1f9d6-1f3fe-200d-2640-fe0f","🧘‍♂️":"1f9d8-200d-2642-fe0f","🧖🏽‍♀":"1f9d6-1f3fd-200d-2640-fe0f","⛹🏾‍♀":"26f9-1f3fe-200d-2640-fe0f","🐻‍❄️":"1f43b-200d-2744-fe0f","🤽🏻‍♂":"1f93d-1f3fb-200d-2642-fe0f","🧖🏼‍♀":"1f9d6-1f3fc-200d-2640-fe0f","🧘🏻‍♂":"1f9d8-1f3fb-200d-2642-fe0f","🧖🏻‍♀":"1f9d6-1f3fb-200d-2640-fe0f","🚵🏾‍♂":"1f6b5-1f3fe-200d-2642-fe0f","🧖‍♀️":"1f9d6-200d-2640-fe0f","🧖🏿‍♂":"1f9d6-1f3ff-200d-2642-fe0f","🧘🏼‍♂":"1f9d8-1f3fc-200d-2642-fe0f","🧖🏾‍♂":"1f9d6-1f3fe-200d-2642-fe0f","🤽🏼‍♂":"1f93d-1f3fc-200d-2642-fe0f","🧖🏽‍♂":"1f9d6-1f3fd-200d-2642-fe0f","🧘🏽‍♂":"1f9d8-1f3fd-200d-2642-fe0f","🧖🏼‍♂":"1f9d6-1f3fc-200d-2642-fe0f","⛹🏽‍♀":"26f9-1f3fd-200d-2640-fe0f","🧖🏻‍♂":"1f9d6-1f3fb-200d-2642-fe0f","🧘🏾‍♂":"1f9d8-1f3fe-200d-2642-fe0f","🧖‍♂️":"1f9d6-200d-2642-fe0f","👯‍♀️":"1f46f-200d-2640-fe0f","👯‍♂️":"1f46f-200d-2642-fe0f","👮🏼‍♂":"1f46e-1f3fc-200d-2642-fe0f","👮🏽‍♂":"1f46e-1f3fd-200d-2642-fe0f","👮🏾‍♂":"1f46e-1f3fe-200d-2642-fe0f","👮🏿‍♂":"1f46e-1f3ff-200d-2642-fe0f","👮‍♀️":"1f46e-200d-2640-fe0f","👮🏻‍♀":"1f46e-1f3fb-200d-2640-fe0f","👮🏼‍♀":"1f46e-1f3fc-200d-2640-fe0f","👮🏽‍♀":"1f46e-1f3fd-200d-2640-fe0f","👮🏾‍♀":"1f46e-1f3fe-200d-2640-fe0f","👮🏿‍♀":"1f46e-1f3ff-200d-2640-fe0f","🤽🏽‍♂":"1f93d-1f3fd-200d-2642-fe0f","🕵‍♂️":"1f575-fe0f-200d-2642-fe0f","🧘🏿‍♂":"1f9d8-1f3ff-200d-2642-fe0f","🕵️‍♂":"1f575-fe0f-200d-2642-fe0f","🕵🏻‍♂":"1f575-1f3fb-200d-2642-fe0f","🕵🏼‍♂":"1f575-1f3fc-200d-2642-fe0f","🕵🏽‍♂":"1f575-1f3fd-200d-2642-fe0f","🕵🏾‍♂":"1f575-1f3fe-200d-2642-fe0f","🕵🏿‍♂":"1f575-1f3ff-200d-2642-fe0f","🕵‍♀️":"1f575-fe0f-200d-2640-fe0f","🕵️‍♀":"1f575-fe0f-200d-2640-fe0f","🕵🏻‍♀":"1f575-1f3fb-200d-2640-fe0f","🧘‍♀️":"1f9d8-200d-2640-fe0f","🕵🏼‍♀":"1f575-1f3fc-200d-2640-fe0f","🕵🏽‍♀":"1f575-1f3fd-200d-2640-fe0f","🕵🏾‍♀":"1f575-1f3fe-200d-2640-fe0f","🕵🏿‍♀":"1f575-1f3ff-200d-2640-fe0f","💂‍♂️":"1f482-200d-2642-fe0f","💂🏻‍♂":"1f482-1f3fb-200d-2642-fe0f","💂🏼‍♂":"1f482-1f3fc-200d-2642-fe0f","💂🏽‍♂":"1f482-1f3fd-200d-2642-fe0f","💂🏾‍♂":"1f482-1f3fe-200d-2642-fe0f","💂🏿‍♂":"1f482-1f3ff-200d-2642-fe0f","💂‍♀️":"1f482-200d-2640-fe0f","💂🏻‍♀":"1f482-1f3fb-200d-2640-fe0f","💂🏼‍♀":"1f482-1f3fc-200d-2640-fe0f","💂🏽‍♀":"1f482-1f3fd-200d-2640-fe0f","💂🏾‍♀":"1f482-1f3fe-200d-2640-fe0f","💂🏿‍♀":"1f482-1f3ff-200d-2640-fe0f","👷‍♂️":"1f477-200d-2642-fe0f","👷🏻‍♂":"1f477-1f3fb-200d-2642-fe0f","👷🏼‍♂":"1f477-1f3fc-200d-2642-fe0f","👷🏽‍♂":"1f477-1f3fd-200d-2642-fe0f","👷🏾‍♂":"1f477-1f3fe-200d-2642-fe0f","👷🏿‍♂":"1f477-1f3ff-200d-2642-fe0f","👷‍♀️":"1f477-200d-2640-fe0f","🧔🏿‍♂":"1f9d4-1f3ff-200d-2642-fe0f","👷🏻‍♀":"1f477-1f3fb-200d-2640-fe0f","👷🏼‍♀":"1f477-1f3fc-200d-2640-fe0f","👷🏽‍♀":"1f477-1f3fd-200d-2640-fe0f","🏃🏿‍➡":"1f3c3-1f3ff-200d-27a1-fe0f","🤽🏾‍♂":"1f93d-1f3fe-200d-2642-fe0f","🏃🏾‍➡":"1f3c3-1f3fe-200d-27a1-fe0f","🧘🏻‍♀":"1f9d8-1f3fb-200d-2640-fe0f","🏃🏽‍➡":"1f3c3-1f3fd-200d-27a1-fe0f","🤸‍♂️":"1f938-200d-2642-fe0f","🏃🏼‍➡":"1f3c3-1f3fc-200d-27a1-fe0f","🧘🏼‍♀":"1f9d8-1f3fc-200d-2640-fe0f","🏃🏻‍➡":"1f3c3-1f3fb-200d-27a1-fe0f","🤽🏿‍♂":"1f93d-1f3ff-200d-2642-fe0f","🏃‍➡️":"1f3c3-200d-27a1-fe0f","🏃🏿‍♀":"1f3c3-1f3ff-200d-2640-fe0f","🧘🏽‍♀":"1f9d8-1f3fd-200d-2640-fe0f","🏃🏾‍♀":"1f3c3-1f3fe-200d-2640-fe0f","🤽‍♀️":"1f93d-200d-2640-fe0f","🏃🏽‍♀":"1f3c3-1f3fd-200d-2640-fe0f","🧘🏾‍♀":"1f9d8-1f3fe-200d-2640-fe0f","🏃🏼‍♀":"1f3c3-1f3fc-200d-2640-fe0f","🧔🏾‍♂":"1f9d4-1f3fe-200d-2642-fe0f","🏃🏻‍♀":"1f3c3-1f3fb-200d-2640-fe0f","🧘🏿‍♀":"1f9d8-1f3ff-200d-2640-fe0f","🏃‍♀️":"1f3c3-200d-2640-fe0f","🏃🏿‍♂":"1f3c3-1f3ff-200d-2642-fe0f","🚵🏿‍♂":"1f6b5-1f3ff-200d-2642-fe0f","🏃🏾‍♂":"1f3c3-1f3fe-200d-2642-fe0f","🏊🏼‍♀":"1f3ca-1f3fc-200d-2640-fe0f","🏃🏽‍♂":"1f3c3-1f3fd-200d-2642-fe0f","🤽🏻‍♀":"1f93d-1f3fb-200d-2640-fe0f","🏃🏼‍♂":"1f3c3-1f3fc-200d-2642-fe0f","🏊🏻‍♀":"1f3ca-1f3fb-200d-2640-fe0f","🏃🏻‍♂":"1f3c3-1f3fb-200d-2642-fe0f","🤸🏻‍♂":"1f938-1f3fb-200d-2642-fe0f","🏃‍♂️":"1f3c3-200d-2642-fe0f","👷🏾‍♀":"1f477-1f3fe-200d-2640-fe0f","👷🏿‍♀":"1f477-1f3ff-200d-2640-fe0f","👳‍♂️":"1f473-200d-2642-fe0f","👳🏻‍♂":"1f473-1f3fb-200d-2642-fe0f","👳🏼‍♂":"1f473-1f3fc-200d-2642-fe0f","👳🏽‍♂":"1f473-1f3fd-200d-2642-fe0f","👳🏾‍♂":"1f473-1f3fe-200d-2642-fe0f","👳🏿‍♂":"1f473-1f3ff-200d-2642-fe0f","👳‍♀️":"1f473-200d-2640-fe0f","🏊‍♀️":"1f3ca-200d-2640-fe0f","👳🏻‍♀":"1f473-1f3fb-200d-2640-fe0f","🏊🏿‍♂":"1f3ca-1f3ff-200d-2642-fe0f","👳🏼‍♀":"1f473-1f3fc-200d-2640-fe0f","👩🏿‍🦽":"1f469-1f3ff-200d-1f9bd","👩🏾‍🦽":"1f469-1f3fe-200d-1f9bd","👩🏽‍🦽":"1f469-1f3fd-200d-1f9bd","👩🏼‍🦽":"1f469-1f3fc-200d-1f9bd","👩🏻‍🦽":"1f469-1f3fb-200d-1f9bd","👳🏽‍♀":"1f473-1f3fd-200d-2640-fe0f","👳🏾‍♀":"1f473-1f3fe-200d-2640-fe0f","👳🏿‍♀":"1f473-1f3ff-200d-2640-fe0f","🤵‍♂️":"1f935-200d-2642-fe0f","🤵🏻‍♂":"1f935-1f3fb-200d-2642-fe0f","🤵🏼‍♂":"1f935-1f3fc-200d-2642-fe0f","🤵🏽‍♂":"1f935-1f3fd-200d-2642-fe0f","🤵🏾‍♂":"1f935-1f3fe-200d-2642-fe0f","🤵🏿‍♂":"1f935-1f3ff-200d-2642-fe0f","🤵‍♀️":"1f935-200d-2640-fe0f","🤽🏼‍♀":"1f93d-1f3fc-200d-2640-fe0f","🤵🏻‍♀":"1f935-1f3fb-200d-2640-fe0f","👨🏿‍🦽":"1f468-1f3ff-200d-1f9bd","👨🏾‍🦽":"1f468-1f3fe-200d-1f9bd","👨🏽‍🦽":"1f468-1f3fd-200d-1f9bd","👨🏼‍🦽":"1f468-1f3fc-200d-1f9bd","👨🏻‍🦽":"1f468-1f3fb-200d-1f9bd","🤵🏼‍♀":"1f935-1f3fc-200d-2640-fe0f","🤵🏽‍♀":"1f935-1f3fd-200d-2640-fe0f","🤵🏾‍♀":"1f935-1f3fe-200d-2640-fe0f","🤵🏿‍♀":"1f935-1f3ff-200d-2640-fe0f","👰‍♂️":"1f470-200d-2642-fe0f","👰🏻‍♂":"1f470-1f3fb-200d-2642-fe0f","👰🏼‍♂":"1f470-1f3fc-200d-2642-fe0f","🏊🏾‍♂":"1f3ca-1f3fe-200d-2642-fe0f","👰🏽‍♂":"1f470-1f3fd-200d-2642-fe0f","👰🏾‍♂":"1f470-1f3fe-200d-2642-fe0f","👰🏿‍♂":"1f470-1f3ff-200d-2642-fe0f","🚵‍♀️":"1f6b5-200d-2640-fe0f","👰‍♀️":"1f470-200d-2640-fe0f","🧑🏿‍🦽":"1f9d1-1f3ff-200d-1f9bd","🧑🏾‍🦽":"1f9d1-1f3fe-200d-1f9bd","🧑🏽‍🦽":"1f9d1-1f3fd-200d-1f9bd","🧑🏼‍🦽":"1f9d1-1f3fc-200d-1f9bd","🧑🏻‍🦽":"1f9d1-1f3fb-200d-1f9bd","👰🏻‍♀":"1f470-1f3fb-200d-2640-fe0f","👰🏼‍♀":"1f470-1f3fc-200d-2640-fe0f","👰🏽‍♀":"1f470-1f3fd-200d-2640-fe0f","👰🏾‍♀":"1f470-1f3fe-200d-2640-fe0f","👰🏿‍♀":"1f470-1f3ff-200d-2640-fe0f","👩🏻‍🍼":"1f469-1f3fb-200d-1f37c","👩🏼‍🍼":"1f469-1f3fc-200d-1f37c","👩🏽‍🍼":"1f469-1f3fd-200d-1f37c","👩🏾‍🍼":"1f469-1f3fe-200d-1f37c","👩🏿‍🍼":"1f469-1f3ff-200d-1f37c","🏊🏽‍♂":"1f3ca-1f3fd-200d-2642-fe0f","👨🏻‍🍼":"1f468-1f3fb-200d-1f37c","👩🏿‍🦼":"1f469-1f3ff-200d-1f9bc","👩🏾‍🦼":"1f469-1f3fe-200d-1f9bc","👩🏽‍🦼":"1f469-1f3fd-200d-1f9bc","👩🏼‍🦼":"1f469-1f3fc-200d-1f9bc","👩🏻‍🦼":"1f469-1f3fb-200d-1f9bc","👨🏼‍🍼":"1f468-1f3fc-200d-1f37c","👨🏽‍🍼":"1f468-1f3fd-200d-1f37c","👨🏿‍🍼":"1f468-1f3ff-200d-1f37c","🦸🏼‍♀️":"1f9b8-1f3fc-200d-2640-fe0f","🏋🏻‍♂️":"1f3cb-1f3fb-200d-2642-fe0f","🏋️‍♂️":"1f3cb-fe0f-200d-2642-fe0f","🚵🏻‍♂️":"1f6b5-1f3fb-200d-2642-fe0f","🚵🏼‍♂️":"1f6b5-1f3fc-200d-2642-fe0f","🚵🏽‍♂️":"1f6b5-1f3fd-200d-2642-fe0f","🚵🏾‍♂️":"1f6b5-1f3fe-200d-2642-fe0f","🚵🏿‍♂️":"1f6b5-1f3ff-200d-2642-fe0f","🧔🏿‍♂️":"1f9d4-1f3ff-200d-2642-fe0f","🚵🏻‍♀️":"1f6b5-1f3fb-200d-2640-fe0f","🚵🏼‍♀️":"1f6b5-1f3fc-200d-2640-fe0f","🚵🏽‍♀️":"1f6b5-1f3fd-200d-2640-fe0f","🚵🏾‍♀️":"1f6b5-1f3fe-200d-2640-fe0f","🚵🏿‍♀️":"1f6b5-1f3ff-200d-2640-fe0f","⛹🏿‍♀️":"26f9-1f3ff-200d-2640-fe0f","⛹🏾‍♀️":"26f9-1f3fe-200d-2640-fe0f","⛹🏽‍♀️":"26f9-1f3fd-200d-2640-fe0f","🤸🏻‍♂️":"1f938-1f3fb-200d-2642-fe0f","🤸🏼‍♂️":"1f938-1f3fc-200d-2642-fe0f","🤸🏽‍♂️":"1f938-1f3fd-200d-2642-fe0f","🤸🏾‍♂️":"1f938-1f3fe-200d-2642-fe0f","🤸🏿‍♂️":"1f938-1f3ff-200d-2642-fe0f","🧔🏾‍♂️":"1f9d4-1f3fe-200d-2642-fe0f","🤸🏻‍♀️":"1f938-1f3fb-200d-2640-fe0f","🤸🏼‍♀️":"1f938-1f3fc-200d-2640-fe0f","🤸🏽‍♀️":"1f938-1f3fd-200d-2640-fe0f","🤸🏾‍♀️":"1f938-1f3fe-200d-2640-fe0f","🤸🏿‍♀️":"1f938-1f3ff-200d-2640-fe0f","🧔🏽‍♂️":"1f9d4-1f3fd-200d-2642-fe0f","⛹🏼‍♀️":"26f9-1f3fc-200d-2640-fe0f","⛹🏻‍♀️":"26f9-1f3fb-200d-2640-fe0f","⛹️‍♀️":"26f9-fe0f-200d-2640-fe0f","🤽🏻‍♂️":"1f93d-1f3fb-200d-2642-fe0f","🤽🏼‍♂️":"1f93d-1f3fc-200d-2642-fe0f","🤽🏽‍♂️":"1f93d-1f3fd-200d-2642-fe0f","🤽🏾‍♂️":"1f93d-1f3fe-200d-2642-fe0f","🤽🏿‍♂️":"1f93d-1f3ff-200d-2642-fe0f","🧔🏼‍♂️":"1f9d4-1f3fc-200d-2642-fe0f","🤽🏻‍♀️":"1f93d-1f3fb-200d-2640-fe0f","🤽🏼‍♀️":"1f93d-1f3fc-200d-2640-fe0f","🤽🏽‍♀️":"1f93d-1f3fd-200d-2640-fe0f","🤽🏾‍♀️":"1f93d-1f3fe-200d-2640-fe0f","🤽🏿‍♀️":"1f93d-1f3ff-200d-2640-fe0f","⛹🏿‍♂️":"26f9-1f3ff-200d-2642-fe0f","⛹🏾‍♂️":"26f9-1f3fe-200d-2642-fe0f","⛹🏽‍♂️":"26f9-1f3fd-200d-2642-fe0f","🤾🏻‍♂️":"1f93e-1f3fb-200d-2642-fe0f","🤾🏼‍♂️":"1f93e-1f3fc-200d-2642-fe0f","🤾🏽‍♂️":"1f93e-1f3fd-200d-2642-fe0f","🤾🏾‍♂️":"1f93e-1f3fe-200d-2642-fe0f","🤾🏿‍♂️":"1f93e-1f3ff-200d-2642-fe0f","🧔🏻‍♂️":"1f9d4-1f3fb-200d-2642-fe0f","🤾🏻‍♀️":"1f93e-1f3fb-200d-2640-fe0f","🤾🏼‍♀️":"1f93e-1f3fc-200d-2640-fe0f","🤾🏽‍♀️":"1f93e-1f3fd-200d-2640-fe0f","🤾🏾‍♀️":"1f93e-1f3fe-200d-2640-fe0f","🤾🏿‍♀️":"1f93e-1f3ff-200d-2640-fe0f","⛹🏼‍♂️":"26f9-1f3fc-200d-2642-fe0f","⛹🏻‍♂️":"26f9-1f3fb-200d-2642-fe0f","🤹🏻‍♂️":"1f939-1f3fb-200d-2642-fe0f","🤹🏼‍♂️":"1f939-1f3fc-200d-2642-fe0f","🤹🏽‍♂️":"1f939-1f3fd-200d-2642-fe0f","🤹🏾‍♂️":"1f939-1f3fe-200d-2642-fe0f","🤹🏿‍♂️":"1f939-1f3ff-200d-2642-fe0f","🫱🏿‍🫲🏾":"1faf1-1f3ff-200d-1faf2-1f3fe","🤹🏻‍♀️":"1f939-1f3fb-200d-2640-fe0f","🤹🏼‍♀️":"1f939-1f3fc-200d-2640-fe0f","🤹🏽‍♀️":"1f939-1f3fd-200d-2640-fe0f","🤹🏾‍♀️":"1f939-1f3fe-200d-2640-fe0f","🤹🏿‍♀️":"1f939-1f3ff-200d-2640-fe0f","⛹️‍♂️":"26f9-fe0f-200d-2642-fe0f","🏊🏿‍♀️":"1f3ca-1f3ff-200d-2640-fe0f","🏊🏾‍♀️":"1f3ca-1f3fe-200d-2640-fe0f","🫱🏿‍🫲🏽":"1faf1-1f3ff-200d-1faf2-1f3fd","🧘🏻‍♂️":"1f9d8-1f3fb-200d-2642-fe0f","🧘🏼‍♂️":"1f9d8-1f3fc-200d-2642-fe0f","🧘🏽‍♂️":"1f9d8-1f3fd-200d-2642-fe0f","🧘🏾‍♂️":"1f9d8-1f3fe-200d-2642-fe0f","🧘🏿‍♂️":"1f9d8-1f3ff-200d-2642-fe0f","🫱🏿‍🫲🏼":"1faf1-1f3ff-200d-1faf2-1f3fc","🧘🏻‍♀️":"1f9d8-1f3fb-200d-2640-fe0f","🧘🏼‍♀️":"1f9d8-1f3fc-200d-2640-fe0f","🧘🏽‍♀️":"1f9d8-1f3fd-200d-2640-fe0f","🧘🏾‍♀️":"1f9d8-1f3fe-200d-2640-fe0f","🧘🏿‍♀️":"1f9d8-1f3ff-200d-2640-fe0f","🏊🏽‍♀️":"1f3ca-1f3fd-200d-2640-fe0f","🏊🏼‍♀️":"1f3ca-1f3fc-200d-2640-fe0f","🏊🏻‍♀️":"1f3ca-1f3fb-200d-2640-fe0f","🏊🏿‍♂️":"1f3ca-1f3ff-200d-2642-fe0f","🏊🏾‍♂️":"1f3ca-1f3fe-200d-2642-fe0f","🏊🏽‍♂️":"1f3ca-1f3fd-200d-2642-fe0f","🧑‍🤝‍🧑":"1f9d1-200d-1f91d-200d-1f9d1","🏊🏼‍♂️":"1f3ca-1f3fc-200d-2642-fe0f","🏊🏻‍♂️":"1f3ca-1f3fb-200d-2642-fe0f","🚣🏿‍♀️":"1f6a3-1f3ff-200d-2640-fe0f","🚣🏾‍♀️":"1f6a3-1f3fe-200d-2640-fe0f","🚣🏽‍♀️":"1f6a3-1f3fd-200d-2640-fe0f","🚣🏼‍♀️":"1f6a3-1f3fc-200d-2640-fe0f","🚣🏻‍♀️":"1f6a3-1f3fb-200d-2640-fe0f","🚣🏿‍♂️":"1f6a3-1f3ff-200d-2642-fe0f","🚣🏾‍♂️":"1f6a3-1f3fe-200d-2642-fe0f","🚣🏽‍♂️":"1f6a3-1f3fd-200d-2642-fe0f","🚣🏼‍♂️":"1f6a3-1f3fc-200d-2642-fe0f","🚣🏻‍♂️":"1f6a3-1f3fb-200d-2642-fe0f","🏄🏿‍♀️":"1f3c4-1f3ff-200d-2640-fe0f","👩‍❤‍👨":"1f469-200d-2764-fe0f-200d-1f468","👨‍❤‍👨":"1f468-200d-2764-fe0f-200d-1f468","👩‍❤‍👩":"1f469-200d-2764-fe0f-200d-1f469","👨‍👩‍👦":"1f468-200d-1f469-200d-1f466","👨‍👩‍👧":"1f468-200d-1f469-200d-1f467","👨‍👨‍👦":"1f468-200d-1f468-200d-1f466","👨‍👨‍👧":"1f468-200d-1f468-200d-1f467","👩‍👩‍👦":"1f469-200d-1f469-200d-1f466","👩‍👩‍👧":"1f469-200d-1f469-200d-1f467","🫱🏿‍🫲🏻":"1faf1-1f3ff-200d-1faf2-1f3fb","👨‍👦‍👦":"1f468-200d-1f466-200d-1f466","🫱🏾‍🫲🏿":"1faf1-1f3fe-200d-1faf2-1f3ff","👨‍👧‍👦":"1f468-200d-1f467-200d-1f466","👨‍👧‍👧":"1f468-200d-1f467-200d-1f467","🫱🏾‍🫲🏽":"1faf1-1f3fe-200d-1faf2-1f3fd","👩‍👦‍👦":"1f469-200d-1f466-200d-1f466","🫱🏾‍🫲🏼":"1faf1-1f3fe-200d-1faf2-1f3fc","👩‍👧‍👦":"1f469-200d-1f467-200d-1f466","👩‍👧‍👧":"1f469-200d-1f467-200d-1f467","🏄🏾‍♀️":"1f3c4-1f3fe-200d-2640-fe0f","🏄🏽‍♀️":"1f3c4-1f3fd-200d-2640-fe0f","🏄🏼‍♀️":"1f3c4-1f3fc-200d-2640-fe0f","🧑‍🧑‍🧒":"1f9d1-200d-1f9d1-200d-1f9d2","🫱🏾‍🫲🏻":"1faf1-1f3fe-200d-1faf2-1f3fb","🧑‍🧒‍🧒":"1f9d1-200d-1f9d2-200d-1f9d2","🏄🏻‍♀️":"1f3c4-1f3fb-200d-2640-fe0f","🏄🏿‍♂️":"1f3c4-1f3ff-200d-2642-fe0f","🏄🏾‍♂️":"1f3c4-1f3fe-200d-2642-fe0f","🏄🏽‍♂️":"1f3c4-1f3fd-200d-2642-fe0f","🏄🏼‍♂️":"1f3c4-1f3fc-200d-2642-fe0f","🏄🏻‍♂️":"1f3c4-1f3fb-200d-2642-fe0f","🏌🏿‍♀️":"1f3cc-1f3ff-200d-2640-fe0f","🏌🏾‍♀️":"1f3cc-1f3fe-200d-2640-fe0f","🫱🏽‍🫲🏿":"1faf1-1f3fd-200d-1faf2-1f3ff","🏌🏽‍♀️":"1f3cc-1f3fd-200d-2640-fe0f","🏌🏼‍♀️":"1f3cc-1f3fc-200d-2640-fe0f","🏌🏻‍♀️":"1f3cc-1f3fb-200d-2640-fe0f","🫱🏽‍🫲🏾":"1faf1-1f3fd-200d-1faf2-1f3fe","🏌️‍♀️":"1f3cc-fe0f-200d-2640-fe0f","🏌🏿‍♂️":"1f3cc-1f3ff-200d-2642-fe0f","🏌🏾‍♂️":"1f3cc-1f3fe-200d-2642-fe0f","🏌🏽‍♂️":"1f3cc-1f3fd-200d-2642-fe0f","🏌🏼‍♂️":"1f3cc-1f3fc-200d-2642-fe0f","🏌🏻‍♂️":"1f3cc-1f3fb-200d-2642-fe0f","🏌️‍♂️":"1f3cc-fe0f-200d-2642-fe0f","🧗🏿‍♀️":"1f9d7-1f3ff-200d-2640-fe0f","🧗🏾‍♀️":"1f9d7-1f3fe-200d-2640-fe0f","🧗🏽‍♀️":"1f9d7-1f3fd-200d-2640-fe0f","🧗🏼‍♀️":"1f9d7-1f3fc-200d-2640-fe0f","🧗🏻‍♀️":"1f9d7-1f3fb-200d-2640-fe0f","🧗🏿‍♂️":"1f9d7-1f3ff-200d-2642-fe0f","🧗🏾‍♂️":"1f9d7-1f3fe-200d-2642-fe0f","🧗🏽‍♂️":"1f9d7-1f3fd-200d-2642-fe0f","🧗🏼‍♂️":"1f9d7-1f3fc-200d-2642-fe0f","🧗🏻‍♂️":"1f9d7-1f3fb-200d-2642-fe0f","🧖🏿‍♀️":"1f9d6-1f3ff-200d-2640-fe0f","🧖🏾‍♀️":"1f9d6-1f3fe-200d-2640-fe0f","🧖🏽‍♀️":"1f9d6-1f3fd-200d-2640-fe0f","🫱🏽‍🫲🏼":"1faf1-1f3fd-200d-1faf2-1f3fc","🧖🏼‍♀️":"1f9d6-1f3fc-200d-2640-fe0f","🧖🏻‍♀️":"1f9d6-1f3fb-200d-2640-fe0f","🧖🏿‍♂️":"1f9d6-1f3ff-200d-2642-fe0f","🧖🏾‍♂️":"1f9d6-1f3fe-200d-2642-fe0f","🧖🏽‍♂️":"1f9d6-1f3fd-200d-2642-fe0f","🧖🏼‍♂️":"1f9d6-1f3fc-200d-2642-fe0f","🧖🏻‍♂️":"1f9d6-1f3fb-200d-2642-fe0f","🫱🏽‍🫲🏻":"1faf1-1f3fd-200d-1faf2-1f3fb","🫱🏼‍🫲🏿":"1faf1-1f3fc-200d-1faf2-1f3ff","🏃‍♂‍➡":"1f3c3-200d-2642-fe0f-200d-27a1-fe0f","🏃‍♀‍➡":"1f3c3-200d-2640-fe0f-200d-27a1-fe0f","🏃🏿‍➡️":"1f3c3-1f3ff-200d-27a1-fe0f","🏃🏾‍➡️":"1f3c3-1f3fe-200d-27a1-fe0f","🏃🏽‍➡️":"1f3c3-1f3fd-200d-27a1-fe0f","🏃🏼‍➡️":"1f3c3-1f3fc-200d-27a1-fe0f","🏃🏻‍➡️":"1f3c3-1f3fb-200d-27a1-fe0f","🏃🏿‍♀️":"1f3c3-1f3ff-200d-2640-fe0f","🏃🏾‍♀️":"1f3c3-1f3fe-200d-2640-fe0f","🏃🏽‍♀️":"1f3c3-1f3fd-200d-2640-fe0f","🏃🏼‍♀️":"1f3c3-1f3fc-200d-2640-fe0f","🏃🏻‍♀️":"1f3c3-1f3fb-200d-2640-fe0f","🏃🏿‍♂️":"1f3c3-1f3ff-200d-2642-fe0f","🏃🏾‍♂️":"1f3c3-1f3fe-200d-2642-fe0f","🏃🏽‍♂️":"1f3c3-1f3fd-200d-2642-fe0f","🏃🏼‍♂️":"1f3c3-1f3fc-200d-2642-fe0f","🏃🏻‍♂️":"1f3c3-1f3fb-200d-2642-fe0f","🫱🏼‍🫲🏾":"1faf1-1f3fc-200d-1faf2-1f3fe","👩‍🦽‍➡":"1f469-200d-1f9bd-200d-27a1-fe0f","👨‍🦽‍➡":"1f468-200d-1f9bd-200d-27a1-fe0f","🫱🏼‍🫲🏽":"1faf1-1f3fc-200d-1faf2-1f3fd","🧑‍🦽‍➡":"1f9d1-200d-1f9bd-200d-27a1-fe0f","👩‍🦼‍➡":"1f469-200d-1f9bc-200d-27a1-fe0f","👨‍🦼‍➡":"1f468-200d-1f9bc-200d-27a1-fe0f","🧑‍🦼‍➡":"1f9d1-200d-1f9bc-200d-27a1-fe0f","👩‍🦯‍➡":"1f469-200d-1f9af-200d-27a1-fe0f","👨‍🦯‍➡":"1f468-200d-1f9af-200d-27a1-fe0f","🧑‍🦯‍➡":"1f9d1-200d-1f9af-200d-27a1-fe0f","🧎‍♂‍➡":"1f9ce-200d-2642-fe0f-200d-27a1-fe0f","🧎‍♀‍➡":"1f9ce-200d-2640-fe0f-200d-27a1-fe0f","🧎🏿‍➡️":"1f9ce-1f3ff-200d-27a1-fe0f","🧎🏾‍➡️":"1f9ce-1f3fe-200d-27a1-fe0f","🧎🏽‍➡️":"1f9ce-1f3fd-200d-27a1-fe0f","🧎🏼‍➡️":"1f9ce-1f3fc-200d-27a1-fe0f","🧎🏻‍➡️":"1f9ce-1f3fb-200d-27a1-fe0f","🧎🏿‍♀️":"1f9ce-1f3ff-200d-2640-fe0f","🧎🏾‍♀️":"1f9ce-1f3fe-200d-2640-fe0f","🧎🏽‍♀️":"1f9ce-1f3fd-200d-2640-fe0f","🧎🏼‍♀️":"1f9ce-1f3fc-200d-2640-fe0f","🧎🏻‍♀️":"1f9ce-1f3fb-200d-2640-fe0f","🧎🏿‍♂️":"1f9ce-1f3ff-200d-2642-fe0f","🧎🏾‍♂️":"1f9ce-1f3fe-200d-2642-fe0f","🧎🏽‍♂️":"1f9ce-1f3fd-200d-2642-fe0f","🧎🏼‍♂️":"1f9ce-1f3fc-200d-2642-fe0f","🧎🏻‍♂️":"1f9ce-1f3fb-200d-2642-fe0f","🧍🏿‍♀️":"1f9cd-1f3ff-200d-2640-fe0f","🧍🏾‍♀️":"1f9cd-1f3fe-200d-2640-fe0f","🧍🏽‍♀️":"1f9cd-1f3fd-200d-2640-fe0f","🧍🏼‍♀️":"1f9cd-1f3fc-200d-2640-fe0f","🧍🏻‍♀️":"1f9cd-1f3fb-200d-2640-fe0f","🧍🏿‍♂️":"1f9cd-1f3ff-200d-2642-fe0f","🧍🏾‍♂️":"1f9cd-1f3fe-200d-2642-fe0f","🧍🏽‍♂️":"1f9cd-1f3fd-200d-2642-fe0f","🧍🏼‍♂️":"1f9cd-1f3fc-200d-2642-fe0f","🧍🏻‍♂️":"1f9cd-1f3fb-200d-2642-fe0f","🚶‍♂‍➡":"1f6b6-200d-2642-fe0f-200d-27a1-fe0f","🚶‍♀‍➡":"1f6b6-200d-2640-fe0f-200d-27a1-fe0f","🚶🏿‍➡️":"1f6b6-1f3ff-200d-27a1-fe0f","🚶🏾‍➡️":"1f6b6-1f3fe-200d-27a1-fe0f","🚶🏽‍➡️":"1f6b6-1f3fd-200d-27a1-fe0f","🚶🏼‍➡️":"1f6b6-1f3fc-200d-27a1-fe0f","🚶🏻‍➡️":"1f6b6-1f3fb-200d-27a1-fe0f","🚶🏿‍♀️":"1f6b6-1f3ff-200d-2640-fe0f","🚶🏾‍♀️":"1f6b6-1f3fe-200d-2640-fe0f","🚶🏽‍♀️":"1f6b6-1f3fd-200d-2640-fe0f","🚶🏼‍♀️":"1f6b6-1f3fc-200d-2640-fe0f","🚶🏻‍♀️":"1f6b6-1f3fb-200d-2640-fe0f","🚶🏿‍♂️":"1f6b6-1f3ff-200d-2642-fe0f","🚶🏾‍♂️":"1f6b6-1f3fe-200d-2642-fe0f","🚶🏽‍♂️":"1f6b6-1f3fd-200d-2642-fe0f","🚶🏼‍♂️":"1f6b6-1f3fc-200d-2642-fe0f","🚶🏻‍♂️":"1f6b6-1f3fb-200d-2642-fe0f","💇🏿‍♀️":"1f487-1f3ff-200d-2640-fe0f","💇🏾‍♀️":"1f487-1f3fe-200d-2640-fe0f","💇🏽‍♀️":"1f487-1f3fd-200d-2640-fe0f","💇🏼‍♀️":"1f487-1f3fc-200d-2640-fe0f","💇🏻‍♀️":"1f487-1f3fb-200d-2640-fe0f","💇🏿‍♂️":"1f487-1f3ff-200d-2642-fe0f","💇🏾‍♂️":"1f487-1f3fe-200d-2642-fe0f","💇🏽‍♂️":"1f487-1f3fd-200d-2642-fe0f","💇🏼‍♂️":"1f487-1f3fc-200d-2642-fe0f","💇🏻‍♂️":"1f487-1f3fb-200d-2642-fe0f","💆🏿‍♀️":"1f486-1f3ff-200d-2640-fe0f","💆🏾‍♀️":"1f486-1f3fe-200d-2640-fe0f","💆🏽‍♀️":"1f486-1f3fd-200d-2640-fe0f","💆🏼‍♀️":"1f486-1f3fc-200d-2640-fe0f","💆🏻‍♀️":"1f486-1f3fb-200d-2640-fe0f","💆🏿‍♂️":"1f486-1f3ff-200d-2642-fe0f","💆🏾‍♂️":"1f486-1f3fe-200d-2642-fe0f","💆🏽‍♂️":"1f486-1f3fd-200d-2642-fe0f","💆🏼‍♂️":"1f486-1f3fc-200d-2642-fe0f","💆🏻‍♂️":"1f486-1f3fb-200d-2642-fe0f","🧝🏿‍♀️":"1f9dd-1f3ff-200d-2640-fe0f","🧝🏾‍♀️":"1f9dd-1f3fe-200d-2640-fe0f","🧝🏽‍♀️":"1f9dd-1f3fd-200d-2640-fe0f","🧝🏼‍♀️":"1f9dd-1f3fc-200d-2640-fe0f","🧝🏻‍♀️":"1f9dd-1f3fb-200d-2640-fe0f","🧝🏿‍♂️":"1f9dd-1f3ff-200d-2642-fe0f","🧝🏾‍♂️":"1f9dd-1f3fe-200d-2642-fe0f","🧝🏽‍♂️":"1f9dd-1f3fd-200d-2642-fe0f","🧝🏼‍♂️":"1f9dd-1f3fc-200d-2642-fe0f","🧝🏻‍♂️":"1f9dd-1f3fb-200d-2642-fe0f","🧜🏿‍♀️":"1f9dc-1f3ff-200d-2640-fe0f","🧜🏾‍♀️":"1f9dc-1f3fe-200d-2640-fe0f","🧜🏽‍♀️":"1f9dc-1f3fd-200d-2640-fe0f","🧜🏼‍♀️":"1f9dc-1f3fc-200d-2640-fe0f","🧜🏻‍♀️":"1f9dc-1f3fb-200d-2640-fe0f","🧜🏿‍♂️":"1f9dc-1f3ff-200d-2642-fe0f","🧜🏾‍♂️":"1f9dc-1f3fe-200d-2642-fe0f","🧜🏽‍♂️":"1f9dc-1f3fd-200d-2642-fe0f","🧜🏼‍♂️":"1f9dc-1f3fc-200d-2642-fe0f","🧜🏻‍♂️":"1f9dc-1f3fb-200d-2642-fe0f","🧛🏿‍♀️":"1f9db-1f3ff-200d-2640-fe0f","🧛🏾‍♀️":"1f9db-1f3fe-200d-2640-fe0f","🧛🏽‍♀️":"1f9db-1f3fd-200d-2640-fe0f","🧛🏼‍♀️":"1f9db-1f3fc-200d-2640-fe0f","🧛🏻‍♀️":"1f9db-1f3fb-200d-2640-fe0f","🧛🏿‍♂️":"1f9db-1f3ff-200d-2642-fe0f","🧛🏾‍♂️":"1f9db-1f3fe-200d-2642-fe0f","🧛🏽‍♂️":"1f9db-1f3fd-200d-2642-fe0f","🧛🏼‍♂️":"1f9db-1f3fc-200d-2642-fe0f","🧛🏻‍♂️":"1f9db-1f3fb-200d-2642-fe0f","🧚🏿‍♀️":"1f9da-1f3ff-200d-2640-fe0f","🧚🏾‍♀️":"1f9da-1f3fe-200d-2640-fe0f","🧚🏽‍♀️":"1f9da-1f3fd-200d-2640-fe0f","🧚🏼‍♀️":"1f9da-1f3fc-200d-2640-fe0f","🧚🏻‍♀️":"1f9da-1f3fb-200d-2640-fe0f","🧚🏿‍♂️":"1f9da-1f3ff-200d-2642-fe0f","🧚🏾‍♂️":"1f9da-1f3fe-200d-2642-fe0f","🧚🏽‍♂️":"1f9da-1f3fd-200d-2642-fe0f","🧚🏼‍♂️":"1f9da-1f3fc-200d-2642-fe0f","🧚🏻‍♂️":"1f9da-1f3fb-200d-2642-fe0f","🧙🏿‍♀️":"1f9d9-1f3ff-200d-2640-fe0f","🧙🏾‍♀️":"1f9d9-1f3fe-200d-2640-fe0f","🧙🏽‍♀️":"1f9d9-1f3fd-200d-2640-fe0f","🧙🏼‍♀️":"1f9d9-1f3fc-200d-2640-fe0f","🧙🏻‍♀️":"1f9d9-1f3fb-200d-2640-fe0f","🧙🏿‍♂️":"1f9d9-1f3ff-200d-2642-fe0f","🧙🏾‍♂️":"1f9d9-1f3fe-200d-2642-fe0f","🧙🏽‍♂️":"1f9d9-1f3fd-200d-2642-fe0f","🧙🏼‍♂️":"1f9d9-1f3fc-200d-2642-fe0f","🧙🏻‍♂️":"1f9d9-1f3fb-200d-2642-fe0f","🦹🏿‍♀️":"1f9b9-1f3ff-200d-2640-fe0f","🦹🏾‍♀️":"1f9b9-1f3fe-200d-2640-fe0f","🦹🏽‍♀️":"1f9b9-1f3fd-200d-2640-fe0f","🦹🏼‍♀️":"1f9b9-1f3fc-200d-2640-fe0f","🦹🏻‍♀️":"1f9b9-1f3fb-200d-2640-fe0f","🦹🏿‍♂️":"1f9b9-1f3ff-200d-2642-fe0f","🦹🏾‍♂️":"1f9b9-1f3fe-200d-2642-fe0f","🦹🏽‍♂️":"1f9b9-1f3fd-200d-2642-fe0f","🦹🏼‍♂️":"1f9b9-1f3fc-200d-2642-fe0f","🦹🏻‍♂️":"1f9b9-1f3fb-200d-2642-fe0f","🦸🏿‍♀️":"1f9b8-1f3ff-200d-2640-fe0f","🦸🏾‍♀️":"1f9b8-1f3fe-200d-2640-fe0f","🦸🏽‍♀️":"1f9b8-1f3fd-200d-2640-fe0f","🏋🏼‍♂️":"1f3cb-1f3fc-200d-2642-fe0f","🦸🏻‍♀️":"1f9b8-1f3fb-200d-2640-fe0f","🦸🏿‍♂️":"1f9b8-1f3ff-200d-2642-fe0f","🦸🏾‍♂️":"1f9b8-1f3fe-200d-2642-fe0f","🦸🏽‍♂️":"1f9b8-1f3fd-200d-2642-fe0f","🦸🏼‍♂️":"1f9b8-1f3fc-200d-2642-fe0f","🦸🏻‍♂️":"1f9b8-1f3fb-200d-2642-fe0f","👰🏿‍♀️":"1f470-1f3ff-200d-2640-fe0f","👰🏾‍♀️":"1f470-1f3fe-200d-2640-fe0f","👰🏽‍♀️":"1f470-1f3fd-200d-2640-fe0f","👰🏼‍♀️":"1f470-1f3fc-200d-2640-fe0f","👰🏻‍♀️":"1f470-1f3fb-200d-2640-fe0f","👰🏿‍♂️":"1f470-1f3ff-200d-2642-fe0f","👰🏾‍♂️":"1f470-1f3fe-200d-2642-fe0f","👰🏽‍♂️":"1f470-1f3fd-200d-2642-fe0f","👰🏼‍♂️":"1f470-1f3fc-200d-2642-fe0f","👰🏻‍♂️":"1f470-1f3fb-200d-2642-fe0f","🤵🏿‍♀️":"1f935-1f3ff-200d-2640-fe0f","🤵🏾‍♀️":"1f935-1f3fe-200d-2640-fe0f","🤵🏽‍♀️":"1f935-1f3fd-200d-2640-fe0f","🤵🏼‍♀️":"1f935-1f3fc-200d-2640-fe0f","🤵🏻‍♀️":"1f935-1f3fb-200d-2640-fe0f","🤵🏿‍♂️":"1f935-1f3ff-200d-2642-fe0f","🤵🏾‍♂️":"1f935-1f3fe-200d-2642-fe0f","🤵🏽‍♂️":"1f935-1f3fd-200d-2642-fe0f","🤵🏼‍♂️":"1f935-1f3fc-200d-2642-fe0f","🤵🏻‍♂️":"1f935-1f3fb-200d-2642-fe0f","👳🏿‍♀️":"1f473-1f3ff-200d-2640-fe0f","👳🏾‍♀️":"1f473-1f3fe-200d-2640-fe0f","👳🏽‍♀️":"1f473-1f3fd-200d-2640-fe0f","👳🏼‍♀️":"1f473-1f3fc-200d-2640-fe0f","👳🏻‍♀️":"1f473-1f3fb-200d-2640-fe0f","👳🏿‍♂️":"1f473-1f3ff-200d-2642-fe0f","👳🏾‍♂️":"1f473-1f3fe-200d-2642-fe0f","👳🏽‍♂️":"1f473-1f3fd-200d-2642-fe0f","👳🏼‍♂️":"1f473-1f3fc-200d-2642-fe0f","👳🏻‍♂️":"1f473-1f3fb-200d-2642-fe0f","👷🏿‍♀️":"1f477-1f3ff-200d-2640-fe0f","👷🏾‍♀️":"1f477-1f3fe-200d-2640-fe0f","👷🏽‍♀️":"1f477-1f3fd-200d-2640-fe0f","👷🏼‍♀️":"1f477-1f3fc-200d-2640-fe0f","👷🏻‍♀️":"1f477-1f3fb-200d-2640-fe0f","👷🏿‍♂️":"1f477-1f3ff-200d-2642-fe0f","👷🏾‍♂️":"1f477-1f3fe-200d-2642-fe0f","👷🏽‍♂️":"1f477-1f3fd-200d-2642-fe0f","👷🏼‍♂️":"1f477-1f3fc-200d-2642-fe0f","👷🏻‍♂️":"1f477-1f3fb-200d-2642-fe0f","💂🏿‍♀️":"1f482-1f3ff-200d-2640-fe0f","💂🏾‍♀️":"1f482-1f3fe-200d-2640-fe0f","💂🏽‍♀️":"1f482-1f3fd-200d-2640-fe0f","💂🏼‍♀️":"1f482-1f3fc-200d-2640-fe0f","💂🏻‍♀️":"1f482-1f3fb-200d-2640-fe0f","💂🏿‍♂️":"1f482-1f3ff-200d-2642-fe0f","💂🏾‍♂️":"1f482-1f3fe-200d-2642-fe0f","💂🏽‍♂️":"1f482-1f3fd-200d-2642-fe0f","💂🏼‍♂️":"1f482-1f3fc-200d-2642-fe0f","💂🏻‍♂️":"1f482-1f3fb-200d-2642-fe0f","🕵🏿‍♀️":"1f575-1f3ff-200d-2640-fe0f","🕵🏾‍♀️":"1f575-1f3fe-200d-2640-fe0f","🕵🏽‍♀️":"1f575-1f3fd-200d-2640-fe0f","🕵🏼‍♀️":"1f575-1f3fc-200d-2640-fe0f","🕵🏻‍♀️":"1f575-1f3fb-200d-2640-fe0f","🕵️‍♀️":"1f575-fe0f-200d-2640-fe0f","🕵🏿‍♂️":"1f575-1f3ff-200d-2642-fe0f","🕵🏾‍♂️":"1f575-1f3fe-200d-2642-fe0f","🕵🏽‍♂️":"1f575-1f3fd-200d-2642-fe0f","🕵🏼‍♂️":"1f575-1f3fc-200d-2642-fe0f","🕵🏻‍♂️":"1f575-1f3fb-200d-2642-fe0f","🕵️‍♂️":"1f575-fe0f-200d-2642-fe0f","👮🏿‍♀️":"1f46e-1f3ff-200d-2640-fe0f","👮🏾‍♀️":"1f46e-1f3fe-200d-2640-fe0f","👮🏽‍♀️":"1f46e-1f3fd-200d-2640-fe0f","👮🏼‍♀️":"1f46e-1f3fc-200d-2640-fe0f","👮🏻‍♀️":"1f46e-1f3fb-200d-2640-fe0f","👮🏿‍♂️":"1f46e-1f3ff-200d-2642-fe0f","👮🏾‍♂️":"1f46e-1f3fe-200d-2642-fe0f","👮🏽‍♂️":"1f46e-1f3fd-200d-2642-fe0f","👮🏼‍♂️":"1f46e-1f3fc-200d-2642-fe0f","👮🏻‍♂️":"1f46e-1f3fb-200d-2642-fe0f","🫱🏼‍🫲🏻":"1faf1-1f3fc-200d-1faf2-1f3fb","👩🏿‍✈️":"1f469-1f3ff-200d-2708-fe0f","👩🏾‍✈️":"1f469-1f3fe-200d-2708-fe0f","👩🏽‍✈️":"1f469-1f3fd-200d-2708-fe0f","👩🏼‍✈️":"1f469-1f3fc-200d-2708-fe0f","👩🏻‍✈️":"1f469-1f3fb-200d-2708-fe0f","👨🏿‍✈️":"1f468-1f3ff-200d-2708-fe0f","👨🏾‍✈️":"1f468-1f3fe-200d-2708-fe0f","👨🏽‍✈️":"1f468-1f3fd-200d-2708-fe0f","👨🏼‍✈️":"1f468-1f3fc-200d-2708-fe0f","👨🏻‍✈️":"1f468-1f3fb-200d-2708-fe0f","🧑🏿‍✈️":"1f9d1-1f3ff-200d-2708-fe0f","🧑🏾‍✈️":"1f9d1-1f3fe-200d-2708-fe0f","🧑🏽‍✈️":"1f9d1-1f3fd-200d-2708-fe0f","🧑🏼‍✈️":"1f9d1-1f3fc-200d-2708-fe0f","🧑🏻‍✈️":"1f9d1-1f3fb-200d-2708-fe0f","👩🏿‍⚖️":"1f469-1f3ff-200d-2696-fe0f","👩🏾‍⚖️":"1f469-1f3fe-200d-2696-fe0f","👩🏽‍⚖️":"1f469-1f3fd-200d-2696-fe0f","👩🏼‍⚖️":"1f469-1f3fc-200d-2696-fe0f","👩🏻‍⚖️":"1f469-1f3fb-200d-2696-fe0f","👨🏿‍⚖️":"1f468-1f3ff-200d-2696-fe0f","👨🏾‍⚖️":"1f468-1f3fe-200d-2696-fe0f","👨🏽‍⚖️":"1f468-1f3fd-200d-2696-fe0f","👨🏼‍⚖️":"1f468-1f3fc-200d-2696-fe0f","👨🏻‍⚖️":"1f468-1f3fb-200d-2696-fe0f","🧑🏿‍⚖️":"1f9d1-1f3ff-200d-2696-fe0f","🧑🏾‍⚖️":"1f9d1-1f3fe-200d-2696-fe0f","🧑🏽‍⚖️":"1f9d1-1f3fd-200d-2696-fe0f","🧑🏼‍⚖️":"1f9d1-1f3fc-200d-2696-fe0f","🧑🏻‍⚖️":"1f9d1-1f3fb-200d-2696-fe0f","👩🏿‍⚕️":"1f469-1f3ff-200d-2695-fe0f","👩🏾‍⚕️":"1f469-1f3fe-200d-2695-fe0f","👩🏽‍⚕️":"1f469-1f3fd-200d-2695-fe0f","👩🏼‍⚕️":"1f469-1f3fc-200d-2695-fe0f","👩🏻‍⚕️":"1f469-1f3fb-200d-2695-fe0f","👨🏿‍⚕️":"1f468-1f3ff-200d-2695-fe0f","👨🏾‍⚕️":"1f468-1f3fe-200d-2695-fe0f","👨🏽‍⚕️":"1f468-1f3fd-200d-2695-fe0f","👨🏼‍⚕️":"1f468-1f3fc-200d-2695-fe0f","👨🏻‍⚕️":"1f468-1f3fb-200d-2695-fe0f","🫱🏻‍🫲🏿":"1faf1-1f3fb-200d-1faf2-1f3ff","🫱🏻‍🫲🏾":"1faf1-1f3fb-200d-1faf2-1f3fe","🫱🏻‍🫲🏽":"1faf1-1f3fb-200d-1faf2-1f3fd","🧑🏿‍⚕️":"1f9d1-1f3ff-200d-2695-fe0f","🫱🏻‍🫲🏼":"1faf1-1f3fb-200d-1faf2-1f3fc","🧑🏾‍⚕️":"1f9d1-1f3fe-200d-2695-fe0f","👁️‍🗨️":"1f441-200d-1f5e8","🧑🏽‍⚕️":"1f9d1-1f3fd-200d-2695-fe0f","🧑🏼‍⚕️":"1f9d1-1f3fc-200d-2695-fe0f","🧑🏻‍⚕️":"1f9d1-1f3fb-200d-2695-fe0f","🤷🏿‍♀️":"1f937-1f3ff-200d-2640-fe0f","🤷🏾‍♀️":"1f937-1f3fe-200d-2640-fe0f","🤷🏽‍♀️":"1f937-1f3fd-200d-2640-fe0f","🤷🏼‍♀️":"1f937-1f3fc-200d-2640-fe0f","🤷🏻‍♀️":"1f937-1f3fb-200d-2640-fe0f","🤷🏿‍♂️":"1f937-1f3ff-200d-2642-fe0f","🤷🏾‍♂️":"1f937-1f3fe-200d-2642-fe0f","🤷🏽‍♂️":"1f937-1f3fd-200d-2642-fe0f","🤷🏼‍♂️":"1f937-1f3fc-200d-2642-fe0f","🤷🏻‍♂️":"1f937-1f3fb-200d-2642-fe0f","🤦🏿‍♀️":"1f926-1f3ff-200d-2640-fe0f","🤦🏾‍♀️":"1f926-1f3fe-200d-2640-fe0f","🤦🏽‍♀️":"1f926-1f3fd-200d-2640-fe0f","🤦🏼‍♀️":"1f926-1f3fc-200d-2640-fe0f","🤦🏻‍♀️":"1f926-1f3fb-200d-2640-fe0f","🤦🏿‍♂️":"1f926-1f3ff-200d-2642-fe0f","🤦🏾‍♂️":"1f926-1f3fe-200d-2642-fe0f","🤦🏽‍♂️":"1f926-1f3fd-200d-2642-fe0f","🤦🏼‍♂️":"1f926-1f3fc-200d-2642-fe0f","🤦🏻‍♂️":"1f926-1f3fb-200d-2642-fe0f","🙇🏿‍♀️":"1f647-1f3ff-200d-2640-fe0f","🙇🏾‍♀️":"1f647-1f3fe-200d-2640-fe0f","🙇🏽‍♀️":"1f647-1f3fd-200d-2640-fe0f","🙇🏼‍♀️":"1f647-1f3fc-200d-2640-fe0f","🙇🏻‍♀️":"1f647-1f3fb-200d-2640-fe0f","🙇🏿‍♂️":"1f647-1f3ff-200d-2642-fe0f","🙇🏾‍♂️":"1f647-1f3fe-200d-2642-fe0f","🙇🏽‍♂️":"1f647-1f3fd-200d-2642-fe0f","🙇🏼‍♂️":"1f647-1f3fc-200d-2642-fe0f","🙇🏻‍♂️":"1f647-1f3fb-200d-2642-fe0f","🧏🏿‍♀️":"1f9cf-1f3ff-200d-2640-fe0f","🧏🏾‍♀️":"1f9cf-1f3fe-200d-2640-fe0f","🧏🏽‍♀️":"1f9cf-1f3fd-200d-2640-fe0f","🧏🏼‍♀️":"1f9cf-1f3fc-200d-2640-fe0f","🧏🏻‍♀️":"1f9cf-1f3fb-200d-2640-fe0f","🧏🏿‍♂️":"1f9cf-1f3ff-200d-2642-fe0f","🧏🏾‍♂️":"1f9cf-1f3fe-200d-2642-fe0f","🧏🏽‍♂️":"1f9cf-1f3fd-200d-2642-fe0f","🧏🏼‍♂️":"1f9cf-1f3fc-200d-2642-fe0f","🧏🏻‍♂️":"1f9cf-1f3fb-200d-2642-fe0f","🙋🏿‍♀️":"1f64b-1f3ff-200d-2640-fe0f","🙋🏾‍♀️":"1f64b-1f3fe-200d-2640-fe0f","🙋🏽‍♀️":"1f64b-1f3fd-200d-2640-fe0f","🏳️‍⚧️":"1f3f3-fe0f-200d-26a7-fe0f","🙋🏼‍♀️":"1f64b-1f3fc-200d-2640-fe0f","🙋🏻‍♀️":"1f64b-1f3fb-200d-2640-fe0f","🙋🏿‍♂️":"1f64b-1f3ff-200d-2642-fe0f","🙋🏾‍♂️":"1f64b-1f3fe-200d-2642-fe0f","🙋🏽‍♂️":"1f64b-1f3fd-200d-2642-fe0f","🙋🏼‍♂️":"1f64b-1f3fc-200d-2642-fe0f","🙋🏻‍♂️":"1f64b-1f3fb-200d-2642-fe0f","💁🏿‍♀️":"1f481-1f3ff-200d-2640-fe0f","💁🏾‍♀️":"1f481-1f3fe-200d-2640-fe0f","💁🏽‍♀️":"1f481-1f3fd-200d-2640-fe0f","💁🏼‍♀️":"1f481-1f3fc-200d-2640-fe0f","💁🏻‍♀️":"1f481-1f3fb-200d-2640-fe0f","💁🏿‍♂️":"1f481-1f3ff-200d-2642-fe0f","💁🏾‍♂️":"1f481-1f3fe-200d-2642-fe0f","💁🏽‍♂️":"1f481-1f3fd-200d-2642-fe0f","💁🏼‍♂️":"1f481-1f3fc-200d-2642-fe0f","💁🏻‍♂️":"1f481-1f3fb-200d-2642-fe0f","🙆🏿‍♀️":"1f646-1f3ff-200d-2640-fe0f","🙆🏾‍♀️":"1f646-1f3fe-200d-2640-fe0f","🙆🏽‍♀️":"1f646-1f3fd-200d-2640-fe0f","🙆🏼‍♀️":"1f646-1f3fc-200d-2640-fe0f","🙆🏻‍♀️":"1f646-1f3fb-200d-2640-fe0f","🙆🏿‍♂️":"1f646-1f3ff-200d-2642-fe0f","🙆🏾‍♂️":"1f646-1f3fe-200d-2642-fe0f","🙆🏽‍♂️":"1f646-1f3fd-200d-2642-fe0f","🙆🏼‍♂️":"1f646-1f3fc-200d-2642-fe0f","🙆🏻‍♂️":"1f646-1f3fb-200d-2642-fe0f","🙅🏿‍♀️":"1f645-1f3ff-200d-2640-fe0f","🙅🏾‍♀️":"1f645-1f3fe-200d-2640-fe0f","🙅🏽‍♀️":"1f645-1f3fd-200d-2640-fe0f","🙅🏼‍♀️":"1f645-1f3fc-200d-2640-fe0f","🙅🏻‍♀️":"1f645-1f3fb-200d-2640-fe0f","🙅🏿‍♂️":"1f645-1f3ff-200d-2642-fe0f","🙅🏾‍♂️":"1f645-1f3fe-200d-2642-fe0f","🙅🏽‍♂️":"1f645-1f3fd-200d-2642-fe0f","🙅🏼‍♂️":"1f645-1f3fc-200d-2642-fe0f","🙅🏻‍♂️":"1f645-1f3fb-200d-2642-fe0f","🙎🏿‍♀️":"1f64e-1f3ff-200d-2640-fe0f","🙎🏾‍♀️":"1f64e-1f3fe-200d-2640-fe0f","🙎🏽‍♀️":"1f64e-1f3fd-200d-2640-fe0f","🙎🏼‍♀️":"1f64e-1f3fc-200d-2640-fe0f","🙎🏻‍♀️":"1f64e-1f3fb-200d-2640-fe0f","🙎🏿‍♂️":"1f64e-1f3ff-200d-2642-fe0f","🙎🏾‍♂️":"1f64e-1f3fe-200d-2642-fe0f","🙎🏽‍♂️":"1f64e-1f3fd-200d-2642-fe0f","🙎🏼‍♂️":"1f64e-1f3fc-200d-2642-fe0f","🙎🏻‍♂️":"1f64e-1f3fb-200d-2642-fe0f","🙍🏿‍♀️":"1f64d-1f3ff-200d-2640-fe0f","🙍🏾‍♀️":"1f64d-1f3fe-200d-2640-fe0f","🙍🏽‍♀️":"1f64d-1f3fd-200d-2640-fe0f","🙍🏼‍♀️":"1f64d-1f3fc-200d-2640-fe0f","🙍🏻‍♀️":"1f64d-1f3fb-200d-2640-fe0f","🙍🏿‍♂️":"1f64d-1f3ff-200d-2642-fe0f","🙍🏾‍♂️":"1f64d-1f3fe-200d-2642-fe0f","🙍🏽‍♂️":"1f64d-1f3fd-200d-2642-fe0f","🙍🏼‍♂️":"1f64d-1f3fc-200d-2642-fe0f","🙍🏻‍♂️":"1f64d-1f3fb-200d-2642-fe0f","👱🏿‍♂️":"1f471-1f3ff-200d-2642-fe0f","👱🏾‍♂️":"1f471-1f3fe-200d-2642-fe0f","👱🏽‍♂️":"1f471-1f3fd-200d-2642-fe0f","👱🏼‍♂️":"1f471-1f3fc-200d-2642-fe0f","👱🏻‍♂️":"1f471-1f3fb-200d-2642-fe0f","👱🏿‍♀️":"1f471-1f3ff-200d-2640-fe0f","👱🏾‍♀️":"1f471-1f3fe-200d-2640-fe0f","👱🏽‍♀️":"1f471-1f3fd-200d-2640-fe0f","👱🏼‍♀️":"1f471-1f3fc-200d-2640-fe0f","👱🏻‍♀️":"1f471-1f3fb-200d-2640-fe0f","🧔🏿‍♀️":"1f9d4-1f3ff-200d-2640-fe0f","🧔🏾‍♀️":"1f9d4-1f3fe-200d-2640-fe0f","🧔🏽‍♀️":"1f9d4-1f3fd-200d-2640-fe0f","🧔🏼‍♀️":"1f9d4-1f3fc-200d-2640-fe0f","🏋️‍♀️":"1f3cb-fe0f-200d-2640-fe0f","🏋🏻‍♀️":"1f3cb-1f3fb-200d-2640-fe0f","🏋🏼‍♀️":"1f3cb-1f3fc-200d-2640-fe0f","🏋🏽‍♀️":"1f3cb-1f3fd-200d-2640-fe0f","🏋🏾‍♀️":"1f3cb-1f3fe-200d-2640-fe0f","🏋🏿‍♀️":"1f3cb-1f3ff-200d-2640-fe0f","🏋🏿‍♂️":"1f3cb-1f3ff-200d-2642-fe0f","🏋🏾‍♂️":"1f3cb-1f3fe-200d-2642-fe0f","🏋🏽‍♂️":"1f3cb-1f3fd-200d-2642-fe0f","🧔🏻‍♀️":"1f9d4-1f3fb-200d-2640-fe0f","🚴🏻‍♂️":"1f6b4-1f3fb-200d-2642-fe0f","🚴🏼‍♂️":"1f6b4-1f3fc-200d-2642-fe0f","🚴🏽‍♂️":"1f6b4-1f3fd-200d-2642-fe0f","🚴🏾‍♂️":"1f6b4-1f3fe-200d-2642-fe0f","🚴🏿‍♂️":"1f6b4-1f3ff-200d-2642-fe0f","🚴🏻‍♀️":"1f6b4-1f3fb-200d-2640-fe0f","🚴🏼‍♀️":"1f6b4-1f3fc-200d-2640-fe0f","🚴🏽‍♀️":"1f6b4-1f3fd-200d-2640-fe0f","🚴🏾‍♀️":"1f6b4-1f3fe-200d-2640-fe0f","🚴🏿‍♀️":"1f6b4-1f3ff-200d-2640-fe0f","🚶‍♂️‍➡":"1f6b6-200d-2642-fe0f-200d-27a1-fe0f","🧎🏿‍♀‍➡":"1f9ce-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","👨‍🦽‍➡️":"1f468-200d-1f9bd-200d-27a1-fe0f","🧑🏿‍🦽‍➡":"1f9d1-1f3ff-200d-1f9bd-200d-27a1-fe0f","🏃🏻‍♂‍➡":"1f3c3-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🏃🏿‍♀‍➡":"1f3c3-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🧑🏾‍🦽‍➡":"1f9d1-1f3fe-200d-1f9bd-200d-27a1-fe0f","👨🏻‍🦯‍➡":"1f468-1f3fb-200d-1f9af-200d-27a1-fe0f","🧑🏽‍🦽‍➡":"1f9d1-1f3fd-200d-1f9bd-200d-27a1-fe0f","🏃🏼‍♀‍➡":"1f3c3-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🧎🏾‍♂‍➡":"1f9ce-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🧑🏼‍🦽‍➡":"1f9d1-1f3fc-200d-1f9bd-200d-27a1-fe0f","🧎🏼‍♀‍➡":"1f9ce-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","👩🏽‍🦯‍➡":"1f469-1f3fd-200d-1f9af-200d-27a1-fe0f","🧑🏼‍🦯‍➡":"1f9d1-1f3fc-200d-1f9af-200d-27a1-fe0f","🧑🏻‍🦽‍➡":"1f9d1-1f3fb-200d-1f9bd-200d-27a1-fe0f","🏃‍♂‍➡️":"1f3c3-200d-2642-fe0f-200d-27a1-fe0f","🧎‍♂‍➡️":"1f9ce-200d-2642-fe0f-200d-27a1-fe0f","🏃🏼‍♂‍➡":"1f3c3-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🧑‍🦽‍➡️":"1f9d1-200d-1f9bd-200d-27a1-fe0f","👩🏿‍🦼‍➡":"1f469-1f3ff-200d-1f9bc-200d-27a1-fe0f","🏃‍♀‍➡️":"1f3c3-200d-2640-fe0f-200d-27a1-fe0f","🧎‍♂️‍➡":"1f9ce-200d-2642-fe0f-200d-27a1-fe0f","🧎🏿‍♂‍➡":"1f9ce-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","👩🏾‍🦼‍➡":"1f469-1f3fe-200d-1f9bc-200d-27a1-fe0f","👨‍🦯‍➡️":"1f468-200d-1f9af-200d-27a1-fe0f","🏃🏿‍♂‍➡":"1f3c3-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","🧑‍🦯‍➡️":"1f9d1-200d-1f9af-200d-27a1-fe0f","👩🏽‍🦼‍➡":"1f469-1f3fd-200d-1f9bc-200d-27a1-fe0f","🏃‍♂️‍➡":"1f3c3-200d-2642-fe0f-200d-27a1-fe0f","🧎‍♀‍➡️":"1f9ce-200d-2640-fe0f-200d-27a1-fe0f","👩🏼‍🦼‍➡":"1f469-1f3fc-200d-1f9bc-200d-27a1-fe0f","🧎‍♀️‍➡":"1f9ce-200d-2640-fe0f-200d-27a1-fe0f","👩🏻‍🦼‍➡":"1f469-1f3fb-200d-1f9bc-200d-27a1-fe0f","🧑🏿‍🦯‍➡":"1f9d1-1f3ff-200d-1f9af-200d-27a1-fe0f","🧑🏻‍🦯‍➡":"1f9d1-1f3fb-200d-1f9af-200d-27a1-fe0f","👩🏿‍🦽‍➡":"1f469-1f3ff-200d-1f9bd-200d-27a1-fe0f","👩‍🦼‍➡️":"1f469-200d-1f9bc-200d-27a1-fe0f","👨🏿‍🦼‍➡":"1f468-1f3ff-200d-1f9bc-200d-27a1-fe0f","🏃🏽‍♀‍➡":"1f3c3-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","👨🏾‍🦼‍➡":"1f468-1f3fe-200d-1f9bc-200d-27a1-fe0f","👩🏼‍🦯‍➡":"1f469-1f3fc-200d-1f9af-200d-27a1-fe0f","🚶🏽‍♂‍➡":"1f6b6-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","👨🏽‍🦼‍➡":"1f468-1f3fd-200d-1f9bc-200d-27a1-fe0f","🧎🏻‍♂‍➡":"1f9ce-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","👨🏼‍🦼‍➡":"1f468-1f3fc-200d-1f9bc-200d-27a1-fe0f","👨🏽‍🦯‍➡":"1f468-1f3fd-200d-1f9af-200d-27a1-fe0f","👩‍❤️‍👩":"1f469-200d-2764-fe0f-200d-1f469","🧎🏽‍♀‍➡":"1f9ce-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🚶🏾‍♂‍➡":"1f6b6-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","👨🏻‍🦼‍➡":"1f468-1f3fb-200d-1f9bc-200d-27a1-fe0f","👩🏾‍🦽‍➡":"1f469-1f3fe-200d-1f9bd-200d-27a1-fe0f","👨‍🦼‍➡️":"1f468-200d-1f9bc-200d-27a1-fe0f","🧑🏿‍🦼‍➡":"1f9d1-1f3ff-200d-1f9bc-200d-27a1-fe0f","🚶‍♀️‍➡":"1f6b6-200d-2640-fe0f-200d-27a1-fe0f","🚶‍♀‍➡️":"1f6b6-200d-2640-fe0f-200d-27a1-fe0f","👩🏻‍🦯‍➡":"1f469-1f3fb-200d-1f9af-200d-27a1-fe0f","🏃🏽‍♂‍➡":"1f3c3-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🧑🏾‍🦯‍➡":"1f9d1-1f3fe-200d-1f9af-200d-27a1-fe0f","🏃‍♀️‍➡":"1f3c3-200d-2640-fe0f-200d-27a1-fe0f","🧑🏾‍🦼‍➡":"1f9d1-1f3fe-200d-1f9bc-200d-27a1-fe0f","👨‍❤️‍👨":"1f468-200d-2764-fe0f-200d-1f468","🧎🏼‍♂‍➡":"1f9ce-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🚶🏻‍♀‍➡":"1f6b6-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","👨🏿‍🦯‍➡":"1f468-1f3ff-200d-1f9af-200d-27a1-fe0f","👩‍❤️‍👨":"1f469-200d-2764-fe0f-200d-1f468","🧑🏽‍🦼‍➡":"1f9d1-1f3fd-200d-1f9bc-200d-27a1-fe0f","👩🏽‍🦽‍➡":"1f469-1f3fd-200d-1f9bd-200d-27a1-fe0f","🧎🏻‍♀‍➡":"1f9ce-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","👩🏾‍🦯‍➡":"1f469-1f3fe-200d-1f9af-200d-27a1-fe0f","🚶🏼‍♀‍➡":"1f6b6-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","👩🏼‍🦽‍➡":"1f469-1f3fc-200d-1f9bd-200d-27a1-fe0f","🧎🏾‍♀‍➡":"1f9ce-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","👨🏼‍🦯‍➡":"1f468-1f3fc-200d-1f9af-200d-27a1-fe0f","👩🏻‍🦽‍➡":"1f469-1f3fb-200d-1f9bd-200d-27a1-fe0f","🧑🏼‍🦼‍➡":"1f9d1-1f3fc-200d-1f9bc-200d-27a1-fe0f","🚶🏼‍♂‍➡":"1f6b6-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","👩‍🦽‍➡️":"1f469-200d-1f9bd-200d-27a1-fe0f","🚶🏽‍♀‍➡":"1f6b6-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🧑🏽‍🦯‍➡":"1f9d1-1f3fd-200d-1f9af-200d-27a1-fe0f","👨🏿‍🦽‍➡":"1f468-1f3ff-200d-1f9bd-200d-27a1-fe0f","🧎🏽‍♂‍➡":"1f9ce-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🧑🏻‍🦼‍➡":"1f9d1-1f3fb-200d-1f9bc-200d-27a1-fe0f","👨🏾‍🦯‍➡":"1f468-1f3fe-200d-1f9af-200d-27a1-fe0f","👨🏾‍🦽‍➡":"1f468-1f3fe-200d-1f9bd-200d-27a1-fe0f","🚶🏾‍♀‍➡":"1f6b6-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","👩‍🦯‍➡️":"1f469-200d-1f9af-200d-27a1-fe0f","👨🏽‍🦽‍➡":"1f468-1f3fd-200d-1f9bd-200d-27a1-fe0f","🏃🏾‍♀‍➡":"1f3c3-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","🚶🏻‍♂‍➡":"1f6b6-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","👨🏼‍🦽‍➡":"1f468-1f3fc-200d-1f9bd-200d-27a1-fe0f","🧑‍🦼‍➡️":"1f9d1-200d-1f9bc-200d-27a1-fe0f","🚶🏿‍♀‍➡":"1f6b6-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🚶🏿‍♂‍➡":"1f6b6-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","🏃🏾‍♂‍➡":"1f3c3-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","👩🏿‍🦯‍➡":"1f469-1f3ff-200d-1f9af-200d-27a1-fe0f","🚶‍♂‍➡️":"1f6b6-200d-2642-fe0f-200d-27a1-fe0f","👨🏻‍🦽‍➡":"1f468-1f3fb-200d-1f9bd-200d-27a1-fe0f","🏃🏻‍♀‍➡":"1f3c3-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🏃🏾‍♂‍➡️":"1f3c3-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","👨🏾‍❤‍👨🏾":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe","👩🏻‍🤝‍👨🏾":"1f469-1f3fb-200d-1f91d-200d-1f468-1f3fe","👨🏾‍❤‍👨🏽":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fd","🏃🏾‍♂️‍➡":"1f3c3-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","👨🏾‍❤‍👨🏼":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fc","👩🏽‍🤝‍👩🏾":"1f469-1f3fd-200d-1f91d-200d-1f469-1f3fe","👨🏾‍❤‍👨🏻":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fb","👨🏽‍❤‍👨🏿":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3ff","👨🏽‍❤‍👨🏾":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fe","👨🏽‍❤‍👨🏽":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd","🏃🏿‍♂‍➡️":"1f3c3-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","👨🏽‍❤‍👨🏼":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fc","🏃🏼‍♀️‍➡":"1f3c3-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","👨🏽‍❤‍👨🏻":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fb","🏃🏿‍♂️‍➡":"1f3c3-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","👨🏼‍❤‍👨🏿":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3ff","👩🏽‍🤝‍👩🏼":"1f469-1f3fd-200d-1f91d-200d-1f469-1f3fc","👨🏼‍❤‍👨🏾":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fe","👨🏼‍❤‍👨🏽":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fd","🏃🏼‍♀‍➡️":"1f3c3-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","👨🏼‍❤‍👨🏼":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc","🧑‍🧑‍🧒‍🧒":"1f9d1-200d-1f9d1-200d-1f9d2-200d-1f9d2","👨🏼‍❤‍👨🏻":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fb","👩🏽‍🤝‍👩🏻":"1f469-1f3fd-200d-1f91d-200d-1f469-1f3fb","👨🏻‍❤‍👨🏿":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3ff","👩‍👩‍👧‍👧":"1f469-200d-1f469-200d-1f467-200d-1f467","👨🏻‍❤‍👨🏾":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fe","👩‍👩‍👦‍👦":"1f469-200d-1f469-200d-1f466-200d-1f466","👨🏻‍❤‍👨🏽":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fd","👩‍👩‍👧‍👦":"1f469-200d-1f469-200d-1f467-200d-1f466","👨🏻‍❤‍👨🏼":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fc","👨‍👨‍👧‍👧":"1f468-200d-1f468-200d-1f467-200d-1f467","👨🏻‍❤‍👨🏻":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb","👨‍👨‍👦‍👦":"1f468-200d-1f468-200d-1f466-200d-1f466","👩🏿‍❤‍👨🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff","👨‍👨‍👧‍👦":"1f468-200d-1f468-200d-1f467-200d-1f466","👩🏿‍❤‍👨🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fe","👨‍👩‍👧‍👧":"1f468-200d-1f469-200d-1f467-200d-1f467","👩🏿‍❤‍👨🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fd","👨‍👩‍👦‍👦":"1f468-200d-1f469-200d-1f466-200d-1f466","👩🏿‍❤‍👨🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fc","👨‍👩‍👧‍👦":"1f468-200d-1f469-200d-1f467-200d-1f466","👩🏿‍❤‍👨🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fb","👩🏿‍❤‍👩🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3ff","👩🏾‍❤‍👨🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3ff","👩🏾‍❤‍👨🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe","👩🏿‍❤‍👩🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fe","👩🏾‍❤‍👨🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fd","🏃‍♂️‍➡️":"1f3c3-200d-2642-fe0f-200d-27a1-fe0f","👩🏾‍❤‍👨🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fc","👩🏿‍❤‍👩🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fd","👩🏾‍❤‍👨🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fb","👩🏿‍🤝‍👩🏼":"1f469-1f3ff-200d-1f91d-200d-1f469-1f3fc","👩🏽‍❤‍👨🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3ff","👩🏿‍❤‍👩🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fc","👩🏽‍❤‍👨🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fe","👩🏽‍❤‍👨🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd","👩🏿‍❤‍👩🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fb","👩🏽‍❤‍👨🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fc","🏃🏾‍♀️‍➡":"1f3c3-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","👩🏽‍❤‍👨🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fb","👩🏾‍❤‍👩🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3ff","👩🏼‍❤‍👨🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3ff","👩🏼‍❤‍👨🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fe","👩🏾‍❤‍👩🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fe","👩🏼‍❤‍👨🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fd","👩🏿‍🤝‍👩🏻":"1f469-1f3ff-200d-1f91d-200d-1f469-1f3fb","👩🏼‍❤‍👨🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc","👩🏾‍❤‍👩🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fd","👩🏼‍❤‍👨🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fb","👩🏿‍🤝‍👩🏾":"1f469-1f3ff-200d-1f91d-200d-1f469-1f3fe","👩🏻‍❤‍👨🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3ff","👩🏾‍❤‍👩🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fc","👩🏻‍❤‍👨🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fe","🏃🏾‍♀‍➡️":"1f3c3-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","👩🏻‍❤‍👨🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fd","👩🏾‍❤‍👩🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fb","👩🏻‍❤‍👨🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fc","🏃🏻‍♂‍➡️":"1f3c3-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","👩🏻‍❤‍👨🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb","👩🏽‍❤‍👩🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3ff","🧑🏿‍❤‍🧑🏾":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fe","🏃🏻‍♂️‍➡":"1f3c3-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🧑🏿‍❤‍🧑🏽":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fd","👩🏽‍❤‍👩🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fe","🧑🏿‍❤‍🧑🏼":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fc","👩🏾‍🤝‍👩🏿":"1f469-1f3fe-200d-1f91d-200d-1f469-1f3ff","🧑🏿‍❤‍🧑🏻":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fb","👩🏻‍🤝‍👨🏿":"1f469-1f3fb-200d-1f91d-200d-1f468-1f3ff","🧑🏾‍❤‍🧑🏿":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3ff","👩🏼‍🤝‍👩🏿":"1f469-1f3fc-200d-1f91d-200d-1f469-1f3ff","🧑🏾‍❤‍🧑🏽":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fd","👩🏽‍❤‍👩🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fd","🧑🏾‍❤‍🧑🏼":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fc","🧑🏾‍❤‍🧑🏻":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fb","🧑🏽‍❤‍🧑🏿":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3ff","👩🏼‍🤝‍👩🏾":"1f469-1f3fc-200d-1f91d-200d-1f469-1f3fe","🧑🏽‍❤‍🧑🏾":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fe","👩🏽‍❤‍👩🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fc","🧑🏽‍❤‍🧑🏼":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fc","👩‍❤‍💋‍👨":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f468","🧑🏽‍❤‍🧑🏻":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fb","👩🏻‍🤝‍👨🏼":"1f469-1f3fb-200d-1f91d-200d-1f468-1f3fc","🧑🏼‍🤝‍🧑🏻":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fb","🧑🏼‍❤‍🧑🏿":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3ff","👩🏼‍🤝‍👩🏽":"1f469-1f3fc-200d-1f91d-200d-1f469-1f3fd","🏃‍♀️‍➡️":"1f3c3-200d-2640-fe0f-200d-27a1-fe0f","🧑🏼‍❤‍🧑🏾":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fe","👩🏻‍🤝‍👩🏽":"1f469-1f3fb-200d-1f91d-200d-1f469-1f3fd","👩🏽‍❤‍👩🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fb","👩🏼‍🤝‍👨🏾":"1f469-1f3fc-200d-1f91d-200d-1f468-1f3fe","🧑🏼‍❤‍🧑🏽":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fd","🏃🏿‍♀‍➡️":"1f3c3-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🧑🏻‍🤝‍🧑🏽":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fd","🧑🏼‍❤‍🧑🏻":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fb","👩🏻‍🤝‍👩🏼":"1f469-1f3fb-200d-1f91d-200d-1f469-1f3fc","🧑🏻‍❤‍🧑🏿":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3ff","👩🏿‍🦽‍➡️":"1f469-1f3ff-200d-1f9bd-200d-27a1-fe0f","👩🏼‍❤‍👩🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3ff","🧑🏽‍🤝‍🧑🏼":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fc","🧑🏻‍❤‍🧑🏾":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fe","👩🏼‍🤝‍👨🏻":"1f469-1f3fc-200d-1f91d-200d-1f468-1f3fb","🧑🏻‍❤‍🧑🏽":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fd","🧑🏿‍🤝‍🧑🏿":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3ff","🏃🏼‍♂‍➡️":"1f3c3-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","👩🏼‍🤝‍👨🏿":"1f469-1f3fc-200d-1f91d-200d-1f468-1f3ff","🧑🏻‍❤‍🧑🏼":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fc","👩🏾‍🦽‍➡️":"1f469-1f3fe-200d-1f9bd-200d-27a1-fe0f","👩🏼‍🤝‍👩🏻":"1f469-1f3fc-200d-1f91d-200d-1f469-1f3fb","🧑🏻‍🤝‍🧑🏿":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3ff","👩🏽‍🤝‍👨🏻":"1f469-1f3fd-200d-1f91d-200d-1f468-1f3fb","👩🏽‍🦽‍➡️":"1f469-1f3fd-200d-1f9bd-200d-27a1-fe0f","👩🏽‍🤝‍👨🏼":"1f469-1f3fd-200d-1f91d-200d-1f468-1f3fc","👩🏼‍🦽‍➡️":"1f469-1f3fc-200d-1f9bd-200d-27a1-fe0f","👩🏻‍🦽‍➡️":"1f469-1f3fb-200d-1f9bd-200d-27a1-fe0f","👩🏽‍🤝‍👨🏾":"1f469-1f3fd-200d-1f91d-200d-1f468-1f3fe","👨🏿‍🦽‍➡️":"1f468-1f3ff-200d-1f9bd-200d-27a1-fe0f","👩🏽‍🤝‍👨🏿":"1f469-1f3fd-200d-1f91d-200d-1f468-1f3ff","👨🏾‍🦽‍➡️":"1f468-1f3fe-200d-1f9bd-200d-27a1-fe0f","👩🏾‍🤝‍👨🏻":"1f469-1f3fe-200d-1f91d-200d-1f468-1f3fb","👨🏽‍🦽‍➡️":"1f468-1f3fd-200d-1f9bd-200d-27a1-fe0f","👩🏾‍🤝‍👨🏼":"1f469-1f3fe-200d-1f91d-200d-1f468-1f3fc","👨🏼‍🦽‍➡️":"1f468-1f3fc-200d-1f9bd-200d-27a1-fe0f","👨🏻‍🦽‍➡️":"1f468-1f3fb-200d-1f9bd-200d-27a1-fe0f","👩🏾‍🤝‍👨🏽":"1f469-1f3fe-200d-1f91d-200d-1f468-1f3fd","🧑🏿‍🦽‍➡️":"1f9d1-1f3ff-200d-1f9bd-200d-27a1-fe0f","👩🏾‍🤝‍👨🏿":"1f469-1f3fe-200d-1f91d-200d-1f468-1f3ff","🧑🏾‍🦽‍➡️":"1f9d1-1f3fe-200d-1f9bd-200d-27a1-fe0f","👩🏿‍🤝‍👨🏻":"1f469-1f3ff-200d-1f91d-200d-1f468-1f3fb","🧑🏽‍🦽‍➡️":"1f9d1-1f3fd-200d-1f9bd-200d-27a1-fe0f","🧑🏿‍🤝‍🧑🏾":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fe","👩🏿‍🤝‍👨🏼":"1f469-1f3ff-200d-1f91d-200d-1f468-1f3fc","🧑🏼‍🦽‍➡️":"1f9d1-1f3fc-200d-1f9bd-200d-27a1-fe0f","🧑🏿‍🤝‍🧑🏽":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fd","👩🏿‍🤝‍👨🏽":"1f469-1f3ff-200d-1f91d-200d-1f468-1f3fd","🧑🏻‍🦽‍➡️":"1f9d1-1f3fb-200d-1f9bd-200d-27a1-fe0f","🧑🏿‍🤝‍🧑🏼":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fc","👩🏿‍🤝‍👨🏾":"1f469-1f3ff-200d-1f91d-200d-1f468-1f3fe","👩🏿‍🦼‍➡️":"1f469-1f3ff-200d-1f9bc-200d-27a1-fe0f","🧑🏽‍🤝‍🧑🏻":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fb","👩🏾‍🦼‍➡️":"1f469-1f3fe-200d-1f9bc-200d-27a1-fe0f","👨🏻‍🤝‍👨🏼":"1f468-1f3fb-200d-1f91d-200d-1f468-1f3fc","👩🏽‍🦼‍➡️":"1f469-1f3fd-200d-1f9bc-200d-27a1-fe0f","🧑🏿‍🤝‍🧑🏻":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fb","👩🏼‍🦼‍➡️":"1f469-1f3fc-200d-1f9bc-200d-27a1-fe0f","🧑🏻‍🤝‍🧑🏻":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fb","👩🏻‍🦼‍➡️":"1f469-1f3fb-200d-1f9bc-200d-27a1-fe0f","🧑🏾‍🤝‍🧑🏿":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3ff","👨🏿‍🦼‍➡️":"1f468-1f3ff-200d-1f9bc-200d-27a1-fe0f","👨🏾‍🦼‍➡️":"1f468-1f3fe-200d-1f9bc-200d-27a1-fe0f","🧑🏼‍🤝‍🧑🏿":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3ff","👨🏽‍🦼‍➡️":"1f468-1f3fd-200d-1f9bc-200d-27a1-fe0f","👩‍❤‍💋‍👩":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f469","🧑🏾‍🤝‍🧑🏾":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fe","👨🏻‍🤝‍👨🏾":"1f468-1f3fb-200d-1f91d-200d-1f468-1f3fe","👨🏼‍🦼‍➡️":"1f468-1f3fc-200d-1f9bc-200d-27a1-fe0f","👨🏻‍🦼‍➡️":"1f468-1f3fb-200d-1f9bc-200d-27a1-fe0f","🚶‍♀️‍➡️":"1f6b6-200d-2640-fe0f-200d-27a1-fe0f","🏃🏻‍♀️‍➡":"1f3c3-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🧑🏿‍🦼‍➡️":"1f9d1-1f3ff-200d-1f9bc-200d-27a1-fe0f","🚶🏻‍♀‍➡️":"1f6b6-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🚶🏻‍♀️‍➡":"1f6b6-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🧑🏾‍🦼‍➡️":"1f9d1-1f3fe-200d-1f9bc-200d-27a1-fe0f","🧑🏻‍🤝‍🧑🏾":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fe","👩🏾‍🤝‍👩🏽":"1f469-1f3fe-200d-1f91d-200d-1f469-1f3fd","🚶🏼‍♀‍➡️":"1f6b6-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🚶🏼‍♀️‍➡":"1f6b6-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","👨🏻‍🤝‍👨🏽":"1f468-1f3fb-200d-1f91d-200d-1f468-1f3fd","🧑🏽‍🦼‍➡️":"1f9d1-1f3fd-200d-1f9bc-200d-27a1-fe0f","👩🏻‍🤝‍👩🏿":"1f469-1f3fb-200d-1f91d-200d-1f469-1f3ff","🧑🏾‍🤝‍🧑🏽":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fd","🚶🏽‍♀‍➡️":"1f6b6-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🚶🏽‍♀️‍➡":"1f6b6-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🏴󠁧󠁢󠁷󠁬󠁳󠁿":"1f3f4-e0067-e0062-e0077-e006c-e0073-e007f","👩🏼‍❤‍👩🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fd","🧑🏼‍🦼‍➡️":"1f9d1-1f3fc-200d-1f9bc-200d-27a1-fe0f","🚶🏾‍♀‍➡️":"1f6b6-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","🚶🏾‍♀️‍➡":"1f6b6-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","🧑🏼‍🤝‍🧑🏾":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fe","🧑🏻‍🦼‍➡️":"1f9d1-1f3fb-200d-1f9bc-200d-27a1-fe0f","🏃🏼‍♂️‍➡":"1f3c3-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🏴󠁧󠁢󠁳󠁣󠁴󠁿":"1f3f4-e0067-e0062-e0073-e0063-e0074-e007f","🚶🏿‍♀‍➡️":"1f6b6-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🚶🏿‍♀️‍➡":"1f6b6-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🧑🏾‍🤝‍🧑🏼":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fc","🚶‍♂️‍➡️":"1f6b6-200d-2642-fe0f-200d-27a1-fe0f","👩🏿‍🦯‍➡️":"1f469-1f3ff-200d-1f9af-200d-27a1-fe0f","👩🏼‍❤‍👩🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fe","🏃🏻‍♀‍➡️":"1f3c3-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🚶🏻‍♂‍➡️":"1f6b6-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🧑🏻‍🤝‍🧑🏼":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fc","🚶🏻‍♂️‍➡":"1f6b6-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🏴󠁧󠁢󠁥󠁮󠁧󠁿":"1f3f4-e0067-e0062-e0065-e006e-e0067-e007f","👩🏼‍❤‍👩🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fc","👩🏾‍🦯‍➡️":"1f469-1f3fe-200d-1f9af-200d-27a1-fe0f","🚶🏼‍♂‍➡️":"1f6b6-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🚶🏼‍♂️‍➡":"1f6b6-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","👩🏽‍🦯‍➡️":"1f469-1f3fd-200d-1f9af-200d-27a1-fe0f","🧑🏽‍🤝‍🧑🏽":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fd","👨🏻‍🤝‍👨🏿":"1f468-1f3fb-200d-1f91d-200d-1f468-1f3ff","🚶🏽‍♂‍➡️":"1f6b6-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🧑🏾‍🤝‍🧑🏻":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fb","🚶🏽‍♂️‍➡":"1f6b6-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","👩🏼‍🦯‍➡️":"1f469-1f3fc-200d-1f9af-200d-27a1-fe0f","🏃🏽‍♀️‍➡":"1f3c3-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🚶🏾‍♂‍➡️":"1f6b6-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","👨🏼‍🤝‍👨🏻":"1f468-1f3fc-200d-1f91d-200d-1f468-1f3fb","🚶🏾‍♂️‍➡":"1f6b6-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🧑🏼‍🤝‍🧑🏽":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fd","👩🏻‍🦯‍➡️":"1f469-1f3fb-200d-1f9af-200d-27a1-fe0f","🚶🏿‍♂‍➡️":"1f6b6-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","🚶🏿‍♂️‍➡":"1f6b6-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","🧎‍♀️‍➡️":"1f9ce-200d-2640-fe0f-200d-27a1-fe0f","👩🏼‍❤‍👩🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fb","👨🏼‍🤝‍👨🏽":"1f468-1f3fc-200d-1f91d-200d-1f468-1f3fd","🧎🏻‍♀‍➡️":"1f9ce-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🧑🏽‍🤝‍🧑🏿":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3ff","🧎🏻‍♀️‍➡":"1f9ce-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","👨🏿‍🦯‍➡️":"1f468-1f3ff-200d-1f9af-200d-27a1-fe0f","👩🏻‍🤝‍👩🏾":"1f469-1f3fb-200d-1f91d-200d-1f469-1f3fe","🧎🏼‍♀‍➡️":"1f9ce-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🧎🏼‍♀️‍➡":"1f9ce-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🧑🏼‍🤝‍🧑🏼":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fc","👨🏼‍🤝‍👨🏾":"1f468-1f3fc-200d-1f91d-200d-1f468-1f3fe","👨🏾‍🦯‍➡️":"1f468-1f3fe-200d-1f9af-200d-27a1-fe0f","🧎🏽‍♀‍➡️":"1f9ce-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","👨‍❤‍💋‍👨":"1f468-200d-2764-fe0f-200d-1f48b-200d-1f468","🧎🏽‍♀️‍➡":"1f9ce-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","👩🏼‍🤝‍👨🏽":"1f469-1f3fc-200d-1f91d-200d-1f468-1f3fd","🧑🏽‍🤝‍🧑🏾":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fe","🧎🏾‍♀‍➡️":"1f9ce-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","👨🏽‍🦯‍➡️":"1f468-1f3fd-200d-1f9af-200d-27a1-fe0f","🧎🏾‍♀️‍➡":"1f9ce-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","👨🏼‍🤝‍👨🏿":"1f468-1f3fc-200d-1f91d-200d-1f468-1f3ff","👩🏻‍❤‍👩🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3ff","👨🏼‍🦯‍➡️":"1f468-1f3fc-200d-1f9af-200d-27a1-fe0f","🧎🏿‍♀‍➡️":"1f9ce-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🧎🏿‍♀️‍➡":"1f9ce-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","👨🏽‍🤝‍👨🏻":"1f468-1f3fd-200d-1f91d-200d-1f468-1f3fb","🧎‍♂️‍➡️":"1f9ce-200d-2642-fe0f-200d-27a1-fe0f","👨🏻‍🦯‍➡️":"1f468-1f3fb-200d-1f9af-200d-27a1-fe0f","👨🏽‍🤝‍👨🏼":"1f468-1f3fd-200d-1f91d-200d-1f468-1f3fc","👩🏾‍🤝‍👩🏼":"1f469-1f3fe-200d-1f91d-200d-1f469-1f3fc","🧎🏻‍♂‍➡️":"1f9ce-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🧎🏻‍♂️‍➡":"1f9ce-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🧑🏿‍🦯‍➡️":"1f9d1-1f3ff-200d-1f9af-200d-27a1-fe0f","👨🏽‍🤝‍👨🏾":"1f468-1f3fd-200d-1f91d-200d-1f468-1f3fe","👩🏻‍❤‍👩🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fe","👨🏿‍🤝‍👨🏾":"1f468-1f3ff-200d-1f91d-200d-1f468-1f3fe","🧎🏼‍♂‍➡️":"1f9ce-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🧎🏼‍♂️‍➡":"1f9ce-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","👨🏿‍🤝‍👨🏽":"1f468-1f3ff-200d-1f91d-200d-1f468-1f3fd","👩🏿‍🤝‍👩🏽":"1f469-1f3ff-200d-1f91d-200d-1f469-1f3fd","🧑🏾‍🦯‍➡️":"1f9d1-1f3fe-200d-1f9af-200d-27a1-fe0f","🧎🏽‍♂‍➡️":"1f9ce-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","👨🏿‍🤝‍👨🏼":"1f468-1f3ff-200d-1f91d-200d-1f468-1f3fc","🧎🏽‍♂️‍➡":"1f9ce-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","👨🏿‍🤝‍👨🏻":"1f468-1f3ff-200d-1f91d-200d-1f468-1f3fb","👩🏻‍❤‍👩🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fd","🧎🏾‍♂‍➡️":"1f9ce-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","👨🏽‍🤝‍👨🏿":"1f468-1f3fd-200d-1f91d-200d-1f468-1f3ff","🧎🏾‍♂️‍➡":"1f9ce-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🧑🏽‍🦯‍➡️":"1f9d1-1f3fd-200d-1f9af-200d-27a1-fe0f","👨🏾‍🤝‍👨🏿":"1f468-1f3fe-200d-1f91d-200d-1f468-1f3ff","🏃🏽‍♂‍➡️":"1f3c3-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🧎🏿‍♂‍➡️":"1f9ce-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","👨🏾‍🤝‍👨🏽":"1f468-1f3fe-200d-1f91d-200d-1f468-1f3fd","🧎🏿‍♂️‍➡":"1f9ce-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","🧑🏼‍🦯‍➡️":"1f9d1-1f3fc-200d-1f9af-200d-27a1-fe0f","👨🏾‍🤝‍👨🏼":"1f468-1f3fe-200d-1f91d-200d-1f468-1f3fc","🧑🏻‍🦯‍➡️":"1f9d1-1f3fb-200d-1f9af-200d-27a1-fe0f","👨🏾‍🤝‍👨🏻":"1f468-1f3fe-200d-1f91d-200d-1f468-1f3fb","👩🏻‍❤‍👩🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fc","👩🏻‍🤝‍👨🏽":"1f469-1f3fb-200d-1f91d-200d-1f468-1f3fd","👩🏻‍❤‍👩🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fb","🏃🏽‍♂️‍➡":"1f3c3-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","👨🏿‍❤‍👨🏿":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff","👩🏾‍🤝‍👩🏻":"1f469-1f3fe-200d-1f91d-200d-1f469-1f3fb","👨🏿‍❤‍👨🏾":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fe","👨🏿‍❤‍👨🏽":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fd","🏃🏽‍♀‍➡️":"1f3c3-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","👨🏿‍❤‍👨🏼":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fc","🏃🏿‍♀️‍➡":"1f3c3-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","👨🏿‍❤‍👨🏻":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fb","👩🏽‍🤝‍👩🏿":"1f469-1f3fd-200d-1f91d-200d-1f469-1f3ff","👨🏾‍❤‍👨🏿":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3ff","🧑🏻‍❤️‍🧑🏿":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3ff","🏃🏻‍♂️‍➡️":"1f3c3-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🏃🏼‍♂️‍➡️":"1f3c3-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🏃🏽‍♂️‍➡️":"1f3c3-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🏃🏾‍♂️‍➡️":"1f3c3-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🏃🏿‍♂️‍➡️":"1f3c3-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","👩🏿‍❤️‍👩🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3ff","👩🏿‍❤️‍👩🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fe","👩🏿‍❤️‍👩🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fd","👩🏿‍❤️‍👩🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fc","👩🏿‍❤️‍👩🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fb","👩🏾‍❤️‍👩🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3ff","👩🏾‍❤️‍👩🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fe","👩🏾‍❤️‍👩🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fd","👩🏾‍❤️‍👩🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fc","👩🏾‍❤️‍👩🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fb","👩🏽‍❤️‍👩🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3ff","👩🏽‍❤️‍👩🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fe","👩🏽‍❤️‍👩🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fd","👩🏽‍❤️‍👩🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fc","👩🏽‍❤️‍👩🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fb","👩‍❤️‍💋‍👨":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f468","👩🏼‍❤️‍👩🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3ff","👩🏼‍❤️‍👩🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fe","👩🏼‍❤️‍👩🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fd","👩🏼‍❤️‍👩🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fc","👩🏼‍❤️‍👩🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fb","👩🏻‍❤️‍👩🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3ff","👩🏻‍❤️‍👩🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fe","👩🏻‍❤️‍👩🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fd","👩🏻‍❤️‍👩🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fc","👩🏻‍❤️‍👩🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fb","👨🏿‍❤️‍👨🏿":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff","👨🏿‍❤️‍👨🏾":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fe","👨🏿‍❤️‍👨🏽":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fd","👨🏿‍❤️‍👨🏼":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fc","👨🏿‍❤️‍👨🏻":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fb","👨🏾‍❤️‍👨🏿":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3ff","👨🏾‍❤️‍👨🏾":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe","👨🏾‍❤️‍👨🏽":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fd","👨🏾‍❤️‍👨🏼":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fc","👨🏾‍❤️‍👨🏻":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fb","👨🏽‍❤️‍👨🏿":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3ff","👨🏽‍❤️‍👨🏾":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fe","👨🏽‍❤️‍👨🏽":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd","👨🏽‍❤️‍👨🏼":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fc","👨🏽‍❤️‍👨🏻":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fb","👨🏼‍❤️‍👨🏿":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3ff","👨🏼‍❤️‍👨🏾":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fe","👨🏼‍❤️‍👨🏽":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fd","👨🏼‍❤️‍👨🏼":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc","👨🏼‍❤️‍👨🏻":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fb","👨🏻‍❤️‍👨🏿":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3ff","👨🏻‍❤️‍👨🏾":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fe","👨🏻‍❤️‍👨🏽":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fd","👨🏻‍❤️‍👨🏼":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fc","👨🏻‍❤️‍👨🏻":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb","👩🏿‍❤️‍👨🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff","👩🏿‍❤️‍👨🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fe","👩🏿‍❤️‍👨🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fd","👩🏿‍❤️‍👨🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fc","👩🏿‍❤️‍👨🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fb","👩🏾‍❤️‍👨🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3ff","👩🏾‍❤️‍👨🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe","👩🏾‍❤️‍👨🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fd","👩🏾‍❤️‍👨🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fc","👩🏾‍❤️‍👨🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fb","👩🏽‍❤️‍👨🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3ff","👩🏽‍❤️‍👨🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fe","👩🏽‍❤️‍👨🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd","👩🏽‍❤️‍👨🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fc","👩🏽‍❤️‍👨🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fb","👩🏼‍❤️‍👨🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3ff","👩🏼‍❤️‍👨🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fe","👩🏼‍❤️‍👨🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fd","👩🏼‍❤️‍👨🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc","👩🏼‍❤️‍👨🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fb","👩🏻‍❤️‍👨🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3ff","👩🏻‍❤️‍👨🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fe","👩🏻‍❤️‍👨🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fd","👩🏻‍❤️‍👨🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fc","👩🏻‍❤️‍👨🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb","🧑🏿‍❤️‍🧑🏾":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fe","🧑🏿‍❤️‍🧑🏽":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fd","🧑🏿‍❤️‍🧑🏼":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fc","🧑🏿‍❤️‍🧑🏻":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fb","🧑🏾‍❤️‍🧑🏿":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3ff","🧑🏾‍❤️‍🧑🏽":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fd","🧑🏾‍❤️‍🧑🏼":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fc","🧑🏾‍❤️‍🧑🏻":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fb","🧑🏽‍❤️‍🧑🏿":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3ff","🧑🏽‍❤️‍🧑🏾":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fe","🧑🏽‍❤️‍🧑🏼":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fc","🧑🏽‍❤️‍🧑🏻":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fb","🧑🏼‍❤️‍🧑🏿":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3ff","🧑🏼‍❤️‍🧑🏾":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fe","🧑🏼‍❤️‍🧑🏽":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fd","🧑🏼‍❤️‍🧑🏻":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fb","🏃🏿‍♀️‍➡️":"1f3c3-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🧑🏻‍❤️‍🧑🏾":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fe","🧑🏻‍❤️‍🧑🏽":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fd","🧑🏻‍❤️‍🧑🏼":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fc","👩‍❤️‍💋‍👩":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f469","🚶🏻‍♀️‍➡️":"1f6b6-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🚶🏼‍♀️‍➡️":"1f6b6-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🚶🏽‍♀️‍➡️":"1f6b6-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🚶🏾‍♀️‍➡️":"1f6b6-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","🚶🏿‍♀️‍➡️":"1f6b6-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🚶🏻‍♂️‍➡️":"1f6b6-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🚶🏼‍♂️‍➡️":"1f6b6-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🚶🏽‍♂️‍➡️":"1f6b6-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🚶🏾‍♂️‍➡️":"1f6b6-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🚶🏿‍♂️‍➡️":"1f6b6-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","🧎🏻‍♀️‍➡️":"1f9ce-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🧎🏼‍♀️‍➡️":"1f9ce-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🧎🏽‍♀️‍➡️":"1f9ce-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🧎🏾‍♀️‍➡️":"1f9ce-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","🧎🏿‍♀️‍➡️":"1f9ce-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🧎🏻‍♂️‍➡️":"1f9ce-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🧎🏼‍♂️‍➡️":"1f9ce-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🧎🏽‍♂️‍➡️":"1f9ce-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🧎🏾‍♂️‍➡️":"1f9ce-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🧎🏿‍♂️‍➡️":"1f9ce-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","👨‍❤️‍💋‍👨":"1f468-200d-2764-fe0f-200d-1f48b-200d-1f468","🏃🏻‍♀️‍➡️":"1f3c3-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🏃🏼‍♀️‍➡️":"1f3c3-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🏃🏽‍♀️‍➡️":"1f3c3-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🏃🏾‍♀️‍➡️":"1f3c3-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","👨🏻‍❤‍💋‍👨🏿":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏻‍❤‍💋‍👨🏻":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏻‍❤‍💋‍👨🏼":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏼‍❤‍💋‍👩🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏾‍❤‍💋‍👨🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","🧑🏾‍❤‍💋‍🧑🏻":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","👨🏾‍❤‍💋‍👨🏾":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏼‍❤‍💋‍👩🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👨🏼‍❤‍💋‍👨🏼":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏿‍❤‍💋‍👨🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏾‍❤‍💋‍👨🏿":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏼‍❤‍💋‍👩🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏿‍❤‍💋‍👨🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏾‍❤‍💋‍👨🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏽‍❤‍💋‍👩🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👨🏽‍❤‍💋‍👨🏽":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏽‍❤‍💋‍👩🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","🧑🏼‍❤‍💋‍🧑🏾":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","👨🏿‍❤‍💋‍👨🏻":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏾‍❤‍💋‍👨🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏽‍❤‍💋‍👩🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","🧑🏻‍❤‍💋‍🧑🏽":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","👩🏽‍❤‍💋‍👩🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","🧑🏻‍❤‍💋‍🧑🏿":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","👨🏽‍❤‍💋‍👨🏾":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏽‍❤‍💋‍👩🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👨🏿‍❤‍💋‍👨🏼":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏾‍❤‍💋‍👩🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏾‍❤‍💋‍👨🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏿‍❤‍💋‍👨🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏾‍❤‍💋‍👩🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👨🏼‍❤‍💋‍👨🏽":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","🧑🏼‍❤‍💋‍🧑🏿":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","👩🏾‍❤‍💋‍👩🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👨🏿‍❤‍💋‍👨🏽":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏾‍❤‍💋‍👩🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👨🏼‍❤‍💋‍👨🏿":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏽‍❤‍💋‍👨🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏾‍❤‍💋‍👩🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👨🏿‍❤‍💋‍👨🏾":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏿‍❤‍💋‍👩🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👨🏽‍❤‍💋‍👨🏿":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏼‍❤‍💋‍👨🏻":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏽‍❤‍💋‍👨🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏿‍❤‍💋‍👩🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👨🏿‍❤‍💋‍👨🏿":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏿‍❤‍💋‍👩🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏿‍❤‍💋‍👨🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏻‍❤‍💋‍👩🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏿‍❤‍💋‍👩🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","🧑🏽‍❤‍💋‍🧑🏻":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","👩🏿‍❤‍💋‍👩🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏽‍❤‍💋‍👨🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","🧑🏾‍❤‍💋‍🧑🏼":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","🧑🏻‍❤‍💋‍🧑🏼":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","🧑🏾‍❤‍💋‍🧑🏽":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","👩🏻‍❤‍💋‍👩🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👨🏾‍❤‍💋‍👨🏻":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","🧑🏾‍❤‍💋‍🧑🏿":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","🧑🏼‍❤‍💋‍🧑🏻":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","🧑🏿‍❤‍💋‍🧑🏻":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","👩🏽‍❤‍💋‍👨🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏻‍❤‍💋‍👩🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","🧑🏿‍❤‍💋‍🧑🏼":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","🧑🏽‍❤‍💋‍🧑🏼":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","👨🏽‍❤‍💋‍👨🏻":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏼‍❤‍💋‍👨🏾":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","🧑🏿‍❤‍💋‍🧑🏽":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","👩🏽‍❤‍💋‍👨🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏻‍❤‍💋‍👩🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","🧑🏿‍❤‍💋‍🧑🏾":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","🧑🏽‍❤‍💋‍🧑🏾":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","🧑🏻‍❤‍💋‍🧑🏾":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","👩🏼‍❤‍💋‍👨🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏻‍❤‍💋‍👨🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏾‍❤‍💋‍👨🏼":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏼‍❤‍💋‍👨🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏻‍❤‍💋‍👨🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏻‍❤‍💋‍👩🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏻‍❤‍💋‍👨🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏼‍❤‍💋‍👨🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏽‍❤‍💋‍👨🏼":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏾‍❤‍💋‍👨🏽":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏻‍❤‍💋‍👨🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏾‍❤‍💋‍👨🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏼‍❤‍💋‍👨🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏻‍❤‍💋‍👨🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","🧑🏽‍❤‍💋‍🧑🏿":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","👩🏼‍❤‍💋‍👩🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏼‍❤‍💋‍👨🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","🧑🏼‍❤‍💋‍🧑🏽":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","👨🏻‍❤‍💋‍👨🏾":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏻‍❤‍💋‍👨🏽":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏿‍❤‍💋‍👨🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏼‍❤‍💋‍👩🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏻‍❤️‍💋‍👨🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏻‍❤️‍💋‍👨🏽":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏼‍❤️‍💋‍👨🏼":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏼‍❤️‍💋‍👨🏽":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","🧑🏻‍❤️‍💋‍🧑🏼":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","👨🏻‍❤️‍💋‍👨🏼":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏼‍❤️‍💋‍👨🏾":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","🧑🏻‍❤️‍💋‍🧑🏽":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","👨🏼‍❤️‍💋‍👨🏿":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏻‍❤️‍💋‍👨🏻":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","🧑🏻‍❤️‍💋‍🧑🏾":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","👨🏽‍❤️‍💋‍👨🏻":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏽‍❤️‍💋‍👨🏼":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏽‍❤️‍💋‍👨🏽":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏼‍❤️‍💋‍👨🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","🧑🏻‍❤️‍💋‍🧑🏿":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","👨🏽‍❤️‍💋‍👨🏾":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏿‍❤️‍💋‍👨🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏽‍❤️‍💋‍👨🏿":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","🧑🏼‍❤️‍💋‍🧑🏻":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","👨🏾‍❤️‍💋‍👨🏻":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏿‍❤️‍💋‍👨🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏾‍❤️‍💋‍👨🏼":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","🧑🏼‍❤️‍💋‍🧑🏽":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","👨🏾‍❤️‍💋‍👨🏽":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏾‍❤️‍💋‍👨🏾":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏿‍❤️‍💋‍👨🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","🧑🏼‍❤️‍💋‍🧑🏾":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","👨🏾‍❤️‍💋‍👨🏿":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏿‍❤️‍💋‍👨🏻":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏿‍❤️‍💋‍👨🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏿‍❤️‍💋‍👨🏼":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","🧑🏼‍❤️‍💋‍🧑🏿":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","👨🏿‍❤️‍💋‍👨🏽":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏿‍❤️‍💋‍👨🏾":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","🧑🏽‍❤️‍💋‍🧑🏻":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","👨🏿‍❤️‍💋‍👨🏿":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏻‍❤️‍💋‍👩🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏻‍❤️‍💋‍👩🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","🧑🏽‍❤️‍💋‍🧑🏼":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","👩🏻‍❤️‍💋‍👩🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏻‍❤️‍💋‍👩🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","🧑🏽‍❤️‍💋‍🧑🏾":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","👩🏻‍❤️‍💋‍👩🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","🧑🏽‍❤️‍💋‍🧑🏿":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","👩🏿‍❤️‍💋‍👨🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏼‍❤️‍💋‍👩🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏼‍❤️‍💋‍👩🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏾‍❤️‍💋‍👨🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","🧑🏾‍❤️‍💋‍🧑🏻":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","👩🏼‍❤️‍💋‍👩🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏼‍❤️‍💋‍👩🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏾‍❤️‍💋‍👨🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","🧑🏾‍❤️‍💋‍🧑🏼":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","👩🏼‍❤️‍💋‍👩🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏽‍❤️‍💋‍👩🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏽‍❤️‍💋‍👩🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏾‍❤️‍💋‍👨🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏽‍❤️‍💋‍👩🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏽‍❤️‍💋‍👩🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏽‍❤️‍💋‍👩🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏾‍❤️‍💋‍👨🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏾‍❤️‍💋‍👩🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏾‍❤️‍💋‍👩🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏾‍❤️‍💋‍👩🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏾‍❤️‍💋‍👨🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏾‍❤️‍💋‍👩🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏾‍❤️‍💋‍👩🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏿‍❤️‍💋‍👩🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏽‍❤️‍💋‍👨🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏿‍❤️‍💋‍👩🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏿‍❤️‍💋‍👩🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏿‍❤️‍💋‍👩🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏽‍❤️‍💋‍👨🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏿‍❤️‍💋‍👩🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","🧑🏾‍❤️‍💋‍🧑🏽":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","🧑🏾‍❤️‍💋‍🧑🏿":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","👩🏽‍❤️‍💋‍👨🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","🧑🏿‍❤️‍💋‍🧑🏻":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","🧑🏿‍❤️‍💋‍🧑🏼":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","🧑🏿‍❤️‍💋‍🧑🏽":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","👩🏽‍❤️‍💋‍👨🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏼‍❤️‍💋‍👨🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","🧑🏿‍❤️‍💋‍🧑🏾":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","👨🏼‍❤️‍💋‍👨🏻":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏽‍❤️‍💋‍👨🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏻‍❤️‍💋‍👨🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏻‍❤️‍💋‍👨🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏼‍❤️‍💋‍👨🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏻‍❤️‍💋‍👨🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏻‍❤️‍💋‍👨🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏼‍❤️‍💋‍👨🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏼‍❤️‍💋‍👨🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏻‍❤️‍💋‍👨🏾":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏻‍❤️‍💋‍👨🏿":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff"} \ No newline at end of file diff --git a/app/javascript/flavours/glitch/features/emoji/emoji_mart_data_light.ts b/app/javascript/flavours/glitch/features/emoji/emoji_mart_data_light.ts index 1315518179b889..59d995e25fff4f 100644 --- a/app/javascript/flavours/glitch/features/emoji/emoji_mart_data_light.ts +++ b/app/javascript/flavours/glitch/features/emoji/emoji_mart_data_light.ts @@ -9,7 +9,7 @@ import type { ShortCodesToEmojiData, } from 'virtual:mastodon-emoji-compressed'; -import { unicodeToUnifiedName } from './unicode_to_unified_name'; +import { unicodeToUnifiedName } from './unicode_utils'; type Emojis = Record< NonNullable, @@ -23,7 +23,7 @@ type Emojis = Record< const [ shortCodesToEmojiData, - skins, + _skins, categories, short_names, _emojisWithoutShortCodes, @@ -47,4 +47,4 @@ Object.keys(shortCodesToEmojiData).forEach((shortCode) => { }; }); -export { emojis, skins, categories, short_names }; +export { emojis, categories, short_names }; diff --git a/app/javascript/flavours/glitch/features/emoji/emoji_mart_search_light.js b/app/javascript/flavours/glitch/features/emoji/emoji_mart_search_light.js index 83e154b0b287e5..038dd120c9a5a8 100644 --- a/app/javascript/flavours/glitch/features/emoji/emoji_mart_search_light.js +++ b/app/javascript/flavours/glitch/features/emoji/emoji_mart_search_light.js @@ -1,7 +1,7 @@ // This code is largely borrowed from: // https://github.com/missive/emoji-mart/blob/5f2ffcc/src/utils/emoji-index.js -import * as data from './emoji_mart_data_light'; +import { emojis, categories } from './emoji_mart_data_light'; import { getData, getSanitizedData, uniq, intersect } from './emoji_utils'; let originalPool = {}; @@ -10,8 +10,8 @@ let emojisList = {}; let emoticonsList = {}; let customEmojisList = []; -for (let emoji in data.emojis) { - let emojiData = data.emojis[emoji]; +for (let emoji in emojis) { + let emojiData = emojis[emoji]; let { short_names, emoticons } = emojiData; let id = short_names[0]; @@ -84,14 +84,14 @@ function search(value, { emojisToShowFilter, maxResults, include, exclude, custo if (include.length || exclude.length) { pool = {}; - data.categories.forEach(category => { + categories.forEach(category => { let isIncluded = include && include.length ? include.indexOf(category.name.toLowerCase()) > -1 : true; let isExcluded = exclude && exclude.length ? exclude.indexOf(category.name.toLowerCase()) > -1 : false; if (!isIncluded || isExcluded) { return; } - category.emojis.forEach(emojiId => pool[emojiId] = data.emojis[emojiId]); + category.emojis.forEach(emojiId => pool[emojiId] = emojis[emojiId]); }); if (custom.length) { @@ -171,7 +171,7 @@ function search(value, { emojisToShowFilter, maxResults, include, exclude, custo if (results) { if (emojisToShowFilter) { - results = results.filter((result) => emojisToShowFilter(data.emojis[result.id])); + results = results.filter((result) => emojisToShowFilter(emojis[result.id])); } if (results && results.length > maxResults) { diff --git a/app/javascript/flavours/glitch/features/emoji/emoji_picker.tsx b/app/javascript/flavours/glitch/features/emoji/emoji_picker.tsx index 8ae001e148d284..0c215285d31670 100644 --- a/app/javascript/flavours/glitch/features/emoji/emoji_picker.tsx +++ b/app/javascript/flavours/glitch/features/emoji/emoji_picker.tsx @@ -4,9 +4,11 @@ import PickerRaw from 'emoji-mart/dist-es/components/picker/nimble-picker'; import { assetHost } from 'flavours/glitch/utils/config'; +import { EMOJI_MODE_NATIVE } from './constants'; import EmojiData from './emoji_data.json'; +import { useEmojiAppState } from './mode'; -const backgroundImageFnDefault = () => `${assetHost}/emoji/sheet_15_1.png`; +const backgroundImageFnDefault = () => `${assetHost}/emoji/sheet_16_0.png`; const Emoji = ({ set = 'twitter', @@ -16,6 +18,7 @@ const Emoji = ({ backgroundImageFn = backgroundImageFnDefault, ...props }: EmojiProps) => { + const { mode } = useEmojiAppState(); return ( @@ -37,6 +41,7 @@ const Picker = ({ backgroundImageFn = backgroundImageFnDefault, ...props }: PickerProps) => { + const { mode } = useEmojiAppState(); return ( ); diff --git a/app/javascript/flavours/glitch/features/emoji/emoji_unicode_mapping_light.ts b/app/javascript/flavours/glitch/features/emoji/emoji_unicode_mapping_light.ts index a53496be2a8e2d..ecf36e3ea85ceb 100644 --- a/app/javascript/flavours/glitch/features/emoji/emoji_unicode_mapping_light.ts +++ b/app/javascript/flavours/glitch/features/emoji/emoji_unicode_mapping_light.ts @@ -8,7 +8,7 @@ import type { ShortCodesToEmojiDataKey, } from 'virtual:mastodon-emoji-compressed'; -import { unicodeToFilename } from './unicode_to_filename'; +import { unicodeToFilename } from './unicode_utils'; type UnicodeMapping = Record< FilenameData[number][0], diff --git a/app/javascript/flavours/glitch/features/emoji/emoji_utils.js b/app/javascript/flavours/glitch/features/emoji/emoji_utils.js index c13d25056706a4..75b2acafa5c853 100644 --- a/app/javascript/flavours/glitch/features/emoji/emoji_utils.js +++ b/app/javascript/flavours/glitch/features/emoji/emoji_utils.js @@ -209,50 +209,9 @@ function intersect(a, b) { return uniqA.filter(item => uniqB.indexOf(item) >= 0); } -function deepMerge(a, b) { - let o = {}; - - for (let key in a) { - let originalValue = a[key], - value = originalValue; - - if (Object.hasOwn(b, key)) { - value = b[key]; - } - - if (typeof value === 'object') { - value = deepMerge(originalValue, value); - } - - o[key] = value; - } - - return o; -} - -// https://github.com/sonicdoe/measure-scrollbar -function measureScrollbar() { - const div = document.createElement('div'); - - div.style.width = '100px'; - div.style.height = '100px'; - div.style.overflow = 'scroll'; - div.style.position = 'absolute'; - div.style.top = '-9999px'; - - document.body.appendChild(div); - const scrollbarWidth = div.offsetWidth - div.clientWidth; - document.body.removeChild(div); - - return scrollbarWidth; -} - export { getData, getSanitizedData, uniq, intersect, - deepMerge, - unifiedToNative, - measureScrollbar, }; diff --git a/app/javascript/flavours/glitch/features/emoji/handlers.ts b/app/javascript/flavours/glitch/features/emoji/handlers.ts deleted file mode 100644 index dbfe194fa4157a..00000000000000 --- a/app/javascript/flavours/glitch/features/emoji/handlers.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { autoPlayGif } from '@/flavours/glitch/initial_state'; - -const PARENT_MAX_DEPTH = 10; - -export function handleAnimateGif(event: MouseEvent) { - // We already check this in ui/index.jsx, but just to be sure. - if (autoPlayGif) { - return; - } - - const { target, type } = event; - const animate = type === 'mouseover'; // Mouse over = animate, mouse out = don't animate. - - if (target instanceof HTMLImageElement) { - setAnimateGif(target, animate); - } else if (!(target instanceof HTMLElement) || target === document.body) { - return; - } - - let parent: HTMLElement | null = null; - let iter = 0; - - if (target.classList.contains('animate-parent')) { - parent = target; - } else { - // Iterate up to PARENT_MAX_DEPTH levels up the DOM tree to find a parent with the class 'animate-parent'. - let current: HTMLElement | null = target; - while (current) { - if (iter >= PARENT_MAX_DEPTH) { - return; // We can just exit right now. - } - current = current.parentElement; - if (current?.classList.contains('animate-parent')) { - parent = current; - break; - } - iter++; - } - } - - // Affect all animated children within the parent. - if (parent) { - const animatedChildren = - parent.querySelectorAll('img.custom-emoji'); - for (const child of animatedChildren) { - setAnimateGif(child, animate); - } - } -} - -function setAnimateGif(image: HTMLImageElement, animate: boolean) { - const { classList, dataset } = image; - if ( - !classList.contains('custom-emoji') || - !dataset.static || - !dataset.original - ) { - return; - } - image.src = animate ? dataset.original : dataset.static; -} diff --git a/app/javascript/flavours/glitch/features/emoji/hooks.ts b/app/javascript/flavours/glitch/features/emoji/hooks.ts deleted file mode 100644 index 9c9eeb7d17d6a2..00000000000000 --- a/app/javascript/flavours/glitch/features/emoji/hooks.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { useCallback, useLayoutEffect, useMemo, useState } from 'react'; - -import { isList } from 'immutable'; - -import type { ApiCustomEmojiJSON } from '@/flavours/glitch/api_types/custom_emoji'; -import { useAppSelector } from '@/flavours/glitch/store'; -import { isModernEmojiEnabled } from '@/flavours/glitch/utils/environment'; - -import { toSupportedLocale } from './locale'; -import { determineEmojiMode } from './mode'; -import { emojifyElement, emojifyText } from './render'; -import type { - CustomEmojiMapArg, - EmojiAppState, - ExtraCustomEmojiMap, -} from './types'; -import { stringHasAnyEmoji } from './utils'; - -interface UseEmojifyOptions { - text: string; - extraEmojis?: CustomEmojiMapArg; - deep?: boolean; -} - -export function useEmojify({ - text, - extraEmojis, - deep = true, -}: UseEmojifyOptions) { - const [emojifiedText, setEmojifiedText] = useState(null); - - const appState = useEmojiAppState(); - const extra: ExtraCustomEmojiMap = useMemo(() => { - if (!extraEmojis) { - return {}; - } - if (isList(extraEmojis)) { - return ( - extraEmojis.toJS() as ApiCustomEmojiJSON[] - ).reduce( - (acc, emoji) => ({ ...acc, [emoji.shortcode]: emoji }), - {}, - ); - } - return extraEmojis; - }, [extraEmojis]); - - const emojify = useCallback( - async (input: string) => { - let result: string | null = null; - if (deep) { - const wrapper = document.createElement('div'); - wrapper.innerHTML = input; - if (await emojifyElement(wrapper, appState, extra)) { - result = wrapper.innerHTML; - } - } else { - result = await emojifyText(text, appState, extra); - } - if (result) { - setEmojifiedText(result); - } else { - setEmojifiedText(input); - } - }, - [appState, deep, extra, text], - ); - useLayoutEffect(() => { - if (isModernEmojiEnabled() && !!text.trim() && stringHasAnyEmoji(text)) { - void emojify(text); - } else { - // If no emoji or we don't want to render, fall back. - setEmojifiedText(text); - } - }, [emojify, text]); - - return emojifiedText; -} - -export function useEmojiAppState(): EmojiAppState { - const locale = useAppSelector((state) => - toSupportedLocale(state.meta.get('locale') as string), - ); - const mode = useAppSelector((state) => - determineEmojiMode(state.meta.get('emoji_style') as string), - ); - - return { - currentLocale: locale, - locales: [locale], - mode, - darkTheme: document.body.classList.contains('theme-default'), - }; -} diff --git a/app/javascript/flavours/glitch/features/emoji/index.ts b/app/javascript/flavours/glitch/features/emoji/index.ts index b664aa64cef206..77142326059639 100644 --- a/app/javascript/flavours/glitch/features/emoji/index.ts +++ b/app/javascript/flavours/glitch/features/emoji/index.ts @@ -1,7 +1,8 @@ -import initialState from '@/flavours/glitch/initial_state'; -import { loadWorker } from '@/flavours/glitch/utils/workers'; +import { initialState } from '@/flavours/glitch/initial_state'; +import type { EMOJI_DB_NAME_SHORTCODES } from './constants'; import { toSupportedLocale } from './locale'; +import type { LocaleOrCustom } from './types'; import { emojiLogger } from './utils'; const userLocale = toSupportedLocale(initialState?.meta.locale ?? 'en'); @@ -10,64 +11,94 @@ let worker: Worker | null = null; const log = emojiLogger('index'); -export function initializeEmoji() { +// This is too short, but better to fallback quickly than wait. +const WORKER_TIMEOUT = 1_000; + +export async function initializeEmoji() { log('initializing emojis'); + + // Create a temp worker, and assign it to the module-level worker once we know it's ready. + let tempWorker: Worker | null = null; if (!worker && 'Worker' in window) { try { - worker = loadWorker(new URL('./worker', import.meta.url), { - type: 'module', - }); + const { default: EmojiWorker } = await import('./worker?worker&inline'); + tempWorker = new EmojiWorker(); } catch (err) { console.warn('Error creating web worker:', err); } } - if (worker) { - // Assign worker to const to make TS happy inside the event listener. - const thisWorker = worker; - const timeoutId = setTimeout(() => { - log('worker is not ready after timeout'); - worker = null; - void fallbackLoad(); - }, 500); - thisWorker.addEventListener('message', (event: MessageEvent) => { - const { data: message } = event; - if (message === 'ready') { - log('worker ready, loading data'); - clearTimeout(timeoutId); - thisWorker.postMessage('custom'); - void loadEmojiLocale(userLocale); - // Load English locale as well, because people are still used to - // using it from before we supported other locales. - if (userLocale !== 'en') { - void loadEmojiLocale('en'); - } - } else { - log('got worker message: %s', message); - } - }); - } else { + if (!tempWorker) { void fallbackLoad(); + return; } + + const timeoutId = setTimeout(() => { + log('worker is not ready after timeout'); + void fallbackLoad(); + }, WORKER_TIMEOUT); + + tempWorker.addEventListener('message', (event: MessageEvent) => { + const { data: message } = event; + + worker ??= tempWorker; + + if (message === 'ready') { + log('worker ready, loading data'); + clearTimeout(timeoutId); + messageWorker('shortcodes'); + void loadCustomEmoji(); + void loadEmojiLocale(userLocale); + } else { + log('got worker message: %s', message); + } + }); } async function fallbackLoad() { log('falling back to main thread for loading'); - const { importCustomEmojiData } = await import('./loader'); - await importCustomEmojiData(); - await loadEmojiLocale(userLocale); - if (userLocale !== 'en') { - await loadEmojiLocale('en'); + + await loadCustomEmoji(); + const { importLegacyShortcodes } = await import('./loader'); + const shortcodes = await importLegacyShortcodes(); + if (shortcodes?.length) { + log('loaded %d legacy shortcodes', shortcodes.length); } + await loadEmojiLocale(userLocale); } -export async function loadEmojiLocale(localeString: string) { +async function loadEmojiLocale(localeString: string) { const locale = toSupportedLocale(localeString); + const { importEmojiData } = await import('./loader'); if (worker) { - worker.postMessage(locale); + log('asking worker to load locale %s', locale); + messageWorker(locale); } else { - const { importEmojiData } = await import('./loader'); - await importEmojiData(locale); + const emojis = await importEmojiData(locale); + if (emojis) { + log('loaded %d emojis to locale %s', emojis.length, locale); + } + } +} + +export async function loadCustomEmoji() { + if (worker) { + messageWorker('custom'); + } else { + const { importCustomEmojiData } = await import('./loader'); + const emojis = await importCustomEmojiData(); + if (emojis && emojis.length > 0) { + log('loaded %d custom emojis', emojis.length); + } + } +} + +function messageWorker( + locale: LocaleOrCustom | typeof EMOJI_DB_NAME_SHORTCODES, +) { + if (!worker) { + return; } + worker.postMessage({ locale }); } diff --git a/app/javascript/flavours/glitch/features/emoji/loader.ts b/app/javascript/flavours/glitch/features/emoji/loader.ts index 94c0ff396e228c..014355d884ffbc 100644 --- a/app/javascript/flavours/glitch/features/emoji/loader.ts +++ b/app/javascript/flavours/glitch/features/emoji/loader.ts @@ -1,56 +1,145 @@ import { flattenEmojiData } from 'emojibase'; -import type { CompactEmoji, FlatCompactEmoji } from 'emojibase'; - -import type { ApiCustomEmojiJSON } from '@/flavours/glitch/api_types/custom_emoji'; +import type { + CompactEmoji, + FlatCompactEmoji, + Locale, + ShortcodesDataset, +} from 'emojibase'; import { putEmojiData, putCustomEmojiData, loadLatestEtag, putLatestEtag, + putLegacyShortcodes, } from './database'; -import { toSupportedLocale, toSupportedLocaleOrCustom } from './locale'; -import type { LocaleOrCustom } from './types'; +import { toSupportedLocale, toValidEtagName } from './locale'; +import type { CustomEmojiData } from './types'; import { emojiLogger } from './utils'; const log = emojiLogger('loader'); -export async function importEmojiData(localeString: string) { +export async function importEmojiData(localeString: string, shortcodes = true) { const locale = toSupportedLocale(localeString); - const emojis = await fetchAndCheckEtag(locale); + + log( + 'importing emoji data for locale %s%s', + locale, + shortcodes ? ' and shortcodes' : '', + ); + + const emojis = await fetchAndCheckEtag({ + etagString: locale, + path: localeToEmojiPath(locale), + }); if (!emojis) { return; } - const flattenedEmojis: FlatCompactEmoji[] = flattenEmojiData(emojis); - log('loaded %d for %s locale', flattenedEmojis.length, locale); + + const shortcodesData: ShortcodesDataset[] = []; + if (shortcodes) { + const shortcodesResponse = await fetchAndCheckEtag({ + etagString: `${locale}-shortcodes`, + path: localeToShortcodesPath(locale), + }); + if (shortcodesResponse) { + shortcodesData.push(shortcodesResponse); + } else { + throw new Error(`No shortcodes data found for locale ${locale}`); + } + } + + const flattenedEmojis: FlatCompactEmoji[] = flattenEmojiData( + emojis, + shortcodesData, + ); await putEmojiData(flattenedEmojis, locale); + return flattenedEmojis; } export async function importCustomEmojiData() { - const emojis = await fetchAndCheckEtag('custom'); + const emojis = await fetchAndCheckEtag({ + etagString: 'custom', + path: '/api/v1/custom_emojis', + }); if (!emojis) { return; } - log('loaded %d custom emojis', emojis.length); - await putCustomEmojiData(emojis); + await putCustomEmojiData({ emojis, clear: true }); + return emojis; +} + +export async function importLegacyShortcodes() { + const globPaths = import.meta.glob( + // We use import.meta.glob to eagerly load the URL, as the regular import() doesn't work inside the Web Worker. + '../../../../../../node_modules/emojibase-data/en/shortcodes/iamcal.json', + { eager: true, import: 'default', query: '?url' }, + ); + const path = Object.values(globPaths)[0]; + if (!path) { + throw new Error('IAMCAL shortcodes path not found'); + } + const shortcodesData = await fetchAndCheckEtag({ + checkEtag: true, + etagString: 'shortcodes', + path, + }); + if (!shortcodesData) { + return; + } + await putLegacyShortcodes(shortcodesData); + return Object.keys(shortcodesData); } -async function fetchAndCheckEtag( - localeOrCustom: LocaleOrCustom, -): Promise { - const locale = toSupportedLocaleOrCustom(localeOrCustom); +function localeToEmojiPath(locale: Locale) { + const key = `../../../../../../node_modules/emojibase-data/${locale}/compact.json`; + const emojiModules = import.meta.glob( + '../../../../../../node_modules/emojibase-data/**/compact.json', + { + query: '?url', + import: 'default', + eager: true, + }, + ); + const path = emojiModules[key]; + if (!path) { + throw new Error(`Unsupported locale: ${locale}`); + } + return path; +} - // Use location.origin as this script may be loaded from a CDN domain. - const url = new URL(location.origin); - if (locale === 'custom') { - url.pathname = '/api/v1/custom_emojis'; - } else { - // This doesn't use isDevelopment() as that module loads initial state - // which breaks workers, as they cannot access the DOM. - url.pathname = `/packs${import.meta.env.DEV ? '-dev' : ''}/emoji/${locale}.json`; +function localeToShortcodesPath(locale: Locale) { + const key = `../../../../../../node_modules/emojibase-data/${locale}/shortcodes/cldr.json`; + const shortcodesModules = import.meta.glob( + '../../../../../../node_modules/emojibase-data/**/shortcodes/cldr.json', + { + query: '?url', + import: 'default', + eager: true, + }, + ); + const path = shortcodesModules[key]; + if (!path) { + throw new Error(`Unsupported locale for shortcodes: ${locale}`); } + return path; +} - const oldEtag = await loadLatestEtag(locale); +async function fetchAndCheckEtag({ + etagString, + path, + checkEtag = false, +}: { + etagString: string; + path: string; + checkEtag?: boolean; +}): Promise { + const etagName = toValidEtagName(etagString); + + // Use location.origin as this script may be loaded from a CDN domain. + const url = new URL(path, location.origin); + + const oldEtag = checkEtag ? await loadLatestEtag(etagName) : null; const response = await fetch(url, { headers: { 'Content-Type': 'application/json', @@ -63,21 +152,17 @@ async function fetchAndCheckEtag( } if (!response.ok) { throw new Error( - `Failed to fetch emoji data for ${localeOrCustom}: ${response.statusText}`, + `Failed to fetch emoji data for ${etagName}: ${response.statusText}`, ); } const data = (await response.json()) as ResultType; - if (!Array.isArray(data)) { - throw new Error( - `Unexpected data format for ${localeOrCustom}: expected an array`, - ); - } // Store the ETag for future requests const etag = response.headers.get('ETag'); - if (etag) { - await putLatestEtag(etag, localeOrCustom); + if (etag && checkEtag) { + log(`storing new etag for ${etagName}: ${etag}`); + await putLatestEtag(etag, etagName); } return data; diff --git a/app/javascript/flavours/glitch/features/emoji/locale.ts b/app/javascript/flavours/glitch/features/emoji/locale.ts index 8ff23f5161a1a4..f39b56d47c22a6 100644 --- a/app/javascript/flavours/glitch/features/emoji/locale.ts +++ b/app/javascript/flavours/glitch/features/emoji/locale.ts @@ -1,7 +1,8 @@ import type { Locale } from 'emojibase'; import { SUPPORTED_LOCALES } from 'emojibase'; -import type { LocaleOrCustom } from './types'; +import { EMOJI_DB_NAME_SHORTCODES, EMOJI_TYPE_CUSTOM } from './constants'; +import type { EtagTypes, LocaleOrCustom, LocaleWithShortcodes } from './types'; export function toSupportedLocale(localeBase: string): Locale { const locale = localeBase.toLowerCase(); @@ -12,12 +13,35 @@ export function toSupportedLocale(localeBase: string): Locale { } export function toSupportedLocaleOrCustom(locale: string): LocaleOrCustom { - if (locale.toLowerCase() === 'custom') { - return 'custom'; + if (locale.toLowerCase() === EMOJI_TYPE_CUSTOM) { + return EMOJI_TYPE_CUSTOM; } return toSupportedLocale(locale); } +export function toValidEtagName(input: string): EtagTypes { + const lower = input.toLowerCase(); + if (lower === EMOJI_TYPE_CUSTOM || lower === EMOJI_DB_NAME_SHORTCODES) { + return lower; + } + + if (isLocaleWithShortcodes(lower)) { + return lower; + } + + return toSupportedLocale(lower); +} + function isSupportedLocale(locale: string): locale is Locale { - return SUPPORTED_LOCALES.includes(locale.toLowerCase() as Locale); + return SUPPORTED_LOCALES.includes(locale as Locale); +} + +function isLocaleWithShortcodes(input: string): input is LocaleWithShortcodes { + const [baseLocale, shortcodes] = input.split('-'); + return ( + !!baseLocale && + !!shortcodes && + isSupportedLocale(baseLocale) && + shortcodes === EMOJI_DB_NAME_SHORTCODES + ); } diff --git a/app/javascript/flavours/glitch/features/emoji/mode.ts b/app/javascript/flavours/glitch/features/emoji/mode.ts index 994881bca1ec65..5b6dc882c70a25 100644 --- a/app/javascript/flavours/glitch/features/emoji/mode.ts +++ b/app/javascript/flavours/glitch/features/emoji/mode.ts @@ -1,14 +1,36 @@ // Credit to Nolan Lawson for the original implementation. // See: https://github.com/nolanlawson/emoji-picker-element/blob/master/src/picker/utils/testColorEmojiSupported.js +import { createAppSelector, useAppSelector } from '@/flavours/glitch/store'; import { isDevelopment } from '@/flavours/glitch/utils/environment'; +import { isDarkMode } from '@/flavours/glitch/utils/theme'; import { EMOJI_MODE_NATIVE, EMOJI_MODE_NATIVE_WITH_FLAGS, EMOJI_MODE_TWEMOJI, } from './constants'; -import type { EmojiMode } from './types'; +import { toSupportedLocale } from './locale'; +import type { EmojiAppState, EmojiMode } from './types'; + +const modeSelector = createAppSelector( + [(state) => state.meta.get('emoji_style') as string], + (emoji_style) => determineEmojiMode(emoji_style), +); + +export function useEmojiAppState(): EmojiAppState { + const locale = useAppSelector((state) => + toSupportedLocale(state.meta.get('locale') as string), + ); + const mode = useAppSelector(modeSelector); + + return { + currentLocale: locale, + locales: [locale], + mode, + darkTheme: isDarkMode(), + }; +} type Feature = Uint8ClampedArray; @@ -55,7 +77,7 @@ function testEmojiSupport(text: string) { return compareFeatures(feature1, feature2); } -const EMOJI_VERSION_TEST_EMOJI = '🫨'; // shaking head, from v15 +const EMOJI_VERSION_TEST_EMOJI = '🫩'; // face with bags under eyes, from Unicode 16.0. const EMOJI_FLAG_TEST_EMOJI = '🇨🇭'; export function determineEmojiMode(style: string): EmojiMode { diff --git a/app/javascript/flavours/glitch/features/emoji/normalize.test.ts b/app/javascript/flavours/glitch/features/emoji/normalize.test.ts index f0ea140590b068..b4c766996112c3 100644 --- a/app/javascript/flavours/glitch/features/emoji/normalize.test.ts +++ b/app/javascript/flavours/glitch/features/emoji/normalize.test.ts @@ -5,11 +5,8 @@ import { flattenEmojiData } from 'emojibase'; import unicodeRawEmojis from 'emojibase-data/en/data.json'; import { - twemojiHasBorder, twemojiToUnicodeInfo, unicodeToTwemojiHex, - CODES_WITH_DARK_BORDER, - CODES_WITH_LIGHT_BORDER, emojiToUnicodeHex, } from './normalize'; @@ -57,26 +54,6 @@ describe('unicodeToTwemojiHex', () => { }); }); -describe('twemojiHasBorder', () => { - test.concurrent.for( - svgFileNames - .filter((file) => file.endsWith('_border')) - .map((file) => { - const hexCode = file.replace('_border', ''); - return [ - hexCode, - CODES_WITH_LIGHT_BORDER.includes(hexCode.toUpperCase()), - CODES_WITH_DARK_BORDER.includes(hexCode.toUpperCase()), - ] as const; - }), - )('twemojiHasBorder for %s', ([hexCode, isLight, isDark], { expect }) => { - const result = twemojiHasBorder(hexCode); - expect(result).toHaveProperty('hexCode', hexCode); - expect(result).toHaveProperty('hasLightBorder', isLight); - expect(result).toHaveProperty('hasDarkBorder', isDark); - }); -}); - describe('twemojiToUnicodeInfo', () => { const unicodeCodeSet = new Set(unicodeEmojis.map((emoji) => emoji.hexcode)); diff --git a/app/javascript/flavours/glitch/features/emoji/normalize.ts b/app/javascript/flavours/glitch/features/emoji/normalize.ts index 6a64c3b8bfabc2..4800352ffa37e9 100644 --- a/app/javascript/flavours/glitch/features/emoji/normalize.ts +++ b/app/javascript/flavours/glitch/features/emoji/normalize.ts @@ -1,3 +1,7 @@ +import { isList } from 'immutable'; + +import { assetHost } from '@/flavours/glitch/utils/config'; + import { VARIATION_SELECTOR_CODE, KEYCAP_CODE, @@ -6,8 +10,10 @@ import { SKIN_TONE_CODES, EMOJIS_WITH_DARK_BORDER, EMOJIS_WITH_LIGHT_BORDER, + EMOJIS_REQUIRING_INVERSION_IN_LIGHT_MODE, + EMOJIS_REQUIRING_INVERSION_IN_DARK_MODE, } from './constants'; -import type { TwemojiBorderInfo } from './types'; +import type { CustomEmojiMapArg, ExtraCustomEmojiMap } from './types'; // Misc codes that have special handling const SKIER_CODE = 0x26f7; @@ -61,21 +67,17 @@ export const CODES_WITH_DARK_BORDER = export const CODES_WITH_LIGHT_BORDER = EMOJIS_WITH_LIGHT_BORDER.map(emojiToUnicodeHex); -export function twemojiHasBorder(twemojiHex: string): TwemojiBorderInfo { - const normalizedHex = twemojiHex.toUpperCase(); - let hasLightBorder = false; - let hasDarkBorder = false; - if (CODES_WITH_LIGHT_BORDER.includes(normalizedHex)) { - hasLightBorder = true; +export function unicodeHexToUrl(unicodeHex: string, darkMode: boolean): string { + const normalizedHex = unicodeToTwemojiHex(unicodeHex); + let url = `${assetHost}/emoji/${normalizedHex}`; + if (darkMode && CODES_WITH_LIGHT_BORDER.includes(normalizedHex)) { + url += '_border'; } if (CODES_WITH_DARK_BORDER.includes(normalizedHex)) { - hasDarkBorder = true; + url += '_border'; } - return { - hexCode: twemojiHex, - hasLightBorder, - hasDarkBorder, - }; + url += '.svg'; + return url; } interface TwemojiSpecificEmoji { @@ -150,6 +152,37 @@ export function twemojiToUnicodeInfo( return hexNumbersToString(mappedCodes); } +export function emojiToInversionClassName(emoji: string): string | null { + if (EMOJIS_REQUIRING_INVERSION_IN_DARK_MODE.includes(emoji)) { + return 'invert-on-dark'; + } + if (EMOJIS_REQUIRING_INVERSION_IN_LIGHT_MODE.includes(emoji)) { + return 'invert-on-light'; + } + return null; +} + +export function cleanExtraEmojis(extraEmojis?: CustomEmojiMapArg) { + if (!extraEmojis) { + return null; + } + if (Array.isArray(extraEmojis)) { + return extraEmojis.reduce( + (acc, emoji) => ({ ...acc, [emoji.shortcode]: emoji }), + {}, + ); + } + if (isList(extraEmojis)) { + return extraEmojis + .toJS() + .reduce( + (acc, emoji) => ({ ...acc, [emoji.shortcode]: emoji }), + {}, + ); + } + return extraEmojis; +} + function hexStringToNumbers(hexString: string): number[] { return hexString .split('-') diff --git a/app/javascript/flavours/glitch/features/emoji/render.test.ts b/app/javascript/flavours/glitch/features/emoji/render.test.ts index e9609e15dc5060..dffebd1f8c576c 100644 --- a/app/javascript/flavours/glitch/features/emoji/render.test.ts +++ b/app/javascript/flavours/glitch/features/emoji/render.test.ts @@ -1,192 +1,15 @@ import { customEmojiFactory, unicodeEmojiFactory } from '@/testing/factories'; -import { - EMOJI_MODE_NATIVE, - EMOJI_MODE_NATIVE_WITH_FLAGS, - EMOJI_MODE_TWEMOJI, -} from './constants'; import * as db from './database'; +import * as loader from './loader'; import { - emojifyElement, - emojifyText, - testCacheClear, + loadEmojiDataToState, + stringToEmojiState, tokenizeText, } from './render'; -import type { EmojiAppState, ExtraCustomEmojiMap } from './types'; - -function mockDatabase() { - return { - searchCustomEmojisByShortcodes: vi - .spyOn(db, 'searchCustomEmojisByShortcodes') - .mockResolvedValue([customEmojiFactory()]), - searchEmojisByHexcodes: vi - .spyOn(db, 'searchEmojisByHexcodes') - .mockResolvedValue([ - unicodeEmojiFactory({ - hexcode: '1F60A', - label: 'smiling face with smiling eyes', - unicode: '😊', - }), - unicodeEmojiFactory({ - hexcode: '1F1EA-1F1FA', - label: 'flag-eu', - unicode: '🇪🇺', - }), - ]), - }; -} - -const expectedSmileImage = - '😊'; -const expectedFlagImage = - '🇪🇺'; -const expectedCustomEmojiImage = - ':custom:'; -const expectedRemoteCustomEmojiImage = - ':remote:'; - -const mockExtraCustom: ExtraCustomEmojiMap = { - remote: { - shortcode: 'remote', - static_url: 'remote.social/static', - url: 'remote.social/custom', - }, -}; - -function testAppState(state: Partial = {}) { - return { - locales: ['en'], - mode: EMOJI_MODE_TWEMOJI, - currentLocale: 'en', - darkTheme: false, - ...state, - } satisfies EmojiAppState; -} - -describe('emojifyElement', () => { - function testElement(text = '

Hello 😊🇪🇺!

:custom:

') { - const testElement = document.createElement('div'); - testElement.innerHTML = text; - return testElement; - } - - afterEach(() => { - testCacheClear(); - vi.restoreAllMocks(); - }); - - test('caches element rendering results', async () => { - const { searchCustomEmojisByShortcodes, searchEmojisByHexcodes } = - mockDatabase(); - await emojifyElement(testElement(), testAppState()); - await emojifyElement(testElement(), testAppState()); - await emojifyElement(testElement(), testAppState()); - expect(searchEmojisByHexcodes).toHaveBeenCalledExactlyOnceWith( - ['1F1EA-1F1FA', '1F60A'], - 'en', - ); - expect(searchCustomEmojisByShortcodes).toHaveBeenCalledExactlyOnceWith([ - 'custom', - ]); - }); - - test('emojifies custom emoji in native mode', async () => { - const { searchEmojisByHexcodes } = mockDatabase(); - const actual = await emojifyElement( - testElement(), - testAppState({ mode: EMOJI_MODE_NATIVE }), - ); - assert(actual); - expect(actual.innerHTML).toBe( - `

Hello 😊🇪🇺!

${expectedCustomEmojiImage}

`, - ); - expect(searchEmojisByHexcodes).not.toHaveBeenCalled(); - }); - - test('emojifies flag emoji in native-with-flags mode', async () => { - const { searchEmojisByHexcodes } = mockDatabase(); - const actual = await emojifyElement( - testElement(), - testAppState({ mode: EMOJI_MODE_NATIVE_WITH_FLAGS }), - ); - assert(actual); - expect(actual.innerHTML).toBe( - `

Hello 😊${expectedFlagImage}!

${expectedCustomEmojiImage}

`, - ); - expect(searchEmojisByHexcodes).toHaveBeenCalledOnce(); - }); - - test('emojifies everything in twemoji mode', async () => { - const { searchCustomEmojisByShortcodes, searchEmojisByHexcodes } = - mockDatabase(); - const actual = await emojifyElement(testElement(), testAppState()); - assert(actual); - expect(actual.innerHTML).toBe( - `

Hello ${expectedSmileImage}${expectedFlagImage}!

${expectedCustomEmojiImage}

`, - ); - expect(searchEmojisByHexcodes).toHaveBeenCalledOnce(); - expect(searchCustomEmojisByShortcodes).toHaveBeenCalledOnce(); - }); - - test('emojifies with provided custom emoji', async () => { - const { searchCustomEmojisByShortcodes, searchEmojisByHexcodes } = - mockDatabase(); - const actual = await emojifyElement( - testElement('

hi :remote:

'), - testAppState(), - mockExtraCustom, - ); - assert(actual); - expect(actual.innerHTML).toBe( - `

hi ${expectedRemoteCustomEmojiImage}

`, - ); - expect(searchEmojisByHexcodes).not.toHaveBeenCalled(); - expect(searchCustomEmojisByShortcodes).not.toHaveBeenCalled(); - }); - - test('returns null when no emoji are found', async () => { - mockDatabase(); - const actual = await emojifyElement( - testElement('

here is just text :)

'), - testAppState(), - ); - expect(actual).toBeNull(); - }); -}); - -describe('emojifyText', () => { - test('returns original input when no emoji are in string', async () => { - const actual = await emojifyText('nothing here', testAppState()); - expect(actual).toBe('nothing here'); - }); - - test('renders Unicode emojis to twemojis', async () => { - mockDatabase(); - const actual = await emojifyText('Hello 😊🇪🇺!', testAppState()); - expect(actual).toBe(`Hello ${expectedSmileImage}${expectedFlagImage}!`); - }); - - test('renders custom emojis', async () => { - mockDatabase(); - const actual = await emojifyText('Hello :custom:!', testAppState()); - expect(actual).toBe(`Hello ${expectedCustomEmojiImage}!`); - }); - - test('renders provided extra emojis', async () => { - const actual = await emojifyText( - 'remote emoji :remote:', - testAppState(), - mockExtraCustom, - ); - expect(actual).toBe(`remote emoji ${expectedRemoteCustomEmojiImage}`); - }); -}); +import type { EmojiStateCustom, EmojiStateUnicode } from './types'; describe('tokenizeText', () => { - test('returns empty array for string with only whitespace', () => { - expect(tokenizeText(' \n')).toEqual([]); - }); - test('returns an array of text to be a single token', () => { expect(tokenizeText('Hello')).toEqual(['Hello']); }); @@ -212,7 +35,7 @@ describe('tokenizeText', () => { 'Hello ', { type: 'custom', - code: 'smile', + code: ':smile:', }, '!!', ]); @@ -223,7 +46,7 @@ describe('tokenizeText', () => { 'Hello ', { type: 'custom', - code: 'smile_123', + code: ':smile_123:', }, '!!', ]); @@ -239,7 +62,7 @@ describe('tokenizeText', () => { ' ', { type: 'custom', - code: 'smile', + code: ':smile:', }, '!!', ]); @@ -251,3 +74,131 @@ describe('tokenizeText', () => { ]); }); }); + +describe('stringToEmojiState', () => { + test('returns unicode emoji state for valid unicode emoji', () => { + expect(stringToEmojiState('😊')).toEqual({ + type: 'unicode', + code: '1F60A', + }); + }); + + test('returns null for custom emoji without data', () => { + expect(stringToEmojiState(':smile:')).toBeNull(); + }); + + test('returns custom emoji state with data when provided', () => { + const customEmoji = { + smile: customEmojiFactory({ + shortcode: 'smile', + url: 'https://example.com/smile.png', + static_url: 'https://example.com/smile_static.png', + }), + }; + expect(stringToEmojiState(':smile:', customEmoji)).toEqual({ + type: 'custom', + code: 'smile', + data: customEmoji.smile, + }); + }); + + test('returns null for invalid emoji strings', () => { + expect(stringToEmojiState('notanemoji')).toBeNull(); + }); +}); + +describe('loadEmojiDataToState', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + test('loads unicode data into state', async () => { + const dbCall = vi + .spyOn(db, 'loadEmojiByHexcode') + .mockResolvedValue(unicodeEmojiFactory()); + const dbLegacyCall = vi + .spyOn(db, 'loadLegacyShortcodesByShortcode') + .mockResolvedValueOnce({ + shortcodes: ['legacy_code'], + hexcode: '1F60A', + }); + const unicodeState = { + type: 'unicode', + code: '1F60A', + } as const satisfies EmojiStateUnicode; + const result = await loadEmojiDataToState(unicodeState, 'en'); + expect(dbCall).toHaveBeenCalledWith('1F60A', 'en'); + expect(dbLegacyCall).toHaveBeenCalledWith('1F60A'); + expect(result).toEqual({ + type: 'unicode', + code: '1F60A', + data: unicodeEmojiFactory(), + shortcode: 'legacy_code', + }); + }); + + test('returns null for custom emoji without data', async () => { + const customState = { + type: 'custom', + code: 'smile', + } as const satisfies EmojiStateCustom; + const result = await loadEmojiDataToState(customState, 'en'); + expect(result).toBeNull(); + }); + + test('loads unicode data using legacy shortcode', async () => { + const dbLegacyCall = vi + .spyOn(db, 'loadLegacyShortcodesByShortcode') + .mockResolvedValueOnce({ + shortcodes: ['test'], + hexcode: 'test', + }); + const dbUnicodeCall = vi + .spyOn(db, 'loadEmojiByHexcode') + .mockResolvedValue(unicodeEmojiFactory()); + const unicodeState = { + type: 'unicode', + code: 'test', + } as const satisfies EmojiStateUnicode; + const result = await loadEmojiDataToState(unicodeState, 'en'); + expect(dbLegacyCall).toHaveBeenCalledWith('test'); + expect(dbUnicodeCall).toHaveBeenCalledWith('test', 'en'); + expect(result).toEqual({ + type: 'unicode', + code: 'test', + data: unicodeEmojiFactory(), + shortcode: 'test', + }); + }); + + test('returns null if unicode emoji not found in database', async () => { + vi.spyOn(db, 'loadEmojiByHexcode').mockResolvedValueOnce(undefined); + const unicodeState = { + type: 'unicode', + code: '1F60A', + } as const satisfies EmojiStateUnicode; + const result = await loadEmojiDataToState(unicodeState, 'en'); + expect(result).toBeNull(); + }); + + test('retries loading emoji data once if initial load fails', async () => { + const dbCall = vi + .spyOn(db, 'loadEmojiByHexcode') + .mockRejectedValue(new db.LocaleNotLoadedError('en')); + vi.spyOn(loader, 'importEmojiData').mockResolvedValueOnce(undefined); + const consoleCall = vi + .spyOn(console, 'warn') + .mockImplementationOnce(() => null); + + const unicodeState = { + type: 'unicode', + code: '1F60A', + } as const satisfies EmojiStateUnicode; + const result = await loadEmojiDataToState(unicodeState, 'en'); + + expect(dbCall).toHaveBeenCalledTimes(2); + expect(loader.importEmojiData).toHaveBeenCalledWith('en'); + expect(consoleCall).toHaveBeenCalled(); + expect(result).toBeNull(); + }); +}); diff --git a/app/javascript/flavours/glitch/features/emoji/render.ts b/app/javascript/flavours/glitch/features/emoji/render.ts index d00c88ab4fbd0c..8fe311014a95c7 100644 --- a/app/javascript/flavours/glitch/features/emoji/render.ts +++ b/app/javascript/flavours/glitch/features/emoji/render.ts @@ -1,212 +1,38 @@ -import { autoPlayGif } from '@/flavours/glitch/initial_state'; -import { createLimitedCache } from '@/flavours/glitch/utils/cache'; -import { assetHost } from '@/flavours/glitch/utils/config'; -import * as perf from '@/flavours/glitch/utils/performance'; - import { EMOJI_MODE_NATIVE, EMOJI_MODE_NATIVE_WITH_FLAGS, EMOJI_TYPE_UNICODE, EMOJI_TYPE_CUSTOM, - EMOJI_STATE_MISSING, } from './constants'; -import { - searchCustomEmojisByShortcodes, - searchEmojisByHexcodes, -} from './database'; -import { - emojiToUnicodeHex, - twemojiHasBorder, - unicodeToTwemojiHex, -} from './normalize'; +import { emojiToUnicodeHex } from './normalize'; import type { - CustomEmojiToken, - EmojiAppState, EmojiLoadedState, EmojiMode, EmojiState, - EmojiStateMap, - EmojiToken, + EmojiStateCustom, + EmojiStateUnicode, ExtraCustomEmojiMap, - LocaleOrCustom, - UnicodeEmojiToken, } from './types'; import { anyEmojiRegex, emojiLogger, - stringHasAnyEmoji, + isCustomEmoji, + isUnicodeEmoji, stringHasUnicodeFlags, } from './utils'; const log = emojiLogger('render'); +type TokenizedText = (string | EmojiState)[]; + /** - * Emojifies an element. This modifies the element in place, replacing text nodes with emojified versions. + * Tokenizes text into strings and emoji states. + * @param text Text to tokenize. + * @returns Array of strings and emoji states. */ -export async function emojifyElement( - element: Element, - appState: EmojiAppState, - extraEmojis: ExtraCustomEmojiMap = {}, -): Promise { - const cacheKey = createCacheKey(element, appState, extraEmojis); - const cached = getCached(cacheKey); - if (cached !== undefined) { - log('Cache hit on %s', element.outerHTML); - if (cached === null) { - return null; - } - element.innerHTML = cached; - return element; - } - if (!stringHasAnyEmoji(element.innerHTML)) { - updateCache(cacheKey, null); - return null; - } - perf.start('emojifyElement()'); - const queue: (HTMLElement | Text)[] = [element]; - while (queue.length > 0) { - const current = queue.shift(); - if ( - !current || - current instanceof HTMLScriptElement || - current instanceof HTMLStyleElement - ) { - continue; - } - - if ( - current.textContent && - (current instanceof Text || !current.hasChildNodes()) - ) { - const renderedContent = await textToElementArray( - current.textContent, - appState, - extraEmojis, - ); - if (renderedContent) { - if (!(current instanceof Text)) { - current.textContent = null; // Clear the text content if it's not a Text node. - } - current.replaceWith(renderedToHTML(renderedContent)); - } - continue; - } - - for (const child of current.childNodes) { - if (child instanceof HTMLElement || child instanceof Text) { - queue.push(child); - } - } - } - updateCache(cacheKey, element.innerHTML); - perf.stop('emojifyElement()'); - return element; -} - -export async function emojifyText( - text: string, - appState: EmojiAppState, - extraEmojis: ExtraCustomEmojiMap = {}, -): Promise { - const cacheKey = createCacheKey(text, appState, extraEmojis); - const cached = getCached(cacheKey); - if (cached !== undefined) { - log('Cache hit on %s', text); - return cached ?? text; - } - if (!stringHasAnyEmoji(text)) { - updateCache(cacheKey, null); - return text; - } - const eleArray = await textToElementArray(text, appState, extraEmojis); - if (!eleArray) { - updateCache(cacheKey, null); - return text; - } - const rendered = renderedToHTML(eleArray, document.createElement('div')); - updateCache(cacheKey, rendered.innerHTML); - return rendered.innerHTML; -} - -// Private functions - -const { - set: updateCache, - get: getCached, - clear: cacheClear, -} = createLimitedCache({ log: log.extend('cache') }); - -function createCacheKey( - input: HTMLElement | string, - appState: EmojiAppState, - extraEmojis: ExtraCustomEmojiMap, -) { - return JSON.stringify([ - input instanceof HTMLElement ? input.outerHTML : input, - appState, - extraEmojis, - ]); -} - -type EmojifiedTextArray = (string | HTMLImageElement)[]; - -async function textToElementArray( - text: string, - appState: EmojiAppState, - extraEmojis: ExtraCustomEmojiMap = {}, -): Promise { - // Exit if no text to convert. - if (!text.trim()) { - return null; - } - - const tokens = tokenizeText(text); - - // If only one token and it's a string, exit early. - if (tokens.length === 1 && typeof tokens[0] === 'string') { - return null; - } - - // Get all emoji from the state map, loading any missing ones. - await loadMissingEmojiIntoCache(tokens, appState, extraEmojis); - - const renderedFragments: EmojifiedTextArray = []; - for (const token of tokens) { - if (typeof token !== 'string' && shouldRenderImage(token, appState.mode)) { - let state: EmojiState | undefined; - if (token.type === EMOJI_TYPE_CUSTOM) { - const extraEmojiData = extraEmojis[token.code]; - if (extraEmojiData) { - state = { type: EMOJI_TYPE_CUSTOM, data: extraEmojiData }; - } else { - state = emojiForLocale(token.code, EMOJI_TYPE_CUSTOM); - } - } else { - state = emojiForLocale( - emojiToUnicodeHex(token.code), - appState.currentLocale, - ); - } - - // If the state is valid, create an image element. Otherwise, just append as text. - if (state && typeof state !== 'string') { - const image = stateToImage(state, appState); - renderedFragments.push(image); - continue; - } - } - const text = typeof token === 'string' ? token : token.code; - renderedFragments.push(text); - } - - return renderedFragments; -} - -type TokenizedText = (string | EmojiToken)[]; - export function tokenizeText(text: string): TokenizedText { if (!text.trim()) { - return []; + return [text]; } const tokens = []; @@ -222,14 +48,14 @@ export function tokenizeText(text: string): TokenizedText { // Custom emoji tokens.push({ type: EMOJI_TYPE_CUSTOM, - code: code.slice(1, -1), // Remove the colons - } satisfies CustomEmojiToken); + code, + } satisfies EmojiStateCustom); } else { // Unicode emoji tokens.push({ type: EMOJI_TYPE_UNICODE, code: code, - } satisfies UnicodeEmojiToken); + } satisfies EmojiStateUnicode); } lastIndex = match.index + code.length; } @@ -239,107 +65,124 @@ export function tokenizeText(text: string): TokenizedText { return tokens; } -const localeCacheMap = new Map([ - [ - EMOJI_TYPE_CUSTOM, - createLimitedCache({ log: log.extend('custom') }), - ], -]); - -function cacheForLocale(locale: LocaleOrCustom): EmojiStateMap { - return ( - localeCacheMap.get(locale) ?? - createLimitedCache({ log: log.extend(locale) }) - ); -} - -function emojiForLocale( +/** + * Parses emoji string to extract emoji state. + * @param code Hex code or custom shortcode. + * @param customEmoji Extra custom emojis. + */ +export function stringToEmojiState( code: string, - locale: LocaleOrCustom, -): EmojiState | undefined { - const cache = cacheForLocale(locale); - return cache.get(code); -} - -async function loadMissingEmojiIntoCache( - tokens: TokenizedText, - { mode, currentLocale }: EmojiAppState, - extraEmojis: ExtraCustomEmojiMap, -) { - const missingUnicodeEmoji = new Set(); - const missingCustomEmoji = new Set(); - - // Iterate over tokens and check if they are in the cache already. - for (const token of tokens) { - if (typeof token === 'string') { - continue; // Skip plain strings. + customEmoji: ExtraCustomEmojiMap = {}, +): EmojiStateUnicode | Required | null { + if (isUnicodeEmoji(code)) { + return { + type: EMOJI_TYPE_UNICODE, + code: emojiToUnicodeHex(code), + }; + } + + if (isCustomEmoji(code)) { + const shortCode = code.slice(1, -1); + if (customEmoji[shortCode]) { + return { + type: EMOJI_TYPE_CUSTOM, + code: shortCode, + data: customEmoji[shortCode], + }; } + } - // If this is a custom emoji, check it separately. - if (token.type === EMOJI_TYPE_CUSTOM) { - const code = token.code; - if (code in extraEmojis) { - continue; // We don't care about extra emoji. - } - const emojiState = emojiForLocale(code, EMOJI_TYPE_CUSTOM); - if (!emojiState) { - missingCustomEmoji.add(code); - } - // Otherwise this is a unicode emoji, so check it against all locales. - } else if (shouldRenderImage(token, mode)) { - const code = emojiToUnicodeHex(token.code); - if (missingUnicodeEmoji.has(code)) { - continue; // Already marked as missing. - } - const emojiState = emojiForLocale(code, currentLocale); - if (!emojiState) { - // If it's missing in one locale, we consider it missing for all. - missingUnicodeEmoji.add(code); - } - } + return null; +} + +/** + * Loads emoji data into the given state if not already loaded. + * @param state Emoji state to load data for. + * @param locale Locale to load data for. Only for Unicode emoji. + * @param retry Internal. Whether this is a retry after loading the locale. + */ +export async function loadEmojiDataToState( + state: EmojiState, + locale: string, + retry = false, +): Promise { + if (isStateLoaded(state)) { + return state; } - if (missingUnicodeEmoji.size > 0) { - const missingEmojis = Array.from(missingUnicodeEmoji).toSorted(); - const emojis = await searchEmojisByHexcodes(missingEmojis, currentLocale); - const cache = cacheForLocale(currentLocale); - for (const emoji of emojis) { - cache.set(emoji.hexcode, { type: EMOJI_TYPE_UNICODE, data: emoji }); - } - const notFoundEmojis = missingEmojis.filter((code) => - emojis.every((emoji) => emoji.hexcode !== code), - ); - for (const code of notFoundEmojis) { - cache.set(code, EMOJI_STATE_MISSING); // Mark as missing if not found, as it's probably not a valid emoji. - } - localeCacheMap.set(currentLocale, cache); + // Don't try to load data for custom emoji. + if (state.type === EMOJI_TYPE_CUSTOM) { + return null; } - if (missingCustomEmoji.size > 0) { - const missingEmojis = Array.from(missingCustomEmoji).toSorted(); - const emojis = await searchCustomEmojisByShortcodes(missingEmojis); - const cache = cacheForLocale(EMOJI_TYPE_CUSTOM); - for (const emoji of emojis) { - cache.set(emoji.shortcode, { type: EMOJI_TYPE_CUSTOM, data: emoji }); - } - const notFoundEmojis = missingEmojis.filter((code) => - emojis.every((emoji) => emoji.shortcode !== code), + const { + loadLegacyShortcodesByShortcode, + loadEmojiByHexcode, + LocaleNotLoadedError, + } = await import('./database'); + + // First, try to load the data from IndexedDB. + try { + const legacyCode = await loadLegacyShortcodesByShortcode(state.code); + // This is duplicative, but that's because TS can't distinguish the state type easily. + const data = await loadEmojiByHexcode( + legacyCode?.hexcode ?? state.code, + locale, ); - for (const code of notFoundEmojis) { - cache.set(code, EMOJI_STATE_MISSING); // Mark as missing if not found, as it's probably not a valid emoji. + if (data) { + return { + ...state, + type: EMOJI_TYPE_UNICODE, + data, + // TODO: Use CLDR shortcodes when the picker supports them. + shortcode: legacyCode?.shortcodes.at(0), + }; + } + + // If not found, assume it's not an emoji and return null. + log( + 'Could not find emoji %s of type %s for locale %s', + state.code, + state.type, + locale, + ); + return null; + } catch (err: unknown) { + // If the locale is not loaded, load it and retry once. + if (!retry && err instanceof LocaleNotLoadedError) { + log( + 'Error loading emoji %s for locale %s, loading locale and retrying.', + state.code, + locale, + ); + const { importEmojiData } = await import('./loader'); + await importEmojiData(locale); // Use this from the loader file as it can be awaited. + return loadEmojiDataToState(state, locale, true); } - localeCacheMap.set(EMOJI_TYPE_CUSTOM, cache); + + console.warn('Error loading emoji data, not retrying:', state, locale, err); + return null; } } -function shouldRenderImage(token: EmojiToken, mode: EmojiMode): boolean { - if (token.type === EMOJI_TYPE_UNICODE) { +export function isStateLoaded(state: EmojiState): state is EmojiLoadedState { + return !!state.data; +} + +/** + * Determines if the given token should be rendered as an image based on the emoji mode. + * @param state Emoji state to parse. + * @param mode Rendering mode. + * @returns Whether to render as an image. + */ +export function shouldRenderImage(state: EmojiState, mode: EmojiMode): boolean { + if (state.type === EMOJI_TYPE_UNICODE) { // If the mode is native or native with flags for non-flag emoji // we can just append the text node directly. if ( mode === EMOJI_MODE_NATIVE || (mode === EMOJI_MODE_NATIVE_WITH_FLAGS && - !stringHasUnicodeFlags(token.code)) + !stringHasUnicodeFlags(state.code)) ) { return false; } @@ -347,61 +190,3 @@ function shouldRenderImage(token: EmojiToken, mode: EmojiMode): boolean { return true; } - -function stateToImage(state: EmojiLoadedState, appState: EmojiAppState) { - const image = document.createElement('img'); - image.draggable = false; - image.classList.add('emojione'); - - if (state.type === EMOJI_TYPE_UNICODE) { - const emojiInfo = twemojiHasBorder(unicodeToTwemojiHex(state.data.hexcode)); - let fileName = emojiInfo.hexCode; - if ( - (appState.darkTheme && emojiInfo.hasDarkBorder) || - (!appState.darkTheme && emojiInfo.hasLightBorder) - ) { - fileName = `${emojiInfo.hexCode}_border`; - } - - image.alt = state.data.unicode; - image.title = state.data.label; - image.src = `${assetHost}/emoji/${fileName}.svg`; - } else { - // Custom emoji - const shortCode = `:${state.data.shortcode}:`; - image.classList.add('custom-emoji'); - image.alt = shortCode; - image.title = shortCode; - image.src = autoPlayGif ? state.data.url : state.data.static_url; - image.dataset.original = state.data.url; - image.dataset.static = state.data.static_url; - } - - return image; -} - -function renderedToHTML(renderedArray: EmojifiedTextArray): DocumentFragment; -function renderedToHTML( - renderedArray: EmojifiedTextArray, - parent: ParentType, -): ParentType; -function renderedToHTML( - renderedArray: EmojifiedTextArray, - parent: ParentNode | null = null, -) { - const fragment = parent ?? document.createDocumentFragment(); - for (const fragmentItem of renderedArray) { - if (typeof fragmentItem === 'string') { - fragment.appendChild(document.createTextNode(fragmentItem)); - } else if (fragmentItem instanceof HTMLImageElement) { - fragment.appendChild(fragmentItem); - } - } - return fragment; -} - -// Testing helpers -export const testCacheClear = () => { - cacheClear(); - localeCacheMap.clear(); -}; diff --git a/app/javascript/flavours/glitch/features/emoji/types.ts b/app/javascript/flavours/glitch/features/emoji/types.ts index a579074ad22a7f..d2133ac1db061b 100644 --- a/app/javascript/flavours/glitch/features/emoji/types.ts +++ b/app/javascript/flavours/glitch/features/emoji/types.ts @@ -4,13 +4,13 @@ import type { FlatCompactEmoji, Locale } from 'emojibase'; import type { ApiCustomEmojiJSON } from '@/flavours/glitch/api_types/custom_emoji'; import type { CustomEmoji } from '@/flavours/glitch/models/custom_emoji'; -import type { LimitedCache } from '@/flavours/glitch/utils/cache'; +import type { RequiredExcept } from '@/flavours/glitch/utils/types'; import type { + EMOJI_DB_NAME_SHORTCODES, EMOJI_MODE_NATIVE, EMOJI_MODE_NATIVE_WITH_FLAGS, EMOJI_MODE_TWEMOJI, - EMOJI_STATE_MISSING, EMOJI_TYPE_CUSTOM, EMOJI_TYPE_UNICODE, } from './constants'; @@ -21,6 +21,11 @@ export type EmojiMode = | typeof EMOJI_MODE_TWEMOJI; export type LocaleOrCustom = Locale | typeof EMOJI_TYPE_CUSTOM; +export type LocaleWithShortcodes = `${Locale}-shortcodes`; +export type EtagTypes = + | LocaleOrCustom + | typeof EMOJI_DB_NAME_SHORTCODES + | LocaleWithShortcodes; export interface EmojiAppState { locales: Locale[]; @@ -29,48 +34,39 @@ export interface EmojiAppState { darkTheme: boolean; } -export interface UnicodeEmojiToken { - type: typeof EMOJI_TYPE_UNICODE; - code: string; -} -export interface CustomEmojiToken { - type: typeof EMOJI_TYPE_CUSTOM; - code: string; -} -export type EmojiToken = UnicodeEmojiToken | CustomEmojiToken; - export type CustomEmojiData = ApiCustomEmojiJSON; export type UnicodeEmojiData = FlatCompactEmoji; export type AnyEmojiData = CustomEmojiData | UnicodeEmojiData; -export type EmojiStateMissing = typeof EMOJI_STATE_MISSING; +type CustomEmojiRenderFields = Pick< + CustomEmojiData, + 'shortcode' | 'static_url' | 'url' +>; + export interface EmojiStateUnicode { type: typeof EMOJI_TYPE_UNICODE; - data: UnicodeEmojiData; + code: string; + data?: UnicodeEmojiData; + shortcode?: string; } export interface EmojiStateCustom { type: typeof EMOJI_TYPE_CUSTOM; - data: CustomEmojiRenderFields; + code: string; + data?: CustomEmojiRenderFields; } -export type EmojiState = - | EmojiStateMissing - | EmojiStateUnicode - | EmojiStateCustom; -export type EmojiLoadedState = EmojiStateUnicode | EmojiStateCustom; +export type EmojiState = EmojiStateUnicode | EmojiStateCustom; -export type EmojiStateMap = LimitedCache; +export type EmojiLoadedState = + | RequiredExcept + | Required; export type CustomEmojiMapArg = | ExtraCustomEmojiMap - | ImmutableList; -export type CustomEmojiRenderFields = Pick< - CustomEmojiData, - 'shortcode' | 'static_url' | 'url' ->; -export type ExtraCustomEmojiMap = Record; + | ImmutableList + | CustomEmoji[] + | ApiCustomEmojiJSON[]; -export interface TwemojiBorderInfo { - hexCode: string; - hasLightBorder: boolean; - hasDarkBorder: boolean; -} +export type ExtraCustomEmojiMap = Record< + string, + Pick +>; diff --git a/app/javascript/flavours/glitch/features/emoji/unicode_to_filename.js b/app/javascript/flavours/glitch/features/emoji/unicode_to_filename.js deleted file mode 100644 index cfe5539c7b9887..00000000000000 --- a/app/javascript/flavours/glitch/features/emoji/unicode_to_filename.js +++ /dev/null @@ -1,26 +0,0 @@ -// taken from: -// https://github.com/twitter/twemoji/blob/47732c7/twemoji-generator.js#L848-L866 -export const unicodeToFilename = (str) => { - let result = ''; - let charCode = 0; - let p = 0; - let i = 0; - while (i < str.length) { - charCode = str.charCodeAt(i++); - if (p) { - if (result.length > 0) { - result += '-'; - } - result += (0x10000 + ((p - 0xD800) << 10) + (charCode - 0xDC00)).toString(16); - p = 0; - } else if (0xD800 <= charCode && charCode <= 0xDBFF) { - p = charCode; - } else { - if (result.length > 0) { - result += '-'; - } - result += charCode.toString(16); - } - } - return result; -}; diff --git a/app/javascript/flavours/glitch/features/emoji/unicode_to_unified_name.js b/app/javascript/flavours/glitch/features/emoji/unicode_to_unified_name.js deleted file mode 100644 index 15f60aa7c30e68..00000000000000 --- a/app/javascript/flavours/glitch/features/emoji/unicode_to_unified_name.js +++ /dev/null @@ -1,21 +0,0 @@ -function padLeft(str, num) { - while (str.length < num) { - str = '0' + str; - } - - return str; -} - -export const unicodeToUnifiedName = (str) => { - let output = ''; - - for (let i = 0; i < str.length; i += 2) { - if (i > 0) { - output += '-'; - } - - output += padLeft(str.codePointAt(i).toString(16).toUpperCase(), 4); - } - - return output; -}; diff --git a/app/javascript/flavours/glitch/features/emoji/unicode_utils.ts b/app/javascript/flavours/glitch/features/emoji/unicode_utils.ts new file mode 100644 index 00000000000000..04175ee9f903ec --- /dev/null +++ b/app/javascript/flavours/glitch/features/emoji/unicode_utils.ts @@ -0,0 +1,43 @@ +// taken from: +// https://github.com/twitter/twemoji/blob/47732c7/twemoji-generator.js#L848-L866 +export function unicodeToFilename(str: string) { + let result = ''; + let charCode = 0; + let p = 0; + let i = 0; + while (i < str.length) { + charCode = str.charCodeAt(i++); + if (p) { + if (result.length > 0) { + result += '-'; + } + result += (0x10000 + ((p - 0xd800) << 10) + (charCode - 0xdc00)).toString( + 16, + ); + p = 0; + } else if (0xd800 <= charCode && charCode <= 0xdbff) { + p = charCode; + } else { + if (result.length > 0) { + result += '-'; + } + result += charCode.toString(16); + } + } + return result; +} + +export function unicodeToUnifiedName(str: string) { + let output = ''; + + for (let i = 0; i < str.length; i += 2) { + if (i > 0) { + output += '-'; + } + + output += + str.codePointAt(i)?.toString(16).toUpperCase().padStart(4, '0') ?? ''; + } + + return output; +} diff --git a/app/javascript/flavours/glitch/features/emoji/utils.test.ts b/app/javascript/flavours/glitch/features/emoji/utils.test.ts index b9062294c47b14..3844c814a10166 100644 --- a/app/javascript/flavours/glitch/features/emoji/utils.test.ts +++ b/app/javascript/flavours/glitch/features/emoji/utils.test.ts @@ -1,35 +1,31 @@ -import { - stringHasAnyEmoji, - stringHasCustomEmoji, - stringHasUnicodeEmoji, - stringHasUnicodeFlags, -} from './utils'; +import { isCustomEmoji, isUnicodeEmoji, stringHasUnicodeFlags } from './utils'; -describe('stringHasUnicodeEmoji', () => { +describe('isUnicodeEmoji', () => { test.concurrent.for([ - ['only text', false], - ['text with non-emoji symbols ™©', false], - ['text with emoji 😀', true], - ['multiple emojis 😀😃😄', true], - ['emoji with skin tone 👍🏽', true], - ['emoji with ZWJ 👩‍❤️‍👨', true], - ['emoji with variation selector ✊️', true], - ['emoji with keycap 1️⃣', true], - ['emoji with flags 🇺🇸', true], - ['emoji with regional indicators 🇦🇺', true], - ['emoji with gender 👩‍⚕️', true], - ['emoji with family 👨‍👩‍👧‍👦', true], - ['emoji with zero width joiner 👩‍🔬', true], - ['emoji with non-BMP codepoint 🧑‍🚀', true], - ['emoji with combining marks 👨‍👩‍👧‍👦', true], - ['emoji with enclosing keycap #️⃣', true], - ['emoji with no visible glyph \u200D', false], - ] as const)( - 'stringHasUnicodeEmoji has emojis in "%s": %o', - ([text, expected], { expect }) => { - expect(stringHasUnicodeEmoji(text)).toBe(expected); - }, - ); + ['😊', true], + ['🇿🇼', true], + ['🏴‍☠️', true], + ['🏳️‍🌈', true], + ['foo', false], + [':smile:', false], + ['😊foo', false], + ] as const)('isUnicodeEmoji("%s") is %o', ([input, expected], { expect }) => { + expect(isUnicodeEmoji(input)).toBe(expected); + }); +}); + +describe('isCustomEmoji', () => { + test.concurrent.for([ + [':smile:', true], + [':smile_123:', true], + [':SMILE:', true], + ['😊', false], + ['foo', false], + [':smile', false], + ['smile:', false], + ] as const)('isCustomEmoji("%s") is %o', ([input, expected], { expect }) => { + expect(isCustomEmoji(input)).toBe(expected); + }); }); describe('stringHasUnicodeFlags', () => { @@ -51,27 +47,3 @@ describe('stringHasUnicodeFlags', () => { }, ); }); - -describe('stringHasCustomEmoji', () => { - test('string with custom emoji returns true', () => { - expect(stringHasCustomEmoji(':custom: :test:')).toBeTruthy(); - }); - test('string without custom emoji returns false', () => { - expect(stringHasCustomEmoji('🏳️‍🌈 :🏳️‍🌈: text ™')).toBeFalsy(); - }); -}); - -describe('stringHasAnyEmoji', () => { - test('string without any emoji or characters', () => { - expect(stringHasAnyEmoji('normal text. 12356?!')).toBeFalsy(); - }); - test('string with non-emoji characters', () => { - expect(stringHasAnyEmoji('™©')).toBeFalsy(); - }); - test('has unicode emoji', () => { - expect(stringHasAnyEmoji('🏳️‍🌈🔥🇸🇹 👩‍🔬')).toBeTruthy(); - }); - test('has custom emoji', () => { - expect(stringHasAnyEmoji(':test: :custom:')).toBeTruthy(); - }); -}); diff --git a/app/javascript/flavours/glitch/features/emoji/utils.ts b/app/javascript/flavours/glitch/features/emoji/utils.ts index 9cb177e4adfafc..4c6750f6e2b2e3 100644 --- a/app/javascript/flavours/glitch/features/emoji/utils.ts +++ b/app/javascript/flavours/glitch/features/emoji/utils.ts @@ -6,8 +6,11 @@ export function emojiLogger(segment: string) { return debug(`emojis:${segment}`); } -export function stringHasUnicodeEmoji(input: string): boolean { - return new RegExp(EMOJI_REGEX, supportedFlags()).test(input); +export function isUnicodeEmoji(input: string): boolean { + return ( + input.length > 0 && + new RegExp(`^(${EMOJI_REGEX})+$`, supportedFlags()).test(input) + ); } export function stringHasUnicodeFlags(input: string): boolean { @@ -27,12 +30,11 @@ export function stringHasUnicodeFlags(input: string): boolean { // Constant as this is supported by all browsers. const CUSTOM_EMOJI_REGEX = /:([a-z0-9_]+):/i; -export function stringHasCustomEmoji(input: string) { - return CUSTOM_EMOJI_REGEX.test(input); -} +// Use the polyfill regex or the Unicode property escapes if supported. +const EMOJI_REGEX = emojiRegexPolyfill?.source ?? '\\p{RGI_Emoji}'; -export function stringHasAnyEmoji(input: string) { - return stringHasUnicodeEmoji(input) || stringHasCustomEmoji(input); +export function isCustomEmoji(input: string): boolean { + return new RegExp(`^${CUSTOM_EMOJI_REGEX.source}$`, 'i').test(input); } export function anyEmojiRegex() { @@ -52,5 +54,3 @@ function supportedFlags(flags = '') { } return flags; } - -const EMOJI_REGEX = emojiRegexPolyfill?.source ?? '\\p{RGI_Emoji}'; diff --git a/app/javascript/flavours/glitch/features/emoji/worker.ts b/app/javascript/flavours/glitch/features/emoji/worker.ts index 6fb7d36e93624b..5602577dbe9ace 100644 --- a/app/javascript/flavours/glitch/features/emoji/worker.ts +++ b/app/javascript/flavours/glitch/features/emoji/worker.ts @@ -1,18 +1,31 @@ -import { importEmojiData, importCustomEmojiData } from './loader'; +import { EMOJI_DB_NAME_SHORTCODES, EMOJI_TYPE_CUSTOM } from './constants'; +import { + importCustomEmojiData, + importEmojiData, + importLegacyShortcodes, +} from './loader'; addEventListener('message', handleMessage); self.postMessage('ready'); // After the worker is ready, notify the main thread -function handleMessage(event: MessageEvent) { - const { data: locale } = event; +function handleMessage(event: MessageEvent<{ locale: string }>) { + const { + data: { locale }, + } = event; void loadData(locale); } async function loadData(locale: string) { - if (locale !== 'custom') { - await importEmojiData(locale); + let importCount: number | undefined; + if (locale === EMOJI_TYPE_CUSTOM) { + importCount = (await importCustomEmojiData())?.length; + } else if (locale === EMOJI_DB_NAME_SHORTCODES) { + importCount = (await importLegacyShortcodes())?.length; } else { - await importCustomEmojiData(); + importCount = (await importEmojiData(locale))?.length; + } + + if (importCount) { + self.postMessage(`loaded ${importCount} emojis into ${locale}`); } - self.postMessage(`loaded ${locale}`); } diff --git a/app/javascript/flavours/glitch/features/explore/components/author_link.jsx b/app/javascript/flavours/glitch/features/explore/components/author_link.jsx deleted file mode 100644 index 9dad72b48aca1e..00000000000000 --- a/app/javascript/flavours/glitch/features/explore/components/author_link.jsx +++ /dev/null @@ -1,23 +0,0 @@ -import PropTypes from 'prop-types'; - -import { Avatar } from 'flavours/glitch/components/avatar'; -import { useAppSelector } from 'flavours/glitch/store'; -import { LinkedDisplayName } from '../../../components/display_name'; - -export const AuthorLink = ({ accountId }) => { - const account = useAppSelector(state => state.getIn(['accounts', accountId])); - - if (!account) { - return null; - } - - return ( - - - - ); -}; - -AuthorLink.propTypes = { - accountId: PropTypes.string.isRequired, -}; diff --git a/app/javascript/flavours/glitch/features/explore/components/author_link.tsx b/app/javascript/flavours/glitch/features/explore/components/author_link.tsx new file mode 100644 index 00000000000000..fefd5803a28ce3 --- /dev/null +++ b/app/javascript/flavours/glitch/features/explore/components/author_link.tsx @@ -0,0 +1,22 @@ +import type { FC } from 'react'; + +import { LinkedDisplayName } from '@/flavours/glitch/components/display_name'; +import { Avatar } from 'flavours/glitch/components/avatar'; +import { useAppSelector } from 'flavours/glitch/store'; + +export const AuthorLink: FC<{ accountId: string }> = ({ accountId }) => { + const account = useAppSelector((state) => state.accounts.get(accountId)); + + if (!account) { + return null; + } + + return ( + + + + ); +}; diff --git a/app/javascript/flavours/glitch/features/favourites/index.jsx b/app/javascript/flavours/glitch/features/favourites/index.jsx index 9e0b79ab5f1c2b..2c6289af3a24a5 100644 --- a/app/javascript/flavours/glitch/features/favourites/index.jsx +++ b/app/javascript/flavours/glitch/features/favourites/index.jsx @@ -43,7 +43,7 @@ class Favourites extends ImmutablePureComponent { intl: PropTypes.object.isRequired, }; - UNSAFE_componentWillMount () { + componentDidMount () { if (!this.props.accountIds) { this.props.dispatch(fetchFavourites(this.props.params.statusId)); } diff --git a/app/javascript/flavours/glitch/features/firehose/index.jsx b/app/javascript/flavours/glitch/features/firehose/index.jsx index 84b87b8b2ae60a..4d70642e3c4520 100644 --- a/app/javascript/flavours/glitch/features/firehose/index.jsx +++ b/app/javascript/flavours/glitch/features/firehose/index.jsx @@ -14,7 +14,8 @@ import { connectPublicStream, connectCommunityStream } from 'flavours/glitch/act import { expandPublicTimeline, expandCommunityTimeline } from 'flavours/glitch/actions/timelines'; import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner'; import SettingText from 'flavours/glitch/components/setting_text'; -import { domain } from 'flavours/glitch/initial_state'; +import { localLiveFeedAccess, remoteLiveFeedAccess, domain } from 'flavours/glitch/initial_state'; +import { canViewFeed } from 'flavours/glitch/permissions'; import { useAppDispatch, useAppSelector } from 'flavours/glitch/store'; import Column from '../../components/column'; @@ -24,6 +25,14 @@ import StatusListContainer from '../ui/containers/status_list_container'; const messages = defineMessages({ title: { id: 'column.firehose', defaultMessage: 'Live feeds' }, + title_local: { + id: 'column.firehose_local', + defaultMessage: 'Live feed for this server', + }, + title_singular: { + id: 'column.firehose_singular', + defaultMessage: 'Live feed', + }, filter_regex: { id: 'home.column_settings.filter_regex', defaultMessage: 'Filter out by regular expressions' }, }); @@ -75,7 +84,7 @@ const ColumnSettings = () => { const Firehose = ({ feedType, multiColumn }) => { const dispatch = useAppDispatch(); const intl = useIntl(); - const { signedIn } = useIdentity(); + const { signedIn, permissions } = useIdentity(); const columnRef = useRef(null); const allowLocalOnly = useAppSelector((state) => state.getIn(['settings', 'firehose', 'allowLocalOnly'])); @@ -177,13 +186,32 @@ const Firehose = ({ feedType, multiColumn }) => { /> ); + const canViewSelectedFeed = canViewFeed(signedIn, permissions, feedType === 'community' ? localLiveFeedAccess : remoteLiveFeedAccess); + + const disabledTimelineMessage = ( + + ); + + let title; + + if (canViewFeed(signedIn, permissions, localLiveFeedAccess) && canViewFeed(signedIn, permissions, remoteLiveFeedAccess)) { + title = messages.title; + } else if (canViewFeed(signedIn, permissions, localLiveFeedAccess)) { + title = messages.title_local; + } else { + title = messages.title_singular; + } + return ( { -
- - - + {(canViewFeed(signedIn, permissions, localLiveFeedAccess) && canViewFeed(signedIn, permissions, remoteLiveFeedAccess)) && ( +
+ + + - - - + + + - - - -
+ + + +
+ )} { onLoadMore={handleLoadMore} trackScroll scrollKey='firehose' - emptyMessage={emptyMessage} + emptyMessage={canViewSelectedFeed ? emptyMessage : disabledTimelineMessage} bindToDocument={!multiColumn} regex={regex} /> diff --git a/app/javascript/flavours/glitch/features/follow_requests/components/account_authorize.jsx b/app/javascript/flavours/glitch/features/follow_requests/components/account_authorize.jsx index 877d84c632478b..079b742c5a3571 100644 --- a/app/javascript/flavours/glitch/features/follow_requests/components/account_authorize.jsx +++ b/app/javascript/flavours/glitch/features/follow_requests/components/account_authorize.jsx @@ -8,10 +8,11 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import CheckIcon from '@/material-icons/400-24px/check.svg?react'; import CloseIcon from '@/material-icons/400-24px/close.svg?react'; -import { Avatar } from '../../../components/avatar'; -import { DisplayName } from '../../../components/display_name'; -import { IconButton } from '../../../components/icon_button'; -import { Permalink } from '../../../components/permalink'; +import { Avatar } from '@/flavours/glitch/components/avatar'; +import { DisplayName } from '@/flavours/glitch/components/display_name'; +import { IconButton } from '@/flavours/glitch/components/icon_button'; +import { EmojiHTML } from '@/flavours/glitch/components/emoji/html'; +import { Permalink } from '@/flavours/glitch/components/permalink'; const messages = defineMessages({ authorize: { id: 'follow_request.authorize', defaultMessage: 'Authorize' }, @@ -29,7 +30,6 @@ class AccountAuthorize extends ImmutablePureComponent { render () { const { intl, account, onAuthorize, onReject } = this.props; - const content = { __html: account.get('note_emojified') }; return (
@@ -39,7 +39,11 @@ class AccountAuthorize extends ImmutablePureComponent { -
+
diff --git a/app/javascript/flavours/glitch/features/follow_requests/index.jsx b/app/javascript/flavours/glitch/features/follow_requests/index.jsx index 7d651f2ca69c24..91648412b55799 100644 --- a/app/javascript/flavours/glitch/features/follow_requests/index.jsx +++ b/app/javascript/flavours/glitch/features/follow_requests/index.jsx @@ -45,7 +45,7 @@ class FollowRequests extends ImmutablePureComponent { multiColumn: PropTypes.bool, }; - UNSAFE_componentWillMount () { + componentDidMount () { this.props.dispatch(fetchFollowRequests()); } diff --git a/app/javascript/flavours/glitch/features/getting_started/components/announcements.jsx b/app/javascript/flavours/glitch/features/getting_started/components/announcements.jsx deleted file mode 100644 index 5bfba51957c179..00000000000000 --- a/app/javascript/flavours/glitch/features/getting_started/components/announcements.jsx +++ /dev/null @@ -1,444 +0,0 @@ -import PropTypes from 'prop-types'; -import { PureComponent, useCallback, useMemo } from 'react'; - -import { defineMessages, injectIntl, FormattedMessage, FormattedDate } from 'react-intl'; - -import classNames from 'classnames'; -import { withRouter } from 'react-router-dom'; - -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; - -import { animated, useTransition } from '@react-spring/web'; -import ReactSwipeableViews from 'react-swipeable-views'; - -import elephantUIPlane from '@/images/elephant_ui_plane.svg'; -import AddIcon from '@/material-icons/400-24px/add.svg?react'; -import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react'; -import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'; -import { AnimatedNumber } from 'flavours/glitch/components/animated_number'; -import { Icon } from 'flavours/glitch/components/icon'; -import { IconButton } from 'flavours/glitch/components/icon_button'; -import EmojiPickerDropdown from 'flavours/glitch/features/compose/containers/emoji_picker_dropdown_container'; -import { unicodeMapping } from 'flavours/glitch/features/emoji/emoji_unicode_mapping_light'; -import { autoPlayGif, reduceMotion, disableSwiping, mascot } from 'flavours/glitch/initial_state'; -import { assetHost } from 'flavours/glitch/utils/config'; -import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; - -const messages = defineMessages({ - close: { id: 'lightbox.close', defaultMessage: 'Close' }, - previous: { id: 'lightbox.previous', defaultMessage: 'Previous' }, - next: { id: 'lightbox.next', defaultMessage: 'Next' }, -}); - -class ContentWithRouter extends ImmutablePureComponent { - static propTypes = { - announcement: ImmutablePropTypes.map.isRequired, - ...WithRouterPropTypes, - }; - - setRef = c => { - this.node = c; - }; - - componentDidMount () { - this._updateLinks(); - } - - componentDidUpdate () { - this._updateLinks(); - } - - _updateLinks () { - const node = this.node; - - if (!node) { - return; - } - - const links = node.querySelectorAll('a'); - - for (var i = 0; i < links.length; ++i) { - let link = links[i]; - - if (link.classList.contains('status-link')) { - continue; - } - - link.classList.add('status-link'); - - let mention = this.props.announcement.get('mentions').find(item => link.href === item.get('url')); - - if (mention) { - link.addEventListener('click', this.onMentionClick.bind(this, mention), false); - link.setAttribute('title', mention.get('acct')); - } else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) { - link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false); - } else { - let status = this.props.announcement.get('statuses').find(item => link.href === item.get('url')); - if (status) { - link.addEventListener('click', this.onStatusClick.bind(this, status), false); - } - link.setAttribute('title', link.href); - link.classList.add('unhandled-link'); - } - - link.setAttribute('target', '_blank'); - link.setAttribute('rel', 'noopener'); - } - } - - onMentionClick = (mention, e) => { - if (this.props.history && e.button === 0 && !(e.ctrlKey || e.metaKey)) { - e.preventDefault(); - this.props.history.push(`/@${mention.get('acct')}`); - } - }; - - onHashtagClick = (hashtag, e) => { - hashtag = hashtag.replace(/^#/, ''); - - if (this.props.history&& e.button === 0 && !(e.ctrlKey || e.metaKey)) { - e.preventDefault(); - this.props.history.push(`/tags/${hashtag}`); - } - }; - - onStatusClick = (status, e) => { - if (this.props.history && e.button === 0 && !(e.ctrlKey || e.metaKey)) { - e.preventDefault(); - this.props.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`); - } - }; - - render () { - const { announcement } = this.props; - - return ( -
- ); - } - -} - -const Content = withRouter(ContentWithRouter); - -class Emoji extends PureComponent { - - static propTypes = { - emoji: PropTypes.string.isRequired, - emojiMap: ImmutablePropTypes.map.isRequired, - hovered: PropTypes.bool.isRequired, - }; - - render () { - const { emoji, emojiMap, hovered } = this.props; - - if (unicodeMapping[emoji]) { - const { filename, shortCode } = unicodeMapping[this.props.emoji]; - const title = shortCode ? `:${shortCode}:` : ''; - - return ( - {emoji} - ); - } else if (emojiMap.get(emoji)) { - const filename = (autoPlayGif || hovered) ? emojiMap.getIn([emoji, 'url']) : emojiMap.getIn([emoji, 'static_url']); - const shortCode = `:${emoji}:`; - - return ( - {shortCode} - ); - } else { - return null; - } - } - -} - -class Reaction extends ImmutablePureComponent { - - static propTypes = { - announcementId: PropTypes.string.isRequired, - reaction: ImmutablePropTypes.map.isRequired, - addReaction: PropTypes.func.isRequired, - removeReaction: PropTypes.func.isRequired, - emojiMap: ImmutablePropTypes.map.isRequired, - style: PropTypes.object, - }; - - state = { - hovered: false, - }; - - handleClick = () => { - const { reaction, announcementId, addReaction, removeReaction } = this.props; - - if (reaction.get('me')) { - removeReaction(announcementId, reaction.get('name')); - } else { - addReaction(announcementId, reaction.get('name')); - } - }; - - handleMouseEnter = () => this.setState({ hovered: true }); - - handleMouseLeave = () => this.setState({ hovered: false }); - - render () { - const { reaction } = this.props; - - let shortCode = reaction.get('name'); - - if (unicodeMapping[shortCode]) { - shortCode = unicodeMapping[shortCode].shortCode; - } - - return ( - - - - - - - - - ); - } - -} - -const ReactionsBar = ({ - announcementId, - reactions, - emojiMap, - addReaction, - removeReaction, -}) => { - const visibleReactions = useMemo(() => reactions.filter(x => x.get('count') > 0).toArray(), [reactions]); - - const handleEmojiPick = useCallback((emoji) => { - addReaction(announcementId, emoji.native.replaceAll(/:/g, '')); - }, [addReaction, announcementId]); - - const transitions = useTransition(visibleReactions, { - from: { - scale: 0, - }, - enter: { - scale: 1, - }, - leave: { - scale: 0, - }, - keys: visibleReactions.map(x => x.get('name')), - }); - - return ( -
- {transitions(({ scale }, reaction) => ( - `scale(${s})`) }} - addReaction={addReaction} - removeReaction={removeReaction} - announcementId={announcementId} - emojiMap={emojiMap} - /> - ))} - - {visibleReactions.length < 8 && ( - } - /> - )} -
- ); -}; -ReactionsBar.propTypes = { - announcementId: PropTypes.string.isRequired, - reactions: ImmutablePropTypes.list.isRequired, - addReaction: PropTypes.func.isRequired, - removeReaction: PropTypes.func.isRequired, - emojiMap: ImmutablePropTypes.map.isRequired, -}; - -class Announcement extends ImmutablePureComponent { - - static propTypes = { - announcement: ImmutablePropTypes.map.isRequired, - emojiMap: ImmutablePropTypes.map.isRequired, - addReaction: PropTypes.func.isRequired, - removeReaction: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - selected: PropTypes.bool, - }; - - state = { - unread: !this.props.announcement.get('read'), - }; - - componentDidUpdate () { - const { selected, announcement } = this.props; - if (!selected && this.state.unread !== !announcement.get('read')) { - this.setState({ unread: !announcement.get('read') }); - } - } - - render () { - const { announcement } = this.props; - const { unread } = this.state; - const startsAt = announcement.get('starts_at') && new Date(announcement.get('starts_at')); - const endsAt = announcement.get('ends_at') && new Date(announcement.get('ends_at')); - const now = new Date(); - const hasTimeRange = startsAt && endsAt; - const skipYear = hasTimeRange && startsAt.getFullYear() === endsAt.getFullYear() && endsAt.getFullYear() === now.getFullYear(); - const skipEndDate = hasTimeRange && startsAt.getDate() === endsAt.getDate() && startsAt.getMonth() === endsAt.getMonth() && startsAt.getFullYear() === endsAt.getFullYear(); - const skipTime = announcement.get('all_day'); - - return ( -
- - - {hasTimeRange && · - } - - - - - - - {unread && } -
- ); - } - -} - -class Announcements extends ImmutablePureComponent { - - static propTypes = { - announcements: ImmutablePropTypes.list, - emojiMap: ImmutablePropTypes.map.isRequired, - dismissAnnouncement: PropTypes.func.isRequired, - addReaction: PropTypes.func.isRequired, - removeReaction: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - }; - - state = { - index: 0, - }; - - static getDerivedStateFromProps(props, state) { - if (props.announcements.size > 0 && state.index >= props.announcements.size) { - return { index: props.announcements.size - 1 }; - } else { - return null; - } - } - - componentDidMount () { - this._markAnnouncementAsRead(); - } - - componentDidUpdate () { - this._markAnnouncementAsRead(); - } - - _markAnnouncementAsRead () { - const { dismissAnnouncement, announcements } = this.props; - const { index } = this.state; - const announcement = announcements.get(announcements.size - 1 - index); - if (!announcement.get('read')) dismissAnnouncement(announcement.get('id')); - } - - handleChangeIndex = index => { - this.setState({ index: index % this.props.announcements.size }); - }; - - handleNextClick = () => { - this.setState({ index: (this.state.index + 1) % this.props.announcements.size }); - }; - - handlePrevClick = () => { - this.setState({ index: (this.props.announcements.size + this.state.index - 1) % this.props.announcements.size }); - }; - - render () { - const { announcements, intl } = this.props; - const { index } = this.state; - - if (announcements.isEmpty()) { - return null; - } - - return ( -
- - -
- - {announcements.map((announcement, idx) => ( - - )).reverse()} - - - {announcements.size > 1 && ( -
- - {index + 1} / {announcements.size} - -
- )} -
-
- ); - } - -} - -export default injectIntl(Announcements); diff --git a/app/javascript/flavours/glitch/features/getting_started/containers/announcements_container.js b/app/javascript/flavours/glitch/features/getting_started/containers/announcements_container.js deleted file mode 100644 index 2a504a3094a6d5..00000000000000 --- a/app/javascript/flavours/glitch/features/getting_started/containers/announcements_container.js +++ /dev/null @@ -1,23 +0,0 @@ -import { createSelector } from '@reduxjs/toolkit'; -import { Map as ImmutableMap } from 'immutable'; -import { connect } from 'react-redux'; - - -import { addReaction, removeReaction, dismissAnnouncement } from 'flavours/glitch/actions/announcements'; - -import Announcements from '../components/announcements'; - -const customEmojiMap = createSelector([state => state.get('custom_emojis')], items => items.reduce((map, emoji) => map.set(emoji.get('shortcode'), emoji), ImmutableMap())); - -const mapStateToProps = state => ({ - announcements: state.getIn(['announcements', 'items']), - emojiMap: customEmojiMap(state), -}); - -const mapDispatchToProps = dispatch => ({ - dismissAnnouncement: id => dispatch(dismissAnnouncement(id)), - addReaction: (id, name) => dispatch(addReaction(id, name)), - removeReaction: (id, name) => dispatch(removeReaction(id, name)), -}); - -export default connect(mapStateToProps, mapDispatchToProps)(Announcements); diff --git a/app/javascript/flavours/glitch/features/hashtag_timeline/components/hashtag_header.tsx b/app/javascript/flavours/glitch/features/hashtag_timeline/components/hashtag_header.tsx index a9013ea0fad938..bc15cee0d80bf8 100644 --- a/app/javascript/flavours/glitch/features/hashtag_timeline/components/hashtag_header.tsx +++ b/app/javascript/flavours/glitch/features/hashtag_timeline/components/hashtag_header.tsx @@ -197,13 +197,16 @@ export const HashtagHeader: React.FC<{ /> )} -
diff --git a/app/javascript/flavours/glitch/features/hashtag_timeline/index.jsx b/app/javascript/flavours/glitch/features/hashtag_timeline/index.jsx index 48335488f07575..4483d7a760765a 100644 --- a/app/javascript/flavours/glitch/features/hashtag_timeline/index.jsx +++ b/app/javascript/flavours/glitch/features/hashtag_timeline/index.jsx @@ -16,15 +16,23 @@ import { expandHashtagTimeline, clearTimeline } from 'flavours/glitch/actions/ti import Column from 'flavours/glitch/components/column'; import ColumnHeader from 'flavours/glitch/components/column_header'; import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context'; +import { remoteTopicFeedAccess, me, localTopicFeedAccess } from 'flavours/glitch/initial_state'; import StatusListContainer from '../ui/containers/status_list_container'; import { HashtagHeader } from './components/hashtag_header'; import ColumnSettingsContainer from './containers/column_settings_container'; -const mapStateToProps = (state, props) => ({ - hasUnread: state.getIn(['timelines', `hashtag:${props.params.id}${props.params.local ? ':local' : ''}`, 'unread']) > 0, -}); +const mapStateToProps = (state, props) => { + const local = props.params.local || (!me && remoteTopicFeedAccess !== 'public'); + const hasFeedAccess = !!me || localTopicFeedAccess === 'public'; + + return ({ + local, + hasFeedAccess, + hasUnread: state.getIn(['timelines', `hashtag:${props.params.id}${local ? ':local' : ''}`, 'unread']) > 0, + }); +}; class HashtagTimeline extends PureComponent { disconnects = []; @@ -113,19 +121,21 @@ class HashtagTimeline extends PureComponent { } _unload () { - const { dispatch } = this.props; - const { id, local } = this.props.params; + const { dispatch, local } = this.props; + const { id } = this.props.params; this._unsubscribe(); dispatch(clearTimeline(`hashtag:${id}${local ? ':local' : ''}`)); } _load() { - const { dispatch } = this.props; - const { id, tags, local } = this.props.params; + const { dispatch, local, hasFeedAccess } = this.props; + const { id, tags } = this.props.params; - this._subscribe(dispatch, id, tags, local); - dispatch(expandHashtagTimeline(id, { tags, local })); + if (hasFeedAccess) { + this._subscribe(dispatch, id, tags, local); + dispatch(expandHashtagTimeline(id, { tags, local })); + } } componentDidMount () { @@ -133,10 +143,10 @@ class HashtagTimeline extends PureComponent { } componentDidUpdate (prevProps) { - const { params } = this.props; - const { id, tags, local } = prevProps.params; + const { params, local } = this.props; + const { id, tags } = prevProps.params; - if (id !== params.id || !isEqual(tags, params.tags) || !isEqual(local, params.local)) { + if (id !== params.id || !isEqual(tags, params.tags) || !isEqual(local, prevProps.local)) { this._unload(); this._load(); } @@ -151,15 +161,15 @@ class HashtagTimeline extends PureComponent { }; handleLoadMore = maxId => { - const { dispatch, params } = this.props; - const { id, tags, local } = params; + const { dispatch, params, local } = this.props; + const { id, tags } = params; dispatch(expandHashtagTimeline(id, { maxId, tags, local })); }; render () { - const { hasUnread, columnId, multiColumn } = this.props; - const { id, local } = this.props.params; + const { hasUnread, columnId, multiColumn, local, hasFeedAccess } = this.props; + const { id } = this.props.params; const pinned = !!columnId; return ( @@ -186,7 +196,20 @@ class HashtagTimeline extends PureComponent { scrollKey={`hashtag_timeline-${columnId}`} timelineId={`hashtag:${id}${local ? ':local' : ''}`} onLoadMore={this.handleLoadMore} - emptyMessage={} + initialLoadingState={hasFeedAccess} + emptyMessage={ + hasFeedAccess ? ( + + ) : ( + + ) + } bindToDocument={!multiColumn} /> diff --git a/app/javascript/flavours/glitch/features/home_timeline/components/announcements/announcement.tsx b/app/javascript/flavours/glitch/features/home_timeline/components/announcements/announcement.tsx new file mode 100644 index 00000000000000..e37aa1317d7946 --- /dev/null +++ b/app/javascript/flavours/glitch/features/home_timeline/components/announcements/announcement.tsx @@ -0,0 +1,136 @@ +import type { FC } from 'react'; +import { useEffect, useState } from 'react'; + +import { FormattedDate, FormattedMessage } from 'react-intl'; + +import { dismissAnnouncement } from '@/flavours/glitch/actions/announcements'; +import type { ApiAnnouncementJSON } from '@/flavours/glitch/api_types/announcements'; +import { AnimateEmojiProvider } from '@/flavours/glitch/components/emoji/context'; +import { EmojiHTML } from '@/flavours/glitch/components/emoji/html'; +import { useAppDispatch } from '@/flavours/glitch/store'; + +import { ReactionsBar } from './reactions'; + +export interface IAnnouncement extends ApiAnnouncementJSON { + contentHtml: string; +} + +interface AnnouncementProps { + announcement: IAnnouncement; + active?: boolean; +} + +export const Announcement: FC = ({ + announcement, + active, +}) => { + const { read, id } = announcement; + + // Dismiss announcement when it becomes active. + const dispatch = useAppDispatch(); + useEffect(() => { + if (active && !read) { + dispatch(dismissAnnouncement(id)); + } + }, [active, id, dispatch, read]); + + // But visually show the announcement as read only when it goes out of view. + const [isVisuallyRead, setIsVisuallyRead] = useState(read); + const [previousActive, setPreviousActive] = useState(active); + if (active !== previousActive) { + setPreviousActive(active); + + // This marks the announcement as read in the UI only after it + // went from active to inactive. + if (!active && isVisuallyRead !== read) { + setIsVisuallyRead(read); + } + } + + return ( + + + + + {' · '} + + + + + + + + + {!isVisuallyRead && } + + ); +}; + +const Timestamp: FC> = ({ + announcement, +}) => { + const startsAt = announcement.starts_at && new Date(announcement.starts_at); + const endsAt = announcement.ends_at && new Date(announcement.ends_at); + const now = new Date(); + const hasTimeRange = startsAt && endsAt; + const skipTime = announcement.all_day; + + if (hasTimeRange) { + const skipYear = + startsAt.getFullYear() === endsAt.getFullYear() && + endsAt.getFullYear() === now.getFullYear(); + const skipEndDate = + startsAt.getDate() === endsAt.getDate() && + startsAt.getMonth() === endsAt.getMonth() && + startsAt.getFullYear() === endsAt.getFullYear(); + return ( + <> + {' '} + -{' '} + + + ); + } + const publishedAt = new Date(announcement.published_at); + return ( + + ); +}; diff --git a/app/javascript/flavours/glitch/features/home_timeline/components/announcements/index.tsx b/app/javascript/flavours/glitch/features/home_timeline/components/announcements/index.tsx new file mode 100644 index 00000000000000..bc049e6740925f --- /dev/null +++ b/app/javascript/flavours/glitch/features/home_timeline/components/announcements/index.tsx @@ -0,0 +1,64 @@ +import { useCallback } from 'react'; +import type { FC } from 'react'; + +import type { Map, List } from 'immutable'; + +import type { RenderSlideFn } from '@/flavours/glitch/components/carousel'; +import { Carousel } from '@/flavours/glitch/components/carousel'; +import { CustomEmojiProvider } from '@/flavours/glitch/components/emoji/context'; +import { mascot } from '@/flavours/glitch/initial_state'; +import { createAppSelector, useAppSelector } from '@/flavours/glitch/store'; +import elephantUIPlane from '@/images/elephant_ui_plane.svg'; + +import type { IAnnouncement } from './announcement'; +import { Announcement } from './announcement'; + +const announcementSelector = createAppSelector( + [(state) => state.announcements as Map>>], + (announcements) => + ((announcements.get('items')?.toJS() as IAnnouncement[] | undefined) ?? []) + .map((announcement) => ({ announcement, id: announcement.id })) + .toReversed(), +); + +export const Announcements: FC = () => { + const announcements = useAppSelector(announcementSelector); + const emojis = useAppSelector((state) => state.custom_emojis); + + const renderSlide: RenderSlideFn<{ + id: string; + announcement: IAnnouncement; + }> = useCallback( + (item, active) => ( + + ), + [], + ); + + if (announcements.length === 0) { + return null; + } + + return ( +
+ + + + + +
+ ); +}; diff --git a/app/javascript/flavours/glitch/features/home_timeline/components/announcements/reactions.tsx b/app/javascript/flavours/glitch/features/home_timeline/components/announcements/reactions.tsx new file mode 100644 index 00000000000000..9efd14e0bc9323 --- /dev/null +++ b/app/javascript/flavours/glitch/features/home_timeline/components/announcements/reactions.tsx @@ -0,0 +1,111 @@ +import { useCallback, useMemo } from 'react'; +import type { FC, HTMLAttributes } from 'react'; + +import classNames from 'classnames'; + +import type { AnimatedProps } from '@react-spring/web'; +import { animated, useTransition } from '@react-spring/web'; + +import { + addReaction, + removeReaction, +} from '@/flavours/glitch/actions/announcements'; +import type { ApiAnnouncementReactionJSON } from '@/flavours/glitch/api_types/announcements'; +import { AnimatedNumber } from '@/flavours/glitch/components/animated_number'; +import { Emoji } from '@/flavours/glitch/components/emoji'; +import { Icon } from '@/flavours/glitch/components/icon'; +import EmojiPickerDropdown from '@/flavours/glitch/features/compose/containers/emoji_picker_dropdown_container'; +import { isUnicodeEmoji } from '@/flavours/glitch/features/emoji/utils'; +import { useAppDispatch } from '@/flavours/glitch/store'; +import AddIcon from '@/material-icons/400-24px/add.svg?react'; + +export const ReactionsBar: FC<{ + reactions: ApiAnnouncementReactionJSON[]; + id: string; +}> = ({ reactions, id }) => { + const visibleReactions = useMemo( + () => reactions.filter((x) => x.count > 0), + [reactions], + ); + + const dispatch = useAppDispatch(); + const handleEmojiPick = useCallback( + (emoji: { native: string }) => { + dispatch(addReaction(id, emoji.native.replaceAll(/:/g, ''))); + }, + [dispatch, id], + ); + + const transitions = useTransition(visibleReactions, { + from: { + scale: 0, + }, + enter: { + scale: 1, + }, + leave: { + scale: 0, + }, + keys: visibleReactions.map((x) => x.name), + }); + + return ( +
+ {transitions(({ scale }, reaction) => ( + `scale(${s})`) }} + id={id} + /> + ))} + + {visibleReactions.length < 8 && ( + } + /> + )} +
+ ); +}; + +const Reaction: FC<{ + reaction: ApiAnnouncementReactionJSON; + id: string; + style: AnimatedProps>['style']; +}> = ({ id, reaction, style }) => { + const dispatch = useAppDispatch(); + const handleClick = useCallback(() => { + if (reaction.me) { + dispatch(removeReaction(id, reaction.name)); + } else { + dispatch(addReaction(id, reaction.name)); + } + }, [dispatch, id, reaction.me, reaction.name]); + + const code = isUnicodeEmoji(reaction.name) + ? reaction.name + : `:${reaction.name}:`; + + return ( + + + + + + + + + ); +}; diff --git a/app/javascript/flavours/glitch/features/home_timeline/components/critical_update_banner.tsx b/app/javascript/flavours/glitch/features/home_timeline/components/critical_update_banner.tsx index d0dd2b6acda66f..774f1b629102cf 100644 --- a/app/javascript/flavours/glitch/features/home_timeline/components/critical_update_banner.tsx +++ b/app/javascript/flavours/glitch/features/home_timeline/components/critical_update_banner.tsx @@ -1,26 +1,34 @@ +import type { FC } from 'react'; + import { FormattedMessage } from 'react-intl'; -export const CriticalUpdateBanner = () => ( -
-
-

+import { criticalUpdatesPending } from '@/flavours/glitch/initial_state'; + +export const CriticalUpdateBanner: FC = () => { + if (!criticalUpdatesPending) { + return null; + } + return ( +
+
-

-

- {' '} - +

- -

+ id='home.pending_critical_update.body' + defaultMessage='Please update your Mastodon server as soon as possible!' + />{' '} + + + +

+
-
-); + ); +}; diff --git a/app/javascript/flavours/glitch/features/home_timeline/components/inline_follow_suggestions.tsx b/app/javascript/flavours/glitch/features/home_timeline/components/inline_follow_suggestions.tsx index e5aa8d0297874f..677e8edf050a41 100644 --- a/app/javascript/flavours/glitch/features/home_timeline/components/inline_follow_suggestions.tsx +++ b/app/javascript/flavours/glitch/features/home_timeline/components/inline_follow_suggestions.tsx @@ -25,8 +25,6 @@ import { domain } from 'flavours/glitch/initial_state'; import { useAppDispatch, useAppSelector } from 'flavours/glitch/store'; const messages = defineMessages({ - follow: { id: 'account.follow', defaultMessage: 'Follow' }, - unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, previous: { id: 'lightbox.previous', defaultMessage: 'Previous' }, next: { id: 'lightbox.next', defaultMessage: 'Next' }, dismiss: { @@ -272,7 +270,7 @@ export const InlineFollowSuggestions: React.FC<{ hidden?: boolean }> = ({
- +
+ + ); +}; + +const NewListWrapper: React.FC<{ + multiColumn?: boolean; +}> = ({ multiColumn }) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + const { id } = useParams<{ id?: string }>(); + const list = useAppSelector((state) => + id ? state.lists.get(id) : undefined, + ); + + useEffect(() => { + if (id) { + dispatch(fetchList(id)); + } + }, [dispatch, id]); + + const isLoading = id && !list; + return (
-
-
-
-
- - -
- -
-
-
-
- -
-
-
- - -
- -
-
-
-
- - {id && ( -
- -
- )} - -
- {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */} - -
- -
- -
-
+ {isLoading ? : }
@@ -298,4 +306,4 @@ const NewList: React.FC<{ }; // eslint-disable-next-line import/no-default-export -export default NewList; +export default NewListWrapper; diff --git a/app/javascript/flavours/glitch/features/mutes/index.jsx b/app/javascript/flavours/glitch/features/mutes/index.jsx index 26ebff17f40ca5..476606abf0a212 100644 --- a/app/javascript/flavours/glitch/features/mutes/index.jsx +++ b/app/javascript/flavours/glitch/features/mutes/index.jsx @@ -40,7 +40,7 @@ class Mutes extends ImmutablePureComponent { multiColumn: PropTypes.bool, }; - UNSAFE_componentWillMount () { + componentDidMount () { this.props.dispatch(fetchMutes()); } diff --git a/app/javascript/flavours/glitch/features/navigation_panel/components/list_panel.tsx b/app/javascript/flavours/glitch/features/navigation_panel/components/list_panel.tsx index ce6ca71b162c12..3c232684d567ac 100644 --- a/app/javascript/flavours/glitch/features/navigation_panel/components/list_panel.tsx +++ b/app/javascript/flavours/glitch/features/navigation_panel/components/list_panel.tsx @@ -27,17 +27,15 @@ export const ListPanel: React.FC = () => { const intl = useIntl(); const dispatch = useAppDispatch(); const lists = useAppSelector((state) => getOrderedLists(state)); - const [loading, setLoading] = useState(false); + const [loading, setLoading] = useState(true); useEffect(() => { - setLoading(true); - void dispatch(fetchLists()).then(() => { setLoading(false); return ''; }); - }, [dispatch, setLoading]); + }, [dispatch]); return ( { return ( - diff --git a/app/javascript/flavours/glitch/features/notifications_v2/components/embedded_status.tsx b/app/javascript/flavours/glitch/features/notifications_v2/components/embedded_status.tsx index edfb82403c7588..4db1dfa0da2a26 100644 --- a/app/javascript/flavours/glitch/features/notifications_v2/components/embedded_status.tsx +++ b/app/javascript/flavours/glitch/features/notifications_v2/components/embedded_status.tsx @@ -6,6 +6,8 @@ import { useHistory } from 'react-router-dom'; import type { List as ImmutableList, RecordOf } from 'immutable'; +import type { ApiMentionJSON } from '@/flavours/glitch/api_types/statuses'; +import { AnimateEmojiProvider } from '@/flavours/glitch/components/emoji/context'; import BarChart4BarsIcon from '@/material-icons/400-24px/bar_chart_4_bars.svg?react'; import PhotoLibraryIcon from '@/material-icons/400-24px/photo_library.svg?react'; import { toggleStatusSpoilers } from 'flavours/glitch/actions/statuses'; @@ -17,7 +19,7 @@ import { useAppSelector, useAppDispatch } from 'flavours/glitch/store'; import { EmbeddedStatusContent } from './embedded_status_content'; -export type Mention = RecordOf<{ url: string; acct: string }>; +export type Mention = RecordOf; export const EmbeddedStatus: React.FC<{ statusId: string }> = ({ statusId, @@ -85,19 +87,16 @@ export const EmbeddedStatus: React.FC<{ statusId: string }> = ({ } // Assign status attributes to variables with a forced type, as status is not yet properly typed - const contentHtml = status.get('contentHtml') as string; - const contentWarning = status.get('spoilerHtml') as string; + const hasContentWarning = !!status.get('spoiler_text'); const poll = status.get('poll'); - const language = status.get('language') as string; - const mentions = status.get('mentions') as ImmutableList; - const expanded = !status.get('hidden') || !contentWarning; + const expanded = !status.get('hidden') || !hasContentWarning; const mediaAttachmentsSize = ( status.get('media_attachments') as ImmutableList ).size; return ( -
= ({
- {contentWarning && ( - - )} + - {(!contentWarning || expanded) && ( + {(!hasContentWarning || expanded) && ( )} @@ -148,6 +143,6 @@ export const EmbeddedStatus: React.FC<{ statusId: string }> = ({ )}
)} -
+ ); }; diff --git a/app/javascript/flavours/glitch/features/notifications_v2/components/embedded_status_content.tsx b/app/javascript/flavours/glitch/features/notifications_v2/components/embedded_status_content.tsx index 1a38be536ba6a8..52e719ec63bae3 100644 --- a/app/javascript/flavours/glitch/features/notifications_v2/components/embedded_status_content.tsx +++ b/app/javascript/flavours/glitch/features/notifications_v2/components/embedded_status_content.tsx @@ -1,93 +1,40 @@ -import { useCallback } from 'react'; - -import { useHistory } from 'react-router-dom'; +import { useCallback, useMemo } from 'react'; import type { List } from 'immutable'; -import type { History } from 'history'; +import { EmojiHTML } from '@/flavours/glitch/components/emoji/html'; +import { useElementHandledLink } from '@/flavours/glitch/components/status/handled_link'; +import type { CustomEmoji } from '@/flavours/glitch/models/custom_emoji'; +import type { Status } from '@/flavours/glitch/models/status'; import type { Mention } from './embedded_status'; -const handleMentionClick = ( - history: History, - mention: Mention, - e: MouseEvent, -) => { - if (e.button === 0 && !(e.ctrlKey || e.metaKey)) { - e.preventDefault(); - history.push(`/@${mention.get('acct')}`); - } -}; - -const handleHashtagClick = ( - history: History, - hashtag: string, - e: MouseEvent, -) => { - if (e.button === 0 && !(e.ctrlKey || e.metaKey)) { - e.preventDefault(); - history.push(`/tags/${hashtag.replace(/^#/, '')}`); - } -}; - export const EmbeddedStatusContent: React.FC<{ - content: string; - mentions: List; - language: string; + status: Status; className?: string; -}> = ({ content, mentions, language, className }) => { - const history = useHistory(); - - const handleContentRef = useCallback( - (node: HTMLDivElement | null) => { - if (!node) { - return; - } - - const links = node.querySelectorAll('a'); - - for (const link of links) { - if (link.classList.contains('status-link')) { - continue; - } - - link.classList.add('status-link'); - - const mention = mentions.find((item) => link.href === item.get('url')); - - if (mention) { - link.addEventListener( - 'click', - handleMentionClick.bind(null, history, mention), - false, - ); - link.setAttribute('title', `@${mention.get('acct')}`); - link.setAttribute('href', `/@${mention.get('acct')}`); - } else if ( - link.textContent?.[0] === '#' || - link.previousSibling?.textContent?.endsWith('#') - ) { - link.addEventListener( - 'click', - handleHashtagClick.bind(null, history, link.text), - false, - ); - link.setAttribute('href', `/tags/${link.text.replace(/^#/, '')}`); - } else { - link.setAttribute('title', link.href); - link.classList.add('unhandled-link'); - } - } +}> = ({ status, className }) => { + const mentions = useMemo( + () => (status.get('mentions') as List).toJS(), + [status], + ); + const hrefToMention = useCallback( + (href: string) => { + return mentions.find((item) => item.url === href); }, - [mentions, history], + [mentions], ); + const htmlHandlers = useElementHandledLink({ + hashtagAccountId: status.get('account') as string | undefined, + hrefToMention, + }); return ( -
} /> ); }; diff --git a/app/javascript/flavours/glitch/features/notifications_v2/components/notification_annual_report.tsx b/app/javascript/flavours/glitch/features/notifications_v2/components/notification_annual_report.tsx index 42754d1490f5f8..bf1c27b7552fcb 100644 --- a/app/javascript/flavours/glitch/features/notifications_v2/components/notification_annual_report.tsx +++ b/app/javascript/flavours/glitch/features/notifications_v2/components/notification_annual_report.tsx @@ -47,7 +47,7 @@ export const NotificationAnnualReport: React.FC<{ values={{ year }} />

- diff --git a/app/javascript/flavours/glitch/features/notifications_v2/index.tsx b/app/javascript/flavours/glitch/features/notifications_v2/index.tsx index e174c2876181f1..73dc3d659346d6 100644 --- a/app/javascript/flavours/glitch/features/notifications_v2/index.tsx +++ b/app/javascript/flavours/glitch/features/notifications_v2/index.tsx @@ -245,6 +245,7 @@ export const Notifications: React.FC<{ title={intl.formatMessage(messages.markAsRead)} onClick={handleMarkAsRead} className='column-header__button' + type='button' > diff --git a/app/javascript/flavours/glitch/features/onboarding/profile.tsx b/app/javascript/flavours/glitch/features/onboarding/profile.tsx index d5d35368f02793..8dab94be562c16 100644 --- a/app/javascript/flavours/glitch/features/onboarding/profile.tsx +++ b/app/javascript/flavours/glitch/features/onboarding/profile.tsx @@ -54,9 +54,7 @@ export const Profile: React.FC<{ me ? state.accounts.get(me) : undefined, ); const [displayName, setDisplayName] = useState(account?.display_name ?? ''); - const [note, setNote] = useState( - account ? (unescapeHTML(account.note) ?? '') : '', - ); + const [note, setNote] = useState(account ? unescapeHTML(account.note) : ''); const [avatar, setAvatar] = useState(); const [header, setHeader] = useState(); const [discoverable, setDiscoverable] = useState( diff --git a/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.tsx b/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.tsx index d2d0f5a6a1f2af..210f2331c2f931 100644 --- a/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.tsx +++ b/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.tsx @@ -2,28 +2,19 @@ import { useCallback, useMemo } from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import classNames from 'classnames'; import { useHistory } from 'react-router-dom'; import OpenInNewIcon from '@/material-icons/400-24px/open_in_new.svg?react'; -import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react'; import ReplyIcon from '@/material-icons/400-24px/reply.svg?react'; import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react'; import StarIcon from '@/material-icons/400-24px/star-fill.svg?react'; import StarBorderIcon from '@/material-icons/400-24px/star.svg?react'; -import RepeatActiveIcon from '@/svg-icons/repeat_active.svg?react'; -import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react'; -import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react'; -import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react'; import { replyCompose } from 'flavours/glitch/actions/compose'; -import { - toggleReblog, - toggleFavourite, -} from 'flavours/glitch/actions/interactions'; +import { toggleFavourite } from 'flavours/glitch/actions/interactions'; import { openModal } from 'flavours/glitch/actions/modal'; import { IconButton } from 'flavours/glitch/components/icon_button'; +import { BoostButton } from 'flavours/glitch/components/status/boost_button'; import { useIdentity } from 'flavours/glitch/identity_context'; -import { me } from 'flavours/glitch/initial_state'; import type { Account } from 'flavours/glitch/models/account'; import type { Status } from 'flavours/glitch/models/status'; import { makeGetStatus } from 'flavours/glitch/selectors'; @@ -130,29 +121,6 @@ export const Footer: React.FC<{ [dispatch, status, signedIn], ); - const handleReblogClick = useCallback( - (e: React.MouseEvent) => { - if (!status) { - return; - } - - if (signedIn) { - dispatch(toggleReblog(status.get('id'), e.shiftKey)); - } else { - dispatch( - openModal({ - modalType: 'INTERACTION', - modalProps: { - accountId: status.getIn(['account', 'id']), - url: status.get('uri'), - }, - }), - ); - } - }, - [dispatch, status, signedIn], - ); - const handleOpenClick = useCallback( (e: React.MouseEvent) => { if (e.button !== 0 || !status) { @@ -170,13 +138,6 @@ export const Footer: React.FC<{ return null; } - const publicStatus = ['public', 'unlisted'].includes( - status.get('visibility') as string, - ); - const reblogPrivate = - status.getIn(['account', 'id']) === me && - status.get('visibility') === 'private'; - let replyIcon, replyIconComponent, replyTitle; if (status.get('in_reply_to_id', null) === null) { @@ -189,24 +150,6 @@ export const Footer: React.FC<{ replyTitle = intl.formatMessage(messages.replyAll); } - let reblogTitle, reblogIconComponent; - - if (status.get('reblogged')) { - reblogTitle = intl.formatMessage(messages.cancel_reblog_private); - reblogIconComponent = publicStatus - ? RepeatActiveIcon - : RepeatPrivateActiveIcon; - } else if (publicStatus) { - reblogTitle = intl.formatMessage(messages.reblog); - reblogIconComponent = RepeatIcon; - } else if (reblogPrivate) { - reblogTitle = intl.formatMessage(messages.reblog_private); - reblogIconComponent = RepeatPrivateIcon; - } else { - reblogTitle = intl.formatMessage(messages.cannot_reblog); - reblogIconComponent = RepeatDisabledIcon; - } - const favouriteTitle = intl.formatMessage( status.get('favourited') ? messages.removeFavourite : messages.favourite, ); @@ -235,19 +178,7 @@ export const Footer: React.FC<{ obfuscateCount={!showReplyCount} /> - + + ) : ( + + ); + return ( } + emptyMessage={emptyMessage} bindToDocument={!multiColumn} regex={this.props.regex} /> diff --git a/app/javascript/flavours/glitch/features/quotes/index.tsx b/app/javascript/flavours/glitch/features/quotes/index.tsx index 0b3ed96415b6d7..6990266a615f1a 100644 --- a/app/javascript/flavours/glitch/features/quotes/index.tsx +++ b/app/javascript/flavours/glitch/features/quotes/index.tsx @@ -12,6 +12,8 @@ import { ColumnHeader } from 'flavours/glitch/components/column_header'; import { Icon } from 'flavours/glitch/components/icon'; import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator'; import StatusList from 'flavours/glitch/components/status_list'; +import { useIdentity } from 'flavours/glitch/identity_context'; +import { domain } from 'flavours/glitch/initial_state'; import { useAppDispatch, useAppSelector } from 'flavours/glitch/store'; import Column from '../ui/components/column'; @@ -31,9 +33,18 @@ export const Quotes: React.FC<{ const statusId = params?.statusId; + const { accountId: me } = useIdentity(); + const isCorrectStatusId: boolean = useAppSelector( (state) => state.status_lists.getIn(['quotes', 'statusId']) === statusId, ); + const quotedAccountId = useAppSelector( + (state) => + state.statuses.getIn([statusId, 'account']) as string | undefined, + ); + const quotedAccount = useAppSelector((state) => + quotedAccountId ? state.accounts.get(quotedAccountId) : undefined, + ); const statusIds = useAppSelector((state) => state.status_lists.getIn(['quotes', 'items'], emptyList), ); @@ -74,6 +85,32 @@ export const Quotes: React.FC<{ /> ); + let prependMessage; + + if (me === quotedAccountId) { + prependMessage = null; + } else if (quotedAccount?.username === quotedAccount?.acct) { + // Local account, we know this to be exhaustive + prependMessage = ( +
+ +
+ ); + } else { + prependMessage = ( +
+ {domain} }} + /> +
+ ); + } + return ( diff --git a/app/javascript/flavours/glitch/features/reblogs/index.jsx b/app/javascript/flavours/glitch/features/reblogs/index.jsx index 69bc423563f080..6e7faa3db6f8a7 100644 --- a/app/javascript/flavours/glitch/features/reblogs/index.jsx +++ b/app/javascript/flavours/glitch/features/reblogs/index.jsx @@ -44,7 +44,7 @@ class Reblogs extends ImmutablePureComponent { intl: PropTypes.object.isRequired, }; - UNSAFE_componentWillMount () { + componentDidMount () { if (!this.props.accountIds) { this.props.dispatch(fetchReblogs(this.props.params.statusId)); } diff --git a/app/javascript/flavours/glitch/features/search/components/search_section.tsx b/app/javascript/flavours/glitch/features/search/components/search_section.tsx index ae0c1296766c9d..c59d0c2fe4df98 100644 --- a/app/javascript/flavours/glitch/features/search/components/search_section.tsx +++ b/app/javascript/flavours/glitch/features/search/components/search_section.tsx @@ -9,7 +9,7 @@ export const SearchSection: React.FC<{

{title}

{onClickMore && ( -
- ); - - spoilerButton = ( -
- {spoilerButton} -
- ); - - if (interactive) { - if (embedded) { - embed = this.renderVideo(); - } else { - embed = ( -
- {canvas} - {thumbnail} - - {revealed ? ( -
-
- - -
-
- ) : spoilerButton} -
- ); - } - - return ( -
- {embed} - {description} -
- ); - } else if (card.get('image')) { - embed = ( -
- {canvas} - {thumbnail} -
- ); - } else { - embed = ( -
- -
- ); - } - - return ( - <> - - {embed} - {description} - - - {showAuthor && } - - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/status/components/card.tsx b/app/javascript/flavours/glitch/features/status/components/card.tsx new file mode 100644 index 00000000000000..7030eaf8c50cfe --- /dev/null +++ b/app/javascript/flavours/glitch/features/status/components/card.tsx @@ -0,0 +1,304 @@ +import { useCallback, useId, useState } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import classNames from 'classnames'; + +import DescriptionIcon from '@/material-icons/400-24px/description-fill.svg?react'; +import OpenInNewIcon from '@/material-icons/400-24px/open_in_new.svg?react'; +import PlayArrowIcon from '@/material-icons/400-24px/play_arrow-fill.svg?react'; +import { Blurhash } from 'flavours/glitch/components/blurhash'; +import { Icon } from 'flavours/glitch/components/icon'; +import { MoreFromAuthor } from 'flavours/glitch/components/more_from_author'; +import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp'; +import { useBlurhash } from 'flavours/glitch/initial_state'; +import type { Card as CardType } from 'flavours/glitch/models/status'; +import { decode as decodeIDNA } from 'flavours/glitch/utils/idna'; + +const getHostname = (url: string) => { + const parser = document.createElement('a'); + parser.href = url; + return parser.hostname; +}; + +const domParser = new DOMParser(); + +const handleIframeUrl = (html: string, url: string, providerName: string) => { + const document = domParser.parseFromString(html, 'text/html').documentElement; + const iframe = document.querySelector('iframe'); + const startTime = new URL(url).searchParams.get('t'); + + if (iframe) { + const iframeUrl = new URL(iframe.src); + + iframeUrl.searchParams.set('autoplay', '1'); + iframeUrl.searchParams.set('auto_play', '1'); + + if (providerName === 'YouTube') { + iframeUrl.searchParams.set('start', startTime ?? ''); + iframe.referrerPolicy = 'strict-origin-when-cross-origin'; + } + + iframe.src = iframeUrl.href; + + // DOM parser creates html/body elements around original HTML fragment, + // so we need to get innerHTML out of the body and not the entire document + return document.querySelector('body')?.innerHTML ?? ''; + } + + return html; +}; + +interface CardProps { + card: CardType | null; + sensitive?: boolean; +} + +const CardVideo: React.FC> = ({ card }) => ( +
+); + +const Card: React.FC = ({ card, sensitive }) => { + const [previewLoaded, setPreviewLoaded] = useState(false); + const [embedded, setEmbedded] = useState(false); + const [revealed, setRevealed] = useState(!sensitive); + + const handleEmbedClick = useCallback(() => { + setEmbedded(true); + }, []); + + const handleExternalLinkClick = useCallback((e: React.MouseEvent) => { + e.stopPropagation(); + }, []); + + const handleImageLoad = useCallback(() => { + setPreviewLoaded(true); + }, []); + + const handleReveal = useCallback((e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + setRevealed(true); + }, []); + + const spoilerButtonId = useId(); + + if (card === null) { + return null; + } + + const provider = + card.get('provider_name').length === 0 + ? decodeIDNA(getHostname(card.get('url'))) + : card.get('provider_name'); + const interactive = card.get('type') === 'video'; + const language = card.get('language') || ''; + const hasImage = (card.get('image')?.length ?? 0) > 0; + const largeImage = + (hasImage && card.get('width') > card.get('height')) || interactive; + const showAuthor = !!card.getIn(['authors', 0, 'accountId']); + + const description = ( +
+ + {provider} + {card.get('published_at') && ( + <> + {' '} + · + + )} + + + + {card.get('title')} + + + {!showAuthor && + (card.get('author_name').length > 0 ? ( + + {card.get('author_name')} }} + /> + + ) : ( + + {card.get('description')} + + ))} +
+ ); + + const thumbnailStyle: React.CSSProperties = { + visibility: revealed ? undefined : 'hidden', + aspectRatio: '1', + }; + + if (largeImage && card.get('type') === 'video') { + thumbnailStyle.aspectRatio = `16 / 9`; + } else if (largeImage) { + thumbnailStyle.aspectRatio = '1.91 / 1'; + } + + let embed; + + const canvas = ( + + ); + + const thumbnailDescription = card.get('image_description'); + const thumbnail = ( + {thumbnailDescription} + ); + + const spoilerButton = ( +
+ +
+ ); + + if (interactive) { + if (embedded) { + embed = ; + } else { + embed = ( +
+ {canvas} + {thumbnail} + + {revealed ? ( +
+
+ + + + +
+
+ ) : ( + spoilerButton + )} +
+ ); + } + + return ( +
+ {embed} + + {description} + +
+ ); + } else if (card.get('image')) { + embed = ( +
+ {canvas} + {thumbnail} +
+ ); + } else { + embed = ( +
+ +
+ ); + } + + return ( + <> + + {embed} + {description} + + + {showAuthor && ( + + )} + + ); +}; + +// eslint-disable-next-line import/no-default-export +export default Card; diff --git a/app/javascript/flavours/glitch/features/status/components/detailed_status.tsx b/app/javascript/flavours/glitch/features/status/components/detailed_status.tsx index b33db9bb6e16ad..a72d9ad714a472 100644 --- a/app/javascript/flavours/glitch/features/status/components/detailed_status.tsx +++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.tsx @@ -4,7 +4,7 @@ @typescript-eslint/no-unsafe-assignment */ import type { CSSProperties } from 'react'; -import { useState, useRef, useCallback } from 'react'; +import { useState, useRef, useCallback, useEffect } from 'react'; import { FormattedMessage } from 'react-intl'; @@ -32,7 +32,7 @@ import { VisibilityIcon } from 'flavours/glitch/components/visibility_icon'; import { Audio } from 'flavours/glitch/features/audio'; import scheduleIdleTask from 'flavours/glitch/features/ui/util/schedule_idle_task'; import { Video } from 'flavours/glitch/features/video'; -import { me } from 'flavours/glitch/initial_state'; +import { useIdentity } from 'flavours/glitch/identity_context'; import { useAppSelector } from 'flavours/glitch/store'; import Card from './card'; @@ -57,6 +57,8 @@ export const DetailedStatus: React.FC<{ pictureInPicture: any; onToggleHidden?: (status: any) => void; onToggleMediaVisibility?: () => void; + ancestors?: number; + multiColumn?: boolean; expanded: boolean; }> = ({ status, @@ -72,6 +74,8 @@ export const DetailedStatus: React.FC<{ pictureInPicture, onToggleMediaVisibility, onToggleHidden, + ancestors = 0, + multiColumn = false, expanded, }) => { const properStatus = status?.get('reblog') ?? status; @@ -79,13 +83,6 @@ export const DetailedStatus: React.FC<{ const [showDespiteFilter, setShowDespiteFilter] = useState(false); const nodeRef = useRef(); - const rewriteMentions = useAppSelector( - (state) => state.local_settings.get('rewrite_mentions', false) as boolean, - ); - const tagMisleadingLinks = useAppSelector( - (state) => - state.local_settings.get('tag_misleading_links', false) as boolean, - ); const letterboxMedia = useAppSelector( (state) => state.local_settings.getIn(['media', 'letterbox'], false) as boolean, @@ -95,6 +92,8 @@ export const DetailedStatus: React.FC<{ state.local_settings.getIn(['media', 'fullwidth'], false) as boolean, ); + const { signedIn } = useIdentity(); + const handleOpenVideo = useCallback( (options: VideoModalOptions) => { const lang = (status.getIn(['translation', 'language']) || @@ -141,6 +140,30 @@ export const DetailedStatus: React.FC<{ if (onTranslate) onTranslate(status); }, [onTranslate, status]); + // The component is managed and will change if the status changes + // Ancestors can increase when loading a thread, in which case we want to scroll, + // or decrease if a post is deleted, in which case we don't want to mess with it + const previousAncestors = useRef(-1); + useEffect(() => { + if (nodeRef.current && previousAncestors.current < ancestors) { + nodeRef.current.scrollIntoView(true); + + // In the single-column interface, `scrollIntoView` will put the post behind the header, so compensate for that. + if (!multiColumn) { + const offset = document + .querySelector('.column-header__wrapper') + ?.getBoundingClientRect().bottom; + + if (offset) { + const scrollingElement = document.scrollingElement ?? document.body; + scrollingElement.scrollBy(0, -offset); + } + } + } + + previousAncestors.current = ancestors; + }, [ancestors, multiColumn]); + if (!properStatus) { return null; } @@ -271,8 +294,8 @@ export const DetailedStatus: React.FC<{ } else if (status.get('card') && !status.get('quote')) { media = ( ); @@ -313,13 +336,17 @@ export const DetailedStatus: React.FC<{ to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}/reblogs`} className='detailed-status__link' > - - - + + + ), + }} /> ); @@ -327,32 +354,40 @@ export const DetailedStatus: React.FC<{ if (['private', 'direct'].includes(status.get('visibility') as string)) { quotesLink = ''; - } else if (status.getIn(['account', 'id']) === me) { + } else if (signedIn) { quotesLink = ( - - - + + + ), + }} /> ); } else { quotesLink = ( - - - + + + ), + }} /> ); @@ -363,13 +398,17 @@ export const DetailedStatus: React.FC<{ to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}/favourites`} className='detailed-status__link' > - - - + + + ), + }} /> ); @@ -427,25 +466,19 @@ export const DetailedStatus: React.FC<{ /> )} - {status.get('spoiler_text').length > 0 && - (!matchedFilters || showDespiteFilter) && ( - - )} + {(!matchedFilters || showDespiteFilter) && ( + + )} {expanded && ( <> @@ -456,6 +489,7 @@ export const DetailedStatus: React.FC<{ )} diff --git a/app/javascript/flavours/glitch/features/status/components/refresh_controller.tsx b/app/javascript/flavours/glitch/features/status/components/refresh_controller.tsx index 1bf5b5b3ef0e5d..e29461347bf644 100644 --- a/app/javascript/flavours/glitch/features/status/components/refresh_controller.tsx +++ b/app/javascript/flavours/glitch/features/status/components/refresh_controller.tsx @@ -1,16 +1,22 @@ -import { useEffect, useState, useCallback } from 'react'; +import { useEffect, useState, useCallback, useMemo } from 'react'; import { useIntl, defineMessages } from 'react-intl'; +import { useDebouncedCallback } from 'use-debounce'; + import { fetchContext, completeContextRefresh, + showPendingReplies, + clearPendingReplies, } from 'flavours/glitch/actions/statuses'; import type { AsyncRefreshHeader } from 'flavours/glitch/api'; import { apiGetAsyncRefresh } from 'flavours/glitch/api/async_refreshes'; import { Alert } from 'flavours/glitch/components/alert'; import { ExitAnimationWrapper } from 'flavours/glitch/components/exit_animation_wrapper'; import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator'; +import { useInterval } from 'flavours/glitch/hooks/useInterval'; +import { useIsDocumentVisible } from 'flavours/glitch/hooks/useIsDocumentVisible'; import { useAppSelector, useAppDispatch } from 'flavours/glitch/store'; const AnimatedAlert: React.FC< @@ -34,13 +40,9 @@ const messages = defineMessages({ id: 'status.context.loading', defaultMessage: 'Loading', }, - loadingMore: { - id: 'status.context.loading_more', - defaultMessage: 'Loading more replies', - }, success: { id: 'status.context.loading_success', - defaultMessage: 'All replies loaded', + defaultMessage: 'New replies loaded', }, error: { id: 'status.context.loading_error', @@ -52,80 +54,224 @@ const messages = defineMessages({ }, }); -type LoadingState = - | 'idle' - | 'more-available' - | 'loading-initial' - | 'loading-more' - | 'success' - | 'error'; +type LoadingState = 'idle' | 'more-available' | 'loading' | 'success' | 'error'; -export const RefreshController: React.FC<{ - statusId: string; -}> = ({ statusId }) => { - const refresh = useAppSelector( - (state) => state.contexts.refreshing[statusId], - ); - const currentReplyCount = useAppSelector( - (state) => state.contexts.replies[statusId]?.length ?? 0, - ); - const autoRefresh = !currentReplyCount; - const dispatch = useAppDispatch(); - const intl = useIntl(); +/** + * Age of thread below which we consider it new & fetch + * replies more frequently + */ +const NEW_THREAD_AGE_THRESHOLD = 30 * 60_000; +/** + * Interval at which we check for new replies for old threads + */ +const LONG_AUTO_FETCH_REPLIES_INTERVAL = 5 * 60_000; +/** + * Interval at which we check for new replies for new threads. + * Also used as a threshold to throttle repeated fetch calls + */ +const SHORT_AUTO_FETCH_REPLIES_INTERVAL = 60_000; +/** + * Number of refresh_async checks at which an early fetch + * will be triggered if there are results + */ +const LONG_RUNNING_FETCH_THRESHOLD = 3; - const [loadingState, setLoadingState] = useState( - refresh && autoRefresh ? 'loading-initial' : 'idle', - ); +/** + * Returns whether the thread is new, based on NEW_THREAD_AGE_THRESHOLD + */ +function getIsThreadNew(statusCreatedAt: string) { + const now = new Date(); + const newThreadThreshold = new Date(now.getTime() - NEW_THREAD_AGE_THRESHOLD); - const [wasDismissed, setWasDismissed] = useState(false); - const dismissPrompt = useCallback(() => { - setWasDismissed(true); - setLoadingState('idle'); - }, []); + return new Date(statusCreatedAt) > newThreadThreshold; +} + +/** + * This hook kicks off a background check for the async refresh job + * and loads any newly found replies once the job has finished, + * and when LONG_RUNNING_FETCH_THRESHOLD was reached and replies were found + */ +function useCheckForRemoteReplies({ + statusId, + refreshHeader, + isEnabled, + onChangeLoadingState, +}: { + statusId: string; + refreshHeader?: AsyncRefreshHeader; + isEnabled: boolean; + onChangeLoadingState: React.Dispatch>; +}) { + const dispatch = useAppDispatch(); useEffect(() => { let timeoutId: ReturnType; - const scheduleRefresh = (refresh: AsyncRefreshHeader) => { + const scheduleRefresh = ( + refresh: AsyncRefreshHeader, + iteration: number, + ) => { timeoutId = setTimeout(() => { void apiGetAsyncRefresh(refresh.id).then((result) => { - if (result.async_refresh.status === 'finished') { + const { status, result_count } = result.async_refresh; + + // At three scheduled refreshes, we consider the job + // long-running and attempt to fetch any new replies so far + const isLongRunning = iteration === LONG_RUNNING_FETCH_THRESHOLD; + + // If the refresh status is not finished and not long-running, + // we just schedule another refresh and exit + if (status === 'running' && !isLongRunning) { + scheduleRefresh(refresh, iteration + 1); + return; + } + + // If refresh status is finished, clear `refreshHeader` + // (we don't want to do this if it's just a long-running job) + if (status === 'finished') { dispatch(completeContextRefresh({ statusId })); + } - if (result.async_refresh.result_count > 0) { - if (autoRefresh) { - void dispatch(fetchContext({ statusId })).then(() => { - setLoadingState('idle'); - }); - } else { - setLoadingState('more-available'); - } + // Exit if there's nothing to fetch + if (result_count === 0) { + if (status === 'finished') { + onChangeLoadingState('idle'); } else { - setLoadingState('idle'); + scheduleRefresh(refresh, iteration + 1); } - } else { - scheduleRefresh(refresh); + return; } + + // A positive result count means there _might_ be new replies, + // so we fetch the context in the background to check if there + // are any new replies. + // If so, they will populate `contexts.pendingReplies[statusId]` + void dispatch(fetchContext({ statusId, prefetchOnly: true })) + .then(() => { + // Reset loading state to `idle`. If the fetch has + // resulted in new pending replies, the `hasPendingReplies` + // flag will switch the loading state to 'more-available' + if (status === 'finished') { + onChangeLoadingState('idle'); + } else { + // Keep background fetch going if `isLongRunning` is true + scheduleRefresh(refresh, iteration + 1); + } + }) + .catch(() => { + // Show an error if the fetch failed + onChangeLoadingState('error'); + }); }); }, refresh.retry * 1000); }; - if (refresh && !wasDismissed) { - scheduleRefresh(refresh); - setLoadingState('loading-initial'); + // Initialise a refresh + if (refreshHeader && isEnabled) { + scheduleRefresh(refreshHeader, 1); + onChangeLoadingState('loading'); } return () => { clearTimeout(timeoutId); }; - }, [dispatch, statusId, refresh, autoRefresh, wasDismissed]); + }, [onChangeLoadingState, dispatch, statusId, refreshHeader, isEnabled]); +} + +/** + * This component fetches new post replies in the background + * and gives users the option to show them. + * + * The following three scenarios are handled: + * + * 1. When the browser tab is visible, replies are refetched periodically + * (more frequently for new posts, less frequently for old ones) + * 2. Replies are refetched when the browser tab is refocused + * after it was hidden or minimised + * 3. For remote posts, remote replies that might not yet be known to the + * server are imported & fetched using the AsyncRefresh API. + */ +export const RefreshController: React.FC<{ + statusId: string; + statusCreatedAt: string; + isLocal: boolean; +}> = ({ statusId, statusCreatedAt, isLocal }) => { + const dispatch = useAppDispatch(); + const intl = useIntl(); + + const refreshHeader = useAppSelector((state) => + isLocal ? undefined : state.contexts.refreshing[statusId], + ); + const hasPendingReplies = useAppSelector( + (state) => !!state.contexts.pendingReplies[statusId]?.length, + ); + const [partialLoadingState, setLoadingState] = useState( + refreshHeader ? 'loading' : 'idle', + ); + const loadingState = hasPendingReplies + ? 'more-available' + : partialLoadingState; + + const [wasDismissed, setWasDismissed] = useState(false); + const dismissPrompt = useCallback(() => { + setWasDismissed(true); + setLoadingState('idle'); + dispatch(clearPendingReplies({ statusId })); + }, [dispatch, statusId]); + + // Prevent too-frequent context calls + const debouncedFetchContext = useDebouncedCallback( + () => { + void dispatch(fetchContext({ statusId, prefetchOnly: true })); + }, + // Ensure the debounce is a bit shorter than the auto-fetch interval + SHORT_AUTO_FETCH_REPLIES_INTERVAL - 500, + { + leading: true, + trailing: false, + }, + ); + + const isDocumentVisible = useIsDocumentVisible({ + onChange: (isVisible) => { + // Auto-fetch new replies when the page is refocused + if (isVisible && partialLoadingState !== 'loading' && !wasDismissed) { + debouncedFetchContext(); + } + }, + }); + + // Check for remote replies + useCheckForRemoteReplies({ + statusId, + refreshHeader, + isEnabled: isDocumentVisible && !isLocal && !wasDismissed, + onChangeLoadingState: setLoadingState, + }); + + // Only auto-fetch new replies if there's no ongoing remote replies check + const shouldAutoFetchReplies = + isDocumentVisible && partialLoadingState !== 'loading' && !wasDismissed; + + const autoFetchInterval = useMemo( + () => + getIsThreadNew(statusCreatedAt) + ? SHORT_AUTO_FETCH_REPLIES_INTERVAL + : LONG_AUTO_FETCH_REPLIES_INTERVAL, + [statusCreatedAt], + ); + + useInterval(debouncedFetchContext, { + delay: autoFetchInterval, + isEnabled: shouldAutoFetchReplies, + }); useEffect(() => { // Hide success message after a short delay if (loadingState === 'success') { const timeoutId = setTimeout(() => { setLoadingState('idle'); - }, 3000); + }, 2500); return () => { clearTimeout(timeoutId); @@ -134,23 +280,22 @@ export const RefreshController: React.FC<{ return () => ''; }, [loadingState]); - const handleClick = useCallback(() => { - setLoadingState('loading-more'); - - dispatch(fetchContext({ statusId })) - .then(() => { - setLoadingState('success'); - return ''; - }) - .catch(() => { - setLoadingState('error'); - }); + useEffect(() => { + // Clear pending replies on unmount + return () => { + dispatch(clearPendingReplies({ statusId })); + }; + }, [dispatch, statusId]); + + const showPending = useCallback(() => { + dispatch(showPendingReplies({ statusId })); + setLoadingState('success'); }, [dispatch, statusId]); - if (loadingState === 'loading-initial') { + if (loadingState === 'loading') { return (
- diff --git a/app/javascript/flavours/glitch/features/status/index.jsx b/app/javascript/flavours/glitch/features/status/index.jsx index d196dff11ac75f..95cd9255223513 100644 --- a/app/javascript/flavours/glitch/features/status/index.jsx +++ b/app/javascript/flavours/glitch/features/status/index.jsx @@ -17,7 +17,7 @@ import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?reac import { Hotkeys } from 'flavours/glitch/components/hotkeys'; import { Icon } from 'flavours/glitch/components/icon'; import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator'; -import ScrollContainer from 'flavours/glitch/containers/scroll_container'; +import { ScrollContainer } from 'flavours/glitch/containers/scroll_container'; import BundleColumnError from 'flavours/glitch/features/ui/components/bundle_column_error'; import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context'; import { autoUnfoldCW } from 'flavours/glitch/utils/content_warning'; @@ -161,8 +161,7 @@ class Status extends ImmutablePureComponent { componentDidMount () { attachFullscreenListener(this.onFullScreenChange); - this.props.dispatch(fetchStatus(this.props.params.statusId)); - this._scrollStatusIntoView(); + this.props.dispatch(fetchStatus(this.props.params.statusId, { forceFetch: true })); } static getDerivedStateFromProps(props, state) { @@ -170,7 +169,7 @@ class Status extends ImmutablePureComponent { let updated = false; if (props.params.statusId && state.statusId !== props.params.statusId) { - props.dispatch(fetchStatus(props.params.statusId)); + props.dispatch(fetchStatus(props.params.statusId, { forceFetch: true })); update.threadExpanded = undefined; update.statusId = props.params.statusId; updated = true; @@ -329,6 +328,12 @@ class Status extends ImmutablePureComponent { dispatch(openModal({ modalType: 'COMPOSE_PRIVACY', modalProps: { statusId, onChange: handleChange } })); }; + handleQuote = (status) => { + const { dispatch } = this.props; + + dispatch(quoteComposeById(status.get('id'))); + }; + handleEditClick = (status) => { const { dispatch, askReplyConfirmation } = this.props; @@ -506,35 +511,13 @@ class Status extends ImmutablePureComponent { this.statusNode = c; }; - _scrollStatusIntoView () { - const { status, multiColumn } = this.props; - - if (status) { - requestIdleCallback(() => { - this.statusNode?.scrollIntoView(true); - - // In the single-column interface, `scrollIntoView` will put the post behind the header, - // so compensate for that. - if (!multiColumn) { - const offset = document.querySelector('.column-header__wrapper')?.getBoundingClientRect()?.bottom; - if (offset) { - const scrollingElement = document.scrollingElement || document.body; - scrollingElement.scrollBy(0, -offset); - } - } - }); - } - } - componentDidUpdate (prevProps) { - const { status, ancestorsIds, descendantsIds } = this.props; + const { status, descendantsIds } = this.props; - if (status && (ancestorsIds.length > prevProps.ancestorsIds.length || prevProps.status?.get('id') !== status.get('id'))) { - this._scrollStatusIntoView(); - } + const isSameStatus = status && (prevProps.status?.get('id') === status.get('id')); // Only highlight replies after the initial load - if (prevProps.descendantsIds.length) { + if (prevProps.descendantsIds.length && isSameStatus) { const newRepliesIds = difference(descendantsIds, prevProps.descendantsIds); if (newRepliesIds.length) { @@ -551,9 +534,9 @@ class Status extends ImmutablePureComponent { this.setState({ fullscreen: isFullscreen() }); }; - shouldUpdateScroll = (prevRouterProps, { location }) => { + shouldUpdateScroll = (prevLocation, location) => { // Do not change scroll when opening a modal - if (location.state?.mastodonModalKey !== prevRouterProps?.location?.state?.mastodonModalKey) { + if (location.state?.mastodonModalKey !== prevLocation?.state?.mastodonModalKey) { return false; } @@ -598,14 +581,6 @@ class Status extends ImmutablePureComponent { const isLocal = status.getIn(['account', 'acct'], '').indexOf('@') === -1; const isIndexable = !status.getIn(['account', 'noindex']); - if (!isLocal) { - remoteHint = ( - - ); - } - const handlers = { reply: this.handleHotkeyReply, favourite: this.handleHotkeyFavourite, @@ -630,12 +605,12 @@ class Status extends ImmutablePureComponent { showBackButton multiColumn={multiColumn} extraButton={( - + )} /> - -
+ +
{ancestors} @@ -653,6 +628,8 @@ class Status extends ImmutablePureComponent { showMedia={this.state.showMedia} onToggleMediaVisibility={this.handleToggleMediaVisibility} pictureInPicture={pictureInPicture} + ancestors={this.props.ancestorsIds.length} + multiColumn={multiColumn} /> {descendants} - {remoteHint} + +
diff --git a/app/javascript/flavours/glitch/features/ui/components/actions_modal.tsx b/app/javascript/flavours/glitch/features/ui/components/actions_modal.tsx index 8d5dabaed940f0..1354032403235d 100644 --- a/app/javascript/flavours/glitch/features/ui/components/actions_modal.tsx +++ b/app/javascript/flavours/glitch/features/ui/components/actions_modal.tsx @@ -25,7 +25,12 @@ export const ActionsModal: React.FC<{ if (isActionItem(option)) { element = ( - ); diff --git a/app/javascript/flavours/glitch/features/ui/components/annual_report_modal.tsx b/app/javascript/flavours/glitch/features/ui/components/annual_report_modal.tsx deleted file mode 100644 index b7f5db04ace68e..00000000000000 --- a/app/javascript/flavours/glitch/features/ui/components/annual_report_modal.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { useEffect } from 'react'; - -import { AnnualReport } from 'flavours/glitch/features/annual_report'; - -const AnnualReportModal: React.FC<{ - year: string; - onChangeBackgroundColor: (arg0: string) => void; -}> = ({ year, onChangeBackgroundColor }) => { - useEffect(() => { - onChangeBackgroundColor('var(--indigo-1)'); - }, [onChangeBackgroundColor]); - - return ( -
- -
- ); -}; - -// eslint-disable-next-line import/no-default-export -export default AnnualReportModal; diff --git a/app/javascript/flavours/glitch/features/ui/components/boost_modal.tsx b/app/javascript/flavours/glitch/features/ui/components/boost_modal.tsx index 5edce68511e971..fb86a215f23a04 100644 --- a/app/javascript/flavours/glitch/features/ui/components/boost_modal.tsx +++ b/app/javascript/flavours/glitch/features/ui/components/boost_modal.tsx @@ -53,7 +53,10 @@ export const BoostModal: React.FC<{ }, [onClose]); const findContainer = useCallback( - () => document.getElementsByClassName('modal-root__container')[0], + () => + document.getElementsByClassName( + 'modal-root__container', + )[0] as HTMLDivElement, [], ); @@ -121,7 +124,7 @@ export const BoostModal: React.FC<{
- diff --git a/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/index.ts b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/index.ts index 63c93b54b65cc3..9aff30eeac16a9 100644 --- a/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/index.ts +++ b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/index.ts @@ -5,7 +5,9 @@ export { ConfirmReplyModal, ConfirmEditStatusModal, } from './discard_draft_confirmation'; +export { ConfirmWithdrawRequestModal } from './withdraw_follow_request'; export { ConfirmUnfollowModal } from './unfollow'; +export { ConfirmUnblockModal } from './unblock'; export { ConfirmClearNotificationsModal } from './clear_notifications'; export { ConfirmLogOutModal } from './log_out'; export { ConfirmFollowToListModal } from './follow_to_list'; diff --git a/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/private_quote_notify.tsx b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/private_quote_notify.tsx new file mode 100644 index 00000000000000..c461e1e43e11cd --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/private_quote_notify.tsx @@ -0,0 +1,88 @@ +import { forwardRef, useCallback, useState } from 'react'; + +import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; + +import { submitCompose } from '@/flavours/glitch/actions/compose'; +import { changeSetting } from '@/flavours/glitch/actions/settings'; +import { CheckBox } from '@/flavours/glitch/components/check_box'; +import { useAppDispatch } from '@/flavours/glitch/store'; + +import { ConfirmationModal } from './confirmation_modal'; +import type { BaseConfirmationModalProps } from './confirmation_modal'; +import classes from './styles.module.css'; + +export const PRIVATE_QUOTE_MODAL_ID = 'quote/private_notify'; + +const messages = defineMessages({ + title: { + id: 'confirmations.private_quote_notify.title', + defaultMessage: 'Share with followers and mentioned users?', + }, + message: { + id: 'confirmations.private_quote_notify.message', + defaultMessage: + 'The person you are quoting and other mentions ' + + "will be notified and will be able to view your post, even if they're not following you.", + }, + confirm: { + id: 'confirmations.private_quote_notify.confirm', + defaultMessage: 'Publish post', + }, + cancel: { + id: 'confirmations.private_quote_notify.cancel', + defaultMessage: 'Back to editing', + }, +}); + +export const PrivateQuoteNotify = forwardRef< + HTMLDivElement, + BaseConfirmationModalProps +>( + ( + { onClose }, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _ref, + ) => { + const intl = useIntl(); + + const [dismiss, setDismissed] = useState(false); + const handleDismissToggle = useCallback(() => { + setDismissed((prev) => !prev); + }, []); + + const dispatch = useAppDispatch(); + const handleConfirm = useCallback(() => { + dispatch(submitCompose()); + if (dismiss) { + dispatch( + changeSetting(['dismissed_banners', PRIVATE_QUOTE_MODAL_ID], true), + ); + } + }, [dismiss, dispatch]); + + return ( + + {' '} + + + } + /> + ); + }, +); +PrivateQuoteNotify.displayName = 'PrivateQuoteNotify'; diff --git a/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/styles.module.css b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/styles.module.css new file mode 100644 index 00000000000000..f685c4525f1ecd --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/styles.module.css @@ -0,0 +1,7 @@ +.checkbox_wrapper { + display: flex; + align-items: center; + gap: 0.5rem; + margin: 1rem 0; + cursor: pointer; +} diff --git a/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/unblock.tsx b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/unblock.tsx new file mode 100644 index 00000000000000..5a86c81d1d5d03 --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/unblock.tsx @@ -0,0 +1,45 @@ +import { useCallback } from 'react'; + +import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; + +import { unblockAccount } from 'flavours/glitch/actions/accounts'; +import type { Account } from 'flavours/glitch/models/account'; +import { useAppDispatch } from 'flavours/glitch/store'; + +import type { BaseConfirmationModalProps } from './confirmation_modal'; +import { ConfirmationModal } from './confirmation_modal'; + +const messages = defineMessages({ + unblockConfirm: { + id: 'confirmations.unblock.confirm', + defaultMessage: 'Unblock', + }, +}); + +export const ConfirmUnblockModal: React.FC< + { + account: Account; + } & BaseConfirmationModalProps +> = ({ account, onClose }) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + + const onConfirm = useCallback(() => { + dispatch(unblockAccount(account.id)); + }, [dispatch, account.id]); + + return ( + + } + confirm={intl.formatMessage(messages.unblockConfirm)} + onConfirm={onConfirm} + onClose={onClose} + /> + ); +}; diff --git a/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/unfollow.tsx b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/unfollow.tsx index fe6f5e362d00e2..1611895891f2a5 100644 --- a/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/unfollow.tsx +++ b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/unfollow.tsx @@ -10,10 +10,6 @@ import type { BaseConfirmationModalProps } from './confirmation_modal'; import { ConfirmationModal } from './confirmation_modal'; const messages = defineMessages({ - unfollowTitle: { - id: 'confirmations.unfollow.title', - defaultMessage: 'Unfollow user?', - }, unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow', @@ -34,12 +30,11 @@ export const ConfirmUnfollowModal: React.FC< return ( @{account.acct} }} + id='confirmations.unfollow.title' + defaultMessage='Unfollow {name}?' + values={{ name: `@${account.acct}` }} /> } confirm={intl.formatMessage(messages.unfollowConfirm)} diff --git a/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/withdraw_follow_request.tsx b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/withdraw_follow_request.tsx new file mode 100644 index 00000000000000..1278a22005f8b2 --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/withdraw_follow_request.tsx @@ -0,0 +1,45 @@ +import { useCallback } from 'react'; + +import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; + +import { unfollowAccount } from 'flavours/glitch/actions/accounts'; +import type { Account } from 'flavours/glitch/models/account'; +import { useAppDispatch } from 'flavours/glitch/store'; + +import type { BaseConfirmationModalProps } from './confirmation_modal'; +import { ConfirmationModal } from './confirmation_modal'; + +const messages = defineMessages({ + withdrawConfirm: { + id: 'confirmations.withdraw_request.confirm', + defaultMessage: 'Withdraw request', + }, +}); + +export const ConfirmWithdrawRequestModal: React.FC< + { + account: Account; + } & BaseConfirmationModalProps +> = ({ account, onClose }) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + + const onConfirm = useCallback(() => { + dispatch(unfollowAccount(account.id)); + }, [dispatch, account.id]); + + return ( + + } + confirm={intl.formatMessage(messages.withdrawConfirm)} + onConfirm={onConfirm} + onClose={onClose} + /> + ); +}; diff --git a/app/javascript/flavours/glitch/features/ui/components/domain_block_modal.tsx b/app/javascript/flavours/glitch/features/ui/components/domain_block_modal.tsx index b056db7f6fe6b4..1fdec8f9a9afe8 100644 --- a/app/javascript/flavours/glitch/features/ui/components/domain_block_modal.tsx +++ b/app/javascript/flavours/glitch/features/ui/components/domain_block_modal.tsx @@ -53,8 +53,6 @@ export const DomainBlockModal: React.FC<{ }, [dispatch]); useEffect(() => { - setLoading(true); - apiRequest('GET', 'v1/domain_blocks/preview', { params: { domain }, timeout: 5000, @@ -68,7 +66,7 @@ export const DomainBlockModal: React.FC<{ setPreview('error'); setLoading(false); }); - }, [setPreview, setLoading, domain]); + }, [domain]); return (
@@ -196,7 +194,7 @@ export const DomainBlockModal: React.FC<{
- ; - const rightNav = media.size > 1 && ; - - const content = media.map((image, idx) => { - const width = image.getIn(['meta', 'original', 'width']) || null; - const height = image.getIn(['meta', 'original', 'height']) || null; - const description = image.getIn(['translation', 'description']) || image.get('description'); - - if (image.get('type') === 'image') { - return ( - - ); - } else if (image.get('type') === 'video') { - const { currentTime, autoPlay, volume } = this.props; - - return ( -
+ + {isQuotePost && visibility === 'direct' && ( +
+ + +
+ )}
)} diff --git a/app/javascript/mastodon/components/alt_text_badge.tsx b/app/javascript/mastodon/components/alt_text_badge.tsx index c7fb0cd81b18e0..33dd3963d21c9a 100644 --- a/app/javascript/mastodon/components/alt_text_badge.tsx +++ b/app/javascript/mastodon/components/alt_text_badge.tsx @@ -47,7 +47,7 @@ export const AltTextBadge: React.FC<{ description: string }> = ({ rootClose onHide={handleClose} show={open} - target={anchorRef.current} + target={anchorRef} placement='top-end' flip offset={offset} diff --git a/app/javascript/mastodon/components/autosuggest_input.jsx b/app/javascript/mastodon/components/autosuggest_input.jsx index f707a18e1d697f..9e342a353a169e 100644 --- a/app/javascript/mastodon/components/autosuggest_input.jsx +++ b/app/javascript/mastodon/components/autosuggest_input.jsx @@ -28,7 +28,7 @@ const textAtCursorMatchesToken = (str, caretPosition, searchTokens) => { return [null, null]; } - word = word.trim().toLowerCase(); + word = word.trim(); if (word.length > 0) { return [left + 1, word]; @@ -61,7 +61,7 @@ export default class AutosuggestInput extends ImmutablePureComponent { static defaultProps = { autoFocus: true, - searchTokens: ['@', ':', '#'], + searchTokens: ['@', '@', ':', '#', '#'], }; state = { @@ -159,8 +159,8 @@ export default class AutosuggestInput extends ImmutablePureComponent { this.input.focus(); }; - UNSAFE_componentWillReceiveProps (nextProps) { - if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden && this.state.focused) { + componentDidUpdate (prevProps) { + if (prevProps.suggestions !== this.props.suggestions && this.props.suggestions.size > 0 && this.state.suggestionsHidden && this.state.focused) { this.setState({ suggestionsHidden: false }); } } diff --git a/app/javascript/mastodon/components/autosuggest_textarea.jsx b/app/javascript/mastodon/components/autosuggest_textarea.jsx index de5accc4b287df..fae078da31b379 100644 --- a/app/javascript/mastodon/components/autosuggest_textarea.jsx +++ b/app/javascript/mastodon/components/autosuggest_textarea.jsx @@ -25,11 +25,11 @@ const textAtCursorMatchesToken = (str, caretPosition) => { word = str.slice(left, right + caretPosition); } - if (!word || word.trim().length < 3 || ['@', ':', '#'].indexOf(word[0]) === -1) { + if (!word || word.trim().length < 3 || ['@', '@', ':', '#', '#'].indexOf(word[0]) === -1) { return [null, null]; } - word = word.trim().toLowerCase(); + word = word.trim(); if (word.length > 0) { return [left + 1, word]; @@ -50,6 +50,7 @@ const AutosuggestTextarea = forwardRef(({ onKeyUp, onKeyDown, onPaste, + onDrop, onFocus, autoFocus = true, lang, @@ -150,12 +151,15 @@ const AutosuggestTextarea = forwardRef(({ }, [suggestions, onSuggestionSelected, textareaRef]); const handlePaste = useCallback((e) => { - if (e.clipboardData && e.clipboardData.files.length === 1) { - onPaste(e.clipboardData.files); - e.preventDefault(); - } + onPaste(e); }, [onPaste]); + const handleDrop = useCallback((e) => { + if (onDrop) { + onDrop(e); + } + }, [onDrop]); + // Show the suggestions again whenever they change and the textarea is focused useEffect(() => { if (suggestions.size > 0 && textareaRef.current === document.activeElement) { @@ -207,6 +211,7 @@ const AutosuggestTextarea = forwardRef(({ onFocus={handleFocus} onBlur={handleBlur} onPaste={handlePaste} + onDrop={handleDrop} dir='auto' aria-autocomplete='list' aria-label={placeholder} @@ -238,6 +243,7 @@ AutosuggestTextarea.propTypes = { onKeyUp: PropTypes.func, onKeyDown: PropTypes.func, onPaste: PropTypes.func.isRequired, + onDrop: PropTypes.func, onFocus:PropTypes.func, autoFocus: PropTypes.bool, lang: PropTypes.string, diff --git a/app/javascript/mastodon/components/blurhash.tsx b/app/javascript/mastodon/components/blurhash.tsx index 8e2a8af23e5937..b7331755b7f0dd 100644 --- a/app/javascript/mastodon/components/blurhash.tsx +++ b/app/javascript/mastodon/components/blurhash.tsx @@ -30,9 +30,12 @@ const Blurhash: React.FC = ({ try { const pixels = decode(hash, width, height); const ctx = canvas.getContext('2d'); - const imageData = new ImageData(pixels, width, height); + const imageData = ctx?.createImageData(width, height); + imageData?.data.set(pixels); - ctx?.putImageData(imageData, 0, 0); + if (imageData) { + ctx?.putImageData(imageData, 0, 0); + } } catch (err) { console.error('Blurhash decoding failure', { err, hash }); } diff --git a/app/javascript/mastodon/components/button/index.tsx b/app/javascript/mastodon/components/button/index.tsx index f940c07d056d5f..a75449b0d58399 100644 --- a/app/javascript/mastodon/components/button/index.tsx +++ b/app/javascript/mastodon/components/button/index.tsx @@ -5,8 +5,10 @@ import classNames from 'classnames'; import { LoadingIndicator } from 'mastodon/components/loading_indicator'; -interface BaseProps - extends Omit, 'children'> { +interface BaseProps extends Omit< + React.ButtonHTMLAttributes, + 'children' +> { block?: boolean; secondary?: boolean; plain?: boolean; @@ -78,6 +80,7 @@ export const Button: React.FC = ({ aria-live={loading !== undefined ? 'polite' : undefined} onClick={handleClick} title={title} + // eslint-disable-next-line react/button-has-type -- set correctly via TS type={type} {...props} > diff --git a/app/javascript/mastodon/components/carousel/carousel.stories.tsx b/app/javascript/mastodon/components/carousel/carousel.stories.tsx new file mode 100644 index 00000000000000..5117bc08e3530d --- /dev/null +++ b/app/javascript/mastodon/components/carousel/carousel.stories.tsx @@ -0,0 +1,126 @@ +import type { FC } from 'react'; + +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { fn, userEvent, expect } from 'storybook/test'; + +import type { CarouselProps } from './index'; +import { Carousel } from './index'; + +interface TestSlideProps { + id: number; + text: string; + color: string; +} + +const TestSlide: FC = ({ + active, + text, + color, +}) => ( +
+ {text} +
+); + +const slides: TestSlideProps[] = [ + { + id: 1, + text: 'first', + color: 'red', + }, + { + id: 2, + text: 'second', + color: 'pink', + }, + { + id: 3, + text: 'third', + color: 'orange', + }, +]; + +type StoryProps = Pick< + CarouselProps, + 'items' | 'renderItem' | 'emptyFallback' | 'onChangeSlide' +>; + +const meta = { + title: 'Components/Carousel', + args: { + items: slides, + renderItem(item, active) { + return ; + }, + onChangeSlide: fn(), + emptyFallback: 'No slides available', + }, + render(args) { + return ( + <> + + + + ); + }, + argTypes: { + emptyFallback: { + type: 'string', + }, + }, + tags: ['test'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + async play({ args, canvas }) { + const nextButton = await canvas.findByRole('button', { name: /next/i }); + const slides = await canvas.findAllByRole('group'); + await expect(slides).toHaveLength(slides.length); + + await userEvent.click(nextButton); + await expect(args.onChangeSlide).toHaveBeenCalledWith(1, slides[1]); + + await userEvent.click(nextButton); + await expect(args.onChangeSlide).toHaveBeenCalledWith(2, slides[2]); + + // Wrap around + await userEvent.click(nextButton); + await expect(args.onChangeSlide).toHaveBeenCalledWith(0, slides[0]); + }, +}; + +export const DifferentHeights: Story = { + args: { + items: slides.map((props, index) => ({ + ...props, + styles: { height: 100 + index * 100 }, + })), + }, +}; + +export const NoSlides: Story = { + args: { + items: [], + }, +}; diff --git a/app/javascript/mastodon/components/carousel/index.tsx b/app/javascript/mastodon/components/carousel/index.tsx new file mode 100644 index 00000000000000..bc287aa969132c --- /dev/null +++ b/app/javascript/mastodon/components/carousel/index.tsx @@ -0,0 +1,244 @@ +import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react'; +import type { + ComponentPropsWithoutRef, + ComponentType, + ReactElement, + ReactNode, +} from 'react'; + +import type { MessageDescriptor } from 'react-intl'; +import { defineMessages, useIntl } from 'react-intl'; + +import classNames from 'classnames'; + +import { usePrevious } from '@dnd-kit/utilities'; +import { animated, useSpring } from '@react-spring/web'; +import { useDrag } from '@use-gesture/react'; + +import type { CarouselPaginationProps } from './pagination'; +import { CarouselPagination } from './pagination'; + +import './styles.scss'; + +const defaultMessages = defineMessages({ + previous: { id: 'lightbox.previous', defaultMessage: 'Previous' }, + next: { id: 'lightbox.next', defaultMessage: 'Next' }, + current: { + id: 'carousel.current', + defaultMessage: 'Slide {current, number} / {max, number}', + }, + slide: { + id: 'carousel.slide', + defaultMessage: 'Slide {current, number} of {max, number}', + }, +}); + +export type MessageKeys = keyof typeof defaultMessages; + +export interface CarouselSlideProps { + id: string | number; +} + +export type RenderSlideFn< + SlideProps extends CarouselSlideProps = CarouselSlideProps, +> = (item: SlideProps, active: boolean, index: number) => ReactElement; + +export interface CarouselProps< + SlideProps extends CarouselSlideProps = CarouselSlideProps, +> { + items: SlideProps[]; + renderItem: RenderSlideFn; + onChangeSlide?: (index: number, ref: Element) => void; + paginationComponent?: ComponentType | null; + paginationProps?: Partial; + messages?: Record; + emptyFallback?: ReactNode; + classNamePrefix?: string; + slideClassName?: string; +} + +export const Carousel = < + SlideProps extends CarouselSlideProps = CarouselSlideProps, +>({ + items, + renderItem, + onChangeSlide, + paginationComponent: Pagination = CarouselPagination, + paginationProps = {}, + messages = defaultMessages, + children, + emptyFallback = null, + className, + classNamePrefix = 'carousel', + slideClassName, + ...wrapperProps +}: CarouselProps & ComponentPropsWithoutRef<'div'>) => { + // Handle slide change + const [slideIndex, setSlideIndex] = useState(0); + const wrapperRef = useRef(null); + // Handle slide heights + const [currentSlideHeight, setCurrentSlideHeight] = useState( + () => wrapperRef.current?.scrollHeight ?? 0, + ); + const previousSlideHeight = usePrevious(currentSlideHeight); + const handleSlideChange = useCallback( + (direction: number) => { + setSlideIndex((prev) => { + const max = items.length - 1; + let newIndex = prev + direction; + if (newIndex < 0) { + newIndex = max; + } else if (newIndex > max) { + newIndex = 0; + } + + const slide = wrapperRef.current?.children[newIndex]; + if (slide) { + setCurrentSlideHeight(slide.scrollHeight); + if (slide instanceof HTMLElement) { + onChangeSlide?.(newIndex, slide); + } + } + + return newIndex; + }); + }, + [items.length, onChangeSlide], + ); + + const observerRef = useRef(null); + observerRef.current ??= new ResizeObserver(() => { + handleSlideChange(0); + }); + + const wrapperStyles = useSpring({ + x: `-${slideIndex * 100}%`, + height: currentSlideHeight, + // Don't animate from zero to the height of the initial slide + immediate: !previousSlideHeight, + }); + useLayoutEffect(() => { + // Update slide height when the component mounts + if (currentSlideHeight === 0) { + handleSlideChange(0); + } + }, [currentSlideHeight, handleSlideChange]); + + // Handle swiping animations + const bind = useDrag( + ({ swipe: [swipeX] }) => { + handleSlideChange(swipeX * -1); // Invert swipe as swiping left loads the next slide. + }, + { pointer: { capture: false } }, + ); + const handlePrev = useCallback(() => { + handleSlideChange(-1); + // We're focusing on the wrapper as the child slides can potentially be inert. + // Because of that, only the active slide can be focused anyway. + wrapperRef.current?.focus(); + }, [handleSlideChange]); + const handleNext = useCallback(() => { + handleSlideChange(1); + wrapperRef.current?.focus(); + }, [handleSlideChange]); + + const intl = useIntl(); + + if (items.length === 0) { + return emptyFallback; + } + + return ( +
+
+ {children} + {Pagination && items.length > 1 && ( + + )} +
+ + + {items.map((itemsProps, index) => ( + + item={itemsProps} + renderItem={renderItem} + observer={observerRef.current} + index={index} + key={`slide-${itemsProps.id}`} + className={classNames(`${classNamePrefix}__slide`, slideClassName, { + active: index === slideIndex, + })} + active={index === slideIndex} + /> + ))} + +
+ ); +}; + +type CarouselSlideWrapperProps = { + observer: ResizeObserver | null; + className: string; + active: boolean; + item: SlideProps; + index: number; +} & Pick, 'renderItem'>; + +const CarouselSlideWrapper = ({ + observer, + className, + active, + renderItem, + item, + index, +}: CarouselSlideWrapperProps) => { + const handleRef = useCallback( + (instance: HTMLDivElement | null) => { + if (observer && instance) { + observer.observe(instance); + } + }, + [observer], + ); + + const children = useMemo( + () => renderItem(item, active, index), + [renderItem, item, active, index], + ); + + return ( +
+ {children} +
+ ); +}; diff --git a/app/javascript/mastodon/components/carousel/pagination.tsx b/app/javascript/mastodon/components/carousel/pagination.tsx new file mode 100644 index 00000000000000..a2666f486fe2ab --- /dev/null +++ b/app/javascript/mastodon/components/carousel/pagination.tsx @@ -0,0 +1,54 @@ +import type { FC, MouseEventHandler } from 'react'; + +import type { MessageDescriptor } from 'react-intl'; +import { useIntl } from 'react-intl'; + +import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react'; +import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'; + +import { IconButton } from '../icon_button'; + +import type { MessageKeys } from './index'; + +export interface CarouselPaginationProps { + onNext: MouseEventHandler; + onPrev: MouseEventHandler; + current: number; + max: number; + className?: string; + messages: Record; +} + +export const CarouselPagination: FC = ({ + onNext, + onPrev, + current, + max, + className = '', + messages, +}) => { + const intl = useIntl(); + return ( +
+ + + {intl.formatMessage(messages.current, { + current: current + 1, + max, + sr: (chunk) => {chunk}, + })} + + +
+ ); +}; diff --git a/app/javascript/mastodon/components/carousel/styles.scss b/app/javascript/mastodon/components/carousel/styles.scss new file mode 100644 index 00000000000000..bcd0bc7d3af76b --- /dev/null +++ b/app/javascript/mastodon/components/carousel/styles.scss @@ -0,0 +1,28 @@ +.carousel { + gap: 16px; + overflow: hidden; + touch-action: pan-y; + + &__header { + padding: 8px 16px; + } + + &__pagination { + display: flex; + align-items: center; + justify-content: center; + gap: 4px; + } + + &__slides { + display: flex; + flex-wrap: nowrap; + align-items: start; + } + + &__slide { + flex: 0 0 100%; + width: 100%; + overflow: hidden; + } +} diff --git a/app/javascript/mastodon/components/column_back_button.tsx b/app/javascript/mastodon/components/column_back_button.tsx index aad355d710df35..8012ba7df694e6 100644 --- a/app/javascript/mastodon/components/column_back_button.tsx +++ b/app/javascript/mastodon/components/column_back_button.tsx @@ -30,7 +30,7 @@ export const ColumnBackButton: React.FC<{ onClick?: OnClickCallback }> = ({ const handleClick = useHandleClick(onClick); const component = ( - @@ -193,6 +196,7 @@ export const ColumnHeader: React.FC = ({ aria-label={intl.formatMessage(messages.moveRight)} className='icon-button column-header__setting-btn' onClick={handleMoveRight} + type='button' > @@ -203,6 +207,7 @@ export const ColumnHeader: React.FC = ({ @@ -248,9 +228,7 @@ export const DropdownMenu = ({ target={option.target ?? '_target'} data-method={option.method} rel='noopener' - ref={i === 0 ? handleFocusedItemRef : undefined} onClick={handleItemClick} - onKeyUp={handleItemKeyUp} data-index={i} > @@ -258,13 +236,7 @@ export const DropdownMenu = ({ ); } else { element = ( - + ); @@ -307,15 +279,7 @@ export const DropdownMenu = ({ })} > {items.map((option, i) => - renderItemMethod( - option, - i, - { - onClick: handleItemClick, - onKeyUp: handleItemKeyUp, - }, - i === 0 ? handleFocusedItemRef : undefined, - ), + renderItemMethod(option, i, handleItemClick), )} )} @@ -340,11 +304,12 @@ interface DropdownProps { */ scrollKey?: string; status?: ImmutableMap; + needsStatusRefresh?: boolean; forceDropdown?: boolean; renderItem?: RenderItemFn; renderHeader?: RenderHeaderFn; onOpen?: // Must use a union type for the full function as a union with void is not allowed. - | ((event: React.MouseEvent | React.KeyboardEvent) => void) + | ((event: React.MouseEvent | React.KeyboardEvent) => void) | ((event: React.MouseEvent | React.KeyboardEvent) => boolean); onItemClick?: ItemClickFn; } @@ -363,6 +328,7 @@ export const Dropdown = ({ placement = 'bottom', offset = [5, 5], status, + needsStatusRefresh, forceDropdown = false, renderItem, renderHeader, @@ -382,6 +348,7 @@ export const Dropdown = ({ const prefetchAccountId = status ? status.getIn(['account', 'id']) : undefined; + const statusId = status?.get('id') as string | undefined; const handleClose = useCallback(() => { if (buttonRef.current) { @@ -399,7 +366,7 @@ export const Dropdown = ({ }, [dispatch, currentId]); const handleItemClick = useCallback( - (e: React.MouseEvent | React.KeyboardEvent) => { + (e: React.MouseEvent) => { const i = Number(e.currentTarget.getAttribute('data-index')); const item = items?.[i]; @@ -420,10 +387,20 @@ export const Dropdown = ({ [handleClose, onItemClick, items], ); - const toggleDropdown = useCallback( - (e: React.MouseEvent | React.KeyboardEvent) => { - const { type } = e; + const isKeypressRef = useRef(false); + + const handleKeyDown = useCallback((e: React.KeyboardEvent) => { + if (e.key === ' ' || e.key === 'Enter') { + isKeypressRef.current = true; + } + }, []); + + const unsetIsKeypress = useCallback(() => { + isKeypressRef.current = false; + }, []); + const toggleDropdown = useCallback( + (e: React.MouseEvent) => { if (open) { handleClose(); } else { @@ -436,6 +413,15 @@ export const Dropdown = ({ dispatch(fetchRelationships([prefetchAccountId])); } + if (needsStatusRefresh && statusId) { + dispatch( + fetchStatus(statusId, { + forceFetch: true, + alsoFetchContext: false, + }), + ); + } + if (isUserTouching() && !forceDropdown) { dispatch( openModal({ @@ -450,10 +436,11 @@ export const Dropdown = ({ dispatch( openDropdownMenu({ id: currentId, - keyboard: type !== 'click', + keyboard: isKeypressRef.current, scrollKey, }), ); + isKeypressRef.current = false; } } }, @@ -468,6 +455,8 @@ export const Dropdown = ({ items, forceDropdown, handleClose, + statusId, + needsStatusRefresh, ], ); @@ -484,6 +473,9 @@ export const Dropdown = ({ const buttonProps = { disabled, onClick: toggleDropdown, + onKeyDown: handleKeyDown, + onKeyUp: unsetIsKeypress, + onBlur: unsetIsKeypress, 'aria-expanded': open, 'aria-controls': menuId, ref: buttonRef, diff --git a/app/javascript/mastodon/components/edited_timestamp/index.tsx b/app/javascript/mastodon/components/edited_timestamp/index.tsx index 63b21cf5bdff2f..36f8db8abff159 100644 --- a/app/javascript/mastodon/components/edited_timestamp/index.tsx +++ b/app/javascript/mastodon/components/edited_timestamp/index.tsx @@ -58,17 +58,7 @@ export const EditedTimestamp: React.FC<{ }, []); const renderItem = useCallback( - ( - item: HistoryItem, - index: number, - { - onClick, - onKeyUp, - }: { - onClick: React.MouseEventHandler; - onKeyUp: React.KeyboardEventHandler; - }, - ) => { + (item: HistoryItem, index: number, onClick: React.MouseEventHandler) => { const formattedDate = ( - @@ -118,7 +108,7 @@ export const EditedTimestamp: React.FC<{ onItemClick={handleItemClick} forceDropdown > - diff --git a/app/javascript/mastodon/components/hashtag_bar.tsx b/app/javascript/mastodon/components/hashtag_bar.tsx index 4f88385bef951f..a9dd6941a47e8a 100644 --- a/app/javascript/mastodon/components/hashtag_bar.tsx +++ b/app/javascript/mastodon/components/hashtag_bar.tsx @@ -33,7 +33,7 @@ function isNodeLinkHashtag(element: Node): element is HTMLLinkElement { return ( element instanceof HTMLAnchorElement && // it may be a starting with a hashtag - (element.textContent?.[0] === '#' || + (element.textContent.startsWith('#') || // or a # element.previousSibling?.textContent?.[ element.previousSibling.textContent.length - 1 @@ -235,7 +235,7 @@ const HashtagBar: React.FC<{ ))} {!expanded && hashtags.length > VISIBLE_HASHTAGS && ( - is focused, " + When a is focused, Enter - " should not trigger "open", but "o - " should. + should not trigger open, but o + should.

When an input element is focused, hotkeys should not interfere with diff --git a/app/javascript/mastodon/components/hotkeys/index.tsx b/app/javascript/mastodon/components/hotkeys/index.tsx index b1484ec3acb816..c62fc0c20acd77 100644 --- a/app/javascript/mastodon/components/hotkeys/index.tsx +++ b/app/javascript/mastodon/components/hotkeys/index.tsx @@ -111,6 +111,7 @@ const hotkeyMatcherMap = { openProfile: just('p'), moveDown: just('j'), moveUp: just('k'), + moveToTop: just('0'), toggleHidden: just('x'), toggleSensitive: just('h'), toggleComposeSpoilers: optionPlus('x'), @@ -180,25 +181,24 @@ export function useHotkeys(handlers: HandlerMap) { if (shouldHandleEvent) { const matchCandidates: { - handler: (event: KeyboardEvent) => void; + // A candidate will be have an undefined handler if it's matched, + // but handled in a parent component rather than this one. + handler: ((event: KeyboardEvent) => void) | undefined; priority: number; }[] = []; (Object.keys(hotkeyMatcherMap) as HotkeyName[]).forEach( (handlerName) => { const handler = handlersRef.current[handlerName]; + const hotkeyMatcher = hotkeyMatcherMap[handlerName]; - if (handler) { - const hotkeyMatcher = hotkeyMatcherMap[handlerName]; + const { isMatch, priority } = hotkeyMatcher( + event, + bufferedKeys.current, + ); - const { isMatch, priority } = hotkeyMatcher( - event, - bufferedKeys.current, - ); - - if (isMatch) { - matchCandidates.push({ handler, priority }); - } + if (isMatch) { + matchCandidates.push({ handler, priority }); } }, ); diff --git a/app/javascript/mastodon/components/hover_card_account.tsx b/app/javascript/mastodon/components/hover_card_account.tsx index a5a5e4c957540f..b51af40e94c2d5 100644 --- a/app/javascript/mastodon/components/hover_card_account.tsx +++ b/app/javascript/mastodon/components/hover_card_account.tsx @@ -105,7 +105,14 @@ export const HoverCardAccount = forwardRef< accountId={account.id} className='hover-card__bio' /> - + +

+ +
+ {note && note.length > 0 && (
diff --git a/app/javascript/mastodon/components/hover_card_controller.tsx b/app/javascript/mastodon/components/hover_card_controller.tsx index 38c3306f30a301..81510e8bd6e42c 100644 --- a/app/javascript/mastodon/components/hover_card_controller.tsx +++ b/app/javascript/mastodon/components/hover_card_controller.tsx @@ -27,7 +27,6 @@ export const HoverCardController: React.FC = () => { const [setLeaveTimeout, cancelLeaveTimeout] = useTimeout(); const [setEnterTimeout, cancelEnterTimeout, delayEnterTimeout] = useTimeout(); const [setScrollTimeout] = useTimeout(); - const location = useLocation(); const handleClose = useCallback(() => { cancelEnterTimeout(); @@ -36,9 +35,12 @@ export const HoverCardController: React.FC = () => { setAnchor(null); }, [cancelEnterTimeout, cancelLeaveTimeout, setOpen, setAnchor]); - useEffect(() => { + const location = useLocation(); + const [previousLocation, setPreviousLocation] = useState(location); + if (location !== previousLocation) { + setPreviousLocation(location); handleClose(); - }, [handleClose, location]); + } useEffect(() => { let isScrolling = false; diff --git a/app/javascript/mastodon/components/html_block/html_block.stories.tsx b/app/javascript/mastodon/components/html_block/html_block.stories.tsx new file mode 100644 index 00000000000000..6fb3206df0e8c1 --- /dev/null +++ b/app/javascript/mastodon/components/html_block/html_block.stories.tsx @@ -0,0 +1,66 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { expect } from 'storybook/test'; + +import { HTMLBlock } from './index'; + +const meta = { + title: 'Components/HTMLBlock', + component: HTMLBlock, + args: { + htmlString: `

Hello, world!

+

A link

+

This should be filtered out:

+

This also has emoji: 🖤

`, + }, + argTypes: { + extraEmojis: { + table: { + disable: true, + }, + }, + onElement: { + table: { + disable: true, + }, + }, + onAttribute: { + table: { + disable: true, + }, + }, + }, + render(args) { + return ( + // Just for visual clarity in Storybook. + + ); + }, + // Force Twemoji to demonstrate emoji rendering. + parameters: { + state: { + meta: { + emoji_style: 'twemoji', + }, + }, + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + async play({ canvas }) { + const link = canvas.queryByRole('link'); + await expect(link).toBeInTheDocument(); + const button = canvas.queryByRole('button'); + await expect(button).not.toBeInTheDocument(); + }, +}; diff --git a/app/javascript/mastodon/components/html_block/index.tsx b/app/javascript/mastodon/components/html_block/index.tsx new file mode 100644 index 00000000000000..ed559269a41de3 --- /dev/null +++ b/app/javascript/mastodon/components/html_block/index.tsx @@ -0,0 +1,30 @@ +import { useCallback } from 'react'; + +import type { OnElementHandler } from '@/mastodon/utils/html'; +import { polymorphicForwardRef } from '@/types/polymorphic'; + +import type { EmojiHTMLProps } from '../emoji/html'; +import { EmojiHTML } from '../emoji/html'; +import { useElementHandledLink } from '../status/handled_link'; + +export const HTMLBlock = polymorphicForwardRef< + 'div', + EmojiHTMLProps & Parameters[0] +>( + ({ + onElement: onParentElement, + hrefToMention, + hashtagAccountId, + ...props + }) => { + const { onElement: onLinkElement } = useElementHandledLink({ + hrefToMention, + hashtagAccountId, + }); + const onElement: OnElementHandler = useCallback( + (...args) => onParentElement?.(...args) ?? onLinkElement(...args), + [onLinkElement, onParentElement], + ); + return ; + }, +); diff --git a/app/javascript/mastodon/components/icon_button.tsx b/app/javascript/mastodon/components/icon_button.tsx index 9d32ab1f52802c..9f64a257f0dc22 100644 --- a/app/javascript/mastodon/components/icon_button.tsx +++ b/app/javascript/mastodon/components/icon_button.tsx @@ -1,7 +1,9 @@ -import { useState, useEffect, useCallback, forwardRef } from 'react'; +import { useCallback, forwardRef } from 'react'; import classNames from 'classnames'; +import { usePrevious } from '../hooks/usePrevious'; + import { AnimatedNumber } from './animated_number'; import type { IconProp } from './icon'; import { Icon } from './icon'; @@ -55,23 +57,6 @@ export const IconButton = forwardRef( }, buttonRef, ) => { - const [activate, setActivate] = useState(false); - const [deactivate, setDeactivate] = useState(false); - - useEffect(() => { - if (!animate) { - return; - } - - if (activate && !active) { - setActivate(false); - setDeactivate(true); - } else if (!activate && active) { - setActivate(true); - setDeactivate(false); - } - }, [setActivate, setDeactivate, animate, active, activate]); - const handleClick: React.MouseEventHandler = useCallback( (e) => { e.preventDefault(); @@ -108,12 +93,15 @@ export const IconButton = forwardRef( ...(active ? activeStyle : {}), }; + const previousActive = usePrevious(active) ?? active; + const shouldAnimate = animate && active !== previousActive; + const classes = classNames(className, 'icon-button', { active, disabled, inverted, - activate, - deactivate, + activate: shouldAnimate && active, + deactivate: shouldAnimate && !active, overlayed: overlay, 'icon-button--with-counter': typeof counter !== 'undefined', }); diff --git a/app/javascript/mastodon/components/learn_more_link.tsx b/app/javascript/mastodon/components/learn_more_link.tsx index b5337794c952a2..8d22bb7a3ba053 100644 --- a/app/javascript/mastodon/components/learn_more_link.tsx +++ b/app/javascript/mastodon/components/learn_more_link.tsx @@ -23,6 +23,7 @@ export const LearnMoreLink: React.FC<{ children: React.ReactNode }> = ({ onClick={handleClick} aria-expanded={open} aria-controls={accessibilityId} + type='button' > = ({
{children}
- )} {!showResults && ( <> - {' '} ·{' '} @@ -186,7 +188,11 @@ export const Poll: React.FC = ({ pollId, disabled, status }) => { )} {showResults && !disabled && ( <> - {' '} ·{' '} @@ -234,12 +240,11 @@ const PollOption: React.FC = (props) => { let titleHtml = option.translation?.titleHtml ?? option.titleHtml; if (!titleHtml) { - const emojiMap = makeEmojiMap(poll.emojis); - titleHtml = emojify(escapeTextContentForBrowser(title), emojiMap); + titleHtml = escapeTextContentForBrowser(title); } return titleHtml; - }, [option, poll, title]); + }, [option, title]); // Handlers const handleOptionChange = useCallback(() => { @@ -305,10 +310,11 @@ const PollOption: React.FC = (props) => { )} - {!!voted && ( diff --git a/app/javascript/mastodon/components/router.tsx b/app/javascript/mastodon/components/router.tsx index 815b4b59abd725..1dc1d45083dfd9 100644 --- a/app/javascript/mastodon/components/router.tsx +++ b/app/javascript/mastodon/components/router.tsx @@ -1,6 +1,7 @@ import type { PropsWithChildren } from 'react'; import type React from 'react'; +import type { useLocation } from 'react-router'; import { Router as OriginalRouter, useHistory } from 'react-router'; import type { @@ -18,7 +19,9 @@ interface MastodonLocationState { mastodonModalKey?: string; } -type LocationState = MastodonLocationState | null | undefined; +export type LocationState = MastodonLocationState | null | undefined; + +export type MastodonLocation = ReturnType>; type HistoryPath = Path | LocationDescriptor; diff --git a/app/javascript/mastodon/components/scrollable_list.jsx b/app/javascript/mastodon/components/scrollable_list.jsx index 22ec18afa9c926..38c3cd991bc736 100644 --- a/app/javascript/mastodon/components/scrollable_list.jsx +++ b/app/javascript/mastodon/components/scrollable_list.jsx @@ -10,7 +10,7 @@ import { connect } from 'react-redux'; import { supportsPassiveEvents } from 'detect-passive-events'; import { throttle } from 'lodash'; -import ScrollContainer from 'mastodon/containers/scroll_container'; +import { ScrollContainer } from 'mastodon/containers/scroll_container'; import IntersectionObserverArticleContainer from '../containers/intersection_observer_article_container'; import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../features/ui/util/fullscreen'; @@ -399,7 +399,7 @@ class ScrollableList extends PureComponent { if (trackScroll) { return ( - + {scrollableArea} ); diff --git a/app/javascript/mastodon/components/status.jsx b/app/javascript/mastodon/components/status.jsx index 196da7c99a8f0c..892270b394165c 100644 --- a/app/javascript/mastodon/components/status.jsx +++ b/app/javascript/mastodon/components/status.jsx @@ -117,8 +117,9 @@ class Status extends ImmutablePureComponent { hidden: PropTypes.bool, unread: PropTypes.bool, showThread: PropTypes.bool, + showActions: PropTypes.bool, isQuotedPost: PropTypes.bool, - shouldHighlightOnMount: PropTypes.bool, + shouldHighlightOnMount: PropTypes.bool, getScrollPosition: PropTypes.func, updateScrollBottom: PropTypes.func, cacheMediaWidth: PropTypes.func, @@ -381,7 +382,7 @@ class Status extends ImmutablePureComponent { }; render () { - const { intl, hidden, featured, unfocusable, unread, showThread, isQuotedPost = false, scrollKey, pictureInPicture, previousId, nextInReplyToId, rootId, skipPrepend, avatarSize = 46, children } = this.props; + const { intl, hidden, featured, unfocusable, unread, showThread, showActions = true, isQuotedPost = false, scrollKey, pictureInPicture, previousId, nextInReplyToId, rootId, skipPrepend, avatarSize = 46, children } = this.props; let { status, account, ...other } = this.props; @@ -538,9 +539,8 @@ class Status extends ImmutablePureComponent { } else if (status.get('card') && !status.get('quote')) { media = ( ); @@ -553,7 +553,6 @@ class Status extends ImmutablePureComponent { } const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status); - return (
@@ -600,7 +599,7 @@ class Status extends ImmutablePureComponent { {matchedFilters && } - {(status.get('spoiler_text').length > 0 && (!matchedFilters || this.state.showDespiteFilter)) && } + {(!matchedFilters || this.state.showDespiteFilter) && } {expanded && ( <> @@ -620,7 +619,7 @@ class Status extends ImmutablePureComponent { )} - {!isQuotedPost && + {(showActions && !isQuotedPost) && }
diff --git a/app/javascript/mastodon/components/status/boost_button.tsx b/app/javascript/mastodon/components/status/boost_button.tsx index 337eca507175d5..023ba8ff1956a4 100644 --- a/app/javascript/mastodon/components/status/boost_button.tsx +++ b/app/javascript/mastodon/components/status/boost_button.tsx @@ -1,5 +1,5 @@ import { useCallback, useMemo } from 'react'; -import type { FC, KeyboardEvent, MouseEvent } from 'react'; +import type { FC, KeyboardEvent, MouseEvent, MouseEventHandler } from 'react'; import { useIntl } from 'react-intl'; @@ -8,12 +8,14 @@ import classNames from 'classnames'; import { quoteComposeById } from '@/mastodon/actions/compose_typed'; import { toggleReblog } from '@/mastodon/actions/interactions'; import { openModal } from '@/mastodon/actions/modal'; +import { fetchStatus } from '@/mastodon/actions/statuses'; +import { quickBoosting } from '@/mastodon/initial_state'; import type { ActionMenuItem } from '@/mastodon/models/dropdown_menu'; import type { Status } from '@/mastodon/models/status'; import { useAppDispatch, useAppSelector } from '@/mastodon/store'; import type { SomeRequired } from '@/mastodon/utils/types'; -import type { RenderItemFn, RenderItemFnHandlers } from '../dropdown_menu'; +import type { RenderItemFn } from '../dropdown_menu'; import { Dropdown, DropdownMenuItemContent } from '../dropdown_menu'; import { IconButton } from '../icon_button'; @@ -24,18 +26,62 @@ import { selectStatusState, } from './boost_button_utils'; -const renderMenuItem: RenderItemFn = ( - item, - index, - handlers, - focusRefCallback, -) => ( +const StandaloneBoostButton: FC = ({ status, counters }) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + + const statusState = useAppSelector((state) => + selectStatusState(state, status), + ); + const { title, meta, iconComponent, disabled } = useMemo( + () => boostItemState(statusState), + [statusState], + ); + + const handleClick: MouseEventHandler = useCallback( + (event) => { + if (statusState.isLoggedIn) { + dispatch(toggleReblog(status.get('id') as string, event.shiftKey)); + } else { + dispatch( + openModal({ + modalType: 'INTERACTION', + modalProps: { + accountId: status.getIn(['account', 'id']), + url: status.get('uri'), + }, + }), + ); + } + }, + [dispatch, status, statusState.isLoggedIn], + ); + + return ( + + ); +}; + +const renderMenuItem: RenderItemFn = (item, index, onClick) => ( ); @@ -46,7 +92,7 @@ interface ReblogButtonProps { type ActionMenuItemWithIcon = SomeRequired; -export const BoostButton: FC = ({ status, counters }) => { +const BoostOrQuoteMenu: FC = ({ status, counters }) => { const intl = useIntl(); const dispatch = useAppDispatch(); const statusState = useAppSelector((state) => @@ -67,6 +113,7 @@ export const BoostButton: FC = ({ status, counters }) => { const statusId = status.get('id') as string; const wasBoosted = !!status.get('reblogged'); + const quoteApproval = status.get('quote_approval'); const showLoginPrompt = useCallback(() => { dispatch( @@ -123,9 +170,16 @@ export const BoostButton: FC = ({ status, counters }) => { dispatch(toggleReblog(status.get('id'), true)); return false; } + + if (quoteApproval === null) { + dispatch( + fetchStatus(statusId, { forceFetch: true, alsoFetchContext: false }), + ); + } + return true; }, - [dispatch, isLoggedIn, showLoginPrompt, status], + [dispatch, isLoggedIn, showLoginPrompt, status, quoteApproval, statusId], ); return ( @@ -142,6 +196,7 @@ export const BoostButton: FC = ({ status, counters }) => { isMenuDisabled ? messages.all_disabled : messages.reblog_or_quote, )} icon='retweet' + className='status__action-bar__button' iconComponent={boostIcon} counter={ counters @@ -158,16 +213,10 @@ export const BoostButton: FC = ({ status, counters }) => { interface ReblogMenuItemProps { item: ActionMenuItem; index: number; - handlers: RenderItemFnHandlers; - focusRefCallback?: (c: HTMLAnchorElement | HTMLButtonElement | null) => void; + onClick: React.MouseEventHandler; } -const ReblogMenuItem: FC = ({ - index, - item, - handlers, - focusRefCallback, -}) => { +const ReblogMenuItem: FC = ({ index, item, onClick }) => { const { text, highlighted, disabled } = item; return ( @@ -178,13 +227,19 @@ const ReblogMenuItem: FC = ({ key={`${text}-${index}`} > ); }; + +// Switch between the standalone boost button or the +// "Boost or quote" menu based on the quickBoosting preference +export const BoostButton = quickBoosting + ? StandaloneBoostButton + : BoostOrQuoteMenu; diff --git a/app/javascript/mastodon/components/status/handled_link.stories.tsx b/app/javascript/mastodon/components/status/handled_link.stories.tsx new file mode 100644 index 00000000000000..e34383370486c3 --- /dev/null +++ b/app/javascript/mastodon/components/status/handled_link.stories.tsx @@ -0,0 +1,102 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; + +import { HashtagMenuController } from '@/mastodon/features/ui/components/hashtag_menu_controller'; +import { accountFactoryState } from '@/testing/factories'; + +import { HoverCardController } from '../hover_card_controller'; + +import type { HandledLinkProps } from './handled_link'; +import { HandledLink } from './handled_link'; + +type HandledLinkStoryProps = Pick< + HandledLinkProps, + 'href' | 'text' | 'prevText' +> & { + mentionAccount: 'local' | 'remote' | 'none'; + hashtagAccount: boolean; +}; + +const meta = { + title: 'Components/Status/HandledLink', + render({ mentionAccount, hashtagAccount, ...args }) { + let mention: HandledLinkProps['mention'] | undefined; + if (mentionAccount === 'local') { + mention = { id: '1', acct: 'testuser' }; + } else if (mentionAccount === 'remote') { + mention = { id: '2', acct: 'remoteuser@mastodon.social' }; + } + return ( + <> + + {args.text} + + + + + ); + }, + args: { + href: 'https://example.com/path/subpath?query=1#hash', + text: 'https://example.com', + mentionAccount: 'none', + hashtagAccount: false, + }, + argTypes: { + mentionAccount: { + control: { type: 'select' }, + options: ['local', 'remote', 'none'], + defaultValue: 'none', + }, + }, + parameters: { + state: { + accounts: { + '1': accountFactoryState({ id: '1', acct: 'hashtaguser' }), + }, + }, + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; + +export const Simple: Story = { + args: { + href: 'https://example.com/test', + }, +}; + +export const Hashtag: Story = { + args: { + text: '#example', + hashtagAccount: true, + }, +}; + +export const Mention: Story = { + args: { + text: '@user', + mentionAccount: 'local', + }, +}; + +export const InternalLink: Story = { + args: { + href: '/about', + text: 'About', + }, +}; + +export const InvalidURL: Story = { + args: { + href: 'ht!tp://invalid-url', + text: 'ht!tp://invalid-url -- invalid!', + }, +}; diff --git a/app/javascript/mastodon/components/status/handled_link.tsx b/app/javascript/mastodon/components/status/handled_link.tsx new file mode 100644 index 00000000000000..5fcea5f8b9841a --- /dev/null +++ b/app/javascript/mastodon/components/status/handled_link.tsx @@ -0,0 +1,116 @@ +import { useCallback } from 'react'; +import type { ComponentProps, FC } from 'react'; + +import classNames from 'classnames'; +import { Link } from 'react-router-dom'; + +import type { ApiMentionJSON } from '@/mastodon/api_types/statuses'; +import type { OnElementHandler } from '@/mastodon/utils/html'; + +export interface HandledLinkProps { + href: string; + text: string; + prevText?: string; + hashtagAccountId?: string; + mention?: Pick; +} + +export const HandledLink: FC> = ({ + href, + text, + prevText, + hashtagAccountId, + mention, + className, + children, + ...props +}) => { + // Handle hashtags + if ( + (text.startsWith('#') || + prevText?.endsWith('#') || + text.startsWith('#') || + prevText?.endsWith('#')) && + !text.includes('%') + ) { + const hashtag = text.slice(1).trim(); + + return ( + + {children} + + ); + } else if (mention) { + // Handle mentions + return ( + + {children} + + ); + } + + // Non-absolute paths treated as internal links. This shouldn't happen, but just in case. + if (href.startsWith('/')) { + return ( + + {children} + + ); + } + + return ( + + {children} + + ); +}; + +export const useElementHandledLink = ({ + hashtagAccountId, + hrefToMention, +}: { + hashtagAccountId?: string; + hrefToMention?: (href: string) => ApiMentionJSON | undefined; +} = {}) => { + const onElement = useCallback( + (element, { key, ...props }, children) => { + if (element instanceof HTMLAnchorElement) { + const mention = hrefToMention?.(element.href); + return ( + + {children} + + ); + } + return undefined; + }, + [hashtagAccountId, hrefToMention], + ); + return { onElement }; +}; diff --git a/app/javascript/mastodon/components/status/intercept_status_clicks.tsx b/app/javascript/mastodon/components/status/intercept_status_clicks.tsx new file mode 100644 index 00000000000000..b0dbc3c693848c --- /dev/null +++ b/app/javascript/mastodon/components/status/intercept_status_clicks.tsx @@ -0,0 +1,45 @@ +import { useCallback, useRef } from 'react'; + +export const InterceptStatusClicks: React.FC<{ + onPreventedClick: ( + clickedArea: 'account' | 'post', + event: React.MouseEvent, + ) => void; + children: React.ReactNode; +}> = ({ onPreventedClick, children }) => { + const wrapperRef = useRef(null); + + const handleClick = useCallback( + (e: React.MouseEvent) => { + const clickTarget = e.target as Element; + const allowedElementsSelector = + '.video-player, .audio-player, .media-gallery, .content-warning'; + const allowedElements = wrapperRef.current?.querySelectorAll( + allowedElementsSelector, + ); + const isTargetClickAllowed = + allowedElements && + Array.from(allowedElements).some((element) => { + return clickTarget === element || element.contains(clickTarget); + }); + + if (!isTargetClickAllowed) { + e.preventDefault(); + e.stopPropagation(); + + const wasAccountAreaClicked = !!clickTarget.closest( + 'a.status__display-name', + ); + + onPreventedClick(wasAccountAreaClicked ? 'account' : 'post', e); + } + }, + [onPreventedClick], + ); + + return ( +
+ {children} +
+ ); +}; diff --git a/app/javascript/mastodon/components/status_action_bar/index.jsx b/app/javascript/mastodon/components/status_action_bar/index.jsx index 3e82912ab1956c..5c79970e23f211 100644 --- a/app/javascript/mastodon/components/status_action_bar/index.jsx +++ b/app/javascript/mastodon/components/status_action_bar/index.jsx @@ -20,11 +20,12 @@ import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/ import { WithRouterPropTypes } from 'mastodon/utils/react_router'; import { Dropdown } from 'mastodon/components/dropdown_menu'; -import { me } from '../../initial_state'; +import { me, quickBoosting } from '../../initial_state'; import { IconButton } from '../icon_button'; import { BoostButton } from '../status/boost_button'; import { RemoveQuoteHint } from './remove_quote_hint'; +import { quoteItemState, selectStatusState } from '../status/boost_button_utils'; const messages = defineMessages({ delete: { id: 'status.delete', defaultMessage: 'Delete' }, @@ -68,6 +69,7 @@ const mapStateToProps = (state, { status }) => { return ({ relationship: state.getIn(['relationships', status.getIn(['account', 'id'])]), quotedAccountId: quotedStatusId ? state.getIn(['statuses', quotedStatusId, 'account']) : null, + statusQuoteState: selectStatusState(state, status), }); }; @@ -76,6 +78,7 @@ class StatusActionBar extends ImmutablePureComponent { identity: identityContextPropShape, status: ImmutablePropTypes.map.isRequired, relationship: ImmutablePropTypes.record, + statusQuoteState: PropTypes.object, quotedAccountId: PropTypes.string, contextType: PropTypes.string, onReply: PropTypes.func, @@ -125,6 +128,10 @@ class StatusActionBar extends ImmutablePureComponent { } }; + handleQuoteClick = () => { + this.props.onQuote(this.props.status); + }; + handleShareClick = () => { navigator.share({ url: this.props.status.get('url'), @@ -241,7 +248,7 @@ class StatusActionBar extends ImmutablePureComponent { }; render () { - const { status, relationship, quotedAccountId, contextType, intl, withDismiss, withCounters, scrollKey } = this.props; + const { status, relationship, statusQuoteState, quotedAccountId, contextType, intl, withDismiss, withCounters, scrollKey } = this.props; const { signedIn, permissions } = this.props.identity; const publicStatus = ['public', 'unlisted'].includes(status.get('visibility')); @@ -270,6 +277,19 @@ class StatusActionBar extends ImmutablePureComponent { menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed }); } + if (quickBoosting && signedIn) { + const quoteItem = quoteItemState(statusQuoteState); + menu.push(null); + menu.push({ + text: intl.formatMessage(quoteItem.title), + description: quoteItem.meta + ? intl.formatMessage(quoteItem.meta) + : undefined, + disabled: quoteItem.disabled, + action: this.handleQuoteClick, + }); + } + if (signedIn) { menu.push(null); @@ -384,16 +404,21 @@ class StatusActionBar extends ImmutablePureComponent { { dismissQuoteHint(); return true; }} - /> + > + + )}
diff --git a/app/javascript/mastodon/components/status_action_bar/remove_quote_hint.module.css b/app/javascript/mastodon/components/status_action_bar/remove_quote_hint.module.css new file mode 100644 index 00000000000000..5045b6d1b95590 --- /dev/null +++ b/app/javascript/mastodon/components/status_action_bar/remove_quote_hint.module.css @@ -0,0 +1,3 @@ +.inlineIcon { + vertical-align: middle; +} diff --git a/app/javascript/mastodon/components/status_action_bar/remove_quote_hint.tsx b/app/javascript/mastodon/components/status_action_bar/remove_quote_hint.tsx index dec9c3ef38cb2f..cfa72feb6843a5 100644 --- a/app/javascript/mastodon/components/status_action_bar/remove_quote_hint.tsx +++ b/app/javascript/mastodon/components/status_action_bar/remove_quote_hint.tsx @@ -6,13 +6,15 @@ import classNames from 'classnames'; import Overlay from 'react-overlays/Overlay'; +import { useDismissible } from '@/mastodon/hooks/useDismissible'; import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; import { Button } from '../button'; -import { useDismissableBannerState } from '../dismissable_banner'; import { Icon } from '../icon'; -const DISMISSABLE_BANNER_ID = 'notifications/remove_quote_hint'; +import classes from './remove_quote_hint.module.css'; + +const DISMISSIBLE_BANNER_ID = 'notifications/remove_quote_hint'; /** * We don't want to show this hint in the UI more than once, @@ -29,9 +31,7 @@ export const RemoveQuoteHint: React.FC<{ const anchorRef = useRef(null); const intl = useIntl(); - const { wasDismissed, dismiss } = useDismissableBannerState({ - id: DISMISSABLE_BANNER_ID, - }); + const { wasDismissed, dismiss } = useDismissible(DISMISSIBLE_BANNER_ID); const shouldShowHint = !wasDismissed && canShowHint; @@ -44,6 +44,7 @@ export const RemoveQuoteHint: React.FC<{ if (!firstHintId) { firstHintId = uniqueId; + // eslint-disable-next-line react-hooks/set-state-in-effect setIsOnlyHint(true); } @@ -64,8 +65,8 @@ export const RemoveQuoteHint: React.FC<{ flip offset={[12, 10]} placement='bottom-end' - target={anchorRef.current} - container={anchorRef.current} + target={anchorRef} + container={anchorRef} > {({ props, placement }) => (
), }} diff --git a/app/javascript/mastodon/components/status_banner.tsx b/app/javascript/mastodon/components/status_banner.tsx index e11b2c9279b761..b7f7289652d8be 100644 --- a/app/javascript/mastodon/components/status_banner.tsx +++ b/app/javascript/mastodon/components/status_banner.tsx @@ -3,6 +3,8 @@ import { useCallback, useRef, useId } from 'react'; import { FormattedMessage } from 'react-intl'; +import { AnimateEmojiProvider } from './emoji/context'; + export enum BannerVariant { Warning = 'warning', Filter = 'filter', @@ -34,8 +36,7 @@ export const StatusBanner: React.FC<{ return ( // Element clicks are passed on to button - // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions -
)} -
+ ); }; diff --git a/app/javascript/mastodon/components/status_content.jsx b/app/javascript/mastodon/components/status_content.jsx index af0059c7d62850..6bb04ddab88473 100644 --- a/app/javascript/mastodon/components/status_content.jsx +++ b/app/javascript/mastodon/components/status_content.jsx @@ -13,9 +13,10 @@ import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react' import { Icon } from 'mastodon/components/icon'; import { Poll } from 'mastodon/components/poll'; import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; -import { autoPlayGif, languages as preloadedLanguages } from 'mastodon/initial_state'; -import { EmojiHTML } from '../features/emoji/emoji_html'; -import { isModernEmojiEnabled } from '../utils/environment'; +import { languages as preloadedLanguages } from 'mastodon/initial_state'; + +import { EmojiHTML } from './emoji/html'; +import { HandledLink } from './status/handled_link'; const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top) @@ -25,9 +26,6 @@ const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top) * @returns {string} */ export function getStatusContent(status) { - if (isModernEmojiEnabled()) { - return status.getIn(['translation', 'content']) || status.get('content'); - } return status.getIn(['translation', 'contentHtml']) || status.get('contentHtml'); } @@ -72,6 +70,17 @@ const mapStateToProps = state => ({ languages: state.getIn(['server', 'translationLanguages', 'items']), }); +const compareUrls = (href1, href2) => { + try { + const url1 = new URL(href1); + const url2 = new URL(href2); + + return url1.origin === url2.origin && url1.pathname === url2.pathname && url1.search === url2.search; + } catch { + return false; + } +}; + class StatusContent extends PureComponent { static propTypes = { identity: identityContextPropShape, @@ -97,36 +106,6 @@ class StatusContent extends PureComponent { } const { status, onCollapsedToggle } = this.props; - const links = node.querySelectorAll('a'); - - let link, mention; - - for (var i = 0; i < links.length; ++i) { - link = links[i]; - - if (link.classList.contains('status-link')) { - continue; - } - - link.classList.add('status-link'); - - mention = this.props.status.get('mentions').find(item => link.href === item.get('url')); - - if (mention) { - link.addEventListener('click', this.onMentionClick.bind(this, mention), false); - link.setAttribute('title', `@${mention.get('acct')}`); - link.setAttribute('href', `/@${mention.get('acct')}`); - link.setAttribute('data-hover-card-account', mention.get('id')); - } else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) { - link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false); - link.setAttribute('href', `/tags/${link.text.replace(/^#/, '')}`); - link.setAttribute('data-menu-hashtag', this.props.status.getIn(['account', 'id'])); - } else { - link.setAttribute('title', link.href); - link.classList.add('unhandled-link'); - } - } - if (status.get('collapsed', null) === null && onCollapsedToggle) { const { collapsible, onClick } = this.props; @@ -148,22 +127,6 @@ class StatusContent extends PureComponent { this._updateStatusLinks(); } - onMentionClick = (mention, e) => { - if (this.props.history && e.button === 0 && !(e.ctrlKey || e.metaKey)) { - e.preventDefault(); - this.props.history.push(`/@${mention.get('acct')}`); - } - }; - - onHashtagClick = (hashtag, e) => { - hashtag = hashtag.replace(/^#/, ''); - - if (this.props.history && e.button === 0 && !(e.ctrlKey || e.metaKey)) { - e.preventDefault(); - this.props.history.push(`/tags/${hashtag}`); - } - }; - handleMouseDown = (e) => { this.startXY = [e.clientX, e.clientY]; }; @@ -199,6 +162,27 @@ class StatusContent extends PureComponent { this.node = c; }; + handleElement = (element, { key, ...props }, children) => { + if (element instanceof HTMLAnchorElement) { + const mention = this.props.status.get('mentions').find(item => compareUrls(element.href, item.get('url'))); + return ( + + {children} + + ); + } else if (element.classList.contains('quote-inline') && this.props.status.get('quote')) { + return null; + } + return undefined; + } + render () { const { status, intl, statusContent } = this.props; @@ -243,6 +227,7 @@ class StatusContent extends PureComponent { lang={language} htmlString={content} extraEmojis={status.get('emojis')} + onElement={this.handleElement} /> {poll} @@ -260,6 +245,7 @@ class StatusContent extends PureComponent { lang={language} htmlString={content} extraEmojis={status.get('emojis')} + onElement={this.handleElement} /> {poll} diff --git a/app/javascript/mastodon/components/status_list.jsx b/app/javascript/mastodon/components/status_list.jsx index cb2a7464cb094e..049905dc085b12 100644 --- a/app/javascript/mastodon/components/status_list.jsx +++ b/app/javascript/mastodon/components/status_list.jsx @@ -63,9 +63,7 @@ export default class StatusList extends ImmutablePureComponent { switch(statusId) { case TIMELINE_SUGGESTIONS: return ( - + ); case TIMELINE_GAP: return ( diff --git a/app/javascript/mastodon/components/status_quoted.stories.tsx b/app/javascript/mastodon/components/status_quoted.stories.tsx new file mode 100644 index 00000000000000..aa17a5422cc065 --- /dev/null +++ b/app/javascript/mastodon/components/status_quoted.stories.tsx @@ -0,0 +1,35 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; + +import { accountFactoryState, statusFactoryState } from '@/testing/factories'; + +import type { StatusQuoteManagerProps } from './status_quoted'; +import { StatusQuoteManager } from './status_quoted'; + +const meta = { + title: 'Components/Status/StatusQuoteManager', + render(args) { + return ; + }, + args: { + id: '1', + }, + parameters: { + state: { + accounts: { + '1': accountFactoryState({ id: '1', acct: 'hashtaguser' }), + }, + statuses: { + '1': statusFactoryState({ + id: '1', + text: 'Hello world!', + }), + }, + }, + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/app/javascript/mastodon/components/status_quoted.tsx b/app/javascript/mastodon/components/status_quoted.tsx index 8c8216701a66ba..33e791a548bfa6 100644 --- a/app/javascript/mastodon/components/status_quoted.tsx +++ b/app/javascript/mastodon/components/status_quoted.tsx @@ -1,22 +1,22 @@ -import { useCallback, useEffect, useMemo, useRef } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { FormattedMessage } from 'react-intl'; import type { Map as ImmutableMap } from 'immutable'; +import { fetchRelationships } from 'mastodon/actions/accounts'; +import { revealAccount } from 'mastodon/actions/accounts_typed'; +import { fetchStatus } from 'mastodon/actions/statuses'; import { LearnMoreLink } from 'mastodon/components/learn_more_link'; import StatusContainer from 'mastodon/containers/status_container'; import { domain } from 'mastodon/initial_state'; import type { Account } from 'mastodon/models/account'; import type { Status } from 'mastodon/models/status'; +import { makeGetStatusWithExtraInfo } from 'mastodon/selectors'; +import { getAccountHidden } from 'mastodon/selectors/accounts'; import type { RootState } from 'mastodon/store'; import { useAppDispatch, useAppSelector } from 'mastodon/store'; -import { revealAccount } from '../actions/accounts_typed'; -import { fetchStatus } from '../actions/statuses'; -import { makeGetStatusWithExtraInfo } from '../selectors'; -import { getAccountHidden } from '../selectors/accounts'; - import { Button } from './button'; const MAX_QUOTE_POSTS_NESTING_LEVEL = 1; @@ -72,7 +72,63 @@ const LimitedAccountHint: React.FC<{ accountId: string }> = ({ accountId }) => { defaultMessage='This account has been hidden by the moderators of {domain}.' values={{ domain }} /> - + + ); +}; + +const FilteredQuote: React.FC<{ + reveal: VoidFunction; + quotedAccountId: string; + quoteState: string; +}> = ({ reveal, quotedAccountId, quoteState }) => { + const account = useAppSelector((state) => + quotedAccountId ? state.accounts.get(quotedAccountId) : undefined, + ); + + const quoteAuthorName = account?.acct; + const domain = quoteAuthorName?.split('@')[1]; + + let message; + + switch (quoteState) { + case 'blocked_account': + message = ( + + ); + break; + case 'blocked_domain': + message = ( + + ); + break; + case 'muted_account': + message = ( + + ); + } + + return ( + <> + {message} + @@ -154,6 +155,7 @@ export const DomainPill: React.FC<{ @@ -169,6 +171,7 @@ export const DomainPill: React.FC<{ diff --git a/app/javascript/mastodon/features/account/components/follow_request_note.jsx b/app/javascript/mastodon/features/account/components/follow_request_note.jsx index 9c20f1e0626259..27ebb8fd64b77d 100644 --- a/app/javascript/mastodon/features/account/components/follow_request_note.jsx +++ b/app/javascript/mastodon/features/account/components/follow_request_note.jsx @@ -24,12 +24,12 @@ export default class FollowRequestNote extends ImmutablePureComponent {
- - diff --git a/app/javascript/mastodon/features/account_timeline/components/account_header.tsx b/app/javascript/mastodon/features/account_timeline/components/account_header.tsx index f58f1f4a8c4c14..040ca16c7204c3 100644 --- a/app/javascript/mastodon/features/account_timeline/components/account_header.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/account_header.tsx @@ -7,8 +7,9 @@ import { Helmet } from 'react-helmet'; import { NavLink } from 'react-router-dom'; import { AccountBio } from '@/mastodon/components/account_bio'; +import { AccountFields } from '@/mastodon/components/account_fields'; import { DisplayName } from '@/mastodon/components/display_name'; -import CheckIcon from '@/material-icons/400-24px/check.svg?react'; +import { AnimateEmojiProvider } from '@/mastodon/components/emoji/context'; import LockIcon from '@/material-icons/400-24px/lock.svg?react'; import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; import NotificationsIcon from '@/material-icons/400-24px/notifications.svg?react'; @@ -33,7 +34,6 @@ import { initMuteModal } from 'mastodon/actions/mutes'; import { initReport } from 'mastodon/actions/reports'; import { Avatar } from 'mastodon/components/avatar'; import { Badge, AutomatedBadge, GroupBadge } from 'mastodon/components/badge'; -import { Button } from 'mastodon/components/button'; import { CopyIconButton } from 'mastodon/components/copy_icon_button'; import { FollowersCounter, @@ -49,7 +49,6 @@ import { ShortNumber } from 'mastodon/components/short_number'; import { AccountNote } from 'mastodon/features/account/components/account_note'; import { DomainPill } from 'mastodon/features/account/components/domain_pill'; import FollowRequestNoteContainer from 'mastodon/features/account/containers/follow_request_note_container'; -import { useLinks } from 'mastodon/hooks/useLinks'; import { useIdentity } from 'mastodon/identity_context'; import { autoPlayGif, me, domain as localDomain } from 'mastodon/initial_state'; import type { Account } from 'mastodon/models/account'; @@ -186,14 +185,6 @@ const titleFromAccount = (account: Account) => { return `${prefix} (@${acct})`; }; -const dateFormatOptions: Intl.DateTimeFormatOptions = { - month: 'short', - day: 'numeric', - year: 'numeric', - hour: '2-digit', - minute: '2-digit', -}; - export const AccountHeader: React.FC<{ accountId: string; hideTabs?: boolean; @@ -206,7 +197,6 @@ export const AccountHeader: React.FC<{ state.relationships.get(accountId), ); const hidden = useAppSelector((state) => getAccountHidden(state, accountId)); - const handleLinkClick = useLinks(); const handleBlock = useCallback(() => { if (!account) { @@ -383,7 +373,7 @@ export const AccountHeader: React.FC<{ const isRemote = account?.acct !== account?.username; const remoteDomain = isRemote ? account?.acct.split('@')[1] : null; - const menu = useMemo(() => { + const menuItems = useMemo(() => { const arr: MenuItem[] = []; if (!account) { @@ -605,6 +595,15 @@ export const AccountHeader: React.FC<{ handleUnblockDomain, ]); + const menu = accountId !== me && ( + + ); + if (!account) { return null; } @@ -718,21 +717,16 @@ export const AccountHeader: React.FC<{ ); } - if (relationship?.blocking) { + const isMovedAndUnfollowedAccount = account.moved && !relationship?.following; + + if (!isMovedAndUnfollowedAccount) { actionBtn = ( -
- {fields.map((pair, i) => ( -
-
- -
- {pair.verified_at && ( - - - - )}{' '} - -
-
- ))} +
@@ -967,7 +918,7 @@ export const AccountHeader: React.FC<{
)}
-
+ {!(hideTabs || hidden) && (
diff --git a/app/javascript/mastodon/features/alt_text_modal/index.tsx b/app/javascript/mastodon/features/alt_text_modal/index.tsx index f285c35929c1cc..3c54fa47249a03 100644 --- a/app/javascript/mastodon/features/alt_text_modal/index.tsx +++ b/app/javascript/mastodon/features/alt_text_modal/index.tsx @@ -101,16 +101,17 @@ const Preview: React.FC<{ position: FocalPoint; onPositionChange: (arg0: FocalPoint) => void; }> = ({ mediaId, position, onPositionChange }) => { - const draggingRef = useRef(false); const nodeRef = useRef(null); + const [dragging, setDragging] = useState<'started' | 'moving' | null>(null); + const [x, y] = position; const style = useSpring({ to: { left: `${x * 100}%`, top: `${y * 100}%`, }, - immediate: draggingRef.current, + immediate: dragging === 'moving', }); const media = useAppSelector((state) => ( @@ -123,8 +124,6 @@ const Preview: React.FC<{ me ? state.accounts.get(me) : undefined, ); - const [dragging, setDragging] = useState(false); - const setRef = useCallback( (e: HTMLImageElement | HTMLVideoElement | null) => { nodeRef.current = e; @@ -140,20 +139,20 @@ const Preview: React.FC<{ const handleMouseMove = (e: MouseEvent) => { const { x, y } = getPointerPosition(nodeRef.current, e); - draggingRef.current = true; // This will disable the animation for quicker feedback, only do this if the mouse actually moves + + setDragging('moving'); // This will disable the animation for quicker feedback, only do this if the mouse actually moves onPositionChange([x, y]); }; const handleMouseUp = () => { - setDragging(false); - draggingRef.current = false; + setDragging(null); document.removeEventListener('mouseup', handleMouseUp); document.removeEventListener('mousemove', handleMouseMove); }; const { x, y } = getPointerPosition(nodeRef.current, e.nativeEvent); - setDragging(true); + setDragging('started'); onPositionChange([x, y]); document.addEventListener('mouseup', handleMouseUp); @@ -330,7 +329,7 @@ export const AltTextModal = forwardRef>( }); }, [dispatch, setIsSaving, mediaId, onClose, position, description]); - const handleKeyUp = useCallback( + const handleKeyDown = useCallback( (e: React.KeyboardEvent) => { if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { e.preventDefault(); @@ -457,7 +456,7 @@ export const AltTextModal = forwardRef>( id='description' value={isDetecting ? ' ' : description} onChange={handleDescriptionChange} - onKeyUp={handleKeyUp} + onKeyDown={handleKeyDown} lang={lang} placeholder={intl.formatMessage( type === 'audio' @@ -489,6 +488,7 @@ export const AltTextModal = forwardRef>( className='link-button' onClick={handleDetectClick} disabled={type !== 'image' || isDetecting} + type='button' > & { + reportState: AnnualReportAnnouncementProps['state']; + }, + AnyFunction // Remove any functions, as they can't meaningfully be controlled in Storybook. +>; + +const meta = { + title: 'Components/AnnualReport/Announcement', + args: { + reportState: 'eligible', + year: '2025', + }, + argTypes: { + reportState: { + control: { + type: 'select', + }, + options: ['eligible', 'generating', 'available'], + }, + }, + render({ reportState, ...args }: Props) { + return ( + + ); + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; + +export const Loading: Story = { + args: { + reportState: 'generating', + }, +}; + +export const WithData: Story = { + args: { + reportState: 'available', + }, +}; diff --git a/app/javascript/mastodon/features/annual_report/announcement/index.tsx b/app/javascript/mastodon/features/annual_report/announcement/index.tsx new file mode 100644 index 00000000000000..283e95f594054c --- /dev/null +++ b/app/javascript/mastodon/features/annual_report/announcement/index.tsx @@ -0,0 +1,59 @@ +import { FormattedMessage } from 'react-intl'; + +import classNames from 'classnames'; + +import type { ApiAnnualReportState } from '@/mastodon/api/annual_report'; +import { Button } from '@/mastodon/components/button'; + +import styles from './styles.module.scss'; + +export interface AnnualReportAnnouncementProps { + year: string; + state: Exclude; + onRequestBuild: () => void; + onOpen?: () => void; // This is optional when inside the modal, as it won't be shown then. + onDismiss: () => void; +} + +export const AnnualReportAnnouncement: React.FC< + AnnualReportAnnouncementProps +> = ({ year, state, onRequestBuild, onOpen, onDismiss }) => { + return ( +
+ + + {state === 'available' ? ( + + ) : ( + + )} + {state === 'eligible' && ( + + )} +
+ ); +}; diff --git a/app/javascript/mastodon/features/annual_report/announcement/styles.module.scss b/app/javascript/mastodon/features/annual_report/announcement/styles.module.scss new file mode 100644 index 00000000000000..9ec62fa0fdbc0e --- /dev/null +++ b/app/javascript/mastodon/features/annual_report/announcement/styles.module.scss @@ -0,0 +1,42 @@ +.wrapper { + display: flex; + flex-direction: column; + align-items: center; + padding: 40px 60px; + text-align: center; + font-size: 15px; + line-height: 1.5; + color: var(--color-text-primary); + background: var(--color-bg-primary); + background: + radial-gradient(at 40% 87%, #240c9a99 0, transparent 50%), + radial-gradient(at 19% 10%, #6b0c9a99 0, transparent 50%), + radial-gradient(at 90% 27%, #9a0c8299 0, transparent 50%), + radial-gradient(at 16% 95%, #1e948299 0, transparent 50%) + var(--color-bg-primary); + border-bottom: 1px solid var(--color-border-primary); + position: relative; + pointer-events: all; + + h2 { + font-size: 20px; + font-weight: 500; + line-height: 1.2; + margin-bottom: 8px; + } + + p { + margin-bottom: 20px; + } + + .closeButton { + position: absolute; + bottom: 8px; + right: 8px; + margin-inline: 0; + } + + :global(.modal-root__modal) & { + border-radius: 16px; + } +} diff --git a/app/javascript/mastodon/features/annual_report/annual_report.stories.tsx b/app/javascript/mastodon/features/annual_report/annual_report.stories.tsx new file mode 100644 index 00000000000000..3ccaceae6c5fd6 --- /dev/null +++ b/app/javascript/mastodon/features/annual_report/annual_report.stories.tsx @@ -0,0 +1,99 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; + +import { + accountFactoryState, + annualReportFactory, + statusFactoryState, +} from '@/testing/factories'; + +import { AnnualReport } from '.'; + +const SAMPLE_HASHTAG = { + name: 'Mastodon', + count: 14, +}; + +const meta = { + title: 'Components/AnnualReport', + component: AnnualReport, + args: { + context: 'standalone', + }, + parameters: { + state: { + accounts: { + '1': accountFactoryState({ display_name: 'Freddie Fruitbat' }), + }, + statuses: { + '1': statusFactoryState(), + }, + annualReport: annualReportFactory({ + top_hashtag: SAMPLE_HASHTAG, + }), + }, + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Standalone: Story = { + args: { + context: 'standalone', + }, +}; + +export const InModal: Story = { + args: { + context: 'modal', + }, +}; + +export const ArchetypeOracle: Story = { + ...InModal, + parameters: { + state: { + annualReport: annualReportFactory({ + archetype: 'oracle', + top_hashtag: SAMPLE_HASHTAG, + }), + }, + }, +}; + +export const NoHashtag: Story = { + ...InModal, + parameters: { + state: { + annualReport: annualReportFactory({ + archetype: 'booster', + }), + }, + }, +}; + +export const NoNewPosts: Story = { + ...InModal, + parameters: { + state: { + annualReport: annualReportFactory({ + archetype: 'pollster', + top_hashtag: SAMPLE_HASHTAG, + without_posts: true, + }), + }, + }, +}; + +export const NoNewPostsNoHashtag: Story = { + ...InModal, + parameters: { + state: { + annualReport: annualReportFactory({ + archetype: 'replier', + without_posts: true, + }), + }, + }, +}; diff --git a/app/javascript/mastodon/features/annual_report/archetype.tsx b/app/javascript/mastodon/features/annual_report/archetype.tsx index fffbc1803ecbe0..7d1cf0bdd48081 100644 --- a/app/javascript/mastodon/features/annual_report/archetype.tsx +++ b/app/javascript/mastodon/features/annual_report/archetype.tsx @@ -1,69 +1,224 @@ -import { FormattedMessage } from 'react-intl'; +import { useCallback, useRef, useState } from 'react'; + +import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; + +import classNames from 'classnames'; import booster from '@/images/archetypes/booster.png'; import lurker from '@/images/archetypes/lurker.png'; import oracle from '@/images/archetypes/oracle.png'; import pollster from '@/images/archetypes/pollster.png'; import replier from '@/images/archetypes/replier.png'; -import type { Archetype as ArchetypeData } from 'mastodon/models/annual_report'; +import space_elements from '@/images/archetypes/space_elements.png'; +import { Avatar } from '@/mastodon/components/avatar'; +import { Button } from '@/mastodon/components/button'; +import { DisplayName } from '@/mastodon/components/display_name'; +import { me } from '@/mastodon/initial_state'; +import type { Account } from '@/mastodon/models/account'; +import type { + AnnualReport, + Archetype as ArchetypeData, +} from '@/mastodon/models/annual_report'; +import { wrapstodonSettings } from '@/mastodon/settings'; + +import styles from './index.module.scss'; +import { ShareButton } from './share_button'; + +export const archetypeNames = defineMessages({ + booster: { + id: 'annual_report.summary.archetype.booster.name', + defaultMessage: 'The Archer', + }, + replier: { + id: 'annual_report.summary.archetype.replier.name', + defaultMessage: 'The Butterfly', + }, + pollster: { + id: 'annual_report.summary.archetype.pollster.name', + defaultMessage: 'The Wonderer', + }, + lurker: { + id: 'annual_report.summary.archetype.lurker.name', + defaultMessage: 'The Stoic', + }, + oracle: { + id: 'annual_report.summary.archetype.oracle.name', + defaultMessage: 'The Oracle', + }, +}); + +export const archetypeSelfDescriptions = defineMessages({ + booster: { + id: 'annual_report.summary.archetype.booster.desc_self', + defaultMessage: + 'You stayed on the hunt for posts to boost, amplifying other creators with perfect aim.', + }, + replier: { + id: 'annual_report.summary.archetype.replier.desc_self', + defaultMessage: + 'You frequently replied to other people’s posts, pollinating Mastodon with new discussions.', + }, + pollster: { + id: 'annual_report.summary.archetype.pollster.desc_self', + defaultMessage: + 'You created more polls than other post types, cultivating curiosity on Mastodon.', + }, + lurker: { + id: 'annual_report.summary.archetype.lurker.desc_self', + defaultMessage: + 'We know you were out there, somewhere, enjoying Mastodon in your own quiet way.', + }, + oracle: { + id: 'annual_report.summary.archetype.oracle.desc_self', + defaultMessage: + 'You created new posts more than replies, keeping Mastodon fresh and future-facing.', + }, +}); + +export const archetypePublicDescriptions = defineMessages({ + booster: { + id: 'annual_report.summary.archetype.booster.desc_public', + defaultMessage: + '{name} stayed on the hunt for posts to boost, amplifying other creators with perfect aim.', + }, + replier: { + id: 'annual_report.summary.archetype.replier.desc_public', + defaultMessage: + '{name} frequently replied to other people’s posts, pollinating Mastodon with new discussions.', + }, + pollster: { + id: 'annual_report.summary.archetype.pollster.desc_public', + defaultMessage: + '{name} created more polls than other post types, cultivating curiosity on Mastodon.', + }, + lurker: { + id: 'annual_report.summary.archetype.lurker.desc_public', + defaultMessage: + 'We know {name} was out there, somewhere, enjoying Mastodon in their own quiet way.', + }, + oracle: { + id: 'annual_report.summary.archetype.oracle.desc_public', + defaultMessage: + '{name} created new posts more than replies, keeping Mastodon fresh and future-facing.', + }, +}); + +const illustrations = { + booster, + replier, + pollster, + lurker, + oracle, +} as const; export const Archetype: React.FC<{ - data: ArchetypeData; -}> = ({ data }) => { - let illustration, label; + report: AnnualReport; + account?: Account; + context: 'modal' | 'standalone'; +}> = ({ report, account, context }) => { + const intl = useIntl(); + const wrapperRef = useRef(null); + const isSelfView = context === 'modal'; - switch (data) { - case 'booster': - illustration = booster; - label = ( - - ); - break; - case 'replier': - illustration = replier; - label = ( - - ); - break; - case 'pollster': - illustration = pollster; - label = ( - - ); - break; - case 'lurker': - illustration = lurker; - label = ( - - ); - break; - case 'oracle': - illustration = oracle; - label = ( - - ); - break; - } + const [isRevealed, setIsRevealed] = useState( + () => + !isSelfView || + (me ? (wrapstodonSettings.get(me)?.archetypeRevealed ?? false) : true), + ); + const reveal = useCallback(() => { + setIsRevealed(true); + if (me) { + wrapstodonSettings.set(me, { archetypeRevealed: true }); + } + wrapperRef.current?.focus(); + }, []); + + const archetype = report.data.archetype; + const descriptions = isSelfView + ? archetypeSelfDescriptions + : archetypePublicDescriptions; return ( -
-
{label}
- +
+
+ {account && ( + + )} +
+ +
+ +
+
+

+ {isSelfView ? ( + + ) : ( + , + }} + /> + )} +

+

+ {isRevealed ? ( + intl.formatMessage(archetypeNames[archetype]) + ) : ( + + )} +

+

+ {isRevealed ? ( + intl.formatMessage(descriptions[archetype], { + name: , + }) + ) : ( + + )} +

+
+ {!isRevealed && ( + + )} + {isRevealed && isSelfView && }
); }; diff --git a/app/javascript/mastodon/features/annual_report/followers.tsx b/app/javascript/mastodon/features/annual_report/followers.tsx index 196013ae9d6b0c..b0f2216bc5bb65 100644 --- a/app/javascript/mastodon/features/annual_report/followers.tsx +++ b/app/javascript/mastodon/features/annual_report/followers.tsx @@ -1,68 +1,24 @@ import { FormattedMessage, FormattedNumber } from 'react-intl'; -import { Sparklines, SparklinesCurve } from 'react-sparklines'; +import classNames from 'classnames'; -import { ShortNumber } from 'mastodon/components/short_number'; -import type { TimeSeriesMonth } from 'mastodon/models/annual_report'; +import styles from './index.module.scss'; export const Followers: React.FC<{ - data: TimeSeriesMonth[]; - total?: number; -}> = ({ data, total }) => { - const change = data.reduce((sum, item) => sum + item.followers, 0); - - const cumulativeGraph = data.reduce( - (newData, item) => [ - ...newData, - item.followers + (newData[newData.length - 1] ?? 0), - ], - [0], - ); - + count: number; +}> = ({ count }) => { return ( -
- - - - - - - - - - - - - -
-
- {change > -1 ? '+' : '-'} - -
+
+
+ +
-
- - - -
- }} - /> -
-
+
+
); diff --git a/app/javascript/mastodon/features/annual_report/highlighted_post.tsx b/app/javascript/mastodon/features/annual_report/highlighted_post.tsx index 7edbb2e614ff54..d6ca3d3f49d417 100644 --- a/app/javascript/mastodon/features/annual_report/highlighted_post.tsx +++ b/app/javascript/mastodon/features/annual_report/highlighted_post.tsx @@ -1,102 +1,102 @@ /* eslint-disable @typescript-eslint/no-unsafe-return, @typescript-eslint/no-explicit-any, - @typescript-eslint/no-unsafe-assignment */ + @typescript-eslint/no-unsafe-assignment, + @typescript-eslint/no-unsafe-member-access, + @typescript-eslint/no-unsafe-call */ +import type { ComponentPropsWithoutRef } from 'react'; import { useCallback } from 'react'; import { FormattedMessage } from 'react-intl'; -import { DisplayName } from '@/mastodon/components/display_name'; -import { toggleStatusSpoilers } from 'mastodon/actions/statuses'; -import { DetailedStatus } from 'mastodon/features/status/components/detailed_status'; -import { me } from 'mastodon/initial_state'; +import classNames from 'classnames'; + +import { InterceptStatusClicks } from 'mastodon/components/status/intercept_status_clicks'; +import { StatusQuoteManager } from 'mastodon/components/status_quoted'; import type { TopStatuses } from 'mastodon/models/annual_report'; -import { makeGetStatus, makeGetPictureInPicture } from 'mastodon/selectors'; -import { useAppSelector, useAppDispatch } from 'mastodon/store'; +import { makeGetStatus } from 'mastodon/selectors'; +import { useAppSelector } from 'mastodon/store'; + +import styles from './index.module.scss'; const getStatus = makeGetStatus() as unknown as (arg0: any, arg1: any) => any; -const getPictureInPicture = makeGetPictureInPicture() as unknown as ( - arg0: any, - arg1: any, -) => any; export const HighlightedPost: React.FC<{ data: TopStatuses; -}> = ({ data }) => { - let statusId, label; + context: 'modal' | 'standalone'; +}> = ({ data, context }) => { + const { by_reblogs, by_favourites, by_replies } = data; + + const statusId = by_reblogs || by_favourites || by_replies; + + const status = useAppSelector((state) => + statusId ? getStatus(state, { id: statusId }) : undefined, + ); - if (data.by_reblogs) { - statusId = data.by_reblogs; + const handleClick = useCallback< + ComponentPropsWithoutRef['onPreventedClick'] + >( + (clickedArea) => { + const link: string = + clickedArea === 'account' + ? status.getIn(['account', 'url']) + : status.get('url'); + + if (context === 'standalone') { + window.location.href = link; + } else { + window.open(link, '_blank'); + } + }, + [status, context], + ); + + if (!status) { + return
; + } + + let label; + if (by_reblogs) { label = ( ); - } else if (data.by_favourites) { - statusId = data.by_favourites; + } else if (by_favourites) { label = ( ); } else { - statusId = data.by_replies; label = ( ); } - const dispatch = useAppDispatch(); - const domain = useAppSelector((state) => state.meta.get('domain')); - const status = useAppSelector((state) => - statusId ? getStatus(state, { id: statusId }) : undefined, - ); - const pictureInPicture = useAppSelector((state) => - statusId ? getPictureInPicture(state, { id: statusId }) : undefined, - ); - const account = useAppSelector((state) => - me ? state.accounts.get(me) : undefined, - ); - - const handleToggleHidden = useCallback(() => { - dispatch(toggleStatusSpoilers(statusId)); - }, [dispatch, statusId]); - - if (!status) { - return ( -
- ); - } - - const displayName = ( - - - , - }} - /> - - {label} - - ); - return ( -
- +
+
+

+ +

+ {context === 'modal' &&

{label}

} +
+ + + +
); }; diff --git a/app/javascript/mastodon/features/annual_report/index.module.scss b/app/javascript/mastodon/features/annual_report/index.module.scss new file mode 100644 index 00000000000000..024518d72f75fe --- /dev/null +++ b/app/javascript/mastodon/features/annual_report/index.module.scss @@ -0,0 +1,351 @@ +$mobile-breakpoint: 540px; + +@font-face { + font-family: silkscreen-wrapstodon; + src: url('@/fonts/silkscreen-wrapstodon/silkscreen-regular.woff2') + format('woff2'); + font-weight: normal; + font-display: swap; + font-style: normal; +} + +.modalWrapper { + box-sizing: border-box; + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + padding: 40px; + overflow-y: auto; + scrollbar-color: var(--color-text-secondary) var(--color-bg-secondary); + + @media (width < $mobile-breakpoint) { + padding: 0; + } + + .loading-indicator .circular-progress { + color: var(--lime); + } +} + +.closeButton { + --default-icon-color: var(--color-bg-primary); + --default-bg-color: var(--color-text-primary); + --hover-icon-color: var(--color-bg-primary); + --hover-bg-color: var(--color-text-primary); + --corner-distance: 18px; + + position: absolute; + top: var(--corner-distance); + right: var(--corner-distance); + padding: 8px; + border-radius: 100%; + + @media (width < $mobile-breakpoint) { + --corner-distance: 16px; + + padding: 4px; + } +} + +.wrapper { + --gradient-strength: 0.4; + + box-sizing: border-box; + position: relative; + max-width: 600px; + padding: 24px; + padding-top: 40px; + contain: layout; + flex: 0 0 auto; + pointer-events: all; + color: var(--color-text-primary); + background: var(--color-bg-primary); + background: + radial-gradient( + at 10% 27%, + rgba(83, 12, 154, var(--gradient-strength)) 0, + transparent 50% + ), + radial-gradient( + at 91% 10%, + rgba(30, 24, 223, var(--gradient-strength)) 0, + transparent 25% + ), + radial-gradient( + at 10% 91%, + rgba(22, 218, 228, var(--gradient-strength)) 0, + transparent 40% + ), + radial-gradient( + at 75% 87%, + rgba(37, 31, 217, var(--gradient-strength)) 0, + transparent 20% + ), + radial-gradient( + at 84% 60%, + rgba(95, 30, 148, var(--gradient-strength)) 0, + transparent 40% + ) + var(--color-bg-primary); + border-radius: 40px; + + @media (width < $mobile-breakpoint) { + padding-inline: 12px; + padding-bottom: 12px; + border-radius: 0; + } +} + +.header { + margin-bottom: 18px; + text-align: center; + + h1 { + font-family: silkscreen-wrapstodon, monospace; + font-size: 28px; + line-height: 1; + margin-bottom: 4px; + padding-inline: 40px; // Prevent overlap with close button + + @media (width < $mobile-breakpoint) { + font-size: 22px; + margin-bottom: 4px; + } + } + + p { + font-size: 14px; + line-height: 1.5; + } +} + +.stack { + --grid-spacing: 12px; + + display: grid; + gap: var(--grid-spacing); +} + +.box { + position: relative; + padding: 24px; + border-radius: 16px; + background: rgb(from var(--color-bg-primary) r g b / 60%); + box-shadow: inset 0 0 0 1px rgb(from var(--color-text-primary) r g b / 40%); + + &::before, + &::after { + content: ''; + position: absolute; + inset-inline: 0; + display: block; + height: 1px; + background-image: linear-gradient( + to right, + transparent, + white, + transparent + ); + } + + &::before { + top: 0; + } + + &::after { + bottom: 0; + } +} + +.content { + display: flex; + flex-direction: column; + justify-content: center; + gap: 8px; + font-size: 14px; + text-align: center; + text-wrap: balance; + + &.comfortable { + gap: 12px; + } +} + +.title { + text-transform: uppercase; + color: var(--color-text-brand-soft); + font-weight: 500; + + &:last-child { + margin-bottom: -3px; + } +} + +.statLarge { + font-size: 24px; + font-weight: 500; + overflow-wrap: break-word; +} + +.statExtraLarge { + font-size: 32px; + font-weight: 500; + line-height: 1; + overflow-wrap: break-word; + + @media (width < $mobile-breakpoint) { + font-size: 24px; + } +} + +.mostBoostedPost { + padding: 0; + padding-top: 24px; + overflow: hidden; +} + +.statsGrid { + display: grid; + gap: var(--grid-spacing); + grid-template-columns: 1fr 2fr; + grid-template-areas: + 'followers hashtag' + 'new-posts hashtag'; + + @media (width < $mobile-breakpoint) { + grid-template-columns: 1fr 1fr; + grid-template-areas: + 'followers new-posts' + 'hashtag hashtag'; + } + + &:empty { + display: none; + } + + &.onlyHashtag { + grid-template-columns: 1fr; + grid-template-areas: 'hashtag'; + } + + &.noHashtag { + grid-template-columns: 1fr 1fr; + grid-template-areas: 'followers new-posts'; + } + + &.singleNumber { + grid-template-columns: 1fr 2fr; + grid-template-areas: 'number hashtag'; + + @media (width < $mobile-breakpoint) { + grid-template-areas: + 'number number' + 'hashtag hashtag'; + } + } + + &.singleNumber.noHashtag { + grid-template-columns: 1fr; + grid-template-areas: 'number'; + } +} + +.followers { + grid-area: followers; + + .singleNumber & { + grid-area: number; + } +} + +.newPosts { + grid-area: new-posts; + + .singleNumber & { + grid-area: number; + } +} + +.mostUsedHashtag { + grid-area: hashtag; + padding-block: 24px; +} + +.archetype { + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; + + p { + max-width: 460px; + } +} + +.archetypeArtboard { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + align-self: center; + width: 180px; + padding-top: 40px; +} + +.archetypeAvatar { + position: absolute; + top: 7px; + left: 4px; + border-radius: 100%; + overflow: hidden; +} + +.archetypeIllustrationWrapper { + position: relative; + width: 92px; + aspect-ratio: 1; + overflow: hidden; + border-radius: 100%; + + &::after { + content: ''; + display: block; + position: absolute; + inset: 0; + border-radius: inherit; + box-shadow: inset -10px -4px 15px #00000080; + } +} + +.archetypeIllustration { + width: 100%; +} + +.blurredImage { + filter: blur(10px); +} + +.archetypePlanetRing { + position: absolute; + top: 0; + left: 0; + mix-blend-mode: screen; +} + +.shareButtonWrapper { + display: flex; + flex-direction: column; + gap: 10px; +} + +.secondaryShareButton { + // Extra selector is needed to override color + &:global(.button) { + color: var(--color-text-primary); + } +} + +.navItemBadge { + background: var(--color-bg-brand-soft); +} diff --git a/app/javascript/mastodon/features/annual_report/index.tsx b/app/javascript/mastodon/features/annual_report/index.tsx index 91f03330c01472..ef6f73fff28605 100644 --- a/app/javascript/mastodon/features/annual_report/index.tsx +++ b/app/javascript/mastodon/features/annual_report/index.tsx @@ -1,98 +1,124 @@ -import { useState, useEffect } from 'react'; +import { useCallback, useEffect, useState } from 'react'; +import type { FC } from 'react'; -import { FormattedMessage } from 'react-intl'; +import { useIntl } from 'react-intl'; +import { useLocation } from 'react-router'; + +import classNames from 'classnames/bind'; + +import { closeModal } from '@/mastodon/actions/modal'; +import { IconButton } from '@/mastodon/components/icon_button'; +import { LoadingIndicator } from '@/mastodon/components/loading_indicator'; +import { getReport } from '@/mastodon/reducers/slices/annual_report'; import { - importFetchedStatuses, - importFetchedAccounts, -} from 'mastodon/actions/importer'; -import { apiRequestGet, apiRequestPost } from 'mastodon/api'; -import { LoadingIndicator } from 'mastodon/components/loading_indicator'; -import { me } from 'mastodon/initial_state'; -import type { Account } from 'mastodon/models/account'; -import type { AnnualReport as AnnualReportData } from 'mastodon/models/annual_report'; -import type { Status } from 'mastodon/models/status'; -import { useAppSelector, useAppDispatch } from 'mastodon/store'; + createAppSelector, + useAppDispatch, + useAppSelector, +} from '@/mastodon/store'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { Archetype } from './archetype'; import { Followers } from './followers'; import { HighlightedPost } from './highlighted_post'; +import styles from './index.module.scss'; import { MostUsedHashtag } from './most_used_hashtag'; import { NewPosts } from './new_posts'; -import { Percentile } from './percentile'; - -interface AnnualReportResponse { - annual_reports: AnnualReportData[]; - accounts: Account[]; - statuses: Status[]; -} - -export const AnnualReport: React.FC<{ - year: string; -}> = ({ year }) => { - const [response, setResponse] = useState(null); - const [loading, setLoading] = useState(false); - const currentAccount = useAppSelector((state) => - me ? state.accounts.get(me) : undefined, - ); + +const moduleClassNames = classNames.bind(styles); + +export const accountSelector = createAppSelector( + [(state) => state.accounts, (state) => state.annualReport.report], + (accounts, report) => { + if (report?.schema_version === 2) { + return accounts.get(report.account_id); + } + return undefined; + }, +); + +export const AnnualReport: FC<{ context?: 'modal' | 'standalone' }> = ({ + context = 'standalone', +}) => { + const intl = useIntl(); const dispatch = useAppDispatch(); + const report = useAppSelector((state) => state.annualReport.report); + const account = useAppSelector(accountSelector); + const needsReport = !report; // Make into boolean to avoid object comparison in deps. useEffect(() => { - setLoading(true); + if (needsReport) { + void dispatch(getReport()); + } + }, [dispatch, needsReport]); - apiRequestGet(`v1/annual_reports/${year}`) - .then((data) => { - dispatch(importFetchedStatuses(data.statuses)); - dispatch(importFetchedAccounts(data.accounts)); + const close = useCallback(() => { + dispatch(closeModal({ modalType: 'ANNUAL_REPORT', ignoreFocus: false })); + }, [dispatch]); - setResponse(data); - setLoading(false); - - return apiRequestPost(`v1/annual_reports/${year}/read`); - }) - .catch(() => { - setLoading(false); - }); - }, [dispatch, year, setResponse, setLoading]); + // Close modal when navigating away from within + const { pathname } = useLocation(); + const [initialPathname] = useState(pathname); + useEffect(() => { + if (pathname !== initialPathname) { + close(); + } + }, [pathname, initialPathname, close]); - if (loading) { + if (needsReport) { return ; } - const report = response?.annual_reports[0]; + const newPostCount = report.data.time_series.reduce( + (sum, item) => sum + item.statuses, + 0, + ); + + const newFollowerCount = + context === 'modal' && + report.data.time_series.reduce((sum, item) => sum + item.followers, 0); - if (!report) { - return null; - } + const topHashtag = report.data.top_hashtags[0]; return ( -
-
-

- -

-

- +

+

Wrapstodon {report.year}

+ {account &&

@{account.acct}

} + {context === 'modal' && ( + -

+ )}
-
- - - - - - +
+ +
+ {!!newFollowerCount && } + {!!newPostCount && } + {topHashtag && ( + + )} +
+
); diff --git a/app/javascript/mastodon/features/annual_report/modal.tsx b/app/javascript/mastodon/features/annual_report/modal.tsx new file mode 100644 index 00000000000000..01d7c4bbdbfc79 --- /dev/null +++ b/app/javascript/mastodon/features/annual_report/modal.tsx @@ -0,0 +1,85 @@ +import type { MouseEventHandler } from 'react'; +import { useCallback, useEffect } from 'react'; + +import classNames from 'classnames'; + +import { closeModal } from '@/mastodon/actions/modal'; +import { generateReport } from '@/mastodon/reducers/slices/annual_report'; +import { useAppDispatch, useAppSelector } from '@/mastodon/store'; + +import { AnnualReport } from '.'; +import { AnnualReportAnnouncement } from './announcement'; +import styles from './index.module.scss'; + +const AnnualReportModal: React.FC<{ + onChangeBackgroundColor: (color: string) => void; +}> = ({ onChangeBackgroundColor }) => { + useEffect(() => { + onChangeBackgroundColor('var(--color-bg-media-base)'); + }, [onChangeBackgroundColor]); + + const { state, year } = useAppSelector((state) => state.annualReport); + + const showAnnouncement = year && state && state !== 'available'; + + const dispatch = useAppDispatch(); + + const handleBuildRequest = useCallback(() => { + void dispatch(generateReport()); + }, [dispatch]); + + const handleClose = useCallback(() => { + dispatch(closeModal({ modalType: 'ANNUAL_REPORT', ignoreFocus: false })); + }, [dispatch]); + + const handleCloseModal: MouseEventHandler = useCallback( + (e) => { + if (e.target === e.currentTarget) { + handleClose(); + } + }, + [handleClose], + ); + + // Auto-close if ineligible + useEffect(() => { + if (state === 'ineligible') { + handleClose(); + } + }, [handleClose, state]); + + if (state === 'ineligible') { + // Not sure how you got here, but don't show anything. + return null; + } + + return ( + // It's fine not to provide a keyboard handler here since there is a global + // [Esc] key listener that will close open modals. + // This onClick handler is needed since the modalWrapper styles overlap the + // default modal backdrop, preventing clicks to pass through. + // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions +
+ {!showAnnouncement ? ( + + ) : ( + + )} +
+ ); +}; + +// eslint-disable-next-line import/no-default-export +export default AnnualReportModal; diff --git a/app/javascript/mastodon/features/annual_report/most_used_hashtag.tsx b/app/javascript/mastodon/features/annual_report/most_used_hashtag.tsx index 4b59b897375aff..293146cc2a7780 100644 --- a/app/javascript/mastodon/features/annual_report/most_used_hashtag.tsx +++ b/app/javascript/mastodon/features/annual_report/most_used_hashtag.tsx @@ -1,30 +1,50 @@ import { FormattedMessage } from 'react-intl'; +import classNames from 'classnames'; + +import { DisplayName } from '@/mastodon/components/display_name'; +import type { Account } from '@/mastodon/models/account'; import type { NameAndCount } from 'mastodon/models/annual_report'; -export const MostUsedHashtag: React.FC<{ - data: NameAndCount[]; -}> = ({ data }) => { - const hashtag = data[0]; +import styles from './index.module.scss'; +export const MostUsedHashtag: React.FC<{ + hashtag: NameAndCount; + context: 'modal' | 'standalone'; + account?: Account; +}> = ({ hashtag, context, account }) => { return ( -
-
- {hashtag ? ( - <>#{hashtag.name} - ) : ( - - )} -
-
+
+
+ +
#{hashtag.name}
+ +

+ {context === 'modal' && ( + + )} + {context !== 'modal' && account && ( + , + }} + /> + )} +

); }; diff --git a/app/javascript/mastodon/features/annual_report/nav_item.tsx b/app/javascript/mastodon/features/annual_report/nav_item.tsx new file mode 100644 index 00000000000000..435e2b1f70c848 --- /dev/null +++ b/app/javascript/mastodon/features/annual_report/nav_item.tsx @@ -0,0 +1,53 @@ +import { useCallback } from 'react'; +import type { FC } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import classNames from 'classnames'; + +import IconPlanet from '@/images/icons/icon_planet.svg?react'; +import { openModal } from '@/mastodon/actions/modal'; +import { Icon } from '@/mastodon/components/icon'; +import { + createAppSelector, + useAppDispatch, + useAppSelector, +} from '@/mastodon/store'; + +import classes from './index.module.scss'; + +const selectReportModalOpen = createAppSelector( + [(state) => state.modal.getIn(['stack', 0, 'modalType'])], + (modalType) => modalType === 'ANNUAL_REPORT', +); + +export const AnnualReportNavItem: FC = () => { + const { state, year } = useAppSelector((state) => state.annualReport); + const active = useAppSelector(selectReportModalOpen); + + const dispatch = useAppDispatch(); + const handleClick = useCallback(() => { + dispatch(openModal({ modalType: 'ANNUAL_REPORT', modalProps: {} })); + }, [dispatch]); + + if (!year || !state || state === 'ineligible') { + return null; + } + + return ( + + ); +}; diff --git a/app/javascript/mastodon/features/annual_report/new_posts.tsx b/app/javascript/mastodon/features/annual_report/new_posts.tsx index 9ead0176b2ad7d..9a265f0b9d47a2 100644 --- a/app/javascript/mastodon/features/annual_report/new_posts.tsx +++ b/app/javascript/mastodon/features/annual_report/new_posts.tsx @@ -1,51 +1,23 @@ import { FormattedNumber, FormattedMessage } from 'react-intl'; -import ChatBubbleIcon from '@/material-icons/400-24px/chat_bubble.svg?react'; -import type { TimeSeriesMonth } from 'mastodon/models/annual_report'; +import classNames from 'classnames'; -export const NewPosts: React.FC<{ - data: TimeSeriesMonth[]; -}> = ({ data }) => { - const posts = data.reduce((sum, item) => sum + item.statuses, 0); +import styles from './index.module.scss'; +export const NewPosts: React.FC<{ + count: number; +}> = ({ count }) => { return ( -
- - - - - - - - - - - -
- +
+
+
-
+ +
diff --git a/app/javascript/mastodon/features/annual_report/share_button.tsx b/app/javascript/mastodon/features/annual_report/share_button.tsx new file mode 100644 index 00000000000000..16dd834f4a17e0 --- /dev/null +++ b/app/javascript/mastodon/features/annual_report/share_button.tsx @@ -0,0 +1,96 @@ +import { useCallback } from 'react'; +import type { FC } from 'react'; + +import { defineMessages, useIntl } from 'react-intl'; + +import { showAlert } from '@/mastodon/actions/alerts'; +import { resetCompose, focusCompose } from '@/mastodon/actions/compose'; +import { closeModal } from '@/mastodon/actions/modal'; +import { Button } from '@/mastodon/components/button'; +import type { AnnualReport as AnnualReportData } from '@/mastodon/models/annual_report'; +import { useAppDispatch } from '@/mastodon/store'; + +import { archetypeNames } from './archetype'; +import styles from './index.module.scss'; + +const messages = defineMessages({ + share_message: { + id: 'annual_report.summary.share_message', + defaultMessage: 'I got the {archetype} archetype!', + }, + share_on_mastodon: { + id: 'annual_report.summary.share_on_mastodon', + defaultMessage: 'Share on Mastodon', + }, + share_elsewhere: { + id: 'annual_report.summary.share_elsewhere', + defaultMessage: 'Share elsewhere', + }, + copy_link: { + id: 'annual_report.summary.copy_link', + defaultMessage: 'Copy link', + }, + copied: { + id: 'copy_icon_button.copied', + defaultMessage: 'Copied to clipboard', + }, +}); + +export const ShareButton: FC<{ report: AnnualReportData }> = ({ report }) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + + const handleShareClick = useCallback(() => { + // Generate the share message. + const archetypeName = intl.formatMessage( + archetypeNames[report.data.archetype], + ); + const shareLines = [ + intl.formatMessage(messages.share_message, { + archetype: archetypeName, + }), + ]; + // Share URL is only available for schema version 2. + if (report.schema_version === 2 && report.share_url) { + shareLines.push(report.share_url); + } + shareLines.push(`#Wrapstodon${report.year}`); + + // Reset the composer and focus it with the share message, then close the modal. + dispatch(resetCompose()); + dispatch(focusCompose(shareLines.join('\n\n'))); + dispatch(closeModal({ modalType: 'ANNUAL_REPORT', ignoreFocus: false })); + }, [report, intl, dispatch]); + + const supportsNativeShare = 'share' in navigator; + + const handleSecondaryShare = useCallback(() => { + if (report.schema_version === 2 && report.share_url) { + if (supportsNativeShare) { + void navigator.share({ + url: report.share_url, + }); + } else { + void navigator.clipboard.writeText(report.share_url); + dispatch(showAlert({ message: messages.copied })); + } + } + }, [report, supportsNativeShare, dispatch]); + + return ( +
+
+ ); +}; diff --git a/app/javascript/mastodon/features/annual_report/shared_page.module.scss b/app/javascript/mastodon/features/annual_report/shared_page.module.scss new file mode 100644 index 00000000000000..ea3ea471b90153 --- /dev/null +++ b/app/javascript/mastodon/features/annual_report/shared_page.module.scss @@ -0,0 +1,58 @@ +$mobile-breakpoint: 540px; + +.wrapper { + box-sizing: border-box; + max-width: 600px; + margin-inline: auto; + padding: 40px 10px; + + @media (width < $mobile-breakpoint) { + padding-top: 0; + padding-inline: 0; + } +} + +.footer { + display: flex; + flex-direction: column; + align-items: center; + gap: 1.8rem; + margin-top: 2rem; + font-size: 16px; + line-height: 1.4; + text-align: center; + color: var(--color-text-secondary); + + strong { + font-weight: 600; + } + + a:any-link { + color: inherit; + text-decoration: underline; + text-underline-offset: 0.2em; + } + + a:hover { + color: var(--color-text-primary); + } +} + +.logo { + width: 2rem; + opacity: 0.6; +} + +.footerSection { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5rem; +} + +.linkList { + list-style: none; + display: flex; + flex-wrap: wrap; + gap: 12px; +} diff --git a/app/javascript/mastodon/features/annual_report/shared_page.tsx b/app/javascript/mastodon/features/annual_report/shared_page.tsx new file mode 100644 index 00000000000000..3defe7194a3ec2 --- /dev/null +++ b/app/javascript/mastodon/features/annual_report/shared_page.tsx @@ -0,0 +1,68 @@ +import type { FC } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import { DisplayName } from '@/mastodon/components/display_name'; +import { IconLogo } from '@/mastodon/components/logo'; +import { useAppSelector } from '@/mastodon/store'; + +import { AnnualReport, accountSelector } from './index'; +import classes from './shared_page.module.scss'; + +export const WrapstodonSharedPage: FC = () => { + const account = useAppSelector(accountSelector); + const domain = useAppSelector((state) => state.meta.get('domain') as string); + return ( +
+ +
+
+ + + +
+ +
+ , + domain: {domain}, + }} + tagName='p' + /> + + + +
+
+
+ ); +}; diff --git a/app/javascript/mastodon/features/annual_report/timeline.tsx b/app/javascript/mastodon/features/annual_report/timeline.tsx new file mode 100644 index 00000000000000..28a4b1d27324ae --- /dev/null +++ b/app/javascript/mastodon/features/annual_report/timeline.tsx @@ -0,0 +1,41 @@ +import { useCallback } from 'react'; +import type { FC } from 'react'; + +import { openModal } from '@/mastodon/actions/modal'; +import { useDismissible } from '@/mastodon/hooks/useDismissible'; +import { generateReport } from '@/mastodon/reducers/slices/annual_report'; +import { useAppDispatch, useAppSelector } from '@/mastodon/store'; + +import { AnnualReportAnnouncement } from './announcement'; + +export const AnnualReportTimeline: FC = () => { + const { state, year } = useAppSelector((state) => state.annualReport); + + const dispatch = useAppDispatch(); + const handleBuildRequest = useCallback(() => { + void dispatch(generateReport()); + }, [dispatch]); + + const { wasDismissed, dismiss } = useDismissible( + `annual_report_announcement_${year}`, + ); + + const handleOpen = useCallback(() => { + dispatch(openModal({ modalType: 'ANNUAL_REPORT', modalProps: {} })); + dismiss(); + }, [dismiss, dispatch]); + + if (!year || wasDismissed || !state || state === 'ineligible') { + return null; + } + + return ( + + ); +}; diff --git a/app/javascript/mastodon/features/audio/index.tsx b/app/javascript/mastodon/features/audio/index.tsx index a6a131c0d4d247..fa0d976377cc30 100644 --- a/app/javascript/mastodon/features/audio/index.tsx +++ b/app/javascript/mastodon/features/audio/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useCallback, useState, useId } from 'react'; +import { useEffect, useRef, useCallback, useState } from 'react'; import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; @@ -22,6 +22,8 @@ import { useAudioVisualizer } from 'mastodon/hooks/useAudioVisualizer'; import { displayMedia, useBlurhash } from 'mastodon/initial_state'; import { playerSettings } from 'mastodon/settings'; +import { AudioVisualizer } from './visualizer'; + const messages = defineMessages({ play: { id: 'video.play', defaultMessage: 'Play' }, pause: { id: 'video.pause', defaultMessage: 'Pause' }, @@ -39,8 +41,8 @@ const persistVolume = (volume: number, muted: boolean) => { }; const restoreVolume = (audio: HTMLAudioElement) => { - const volume = (playerSettings.get('volume') as number | undefined) ?? 0.5; - const muted = (playerSettings.get('muted') as boolean | undefined) ?? false; + const volume = playerSettings.get('volume') ?? 0.5; + const muted = playerSettings.get('muted') ?? false; audio.volume = volume; audio.muted = muted; @@ -116,7 +118,6 @@ export const Audio: React.FC<{ const seekRef = useRef(null); const volumeRef = useRef(null); const hoverTimeoutRef = useRef | null>(); - const accessibilityId = useId(); const { audioContextRef, sourceRef, gainNodeRef, playAudio, pauseAudio } = useAudioContext({ audioElementRef: audioRef }); @@ -538,19 +539,6 @@ export const Audio: React.FC<{ [togglePlay, toggleMute], ); - const springForBand0 = useSpring({ - to: { r: 50 + (frequencyBands[0] ?? 0) * 10 }, - config: config.wobbly, - }); - const springForBand1 = useSpring({ - to: { r: 50 + (frequencyBands[1] ?? 0) * 10 }, - config: config.wobbly, - }); - const springForBand2 = useSpring({ - to: { r: 50 + (frequencyBands[2] ?? 0) * 10 }, - config: config.wobbly, - }); - const progress = Math.min((currentTime / loadedDuration) * 100, 100); const effectivelyMuted = muted || volume === 0; @@ -641,81 +629,7 @@ export const Audio: React.FC<{
- - - - - - - - - - - - - - - - - - - - - - +
)} -
+ ); }; diff --git a/app/javascript/mastodon/features/compose/components/privacy_dropdown.jsx b/app/javascript/mastodon/features/compose/components/privacy_dropdown.jsx deleted file mode 100644 index 1c9434502c01a6..00000000000000 --- a/app/javascript/mastodon/features/compose/components/privacy_dropdown.jsx +++ /dev/null @@ -1,158 +0,0 @@ -import PropTypes from 'prop-types'; -import { PureComponent } from 'react'; - -import { injectIntl, defineMessages } from 'react-intl'; - -import classNames from 'classnames'; - -import Overlay from 'react-overlays/Overlay'; - -import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react'; -import LockIcon from '@/material-icons/400-24px/lock.svg?react'; -import PublicIcon from '@/material-icons/400-24px/public.svg?react'; -import QuietTimeIcon from '@/material-icons/400-24px/quiet_time.svg?react'; -import { DropdownSelector } from 'mastodon/components/dropdown_selector'; -import { Icon } from 'mastodon/components/icon'; - -export const messages = defineMessages({ - public_short: { id: 'privacy.public.short', defaultMessage: 'Public' }, - public_long: { id: 'privacy.public.long', defaultMessage: 'Anyone on and off Mastodon' }, - unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Quiet public' }, - unlisted_long: { id: 'privacy.unlisted.long', defaultMessage: 'Hidden from Mastodon search results, trending, and public timelines' }, - private_short: { id: 'privacy.private.short', defaultMessage: 'Followers' }, - private_long: { id: 'privacy.private.long', defaultMessage: 'Only your followers' }, - direct_short: { id: 'privacy.direct.short', defaultMessage: 'Specific people' }, - direct_long: { id: 'privacy.direct.long', defaultMessage: 'Everyone mentioned in the post' }, - change_privacy: { id: 'privacy.change', defaultMessage: 'Change post privacy' }, - unlisted_extra: { id: 'privacy.unlisted.additional', defaultMessage: 'This behaves exactly like public, except the post will not appear in live feeds or hashtags, explore, or Mastodon search, even if you are opted-in account-wide.' }, -}); - -class PrivacyDropdown extends PureComponent { - - static propTypes = { - value: PropTypes.string.isRequired, - onChange: PropTypes.func.isRequired, - noDirect: PropTypes.bool, - container: PropTypes.func, - disabled: PropTypes.bool, - intl: PropTypes.object.isRequired, - }; - - state = { - open: false, - placement: 'bottom', - }; - - handleToggle = () => { - if (this.state.open && this.activeElement) { - this.activeElement.focus({ preventScroll: true }); - } - - this.setState({ open: !this.state.open }); - }; - - handleKeyDown = e => { - switch(e.key) { - case 'Escape': - this.handleClose(); - break; - } - }; - - handleMouseDown = () => { - if (!this.state.open) { - this.activeElement = document.activeElement; - } - }; - - handleButtonKeyDown = (e) => { - switch(e.key) { - case ' ': - case 'Enter': - this.handleMouseDown(); - break; - } - }; - - handleClose = () => { - if (this.state.open && this.activeElement) { - this.activeElement.focus({ preventScroll: true }); - } - this.setState({ open: false }); - }; - - handleChange = value => { - this.props.onChange(value); - }; - - UNSAFE_componentWillMount () { - const { intl: { formatMessage } } = this.props; - - this.options = [ - { icon: 'globe', iconComponent: PublicIcon, value: 'public', text: formatMessage(messages.public_short), meta: formatMessage(messages.public_long) }, - { icon: 'unlock', iconComponent: QuietTimeIcon, value: 'unlisted', text: formatMessage(messages.unlisted_short), meta: formatMessage(messages.unlisted_long), extra: formatMessage(messages.unlisted_extra) }, - { icon: 'lock', iconComponent: LockIcon, value: 'private', text: formatMessage(messages.private_short), meta: formatMessage(messages.private_long) }, - ]; - - if (!this.props.noDirect) { - this.options.push( - { icon: 'at', iconComponent: AlternateEmailIcon, value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) }, - ); - } - } - - setTargetRef = c => { - this.target = c; - }; - - findTarget = () => { - return this.target; - }; - - handleOverlayEnter = (state) => { - this.setState({ placement: state.placement }); - }; - - render () { - const { value, container, disabled, intl } = this.props; - const { open, placement } = this.state; - - const valueOption = this.options.find(item => item.value === value); - - return ( -
- - - - {({ props, placement }) => ( -
-
- -
-
- )} -
-
- ); - } - -} - -export default injectIntl(PrivacyDropdown); diff --git a/app/javascript/mastodon/features/compose/components/privacy_dropdown.tsx b/app/javascript/mastodon/features/compose/components/privacy_dropdown.tsx new file mode 100644 index 00000000000000..5a463d86e79a53 --- /dev/null +++ b/app/javascript/mastodon/features/compose/components/privacy_dropdown.tsx @@ -0,0 +1,199 @@ +import { useCallback, useRef, useState } from 'react'; + +import { defineMessages, useIntl } from 'react-intl'; + +import classNames from 'classnames'; + +import type { OverlayProps } from 'react-overlays/Overlay'; +import Overlay from 'react-overlays/Overlay'; + +import type { StatusVisibility } from '@/mastodon/api_types/statuses'; +import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react'; +import LockIcon from '@/material-icons/400-24px/lock.svg?react'; +import PublicIcon from '@/material-icons/400-24px/public.svg?react'; +import QuietTimeIcon from '@/material-icons/400-24px/quiet_time.svg?react'; +import { DropdownSelector } from 'mastodon/components/dropdown_selector'; +import { Icon } from 'mastodon/components/icon'; + +export const messages = defineMessages({ + public_short: { id: 'privacy.public.short', defaultMessage: 'Public' }, + public_long: { + id: 'privacy.public.long', + defaultMessage: 'Anyone on and off Mastodon', + }, + unlisted_short: { + id: 'privacy.unlisted.short', + defaultMessage: 'Quiet public', + }, + unlisted_long: { + id: 'privacy.unlisted.long', + defaultMessage: + 'Hidden from Mastodon search results, trending, and public timelines', + }, + private_short: { id: 'privacy.private.short', defaultMessage: 'Followers' }, + private_long: { + id: 'privacy.private.long', + defaultMessage: 'Only your followers', + }, + direct_short: { + id: 'privacy.direct.short', + defaultMessage: 'Specific people', + }, + direct_long: { + id: 'privacy.direct.long', + defaultMessage: 'Everyone mentioned in the post', + }, + change_privacy: { + id: 'privacy.change', + defaultMessage: 'Change post privacy', + }, + unlisted_extra: { + id: 'privacy.unlisted.additional', + defaultMessage: + 'This behaves exactly like public, except the post will not appear in live feeds or hashtags, explore, or Mastodon search, even if you are opted-in account-wide.', + }, +}); + +interface PrivacyDropdownProps { + value: StatusVisibility; + onChange: (value: StatusVisibility) => void; + noDirect?: boolean; + container?: OverlayProps['container']; + disabled?: boolean; +} + +const PrivacyDropdown: React.FC = ({ + value, + onChange, + noDirect, + container, + disabled, +}) => { + const intl = useIntl(); + const overlayTargetRef = useRef(null); + const previousFocusTargetRef = useRef(null); + const [isOpen, setIsOpen] = useState(false); + + const handleClose = useCallback(() => { + if (isOpen && previousFocusTargetRef.current) { + previousFocusTargetRef.current.focus({ preventScroll: true }); + } + setIsOpen(false); + }, [isOpen]); + + const handleToggle = useCallback(() => { + if (isOpen) { + handleClose(); + } + setIsOpen((prev) => !prev); + }, [handleClose, isOpen]); + + const registerPreviousFocusTarget = useCallback(() => { + if (!isOpen) { + previousFocusTargetRef.current = document.activeElement as HTMLElement; + } + }, [isOpen]); + + const handleButtonKeyDown = useCallback( + (e: React.KeyboardEvent) => { + if ([' ', 'Enter'].includes(e.key)) { + registerPreviousFocusTarget(); + } + }, + [registerPreviousFocusTarget], + ); + + const options = [ + { + icon: 'globe', + iconComponent: PublicIcon, + value: 'public', + text: intl.formatMessage(messages.public_short), + meta: intl.formatMessage(messages.public_long), + }, + { + icon: 'unlock', + iconComponent: QuietTimeIcon, + value: 'unlisted', + text: intl.formatMessage(messages.unlisted_short), + meta: intl.formatMessage(messages.unlisted_long), + extra: intl.formatMessage(messages.unlisted_extra), + }, + { + icon: 'lock', + iconComponent: LockIcon, + value: 'private', + text: intl.formatMessage(messages.private_short), + meta: intl.formatMessage(messages.private_long), + }, + ]; + + if (!noDirect) { + options.push({ + icon: 'at', + iconComponent: AlternateEmailIcon, + value: 'direct', + text: intl.formatMessage(messages.direct_short), + meta: intl.formatMessage(messages.direct_long), + }); + } + + const selectedOption = + options.find((item) => item.value === value) ?? options.at(0); + + return ( +
+ + + + {({ props, placement }) => ( +
+
+ +
+
+ )} +
+
+ ); +}; + +// eslint-disable-next-line import/no-default-export +export default PrivacyDropdown; diff --git a/app/javascript/mastodon/features/compose/components/quote_placeholder.tsx b/app/javascript/mastodon/features/compose/components/quote_placeholder.tsx new file mode 100644 index 00000000000000..706594e9cbdc02 --- /dev/null +++ b/app/javascript/mastodon/features/compose/components/quote_placeholder.tsx @@ -0,0 +1,48 @@ +import { useCallback } from 'react'; +import type { FC } from 'react'; + +import { defineMessages, useIntl } from 'react-intl'; + +import { cancelPasteLinkCompose } from '@/mastodon/actions/compose_typed'; +import { useAppDispatch } from '@/mastodon/store'; +import CancelFillIcon from '@/material-icons/400-24px/cancel-fill.svg?react'; +import { DisplayName } from 'mastodon/components/display_name'; +import { IconButton } from 'mastodon/components/icon_button'; +import { Skeleton } from 'mastodon/components/skeleton'; + +const messages = defineMessages({ + quote_cancel: { id: 'status.quote.cancel', defaultMessage: 'Cancel quote' }, +}); + +export const QuotePlaceholder: FC = () => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + const handleQuoteCancel = useCallback(() => { + dispatch(cancelPasteLinkCompose()); + }, [dispatch]); + + return ( +
+
+
+
+ +
+
+ +
+ +
+
+ +
+
+
+ ); +}; diff --git a/app/javascript/mastodon/features/compose/components/quoted_post.tsx b/app/javascript/mastodon/features/compose/components/quoted_post.tsx index f09d6fcd3443ff..8be3c7e62c62ea 100644 --- a/app/javascript/mastodon/features/compose/components/quoted_post.tsx +++ b/app/javascript/mastodon/features/compose/components/quoted_post.tsx @@ -7,11 +7,17 @@ import { quoteComposeCancel } from '@/mastodon/actions/compose_typed'; import { QuotedStatus } from '@/mastodon/components/status_quoted'; import { useAppDispatch, useAppSelector } from '@/mastodon/store'; +import { QuotePlaceholder } from './quote_placeholder'; + export const ComposeQuotedStatus: FC = () => { const quotedStatusId = useAppSelector( (state) => state.compose.get('quoted_status_id') as string | null, ); + const isFetchingLink = useAppSelector( + (state) => !!state.compose.get('fetching_link'), + ); + const isEditing = useAppSelector((state) => !!state.compose.get('id')); const quote = useMemo( @@ -30,7 +36,9 @@ export const ComposeQuotedStatus: FC = () => { dispatch(quoteComposeCancel()); }, [dispatch]); - if (!quote) { + if (isFetchingLink && !quote) { + return ; + } else if (!quote) { return null; } diff --git a/app/javascript/mastodon/features/compose/components/reply_indicator.jsx b/app/javascript/mastodon/features/compose/components/reply_indicator.jsx index 35733ac23b662c..e746fe6a6d22cb 100644 --- a/app/javascript/mastodon/features/compose/components/reply_indicator.jsx +++ b/app/javascript/mastodon/features/compose/components/reply_indicator.jsx @@ -35,9 +35,7 @@ export const ReplyIndicator = () => { {(status.get('poll') || status.get('media_attachments').size > 0) && ( diff --git a/app/javascript/mastodon/features/compose/components/search.tsx b/app/javascript/mastodon/features/compose/components/search.tsx index 2d44772ba2da8f..c2855e8cb58676 100644 --- a/app/javascript/mastodon/features/compose/components/search.tsx +++ b/app/javascript/mastodon/features/compose/components/search.tsx @@ -1,4 +1,11 @@ -import { useCallback, useState, useRef, useEffect } from 'react'; +import { + useCallback, + useState, + useRef, + useEffect, + useMemo, + useId, +} from 'react'; import { defineMessages, @@ -97,183 +104,197 @@ export const Search: React.FC<{ const [expanded, setExpanded] = useState(false); const [selectedOption, setSelectedOption] = useState(-1); const [quickActions, setQuickActions] = useState([]); - useEffect(() => { - setValue(initialValue ?? ''); - setQuickActions([]); - }, [initialValue]); - const searchOptions: SearchOption[] = []; const unfocus = useCallback(() => { document.querySelector('.ui')?.parentElement?.focus(); setExpanded(false); }, []); - if (searchEnabled) { - searchOptions.push( - { - key: 'prompt-has', - label: ( - <> - has:{' '} - - - ), - action: (e) => { - e.preventDefault(); - insertText('has:'); + const insertText = useCallback((text: string) => { + setValue((currentValue) => { + if (currentValue === '') { + return text; + } else if (currentValue.endsWith(' ')) { + return `${currentValue}${text}`; + } else { + return `${currentValue} ${text}`; + } + }); + }, []); + + const searchOptions = useMemo(() => { + if (!searchEnabled) { + return []; + } else { + const options: SearchOption[] = [ + { + key: 'prompt-has', + label: ( + <> + has:{' '} + + + ), + action: (e) => { + e.preventDefault(); + insertText('has:'); + }, }, - }, - { - key: 'prompt-is', - label: ( - <> - is:{' '} - - - ), - action: (e) => { - e.preventDefault(); - insertText('is:'); + { + key: 'prompt-is', + label: ( + <> + is:{' '} + + + ), + action: (e) => { + e.preventDefault(); + insertText('is:'); + }, }, - }, - { - key: 'prompt-language', - label: ( - <> - language:{' '} - - - ), - action: (e) => { - e.preventDefault(); - insertText('language:'); + { + key: 'prompt-language', + label: ( + <> + language:{' '} + + + ), + action: (e) => { + e.preventDefault(); + insertText('language:'); + }, }, - }, - { - key: 'prompt-from', - label: ( - <> - from:{' '} - - - ), - action: (e) => { - e.preventDefault(); - insertText('from:'); + { + key: 'prompt-from', + label: ( + <> + from:{' '} + + + ), + action: (e) => { + e.preventDefault(); + insertText('from:'); + }, }, - }, - { - key: 'prompt-before', - label: ( - <> - before:{' '} - - - ), - action: (e) => { - e.preventDefault(); - insertText('before:'); + { + key: 'prompt-before', + label: ( + <> + before:{' '} + + + ), + action: (e) => { + e.preventDefault(); + insertText('before:'); + }, }, - }, - { - key: 'prompt-during', - label: ( - <> - during:{' '} - - - ), - action: (e) => { - e.preventDefault(); - insertText('during:'); + { + key: 'prompt-during', + label: ( + <> + during:{' '} + + + ), + action: (e) => { + e.preventDefault(); + insertText('during:'); + }, }, - }, - { - key: 'prompt-after', - label: ( - <> - after:{' '} - - - ), - action: (e) => { - e.preventDefault(); - insertText('after:'); + { + key: 'prompt-after', + label: ( + <> + after:{' '} + + + ), + action: (e) => { + e.preventDefault(); + insertText('after:'); + }, }, - }, - { - key: 'prompt-in', - label: ( - <> - in:{' '} - - - ), - action: (e) => { - e.preventDefault(); - insertText('in:'); + { + key: 'prompt-in', + label: ( + <> + in:{' '} + + + ), + action: (e) => { + e.preventDefault(); + insertText('in:'); + }, }, - }, - ); - } - - const recentOptions: SearchOption[] = recent.map((search) => ({ - key: `${search.type}/${search.q}`, - label: labelForRecentSearch(search), - action: () => { - setValue(search.q); - - if (search.type === 'account') { - history.push(`/@${search.q}`); - } else if (search.type === 'hashtag') { - history.push(`/tags/${search.q}`); - } else { - const queryParams = new URLSearchParams({ q: search.q }); - if (search.type) queryParams.set('type', search.type); - history.push({ pathname: '/search', search: queryParams.toString() }); - } - - unfocus(); - }, - forget: (e) => { - e.stopPropagation(); - void dispatch(forgetSearchResult(search)); - }, - })); + ]; + return options; + } + }, [insertText]); + + const recentOptions: SearchOption[] = useMemo( + () => + recent.map((search) => ({ + key: `${search.type}/${search.q}`, + label: labelForRecentSearch(search), + action: () => { + setValue(search.q); + + if (search.type === 'account') { + history.push(`/@${search.q}`); + } else if (search.type === 'hashtag') { + history.push(`/tags/${search.q}`); + } else { + const queryParams = new URLSearchParams({ q: search.q }); + if (search.type) queryParams.set('type', search.type); + history.push({ + pathname: '/search', + search: queryParams.toString(), + }); + } - const navigableOptions = hasValue - ? quickActions.concat(searchOptions) - : recentOptions.concat(quickActions, searchOptions); + unfocus(); + }, + forget: (e) => { + e.stopPropagation(); + void dispatch(forgetSearchResult(search)); + }, + })), + [dispatch, history, recent, unfocus], + ); - const insertText = (text: string) => { - setValue((currentValue) => { - if (currentValue === '') { - return text; - } else if (currentValue.endsWith(' ')) { - return `${currentValue}${text}`; - } else { - return `${currentValue} ${text}`; - } - }); - }; + const navigableOptions: SearchOption[] = useMemo( + () => + hasValue + ? quickActions.concat(searchOptions) + : recentOptions.concat(quickActions, searchOptions), + [hasValue, quickActions, recentOptions, searchOptions], + ); const submit = useCallback( (q: string, type?: SearchType) => { @@ -418,12 +439,17 @@ export const Search: React.FC<{ switch (e.key) { case 'Escape': e.preventDefault(); - unfocus(); + searchInputRef.current?.focus(); + setExpanded(false); break; case 'ArrowDown': e.preventDefault(); + if (!expanded) { + setExpanded(true); + } + if (navigableOptions.length > 0) { setSelectedOption( Math.min(selectedOption + 1, navigableOptions.length - 1), @@ -462,10 +488,10 @@ export const Search: React.FC<{ break; } }, - [unfocus, navigableOptions, selectedOption, submit, value], + [expanded, navigableOptions, selectedOption, submit, value], ); - const handleFocus = useCallback(() => { + const handleInputFocus = useCallback(() => { setExpanded(true); setSelectedOption(-1); @@ -481,10 +507,16 @@ export const Search: React.FC<{ } }, [setExpanded, setSelectedOption, singleColumn]); - const handleBlur = useCallback(() => { + const handleInputBlur = useCallback(() => { setSelectedOption(-1); }, [setSelectedOption]); + const getOptionFocusHandler = useCallback((index: number) => { + return () => { + setSelectedOption(index); + }; + }, []); + const formRef = useRef(null); useEffect(() => { @@ -512,6 +544,8 @@ export const Search: React.FC<{ return () => null; }, [expanded]); + const searchOptionsHeading = useId(); + return (
-
+ {/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions */} +
{!hasValue && ( <>

@@ -551,13 +592,18 @@ export const Search: React.FC<{ tabIndex={0} role='button' onMouseDown={action} + onFocus={getOptionFocusHandler(i)} className={classNames( 'search__popout__menu__item search__popout__menu__item--flex', { selected: selectedOption === i }, )} > {label} -

@@ -588,9 +634,11 @@ export const Search: React.FC<{ @@ -599,7 +647,7 @@ export const Search: React.FC<{ )} -

+

- {searchOptions.map(({ key, label, action }, i) => ( - - ))} + {searchOptions.map(({ key, label, action }, i) => { + const currentIndex = (quickActions.length || recent.length) + i; + return ( + + ); + })}

) : (
diff --git a/app/javascript/mastodon/features/compose/components/text_icon_button.jsx b/app/javascript/mastodon/features/compose/components/text_icon_button.jsx deleted file mode 100644 index 166d022b88ac79..00000000000000 --- a/app/javascript/mastodon/features/compose/components/text_icon_button.jsx +++ /dev/null @@ -1,38 +0,0 @@ -import PropTypes from 'prop-types'; -import { PureComponent } from 'react'; - -const iconStyle = { - height: null, - lineHeight: '27px', - minWidth: `${18 * 1.28571429}px`, -}; - -export default class TextIconButton extends PureComponent { - - static propTypes = { - label: PropTypes.string.isRequired, - title: PropTypes.string, - active: PropTypes.bool, - onClick: PropTypes.func.isRequired, - ariaControls: PropTypes.string, - }; - - render () { - const { label, title, active, ariaControls } = this.props; - - return ( - - ); - } - -} diff --git a/app/javascript/mastodon/features/compose/components/upload.tsx b/app/javascript/mastodon/features/compose/components/upload.tsx index adc51733d9af37..85fed0cbd3bf78 100644 --- a/app/javascript/mastodon/features/compose/components/upload.tsx +++ b/app/javascript/mastodon/features/compose/components/upload.tsx @@ -10,6 +10,7 @@ import { useSortable } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import CloseIcon from '@/material-icons/400-20px/close.svg?react'; +import SoundIcon from '@/material-icons/400-24px/audio.svg?react'; import EditIcon from '@/material-icons/400-24px/edit.svg?react'; import WarningIcon from '@/material-icons/400-24px/warning.svg?react'; import { undoUploadCompose } from 'mastodon/actions/compose'; @@ -17,7 +18,18 @@ import { openModal } from 'mastodon/actions/modal'; import { Blurhash } from 'mastodon/components/blurhash'; import { Icon } from 'mastodon/components/icon'; import type { MediaAttachment } from 'mastodon/models/media_attachment'; -import { useAppDispatch, useAppSelector } from 'mastodon/store'; +import { + createAppSelector, + useAppDispatch, + useAppSelector, +} from 'mastodon/store'; + +import { AudioVisualizer } from '../../audio/visualizer'; + +const selectUserAvatar = createAppSelector( + [(state) => state.accounts, (state) => state.meta.get('me') as string], + (accounts, myId) => accounts.get(myId)?.avatar_static, +); export const Upload: React.FC<{ id: string; @@ -38,6 +50,7 @@ export const Upload: React.FC<{ const sensitive = useAppSelector( (state) => state.compose.get('spoiler') as boolean, ); + const userAvatar = useAppSelector(selectUserAvatar); const handleUndoClick = useCallback(() => { dispatch(undoUploadCompose(id)); @@ -67,6 +80,8 @@ export const Upload: React.FC<{ transform: CSS.Transform.toString(transform), transition, }; + const preview_url = media.get('preview_url') as string | null; + const blurhash = media.get('blurhash') as string | null; return (
- {sensitive && ( - + {sensitive && blurhash && ( + + )} + {!sensitive && !preview_url && ( +
+ + +
)}
diff --git a/app/javascript/mastodon/features/compose/components/visibility_button.tsx b/app/javascript/mastodon/features/compose/components/visibility_button.tsx index 1ea504ab1a3293..d93940502071af 100644 --- a/app/javascript/mastodon/features/compose/components/visibility_button.tsx +++ b/app/javascript/mastodon/features/compose/components/visibility_button.tsx @@ -5,8 +5,10 @@ import { defineMessages, useIntl } from 'react-intl'; import classNames from 'classnames'; -import { changeComposeVisibility } from '@/mastodon/actions/compose'; -import { setComposeQuotePolicy } from '@/mastodon/actions/compose_typed'; +import { + changeComposeVisibility, + setComposeQuotePolicy, +} from '@/mastodon/actions/compose_typed'; import { openModal } from '@/mastodon/actions/modal'; import type { ApiQuotePolicy } from '@/mastodon/api_types/quotes'; import type { StatusVisibility } from '@/mastodon/api_types/statuses'; diff --git a/app/javascript/mastodon/features/compose/containers/compose_form_container.js b/app/javascript/mastodon/features/compose/containers/compose_form_container.js index 5f86426c4d41af..4ace6d934de5eb 100644 --- a/app/javascript/mastodon/features/compose/containers/compose_form_container.js +++ b/app/javascript/mastodon/features/compose/containers/compose_form_container.js @@ -10,10 +10,32 @@ import { insertEmojiCompose, uploadCompose, } from 'mastodon/actions/compose'; +import { pasteLinkCompose } from 'mastodon/actions/compose_typed'; import { openModal } from 'mastodon/actions/modal'; +import { PRIVATE_QUOTE_MODAL_ID } from 'mastodon/features/ui/components/confirmation_modals/private_quote_notify'; +import { me } from 'mastodon/initial_state'; import ComposeForm from '../components/compose_form'; +const urlLikeRegex = /^https?:\/\/[^\s]+\/[^\s]+$/i; + +const processPasteOrDrop = (transfer, e, dispatch) => { + if (transfer && transfer.files.length === 1) { + dispatch(uploadCompose(transfer.files)); + e.preventDefault(); + } else if (transfer && transfer.files.length === 0) { + const data = transfer.getData('text/plain'); + if (!data.match(urlLikeRegex)) return; + + try { + const url = new URL(data); + dispatch(pasteLinkCompose({ url })); + } catch { + return; + } + } +}; + const mapStateToProps = state => ({ text: state.getIn(['compose', 'text']), suggestions: state.getIn(['compose', 'suggestions']), @@ -29,6 +51,11 @@ const mapStateToProps = state => ({ isUploading: state.getIn(['compose', 'is_uploading']), anyMedia: state.getIn(['compose', 'media_attachments']).size > 0, missingAltText: state.getIn(['compose', 'media_attachments']).some(media => ['image', 'gifv'].includes(media.get('type')) && (media.get('description') ?? '').length === 0), + quoteToPrivate: + !!state.getIn(['compose', 'quoted_status_id']) + && state.getIn(['compose', 'privacy']) === 'private' + && state.getIn(['statuses', state.getIn(['compose', 'quoted_status_id']), 'account']) !== me + && !state.getIn(['settings', 'dismissed_banners', PRIVATE_QUOTE_MODAL_ID]), isInReply: state.getIn(['compose', 'in_reply_to']) !== null, lang: state.getIn(['compose', 'language']), maxChars: state.getIn(['server', 'server', 'configuration', 'statuses', 'max_characters'], 500), @@ -40,12 +67,17 @@ const mapDispatchToProps = (dispatch, props) => ({ dispatch(changeCompose(text)); }, - onSubmit (missingAltText) { + onSubmit ({ missingAltText, quoteToPrivate }) { if (missingAltText) { dispatch(openModal({ modalType: 'CONFIRM_MISSING_ALT_TEXT', modalProps: {}, })); + } else if (quoteToPrivate) { + dispatch(openModal({ + modalType: 'CONFIRM_PRIVATE_QUOTE_NOTIFY', + modalProps: {}, + })); } else { dispatch(submitCompose((status) => { if (props.redirectOnSuccess) { @@ -71,8 +103,12 @@ const mapDispatchToProps = (dispatch, props) => ({ dispatch(changeComposeSpoilerText(checked)); }, - onPaste (files) { - dispatch(uploadCompose(files)); + onPaste (e) { + processPasteOrDrop(e.clipboardData, e, dispatch); + }, + + onDrop (e) { + processPasteOrDrop(e.dataTransfer, e, dispatch); }, onPickEmoji (position, data, needsSpace) { diff --git a/app/javascript/mastodon/features/compose/containers/privacy_dropdown_container.js b/app/javascript/mastodon/features/compose/containers/privacy_dropdown_container.js deleted file mode 100644 index 6d3eef13aa237d..00000000000000 --- a/app/javascript/mastodon/features/compose/containers/privacy_dropdown_container.js +++ /dev/null @@ -1,20 +0,0 @@ -import { connect } from 'react-redux'; - -import { changeComposeVisibility } from '../../../actions/compose'; -import { openModal, closeModal } from '../../../actions/modal'; -import { isUserTouching } from '../../../is_mobile'; -import PrivacyDropdown from '../components/privacy_dropdown'; - -const mapStateToProps = state => ({ - value: state.getIn(['compose', 'privacy']), -}); - -const mapDispatchToProps = dispatch => ({ - - onChange (value) { - dispatch(changeComposeVisibility(value)); - }, - -}); - -export default connect(mapStateToProps, mapDispatchToProps)(PrivacyDropdown); diff --git a/app/javascript/mastodon/features/compose/index.tsx b/app/javascript/mastodon/features/compose/index.tsx index 892cbb97617566..527e541e84dbe1 100644 --- a/app/javascript/mastodon/features/compose/index.tsx +++ b/app/javascript/mastodon/features/compose/index.tsx @@ -166,7 +166,7 @@ const Compose: React.FC<{ multiColumn: boolean }> = ({ multiColumn }) => {
-
+
diff --git a/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx b/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx index bb0815087b080f..fbe37f58a29888 100644 --- a/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx +++ b/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx @@ -25,6 +25,7 @@ import StatusContent from 'mastodon/components/status_content'; import { Dropdown } from 'mastodon/components/dropdown_menu'; import { makeGetStatus } from 'mastodon/selectors'; import { LinkedDisplayName } from '@/mastodon/components/display_name'; +import { AnimateEmojiProvider } from '@/mastodon/components/emoji/context'; const messages = defineMessages({ more: { id: 'status.more', defaultMessage: 'More' }, @@ -136,9 +137,9 @@ export const Conversation = ({ conversation, scrollKey }) => { {unread && }
-
+ {names} }} /> -
+
= ({ accountId }) => { - const intl = useIntl(); const account = useAppSelector((s) => getAccount(s, accountId)); - const dispatch = useAppDispatch(); - - const handleFollow = useCallback(() => { - if (!account) return; - - if ( - account.getIn(['relationship', 'following']) || - account.getIn(['relationship', 'requested']) - ) { - dispatch( - openModal({ modalType: 'CONFIRM_UNFOLLOW', modalProps: { account } }), - ); - } else { - dispatch(followAccount(account.get('id'))); - } - }, [account, dispatch]); - - const handleBlock = useCallback(() => { - if (account?.relationship?.blocking) { - dispatch(unblockAccount(account.get('id'))); - } - }, [account, dispatch]); - - const handleMute = useCallback(() => { - if (account?.relationship?.muting) { - dispatch(unmuteAccount(account.get('id'))); - } - }, [account, dispatch]); - - const handleEditProfile = useCallback(() => { - window.open('/settings/profile', '_blank'); - }, []); if (!account) return null; - let actionBtn; - - if (me !== account.get('id')) { - if (!account.get('relationship')) { - // Wait until the relationship is loaded - actionBtn = ''; - } else if (account.getIn(['relationship', 'requested'])) { - actionBtn = ( -
); diff --git a/app/javascript/mastodon/features/directory/index.tsx b/app/javascript/mastodon/features/directory/index.tsx index a29febcd1ab25b..0fe140b4eb49d4 100644 --- a/app/javascript/mastodon/features/directory/index.tsx +++ b/app/javascript/mastodon/features/directory/index.tsx @@ -21,7 +21,7 @@ import { ColumnHeader } from 'mastodon/components/column_header'; import { LoadMore } from 'mastodon/components/load_more'; import { LoadingIndicator } from 'mastodon/components/loading_indicator'; import { RadioButton } from 'mastodon/components/radio_button'; -import ScrollContainer from 'mastodon/containers/scroll_container'; +import { ScrollContainer } from 'mastodon/containers/scroll_container'; import { useSearchParam } from 'mastodon/hooks/useSearchParam'; import { useAppDispatch, useAppSelector } from 'mastodon/store'; @@ -206,7 +206,6 @@ export const Directory: React.FC<{ /> {multiColumn && !pinned ? ( - // @ts-expect-error ScrollContainer is not properly typed yet {scrollableArea} diff --git a/app/javascript/mastodon/features/domain_blocks/index.tsx b/app/javascript/mastodon/features/domain_blocks/index.tsx index 900aba4745c517..2c5860af98752d 100644 --- a/app/javascript/mastodon/features/domain_blocks/index.tsx +++ b/app/javascript/mastodon/features/domain_blocks/index.tsx @@ -19,14 +19,12 @@ const messages = defineMessages({ const Blocks: React.FC<{ multiColumn: boolean }> = ({ multiColumn }) => { const intl = useIntl(); const [domains, setDomains] = useState([]); - const [loading, setLoading] = useState(false); + const [loading, setLoading] = useState(true); const [next, setNext] = useState(); const hasMore = !!next; const columnRef = useRef(null); useEffect(() => { - setLoading(true); - void apiGetDomainBlocks() .then(({ domains, links }) => { const next = links.refs.find((link) => link.rel === 'next'); @@ -40,7 +38,7 @@ const Blocks: React.FC<{ multiColumn: boolean }> = ({ multiColumn }) => { .catch(() => { setLoading(false); }); - }, [setLoading, setDomains, setNext]); + }, []); const handleLoadMore = useCallback(() => { setLoading(true); diff --git a/app/javascript/mastodon/features/emoji/constants.ts b/app/javascript/mastodon/features/emoji/constants.ts index 09022371b2248c..e02663c9d84082 100644 --- a/app/javascript/mastodon/features/emoji/constants.ts +++ b/app/javascript/mastodon/features/emoji/constants.ts @@ -23,7 +23,9 @@ export const EMOJI_MODE_TWEMOJI = 'twemoji'; export const EMOJI_TYPE_UNICODE = 'unicode'; export const EMOJI_TYPE_CUSTOM = 'custom'; -export const EMOJI_STATE_MISSING = 'missing'; +export const EMOJI_DB_NAME_SHORTCODES = 'shortcodes'; + +export const EMOJI_DB_SHORTCODE_TEST = '2122'; // 2122 is the trademark sign, which we know has shortcodes in all datasets. export const EMOJIS_WITH_DARK_BORDER = [ '🎱', // 1F3B1 @@ -118,3 +120,29 @@ export const EMOJIS_WITH_LIGHT_BORDER = [ '🪽', // 1FAE8 '🪿', // 1FABF ]; + +export const EMOJIS_REQUIRING_INVERSION_IN_LIGHT_MODE = [ + '⛓️', // 26D3-FE0F +]; + +export const EMOJIS_REQUIRING_INVERSION_IN_DARK_MODE = [ + '🔜', // 1F51C + '🔙', // 1F519 + '🔛', // 1F51B + '🔝', // 1F51D + '🔚', // 1F51A + '©️', // 00A9 FE0F + '➰', // 27B0 + '💱', // 1F4B1 + '✔️', // 2714 FE0F + '➗', // 2797 + '💲', // 1F4B2 + '➖', // 2796 + '✖️', // 2716 FE0F + '➕', // 2795 + '®️', // 00AE FE0F + '🕷️', // 1F577 FE0F + '📞', // 1F4DE + '™️', // 2122 FE0F + '〰️', // 3030 FE0F +]; diff --git a/app/javascript/mastodon/features/emoji/database.test.ts b/app/javascript/mastodon/features/emoji/database.test.ts index 0689fd7c542d7f..6b6ea952b74cdc 100644 --- a/app/javascript/mastodon/features/emoji/database.test.ts +++ b/app/javascript/mastodon/features/emoji/database.test.ts @@ -1,7 +1,8 @@ import { IDBFactory } from 'fake-indexeddb'; -import { unicodeEmojiFactory } from '@/testing/factories'; +import { customEmojiFactory, unicodeEmojiFactory } from '@/testing/factories'; +import { EMOJI_DB_SHORTCODE_TEST } from './constants'; import { putEmojiData, loadEmojiByHexcode, @@ -9,6 +10,11 @@ import { searchEmojisByTag, testClear, testGet, + putCustomEmojiData, + putLegacyShortcodes, + loadLegacyShortcodesByShortcode, + loadLatestEtag, + putLatestEtag, } from './database'; describe('emoji database', () => { @@ -16,6 +22,7 @@ describe('emoji database', () => { testClear(); indexedDB = new IDBFactory(); }); + describe('putEmojiData', () => { test('adds to loaded locales', async () => { const { loadedLocales } = await testGet(); @@ -33,6 +40,44 @@ describe('emoji database', () => { }); }); + describe('putCustomEmojiData', () => { + test('loads custom emoji into indexedDB', async () => { + const { db } = await testGet(); + await putCustomEmojiData({ emojis: [customEmojiFactory()] }); + await expect(db.get('custom', 'custom')).resolves.toEqual( + customEmojiFactory(), + ); + }); + + test('clears existing custom emoji if specified', async () => { + const { db } = await testGet(); + await putCustomEmojiData({ + emojis: [customEmojiFactory({ shortcode: 'emoji1' })], + }); + await putCustomEmojiData({ + emojis: [customEmojiFactory({ shortcode: 'emoji2' })], + clear: true, + }); + await expect(db.get('custom', 'emoji1')).resolves.toBeUndefined(); + await expect(db.get('custom', 'emoji2')).resolves.toEqual( + customEmojiFactory({ shortcode: 'emoji2' }), + ); + }); + }); + + describe('putLegacyShortcodes', () => { + test('loads shortcodes into indexedDB', async () => { + const { db } = await testGet(); + await putLegacyShortcodes({ + test_hexcode: ['shortcode1', 'shortcode2'], + }); + await expect(db.get('shortcodes', 'test_hexcode')).resolves.toEqual({ + hexcode: 'test_hexcode', + shortcodes: ['shortcode1', 'shortcode2'], + }); + }); + }); + describe('loadEmojiByHexcode', () => { test('throws if the locale is not loaded', async () => { await expect(loadEmojiByHexcode('en', 'test')).rejects.toThrowError( @@ -136,4 +181,58 @@ describe('emoji database', () => { expect(actual).toHaveLength(0); }); }); + + describe('loadLegacyShortcodesByShortcode', () => { + const data = { + hexcode: 'test_hexcode', + shortcodes: ['shortcode1', 'shortcode2'], + }; + + beforeEach(async () => { + await putLegacyShortcodes({ + [data.hexcode]: data.shortcodes, + }); + }); + + test('retrieves the shortcodes', async () => { + await expect( + loadLegacyShortcodesByShortcode('shortcode1'), + ).resolves.toEqual(data); + await expect( + loadLegacyShortcodesByShortcode('shortcode2'), + ).resolves.toEqual(data); + }); + }); + + describe('loadLatestEtag', () => { + beforeEach(async () => { + await putLatestEtag('etag', 'en'); + await putEmojiData([unicodeEmojiFactory()], 'en'); + await putLatestEtag('fr-etag', 'fr'); + }); + + test('retrieves the etag for loaded locale', async () => { + await putEmojiData( + [unicodeEmojiFactory({ hexcode: EMOJI_DB_SHORTCODE_TEST })], + 'en', + ); + const etag = await loadLatestEtag('en'); + expect(etag).toBe('etag'); + }); + + test('returns null if locale has no shortcodes', async () => { + const etag = await loadLatestEtag('en'); + expect(etag).toBeNull(); + }); + + test('returns null if locale not loaded', async () => { + const etag = await loadLatestEtag('de'); + expect(etag).toBeNull(); + }); + + test('returns null if locale has no data', async () => { + const etag = await loadLatestEtag('fr'); + expect(etag).toBeNull(); + }); + }); }); diff --git a/app/javascript/mastodon/features/emoji/database.ts b/app/javascript/mastodon/features/emoji/database.ts index 0e8ada1d0e05a9..fe4010a861d994 100644 --- a/app/javascript/mastodon/features/emoji/database.ts +++ b/app/javascript/mastodon/features/emoji/database.ts @@ -1,14 +1,11 @@ import { SUPPORTED_LOCALES } from 'emojibase'; -import type { Locale } from 'emojibase'; +import type { Locale, ShortcodesDataset } from 'emojibase'; import type { DBSchema, IDBPDatabase } from 'idb'; import { openDB } from 'idb'; +import { EMOJI_DB_SHORTCODE_TEST } from './constants'; import { toSupportedLocale, toSupportedLocaleOrCustom } from './locale'; -import type { - CustomEmojiData, - UnicodeEmojiData, - LocaleOrCustom, -} from './types'; +import type { CustomEmojiData, UnicodeEmojiData, EtagTypes } from './types'; import { emojiLogger } from './utils'; interface EmojiDB extends LocaleTables, DBSchema { @@ -19,8 +16,19 @@ interface EmojiDB extends LocaleTables, DBSchema { category: string; }; }; + shortcodes: { + key: string; + value: { + hexcode: string; + shortcodes: string[]; + }; + indexes: { + hexcode: string; + shortcodes: string[]; + }; + }; etags: { - key: LocaleOrCustom; + key: EtagTypes; value: string; }; } @@ -33,13 +41,14 @@ interface LocaleTable { label: string; order: number; tags: string[]; + shortcodes: string[]; }; } type LocaleTables = Record; type Database = IDBPDatabase; -const SCHEMA_VERSION = 1; +const SCHEMA_VERSION = 2; const loadedLocales = new Set(); @@ -52,28 +61,76 @@ const loadDB = (() => { // Actually load the DB. async function initDB() { const db = await openDB('mastodon-emoji', SCHEMA_VERSION, { - upgrade(database) { - const customTable = database.createObjectStore('custom', { - keyPath: 'shortcode', - autoIncrement: false, - }); - customTable.createIndex('category', 'category'); + upgrade(database, oldVersion, newVersion, trx) { + if (!database.objectStoreNames.contains('custom')) { + const customTable = database.createObjectStore('custom', { + keyPath: 'shortcode', + autoIncrement: false, + }); + customTable.createIndex('category', 'category'); + } - database.createObjectStore('etags'); + if (!database.objectStoreNames.contains('etags')) { + database.createObjectStore('etags'); + } for (const locale of SUPPORTED_LOCALES) { - const localeTable = database.createObjectStore(locale, { + if (!database.objectStoreNames.contains(locale)) { + const localeTable = database.createObjectStore(locale, { + keyPath: 'hexcode', + autoIncrement: false, + }); + localeTable.createIndex('group', 'group'); + localeTable.createIndex('label', 'label'); + localeTable.createIndex('order', 'order'); + localeTable.createIndex('tags', 'tags', { multiEntry: true }); + localeTable.createIndex('shortcodes', 'shortcodes', { + multiEntry: true, + }); + } + // Added in version 2. + const localeTable = trx.objectStore(locale); + if (!localeTable.indexNames.contains('shortcodes')) { + localeTable.createIndex('shortcodes', 'shortcodes', { + multiEntry: true, + }); + } + } + + if (!database.objectStoreNames.contains('shortcodes')) { + const shortcodeTable = database.createObjectStore('shortcodes', { keyPath: 'hexcode', autoIncrement: false, }); - localeTable.createIndex('group', 'group'); - localeTable.createIndex('label', 'label'); - localeTable.createIndex('order', 'order'); - localeTable.createIndex('tags', 'tags', { multiEntry: true }); + shortcodeTable.createIndex('hexcode', 'hexcode'); + shortcodeTable.createIndex('shortcodes', 'shortcodes', { + multiEntry: true, + }); } + + log( + 'Upgraded emoji database from version %d to %d', + oldVersion, + newVersion, + ); + }, + blocked(currentVersion, blockedVersion) { + log( + 'Emoji database upgrade from version %d to %d is blocked', + currentVersion, + blockedVersion, + ); + }, + blocking(currentVersion, blockedVersion) { + log( + 'Emoji database upgrade from version %d is blocking upgrade to %d', + currentVersion, + blockedVersion, + ); }, }); await syncLocales(db); + log('Loaded database version %d', db.version); return db; } @@ -100,17 +157,52 @@ export async function putEmojiData(emojis: UnicodeEmojiData[], locale: Locale) { await trx.done; } -export async function putCustomEmojiData(emojis: CustomEmojiData[]) { +export async function putCustomEmojiData({ + emojis, + clear = false, +}: { + emojis: CustomEmojiData[]; + clear?: boolean; +}) { const db = await loadDB(); const trx = db.transaction('custom', 'readwrite'); + + // When importing from the API, clear everything first. + if (clear) { + await trx.store.clear(); + log('Cleared existing custom emojis in database'); + } + await Promise.all(emojis.map((emoji) => trx.store.put(emoji))); await trx.done; + + log('Imported %d custom emojis into database', emojis.length); } -export async function putLatestEtag(etag: string, localeString: string) { +export async function putLegacyShortcodes(shortcodes: ShortcodesDataset) { + const db = await loadDB(); + const trx = db.transaction('shortcodes', 'readwrite'); + await Promise.all( + Object.entries(shortcodes).map(([hexcode, codes]) => + trx.store.put({ + hexcode, + shortcodes: Array.isArray(codes) ? codes : [codes], + }), + ), + ); + await trx.done; +} + +export async function putLatestEtag(etag: string, name: EtagTypes) { + const db = await loadDB(); + await db.put('etags', etag, name); +} + +export async function clearEtag(localeString: string) { const locale = toSupportedLocaleOrCustom(localeString); const db = await loadDB(); - await db.put('etags', etag, locale); + await db.delete('etags', locale); + log('Cleared etag for %s', locale); } export async function loadEmojiByHexcode( @@ -161,6 +253,15 @@ export async function searchCustomEmojisByShortcodes(shortcodes: string[]) { return results.filter((emoji) => shortcodes.includes(emoji.shortcode)); } +export async function loadLegacyShortcodesByShortcode(shortcode: string) { + const db = await loadDB(); + return db.getFromIndex( + 'shortcodes', + 'shortcodes', + IDBKeyRange.only(shortcode), + ); +} + export async function loadLatestEtag(localeString: string) { const locale = toSupportedLocaleOrCustom(localeString); const db = await loadDB(); @@ -168,6 +269,15 @@ export async function loadLatestEtag(localeString: string) { if (!rowCount) { return null; // No data for this locale, return null even if there is an etag. } + + // Check if shortcodes exist for the given Unicode locale. + if (locale !== 'custom') { + const result = await db.get(locale, EMOJI_DB_SHORTCODE_TEST); + if (!result?.shortcodes) { + return null; + } + } + const etag = await db.get('etags', locale); return etag ?? null; } @@ -197,11 +307,18 @@ function toLoadedLocale(localeString: string) { log(`Locale ${locale} is different from provided ${localeString}`); } if (!loadedLocales.has(locale)) { - throw new Error(`Locale ${locale} is not loaded in emoji database`); + throw new LocaleNotLoadedError(locale); } return locale; } +export class LocaleNotLoadedError extends Error { + constructor(locale: Locale) { + super(`Locale ${locale} is not loaded in emoji database`); + this.name = 'LocaleNotLoadedError'; + } +} + async function hasLocale(locale: Locale, db: Database): Promise { if (loadedLocales.has(locale)) { return true; diff --git a/app/javascript/mastodon/features/emoji/emoji.js b/app/javascript/mastodon/features/emoji/emoji.js index d1843c33bdad58..f8fa0ae1923785 100644 --- a/app/javascript/mastodon/features/emoji/emoji.js +++ b/app/javascript/mastodon/features/emoji/emoji.js @@ -1,5 +1,6 @@ import Trie from 'substring-trie'; +import { getUserTheme, isDarkMode } from '@/mastodon/utils/theme'; import { assetHost } from 'mastodon/utils/config'; import { autoPlayGif } from '../../initial_state'; @@ -97,9 +98,9 @@ const emojifyTextNode = (node, customEmojis) => { const { filename, shortCode } = unicodeMapping[unicode_emoji]; const title = shortCode ? `:${shortCode}:` : ''; - const isSystemTheme = !!document.body?.classList.contains('theme-system'); + const isSystemTheme = getUserTheme() === 'system'; - const theme = (isSystemTheme || document.body?.classList.contains('theme-mastodon-light')) ? 'light' : 'dark'; + const theme = (isSystemTheme || !isDarkMode()) ? 'light' : 'dark'; const imageFilename = emojiFilename(filename, theme); @@ -148,6 +149,12 @@ const emojifyNode = (node, customEmojis) => { } }; +/** + * Legacy emoji processing function. + * @param {string} str + * @param {object} customEmojis + * @returns {string} + */ const emojify = (str, customEmojis = {}) => { const wrapper = document.createElement('div'); wrapper.innerHTML = str; diff --git a/app/javascript/mastodon/features/emoji/emoji_compressed.mjs b/app/javascript/mastodon/features/emoji/emoji_compressed.mjs index db8a4bc122a57e..c565b861fd75e2 100644 --- a/app/javascript/mastodon/features/emoji/emoji_compressed.mjs +++ b/app/javascript/mastodon/features/emoji/emoji_compressed.mjs @@ -14,8 +14,7 @@ import { uncompress as emojiMartUncompress } from 'emoji-mart/dist/utils/data'; import data from './emoji_data.json'; import emojiMap from './emoji_map.json'; -import { unicodeToFilename } from './unicode_to_filename'; -import { unicodeToUnifiedName } from './unicode_to_unified_name'; +import { unicodeToFilename, unicodeToUnifiedName } from './unicode_utils'; emojiMartUncompress(data); diff --git a/app/javascript/mastodon/features/emoji/emoji_data.json b/app/javascript/mastodon/features/emoji/emoji_data.json index 4d7a48692bec81..7ef1c9838b2849 100644 --- a/app/javascript/mastodon/features/emoji/emoji_data.json +++ b/app/javascript/mastodon/features/emoji/emoji_data.json @@ -1 +1 @@ -{"compressed":true,"categories":[{"id":"people","name":"Smileys & People","emojis":["grinning","smiley","smile","grin","laughing","sweat_smile","rolling_on_the_floor_laughing","joy","slightly_smiling_face","upside_down_face","melting_face","wink","blush","innocent","smiling_face_with_3_hearts","heart_eyes","star-struck","kissing_heart","kissing","relaxed","kissing_closed_eyes","kissing_smiling_eyes","smiling_face_with_tear","yum","stuck_out_tongue","stuck_out_tongue_winking_eye","zany_face","stuck_out_tongue_closed_eyes","money_mouth_face","hugging_face","face_with_hand_over_mouth","face_with_open_eyes_and_hand_over_mouth","face_with_peeking_eye","shushing_face","thinking_face","saluting_face","zipper_mouth_face","face_with_raised_eyebrow","neutral_face","expressionless","no_mouth","dotted_line_face","face_in_clouds","smirk","unamused","face_with_rolling_eyes","grimacing","face_exhaling","lying_face","shaking_face","head_shaking_horizontally","head_shaking_vertically","relieved","pensive","sleepy","drooling_face","sleeping","mask","face_with_thermometer","face_with_head_bandage","nauseated_face","face_vomiting","sneezing_face","hot_face","cold_face","woozy_face","dizzy_face","face_with_spiral_eyes","exploding_head","face_with_cowboy_hat","partying_face","disguised_face","sunglasses","nerd_face","face_with_monocle","confused","face_with_diagonal_mouth","worried","slightly_frowning_face","white_frowning_face","open_mouth","hushed","astonished","flushed","pleading_face","face_holding_back_tears","frowning","anguished","fearful","cold_sweat","disappointed_relieved","cry","sob","scream","confounded","persevere","disappointed","sweat","weary","tired_face","yawning_face","triumph","rage","angry","face_with_symbols_on_mouth","smiling_imp","imp","skull","skull_and_crossbones","hankey","clown_face","japanese_ogre","japanese_goblin","ghost","alien","space_invader","robot_face","smiley_cat","smile_cat","joy_cat","heart_eyes_cat","smirk_cat","kissing_cat","scream_cat","crying_cat_face","pouting_cat","see_no_evil","hear_no_evil","speak_no_evil","wave","raised_back_of_hand","raised_hand_with_fingers_splayed","hand","spock-hand","rightwards_hand","leftwards_hand","palm_down_hand","palm_up_hand","leftwards_pushing_hand","rightwards_pushing_hand","ok_hand","pinched_fingers","pinching_hand","v","crossed_fingers","hand_with_index_finger_and_thumb_crossed","i_love_you_hand_sign","the_horns","call_me_hand","point_left","point_right","point_up_2","middle_finger","point_down","point_up","index_pointing_at_the_viewer","+1","-1","fist","facepunch","left-facing_fist","right-facing_fist","clap","raised_hands","heart_hands","open_hands","palms_up_together","handshake","pray","writing_hand","nail_care","selfie","muscle","mechanical_arm","mechanical_leg","leg","foot","ear","ear_with_hearing_aid","nose","brain","anatomical_heart","lungs","tooth","bone","eyes","eye","tongue","lips","biting_lip","baby","child","boy","girl","adult","person_with_blond_hair","man","bearded_person","man_with_beard","woman_with_beard","red_haired_man","curly_haired_man","white_haired_man","bald_man","woman","red_haired_woman","red_haired_person","curly_haired_woman","curly_haired_person","white_haired_woman","white_haired_person","bald_woman","bald_person","blond-haired-woman","blond-haired-man","older_adult","older_man","older_woman","person_frowning","man-frowning","woman-frowning","person_with_pouting_face","man-pouting","woman-pouting","no_good","man-gesturing-no","woman-gesturing-no","ok_woman","man-gesturing-ok","woman-gesturing-ok","information_desk_person","man-tipping-hand","woman-tipping-hand","raising_hand","man-raising-hand","woman-raising-hand","deaf_person","deaf_man","deaf_woman","bow","man-bowing","woman-bowing","face_palm","man-facepalming","woman-facepalming","shrug","man-shrugging","woman-shrugging","health_worker","male-doctor","female-doctor","student","male-student","female-student","teacher","male-teacher","female-teacher","judge","male-judge","female-judge","farmer","male-farmer","female-farmer","cook","male-cook","female-cook","mechanic","male-mechanic","female-mechanic","factory_worker","male-factory-worker","female-factory-worker","office_worker","male-office-worker","female-office-worker","scientist","male-scientist","female-scientist","technologist","male-technologist","female-technologist","singer","male-singer","female-singer","artist","male-artist","female-artist","pilot","male-pilot","female-pilot","astronaut","male-astronaut","female-astronaut","firefighter","male-firefighter","female-firefighter","cop","male-police-officer","female-police-officer","sleuth_or_spy","male-detective","female-detective","guardsman","male-guard","female-guard","ninja","construction_worker","male-construction-worker","female-construction-worker","person_with_crown","prince","princess","man_with_turban","man-wearing-turban","woman-wearing-turban","man_with_gua_pi_mao","person_with_headscarf","person_in_tuxedo","man_in_tuxedo","woman_in_tuxedo","bride_with_veil","man_with_veil","woman_with_veil","pregnant_woman","pregnant_man","pregnant_person","breast-feeding","woman_feeding_baby","man_feeding_baby","person_feeding_baby","angel","santa","mrs_claus","mx_claus","superhero","male_superhero","female_superhero","supervillain","male_supervillain","female_supervillain","mage","male_mage","female_mage","fairy","male_fairy","female_fairy","vampire","male_vampire","female_vampire","merperson","merman","mermaid","elf","male_elf","female_elf","genie","male_genie","female_genie","zombie","male_zombie","female_zombie","troll","massage","man-getting-massage","woman-getting-massage","haircut","man-getting-haircut","woman-getting-haircut","walking","man-walking","woman-walking","person_walking_facing_right","woman_walking_facing_right","man_walking_facing_right","standing_person","man_standing","woman_standing","kneeling_person","man_kneeling","woman_kneeling","person_kneeling_facing_right","woman_kneeling_facing_right","man_kneeling_facing_right","person_with_probing_cane","person_with_white_cane_facing_right","man_with_probing_cane","man_with_white_cane_facing_right","woman_with_probing_cane","woman_with_white_cane_facing_right","person_in_motorized_wheelchair","person_in_motorized_wheelchair_facing_right","man_in_motorized_wheelchair","man_in_motorized_wheelchair_facing_right","woman_in_motorized_wheelchair","woman_in_motorized_wheelchair_facing_right","person_in_manual_wheelchair","person_in_manual_wheelchair_facing_right","man_in_manual_wheelchair","man_in_manual_wheelchair_facing_right","woman_in_manual_wheelchair","woman_in_manual_wheelchair_facing_right","runner","man-running","woman-running","person_running_facing_right","woman_running_facing_right","man_running_facing_right","dancer","man_dancing","man_in_business_suit_levitating","dancers","men-with-bunny-ears-partying","women-with-bunny-ears-partying","person_in_steamy_room","man_in_steamy_room","woman_in_steamy_room","person_climbing","man_climbing","woman_climbing","fencer","horse_racing","skier","snowboarder","golfer","man-golfing","woman-golfing","surfer","man-surfing","woman-surfing","rowboat","man-rowing-boat","woman-rowing-boat","swimmer","man-swimming","woman-swimming","person_with_ball","man-bouncing-ball","woman-bouncing-ball","weight_lifter","man-lifting-weights","woman-lifting-weights","bicyclist","man-biking","woman-biking","mountain_bicyclist","man-mountain-biking","woman-mountain-biking","person_doing_cartwheel","man-cartwheeling","woman-cartwheeling","wrestlers","man-wrestling","woman-wrestling","water_polo","man-playing-water-polo","woman-playing-water-polo","handball","man-playing-handball","woman-playing-handball","juggling","man-juggling","woman-juggling","person_in_lotus_position","man_in_lotus_position","woman_in_lotus_position","bath","sleeping_accommodation","people_holding_hands","two_women_holding_hands","man_and_woman_holding_hands","two_men_holding_hands","couplekiss","woman-kiss-man","man-kiss-man","woman-kiss-woman","couple_with_heart","woman-heart-man","man-heart-man","woman-heart-woman","man-woman-boy","man-woman-girl","man-woman-girl-boy","man-woman-boy-boy","man-woman-girl-girl","man-man-boy","man-man-girl","man-man-girl-boy","man-man-boy-boy","man-man-girl-girl","woman-woman-boy","woman-woman-girl","woman-woman-girl-boy","woman-woman-boy-boy","woman-woman-girl-girl","man-boy","man-boy-boy","man-girl","man-girl-boy","man-girl-girl","woman-boy","woman-boy-boy","woman-girl","woman-girl-boy","woman-girl-girl","speaking_head_in_silhouette","bust_in_silhouette","busts_in_silhouette","people_hugging","family","family_adult_adult_child","family_adult_adult_child_child","family_adult_child","family_adult_child_child","footprints","love_letter","cupid","gift_heart","sparkling_heart","heartpulse","heartbeat","revolving_hearts","two_hearts","heart_decoration","heavy_heart_exclamation_mark_ornament","broken_heart","heart_on_fire","mending_heart","heart","pink_heart","orange_heart","yellow_heart","green_heart","blue_heart","light_blue_heart","purple_heart","brown_heart","black_heart","grey_heart","white_heart","kiss","100","anger","boom","dizzy","sweat_drops","dash","hole","speech_balloon","eye-in-speech-bubble","left_speech_bubble","right_anger_bubble","thought_balloon","zzz"]},{"id":"nature","name":"Animals & Nature","emojis":["monkey_face","monkey","gorilla","orangutan","dog","dog2","guide_dog","service_dog","poodle","wolf","fox_face","raccoon","cat","cat2","black_cat","lion_face","tiger","tiger2","leopard","horse","moose","donkey","racehorse","unicorn_face","zebra_face","deer","bison","cow","ox","water_buffalo","cow2","pig","pig2","boar","pig_nose","ram","sheep","goat","dromedary_camel","camel","llama","giraffe_face","elephant","mammoth","rhinoceros","hippopotamus","mouse","mouse2","rat","hamster","rabbit","rabbit2","chipmunk","beaver","hedgehog","bat","bear","polar_bear","koala","panda_face","sloth","otter","skunk","kangaroo","badger","feet","turkey","chicken","rooster","hatching_chick","baby_chick","hatched_chick","bird","penguin","dove_of_peace","eagle","duck","swan","owl","dodo","feather","flamingo","peacock","parrot","wing","black_bird","goose","phoenix","frog","crocodile","turtle","lizard","snake","dragon_face","dragon","sauropod","t-rex","whale","whale2","dolphin","seal","fish","tropical_fish","blowfish","shark","octopus","shell","coral","jellyfish","snail","butterfly","bug","ant","bee","beetle","ladybug","cricket","cockroach","spider","spider_web","scorpion","mosquito","fly","worm","microbe","bouquet","cherry_blossom","white_flower","lotus","rosette","rose","wilted_flower","hibiscus","sunflower","blossom","tulip","hyacinth","seedling","potted_plant","evergreen_tree","deciduous_tree","palm_tree","cactus","ear_of_rice","herb","shamrock","four_leaf_clover","maple_leaf","fallen_leaf","leaves","empty_nest","nest_with_eggs","mushroom"]},{"id":"foods","name":"Food & Drink","emojis":["grapes","melon","watermelon","tangerine","lemon","lime","banana","pineapple","mango","apple","green_apple","pear","peach","cherries","strawberry","blueberries","kiwifruit","tomato","olive","coconut","avocado","eggplant","potato","carrot","corn","hot_pepper","bell_pepper","cucumber","leafy_green","broccoli","garlic","onion","peanuts","beans","chestnut","ginger_root","pea_pod","brown_mushroom","bread","croissant","baguette_bread","flatbread","pretzel","bagel","pancakes","waffle","cheese_wedge","meat_on_bone","poultry_leg","cut_of_meat","bacon","hamburger","fries","pizza","hotdog","sandwich","taco","burrito","tamale","stuffed_flatbread","falafel","egg","fried_egg","shallow_pan_of_food","stew","fondue","bowl_with_spoon","green_salad","popcorn","butter","salt","canned_food","bento","rice_cracker","rice_ball","rice","curry","ramen","spaghetti","sweet_potato","oden","sushi","fried_shrimp","fish_cake","moon_cake","dango","dumpling","fortune_cookie","takeout_box","crab","lobster","shrimp","squid","oyster","icecream","shaved_ice","ice_cream","doughnut","cookie","birthday","cake","cupcake","pie","chocolate_bar","candy","lollipop","custard","honey_pot","baby_bottle","glass_of_milk","coffee","teapot","tea","sake","champagne","wine_glass","cocktail","tropical_drink","beer","beers","clinking_glasses","tumbler_glass","pouring_liquid","cup_with_straw","bubble_tea","beverage_box","mate_drink","ice_cube","chopsticks","knife_fork_plate","fork_and_knife","spoon","hocho","jar","amphora"]},{"id":"activity","name":"Activities","emojis":["jack_o_lantern","christmas_tree","fireworks","sparkler","firecracker","sparkles","balloon","tada","confetti_ball","tanabata_tree","bamboo","dolls","flags","wind_chime","rice_scene","red_envelope","ribbon","gift","reminder_ribbon","admission_tickets","ticket","medal","trophy","sports_medal","first_place_medal","second_place_medal","third_place_medal","soccer","baseball","softball","basketball","volleyball","football","rugby_football","tennis","flying_disc","bowling","cricket_bat_and_ball","field_hockey_stick_and_ball","ice_hockey_stick_and_puck","lacrosse","table_tennis_paddle_and_ball","badminton_racquet_and_shuttlecock","boxing_glove","martial_arts_uniform","goal_net","golf","ice_skate","fishing_pole_and_fish","diving_mask","running_shirt_with_sash","ski","sled","curling_stone","dart","yo-yo","kite","gun","8ball","crystal_ball","magic_wand","video_game","joystick","slot_machine","game_die","jigsaw","teddy_bear","pinata","mirror_ball","nesting_dolls","spades","hearts","diamonds","clubs","chess_pawn","black_joker","mahjong","flower_playing_cards","performing_arts","frame_with_picture","art","thread","sewing_needle","yarn","knot"]},{"id":"places","name":"Travel & Places","emojis":["earth_africa","earth_americas","earth_asia","globe_with_meridians","world_map","japan","compass","snow_capped_mountain","mountain","volcano","mount_fuji","camping","beach_with_umbrella","desert","desert_island","national_park","stadium","classical_building","building_construction","bricks","rock","wood","hut","house_buildings","derelict_house_building","house","house_with_garden","office","post_office","european_post_office","hospital","bank","hotel","love_hotel","convenience_store","school","department_store","factory","japanese_castle","european_castle","wedding","tokyo_tower","statue_of_liberty","church","mosque","hindu_temple","synagogue","shinto_shrine","kaaba","fountain","tent","foggy","night_with_stars","cityscape","sunrise_over_mountains","sunrise","city_sunset","city_sunrise","bridge_at_night","hotsprings","carousel_horse","playground_slide","ferris_wheel","roller_coaster","barber","circus_tent","steam_locomotive","railway_car","bullettrain_side","bullettrain_front","train2","metro","light_rail","station","tram","monorail","mountain_railway","train","bus","oncoming_bus","trolleybus","minibus","ambulance","fire_engine","police_car","oncoming_police_car","taxi","oncoming_taxi","car","oncoming_automobile","blue_car","pickup_truck","truck","articulated_lorry","tractor","racing_car","racing_motorcycle","motor_scooter","manual_wheelchair","motorized_wheelchair","auto_rickshaw","bike","scooter","skateboard","roller_skate","busstop","motorway","railway_track","oil_drum","fuelpump","wheel","rotating_light","traffic_light","vertical_traffic_light","octagonal_sign","construction","anchor","ring_buoy","boat","canoe","speedboat","passenger_ship","ferry","motor_boat","ship","airplane","small_airplane","airplane_departure","airplane_arriving","parachute","seat","helicopter","suspension_railway","mountain_cableway","aerial_tramway","satellite","rocket","flying_saucer","bellhop_bell","luggage","hourglass","hourglass_flowing_sand","watch","alarm_clock","stopwatch","timer_clock","mantelpiece_clock","clock12","clock1230","clock1","clock130","clock2","clock230","clock3","clock330","clock4","clock430","clock5","clock530","clock6","clock630","clock7","clock730","clock8","clock830","clock9","clock930","clock10","clock1030","clock11","clock1130","new_moon","waxing_crescent_moon","first_quarter_moon","moon","full_moon","waning_gibbous_moon","last_quarter_moon","waning_crescent_moon","crescent_moon","new_moon_with_face","first_quarter_moon_with_face","last_quarter_moon_with_face","thermometer","sunny","full_moon_with_face","sun_with_face","ringed_planet","star","star2","stars","milky_way","cloud","partly_sunny","thunder_cloud_and_rain","mostly_sunny","barely_sunny","partly_sunny_rain","rain_cloud","snow_cloud","lightning","tornado","fog","wind_blowing_face","cyclone","rainbow","closed_umbrella","umbrella","umbrella_with_rain_drops","umbrella_on_ground","zap","snowflake","snowman","snowman_without_snow","comet","fire","droplet","ocean"]},{"id":"objects","name":"Objects","emojis":["eyeglasses","dark_sunglasses","goggles","lab_coat","safety_vest","necktie","shirt","jeans","scarf","gloves","coat","socks","dress","kimono","sari","one-piece_swimsuit","briefs","shorts","bikini","womans_clothes","folding_hand_fan","purse","handbag","pouch","shopping_bags","school_satchel","thong_sandal","mans_shoe","athletic_shoe","hiking_boot","womans_flat_shoe","high_heel","sandal","ballet_shoes","boot","hair_pick","crown","womans_hat","tophat","mortar_board","billed_cap","military_helmet","helmet_with_white_cross","prayer_beads","lipstick","ring","gem","mute","speaker","sound","loud_sound","loudspeaker","mega","postal_horn","bell","no_bell","musical_score","musical_note","notes","studio_microphone","level_slider","control_knobs","microphone","headphones","radio","saxophone","accordion","guitar","musical_keyboard","trumpet","violin","banjo","drum_with_drumsticks","long_drum","maracas","flute","iphone","calling","phone","telephone_receiver","pager","fax","battery","low_battery","electric_plug","computer","desktop_computer","printer","keyboard","three_button_mouse","trackball","minidisc","floppy_disk","cd","dvd","abacus","movie_camera","film_frames","film_projector","clapper","tv","camera","camera_with_flash","video_camera","vhs","mag","mag_right","candle","bulb","flashlight","izakaya_lantern","diya_lamp","notebook_with_decorative_cover","closed_book","book","green_book","blue_book","orange_book","books","notebook","ledger","page_with_curl","scroll","page_facing_up","newspaper","rolled_up_newspaper","bookmark_tabs","bookmark","label","moneybag","coin","yen","dollar","euro","pound","money_with_wings","credit_card","receipt","chart","email","e-mail","incoming_envelope","envelope_with_arrow","outbox_tray","inbox_tray","package","mailbox","mailbox_closed","mailbox_with_mail","mailbox_with_no_mail","postbox","ballot_box_with_ballot","pencil2","black_nib","lower_left_fountain_pen","lower_left_ballpoint_pen","lower_left_paintbrush","lower_left_crayon","memo","briefcase","file_folder","open_file_folder","card_index_dividers","date","calendar","spiral_note_pad","spiral_calendar_pad","card_index","chart_with_upwards_trend","chart_with_downwards_trend","bar_chart","clipboard","pushpin","round_pushpin","paperclip","linked_paperclips","straight_ruler","triangular_ruler","scissors","card_file_box","file_cabinet","wastebasket","lock","unlock","lock_with_ink_pen","closed_lock_with_key","key","old_key","hammer","axe","pick","hammer_and_pick","hammer_and_wrench","dagger_knife","crossed_swords","bomb","boomerang","bow_and_arrow","shield","carpentry_saw","wrench","screwdriver","nut_and_bolt","gear","compression","scales","probing_cane","link","broken_chain","chains","hook","toolbox","magnet","ladder","alembic","test_tube","petri_dish","dna","microscope","telescope","satellite_antenna","syringe","drop_of_blood","pill","adhesive_bandage","crutch","stethoscope","x-ray","door","elevator","mirror","window","bed","couch_and_lamp","chair","toilet","plunger","shower","bathtub","mouse_trap","razor","lotion_bottle","safety_pin","broom","basket","roll_of_paper","bucket","soap","bubbles","toothbrush","sponge","fire_extinguisher","shopping_trolley","smoking","coffin","headstone","funeral_urn","nazar_amulet","hamsa","moyai","placard","identification_card"]},{"id":"symbols","name":"Symbols","emojis":["atm","put_litter_in_its_place","potable_water","wheelchair","mens","womens","restroom","baby_symbol","wc","passport_control","customs","baggage_claim","left_luggage","warning","children_crossing","no_entry","no_entry_sign","no_bicycles","no_smoking","do_not_litter","non-potable_water","no_pedestrians","no_mobile_phones","underage","radioactive_sign","biohazard_sign","arrow_up","arrow_upper_right","arrow_right","arrow_lower_right","arrow_down","arrow_lower_left","arrow_left","arrow_upper_left","arrow_up_down","left_right_arrow","leftwards_arrow_with_hook","arrow_right_hook","arrow_heading_up","arrow_heading_down","arrows_clockwise","arrows_counterclockwise","back","end","on","soon","top","place_of_worship","atom_symbol","om_symbol","star_of_david","wheel_of_dharma","yin_yang","latin_cross","orthodox_cross","star_and_crescent","peace_symbol","menorah_with_nine_branches","six_pointed_star","khanda","aries","taurus","gemini","cancer","leo","virgo","libra","scorpius","sagittarius","capricorn","aquarius","pisces","ophiuchus","twisted_rightwards_arrows","repeat","repeat_one","arrow_forward","fast_forward","black_right_pointing_double_triangle_with_vertical_bar","black_right_pointing_triangle_with_double_vertical_bar","arrow_backward","rewind","black_left_pointing_double_triangle_with_vertical_bar","arrow_up_small","arrow_double_up","arrow_down_small","arrow_double_down","double_vertical_bar","black_square_for_stop","black_circle_for_record","eject","cinema","low_brightness","high_brightness","signal_strength","wireless","vibration_mode","mobile_phone_off","female_sign","male_sign","transgender_symbol","heavy_multiplication_x","heavy_plus_sign","heavy_minus_sign","heavy_division_sign","heavy_equals_sign","infinity","bangbang","interrobang","question","grey_question","grey_exclamation","exclamation","wavy_dash","currency_exchange","heavy_dollar_sign","medical_symbol","recycle","fleur_de_lis","trident","name_badge","beginner","o","white_check_mark","ballot_box_with_check","heavy_check_mark","x","negative_squared_cross_mark","curly_loop","loop","part_alternation_mark","eight_spoked_asterisk","eight_pointed_black_star","sparkle","copyright","registered","tm","hash","keycap_star","zero","one","two","three","four","five","six","seven","eight","nine","keycap_ten","capital_abcd","abcd","1234","symbols","abc","a","ab","b","cl","cool","free","information_source","id","m","new","ng","o2","ok","parking","sos","up","vs","koko","sa","u6708","u6709","u6307","ideograph_advantage","u5272","u7121","u7981","accept","u7533","u5408","u7a7a","congratulations","secret","u55b6","u6e80","red_circle","large_orange_circle","large_yellow_circle","large_green_circle","large_blue_circle","large_purple_circle","large_brown_circle","black_circle","white_circle","large_red_square","large_orange_square","large_yellow_square","large_green_square","large_blue_square","large_purple_square","large_brown_square","black_large_square","white_large_square","black_medium_square","white_medium_square","black_medium_small_square","white_medium_small_square","black_small_square","white_small_square","large_orange_diamond","large_blue_diamond","small_orange_diamond","small_blue_diamond","small_red_triangle","small_red_triangle_down","diamond_shape_with_a_dot_inside","radio_button","white_square_button","black_square_button"]},{"id":"flags","name":"Flags","emojis":["checkered_flag","triangular_flag_on_post","crossed_flags","waving_black_flag","waving_white_flag","rainbow-flag","transgender_flag","pirate_flag","flag-ac","flag-ad","flag-ae","flag-af","flag-ag","flag-ai","flag-al","flag-am","flag-ao","flag-aq","flag-ar","flag-as","flag-at","flag-au","flag-aw","flag-ax","flag-az","flag-ba","flag-bb","flag-bd","flag-be","flag-bf","flag-bg","flag-bh","flag-bi","flag-bj","flag-bl","flag-bm","flag-bn","flag-bo","flag-bq","flag-br","flag-bs","flag-bt","flag-bv","flag-bw","flag-by","flag-bz","flag-ca","flag-cc","flag-cd","flag-cf","flag-cg","flag-ch","flag-ci","flag-ck","flag-cl","flag-cm","cn","flag-co","flag-cp","flag-cr","flag-cu","flag-cv","flag-cw","flag-cx","flag-cy","flag-cz","de","flag-dg","flag-dj","flag-dk","flag-dm","flag-do","flag-dz","flag-ea","flag-ec","flag-ee","flag-eg","flag-eh","flag-er","es","flag-et","flag-eu","flag-fi","flag-fj","flag-fk","flag-fm","flag-fo","fr","flag-ga","gb","flag-gd","flag-ge","flag-gf","flag-gg","flag-gh","flag-gi","flag-gl","flag-gm","flag-gn","flag-gp","flag-gq","flag-gr","flag-gs","flag-gt","flag-gu","flag-gw","flag-gy","flag-hk","flag-hm","flag-hn","flag-hr","flag-ht","flag-hu","flag-ic","flag-id","flag-ie","flag-il","flag-im","flag-in","flag-io","flag-iq","flag-ir","flag-is","it","flag-je","flag-jm","flag-jo","jp","flag-ke","flag-kg","flag-kh","flag-ki","flag-km","flag-kn","flag-kp","kr","flag-kw","flag-ky","flag-kz","flag-la","flag-lb","flag-lc","flag-li","flag-lk","flag-lr","flag-ls","flag-lt","flag-lu","flag-lv","flag-ly","flag-ma","flag-mc","flag-md","flag-me","flag-mf","flag-mg","flag-mh","flag-mk","flag-ml","flag-mm","flag-mn","flag-mo","flag-mp","flag-mq","flag-mr","flag-ms","flag-mt","flag-mu","flag-mv","flag-mw","flag-mx","flag-my","flag-mz","flag-na","flag-nc","flag-ne","flag-nf","flag-ng","flag-ni","flag-nl","flag-no","flag-np","flag-nr","flag-nu","flag-nz","flag-om","flag-pa","flag-pe","flag-pf","flag-pg","flag-ph","flag-pk","flag-pl","flag-pm","flag-pn","flag-pr","flag-ps","flag-pt","flag-pw","flag-py","flag-qa","flag-re","flag-ro","flag-rs","ru","flag-rw","flag-sa","flag-sb","flag-sc","flag-sd","flag-se","flag-sg","flag-sh","flag-si","flag-sj","flag-sk","flag-sl","flag-sm","flag-sn","flag-so","flag-sr","flag-ss","flag-st","flag-sv","flag-sx","flag-sy","flag-sz","flag-ta","flag-tc","flag-td","flag-tf","flag-tg","flag-th","flag-tj","flag-tk","flag-tl","flag-tm","flag-tn","flag-to","flag-tr","flag-tt","flag-tv","flag-tw","flag-tz","flag-ua","flag-ug","flag-um","flag-un","us","flag-uy","flag-uz","flag-va","flag-vc","flag-ve","flag-vg","flag-vi","flag-vn","flag-vu","flag-wf","flag-ws","flag-xk","flag-ye","flag-yt","flag-za","flag-zm","flag-zw","flag-england","flag-scotland","flag-wales"]}],"emojis":{"grinning":{"a":"Grinning Face","b":"1F600","f":true,"k":[32,46],"j":["grinning_face","face","smile","happy","joy",":D","grin"],"m":":D"},"smiley":{"a":"Smiling Face with Open Mouth","b":"1F603","f":true,"k":[32,49],"j":["grinning_face_with_big_eyes","face","happy","joy","haha",":D",":)","smile","funny"],"l":["=)","=-)"],"m":":)"},"smile":{"a":"Smiling Face with Open Mouth and Smiling Eyes","b":"1F604","f":true,"k":[32,50],"j":["grinning_face_with_smiling_eyes","face","happy","joy","funny","haha","laugh","like",":D",":)"],"l":["C:","c:",":D",":-D"],"m":":)"},"grin":{"a":"Grinning Face with Smiling Eyes","b":"1F601","f":true,"k":[32,47],"j":["beaming_face_with_smiling_eyes","face","happy","smile","joy","kawaii"]},"laughing":{"a":"Smiling Face with Open Mouth and Tightly-Closed Eyes","b":"1F606","f":true,"k":[32,52],"j":["grinning_squinting_face","happy","joy","lol","satisfied","haha","face","glad","XD","laugh"],"l":[":>",":->"]},"sweat_smile":{"a":"Smiling Face with Open Mouth and Cold Sweat","b":"1F605","f":true,"k":[32,51],"j":["grinning_face_with_sweat","face","hot","happy","laugh","sweat","smile","relief"]},"rolling_on_the_floor_laughing":{"a":"Rolling On the Floor Laughing","b":"1F923","f":true,"k":[40,54],"j":["face","rolling","floor","laughing","lol","haha","rofl"]},"joy":{"a":"Face with Tears Of Joy","b":"1F602","f":true,"k":[32,48],"j":["face_with_tears_of_joy","face","cry","tears","weep","happy","happytears","haha"]},"slightly_smiling_face":{"a":"Slightly Smiling Face","b":"1F642","f":true,"k":[33,55],"j":["face","smile"],"l":[":)","(:",":-)"]},"upside_down_face":{"a":"Upside-Down Face","b":"1F643","f":true,"k":[33,56],"j":["face","flipped","silly","smile"]},"melting_face":{"a":"Melting Face","b":"1FAE0","f":true,"k":[56,30],"j":["melting face","hot","heat"]},"wink":{"a":"Winking Face","b":"1F609","f":true,"k":[32,55],"j":["winking_face","face","happy","mischievous","secret",";)","smile","eye"],"l":[";)",";-)"],"m":";)"},"blush":{"a":"Smiling Face with Smiling Eyes","b":"1F60A","f":true,"k":[32,56],"j":["smiling_face_with_smiling_eyes","face","smile","happy","flushed","crush","embarrassed","shy","joy"],"m":":)"},"innocent":{"a":"Smiling Face with Halo","b":"1F607","f":true,"k":[32,53],"j":["smiling_face_with_halo","face","angel","heaven","halo"]},"smiling_face_with_3_hearts":{"a":"Smiling Face with Smiling Eyes and Three Hearts","b":"1F970","f":true,"k":[44,32],"j":["smiling_face_with_hearts","face","love","like","affection","valentines","infatuation","crush","hearts","adore"]},"heart_eyes":{"a":"Smiling Face with Heart-Shaped Eyes","b":"1F60D","f":true,"k":[32,59],"j":["smiling_face_with_heart_eyes","face","love","like","affection","valentines","infatuation","crush","heart"]},"star-struck":{"a":"Grinning Face with Star Eyes","b":"1F929","f":true,"k":[41,15],"j":["star_struck","face","smile","starry","eyes","grinning"]},"kissing_heart":{"a":"Face Throwing a Kiss","b":"1F618","f":true,"k":[33,8],"j":["face_blowing_a_kiss","face","love","like","affection","valentines","infatuation","kiss"],"l":[":*",":-*"]},"kissing":{"a":"Kissing Face","b":"1F617","f":true,"k":[33,7],"j":["kissing_face","love","like","face","3","valentines","infatuation","kiss"]},"relaxed":{"a":"White Smiling Face","b":"263A-FE0F","f":true,"k":[58,33],"c":"263A"},"kissing_closed_eyes":{"a":"Kissing Face with Closed Eyes","b":"1F61A","f":true,"k":[33,10],"j":["kissing_face_with_closed_eyes","face","love","like","affection","valentines","infatuation","kiss"]},"kissing_smiling_eyes":{"a":"Kissing Face with Smiling Eyes","b":"1F619","f":true,"k":[33,9],"j":["kissing_face_with_smiling_eyes","face","affection","valentines","infatuation","kiss"]},"smiling_face_with_tear":{"a":"Smiling Face with Tear","b":"1F972","f":true,"k":[44,34],"j":["smiling face with tear","sad","cry","pretend"]},"yum":{"a":"Face Savouring Delicious Food","b":"1F60B","f":true,"k":[32,57],"j":["face_savoring_food","happy","joy","tongue","smile","face","silly","yummy","nom","delicious","savouring"]},"stuck_out_tongue":{"a":"Face with Stuck-Out Tongue","b":"1F61B","f":true,"k":[33,11],"j":["face_with_tongue","face","prank","childish","playful","mischievous","smile","tongue"],"l":[":p",":-p",":P",":-P",":b",":-b"],"m":":p"},"stuck_out_tongue_winking_eye":{"a":"Face with Stuck-Out Tongue and Winking Eye","b":"1F61C","f":true,"k":[33,12],"j":["winking_face_with_tongue","face","prank","childish","playful","mischievous","smile","wink","tongue"],"l":[";p",";-p",";b",";-b",";P",";-P"],"m":";p"},"zany_face":{"a":"Grinning Face with One Large and One Small Eye","b":"1F92A","f":true,"k":[41,16],"j":["face","goofy","crazy"]},"stuck_out_tongue_closed_eyes":{"a":"Face with Stuck-Out Tongue and Tightly-Closed Eyes","b":"1F61D","f":true,"k":[33,13],"j":["squinting_face_with_tongue","face","prank","playful","mischievous","smile","tongue"]},"money_mouth_face":{"a":"Money-Mouth Face","b":"1F911","f":true,"k":[39,38],"j":["face","rich","dollar","money"]},"hugging_face":{"a":"Hugging Face","b":"1F917","f":true,"k":[39,44],"j":["face","smile","hug"]},"face_with_hand_over_mouth":{"a":"Smiling Face with Smiling Eyes and Hand Covering Mouth","b":"1F92D","f":true,"k":[41,19],"j":["face","whoops","shock","surprise"]},"face_with_open_eyes_and_hand_over_mouth":{"a":"Face with Open Eyes and Hand Over Mouth","b":"1FAE2","f":true,"k":[56,32],"j":["face with open eyes and hand over mouth","silence","secret","shock","surprise"]},"face_with_peeking_eye":{"a":"Face with Peeking Eye","b":"1FAE3","f":true,"k":[56,33],"j":["face with peeking eye","scared","frightening","embarrassing","shy"]},"shushing_face":{"a":"Face with Finger Covering Closed Lips","b":"1F92B","f":true,"k":[41,17],"j":["face","quiet","shhh"]},"thinking_face":{"a":"Thinking Face","b":"1F914","f":true,"k":[39,41],"j":["face","hmmm","think","consider"]},"saluting_face":{"a":"Saluting Face","b":"1FAE1","f":true,"k":[56,31],"j":["saluting face","respect","salute"]},"zipper_mouth_face":{"a":"Zipper-Mouth Face","b":"1F910","f":true,"k":[39,37],"j":["face","sealed","zipper","secret"]},"face_with_raised_eyebrow":{"a":"Face with One Eyebrow Raised","b":"1F928","f":true,"k":[41,14],"j":["face","distrust","scepticism","disapproval","disbelief","surprise","suspicious"]},"neutral_face":{"a":"Neutral Face","b":"1F610","f":true,"k":[33,0],"j":["indifference","meh",":|","neutral"],"l":[":|",":-|"]},"expressionless":{"a":"Expressionless Face","b":"1F611","f":true,"k":[33,1],"j":["expressionless_face","face","indifferent","-_-","meh","deadpan"]},"no_mouth":{"a":"Face Without Mouth","b":"1F636","f":true,"k":[33,41],"j":["face_without_mouth","face"]},"dotted_line_face":{"a":"Dotted Line Face","b":"1FAE5","f":true,"k":[56,35],"j":["dotted line face","invisible","lonely","isolation","depression"]},"face_in_clouds":{"a":"Face In Clouds","b":"1F636-200D-1F32B-FE0F","f":true,"k":[33,40],"c":"1F636-200D-1F32B","j":["face_without_mouth","face"]},"smirk":{"a":"Smirking Face","b":"1F60F","f":true,"k":[32,61],"j":["smirking_face","face","smile","mean","prank","smug","sarcasm"]},"unamused":{"a":"Unamused Face","b":"1F612","f":true,"k":[33,2],"j":["unamused_face","indifference","bored","straight face","serious","sarcasm","unimpressed","skeptical","dubious","ugh","side_eye"],"m":":("},"face_with_rolling_eyes":{"a":"Face with Rolling Eyes","b":"1F644","f":true,"k":[33,57],"j":["face","eyeroll","frustrated"]},"grimacing":{"a":"Grimacing Face","b":"1F62C","f":true,"k":[33,28],"j":["grimacing_face","face","grimace","teeth"]},"face_exhaling":{"a":"Face Exhaling","b":"1F62E-200D-1F4A8","f":true,"k":[33,30],"j":["face_with_open_mouth","face","surprise","impressed","wow","whoa",":O"]},"lying_face":{"a":"Lying Face","b":"1F925","f":true,"k":[40,56],"j":["face","lie","pinocchio"]},"shaking_face":{"a":"Shaking Face","b":"1FAE8","f":true,"k":[56,38],"j":["shaking face","dizzy","shock","blurry","earthquake"]},"head_shaking_horizontally":{"a":"Head Shaking Horizontally","b":"1F642-200D-2194-FE0F","f":true,"k":[33,53],"c":"1F642-200D-2194","j":["slightly_smiling_face","face","smile"]},"head_shaking_vertically":{"a":"Head Shaking Vertically","b":"1F642-200D-2195-FE0F","f":true,"k":[33,54],"c":"1F642-200D-2195","j":["slightly_smiling_face","face","smile"]},"relieved":{"a":"Relieved Face","b":"1F60C","f":true,"k":[32,58],"j":["relieved_face","face","relaxed","phew","massage","happiness"]},"pensive":{"a":"Pensive Face","b":"1F614","f":true,"k":[33,4],"j":["pensive_face","face","sad","depressed","upset"]},"sleepy":{"a":"Sleepy Face","b":"1F62A","f":true,"k":[33,26],"j":["sleepy_face","face","tired","rest","nap"]},"drooling_face":{"a":"Drooling Face","b":"1F924","f":true,"k":[40,55],"j":["face"]},"sleeping":{"a":"Sleeping Face","b":"1F634","f":true,"k":[33,37],"j":["sleeping_face","face","tired","sleepy","night","zzz"]},"mask":{"a":"Face with Medical Mask","b":"1F637","f":true,"k":[33,42],"j":["face_with_medical_mask","face","sick","ill","disease","covid"]},"face_with_thermometer":{"a":"Face with Thermometer","b":"1F912","f":true,"k":[39,39],"j":["sick","temperature","thermometer","cold","fever","covid"]},"face_with_head_bandage":{"a":"Face with Head-Bandage","b":"1F915","f":true,"k":[39,42],"j":["injured","clumsy","bandage","hurt"]},"nauseated_face":{"a":"Nauseated Face","b":"1F922","f":true,"k":[40,53],"j":["face","vomit","gross","green","sick","throw up","ill"]},"face_vomiting":{"a":"Face with Open Mouth Vomiting","b":"1F92E","f":true,"k":[41,20],"j":["face","sick"]},"sneezing_face":{"a":"Sneezing Face","b":"1F927","f":true,"k":[41,13],"j":["face","gesundheit","sneeze","sick","allergy"]},"hot_face":{"a":"Overheated Face","b":"1F975","f":true,"k":[44,37],"j":["face","feverish","heat","red","sweating"]},"cold_face":{"a":"Freezing Face","b":"1F976","f":true,"k":[44,38],"j":["face","blue","freezing","frozen","frostbite","icicles"]},"woozy_face":{"a":"Face with Uneven Eyes and Wavy Mouth","b":"1F974","f":true,"k":[44,36],"j":["face","dizzy","intoxicated","tipsy","wavy"]},"dizzy_face":{"a":"Dizzy Face","b":"1F635","f":true,"k":[33,39],"j":["spent","unconscious","xox","dizzy"]},"face_with_spiral_eyes":{"a":"Face with Spiral Eyes","b":"1F635-200D-1F4AB","f":true,"k":[33,38],"j":["dizzy_face","spent","unconscious","xox","dizzy"]},"exploding_head":{"a":"Shocked Face with Exploding Head","b":"1F92F","f":true,"k":[41,21],"j":["face","shocked","mind","blown"]},"face_with_cowboy_hat":{"a":"Face with Cowboy Hat","b":"1F920","f":true,"k":[40,51],"j":["cowboy_hat_face","face","cowgirl","hat"]},"partying_face":{"a":"Face with Party Horn and Party Hat","b":"1F973","f":true,"k":[44,35],"j":["face","celebration","woohoo"]},"disguised_face":{"a":"Disguised Face","b":"1F978","f":true,"k":[44,45],"j":["disguised face","pretent","brows","glasses","moustache"]},"sunglasses":{"a":"Smiling Face with Sunglasses","b":"1F60E","f":true,"k":[32,60],"j":["smiling_face_with_sunglasses","face","cool","smile","summer","beach","sunglass"],"l":["8)"]},"nerd_face":{"a":"Nerd Face","b":"1F913","f":true,"k":[39,40],"j":["face","nerdy","geek","dork"]},"face_with_monocle":{"a":"Face with Monocle","b":"1F9D0","f":true,"k":[47,61],"j":["face","stuffy","wealthy"]},"confused":{"a":"Confused Face","b":"1F615","f":true,"k":[33,5],"j":["confused_face","face","indifference","huh","weird","hmmm",":/"],"l":[":\\",":-\\",":/",":-/"]},"face_with_diagonal_mouth":{"a":"Face with Diagonal Mouth","b":"1FAE4","f":true,"k":[56,34],"j":["face with diagonal mouth","skeptic","confuse","frustrated","indifferent"]},"worried":{"a":"Worried Face","b":"1F61F","f":true,"k":[33,15],"j":["worried_face","face","concern","nervous",":("]},"slightly_frowning_face":{"a":"Slightly Frowning Face","b":"1F641","f":true,"k":[33,52],"j":["face","frowning","disappointed","sad","upset"]},"white_frowning_face":{"a":"Frowning Face","b":"2639-FE0F","f":true,"k":[58,32],"c":"2639"},"open_mouth":{"a":"Face with Open Mouth","b":"1F62E","f":true,"k":[33,31],"j":["face_with_open_mouth","face","surprise","impressed","wow","whoa",":O"],"l":[":o",":-o",":O",":-O"]},"hushed":{"a":"Hushed Face","b":"1F62F","f":true,"k":[33,32],"j":["hushed_face","face","woo","shh"]},"astonished":{"a":"Astonished Face","b":"1F632","f":true,"k":[33,35],"j":["astonished_face","face","xox","surprised","poisoned"]},"flushed":{"a":"Flushed Face","b":"1F633","f":true,"k":[33,36],"j":["flushed_face","face","blush","shy","flattered"]},"pleading_face":{"a":"Face with Pleading Eyes","b":"1F97A","f":true,"k":[44,47],"j":["face","begging","mercy","cry","tears","sad","grievance"]},"face_holding_back_tears":{"a":"Face Holding Back Tears","b":"1F979","f":true,"k":[44,46],"j":["face holding back tears","touched","gratitude","cry"]},"frowning":{"a":"Frowning Face with Open Mouth","b":"1F626","f":true,"k":[33,22],"j":["frowning_face_with_open_mouth","face","aw","what"]},"anguished":{"a":"Anguished Face","b":"1F627","f":true,"k":[33,23],"j":["anguished_face","face","stunned","nervous"],"l":["D:"]},"fearful":{"a":"Fearful Face","b":"1F628","f":true,"k":[33,24],"j":["fearful_face","face","scared","terrified","nervous"]},"cold_sweat":{"a":"Face with Open Mouth and Cold Sweat","b":"1F630","f":true,"k":[33,33],"j":["anxious_face_with_sweat","face","nervous","sweat"]},"disappointed_relieved":{"a":"Disappointed But Relieved Face","b":"1F625","f":true,"k":[33,21],"j":["sad_but_relieved_face","face","phew","sweat","nervous"]},"cry":{"a":"Crying Face","b":"1F622","f":true,"k":[33,18],"j":["crying_face","face","tears","sad","depressed","upset",":'("],"l":[":'("],"m":":'("},"sob":{"a":"Loudly Crying Face","b":"1F62D","f":true,"k":[33,29],"j":["loudly_crying_face","sobbing","face","cry","tears","sad","upset","depressed"],"m":":'("},"scream":{"a":"Face Screaming In Fear","b":"1F631","f":true,"k":[33,34],"j":["face_screaming_in_fear","face","munch","scared","omg"]},"confounded":{"a":"Confounded Face","b":"1F616","f":true,"k":[33,6],"j":["confounded_face","face","confused","sick","unwell","oops",":S"]},"persevere":{"a":"Persevering Face","b":"1F623","f":true,"k":[33,19],"j":["persevering_face","face","sick","no","upset","oops"]},"disappointed":{"a":"Disappointed Face","b":"1F61E","f":true,"k":[33,14],"j":["disappointed_face","face","sad","upset","depressed",":("],"l":["):",":(",":-("],"m":":("},"sweat":{"a":"Face with Cold Sweat","b":"1F613","f":true,"k":[33,3],"j":["downcast_face_with_sweat","face","hot","sad","tired","exercise"]},"weary":{"a":"Weary Face","b":"1F629","f":true,"k":[33,25],"j":["weary_face","face","tired","sleepy","sad","frustrated","upset"]},"tired_face":{"a":"Tired Face","b":"1F62B","f":true,"k":[33,27],"j":["sick","whine","upset","frustrated"]},"yawning_face":{"a":"Yawning Face","b":"1F971","f":true,"k":[44,33],"j":["tired","sleepy"]},"triumph":{"a":"Face with Look Of Triumph","b":"1F624","f":true,"k":[33,20],"j":["face_with_steam_from_nose","face","gas","phew","proud","pride"]},"rage":{"a":"Pouting Face","b":"1F621","f":true,"k":[33,17],"j":["pouting_face","angry","mad","hate","despise"]},"angry":{"a":"Angry Face","b":"1F620","f":true,"k":[33,16],"j":["angry_face","mad","face","annoyed","frustrated"],"l":[">:(",">:-("]},"face_with_symbols_on_mouth":{"a":"Serious Face with Symbols Covering Mouth","b":"1F92C","f":true,"k":[41,18],"j":["face","swearing","cursing","cussing","profanity","expletive"]},"smiling_imp":{"a":"Smiling Face with Horns","b":"1F608","f":true,"k":[32,54],"j":["smiling_face_with_horns","devil","horns"]},"imp":{"a":"Imp","b":"1F47F","f":true,"k":[25,41],"j":["angry_face_with_horns","devil","angry","horns"]},"skull":{"a":"Skull","b":"1F480","f":true,"k":[25,42],"j":["dead","skeleton","creepy","death","dead"]},"skull_and_crossbones":{"a":"Skull and Crossbones","b":"2620-FE0F","f":true,"k":[58,24],"c":"2620"},"hankey":{"a":"Pile Of Poo","b":"1F4A9","f":true,"k":[28,25],"j":["pile_of_poo","shitface","fail","turd","shit"]},"clown_face":{"a":"Clown Face","b":"1F921","f":true,"k":[40,52],"j":["face"]},"japanese_ogre":{"a":"Japanese Ogre","b":"1F479","f":true,"k":[25,30],"j":["ogre","monster","red","mask","halloween","scary","creepy","devil","demon"]},"japanese_goblin":{"a":"Japanese Goblin","b":"1F47A","f":true,"k":[25,31],"j":["goblin","red","evil","mask","monster","scary","creepy"]},"ghost":{"a":"Ghost","b":"1F47B","f":true,"k":[25,32],"j":["halloween","spooky","scary"]},"alien":{"a":"Extraterrestrial Alien","b":"1F47D","f":true,"k":[25,39],"j":["UFO","paul","weird","outer_space"]},"space_invader":{"a":"Alien Monster","b":"1F47E","f":true,"k":[25,40],"j":["alien_monster","game","arcade","play"]},"robot_face":{"a":"Robot Face","b":"1F916","f":true,"k":[39,43],"j":["robot","computer","machine","bot"]},"smiley_cat":{"a":"Smiling Cat Face with Open Mouth","b":"1F63A","f":true,"k":[33,45],"j":["grinning_cat","animal","cats","happy","smile"]},"smile_cat":{"a":"Grinning Cat Face with Smiling Eyes","b":"1F638","f":true,"k":[33,43],"j":["grinning_cat_with_smiling_eyes","animal","cats","smile"]},"joy_cat":{"a":"Cat Face with Tears Of Joy","b":"1F639","f":true,"k":[33,44],"j":["cat_with_tears_of_joy","animal","cats","haha","happy","tears"]},"heart_eyes_cat":{"a":"Smiling Cat Face with Heart-Shaped Eyes","b":"1F63B","f":true,"k":[33,46],"j":["smiling_cat_with_heart_eyes","animal","love","like","affection","cats","valentines","heart"]},"smirk_cat":{"a":"Cat Face with Wry Smile","b":"1F63C","f":true,"k":[33,47],"j":["cat_with_wry_smile","animal","cats","smirk"]},"kissing_cat":{"a":"Kissing Cat Face with Closed Eyes","b":"1F63D","f":true,"k":[33,48],"j":["animal","cats","kiss"]},"scream_cat":{"a":"Weary Cat Face","b":"1F640","f":true,"k":[33,51],"j":["weary_cat","animal","cats","munch","scared","scream"]},"crying_cat_face":{"a":"Crying Cat Face","b":"1F63F","f":true,"k":[33,50],"j":["crying_cat","animal","tears","weep","sad","cats","upset","cry"]},"pouting_cat":{"a":"Pouting Cat Face","b":"1F63E","f":true,"k":[33,49],"j":["animal","cats"]},"see_no_evil":{"a":"See-No-Evil Monkey","b":"1F648","f":true,"k":[34,50],"j":["see_no_evil_monkey","monkey","animal","nature","haha"]},"hear_no_evil":{"a":"Hear-No-Evil Monkey","b":"1F649","f":true,"k":[34,51],"j":["hear_no_evil_monkey","animal","monkey","nature"]},"speak_no_evil":{"a":"Speak-No-Evil Monkey","b":"1F64A","f":true,"k":[34,52],"j":["speak_no_evil_monkey","monkey","animal","nature","omg"]},"love_letter":{"a":"Love Letter","b":"1F48C","f":true,"k":[27,8],"j":["email","like","affection","envelope","valentines"]},"cupid":{"a":"Heart with Arrow","b":"1F498","f":true,"k":[28,8],"j":["heart_with_arrow","love","like","heart","affection","valentines"]},"gift_heart":{"a":"Heart with Ribbon","b":"1F49D","f":true,"k":[28,13],"j":["heart_with_ribbon","love","valentines"]},"sparkling_heart":{"a":"Sparkling Heart","b":"1F496","f":true,"k":[28,6],"j":["love","like","affection","valentines"]},"heartpulse":{"a":"Growing Heart","b":"1F497","f":true,"k":[28,7],"j":["growing_heart","like","love","affection","valentines","pink"]},"heartbeat":{"a":"Beating Heart","b":"1F493","f":true,"k":[28,3],"j":["beating_heart","love","like","affection","valentines","pink","heart"]},"revolving_hearts":{"a":"Revolving Hearts","b":"1F49E","f":true,"k":[28,14],"j":["love","like","affection","valentines"]},"two_hearts":{"a":"Two Hearts","b":"1F495","f":true,"k":[28,5],"j":["love","like","affection","valentines","heart"]},"heart_decoration":{"a":"Heart Decoration","b":"1F49F","f":true,"k":[28,15],"j":["purple-square","love","like"]},"heavy_heart_exclamation_mark_ornament":{"a":"Heart Exclamation","b":"2763-FE0F","f":true,"k":[60,35],"c":"2763"},"broken_heart":{"a":"Broken Heart","b":"1F494","f":true,"k":[28,4],"j":["sad","sorry","break","heart","heartbreak"],"l":["",":->"]},"sweat_smile":{"a":"Smiling Face with Open Mouth and Cold Sweat","b":"1F605","f":true,"k":[32,52],"j":["grinning_face_with_sweat","face","hot","happy","laugh","sweat","smile","relief"]},"rolling_on_the_floor_laughing":{"a":"Rolling On the Floor Laughing","b":"1F923","f":true,"k":[40,55],"j":["face","rolling","floor","laughing","lol","haha","rofl"]},"joy":{"a":"Face with Tears Of Joy","b":"1F602","f":true,"k":[32,49],"j":["face_with_tears_of_joy","face","cry","tears","weep","happy","happytears","haha"]},"slightly_smiling_face":{"a":"Slightly Smiling Face","b":"1F642","f":true,"k":[33,56],"j":["face","smile"],"l":[":)","(:",":-)"]},"upside_down_face":{"a":"Upside-Down Face","b":"1F643","f":true,"k":[33,57],"j":["face","flipped","silly","smile"]},"melting_face":{"a":"Melting Face","b":"1FAE0","f":true,"k":[56,37],"j":["melting face","hot","heat"]},"wink":{"a":"Winking Face","b":"1F609","f":true,"k":[32,56],"j":["winking_face","face","happy","mischievous","secret",";)","smile","eye"],"l":[";)",";-)"],"m":";)"},"blush":{"a":"Smiling Face with Smiling Eyes","b":"1F60A","f":true,"k":[32,57],"j":["smiling_face_with_smiling_eyes","face","smile","happy","flushed","crush","embarrassed","shy","joy"],"m":":)"},"innocent":{"a":"Smiling Face with Halo","b":"1F607","f":true,"k":[32,54],"j":["smiling_face_with_halo","face","angel","heaven","halo"]},"smiling_face_with_3_hearts":{"a":"Smiling Face with Smiling Eyes and Three Hearts","b":"1F970","f":true,"k":[44,33],"j":["smiling_face_with_hearts","face","love","like","affection","valentines","infatuation","crush","hearts","adore"]},"heart_eyes":{"a":"Smiling Face with Heart-Shaped Eyes","b":"1F60D","f":true,"k":[32,60],"j":["smiling_face_with_heart_eyes","face","love","like","affection","valentines","infatuation","crush","heart"]},"star-struck":{"a":"Grinning Face with Star Eyes","b":"1F929","f":true,"k":[41,16],"j":["star_struck","face","smile","starry","eyes","grinning"]},"kissing_heart":{"a":"Face Throwing a Kiss","b":"1F618","f":true,"k":[33,9],"j":["face_blowing_a_kiss","face","love","like","affection","valentines","infatuation","kiss"],"l":[":*",":-*"]},"kissing":{"a":"Kissing Face","b":"1F617","f":true,"k":[33,8],"j":["kissing_face","love","like","face","3","valentines","infatuation","kiss"]},"relaxed":{"a":"White Smiling Face","b":"263A-FE0F","f":true,"k":[58,41],"c":"263A"},"kissing_closed_eyes":{"a":"Kissing Face with Closed Eyes","b":"1F61A","f":true,"k":[33,11],"j":["kissing_face_with_closed_eyes","face","love","like","affection","valentines","infatuation","kiss"]},"kissing_smiling_eyes":{"a":"Kissing Face with Smiling Eyes","b":"1F619","f":true,"k":[33,10],"j":["kissing_face_with_smiling_eyes","face","affection","valentines","infatuation","kiss"]},"smiling_face_with_tear":{"a":"Smiling Face with Tear","b":"1F972","f":true,"k":[44,35],"j":["smiling face with tear","sad","cry","pretend"]},"yum":{"a":"Face Savouring Delicious Food","b":"1F60B","f":true,"k":[32,58],"j":["face_savoring_food","happy","joy","tongue","smile","face","silly","yummy","nom","delicious","savouring"]},"stuck_out_tongue":{"a":"Face with Stuck-Out Tongue","b":"1F61B","f":true,"k":[33,12],"j":["face_with_tongue","face","prank","childish","playful","mischievous","smile","tongue"],"l":[":p",":-p",":P",":-P",":b",":-b"],"m":":p"},"stuck_out_tongue_winking_eye":{"a":"Face with Stuck-Out Tongue and Winking Eye","b":"1F61C","f":true,"k":[33,13],"j":["winking_face_with_tongue","face","prank","childish","playful","mischievous","smile","wink","tongue"],"l":[";p",";-p",";b",";-b",";P",";-P"],"m":";p"},"zany_face":{"a":"Grinning Face with One Large and One Small Eye","b":"1F92A","f":true,"k":[41,17],"j":["face","goofy","crazy"]},"stuck_out_tongue_closed_eyes":{"a":"Face with Stuck-Out Tongue and Tightly-Closed Eyes","b":"1F61D","f":true,"k":[33,14],"j":["squinting_face_with_tongue","face","prank","playful","mischievous","smile","tongue"]},"money_mouth_face":{"a":"Money-Mouth Face","b":"1F911","f":true,"k":[39,39],"j":["face","rich","dollar","money"]},"hugging_face":{"a":"Hugging Face","b":"1F917","f":true,"k":[39,45],"j":["face","smile","hug"]},"face_with_hand_over_mouth":{"a":"Smiling Face with Smiling Eyes and Hand Covering Mouth","b":"1F92D","f":true,"k":[41,20],"j":["face","whoops","shock","surprise"]},"face_with_open_eyes_and_hand_over_mouth":{"a":"Face with Open Eyes and Hand Over Mouth","b":"1FAE2","f":true,"k":[56,39],"j":["face with open eyes and hand over mouth","silence","secret","shock","surprise"]},"face_with_peeking_eye":{"a":"Face with Peeking Eye","b":"1FAE3","f":true,"k":[56,40],"j":["face with peeking eye","scared","frightening","embarrassing","shy"]},"shushing_face":{"a":"Face with Finger Covering Closed Lips","b":"1F92B","f":true,"k":[41,18],"j":["face","quiet","shhh"]},"thinking_face":{"a":"Thinking Face","b":"1F914","f":true,"k":[39,42],"j":["face","hmmm","think","consider"]},"saluting_face":{"a":"Saluting Face","b":"1FAE1","f":true,"k":[56,38],"j":["saluting face","respect","salute"]},"zipper_mouth_face":{"a":"Zipper-Mouth Face","b":"1F910","f":true,"k":[39,38],"j":["face","sealed","zipper","secret"]},"face_with_raised_eyebrow":{"a":"Face with One Eyebrow Raised","b":"1F928","f":true,"k":[41,15],"j":["face","distrust","scepticism","disapproval","disbelief","surprise","suspicious"]},"neutral_face":{"a":"Neutral Face","b":"1F610","f":true,"k":[33,1],"j":["indifference","meh",":|","neutral"],"l":[":|",":-|"]},"expressionless":{"a":"Expressionless Face","b":"1F611","f":true,"k":[33,2],"j":["expressionless_face","face","indifferent","-_-","meh","deadpan"]},"no_mouth":{"a":"Face Without Mouth","b":"1F636","f":true,"k":[33,42],"j":["face_without_mouth","face"]},"dotted_line_face":{"a":"Dotted Line Face","b":"1FAE5","f":true,"k":[56,42],"j":["dotted line face","invisible","lonely","isolation","depression"]},"face_in_clouds":{"a":"Face In Clouds","b":"1F636-200D-1F32B-FE0F","f":true,"k":[33,41],"c":"1F636-200D-1F32B","j":["face_without_mouth","face"]},"smirk":{"a":"Smirking Face","b":"1F60F","f":true,"k":[33,0],"j":["smirking_face","face","smile","mean","prank","smug","sarcasm"]},"unamused":{"a":"Unamused Face","b":"1F612","f":true,"k":[33,3],"j":["unamused_face","indifference","bored","straight face","serious","sarcasm","unimpressed","skeptical","dubious","ugh","side_eye"],"m":":("},"face_with_rolling_eyes":{"a":"Face with Rolling Eyes","b":"1F644","f":true,"k":[33,58],"j":["face","eyeroll","frustrated"]},"grimacing":{"a":"Grimacing Face","b":"1F62C","f":true,"k":[33,29],"j":["grimacing_face","face","grimace","teeth"]},"face_exhaling":{"a":"Face Exhaling","b":"1F62E-200D-1F4A8","f":true,"k":[33,31],"j":["face_with_open_mouth","face","surprise","impressed","wow","whoa",":O"]},"lying_face":{"a":"Lying Face","b":"1F925","f":true,"k":[40,57],"j":["face","lie","pinocchio"]},"shaking_face":{"a":"Shaking Face","b":"1FAE8","f":true,"k":[56,45],"j":["shaking face","dizzy","shock","blurry","earthquake"]},"head_shaking_horizontally":{"a":"Head Shaking Horizontally","b":"1F642-200D-2194-FE0F","f":true,"k":[33,54],"c":"1F642-200D-2194","j":["slightly_smiling_face","face","smile"]},"head_shaking_vertically":{"a":"Head Shaking Vertically","b":"1F642-200D-2195-FE0F","f":true,"k":[33,55],"c":"1F642-200D-2195","j":["slightly_smiling_face","face","smile"]},"relieved":{"a":"Relieved Face","b":"1F60C","f":true,"k":[32,59],"j":["relieved_face","face","relaxed","phew","massage","happiness"]},"pensive":{"a":"Pensive Face","b":"1F614","f":true,"k":[33,5],"j":["pensive_face","face","sad","depressed","upset"]},"sleepy":{"a":"Sleepy Face","b":"1F62A","f":true,"k":[33,27],"j":["sleepy_face","face","tired","rest","nap"]},"drooling_face":{"a":"Drooling Face","b":"1F924","f":true,"k":[40,56],"j":["face"]},"sleeping":{"a":"Sleeping Face","b":"1F634","f":true,"k":[33,38],"j":["sleeping_face","face","tired","sleepy","night","zzz"]},"face_with_bags_under_eyes":{"a":"Face with Bags Under Eyes","b":"1FAE9","f":true,"k":[56,46]},"mask":{"a":"Face with Medical Mask","b":"1F637","f":true,"k":[33,43],"j":["face_with_medical_mask","face","sick","ill","disease","covid"]},"face_with_thermometer":{"a":"Face with Thermometer","b":"1F912","f":true,"k":[39,40],"j":["sick","temperature","thermometer","cold","fever","covid"]},"face_with_head_bandage":{"a":"Face with Head-Bandage","b":"1F915","f":true,"k":[39,43],"j":["injured","clumsy","bandage","hurt"]},"nauseated_face":{"a":"Nauseated Face","b":"1F922","f":true,"k":[40,54],"j":["face","vomit","gross","green","sick","throw up","ill"]},"face_vomiting":{"a":"Face with Open Mouth Vomiting","b":"1F92E","f":true,"k":[41,21],"j":["face","sick"]},"sneezing_face":{"a":"Sneezing Face","b":"1F927","f":true,"k":[41,14],"j":["face","gesundheit","sneeze","sick","allergy"]},"hot_face":{"a":"Overheated Face","b":"1F975","f":true,"k":[44,38],"j":["face","feverish","heat","red","sweating"]},"cold_face":{"a":"Freezing Face","b":"1F976","f":true,"k":[44,39],"j":["face","blue","freezing","frozen","frostbite","icicles"]},"woozy_face":{"a":"Face with Uneven Eyes and Wavy Mouth","b":"1F974","f":true,"k":[44,37],"j":["face","dizzy","intoxicated","tipsy","wavy"]},"dizzy_face":{"a":"Dizzy Face","b":"1F635","f":true,"k":[33,40],"j":["spent","unconscious","xox","dizzy"]},"face_with_spiral_eyes":{"a":"Face with Spiral Eyes","b":"1F635-200D-1F4AB","f":true,"k":[33,39],"j":["dizzy_face","spent","unconscious","xox","dizzy"]},"exploding_head":{"a":"Shocked Face with Exploding Head","b":"1F92F","f":true,"k":[41,22],"j":["face","shocked","mind","blown"]},"face_with_cowboy_hat":{"a":"Face with Cowboy Hat","b":"1F920","f":true,"k":[40,52],"j":["cowboy_hat_face","face","cowgirl","hat"]},"partying_face":{"a":"Face with Party Horn and Party Hat","b":"1F973","f":true,"k":[44,36],"j":["face","celebration","woohoo"]},"disguised_face":{"a":"Disguised Face","b":"1F978","f":true,"k":[44,46],"j":["disguised face","pretent","brows","glasses","moustache"]},"sunglasses":{"a":"Smiling Face with Sunglasses","b":"1F60E","f":true,"k":[32,61],"j":["smiling_face_with_sunglasses","face","cool","smile","summer","beach","sunglass"],"l":["8)"]},"nerd_face":{"a":"Nerd Face","b":"1F913","f":true,"k":[39,41],"j":["face","nerdy","geek","dork"]},"face_with_monocle":{"a":"Face with Monocle","b":"1F9D0","f":true,"k":[48,0],"j":["face","stuffy","wealthy"]},"confused":{"a":"Confused Face","b":"1F615","f":true,"k":[33,6],"j":["confused_face","face","indifference","huh","weird","hmmm",":/"],"l":[":\\",":-\\",":/",":-/"]},"face_with_diagonal_mouth":{"a":"Face with Diagonal Mouth","b":"1FAE4","f":true,"k":[56,41],"j":["face with diagonal mouth","skeptic","confuse","frustrated","indifferent"]},"worried":{"a":"Worried Face","b":"1F61F","f":true,"k":[33,16],"j":["worried_face","face","concern","nervous",":("]},"slightly_frowning_face":{"a":"Slightly Frowning Face","b":"1F641","f":true,"k":[33,53],"j":["face","frowning","disappointed","sad","upset"]},"white_frowning_face":{"a":"Frowning Face","b":"2639-FE0F","f":true,"k":[58,40],"c":"2639"},"open_mouth":{"a":"Face with Open Mouth","b":"1F62E","f":true,"k":[33,32],"j":["face_with_open_mouth","face","surprise","impressed","wow","whoa",":O"],"l":[":o",":-o",":O",":-O"]},"hushed":{"a":"Hushed Face","b":"1F62F","f":true,"k":[33,33],"j":["hushed_face","face","woo","shh"]},"astonished":{"a":"Astonished Face","b":"1F632","f":true,"k":[33,36],"j":["astonished_face","face","xox","surprised","poisoned"]},"flushed":{"a":"Flushed Face","b":"1F633","f":true,"k":[33,37],"j":["flushed_face","face","blush","shy","flattered"]},"pleading_face":{"a":"Face with Pleading Eyes","b":"1F97A","f":true,"k":[44,48],"j":["face","begging","mercy","cry","tears","sad","grievance"]},"face_holding_back_tears":{"a":"Face Holding Back Tears","b":"1F979","f":true,"k":[44,47],"j":["face holding back tears","touched","gratitude","cry"]},"frowning":{"a":"Frowning Face with Open Mouth","b":"1F626","f":true,"k":[33,23],"j":["frowning_face_with_open_mouth","face","aw","what"]},"anguished":{"a":"Anguished Face","b":"1F627","f":true,"k":[33,24],"j":["anguished_face","face","stunned","nervous"],"l":["D:"]},"fearful":{"a":"Fearful Face","b":"1F628","f":true,"k":[33,25],"j":["fearful_face","face","scared","terrified","nervous"]},"cold_sweat":{"a":"Face with Open Mouth and Cold Sweat","b":"1F630","f":true,"k":[33,34],"j":["anxious_face_with_sweat","face","nervous","sweat"]},"disappointed_relieved":{"a":"Disappointed But Relieved Face","b":"1F625","f":true,"k":[33,22],"j":["sad_but_relieved_face","face","phew","sweat","nervous"]},"cry":{"a":"Crying Face","b":"1F622","f":true,"k":[33,19],"j":["crying_face","face","tears","sad","depressed","upset",":'("],"l":[":'("],"m":":'("},"sob":{"a":"Loudly Crying Face","b":"1F62D","f":true,"k":[33,30],"j":["loudly_crying_face","sobbing","face","cry","tears","sad","upset","depressed"],"m":":'("},"scream":{"a":"Face Screaming In Fear","b":"1F631","f":true,"k":[33,35],"j":["face_screaming_in_fear","face","munch","scared","omg"]},"confounded":{"a":"Confounded Face","b":"1F616","f":true,"k":[33,7],"j":["confounded_face","face","confused","sick","unwell","oops",":S"]},"persevere":{"a":"Persevering Face","b":"1F623","f":true,"k":[33,20],"j":["persevering_face","face","sick","no","upset","oops"]},"disappointed":{"a":"Disappointed Face","b":"1F61E","f":true,"k":[33,15],"j":["disappointed_face","face","sad","upset","depressed",":("],"l":["):",":(",":-("],"m":":("},"sweat":{"a":"Face with Cold Sweat","b":"1F613","f":true,"k":[33,4],"j":["downcast_face_with_sweat","face","hot","sad","tired","exercise"]},"weary":{"a":"Weary Face","b":"1F629","f":true,"k":[33,26],"j":["weary_face","face","tired","sleepy","sad","frustrated","upset"]},"tired_face":{"a":"Tired Face","b":"1F62B","f":true,"k":[33,28],"j":["sick","whine","upset","frustrated"]},"yawning_face":{"a":"Yawning Face","b":"1F971","f":true,"k":[44,34],"j":["tired","sleepy"]},"triumph":{"a":"Face with Look Of Triumph","b":"1F624","f":true,"k":[33,21],"j":["face_with_steam_from_nose","face","gas","phew","proud","pride"]},"rage":{"a":"Pouting Face","b":"1F621","f":true,"k":[33,18],"j":["pouting_face","angry","mad","hate","despise"]},"angry":{"a":"Angry Face","b":"1F620","f":true,"k":[33,17],"j":["angry_face","mad","face","annoyed","frustrated"],"l":[">:(",">:-("]},"face_with_symbols_on_mouth":{"a":"Serious Face with Symbols Covering Mouth","b":"1F92C","f":true,"k":[41,19],"j":["face","swearing","cursing","cussing","profanity","expletive"]},"smiling_imp":{"a":"Smiling Face with Horns","b":"1F608","f":true,"k":[32,55],"j":["smiling_face_with_horns","devil","horns"]},"imp":{"a":"Imp","b":"1F47F","f":true,"k":[25,42],"j":["angry_face_with_horns","devil","angry","horns"]},"skull":{"a":"Skull","b":"1F480","f":true,"k":[25,43],"j":["dead","skeleton","creepy","death","dead"]},"skull_and_crossbones":{"a":"Skull and Crossbones","b":"2620-FE0F","f":true,"k":[58,32],"c":"2620"},"hankey":{"a":"Pile Of Poo","b":"1F4A9","f":true,"k":[28,26],"j":["pile_of_poo","shitface","fail","turd","shit"]},"clown_face":{"a":"Clown Face","b":"1F921","f":true,"k":[40,53],"j":["face"]},"japanese_ogre":{"a":"Japanese Ogre","b":"1F479","f":true,"k":[25,31],"j":["ogre","monster","red","mask","halloween","scary","creepy","devil","demon"]},"japanese_goblin":{"a":"Japanese Goblin","b":"1F47A","f":true,"k":[25,32],"j":["goblin","red","evil","mask","monster","scary","creepy"]},"ghost":{"a":"Ghost","b":"1F47B","f":true,"k":[25,33],"j":["halloween","spooky","scary"]},"alien":{"a":"Extraterrestrial Alien","b":"1F47D","f":true,"k":[25,40],"j":["UFO","paul","weird","outer_space"]},"space_invader":{"a":"Alien Monster","b":"1F47E","f":true,"k":[25,41],"j":["alien_monster","game","arcade","play"]},"robot_face":{"a":"Robot Face","b":"1F916","f":true,"k":[39,44],"j":["robot","computer","machine","bot"]},"smiley_cat":{"a":"Smiling Cat Face with Open Mouth","b":"1F63A","f":true,"k":[33,46],"j":["grinning_cat","animal","cats","happy","smile"]},"smile_cat":{"a":"Grinning Cat Face with Smiling Eyes","b":"1F638","f":true,"k":[33,44],"j":["grinning_cat_with_smiling_eyes","animal","cats","smile"]},"joy_cat":{"a":"Cat Face with Tears Of Joy","b":"1F639","f":true,"k":[33,45],"j":["cat_with_tears_of_joy","animal","cats","haha","happy","tears"]},"heart_eyes_cat":{"a":"Smiling Cat Face with Heart-Shaped Eyes","b":"1F63B","f":true,"k":[33,47],"j":["smiling_cat_with_heart_eyes","animal","love","like","affection","cats","valentines","heart"]},"smirk_cat":{"a":"Cat Face with Wry Smile","b":"1F63C","f":true,"k":[33,48],"j":["cat_with_wry_smile","animal","cats","smirk"]},"kissing_cat":{"a":"Kissing Cat Face with Closed Eyes","b":"1F63D","f":true,"k":[33,49],"j":["animal","cats","kiss"]},"scream_cat":{"a":"Weary Cat Face","b":"1F640","f":true,"k":[33,52],"j":["weary_cat","animal","cats","munch","scared","scream"]},"crying_cat_face":{"a":"Crying Cat Face","b":"1F63F","f":true,"k":[33,51],"j":["crying_cat","animal","tears","weep","sad","cats","upset","cry"]},"pouting_cat":{"a":"Pouting Cat Face","b":"1F63E","f":true,"k":[33,50],"j":["animal","cats"]},"see_no_evil":{"a":"See-No-Evil Monkey","b":"1F648","f":true,"k":[34,51],"j":["see_no_evil_monkey","monkey","animal","nature","haha"]},"hear_no_evil":{"a":"Hear-No-Evil Monkey","b":"1F649","f":true,"k":[34,52],"j":["hear_no_evil_monkey","animal","monkey","nature"]},"speak_no_evil":{"a":"Speak-No-Evil Monkey","b":"1F64A","f":true,"k":[34,53],"j":["speak_no_evil_monkey","monkey","animal","nature","omg"]},"love_letter":{"a":"Love Letter","b":"1F48C","f":true,"k":[27,9],"j":["email","like","affection","envelope","valentines"]},"cupid":{"a":"Heart with Arrow","b":"1F498","f":true,"k":[28,9],"j":["heart_with_arrow","love","like","heart","affection","valentines"]},"gift_heart":{"a":"Heart with Ribbon","b":"1F49D","f":true,"k":[28,14],"j":["heart_with_ribbon","love","valentines"]},"sparkling_heart":{"a":"Sparkling Heart","b":"1F496","f":true,"k":[28,7],"j":["love","like","affection","valentines"]},"heartpulse":{"a":"Growing Heart","b":"1F497","f":true,"k":[28,8],"j":["growing_heart","like","love","affection","valentines","pink"]},"heartbeat":{"a":"Beating Heart","b":"1F493","f":true,"k":[28,4],"j":["beating_heart","love","like","affection","valentines","pink","heart"]},"revolving_hearts":{"a":"Revolving Hearts","b":"1F49E","f":true,"k":[28,15],"j":["love","like","affection","valentines"]},"two_hearts":{"a":"Two Hearts","b":"1F495","f":true,"k":[28,6],"j":["love","like","affection","valentines","heart"]},"heart_decoration":{"a":"Heart Decoration","b":"1F49F","f":true,"k":[28,16],"j":["purple-square","love","like"]},"heavy_heart_exclamation_mark_ornament":{"a":"Heart Exclamation","b":"2763-FE0F","f":true,"k":[60,43],"c":"2763"},"broken_heart":{"a":"Broken Heart","b":"1F494","f":true,"k":[28,5],"j":["sad","sorry","break","heart","heartbreak"],"l":[" = Omit< - ComponentPropsWithoutRef, - 'dangerouslySetInnerHTML' | 'className' -> & { - htmlString: string; - extraEmojis?: CustomEmojiMapArg; - as?: Element; - shallow?: boolean; - className?: string; -}; - -export const ModernEmojiHTML = ({ - extraEmojis, - htmlString, - as: Wrapper = 'div', // Rename for syntax highlighting - shallow, - className = '', - ...props -}: EmojiHTMLProps) => { - const emojifiedHtml = useEmojify({ - text: htmlString, - extraEmojis, - deep: !shallow, - }); - - if (emojifiedHtml === null) { - return null; - } - - return ( - - ); -}; - -export const EmojiHTML = ( - props: EmojiHTMLProps, -) => { - if (isModernEmojiEnabled()) { - return ; - } - const { as: asElement, htmlString, extraEmojis, className, ...rest } = props; - const Wrapper = asElement ?? 'div'; - return ( - - ); -}; diff --git a/app/javascript/mastodon/features/emoji/emoji_map.json b/app/javascript/mastodon/features/emoji/emoji_map.json index 6e09bd0f7b16a5..c4c91bc72f6017 100644 --- a/app/javascript/mastodon/features/emoji/emoji_map.json +++ b/app/javascript/mastodon/features/emoji/emoji_map.json @@ -1 +1 @@ -{"😀":"1f600","😃":"1f603","😄":"1f604","😁":"1f601","😆":"1f606","😅":"1f605","🤣":"1f923","😂":"1f602","🙂":"1f642","🙃":"1f643","🫠":"1fae0","😉":"1f609","😊":"1f60a","😇":"1f607","🥰":"1f970","😍":"1f60d","🤩":"1f929","😘":"1f618","😗":"1f617","☺":"263a","😚":"1f61a","😙":"1f619","🥲":"1f972","😋":"1f60b","😛":"1f61b","😜":"1f61c","🤪":"1f92a","😝":"1f61d","🤑":"1f911","🤗":"1f917","🤭":"1f92d","🫢":"1fae2","🫣":"1fae3","🤫":"1f92b","🤔":"1f914","🫡":"1fae1","🤐":"1f910","🤨":"1f928","😐":"1f610","😑":"1f611","😶":"1f636","🫥":"1fae5","😏":"1f60f","😒":"1f612","🙄":"1f644","😬":"1f62c","🤥":"1f925","🫨":"1fae8","😌":"1f60c","😔":"1f614","😪":"1f62a","🤤":"1f924","😴":"1f634","😷":"1f637","🤒":"1f912","🤕":"1f915","🤢":"1f922","🤮":"1f92e","🤧":"1f927","🥵":"1f975","🥶":"1f976","🥴":"1f974","😵":"1f635","🤯":"1f92f","🤠":"1f920","🥳":"1f973","🥸":"1f978","😎":"1f60e","🤓":"1f913","🧐":"1f9d0","😕":"1f615","🫤":"1fae4","😟":"1f61f","🙁":"1f641","☹":"2639","😮":"1f62e","😯":"1f62f","😲":"1f632","😳":"1f633","🥺":"1f97a","🥹":"1f979","😦":"1f626","😧":"1f627","😨":"1f628","😰":"1f630","😥":"1f625","😢":"1f622","😭":"1f62d","😱":"1f631","😖":"1f616","😣":"1f623","😞":"1f61e","😓":"1f613","😩":"1f629","😫":"1f62b","🥱":"1f971","😤":"1f624","😡":"1f621","😠":"1f620","🤬":"1f92c","😈":"1f608","👿":"1f47f","💀":"1f480","☠":"2620","💩":"1f4a9","🤡":"1f921","👹":"1f479","👺":"1f47a","👻":"1f47b","👽":"1f47d","👾":"1f47e","🤖":"1f916","😺":"1f63a","😸":"1f638","😹":"1f639","😻":"1f63b","😼":"1f63c","😽":"1f63d","🙀":"1f640","😿":"1f63f","😾":"1f63e","🙈":"1f648","🙉":"1f649","🙊":"1f64a","💌":"1f48c","💘":"1f498","💝":"1f49d","💖":"1f496","💗":"1f497","💓":"1f493","💞":"1f49e","💕":"1f495","💟":"1f49f","❣":"2763","💔":"1f494","❤":"2764","🩷":"1fa77","🧡":"1f9e1","💛":"1f49b","💚":"1f49a","💙":"1f499","🩵":"1fa75","💜":"1f49c","🤎":"1f90e","🖤":"1f5a4","🩶":"1fa76","🤍":"1f90d","💋":"1f48b","💯":"1f4af","💢":"1f4a2","💥":"1f4a5","💫":"1f4ab","💦":"1f4a6","💨":"1f4a8","🕳":"1f573","💬":"1f4ac","🗨":"1f5e8","🗯":"1f5ef","💭":"1f4ad","💤":"1f4a4","👋":"1f44b","🤚":"1f91a","🖐":"1f590","✋":"270b","🖖":"1f596","🫱":"1faf1","🫲":"1faf2","🫳":"1faf3","🫴":"1faf4","🫷":"1faf7","🫸":"1faf8","👌":"1f44c","🤌":"1f90c","🤏":"1f90f","✌":"270c","🤞":"1f91e","🫰":"1faf0","🤟":"1f91f","🤘":"1f918","🤙":"1f919","👈":"1f448","👉":"1f449","👆":"1f446","🖕":"1f595","👇":"1f447","☝":"261d","🫵":"1faf5","👍":"1f44d","👎":"1f44e","✊":"270a","👊":"1f44a","🤛":"1f91b","🤜":"1f91c","👏":"1f44f","🙌":"1f64c","🫶":"1faf6","👐":"1f450","🤲":"1f932","🤝":"1f91d","🙏":"1f64f","✍":"270d","💅":"1f485","🤳":"1f933","💪":"1f4aa","🦾":"1f9be","🦿":"1f9bf","🦵":"1f9b5","🦶":"1f9b6","👂":"1f442","🦻":"1f9bb","👃":"1f443","🧠":"1f9e0","🫀":"1fac0","🫁":"1fac1","🦷":"1f9b7","🦴":"1f9b4","👀":"1f440","👁":"1f441","👅":"1f445","👄":"1f444","🫦":"1fae6","👶":"1f476","🧒":"1f9d2","👦":"1f466","👧":"1f467","🧑":"1f9d1","👱":"1f471","👨":"1f468","🧔":"1f9d4","👩":"1f469","🧓":"1f9d3","👴":"1f474","👵":"1f475","🙍":"1f64d","🙎":"1f64e","🙅":"1f645","🙆":"1f646","💁":"1f481","🙋":"1f64b","🧏":"1f9cf","🙇":"1f647","🤦":"1f926","🤷":"1f937","👮":"1f46e","🕵":"1f575","💂":"1f482","🥷":"1f977","👷":"1f477","🫅":"1fac5","🤴":"1f934","👸":"1f478","👳":"1f473","👲":"1f472","🧕":"1f9d5","🤵":"1f935","👰":"1f470","🤰":"1f930","🫃":"1fac3","🫄":"1fac4","🤱":"1f931","👼":"1f47c","🎅":"1f385","🤶":"1f936","🦸":"1f9b8","🦹":"1f9b9","🧙":"1f9d9","🧚":"1f9da","🧛":"1f9db","🧜":"1f9dc","🧝":"1f9dd","🧞":"1f9de","🧟":"1f9df","🧌":"1f9cc","💆":"1f486","💇":"1f487","🚶":"1f6b6","🧍":"1f9cd","🧎":"1f9ce","🏃":"1f3c3","💃":"1f483","🕺":"1f57a","🕴":"1f574","👯":"1f46f","🧖":"1f9d6","🧗":"1f9d7","🤺":"1f93a","🏇":"1f3c7","⛷":"26f7","🏂":"1f3c2","🏌":"1f3cc","🏄":"1f3c4","🚣":"1f6a3","🏊":"1f3ca","⛹":"26f9","🏋":"1f3cb","🚴":"1f6b4","🚵":"1f6b5","🤸":"1f938","🤼":"1f93c","🤽":"1f93d","🤾":"1f93e","🤹":"1f939","🧘":"1f9d8","🛀":"1f6c0","🛌":"1f6cc","👭":"1f46d","👫":"1f46b","👬":"1f46c","💏":"1f48f","💑":"1f491","🗣":"1f5e3","👤":"1f464","👥":"1f465","🫂":"1fac2","👪":"1f46a","👣":"1f463","🏻":"1f463","🏼":"1f463","🏽":"1f463","🏾":"1f463","🏿":"1f463","🦰":"1f463","🦱":"1f463","🦳":"1f463","🦲":"1f463","🐵":"1f435","🐒":"1f412","🦍":"1f98d","🦧":"1f9a7","🐶":"1f436","🐕":"1f415","🦮":"1f9ae","🐩":"1f429","🐺":"1f43a","🦊":"1f98a","🦝":"1f99d","🐱":"1f431","🐈":"1f408","🦁":"1f981","🐯":"1f42f","🐅":"1f405","🐆":"1f406","🐴":"1f434","🫎":"1face","🫏":"1facf","🐎":"1f40e","🦄":"1f984","🦓":"1f993","🦌":"1f98c","🦬":"1f9ac","🐮":"1f42e","🐂":"1f402","🐃":"1f403","🐄":"1f404","🐷":"1f437","🐖":"1f416","🐗":"1f417","🐽":"1f43d","🐏":"1f40f","🐑":"1f411","🐐":"1f410","🐪":"1f42a","🐫":"1f42b","🦙":"1f999","🦒":"1f992","🐘":"1f418","🦣":"1f9a3","🦏":"1f98f","🦛":"1f99b","🐭":"1f42d","🐁":"1f401","🐀":"1f400","🐹":"1f439","🐰":"1f430","🐇":"1f407","🐿":"1f43f","🦫":"1f9ab","🦔":"1f994","🦇":"1f987","🐻":"1f43b","🐨":"1f428","🐼":"1f43c","🦥":"1f9a5","🦦":"1f9a6","🦨":"1f9a8","🦘":"1f998","🦡":"1f9a1","🐾":"1f43e","🦃":"1f983","🐔":"1f414","🐓":"1f413","🐣":"1f423","🐤":"1f424","🐥":"1f425","🐦":"1f426","🐧":"1f427","🕊":"1f54a","🦅":"1f985","🦆":"1f986","🦢":"1f9a2","🦉":"1f989","🦤":"1f9a4","🪶":"1fab6","🦩":"1f9a9","🦚":"1f99a","🦜":"1f99c","🪽":"1fabd","🪿":"1fabf","🐸":"1f438","🐊":"1f40a","🐢":"1f422","🦎":"1f98e","🐍":"1f40d","🐲":"1f432","🐉":"1f409","🦕":"1f995","🦖":"1f996","🐳":"1f433","🐋":"1f40b","🐬":"1f42c","🦭":"1f9ad","🐟":"1f41f","🐠":"1f420","🐡":"1f421","🦈":"1f988","🐙":"1f419","🐚":"1f41a","🪸":"1fab8","🪼":"1fabc","🐌":"1f40c","🦋":"1f98b","🐛":"1f41b","🐜":"1f41c","🐝":"1f41d","🪲":"1fab2","🐞":"1f41e","🦗":"1f997","🪳":"1fab3","🕷":"1f577","🕸":"1f578","🦂":"1f982","🦟":"1f99f","🪰":"1fab0","🪱":"1fab1","🦠":"1f9a0","💐":"1f490","🌸":"1f338","💮":"1f4ae","🪷":"1fab7","🏵":"1f3f5","🌹":"1f339","🥀":"1f940","🌺":"1f33a","🌻":"1f33b","🌼":"1f33c","🌷":"1f337","🪻":"1fabb","🌱":"1f331","🪴":"1fab4","🌲":"1f332","🌳":"1f333","🌴":"1f334","🌵":"1f335","🌾":"1f33e","🌿":"1f33f","☘":"2618","🍀":"1f340","🍁":"1f341","🍂":"1f342","🍃":"1f343","🪹":"1fab9","🪺":"1faba","🍄":"1f344","🍇":"1f347","🍈":"1f348","🍉":"1f349","🍊":"1f34a","🍋":"1f34b","🍌":"1f34c","🍍":"1f34d","🥭":"1f96d","🍎":"1f34e","🍏":"1f34f","🍐":"1f350","🍑":"1f351","🍒":"1f352","🍓":"1f353","🫐":"1fad0","🥝":"1f95d","🍅":"1f345","🫒":"1fad2","🥥":"1f965","🥑":"1f951","🍆":"1f346","🥔":"1f954","🥕":"1f955","🌽":"1f33d","🌶":"1f336","🫑":"1fad1","🥒":"1f952","🥬":"1f96c","🥦":"1f966","🧄":"1f9c4","🧅":"1f9c5","🥜":"1f95c","🫘":"1fad8","🌰":"1f330","🫚":"1fada","🫛":"1fadb","🍞":"1f35e","🥐":"1f950","🥖":"1f956","🫓":"1fad3","🥨":"1f968","🥯":"1f96f","🥞":"1f95e","🧇":"1f9c7","🧀":"1f9c0","🍖":"1f356","🍗":"1f357","🥩":"1f969","🥓":"1f953","🍔":"1f354","🍟":"1f35f","🍕":"1f355","🌭":"1f32d","🥪":"1f96a","🌮":"1f32e","🌯":"1f32f","🫔":"1fad4","🥙":"1f959","🧆":"1f9c6","🥚":"1f95a","🍳":"1f373","🥘":"1f958","🍲":"1f372","🫕":"1fad5","🥣":"1f963","🥗":"1f957","🍿":"1f37f","🧈":"1f9c8","🧂":"1f9c2","🥫":"1f96b","🍱":"1f371","🍘":"1f358","🍙":"1f359","🍚":"1f35a","🍛":"1f35b","🍜":"1f35c","🍝":"1f35d","🍠":"1f360","🍢":"1f362","🍣":"1f363","🍤":"1f364","🍥":"1f365","🥮":"1f96e","🍡":"1f361","🥟":"1f95f","🥠":"1f960","🥡":"1f961","🦀":"1f980","🦞":"1f99e","🦐":"1f990","🦑":"1f991","🦪":"1f9aa","🍦":"1f366","🍧":"1f367","🍨":"1f368","🍩":"1f369","🍪":"1f36a","🎂":"1f382","🍰":"1f370","🧁":"1f9c1","🥧":"1f967","🍫":"1f36b","🍬":"1f36c","🍭":"1f36d","🍮":"1f36e","🍯":"1f36f","🍼":"1f37c","🥛":"1f95b","☕":"2615","🫖":"1fad6","🍵":"1f375","🍶":"1f376","🍾":"1f37e","🍷":"1f377","🍸":"1f378","🍹":"1f379","🍺":"1f37a","🍻":"1f37b","🥂":"1f942","🥃":"1f943","🫗":"1fad7","🥤":"1f964","🧋":"1f9cb","🧃":"1f9c3","🧉":"1f9c9","🧊":"1f9ca","🥢":"1f962","🍽":"1f37d","🍴":"1f374","🥄":"1f944","🔪":"1f52a","🫙":"1fad9","🏺":"1f3fa","🌍":"1f30d","🌎":"1f30e","🌏":"1f30f","🌐":"1f310","🗺":"1f5fa","🗾":"1f5fe","🧭":"1f9ed","🏔":"1f3d4","⛰":"26f0","🌋":"1f30b","🗻":"1f5fb","🏕":"1f3d5","🏖":"1f3d6","🏜":"1f3dc","🏝":"1f3dd","🏞":"1f3de","🏟":"1f3df","🏛":"1f3db","🏗":"1f3d7","🧱":"1f9f1","🪨":"1faa8","🪵":"1fab5","🛖":"1f6d6","🏘":"1f3d8","🏚":"1f3da","🏠":"1f3e0","🏡":"1f3e1","🏢":"1f3e2","🏣":"1f3e3","🏤":"1f3e4","🏥":"1f3e5","🏦":"1f3e6","🏨":"1f3e8","🏩":"1f3e9","🏪":"1f3ea","🏫":"1f3eb","🏬":"1f3ec","🏭":"1f3ed","🏯":"1f3ef","🏰":"1f3f0","💒":"1f492","🗼":"1f5fc","🗽":"1f5fd","⛪":"26ea","🕌":"1f54c","🛕":"1f6d5","🕍":"1f54d","⛩":"26e9","🕋":"1f54b","⛲":"26f2","⛺":"26fa","🌁":"1f301","🌃":"1f303","🏙":"1f3d9","🌄":"1f304","🌅":"1f305","🌆":"1f306","🌇":"1f307","🌉":"1f309","♨":"2668","🎠":"1f3a0","🛝":"1f6dd","🎡":"1f3a1","🎢":"1f3a2","💈":"1f488","🎪":"1f3aa","🚂":"1f682","🚃":"1f683","🚄":"1f684","🚅":"1f685","🚆":"1f686","🚇":"1f687","🚈":"1f688","🚉":"1f689","🚊":"1f68a","🚝":"1f69d","🚞":"1f69e","🚋":"1f68b","🚌":"1f68c","🚍":"1f68d","🚎":"1f68e","🚐":"1f690","🚑":"1f691","🚒":"1f692","🚓":"1f693","🚔":"1f694","🚕":"1f695","🚖":"1f696","🚗":"1f697","🚘":"1f698","🚙":"1f699","🛻":"1f6fb","🚚":"1f69a","🚛":"1f69b","🚜":"1f69c","🏎":"1f3ce","🏍":"1f3cd","🛵":"1f6f5","🦽":"1f9bd","🦼":"1f9bc","🛺":"1f6fa","🚲":"1f6b2","🛴":"1f6f4","🛹":"1f6f9","🛼":"1f6fc","🚏":"1f68f","🛣":"1f6e3","🛤":"1f6e4","🛢":"1f6e2","⛽":"26fd","🛞":"1f6de","🚨":"1f6a8","🚥":"1f6a5","🚦":"1f6a6","🛑":"1f6d1","🚧":"1f6a7","⚓":"2693","🛟":"1f6df","⛵":"26f5","🛶":"1f6f6","🚤":"1f6a4","🛳":"1f6f3","⛴":"26f4","🛥":"1f6e5","🚢":"1f6a2","✈":"2708","🛩":"1f6e9","🛫":"1f6eb","🛬":"1f6ec","🪂":"1fa82","💺":"1f4ba","🚁":"1f681","🚟":"1f69f","🚠":"1f6a0","🚡":"1f6a1","🛰":"1f6f0","🚀":"1f680","🛸":"1f6f8","🛎":"1f6ce","🧳":"1f9f3","⌛":"231b","⏳":"23f3","⌚":"231a","⏰":"23f0","⏱":"23f1","⏲":"23f2","🕰":"1f570","🕛":"1f55b","🕧":"1f567","🕐":"1f550","🕜":"1f55c","🕑":"1f551","🕝":"1f55d","🕒":"1f552","🕞":"1f55e","🕓":"1f553","🕟":"1f55f","🕔":"1f554","🕠":"1f560","🕕":"1f555","🕡":"1f561","🕖":"1f556","🕢":"1f562","🕗":"1f557","🕣":"1f563","🕘":"1f558","🕤":"1f564","🕙":"1f559","🕥":"1f565","🕚":"1f55a","🕦":"1f566","🌑":"1f311","🌒":"1f312","🌓":"1f313","🌔":"1f314","🌕":"1f315","🌖":"1f316","🌗":"1f317","🌘":"1f318","🌙":"1f319","🌚":"1f31a","🌛":"1f31b","🌜":"1f31c","🌡":"1f321","☀":"2600","🌝":"1f31d","🌞":"1f31e","🪐":"1fa90","⭐":"2b50","🌟":"1f31f","🌠":"1f320","🌌":"1f30c","☁":"2601","⛅":"26c5","⛈":"26c8","🌤":"1f324","🌥":"1f325","🌦":"1f326","🌧":"1f327","🌨":"1f328","🌩":"1f329","🌪":"1f32a","🌫":"1f32b","🌬":"1f32c","🌀":"1f300","🌈":"1f308","🌂":"1f302","☂":"2602","☔":"2614","⛱":"26f1","⚡":"26a1","❄":"2744","☃":"2603","⛄":"26c4","☄":"2604","🔥":"1f525","💧":"1f4a7","🌊":"1f30a","🎃":"1f383","🎄":"1f384","🎆":"1f386","🎇":"1f387","🧨":"1f9e8","✨":"2728","🎈":"1f388","🎉":"1f389","🎊":"1f38a","🎋":"1f38b","🎍":"1f38d","🎎":"1f38e","🎏":"1f38f","🎐":"1f390","🎑":"1f391","🧧":"1f9e7","🎀":"1f380","🎁":"1f381","🎗":"1f397","🎟":"1f39f","🎫":"1f3ab","🎖":"1f396","🏆":"1f3c6","🏅":"1f3c5","🥇":"1f947","🥈":"1f948","🥉":"1f949","⚽":"26bd","⚾":"26be","🥎":"1f94e","🏀":"1f3c0","🏐":"1f3d0","🏈":"1f3c8","🏉":"1f3c9","🎾":"1f3be","🥏":"1f94f","🎳":"1f3b3","🏏":"1f3cf","🏑":"1f3d1","🏒":"1f3d2","🥍":"1f94d","🏓":"1f3d3","🏸":"1f3f8","🥊":"1f94a","🥋":"1f94b","🥅":"1f945","⛳":"26f3","⛸":"26f8","🎣":"1f3a3","🤿":"1f93f","🎽":"1f3bd","🎿":"1f3bf","🛷":"1f6f7","🥌":"1f94c","🎯":"1f3af","🪀":"1fa80","🪁":"1fa81","🔫":"1f52b","🎱":"1f3b1","🔮":"1f52e","🪄":"1fa84","🎮":"1f3ae","🕹":"1f579","🎰":"1f3b0","🎲":"1f3b2","🧩":"1f9e9","🧸":"1f9f8","🪅":"1fa85","🪩":"1faa9","🪆":"1fa86","♠":"2660","♥":"2665","♦":"2666","♣":"2663","♟":"265f","🃏":"1f0cf","🀄":"1f004","🎴":"1f3b4","🎭":"1f3ad","🖼":"1f5bc","🎨":"1f3a8","🧵":"1f9f5","🪡":"1faa1","🧶":"1f9f6","🪢":"1faa2","👓":"1f453","🕶":"1f576","🥽":"1f97d","🥼":"1f97c","🦺":"1f9ba","👔":"1f454","👕":"1f455","👖":"1f456","🧣":"1f9e3","🧤":"1f9e4","🧥":"1f9e5","🧦":"1f9e6","👗":"1f457","👘":"1f458","🥻":"1f97b","🩱":"1fa71","🩲":"1fa72","🩳":"1fa73","👙":"1f459","👚":"1f45a","🪭":"1faad","👛":"1f45b","👜":"1f45c","👝":"1f45d","🛍":"1f6cd","🎒":"1f392","🩴":"1fa74","👞":"1f45e","👟":"1f45f","🥾":"1f97e","🥿":"1f97f","👠":"1f460","👡":"1f461","🩰":"1fa70","👢":"1f462","🪮":"1faae","👑":"1f451","👒":"1f452","🎩":"1f3a9","🎓":"1f393","🧢":"1f9e2","🪖":"1fa96","⛑":"26d1","📿":"1f4ff","💄":"1f484","💍":"1f48d","💎":"1f48e","🔇":"1f507","🔈":"1f508","🔉":"1f509","🔊":"1f50a","📢":"1f4e2","📣":"1f4e3","📯":"1f4ef","🔔":"1f514","🔕":"1f515","🎼":"1f3bc","🎵":"1f3b5","🎶":"1f3b6","🎙":"1f399","🎚":"1f39a","🎛":"1f39b","🎤":"1f3a4","🎧":"1f3a7","📻":"1f4fb","🎷":"1f3b7","🪗":"1fa97","🎸":"1f3b8","🎹":"1f3b9","🎺":"1f3ba","🎻":"1f3bb","🪕":"1fa95","🥁":"1f941","🪘":"1fa98","🪇":"1fa87","🪈":"1fa88","📱":"1f4f1","📲":"1f4f2","☎":"260e","📞":"1f4de","📟":"1f4df","📠":"1f4e0","🔋":"1f50b","🪫":"1faab","🔌":"1f50c","💻":"1f4bb","🖥":"1f5a5","🖨":"1f5a8","⌨":"2328","🖱":"1f5b1","🖲":"1f5b2","💽":"1f4bd","💾":"1f4be","💿":"1f4bf","📀":"1f4c0","🧮":"1f9ee","🎥":"1f3a5","🎞":"1f39e","📽":"1f4fd","🎬":"1f3ac","📺":"1f4fa","📷":"1f4f7","📸":"1f4f8","📹":"1f4f9","📼":"1f4fc","🔍":"1f50d","🔎":"1f50e","🕯":"1f56f","💡":"1f4a1","🔦":"1f526","🏮":"1f3ee","🪔":"1fa94","📔":"1f4d4","📕":"1f4d5","📖":"1f4d6","📗":"1f4d7","📘":"1f4d8","📙":"1f4d9","📚":"1f4da","📓":"1f4d3","📒":"1f4d2","📃":"1f4c3","📜":"1f4dc","📄":"1f4c4","📰":"1f4f0","🗞":"1f5de","📑":"1f4d1","🔖":"1f516","🏷":"1f3f7","💰":"1f4b0","🪙":"1fa99","💴":"1f4b4","💵":"1f4b5","💶":"1f4b6","💷":"1f4b7","💸":"1f4b8","💳":"1f4b3","🧾":"1f9fe","💹":"1f4b9","✉":"2709","📧":"1f4e7","📨":"1f4e8","📩":"1f4e9","📤":"1f4e4","📥":"1f4e5","📦":"1f4e6","📫":"1f4eb","📪":"1f4ea","📬":"1f4ec","📭":"1f4ed","📮":"1f4ee","🗳":"1f5f3","✏":"270f","✒":"2712","🖋":"1f58b","🖊":"1f58a","🖌":"1f58c","🖍":"1f58d","📝":"1f4dd","💼":"1f4bc","📁":"1f4c1","📂":"1f4c2","🗂":"1f5c2","📅":"1f4c5","📆":"1f4c6","🗒":"1f5d2","🗓":"1f5d3","📇":"1f4c7","📈":"1f4c8","📉":"1f4c9","📊":"1f4ca","📋":"1f4cb","📌":"1f4cc","📍":"1f4cd","📎":"1f4ce","🖇":"1f587","📏":"1f4cf","📐":"1f4d0","✂":"2702","🗃":"1f5c3","🗄":"1f5c4","🗑":"1f5d1","🔒":"1f512","🔓":"1f513","🔏":"1f50f","🔐":"1f510","🔑":"1f511","🗝":"1f5dd","🔨":"1f528","🪓":"1fa93","⛏":"26cf","⚒":"2692","🛠":"1f6e0","🗡":"1f5e1","⚔":"2694","💣":"1f4a3","🪃":"1fa83","🏹":"1f3f9","🛡":"1f6e1","🪚":"1fa9a","🔧":"1f527","🪛":"1fa9b","🔩":"1f529","⚙":"2699","🗜":"1f5dc","⚖":"2696","🦯":"1f9af","🔗":"1f517","⛓":"26d3","🪝":"1fa9d","🧰":"1f9f0","🧲":"1f9f2","🪜":"1fa9c","⚗":"2697","🧪":"1f9ea","🧫":"1f9eb","🧬":"1f9ec","🔬":"1f52c","🔭":"1f52d","📡":"1f4e1","💉":"1f489","🩸":"1fa78","💊":"1f48a","🩹":"1fa79","🩼":"1fa7c","🩺":"1fa7a","🩻":"1fa7b","🚪":"1f6aa","🛗":"1f6d7","🪞":"1fa9e","🪟":"1fa9f","🛏":"1f6cf","🛋":"1f6cb","🪑":"1fa91","🚽":"1f6bd","🪠":"1faa0","🚿":"1f6bf","🛁":"1f6c1","🪤":"1faa4","🪒":"1fa92","🧴":"1f9f4","🧷":"1f9f7","🧹":"1f9f9","🧺":"1f9fa","🧻":"1f9fb","🪣":"1faa3","🧼":"1f9fc","🫧":"1fae7","🪥":"1faa5","🧽":"1f9fd","🧯":"1f9ef","🛒":"1f6d2","🚬":"1f6ac","⚰":"26b0","🪦":"1faa6","⚱":"26b1","🧿":"1f9ff","🪬":"1faac","🗿":"1f5ff","🪧":"1faa7","🪪":"1faaa","🏧":"1f3e7","🚮":"1f6ae","🚰":"1f6b0","♿":"267f","🚹":"1f6b9","🚺":"1f6ba","🚻":"1f6bb","🚼":"1f6bc","🚾":"1f6be","🛂":"1f6c2","🛃":"1f6c3","🛄":"1f6c4","🛅":"1f6c5","⚠":"26a0","🚸":"1f6b8","⛔":"26d4","🚫":"1f6ab","🚳":"1f6b3","🚭":"1f6ad","🚯":"1f6af","🚱":"1f6b1","🚷":"1f6b7","📵":"1f4f5","🔞":"1f51e","☢":"2622","☣":"2623","⬆":"2b06","↗":"2197","➡":"27a1","↘":"2198","⬇":"2b07","↙":"2199","⬅":"2b05","↖":"2196","↕":"2195","↔":"2194","↩":"21a9","↪":"21aa","⤴":"2934","⤵":"2935","🔃":"1f503","🔄":"1f504","🔙":"1f519","🔚":"1f51a","🔛":"1f51b","🔜":"1f51c","🔝":"1f51d","🛐":"1f6d0","⚛":"269b","🕉":"1f549","✡":"2721","☸":"2638","☯":"262f","✝":"271d","☦":"2626","☪":"262a","☮":"262e","🕎":"1f54e","🔯":"1f52f","🪯":"1faaf","♈":"2648","♉":"2649","♊":"264a","♋":"264b","♌":"264c","♍":"264d","♎":"264e","♏":"264f","♐":"2650","♑":"2651","♒":"2652","♓":"2653","⛎":"26ce","🔀":"1f500","🔁":"1f501","🔂":"1f502","▶":"25b6","⏩":"23e9","⏭":"23ed","⏯":"23ef","◀":"25c0","⏪":"23ea","⏮":"23ee","🔼":"1f53c","⏫":"23eb","🔽":"1f53d","⏬":"23ec","⏸":"23f8","⏹":"23f9","⏺":"23fa","⏏":"23cf","🎦":"1f3a6","🔅":"1f505","🔆":"1f506","📶":"1f4f6","🛜":"1f6dc","📳":"1f4f3","📴":"1f4f4","♀":"2640","♂":"2642","⚧":"26a7","✖":"2716","➕":"2795","➖":"2796","➗":"2797","🟰":"1f7f0","♾":"267e","‼":"203c","⁉":"2049","❓":"2753","❔":"2754","❕":"2755","❗":"2757","〰":"3030","💱":"1f4b1","💲":"1f4b2","⚕":"2695","♻":"267b","⚜":"269c","🔱":"1f531","📛":"1f4db","🔰":"1f530","⭕":"2b55","✅":"2705","☑":"2611","✔":"2714","❌":"274c","❎":"274e","➰":"27b0","➿":"27bf","〽":"303d","✳":"2733","✴":"2734","❇":"2747","©":"a9","®":"ae","™":"2122","🔟":"1f51f","🔠":"1f520","🔡":"1f521","🔢":"1f522","🔣":"1f523","🔤":"1f524","🅰":"1f170","🆎":"1f18e","🅱":"1f171","🆑":"1f191","🆒":"1f192","🆓":"1f193","ℹ":"2139","🆔":"1f194","Ⓜ":"24c2","🆕":"1f195","🆖":"1f196","🅾":"1f17e","🆗":"1f197","🅿":"1f17f","🆘":"1f198","🆙":"1f199","🆚":"1f19a","🈁":"1f201","🈂":"1f202","🈷":"1f237","🈶":"1f236","🈯":"1f22f","🉐":"1f250","🈹":"1f239","🈚":"1f21a","🈲":"1f232","🉑":"1f251","🈸":"1f238","🈴":"1f234","🈳":"1f233","㊗":"3297","㊙":"3299","🈺":"1f23a","🈵":"1f235","🔴":"1f534","🟠":"1f7e0","🟡":"1f7e1","🟢":"1f7e2","🔵":"1f535","🟣":"1f7e3","🟤":"1f7e4","⚫":"26ab","⚪":"26aa","🟥":"1f7e5","🟧":"1f7e7","🟨":"1f7e8","🟩":"1f7e9","🟦":"1f7e6","🟪":"1f7ea","🟫":"1f7eb","⬛":"2b1b","⬜":"2b1c","◼":"25fc","◻":"25fb","◾":"25fe","◽":"25fd","▪":"25aa","▫":"25ab","🔶":"1f536","🔷":"1f537","🔸":"1f538","🔹":"1f539","🔺":"1f53a","🔻":"1f53b","💠":"1f4a0","🔘":"1f518","🔳":"1f533","🔲":"1f532","🏁":"1f3c1","🚩":"1f6a9","🎌":"1f38c","🏴":"1f3f4","🏳":"1f3f3","☺️":"263a","☹️":"2639","☠️":"2620","❣️":"2763","❤️":"2764","🕳️":"1f573","🗨️":"1f5e8","🗯️":"1f5ef","👋🏻":"1f44b-1f3fb","👋🏼":"1f44b-1f3fc","👋🏽":"1f44b-1f3fd","👋🏾":"1f44b-1f3fe","👋🏿":"1f44b-1f3ff","🤚🏻":"1f91a-1f3fb","🤚🏼":"1f91a-1f3fc","🤚🏽":"1f91a-1f3fd","🤚🏾":"1f91a-1f3fe","🤚🏿":"1f91a-1f3ff","🖐️":"1f590","🖐🏻":"1f590-1f3fb","🖐🏼":"1f590-1f3fc","🖐🏽":"1f590-1f3fd","🖐🏾":"1f590-1f3fe","🖐🏿":"1f590-1f3ff","✋🏻":"270b-1f3fb","✋🏼":"270b-1f3fc","✋🏽":"270b-1f3fd","✋🏾":"270b-1f3fe","✋🏿":"270b-1f3ff","🖖🏻":"1f596-1f3fb","🖖🏼":"1f596-1f3fc","🖖🏽":"1f596-1f3fd","🖖🏾":"1f596-1f3fe","🖖🏿":"1f596-1f3ff","🫱🏻":"1faf1-1f3fb","🫱🏼":"1faf1-1f3fc","🫱🏽":"1faf1-1f3fd","🫱🏾":"1faf1-1f3fe","🫱🏿":"1faf1-1f3ff","🫲🏻":"1faf2-1f3fb","🫲🏼":"1faf2-1f3fc","🫲🏽":"1faf2-1f3fd","🫲🏾":"1faf2-1f3fe","🫲🏿":"1faf2-1f3ff","🫳🏻":"1faf3-1f3fb","🫳🏼":"1faf3-1f3fc","🫳🏽":"1faf3-1f3fd","🫳🏾":"1faf3-1f3fe","🫳🏿":"1faf3-1f3ff","🫴🏻":"1faf4-1f3fb","🫴🏼":"1faf4-1f3fc","🫴🏽":"1faf4-1f3fd","🫴🏾":"1faf4-1f3fe","🫴🏿":"1faf4-1f3ff","🫷🏻":"1faf7-1f3fb","🫷🏼":"1faf7-1f3fc","🫷🏽":"1faf7-1f3fd","🫷🏾":"1faf7-1f3fe","🫷🏿":"1faf7-1f3ff","🫸🏻":"1faf8-1f3fb","🫸🏼":"1faf8-1f3fc","🫸🏽":"1faf8-1f3fd","🫸🏾":"1faf8-1f3fe","🫸🏿":"1faf8-1f3ff","👌🏻":"1f44c-1f3fb","👌🏼":"1f44c-1f3fc","👌🏽":"1f44c-1f3fd","👌🏾":"1f44c-1f3fe","👌🏿":"1f44c-1f3ff","🤌🏻":"1f90c-1f3fb","🤌🏼":"1f90c-1f3fc","🤌🏽":"1f90c-1f3fd","🤌🏾":"1f90c-1f3fe","🤌🏿":"1f90c-1f3ff","🤏🏻":"1f90f-1f3fb","🤏🏼":"1f90f-1f3fc","🤏🏽":"1f90f-1f3fd","🤏🏾":"1f90f-1f3fe","🤏🏿":"1f90f-1f3ff","✌️":"270c","✌🏻":"270c-1f3fb","✌🏼":"270c-1f3fc","✌🏽":"270c-1f3fd","✌🏾":"270c-1f3fe","✌🏿":"270c-1f3ff","🤞🏻":"1f91e-1f3fb","🤞🏼":"1f91e-1f3fc","🤞🏽":"1f91e-1f3fd","🤞🏾":"1f91e-1f3fe","🤞🏿":"1f91e-1f3ff","🫰🏻":"1faf0-1f3fb","🫰🏼":"1faf0-1f3fc","🫰🏽":"1faf0-1f3fd","🫰🏾":"1faf0-1f3fe","🫰🏿":"1faf0-1f3ff","🤟🏻":"1f91f-1f3fb","🤟🏼":"1f91f-1f3fc","🤟🏽":"1f91f-1f3fd","🤟🏾":"1f91f-1f3fe","🤟🏿":"1f91f-1f3ff","🤘🏻":"1f918-1f3fb","🤘🏼":"1f918-1f3fc","🤘🏽":"1f918-1f3fd","🤘🏾":"1f918-1f3fe","🤘🏿":"1f918-1f3ff","🤙🏻":"1f919-1f3fb","🤙🏼":"1f919-1f3fc","🤙🏽":"1f919-1f3fd","🤙🏾":"1f919-1f3fe","🤙🏿":"1f919-1f3ff","👈🏻":"1f448-1f3fb","👈🏼":"1f448-1f3fc","👈🏽":"1f448-1f3fd","👈🏾":"1f448-1f3fe","👈🏿":"1f448-1f3ff","👉🏻":"1f449-1f3fb","👉🏼":"1f449-1f3fc","👉🏽":"1f449-1f3fd","👉🏾":"1f449-1f3fe","👉🏿":"1f449-1f3ff","👆🏻":"1f446-1f3fb","👆🏼":"1f446-1f3fc","👆🏽":"1f446-1f3fd","👆🏾":"1f446-1f3fe","👆🏿":"1f446-1f3ff","🖕🏻":"1f595-1f3fb","🖕🏼":"1f595-1f3fc","🖕🏽":"1f595-1f3fd","🖕🏾":"1f595-1f3fe","🖕🏿":"1f595-1f3ff","👇🏻":"1f447-1f3fb","👇🏼":"1f447-1f3fc","👇🏽":"1f447-1f3fd","👇🏾":"1f447-1f3fe","👇🏿":"1f447-1f3ff","☝️":"261d","☝🏻":"261d-1f3fb","☝🏼":"261d-1f3fc","☝🏽":"261d-1f3fd","☝🏾":"261d-1f3fe","☝🏿":"261d-1f3ff","🫵🏻":"1faf5-1f3fb","🫵🏼":"1faf5-1f3fc","🫵🏽":"1faf5-1f3fd","🫵🏾":"1faf5-1f3fe","🫵🏿":"1faf5-1f3ff","👍🏻":"1f44d-1f3fb","👍🏼":"1f44d-1f3fc","👍🏽":"1f44d-1f3fd","👍🏾":"1f44d-1f3fe","👍🏿":"1f44d-1f3ff","👎🏻":"1f44e-1f3fb","👎🏼":"1f44e-1f3fc","👎🏽":"1f44e-1f3fd","👎🏾":"1f44e-1f3fe","👎🏿":"1f44e-1f3ff","✊🏻":"270a-1f3fb","✊🏼":"270a-1f3fc","✊🏽":"270a-1f3fd","✊🏾":"270a-1f3fe","✊🏿":"270a-1f3ff","👊🏻":"1f44a-1f3fb","👊🏼":"1f44a-1f3fc","👊🏽":"1f44a-1f3fd","👊🏾":"1f44a-1f3fe","👊🏿":"1f44a-1f3ff","🤛🏻":"1f91b-1f3fb","🤛🏼":"1f91b-1f3fc","🤛🏽":"1f91b-1f3fd","🤛🏾":"1f91b-1f3fe","🤛🏿":"1f91b-1f3ff","🤜🏻":"1f91c-1f3fb","🤜🏼":"1f91c-1f3fc","🤜🏽":"1f91c-1f3fd","🤜🏾":"1f91c-1f3fe","🤜🏿":"1f91c-1f3ff","👏🏻":"1f44f-1f3fb","👏🏼":"1f44f-1f3fc","👏🏽":"1f44f-1f3fd","👏🏾":"1f44f-1f3fe","👏🏿":"1f44f-1f3ff","🙌🏻":"1f64c-1f3fb","🙌🏼":"1f64c-1f3fc","🙌🏽":"1f64c-1f3fd","🙌🏾":"1f64c-1f3fe","🙌🏿":"1f64c-1f3ff","🫶🏻":"1faf6-1f3fb","🫶🏼":"1faf6-1f3fc","🫶🏽":"1faf6-1f3fd","🫶🏾":"1faf6-1f3fe","🫶🏿":"1faf6-1f3ff","👐🏻":"1f450-1f3fb","👐🏼":"1f450-1f3fc","👐🏽":"1f450-1f3fd","👐🏾":"1f450-1f3fe","👐🏿":"1f450-1f3ff","🤲🏻":"1f932-1f3fb","🤲🏼":"1f932-1f3fc","🤲🏽":"1f932-1f3fd","🤲🏾":"1f932-1f3fe","🤲🏿":"1f932-1f3ff","🤝🏻":"1f91d-1f3fb","🤝🏼":"1f91d-1f3fc","🤝🏽":"1f91d-1f3fd","🤝🏾":"1f91d-1f3fe","🤝🏿":"1f91d-1f3ff","🙏🏻":"1f64f-1f3fb","🙏🏼":"1f64f-1f3fc","🙏🏽":"1f64f-1f3fd","🙏🏾":"1f64f-1f3fe","🙏🏿":"1f64f-1f3ff","✍️":"270d","✍🏻":"270d-1f3fb","✍🏼":"270d-1f3fc","✍🏽":"270d-1f3fd","✍🏾":"270d-1f3fe","✍🏿":"270d-1f3ff","💅🏻":"1f485-1f3fb","💅🏼":"1f485-1f3fc","💅🏽":"1f485-1f3fd","💅🏾":"1f485-1f3fe","💅🏿":"1f485-1f3ff","🤳🏻":"1f933-1f3fb","🤳🏼":"1f933-1f3fc","🤳🏽":"1f933-1f3fd","🤳🏾":"1f933-1f3fe","🤳🏿":"1f933-1f3ff","💪🏻":"1f4aa-1f3fb","💪🏼":"1f4aa-1f3fc","💪🏽":"1f4aa-1f3fd","💪🏾":"1f4aa-1f3fe","💪🏿":"1f4aa-1f3ff","🦵🏻":"1f9b5-1f3fb","🦵🏼":"1f9b5-1f3fc","🦵🏽":"1f9b5-1f3fd","🦵🏾":"1f9b5-1f3fe","🦵🏿":"1f9b5-1f3ff","🦶🏻":"1f9b6-1f3fb","🦶🏼":"1f9b6-1f3fc","🦶🏽":"1f9b6-1f3fd","🦶🏾":"1f9b6-1f3fe","🦶🏿":"1f9b6-1f3ff","👂🏻":"1f442-1f3fb","👂🏼":"1f442-1f3fc","👂🏽":"1f442-1f3fd","👂🏾":"1f442-1f3fe","👂🏿":"1f442-1f3ff","🦻🏻":"1f9bb-1f3fb","🦻🏼":"1f9bb-1f3fc","🦻🏽":"1f9bb-1f3fd","🦻🏾":"1f9bb-1f3fe","🦻🏿":"1f9bb-1f3ff","👃🏻":"1f443-1f3fb","👃🏼":"1f443-1f3fc","👃🏽":"1f443-1f3fd","👃🏾":"1f443-1f3fe","👃🏿":"1f443-1f3ff","👁️":"1f441","👶🏻":"1f476-1f3fb","👶🏼":"1f476-1f3fc","👶🏽":"1f476-1f3fd","👶🏾":"1f476-1f3fe","👶🏿":"1f476-1f3ff","🧒🏻":"1f9d2-1f3fb","🧒🏼":"1f9d2-1f3fc","🧒🏽":"1f9d2-1f3fd","🧒🏾":"1f9d2-1f3fe","🧒🏿":"1f9d2-1f3ff","👦🏻":"1f466-1f3fb","👦🏼":"1f466-1f3fc","👦🏽":"1f466-1f3fd","👦🏾":"1f466-1f3fe","👦🏿":"1f466-1f3ff","👧🏻":"1f467-1f3fb","👧🏼":"1f467-1f3fc","👧🏽":"1f467-1f3fd","👧🏾":"1f467-1f3fe","👧🏿":"1f467-1f3ff","🧑🏻":"1f9d1-1f3fb","🧑🏼":"1f9d1-1f3fc","🧑🏽":"1f9d1-1f3fd","🧑🏾":"1f9d1-1f3fe","🧑🏿":"1f9d1-1f3ff","👱🏻":"1f471-1f3fb","👱🏼":"1f471-1f3fc","👱🏽":"1f471-1f3fd","👱🏾":"1f471-1f3fe","👱🏿":"1f471-1f3ff","👨🏻":"1f468-1f3fb","👨🏼":"1f468-1f3fc","👨🏽":"1f468-1f3fd","👨🏾":"1f468-1f3fe","👨🏿":"1f468-1f3ff","🧔🏻":"1f9d4-1f3fb","🧔🏼":"1f9d4-1f3fc","🧔🏽":"1f9d4-1f3fd","🧔🏾":"1f9d4-1f3fe","🧔🏿":"1f9d4-1f3ff","👩🏻":"1f469-1f3fb","👩🏼":"1f469-1f3fc","👩🏽":"1f469-1f3fd","👩🏾":"1f469-1f3fe","👩🏿":"1f469-1f3ff","🧓🏻":"1f9d3-1f3fb","🧓🏼":"1f9d3-1f3fc","🧓🏽":"1f9d3-1f3fd","🧓🏾":"1f9d3-1f3fe","🧓🏿":"1f9d3-1f3ff","👴🏻":"1f474-1f3fb","👴🏼":"1f474-1f3fc","👴🏽":"1f474-1f3fd","👴🏾":"1f474-1f3fe","👴🏿":"1f474-1f3ff","👵🏻":"1f475-1f3fb","👵🏼":"1f475-1f3fc","👵🏽":"1f475-1f3fd","👵🏾":"1f475-1f3fe","👵🏿":"1f475-1f3ff","🙍🏻":"1f64d-1f3fb","🙍🏼":"1f64d-1f3fc","🙍🏽":"1f64d-1f3fd","🙍🏾":"1f64d-1f3fe","🙍🏿":"1f64d-1f3ff","🙎🏻":"1f64e-1f3fb","🙎🏼":"1f64e-1f3fc","🙎🏽":"1f64e-1f3fd","🙎🏾":"1f64e-1f3fe","🙎🏿":"1f64e-1f3ff","🙅🏻":"1f645-1f3fb","🙅🏼":"1f645-1f3fc","🙅🏽":"1f645-1f3fd","🙅🏾":"1f645-1f3fe","🙅🏿":"1f645-1f3ff","🙆🏻":"1f646-1f3fb","🙆🏼":"1f646-1f3fc","🙆🏽":"1f646-1f3fd","🙆🏾":"1f646-1f3fe","🙆🏿":"1f646-1f3ff","💁🏻":"1f481-1f3fb","💁🏼":"1f481-1f3fc","💁🏽":"1f481-1f3fd","💁🏾":"1f481-1f3fe","💁🏿":"1f481-1f3ff","🙋🏻":"1f64b-1f3fb","🙋🏼":"1f64b-1f3fc","🙋🏽":"1f64b-1f3fd","🙋🏾":"1f64b-1f3fe","🙋🏿":"1f64b-1f3ff","🧏🏻":"1f9cf-1f3fb","🧏🏼":"1f9cf-1f3fc","🧏🏽":"1f9cf-1f3fd","🧏🏾":"1f9cf-1f3fe","🧏🏿":"1f9cf-1f3ff","🙇🏻":"1f647-1f3fb","🙇🏼":"1f647-1f3fc","🙇🏽":"1f647-1f3fd","🙇🏾":"1f647-1f3fe","🙇🏿":"1f647-1f3ff","🤦🏻":"1f926-1f3fb","🤦🏼":"1f926-1f3fc","🤦🏽":"1f926-1f3fd","🤦🏾":"1f926-1f3fe","🤦🏿":"1f926-1f3ff","🤷🏻":"1f937-1f3fb","🤷🏼":"1f937-1f3fc","🤷🏽":"1f937-1f3fd","🤷🏾":"1f937-1f3fe","🤷🏿":"1f937-1f3ff","👮🏻":"1f46e-1f3fb","👮🏼":"1f46e-1f3fc","👮🏽":"1f46e-1f3fd","👮🏾":"1f46e-1f3fe","👮🏿":"1f46e-1f3ff","🕵️":"1f575","🕵🏻":"1f575-1f3fb","🕵🏼":"1f575-1f3fc","🕵🏽":"1f575-1f3fd","🕵🏾":"1f575-1f3fe","🕵🏿":"1f575-1f3ff","💂🏻":"1f482-1f3fb","💂🏼":"1f482-1f3fc","💂🏽":"1f482-1f3fd","💂🏾":"1f482-1f3fe","💂🏿":"1f482-1f3ff","🥷🏻":"1f977-1f3fb","🥷🏼":"1f977-1f3fc","🥷🏽":"1f977-1f3fd","🥷🏾":"1f977-1f3fe","🥷🏿":"1f977-1f3ff","👷🏻":"1f477-1f3fb","👷🏼":"1f477-1f3fc","👷🏽":"1f477-1f3fd","👷🏾":"1f477-1f3fe","👷🏿":"1f477-1f3ff","🫅🏻":"1fac5-1f3fb","🫅🏼":"1fac5-1f3fc","🫅🏽":"1fac5-1f3fd","🫅🏾":"1fac5-1f3fe","🫅🏿":"1fac5-1f3ff","🤴🏻":"1f934-1f3fb","🤴🏼":"1f934-1f3fc","🤴🏽":"1f934-1f3fd","🤴🏾":"1f934-1f3fe","🤴🏿":"1f934-1f3ff","👸🏻":"1f478-1f3fb","👸🏼":"1f478-1f3fc","👸🏽":"1f478-1f3fd","👸🏾":"1f478-1f3fe","👸🏿":"1f478-1f3ff","👳🏻":"1f473-1f3fb","👳🏼":"1f473-1f3fc","👳🏽":"1f473-1f3fd","👳🏾":"1f473-1f3fe","👳🏿":"1f473-1f3ff","👲🏻":"1f472-1f3fb","👲🏼":"1f472-1f3fc","👲🏽":"1f472-1f3fd","👲🏾":"1f472-1f3fe","👲🏿":"1f472-1f3ff","🧕🏻":"1f9d5-1f3fb","🧕🏼":"1f9d5-1f3fc","🧕🏽":"1f9d5-1f3fd","🧕🏾":"1f9d5-1f3fe","🧕🏿":"1f9d5-1f3ff","🤵🏻":"1f935-1f3fb","🤵🏼":"1f935-1f3fc","🤵🏽":"1f935-1f3fd","🤵🏾":"1f935-1f3fe","🤵🏿":"1f935-1f3ff","👰🏻":"1f470-1f3fb","👰🏼":"1f470-1f3fc","👰🏽":"1f470-1f3fd","👰🏾":"1f470-1f3fe","👰🏿":"1f470-1f3ff","🤰🏻":"1f930-1f3fb","🤰🏼":"1f930-1f3fc","🤰🏽":"1f930-1f3fd","🤰🏾":"1f930-1f3fe","🤰🏿":"1f930-1f3ff","🫃🏻":"1fac3-1f3fb","🫃🏼":"1fac3-1f3fc","🫃🏽":"1fac3-1f3fd","🫃🏾":"1fac3-1f3fe","🫃🏿":"1fac3-1f3ff","🫄🏻":"1fac4-1f3fb","🫄🏼":"1fac4-1f3fc","🫄🏽":"1fac4-1f3fd","🫄🏾":"1fac4-1f3fe","🫄🏿":"1fac4-1f3ff","🤱🏻":"1f931-1f3fb","🤱🏼":"1f931-1f3fc","🤱🏽":"1f931-1f3fd","🤱🏾":"1f931-1f3fe","🤱🏿":"1f931-1f3ff","👼🏻":"1f47c-1f3fb","👼🏼":"1f47c-1f3fc","👼🏽":"1f47c-1f3fd","👼🏾":"1f47c-1f3fe","👼🏿":"1f47c-1f3ff","🎅🏻":"1f385-1f3fb","🎅🏼":"1f385-1f3fc","🎅🏽":"1f385-1f3fd","🎅🏾":"1f385-1f3fe","🎅🏿":"1f385-1f3ff","🤶🏻":"1f936-1f3fb","🤶🏼":"1f936-1f3fc","🤶🏽":"1f936-1f3fd","🤶🏾":"1f936-1f3fe","🤶🏿":"1f936-1f3ff","🦸🏻":"1f9b8-1f3fb","🦸🏼":"1f9b8-1f3fc","🦸🏽":"1f9b8-1f3fd","🦸🏾":"1f9b8-1f3fe","🦸🏿":"1f9b8-1f3ff","🦹🏻":"1f9b9-1f3fb","🦹🏼":"1f9b9-1f3fc","🦹🏽":"1f9b9-1f3fd","🦹🏾":"1f9b9-1f3fe","🦹🏿":"1f9b9-1f3ff","🧙🏻":"1f9d9-1f3fb","🧙🏼":"1f9d9-1f3fc","🧙🏽":"1f9d9-1f3fd","🧙🏾":"1f9d9-1f3fe","🧙🏿":"1f9d9-1f3ff","🧚🏻":"1f9da-1f3fb","🧚🏼":"1f9da-1f3fc","🧚🏽":"1f9da-1f3fd","🧚🏾":"1f9da-1f3fe","🧚🏿":"1f9da-1f3ff","🧛🏻":"1f9db-1f3fb","🧛🏼":"1f9db-1f3fc","🧛🏽":"1f9db-1f3fd","🧛🏾":"1f9db-1f3fe","🧛🏿":"1f9db-1f3ff","🧜🏻":"1f9dc-1f3fb","🧜🏼":"1f9dc-1f3fc","🧜🏽":"1f9dc-1f3fd","🧜🏾":"1f9dc-1f3fe","🧜🏿":"1f9dc-1f3ff","🧝🏻":"1f9dd-1f3fb","🧝🏼":"1f9dd-1f3fc","🧝🏽":"1f9dd-1f3fd","🧝🏾":"1f9dd-1f3fe","🧝🏿":"1f9dd-1f3ff","💆🏻":"1f486-1f3fb","💆🏼":"1f486-1f3fc","💆🏽":"1f486-1f3fd","💆🏾":"1f486-1f3fe","💆🏿":"1f486-1f3ff","💇🏻":"1f487-1f3fb","💇🏼":"1f487-1f3fc","💇🏽":"1f487-1f3fd","💇🏾":"1f487-1f3fe","💇🏿":"1f487-1f3ff","🚶🏻":"1f6b6-1f3fb","🚶🏼":"1f6b6-1f3fc","🚶🏽":"1f6b6-1f3fd","🚶🏾":"1f6b6-1f3fe","🚶🏿":"1f6b6-1f3ff","🧍🏻":"1f9cd-1f3fb","🧍🏼":"1f9cd-1f3fc","🧍🏽":"1f9cd-1f3fd","🧍🏾":"1f9cd-1f3fe","🧍🏿":"1f9cd-1f3ff","🧎🏻":"1f9ce-1f3fb","🧎🏼":"1f9ce-1f3fc","🧎🏽":"1f9ce-1f3fd","🧎🏾":"1f9ce-1f3fe","🧎🏿":"1f9ce-1f3ff","🏃🏻":"1f3c3-1f3fb","🏃🏼":"1f3c3-1f3fc","🏃🏽":"1f3c3-1f3fd","🏃🏾":"1f3c3-1f3fe","🏃🏿":"1f3c3-1f3ff","💃🏻":"1f483-1f3fb","💃🏼":"1f483-1f3fc","💃🏽":"1f483-1f3fd","💃🏾":"1f483-1f3fe","💃🏿":"1f483-1f3ff","🕺🏻":"1f57a-1f3fb","🕺🏼":"1f57a-1f3fc","🕺🏽":"1f57a-1f3fd","🕺🏾":"1f57a-1f3fe","🕺🏿":"1f57a-1f3ff","🕴️":"1f574","🕴🏻":"1f574-1f3fb","🕴🏼":"1f574-1f3fc","🕴🏽":"1f574-1f3fd","🕴🏾":"1f574-1f3fe","🕴🏿":"1f574-1f3ff","🧖🏻":"1f9d6-1f3fb","🧖🏼":"1f9d6-1f3fc","🧖🏽":"1f9d6-1f3fd","🧖🏾":"1f9d6-1f3fe","🧖🏿":"1f9d6-1f3ff","🧗🏻":"1f9d7-1f3fb","🧗🏼":"1f9d7-1f3fc","🧗🏽":"1f9d7-1f3fd","🧗🏾":"1f9d7-1f3fe","🧗🏿":"1f9d7-1f3ff","🏇🏻":"1f3c7-1f3fb","🏇🏼":"1f3c7-1f3fc","🏇🏽":"1f3c7-1f3fd","🏇🏾":"1f3c7-1f3fe","🏇🏿":"1f3c7-1f3ff","⛷️":"26f7","🏂🏻":"1f3c2-1f3fb","🏂🏼":"1f3c2-1f3fc","🏂🏽":"1f3c2-1f3fd","🏂🏾":"1f3c2-1f3fe","🏂🏿":"1f3c2-1f3ff","🏌️":"1f3cc","🏌🏻":"1f3cc-1f3fb","🏌🏼":"1f3cc-1f3fc","🏌🏽":"1f3cc-1f3fd","🏌🏾":"1f3cc-1f3fe","🏌🏿":"1f3cc-1f3ff","🏄🏻":"1f3c4-1f3fb","🏄🏼":"1f3c4-1f3fc","🏄🏽":"1f3c4-1f3fd","🏄🏾":"1f3c4-1f3fe","🏄🏿":"1f3c4-1f3ff","🚣🏻":"1f6a3-1f3fb","🚣🏼":"1f6a3-1f3fc","🚣🏽":"1f6a3-1f3fd","🚣🏾":"1f6a3-1f3fe","🚣🏿":"1f6a3-1f3ff","🏊🏻":"1f3ca-1f3fb","🏊🏼":"1f3ca-1f3fc","🏊🏽":"1f3ca-1f3fd","🏊🏾":"1f3ca-1f3fe","🏊🏿":"1f3ca-1f3ff","⛹️":"26f9","⛹🏻":"26f9-1f3fb","⛹🏼":"26f9-1f3fc","⛹🏽":"26f9-1f3fd","⛹🏾":"26f9-1f3fe","⛹🏿":"26f9-1f3ff","🏋️":"1f3cb","🏋🏻":"1f3cb-1f3fb","🏋🏼":"1f3cb-1f3fc","🏋🏽":"1f3cb-1f3fd","🏋🏾":"1f3cb-1f3fe","🏋🏿":"1f3cb-1f3ff","🚴🏻":"1f6b4-1f3fb","🚴🏼":"1f6b4-1f3fc","🚴🏽":"1f6b4-1f3fd","🚴🏾":"1f6b4-1f3fe","🚴🏿":"1f6b4-1f3ff","🚵🏻":"1f6b5-1f3fb","🚵🏼":"1f6b5-1f3fc","🚵🏽":"1f6b5-1f3fd","🚵🏾":"1f6b5-1f3fe","🚵🏿":"1f6b5-1f3ff","🤸🏻":"1f938-1f3fb","🤸🏼":"1f938-1f3fc","🤸🏽":"1f938-1f3fd","🤸🏾":"1f938-1f3fe","🤸🏿":"1f938-1f3ff","🤽🏻":"1f93d-1f3fb","🤽🏼":"1f93d-1f3fc","🤽🏽":"1f93d-1f3fd","🤽🏾":"1f93d-1f3fe","🤽🏿":"1f93d-1f3ff","🤾🏻":"1f93e-1f3fb","🤾🏼":"1f93e-1f3fc","🤾🏽":"1f93e-1f3fd","🤾🏾":"1f93e-1f3fe","🤾🏿":"1f93e-1f3ff","🤹🏻":"1f939-1f3fb","🤹🏼":"1f939-1f3fc","🤹🏽":"1f939-1f3fd","🤹🏾":"1f939-1f3fe","🤹🏿":"1f939-1f3ff","🧘🏻":"1f9d8-1f3fb","🧘🏼":"1f9d8-1f3fc","🧘🏽":"1f9d8-1f3fd","🧘🏾":"1f9d8-1f3fe","🧘🏿":"1f9d8-1f3ff","🛀🏻":"1f6c0-1f3fb","🛀🏼":"1f6c0-1f3fc","🛀🏽":"1f6c0-1f3fd","🛀🏾":"1f6c0-1f3fe","🛀🏿":"1f6c0-1f3ff","🛌🏻":"1f6cc-1f3fb","🛌🏼":"1f6cc-1f3fc","🛌🏽":"1f6cc-1f3fd","🛌🏾":"1f6cc-1f3fe","🛌🏿":"1f6cc-1f3ff","👭🏻":"1f46d-1f3fb","👭🏼":"1f46d-1f3fc","👭🏽":"1f46d-1f3fd","👭🏾":"1f46d-1f3fe","👭🏿":"1f46d-1f3ff","👫🏻":"1f46b-1f3fb","👫🏼":"1f46b-1f3fc","👫🏽":"1f46b-1f3fd","👫🏾":"1f46b-1f3fe","👫🏿":"1f46b-1f3ff","👬🏻":"1f46c-1f3fb","👬🏼":"1f46c-1f3fc","👬🏽":"1f46c-1f3fd","👬🏾":"1f46c-1f3fe","👬🏿":"1f46c-1f3ff","💏🏻":"1f48f-1f3fb","💏🏼":"1f48f-1f3fc","💏🏽":"1f48f-1f3fd","💏🏾":"1f48f-1f3fe","💏🏿":"1f48f-1f3ff","💑🏻":"1f491-1f3fb","💑🏼":"1f491-1f3fc","💑🏽":"1f491-1f3fd","💑🏾":"1f491-1f3fe","💑🏿":"1f491-1f3ff","🗣️":"1f5e3","🐿️":"1f43f","🕊️":"1f54a","🕷️":"1f577","🕸️":"1f578","🏵️":"1f3f5","☘️":"2618","🌶️":"1f336","🍽️":"1f37d","🗺️":"1f5fa","🏔️":"1f3d4","⛰️":"26f0","🏕️":"1f3d5","🏖️":"1f3d6","🏜️":"1f3dc","🏝️":"1f3dd","🏞️":"1f3de","🏟️":"1f3df","🏛️":"1f3db","🏗️":"1f3d7","🏘️":"1f3d8","🏚️":"1f3da","⛩️":"26e9","🏙️":"1f3d9","♨️":"2668","🏎️":"1f3ce","🏍️":"1f3cd","🛣️":"1f6e3","🛤️":"1f6e4","🛢️":"1f6e2","🛳️":"1f6f3","⛴️":"26f4","🛥️":"1f6e5","✈️":"2708","🛩️":"1f6e9","🛰️":"1f6f0","🛎️":"1f6ce","⏱️":"23f1","⏲️":"23f2","🕰️":"1f570","🌡️":"1f321","☀️":"2600","☁️":"2601","⛈️":"26c8","🌤️":"1f324","🌥️":"1f325","🌦️":"1f326","🌧️":"1f327","🌨️":"1f328","🌩️":"1f329","🌪️":"1f32a","🌫️":"1f32b","🌬️":"1f32c","☂️":"2602","⛱️":"26f1","❄️":"2744","☃️":"2603","☄️":"2604","🎗️":"1f397","🎟️":"1f39f","🎖️":"1f396","⛸️":"26f8","🕹️":"1f579","♠️":"2660","♥️":"2665","♦️":"2666","♣️":"2663","♟️":"265f","🖼️":"1f5bc","🕶️":"1f576","🛍️":"1f6cd","⛑️":"26d1","🎙️":"1f399","🎚️":"1f39a","🎛️":"1f39b","☎️":"260e","🖥️":"1f5a5","🖨️":"1f5a8","⌨️":"2328","🖱️":"1f5b1","🖲️":"1f5b2","🎞️":"1f39e","📽️":"1f4fd","🕯️":"1f56f","🗞️":"1f5de","🏷️":"1f3f7","✉️":"2709","🗳️":"1f5f3","✏️":"270f","✒️":"2712","🖋️":"1f58b","🖊️":"1f58a","🖌️":"1f58c","🖍️":"1f58d","🗂️":"1f5c2","🗒️":"1f5d2","🗓️":"1f5d3","🖇️":"1f587","✂️":"2702","🗃️":"1f5c3","🗄️":"1f5c4","🗑️":"1f5d1","🗝️":"1f5dd","⛏️":"26cf","⚒️":"2692","🛠️":"1f6e0","🗡️":"1f5e1","⚔️":"2694","🛡️":"1f6e1","⚙️":"2699","🗜️":"1f5dc","⚖️":"2696","⛓️":"26d3","⚗️":"2697","🛏️":"1f6cf","🛋️":"1f6cb","⚰️":"26b0","⚱️":"26b1","⚠️":"26a0","☢️":"2622","☣️":"2623","⬆️":"2b06","↗️":"2197","➡️":"27a1","↘️":"2198","⬇️":"2b07","↙️":"2199","⬅️":"2b05","↖️":"2196","↕️":"2195","↔️":"2194","↩️":"21a9","↪️":"21aa","⤴️":"2934","⤵️":"2935","⚛️":"269b","🕉️":"1f549","✡️":"2721","☸️":"2638","☯️":"262f","✝️":"271d","☦️":"2626","☪️":"262a","☮️":"262e","▶️":"25b6","⏭️":"23ed","⏯️":"23ef","◀️":"25c0","⏮️":"23ee","⏸️":"23f8","⏹️":"23f9","⏺️":"23fa","⏏️":"23cf","♀️":"2640","♂️":"2642","⚧️":"26a7","✖️":"2716","♾️":"267e","‼️":"203c","⁉️":"2049","〰️":"3030","⚕️":"2695","♻️":"267b","⚜️":"269c","☑️":"2611","✔️":"2714","〽️":"303d","✳️":"2733","✴️":"2734","❇️":"2747","©️":"a9","®️":"ae","™️":"2122","#⃣":"23-20e3","*⃣":"2a-20e3","0⃣":"30-20e3","1⃣":"31-20e3","2⃣":"32-20e3","3⃣":"33-20e3","4⃣":"34-20e3","5⃣":"35-20e3","6⃣":"36-20e3","7⃣":"37-20e3","8⃣":"38-20e3","9⃣":"39-20e3","🅰️":"1f170","🅱️":"1f171","ℹ️":"2139","Ⓜ️":"24c2","🅾️":"1f17e","🅿️":"1f17f","🈂️":"1f202","🈷️":"1f237","㊗️":"3297","㊙️":"3299","◼️":"25fc","◻️":"25fb","▪️":"25aa","▫️":"25ab","🏳️":"1f3f3","🇦🇨":"1f1e6-1f1e8","🇦🇩":"1f1e6-1f1e9","🇦🇪":"1f1e6-1f1ea","🇦🇫":"1f1e6-1f1eb","🇦🇬":"1f1e6-1f1ec","🇦🇮":"1f1e6-1f1ee","🇦🇱":"1f1e6-1f1f1","🇦🇲":"1f1e6-1f1f2","🇦🇴":"1f1e6-1f1f4","🇦🇶":"1f1e6-1f1f6","🇦🇷":"1f1e6-1f1f7","🇦🇸":"1f1e6-1f1f8","🇦🇹":"1f1e6-1f1f9","🇦🇺":"1f1e6-1f1fa","🇦🇼":"1f1e6-1f1fc","🇦🇽":"1f1e6-1f1fd","🇦🇿":"1f1e6-1f1ff","🇧🇦":"1f1e7-1f1e6","🇧🇧":"1f1e7-1f1e7","🇧🇩":"1f1e7-1f1e9","🇧🇪":"1f1e7-1f1ea","🇧🇫":"1f1e7-1f1eb","🇧🇬":"1f1e7-1f1ec","🇧🇭":"1f1e7-1f1ed","🇧🇮":"1f1e7-1f1ee","🇧🇯":"1f1e7-1f1ef","🇧🇱":"1f1e7-1f1f1","🇧🇲":"1f1e7-1f1f2","🇧🇳":"1f1e7-1f1f3","🇧🇴":"1f1e7-1f1f4","🇧🇶":"1f1e7-1f1f6","🇧🇷":"1f1e7-1f1f7","🇧🇸":"1f1e7-1f1f8","🇧🇹":"1f1e7-1f1f9","🇧🇻":"1f1e7-1f1fb","🇧🇼":"1f1e7-1f1fc","🇧🇾":"1f1e7-1f1fe","🇧🇿":"1f1e7-1f1ff","🇨🇦":"1f1e8-1f1e6","🇨🇨":"1f1e8-1f1e8","🇨🇩":"1f1e8-1f1e9","🇨🇫":"1f1e8-1f1eb","🇨🇬":"1f1e8-1f1ec","🇨🇭":"1f1e8-1f1ed","🇨🇮":"1f1e8-1f1ee","🇨🇰":"1f1e8-1f1f0","🇨🇱":"1f1e8-1f1f1","🇨🇲":"1f1e8-1f1f2","🇨🇳":"1f1e8-1f1f3","🇨🇴":"1f1e8-1f1f4","🇨🇵":"1f1e8-1f1f5","🇨🇷":"1f1e8-1f1f7","🇨🇺":"1f1e8-1f1fa","🇨🇻":"1f1e8-1f1fb","🇨🇼":"1f1e8-1f1fc","🇨🇽":"1f1e8-1f1fd","🇨🇾":"1f1e8-1f1fe","🇨🇿":"1f1e8-1f1ff","🇩🇪":"1f1e9-1f1ea","🇩🇬":"1f1e9-1f1ec","🇩🇯":"1f1e9-1f1ef","🇩🇰":"1f1e9-1f1f0","🇩🇲":"1f1e9-1f1f2","🇩🇴":"1f1e9-1f1f4","🇩🇿":"1f1e9-1f1ff","🇪🇦":"1f1ea-1f1e6","🇪🇨":"1f1ea-1f1e8","🇪🇪":"1f1ea-1f1ea","🇪🇬":"1f1ea-1f1ec","🇪🇭":"1f1ea-1f1ed","🇪🇷":"1f1ea-1f1f7","🇪🇸":"1f1ea-1f1f8","🇪🇹":"1f1ea-1f1f9","🇪🇺":"1f1ea-1f1fa","🇫🇮":"1f1eb-1f1ee","🇫🇯":"1f1eb-1f1ef","🇫🇰":"1f1eb-1f1f0","🇫🇲":"1f1eb-1f1f2","🇫🇴":"1f1eb-1f1f4","🇫🇷":"1f1eb-1f1f7","🇬🇦":"1f1ec-1f1e6","🇬🇧":"1f1ec-1f1e7","🇬🇩":"1f1ec-1f1e9","🇬🇪":"1f1ec-1f1ea","🇬🇫":"1f1ec-1f1eb","🇬🇬":"1f1ec-1f1ec","🇬🇭":"1f1ec-1f1ed","🇬🇮":"1f1ec-1f1ee","🇬🇱":"1f1ec-1f1f1","🇬🇲":"1f1ec-1f1f2","🇬🇳":"1f1ec-1f1f3","🇬🇵":"1f1ec-1f1f5","🇬🇶":"1f1ec-1f1f6","🇬🇷":"1f1ec-1f1f7","🇬🇸":"1f1ec-1f1f8","🇬🇹":"1f1ec-1f1f9","🇬🇺":"1f1ec-1f1fa","🇬🇼":"1f1ec-1f1fc","🇬🇾":"1f1ec-1f1fe","🇭🇰":"1f1ed-1f1f0","🇭🇲":"1f1ed-1f1f2","🇭🇳":"1f1ed-1f1f3","🇭🇷":"1f1ed-1f1f7","🇭🇹":"1f1ed-1f1f9","🇭🇺":"1f1ed-1f1fa","🇮🇨":"1f1ee-1f1e8","🇮🇩":"1f1ee-1f1e9","🇮🇪":"1f1ee-1f1ea","🇮🇱":"1f1ee-1f1f1","🇮🇲":"1f1ee-1f1f2","🇮🇳":"1f1ee-1f1f3","🇮🇴":"1f1ee-1f1f4","🇮🇶":"1f1ee-1f1f6","🇮🇷":"1f1ee-1f1f7","🇮🇸":"1f1ee-1f1f8","🇮🇹":"1f1ee-1f1f9","🇯🇪":"1f1ef-1f1ea","🇯🇲":"1f1ef-1f1f2","🇯🇴":"1f1ef-1f1f4","🇯🇵":"1f1ef-1f1f5","🇰🇪":"1f1f0-1f1ea","🇰🇬":"1f1f0-1f1ec","🇰🇭":"1f1f0-1f1ed","🇰🇮":"1f1f0-1f1ee","🇰🇲":"1f1f0-1f1f2","🇰🇳":"1f1f0-1f1f3","🇰🇵":"1f1f0-1f1f5","🇰🇷":"1f1f0-1f1f7","🇰🇼":"1f1f0-1f1fc","🇰🇾":"1f1f0-1f1fe","🇰🇿":"1f1f0-1f1ff","🇱🇦":"1f1f1-1f1e6","🇱🇧":"1f1f1-1f1e7","🇱🇨":"1f1f1-1f1e8","🇱🇮":"1f1f1-1f1ee","🇱🇰":"1f1f1-1f1f0","🇱🇷":"1f1f1-1f1f7","🇱🇸":"1f1f1-1f1f8","🇱🇹":"1f1f1-1f1f9","🇱🇺":"1f1f1-1f1fa","🇱🇻":"1f1f1-1f1fb","🇱🇾":"1f1f1-1f1fe","🇲🇦":"1f1f2-1f1e6","🇲🇨":"1f1f2-1f1e8","🇲🇩":"1f1f2-1f1e9","🇲🇪":"1f1f2-1f1ea","🇲🇫":"1f1f2-1f1eb","🇲🇬":"1f1f2-1f1ec","🇲🇭":"1f1f2-1f1ed","🇲🇰":"1f1f2-1f1f0","🇲🇱":"1f1f2-1f1f1","🇲🇲":"1f1f2-1f1f2","🇲🇳":"1f1f2-1f1f3","🇲🇴":"1f1f2-1f1f4","🇲🇵":"1f1f2-1f1f5","🇲🇶":"1f1f2-1f1f6","🇲🇷":"1f1f2-1f1f7","🇲🇸":"1f1f2-1f1f8","🇲🇹":"1f1f2-1f1f9","🇲🇺":"1f1f2-1f1fa","🇲🇻":"1f1f2-1f1fb","🇲🇼":"1f1f2-1f1fc","🇲🇽":"1f1f2-1f1fd","🇲🇾":"1f1f2-1f1fe","🇲🇿":"1f1f2-1f1ff","🇳🇦":"1f1f3-1f1e6","🇳🇨":"1f1f3-1f1e8","🇳🇪":"1f1f3-1f1ea","🇳🇫":"1f1f3-1f1eb","🇳🇬":"1f1f3-1f1ec","🇳🇮":"1f1f3-1f1ee","🇳🇱":"1f1f3-1f1f1","🇳🇴":"1f1f3-1f1f4","🇳🇵":"1f1f3-1f1f5","🇳🇷":"1f1f3-1f1f7","🇳🇺":"1f1f3-1f1fa","🇳🇿":"1f1f3-1f1ff","🇴🇲":"1f1f4-1f1f2","🇵🇦":"1f1f5-1f1e6","🇵🇪":"1f1f5-1f1ea","🇵🇫":"1f1f5-1f1eb","🇵🇬":"1f1f5-1f1ec","🇵🇭":"1f1f5-1f1ed","🇵🇰":"1f1f5-1f1f0","🇵🇱":"1f1f5-1f1f1","🇵🇲":"1f1f5-1f1f2","🇵🇳":"1f1f5-1f1f3","🇵🇷":"1f1f5-1f1f7","🇵🇸":"1f1f5-1f1f8","🇵🇹":"1f1f5-1f1f9","🇵🇼":"1f1f5-1f1fc","🇵🇾":"1f1f5-1f1fe","🇶🇦":"1f1f6-1f1e6","🇷🇪":"1f1f7-1f1ea","🇷🇴":"1f1f7-1f1f4","🇷🇸":"1f1f7-1f1f8","🇷🇺":"1f1f7-1f1fa","🇷🇼":"1f1f7-1f1fc","🇸🇦":"1f1f8-1f1e6","🇸🇧":"1f1f8-1f1e7","🇸🇨":"1f1f8-1f1e8","🇸🇩":"1f1f8-1f1e9","🇸🇪":"1f1f8-1f1ea","🇸🇬":"1f1f8-1f1ec","🇸🇭":"1f1f8-1f1ed","🇸🇮":"1f1f8-1f1ee","🇸🇯":"1f1f8-1f1ef","🇸🇰":"1f1f8-1f1f0","🇸🇱":"1f1f8-1f1f1","🇸🇲":"1f1f8-1f1f2","🇸🇳":"1f1f8-1f1f3","🇸🇴":"1f1f8-1f1f4","🇸🇷":"1f1f8-1f1f7","🇸🇸":"1f1f8-1f1f8","🇸🇹":"1f1f8-1f1f9","🇸🇻":"1f1f8-1f1fb","🇸🇽":"1f1f8-1f1fd","🇸🇾":"1f1f8-1f1fe","🇸🇿":"1f1f8-1f1ff","🇹🇦":"1f1f9-1f1e6","🇹🇨":"1f1f9-1f1e8","🇹🇩":"1f1f9-1f1e9","🇹🇫":"1f1f9-1f1eb","🇹🇬":"1f1f9-1f1ec","🇹🇭":"1f1f9-1f1ed","🇹🇯":"1f1f9-1f1ef","🇹🇰":"1f1f9-1f1f0","🇹🇱":"1f1f9-1f1f1","🇹🇲":"1f1f9-1f1f2","🇹🇳":"1f1f9-1f1f3","🇹🇴":"1f1f9-1f1f4","🇹🇷":"1f1f9-1f1f7","🇹🇹":"1f1f9-1f1f9","🇹🇻":"1f1f9-1f1fb","🇹🇼":"1f1f9-1f1fc","🇹🇿":"1f1f9-1f1ff","🇺🇦":"1f1fa-1f1e6","🇺🇬":"1f1fa-1f1ec","🇺🇲":"1f1fa-1f1f2","🇺🇳":"1f1fa-1f1f3","🇺🇸":"1f1fa-1f1f8","🇺🇾":"1f1fa-1f1fe","🇺🇿":"1f1fa-1f1ff","🇻🇦":"1f1fb-1f1e6","🇻🇨":"1f1fb-1f1e8","🇻🇪":"1f1fb-1f1ea","🇻🇬":"1f1fb-1f1ec","🇻🇮":"1f1fb-1f1ee","🇻🇳":"1f1fb-1f1f3","🇻🇺":"1f1fb-1f1fa","🇼🇫":"1f1fc-1f1eb","🇼🇸":"1f1fc-1f1f8","🇽🇰":"1f1fd-1f1f0","🇾🇪":"1f1fe-1f1ea","🇾🇹":"1f1fe-1f1f9","🇿🇦":"1f1ff-1f1e6","🇿🇲":"1f1ff-1f1f2","🇿🇼":"1f1ff-1f1fc","😶‍🌫":"1f636-200d-1f32b-fe0f","😮‍💨":"1f62e-200d-1f4a8","🙂‍↔":"1f642-200d-2194-fe0f","🙂‍↕":"1f642-200d-2195-fe0f","😵‍💫":"1f635-200d-1f4ab","❤‍🔥":"2764-fe0f-200d-1f525","❤‍🩹":"2764-fe0f-200d-1fa79","👁‍🗨":"1f441-200d-1f5e8","🧔‍♂":"1f9d4-200d-2642-fe0f","🧔‍♀":"1f9d4-200d-2640-fe0f","👨‍🦰":"1f468-200d-1f9b0","👨‍🦱":"1f468-200d-1f9b1","👨‍🦳":"1f468-200d-1f9b3","👨‍🦲":"1f468-200d-1f9b2","👩‍🦰":"1f469-200d-1f9b0","🧑‍🦰":"1f9d1-200d-1f9b0","👩‍🦱":"1f469-200d-1f9b1","🧑‍🦱":"1f9d1-200d-1f9b1","👩‍🦳":"1f469-200d-1f9b3","🧑‍🦳":"1f9d1-200d-1f9b3","👩‍🦲":"1f469-200d-1f9b2","🧑‍🦲":"1f9d1-200d-1f9b2","👱‍♀":"1f471-200d-2640-fe0f","👱‍♂":"1f471-200d-2642-fe0f","🙍‍♂":"1f64d-200d-2642-fe0f","🙍‍♀":"1f64d-200d-2640-fe0f","🙎‍♂":"1f64e-200d-2642-fe0f","🙎‍♀":"1f64e-200d-2640-fe0f","🙅‍♂":"1f645-200d-2642-fe0f","🙅‍♀":"1f645-200d-2640-fe0f","🙆‍♂":"1f646-200d-2642-fe0f","🙆‍♀":"1f646-200d-2640-fe0f","💁‍♂":"1f481-200d-2642-fe0f","💁‍♀":"1f481-200d-2640-fe0f","🙋‍♂":"1f64b-200d-2642-fe0f","🙋‍♀":"1f64b-200d-2640-fe0f","🧏‍♂":"1f9cf-200d-2642-fe0f","🧏‍♀":"1f9cf-200d-2640-fe0f","🙇‍♂":"1f647-200d-2642-fe0f","🙇‍♀":"1f647-200d-2640-fe0f","🤦‍♂":"1f926-200d-2642-fe0f","🤦‍♀":"1f926-200d-2640-fe0f","🤷‍♂":"1f937-200d-2642-fe0f","🤷‍♀":"1f937-200d-2640-fe0f","🧑‍⚕":"1f9d1-200d-2695-fe0f","👨‍⚕":"1f468-200d-2695-fe0f","👩‍⚕":"1f469-200d-2695-fe0f","🧑‍🎓":"1f9d1-200d-1f393","👨‍🎓":"1f468-200d-1f393","👩‍🎓":"1f469-200d-1f393","🧑‍🏫":"1f9d1-200d-1f3eb","👨‍🏫":"1f468-200d-1f3eb","👩‍🏫":"1f469-200d-1f3eb","🧑‍⚖":"1f9d1-200d-2696-fe0f","👨‍⚖":"1f468-200d-2696-fe0f","👩‍⚖":"1f469-200d-2696-fe0f","🧑‍🌾":"1f9d1-200d-1f33e","👨‍🌾":"1f468-200d-1f33e","👩‍🌾":"1f469-200d-1f33e","🧑‍🍳":"1f9d1-200d-1f373","👨‍🍳":"1f468-200d-1f373","👩‍🍳":"1f469-200d-1f373","🧑‍🔧":"1f9d1-200d-1f527","👨‍🔧":"1f468-200d-1f527","👩‍🔧":"1f469-200d-1f527","🧑‍🏭":"1f9d1-200d-1f3ed","👨‍🏭":"1f468-200d-1f3ed","👩‍🏭":"1f469-200d-1f3ed","🧑‍💼":"1f9d1-200d-1f4bc","👨‍💼":"1f468-200d-1f4bc","👩‍💼":"1f469-200d-1f4bc","🧑‍🔬":"1f9d1-200d-1f52c","👨‍🔬":"1f468-200d-1f52c","👩‍🔬":"1f469-200d-1f52c","🧑‍💻":"1f9d1-200d-1f4bb","👨‍💻":"1f468-200d-1f4bb","👩‍💻":"1f469-200d-1f4bb","🧑‍🎤":"1f9d1-200d-1f3a4","👨‍🎤":"1f468-200d-1f3a4","👩‍🎤":"1f469-200d-1f3a4","🧑‍🎨":"1f9d1-200d-1f3a8","👨‍🎨":"1f468-200d-1f3a8","👩‍🎨":"1f469-200d-1f3a8","🧑‍✈":"1f9d1-200d-2708-fe0f","👨‍✈":"1f468-200d-2708-fe0f","👩‍✈":"1f469-200d-2708-fe0f","🧑‍🚀":"1f9d1-200d-1f680","👨‍🚀":"1f468-200d-1f680","👩‍🚀":"1f469-200d-1f680","🧑‍🚒":"1f9d1-200d-1f692","👨‍🚒":"1f468-200d-1f692","👩‍🚒":"1f469-200d-1f692","👮‍♂":"1f46e-200d-2642-fe0f","👮‍♀":"1f46e-200d-2640-fe0f","🕵‍♂":"1f575-fe0f-200d-2642-fe0f","🕵‍♀":"1f575-fe0f-200d-2640-fe0f","💂‍♂":"1f482-200d-2642-fe0f","💂‍♀":"1f482-200d-2640-fe0f","👷‍♂":"1f477-200d-2642-fe0f","👷‍♀":"1f477-200d-2640-fe0f","👳‍♂":"1f473-200d-2642-fe0f","👳‍♀":"1f473-200d-2640-fe0f","🤵‍♂":"1f935-200d-2642-fe0f","🤵‍♀":"1f935-200d-2640-fe0f","👰‍♂":"1f470-200d-2642-fe0f","👰‍♀":"1f470-200d-2640-fe0f","👩‍🍼":"1f469-200d-1f37c","👨‍🍼":"1f468-200d-1f37c","🧑‍🍼":"1f9d1-200d-1f37c","🧑‍🎄":"1f9d1-200d-1f384","🦸‍♂":"1f9b8-200d-2642-fe0f","🦸‍♀":"1f9b8-200d-2640-fe0f","🦹‍♂":"1f9b9-200d-2642-fe0f","🦹‍♀":"1f9b9-200d-2640-fe0f","🧙‍♂":"1f9d9-200d-2642-fe0f","🧙‍♀":"1f9d9-200d-2640-fe0f","🧚‍♂":"1f9da-200d-2642-fe0f","🧚‍♀":"1f9da-200d-2640-fe0f","🧛‍♂":"1f9db-200d-2642-fe0f","🧛‍♀":"1f9db-200d-2640-fe0f","🧜‍♂":"1f9dc-200d-2642-fe0f","🧜‍♀":"1f9dc-200d-2640-fe0f","🧝‍♂":"1f9dd-200d-2642-fe0f","🧝‍♀":"1f9dd-200d-2640-fe0f","🧞‍♂":"1f9de-200d-2642-fe0f","🧞‍♀":"1f9de-200d-2640-fe0f","🧟‍♂":"1f9df-200d-2642-fe0f","🧟‍♀":"1f9df-200d-2640-fe0f","💆‍♂":"1f486-200d-2642-fe0f","💆‍♀":"1f486-200d-2640-fe0f","💇‍♂":"1f487-200d-2642-fe0f","💇‍♀":"1f487-200d-2640-fe0f","🚶‍♂":"1f6b6-200d-2642-fe0f","🚶‍♀":"1f6b6-200d-2640-fe0f","🚶‍➡":"1f6b6-200d-27a1-fe0f","🧍‍♂":"1f9cd-200d-2642-fe0f","🧍‍♀":"1f9cd-200d-2640-fe0f","🧎‍♂":"1f9ce-200d-2642-fe0f","🧎‍♀":"1f9ce-200d-2640-fe0f","🧎‍➡":"1f9ce-200d-27a1-fe0f","🧑‍🦯":"1f9d1-200d-1f9af","👨‍🦯":"1f468-200d-1f9af","👩‍🦯":"1f469-200d-1f9af","🧑‍🦼":"1f9d1-200d-1f9bc","👨‍🦼":"1f468-200d-1f9bc","👩‍🦼":"1f469-200d-1f9bc","🧑‍🦽":"1f9d1-200d-1f9bd","👨‍🦽":"1f468-200d-1f9bd","👩‍🦽":"1f469-200d-1f9bd","🏃‍♂":"1f3c3-200d-2642-fe0f","🏃‍♀":"1f3c3-200d-2640-fe0f","🏃‍➡":"1f3c3-200d-27a1-fe0f","👯‍♂":"1f46f-200d-2642-fe0f","👯‍♀":"1f46f-200d-2640-fe0f","🧖‍♂":"1f9d6-200d-2642-fe0f","🧖‍♀":"1f9d6-200d-2640-fe0f","🧗‍♂":"1f9d7-200d-2642-fe0f","🧗‍♀":"1f9d7-200d-2640-fe0f","🏌‍♂":"1f3cc-fe0f-200d-2642-fe0f","🏌‍♀":"1f3cc-fe0f-200d-2640-fe0f","🏄‍♂":"1f3c4-200d-2642-fe0f","🏄‍♀":"1f3c4-200d-2640-fe0f","🚣‍♂":"1f6a3-200d-2642-fe0f","🚣‍♀":"1f6a3-200d-2640-fe0f","🏊‍♂":"1f3ca-200d-2642-fe0f","🏊‍♀":"1f3ca-200d-2640-fe0f","⛹‍♂":"26f9-fe0f-200d-2642-fe0f","⛹‍♀":"26f9-fe0f-200d-2640-fe0f","🏋‍♂":"1f3cb-fe0f-200d-2642-fe0f","🏋‍♀":"1f3cb-fe0f-200d-2640-fe0f","🚴‍♂":"1f6b4-200d-2642-fe0f","🚴‍♀":"1f6b4-200d-2640-fe0f","🚵‍♂":"1f6b5-200d-2642-fe0f","🚵‍♀":"1f6b5-200d-2640-fe0f","🤸‍♂":"1f938-200d-2642-fe0f","🤸‍♀":"1f938-200d-2640-fe0f","🤼‍♂":"1f93c-200d-2642-fe0f","🤼‍♀":"1f93c-200d-2640-fe0f","🤽‍♂":"1f93d-200d-2642-fe0f","🤽‍♀":"1f93d-200d-2640-fe0f","🤾‍♂":"1f93e-200d-2642-fe0f","🤾‍♀":"1f93e-200d-2640-fe0f","🤹‍♂":"1f939-200d-2642-fe0f","🤹‍♀":"1f939-200d-2640-fe0f","🧘‍♂":"1f9d8-200d-2642-fe0f","🧘‍♀":"1f9d8-200d-2640-fe0f","👨‍👦":"1f468-200d-1f466","👨‍👧":"1f468-200d-1f467","👩‍👦":"1f469-200d-1f466","👩‍👧":"1f469-200d-1f467","🧑‍🧒":"1f9d1-200d-1f9d2","🐕‍🦺":"1f415-200d-1f9ba","🐈‍⬛":"1f408-200d-2b1b","🐻‍❄":"1f43b-200d-2744-fe0f","🐦‍⬛":"1f426-200d-2b1b","🐦‍🔥":"1f426-200d-1f525","🍋‍🟩":"1f34b-200d-1f7e9","🍄‍🟫":"1f344-200d-1f7eb","⛓‍💥":"26d3-fe0f-200d-1f4a5","#️⃣":"23-20e3","*️⃣":"2a-20e3","0️⃣":"30-20e3","1️⃣":"31-20e3","2️⃣":"32-20e3","3️⃣":"33-20e3","4️⃣":"34-20e3","5️⃣":"35-20e3","6️⃣":"36-20e3","7️⃣":"37-20e3","8️⃣":"38-20e3","9️⃣":"39-20e3","🏳‍🌈":"1f3f3-fe0f-200d-1f308","🏳‍⚧":"1f3f3-fe0f-200d-26a7-fe0f","🏴‍☠":"1f3f4-200d-2620-fe0f","😶‍🌫️":"1f636-200d-1f32b-fe0f","🙂‍↔️":"1f642-200d-2194-fe0f","🙂‍↕️":"1f642-200d-2195-fe0f","❤️‍🔥":"2764-fe0f-200d-1f525","❤️‍🩹":"2764-fe0f-200d-1fa79","👁‍🗨️":"1f441-200d-1f5e8","👁️‍🗨":"1f441-200d-1f5e8","🧔‍♂️":"1f9d4-200d-2642-fe0f","🧔🏻‍♂":"1f9d4-1f3fb-200d-2642-fe0f","🧔🏼‍♂":"1f9d4-1f3fc-200d-2642-fe0f","🧔🏽‍♂":"1f9d4-1f3fd-200d-2642-fe0f","🧔🏾‍♂":"1f9d4-1f3fe-200d-2642-fe0f","🧔🏿‍♂":"1f9d4-1f3ff-200d-2642-fe0f","🧔‍♀️":"1f9d4-200d-2640-fe0f","🧔🏻‍♀":"1f9d4-1f3fb-200d-2640-fe0f","🧔🏼‍♀":"1f9d4-1f3fc-200d-2640-fe0f","🧔🏽‍♀":"1f9d4-1f3fd-200d-2640-fe0f","🧔🏾‍♀":"1f9d4-1f3fe-200d-2640-fe0f","🧔🏿‍♀":"1f9d4-1f3ff-200d-2640-fe0f","👨🏻‍🦰":"1f468-1f3fb-200d-1f9b0","👨🏼‍🦰":"1f468-1f3fc-200d-1f9b0","👨🏽‍🦰":"1f468-1f3fd-200d-1f9b0","👨🏾‍🦰":"1f468-1f3fe-200d-1f9b0","👨🏿‍🦰":"1f468-1f3ff-200d-1f9b0","👨🏻‍🦱":"1f468-1f3fb-200d-1f9b1","👨🏼‍🦱":"1f468-1f3fc-200d-1f9b1","👨🏽‍🦱":"1f468-1f3fd-200d-1f9b1","👨🏾‍🦱":"1f468-1f3fe-200d-1f9b1","👨🏿‍🦱":"1f468-1f3ff-200d-1f9b1","👨🏻‍🦳":"1f468-1f3fb-200d-1f9b3","👨🏼‍🦳":"1f468-1f3fc-200d-1f9b3","👨🏽‍🦳":"1f468-1f3fd-200d-1f9b3","👨🏾‍🦳":"1f468-1f3fe-200d-1f9b3","👨🏿‍🦳":"1f468-1f3ff-200d-1f9b3","👨🏻‍🦲":"1f468-1f3fb-200d-1f9b2","👨🏼‍🦲":"1f468-1f3fc-200d-1f9b2","👨🏽‍🦲":"1f468-1f3fd-200d-1f9b2","👨🏾‍🦲":"1f468-1f3fe-200d-1f9b2","👨🏿‍🦲":"1f468-1f3ff-200d-1f9b2","👩🏻‍🦰":"1f469-1f3fb-200d-1f9b0","👩🏼‍🦰":"1f469-1f3fc-200d-1f9b0","👩🏽‍🦰":"1f469-1f3fd-200d-1f9b0","👩🏾‍🦰":"1f469-1f3fe-200d-1f9b0","👩🏿‍🦰":"1f469-1f3ff-200d-1f9b0","🧑🏻‍🦰":"1f9d1-1f3fb-200d-1f9b0","🧑🏼‍🦰":"1f9d1-1f3fc-200d-1f9b0","🧑🏽‍🦰":"1f9d1-1f3fd-200d-1f9b0","🧑🏾‍🦰":"1f9d1-1f3fe-200d-1f9b0","🧑🏿‍🦰":"1f9d1-1f3ff-200d-1f9b0","👩🏻‍🦱":"1f469-1f3fb-200d-1f9b1","👩🏼‍🦱":"1f469-1f3fc-200d-1f9b1","👩🏽‍🦱":"1f469-1f3fd-200d-1f9b1","👩🏾‍🦱":"1f469-1f3fe-200d-1f9b1","👩🏿‍🦱":"1f469-1f3ff-200d-1f9b1","🧑🏻‍🦱":"1f9d1-1f3fb-200d-1f9b1","🧑🏼‍🦱":"1f9d1-1f3fc-200d-1f9b1","🧑🏽‍🦱":"1f9d1-1f3fd-200d-1f9b1","🧑🏾‍🦱":"1f9d1-1f3fe-200d-1f9b1","🧑🏿‍🦱":"1f9d1-1f3ff-200d-1f9b1","👩🏻‍🦳":"1f469-1f3fb-200d-1f9b3","👩🏼‍🦳":"1f469-1f3fc-200d-1f9b3","👩🏽‍🦳":"1f469-1f3fd-200d-1f9b3","👩🏾‍🦳":"1f469-1f3fe-200d-1f9b3","👩🏿‍🦳":"1f469-1f3ff-200d-1f9b3","🧑🏻‍🦳":"1f9d1-1f3fb-200d-1f9b3","🧑🏼‍🦳":"1f9d1-1f3fc-200d-1f9b3","🧑🏽‍🦳":"1f9d1-1f3fd-200d-1f9b3","🧑🏾‍🦳":"1f9d1-1f3fe-200d-1f9b3","🧑🏿‍🦳":"1f9d1-1f3ff-200d-1f9b3","👩🏻‍🦲":"1f469-1f3fb-200d-1f9b2","👩🏼‍🦲":"1f469-1f3fc-200d-1f9b2","👩🏽‍🦲":"1f469-1f3fd-200d-1f9b2","👩🏾‍🦲":"1f469-1f3fe-200d-1f9b2","👩🏿‍🦲":"1f469-1f3ff-200d-1f9b2","🧑🏻‍🦲":"1f9d1-1f3fb-200d-1f9b2","🧑🏼‍🦲":"1f9d1-1f3fc-200d-1f9b2","🧑🏽‍🦲":"1f9d1-1f3fd-200d-1f9b2","🧑🏾‍🦲":"1f9d1-1f3fe-200d-1f9b2","🧑🏿‍🦲":"1f9d1-1f3ff-200d-1f9b2","👱‍♀️":"1f471-200d-2640-fe0f","👱🏻‍♀":"1f471-1f3fb-200d-2640-fe0f","👱🏼‍♀":"1f471-1f3fc-200d-2640-fe0f","👱🏽‍♀":"1f471-1f3fd-200d-2640-fe0f","👱🏾‍♀":"1f471-1f3fe-200d-2640-fe0f","👱🏿‍♀":"1f471-1f3ff-200d-2640-fe0f","👱‍♂️":"1f471-200d-2642-fe0f","👱🏻‍♂":"1f471-1f3fb-200d-2642-fe0f","👱🏼‍♂":"1f471-1f3fc-200d-2642-fe0f","👱🏽‍♂":"1f471-1f3fd-200d-2642-fe0f","👱🏾‍♂":"1f471-1f3fe-200d-2642-fe0f","👱🏿‍♂":"1f471-1f3ff-200d-2642-fe0f","🙍‍♂️":"1f64d-200d-2642-fe0f","🙍🏻‍♂":"1f64d-1f3fb-200d-2642-fe0f","🙍🏼‍♂":"1f64d-1f3fc-200d-2642-fe0f","🙍🏽‍♂":"1f64d-1f3fd-200d-2642-fe0f","🙍🏾‍♂":"1f64d-1f3fe-200d-2642-fe0f","🙍🏿‍♂":"1f64d-1f3ff-200d-2642-fe0f","🙍‍♀️":"1f64d-200d-2640-fe0f","🙍🏻‍♀":"1f64d-1f3fb-200d-2640-fe0f","🙍🏼‍♀":"1f64d-1f3fc-200d-2640-fe0f","🙍🏽‍♀":"1f64d-1f3fd-200d-2640-fe0f","🙍🏾‍♀":"1f64d-1f3fe-200d-2640-fe0f","🙍🏿‍♀":"1f64d-1f3ff-200d-2640-fe0f","🙎‍♂️":"1f64e-200d-2642-fe0f","🙎🏻‍♂":"1f64e-1f3fb-200d-2642-fe0f","🙎🏼‍♂":"1f64e-1f3fc-200d-2642-fe0f","🙎🏽‍♂":"1f64e-1f3fd-200d-2642-fe0f","🙎🏾‍♂":"1f64e-1f3fe-200d-2642-fe0f","🙎🏿‍♂":"1f64e-1f3ff-200d-2642-fe0f","🙎‍♀️":"1f64e-200d-2640-fe0f","🙎🏻‍♀":"1f64e-1f3fb-200d-2640-fe0f","🙎🏼‍♀":"1f64e-1f3fc-200d-2640-fe0f","🙎🏽‍♀":"1f64e-1f3fd-200d-2640-fe0f","🙎🏾‍♀":"1f64e-1f3fe-200d-2640-fe0f","🙎🏿‍♀":"1f64e-1f3ff-200d-2640-fe0f","🙅‍♂️":"1f645-200d-2642-fe0f","🙅🏻‍♂":"1f645-1f3fb-200d-2642-fe0f","🙅🏼‍♂":"1f645-1f3fc-200d-2642-fe0f","🙅🏽‍♂":"1f645-1f3fd-200d-2642-fe0f","🙅🏾‍♂":"1f645-1f3fe-200d-2642-fe0f","🙅🏿‍♂":"1f645-1f3ff-200d-2642-fe0f","🙅‍♀️":"1f645-200d-2640-fe0f","🙅🏻‍♀":"1f645-1f3fb-200d-2640-fe0f","🙅🏼‍♀":"1f645-1f3fc-200d-2640-fe0f","🙅🏽‍♀":"1f645-1f3fd-200d-2640-fe0f","🙅🏾‍♀":"1f645-1f3fe-200d-2640-fe0f","🙅🏿‍♀":"1f645-1f3ff-200d-2640-fe0f","🙆‍♂️":"1f646-200d-2642-fe0f","🙆🏻‍♂":"1f646-1f3fb-200d-2642-fe0f","🙆🏼‍♂":"1f646-1f3fc-200d-2642-fe0f","🙆🏽‍♂":"1f646-1f3fd-200d-2642-fe0f","🙆🏾‍♂":"1f646-1f3fe-200d-2642-fe0f","🙆🏿‍♂":"1f646-1f3ff-200d-2642-fe0f","🙆‍♀️":"1f646-200d-2640-fe0f","🙆🏻‍♀":"1f646-1f3fb-200d-2640-fe0f","🙆🏼‍♀":"1f646-1f3fc-200d-2640-fe0f","🙆🏽‍♀":"1f646-1f3fd-200d-2640-fe0f","🙆🏾‍♀":"1f646-1f3fe-200d-2640-fe0f","🙆🏿‍♀":"1f646-1f3ff-200d-2640-fe0f","💁‍♂️":"1f481-200d-2642-fe0f","💁🏻‍♂":"1f481-1f3fb-200d-2642-fe0f","💁🏼‍♂":"1f481-1f3fc-200d-2642-fe0f","💁🏽‍♂":"1f481-1f3fd-200d-2642-fe0f","💁🏾‍♂":"1f481-1f3fe-200d-2642-fe0f","💁🏿‍♂":"1f481-1f3ff-200d-2642-fe0f","💁‍♀️":"1f481-200d-2640-fe0f","💁🏻‍♀":"1f481-1f3fb-200d-2640-fe0f","💁🏼‍♀":"1f481-1f3fc-200d-2640-fe0f","💁🏽‍♀":"1f481-1f3fd-200d-2640-fe0f","💁🏾‍♀":"1f481-1f3fe-200d-2640-fe0f","💁🏿‍♀":"1f481-1f3ff-200d-2640-fe0f","🙋‍♂️":"1f64b-200d-2642-fe0f","🙋🏻‍♂":"1f64b-1f3fb-200d-2642-fe0f","🙋🏼‍♂":"1f64b-1f3fc-200d-2642-fe0f","🙋🏽‍♂":"1f64b-1f3fd-200d-2642-fe0f","🙋🏾‍♂":"1f64b-1f3fe-200d-2642-fe0f","🙋🏿‍♂":"1f64b-1f3ff-200d-2642-fe0f","🙋‍♀️":"1f64b-200d-2640-fe0f","🙋🏻‍♀":"1f64b-1f3fb-200d-2640-fe0f","🙋🏼‍♀":"1f64b-1f3fc-200d-2640-fe0f","🙋🏽‍♀":"1f64b-1f3fd-200d-2640-fe0f","🙋🏾‍♀":"1f64b-1f3fe-200d-2640-fe0f","🙋🏿‍♀":"1f64b-1f3ff-200d-2640-fe0f","🧏‍♂️":"1f9cf-200d-2642-fe0f","🧏🏻‍♂":"1f9cf-1f3fb-200d-2642-fe0f","🧏🏼‍♂":"1f9cf-1f3fc-200d-2642-fe0f","🧏🏽‍♂":"1f9cf-1f3fd-200d-2642-fe0f","🧏🏾‍♂":"1f9cf-1f3fe-200d-2642-fe0f","🧏🏿‍♂":"1f9cf-1f3ff-200d-2642-fe0f","🧏‍♀️":"1f9cf-200d-2640-fe0f","🧏🏻‍♀":"1f9cf-1f3fb-200d-2640-fe0f","🧏🏼‍♀":"1f9cf-1f3fc-200d-2640-fe0f","🧏🏽‍♀":"1f9cf-1f3fd-200d-2640-fe0f","🧏🏾‍♀":"1f9cf-1f3fe-200d-2640-fe0f","🧏🏿‍♀":"1f9cf-1f3ff-200d-2640-fe0f","🙇‍♂️":"1f647-200d-2642-fe0f","🙇🏻‍♂":"1f647-1f3fb-200d-2642-fe0f","🙇🏼‍♂":"1f647-1f3fc-200d-2642-fe0f","🙇🏽‍♂":"1f647-1f3fd-200d-2642-fe0f","🙇🏾‍♂":"1f647-1f3fe-200d-2642-fe0f","🙇🏿‍♂":"1f647-1f3ff-200d-2642-fe0f","🙇‍♀️":"1f647-200d-2640-fe0f","🙇🏻‍♀":"1f647-1f3fb-200d-2640-fe0f","🙇🏼‍♀":"1f647-1f3fc-200d-2640-fe0f","🙇🏽‍♀":"1f647-1f3fd-200d-2640-fe0f","🙇🏾‍♀":"1f647-1f3fe-200d-2640-fe0f","🙇🏿‍♀":"1f647-1f3ff-200d-2640-fe0f","🤦‍♂️":"1f926-200d-2642-fe0f","🤦🏻‍♂":"1f926-1f3fb-200d-2642-fe0f","🤦🏼‍♂":"1f926-1f3fc-200d-2642-fe0f","🤦🏽‍♂":"1f926-1f3fd-200d-2642-fe0f","🤦🏾‍♂":"1f926-1f3fe-200d-2642-fe0f","🤦🏿‍♂":"1f926-1f3ff-200d-2642-fe0f","🤦‍♀️":"1f926-200d-2640-fe0f","🤦🏻‍♀":"1f926-1f3fb-200d-2640-fe0f","🤦🏼‍♀":"1f926-1f3fc-200d-2640-fe0f","🤦🏽‍♀":"1f926-1f3fd-200d-2640-fe0f","🤦🏾‍♀":"1f926-1f3fe-200d-2640-fe0f","🤦🏿‍♀":"1f926-1f3ff-200d-2640-fe0f","🤷‍♂️":"1f937-200d-2642-fe0f","🤷🏻‍♂":"1f937-1f3fb-200d-2642-fe0f","🤷🏼‍♂":"1f937-1f3fc-200d-2642-fe0f","🤷🏽‍♂":"1f937-1f3fd-200d-2642-fe0f","🤷🏾‍♂":"1f937-1f3fe-200d-2642-fe0f","🤷🏿‍♂":"1f937-1f3ff-200d-2642-fe0f","🤷‍♀️":"1f937-200d-2640-fe0f","🤷🏻‍♀":"1f937-1f3fb-200d-2640-fe0f","🤷🏼‍♀":"1f937-1f3fc-200d-2640-fe0f","🤷🏽‍♀":"1f937-1f3fd-200d-2640-fe0f","🤷🏾‍♀":"1f937-1f3fe-200d-2640-fe0f","🤷🏿‍♀":"1f937-1f3ff-200d-2640-fe0f","🧑‍⚕️":"1f9d1-200d-2695-fe0f","🧑🏻‍⚕":"1f9d1-1f3fb-200d-2695-fe0f","🧑🏼‍⚕":"1f9d1-1f3fc-200d-2695-fe0f","🧑🏽‍⚕":"1f9d1-1f3fd-200d-2695-fe0f","🧑🏾‍⚕":"1f9d1-1f3fe-200d-2695-fe0f","🧑🏿‍⚕":"1f9d1-1f3ff-200d-2695-fe0f","👨‍⚕️":"1f468-200d-2695-fe0f","👨🏻‍⚕":"1f468-1f3fb-200d-2695-fe0f","👨🏼‍⚕":"1f468-1f3fc-200d-2695-fe0f","👨🏽‍⚕":"1f468-1f3fd-200d-2695-fe0f","👨🏾‍⚕":"1f468-1f3fe-200d-2695-fe0f","👨🏿‍⚕":"1f468-1f3ff-200d-2695-fe0f","👩‍⚕️":"1f469-200d-2695-fe0f","👩🏻‍⚕":"1f469-1f3fb-200d-2695-fe0f","👩🏼‍⚕":"1f469-1f3fc-200d-2695-fe0f","👩🏽‍⚕":"1f469-1f3fd-200d-2695-fe0f","👩🏾‍⚕":"1f469-1f3fe-200d-2695-fe0f","👩🏿‍⚕":"1f469-1f3ff-200d-2695-fe0f","🧑🏻‍🎓":"1f9d1-1f3fb-200d-1f393","🧑🏼‍🎓":"1f9d1-1f3fc-200d-1f393","🧑🏽‍🎓":"1f9d1-1f3fd-200d-1f393","🧑🏾‍🎓":"1f9d1-1f3fe-200d-1f393","🧑🏿‍🎓":"1f9d1-1f3ff-200d-1f393","👨🏻‍🎓":"1f468-1f3fb-200d-1f393","👨🏼‍🎓":"1f468-1f3fc-200d-1f393","👨🏽‍🎓":"1f468-1f3fd-200d-1f393","👨🏾‍🎓":"1f468-1f3fe-200d-1f393","👨🏿‍🎓":"1f468-1f3ff-200d-1f393","👩🏻‍🎓":"1f469-1f3fb-200d-1f393","👩🏼‍🎓":"1f469-1f3fc-200d-1f393","👩🏽‍🎓":"1f469-1f3fd-200d-1f393","👩🏾‍🎓":"1f469-1f3fe-200d-1f393","👩🏿‍🎓":"1f469-1f3ff-200d-1f393","🧑🏻‍🏫":"1f9d1-1f3fb-200d-1f3eb","🧑🏼‍🏫":"1f9d1-1f3fc-200d-1f3eb","🧑🏽‍🏫":"1f9d1-1f3fd-200d-1f3eb","🧑🏾‍🏫":"1f9d1-1f3fe-200d-1f3eb","🧑🏿‍🏫":"1f9d1-1f3ff-200d-1f3eb","👨🏻‍🏫":"1f468-1f3fb-200d-1f3eb","👨🏼‍🏫":"1f468-1f3fc-200d-1f3eb","👨🏽‍🏫":"1f468-1f3fd-200d-1f3eb","👨🏾‍🏫":"1f468-1f3fe-200d-1f3eb","👨🏿‍🏫":"1f468-1f3ff-200d-1f3eb","👩🏻‍🏫":"1f469-1f3fb-200d-1f3eb","👩🏼‍🏫":"1f469-1f3fc-200d-1f3eb","👩🏽‍🏫":"1f469-1f3fd-200d-1f3eb","👩🏾‍🏫":"1f469-1f3fe-200d-1f3eb","👩🏿‍🏫":"1f469-1f3ff-200d-1f3eb","🧑‍⚖️":"1f9d1-200d-2696-fe0f","🧑🏻‍⚖":"1f9d1-1f3fb-200d-2696-fe0f","🧑🏼‍⚖":"1f9d1-1f3fc-200d-2696-fe0f","🧑🏽‍⚖":"1f9d1-1f3fd-200d-2696-fe0f","🧑🏾‍⚖":"1f9d1-1f3fe-200d-2696-fe0f","🧑🏿‍⚖":"1f9d1-1f3ff-200d-2696-fe0f","👨‍⚖️":"1f468-200d-2696-fe0f","👨🏻‍⚖":"1f468-1f3fb-200d-2696-fe0f","👨🏼‍⚖":"1f468-1f3fc-200d-2696-fe0f","👨🏽‍⚖":"1f468-1f3fd-200d-2696-fe0f","👨🏾‍⚖":"1f468-1f3fe-200d-2696-fe0f","👨🏿‍⚖":"1f468-1f3ff-200d-2696-fe0f","👩‍⚖️":"1f469-200d-2696-fe0f","👩🏻‍⚖":"1f469-1f3fb-200d-2696-fe0f","👩🏼‍⚖":"1f469-1f3fc-200d-2696-fe0f","👩🏽‍⚖":"1f469-1f3fd-200d-2696-fe0f","👩🏾‍⚖":"1f469-1f3fe-200d-2696-fe0f","👩🏿‍⚖":"1f469-1f3ff-200d-2696-fe0f","🧑🏻‍🌾":"1f9d1-1f3fb-200d-1f33e","🧑🏼‍🌾":"1f9d1-1f3fc-200d-1f33e","🧑🏽‍🌾":"1f9d1-1f3fd-200d-1f33e","🧑🏾‍🌾":"1f9d1-1f3fe-200d-1f33e","🧑🏿‍🌾":"1f9d1-1f3ff-200d-1f33e","👨🏻‍🌾":"1f468-1f3fb-200d-1f33e","👨🏼‍🌾":"1f468-1f3fc-200d-1f33e","👨🏽‍🌾":"1f468-1f3fd-200d-1f33e","👨🏾‍🌾":"1f468-1f3fe-200d-1f33e","👨🏿‍🌾":"1f468-1f3ff-200d-1f33e","👩🏻‍🌾":"1f469-1f3fb-200d-1f33e","👩🏼‍🌾":"1f469-1f3fc-200d-1f33e","👩🏽‍🌾":"1f469-1f3fd-200d-1f33e","👩🏾‍🌾":"1f469-1f3fe-200d-1f33e","👩🏿‍🌾":"1f469-1f3ff-200d-1f33e","🧑🏻‍🍳":"1f9d1-1f3fb-200d-1f373","🧑🏼‍🍳":"1f9d1-1f3fc-200d-1f373","🧑🏽‍🍳":"1f9d1-1f3fd-200d-1f373","🧑🏾‍🍳":"1f9d1-1f3fe-200d-1f373","🧑🏿‍🍳":"1f9d1-1f3ff-200d-1f373","👨🏻‍🍳":"1f468-1f3fb-200d-1f373","👨🏼‍🍳":"1f468-1f3fc-200d-1f373","👨🏽‍🍳":"1f468-1f3fd-200d-1f373","👨🏾‍🍳":"1f468-1f3fe-200d-1f373","👨🏿‍🍳":"1f468-1f3ff-200d-1f373","👩🏻‍🍳":"1f469-1f3fb-200d-1f373","👩🏼‍🍳":"1f469-1f3fc-200d-1f373","👩🏽‍🍳":"1f469-1f3fd-200d-1f373","👩🏾‍🍳":"1f469-1f3fe-200d-1f373","👩🏿‍🍳":"1f469-1f3ff-200d-1f373","🧑🏻‍🔧":"1f9d1-1f3fb-200d-1f527","🧑🏼‍🔧":"1f9d1-1f3fc-200d-1f527","🧑🏽‍🔧":"1f9d1-1f3fd-200d-1f527","🧑🏾‍🔧":"1f9d1-1f3fe-200d-1f527","🧑🏿‍🔧":"1f9d1-1f3ff-200d-1f527","👨🏻‍🔧":"1f468-1f3fb-200d-1f527","👨🏼‍🔧":"1f468-1f3fc-200d-1f527","👨🏽‍🔧":"1f468-1f3fd-200d-1f527","👨🏾‍🔧":"1f468-1f3fe-200d-1f527","👨🏿‍🔧":"1f468-1f3ff-200d-1f527","👩🏻‍🔧":"1f469-1f3fb-200d-1f527","👩🏼‍🔧":"1f469-1f3fc-200d-1f527","👩🏽‍🔧":"1f469-1f3fd-200d-1f527","👩🏾‍🔧":"1f469-1f3fe-200d-1f527","👩🏿‍🔧":"1f469-1f3ff-200d-1f527","🧑🏻‍🏭":"1f9d1-1f3fb-200d-1f3ed","🧑🏼‍🏭":"1f9d1-1f3fc-200d-1f3ed","🧑🏽‍🏭":"1f9d1-1f3fd-200d-1f3ed","🧑🏾‍🏭":"1f9d1-1f3fe-200d-1f3ed","🧑🏿‍🏭":"1f9d1-1f3ff-200d-1f3ed","👨🏻‍🏭":"1f468-1f3fb-200d-1f3ed","👨🏼‍🏭":"1f468-1f3fc-200d-1f3ed","👨🏽‍🏭":"1f468-1f3fd-200d-1f3ed","👨🏾‍🏭":"1f468-1f3fe-200d-1f3ed","👨🏿‍🏭":"1f468-1f3ff-200d-1f3ed","👩🏻‍🏭":"1f469-1f3fb-200d-1f3ed","👩🏼‍🏭":"1f469-1f3fc-200d-1f3ed","👩🏽‍🏭":"1f469-1f3fd-200d-1f3ed","👩🏾‍🏭":"1f469-1f3fe-200d-1f3ed","👩🏿‍🏭":"1f469-1f3ff-200d-1f3ed","🧑🏻‍💼":"1f9d1-1f3fb-200d-1f4bc","🧑🏼‍💼":"1f9d1-1f3fc-200d-1f4bc","🧑🏽‍💼":"1f9d1-1f3fd-200d-1f4bc","🧑🏾‍💼":"1f9d1-1f3fe-200d-1f4bc","🧑🏿‍💼":"1f9d1-1f3ff-200d-1f4bc","👨🏻‍💼":"1f468-1f3fb-200d-1f4bc","👨🏼‍💼":"1f468-1f3fc-200d-1f4bc","👨🏽‍💼":"1f468-1f3fd-200d-1f4bc","👨🏾‍💼":"1f468-1f3fe-200d-1f4bc","👨🏿‍💼":"1f468-1f3ff-200d-1f4bc","👩🏻‍💼":"1f469-1f3fb-200d-1f4bc","👩🏼‍💼":"1f469-1f3fc-200d-1f4bc","👩🏽‍💼":"1f469-1f3fd-200d-1f4bc","👩🏾‍💼":"1f469-1f3fe-200d-1f4bc","👩🏿‍💼":"1f469-1f3ff-200d-1f4bc","🧑🏻‍🔬":"1f9d1-1f3fb-200d-1f52c","🧑🏼‍🔬":"1f9d1-1f3fc-200d-1f52c","🧑🏽‍🔬":"1f9d1-1f3fd-200d-1f52c","🧑🏾‍🔬":"1f9d1-1f3fe-200d-1f52c","🧑🏿‍🔬":"1f9d1-1f3ff-200d-1f52c","👨🏻‍🔬":"1f468-1f3fb-200d-1f52c","👨🏼‍🔬":"1f468-1f3fc-200d-1f52c","👨🏽‍🔬":"1f468-1f3fd-200d-1f52c","👨🏾‍🔬":"1f468-1f3fe-200d-1f52c","👨🏿‍🔬":"1f468-1f3ff-200d-1f52c","👩🏻‍🔬":"1f469-1f3fb-200d-1f52c","👩🏼‍🔬":"1f469-1f3fc-200d-1f52c","👩🏽‍🔬":"1f469-1f3fd-200d-1f52c","👩🏾‍🔬":"1f469-1f3fe-200d-1f52c","👩🏿‍🔬":"1f469-1f3ff-200d-1f52c","🧑🏻‍💻":"1f9d1-1f3fb-200d-1f4bb","🧑🏼‍💻":"1f9d1-1f3fc-200d-1f4bb","🧑🏽‍💻":"1f9d1-1f3fd-200d-1f4bb","🧑🏾‍💻":"1f9d1-1f3fe-200d-1f4bb","🧑🏿‍💻":"1f9d1-1f3ff-200d-1f4bb","👨🏻‍💻":"1f468-1f3fb-200d-1f4bb","👨🏼‍💻":"1f468-1f3fc-200d-1f4bb","👨🏽‍💻":"1f468-1f3fd-200d-1f4bb","👨🏾‍💻":"1f468-1f3fe-200d-1f4bb","👨🏿‍💻":"1f468-1f3ff-200d-1f4bb","👩🏻‍💻":"1f469-1f3fb-200d-1f4bb","👩🏼‍💻":"1f469-1f3fc-200d-1f4bb","👩🏽‍💻":"1f469-1f3fd-200d-1f4bb","👩🏾‍💻":"1f469-1f3fe-200d-1f4bb","👩🏿‍💻":"1f469-1f3ff-200d-1f4bb","🧑🏻‍🎤":"1f9d1-1f3fb-200d-1f3a4","🧑🏼‍🎤":"1f9d1-1f3fc-200d-1f3a4","🧑🏽‍🎤":"1f9d1-1f3fd-200d-1f3a4","🧑🏾‍🎤":"1f9d1-1f3fe-200d-1f3a4","🧑🏿‍🎤":"1f9d1-1f3ff-200d-1f3a4","👨🏻‍🎤":"1f468-1f3fb-200d-1f3a4","👨🏼‍🎤":"1f468-1f3fc-200d-1f3a4","👨🏽‍🎤":"1f468-1f3fd-200d-1f3a4","👨🏾‍🎤":"1f468-1f3fe-200d-1f3a4","👨🏿‍🎤":"1f468-1f3ff-200d-1f3a4","👩🏻‍🎤":"1f469-1f3fb-200d-1f3a4","👩🏼‍🎤":"1f469-1f3fc-200d-1f3a4","👩🏽‍🎤":"1f469-1f3fd-200d-1f3a4","👩🏾‍🎤":"1f469-1f3fe-200d-1f3a4","👩🏿‍🎤":"1f469-1f3ff-200d-1f3a4","🧑🏻‍🎨":"1f9d1-1f3fb-200d-1f3a8","🧑🏼‍🎨":"1f9d1-1f3fc-200d-1f3a8","🧑🏽‍🎨":"1f9d1-1f3fd-200d-1f3a8","🧑🏾‍🎨":"1f9d1-1f3fe-200d-1f3a8","🧑🏿‍🎨":"1f9d1-1f3ff-200d-1f3a8","👨🏻‍🎨":"1f468-1f3fb-200d-1f3a8","👨🏼‍🎨":"1f468-1f3fc-200d-1f3a8","👨🏽‍🎨":"1f468-1f3fd-200d-1f3a8","👨🏾‍🎨":"1f468-1f3fe-200d-1f3a8","👨🏿‍🎨":"1f468-1f3ff-200d-1f3a8","👩🏻‍🎨":"1f469-1f3fb-200d-1f3a8","👩🏼‍🎨":"1f469-1f3fc-200d-1f3a8","👩🏽‍🎨":"1f469-1f3fd-200d-1f3a8","👩🏾‍🎨":"1f469-1f3fe-200d-1f3a8","👩🏿‍🎨":"1f469-1f3ff-200d-1f3a8","🧑‍✈️":"1f9d1-200d-2708-fe0f","🧑🏻‍✈":"1f9d1-1f3fb-200d-2708-fe0f","🧑🏼‍✈":"1f9d1-1f3fc-200d-2708-fe0f","🧑🏽‍✈":"1f9d1-1f3fd-200d-2708-fe0f","🧑🏾‍✈":"1f9d1-1f3fe-200d-2708-fe0f","🧑🏿‍✈":"1f9d1-1f3ff-200d-2708-fe0f","👨‍✈️":"1f468-200d-2708-fe0f","👨🏻‍✈":"1f468-1f3fb-200d-2708-fe0f","👨🏼‍✈":"1f468-1f3fc-200d-2708-fe0f","👨🏽‍✈":"1f468-1f3fd-200d-2708-fe0f","👨🏾‍✈":"1f468-1f3fe-200d-2708-fe0f","👨🏿‍✈":"1f468-1f3ff-200d-2708-fe0f","👩‍✈️":"1f469-200d-2708-fe0f","👩🏻‍✈":"1f469-1f3fb-200d-2708-fe0f","👩🏼‍✈":"1f469-1f3fc-200d-2708-fe0f","👩🏽‍✈":"1f469-1f3fd-200d-2708-fe0f","👩🏾‍✈":"1f469-1f3fe-200d-2708-fe0f","👩🏿‍✈":"1f469-1f3ff-200d-2708-fe0f","🧑🏻‍🚀":"1f9d1-1f3fb-200d-1f680","🧑🏼‍🚀":"1f9d1-1f3fc-200d-1f680","🧑🏽‍🚀":"1f9d1-1f3fd-200d-1f680","🧑🏾‍🚀":"1f9d1-1f3fe-200d-1f680","🧑🏿‍🚀":"1f9d1-1f3ff-200d-1f680","👨🏻‍🚀":"1f468-1f3fb-200d-1f680","👨🏼‍🚀":"1f468-1f3fc-200d-1f680","👨🏽‍🚀":"1f468-1f3fd-200d-1f680","👨🏾‍🚀":"1f468-1f3fe-200d-1f680","👨🏿‍🚀":"1f468-1f3ff-200d-1f680","👩🏻‍🚀":"1f469-1f3fb-200d-1f680","👩🏼‍🚀":"1f469-1f3fc-200d-1f680","👩🏽‍🚀":"1f469-1f3fd-200d-1f680","👩🏾‍🚀":"1f469-1f3fe-200d-1f680","👩🏿‍🚀":"1f469-1f3ff-200d-1f680","🧑🏻‍🚒":"1f9d1-1f3fb-200d-1f692","🧑🏼‍🚒":"1f9d1-1f3fc-200d-1f692","🧑🏽‍🚒":"1f9d1-1f3fd-200d-1f692","🧑🏾‍🚒":"1f9d1-1f3fe-200d-1f692","🧑🏿‍🚒":"1f9d1-1f3ff-200d-1f692","👨🏻‍🚒":"1f468-1f3fb-200d-1f692","👨🏼‍🚒":"1f468-1f3fc-200d-1f692","👨🏽‍🚒":"1f468-1f3fd-200d-1f692","👨🏾‍🚒":"1f468-1f3fe-200d-1f692","👨🏿‍🚒":"1f468-1f3ff-200d-1f692","👩🏻‍🚒":"1f469-1f3fb-200d-1f692","👩🏼‍🚒":"1f469-1f3fc-200d-1f692","👩🏽‍🚒":"1f469-1f3fd-200d-1f692","👩🏾‍🚒":"1f469-1f3fe-200d-1f692","👩🏿‍🚒":"1f469-1f3ff-200d-1f692","👮‍♂️":"1f46e-200d-2642-fe0f","👮🏻‍♂":"1f46e-1f3fb-200d-2642-fe0f","👮🏼‍♂":"1f46e-1f3fc-200d-2642-fe0f","👮🏽‍♂":"1f46e-1f3fd-200d-2642-fe0f","👮🏾‍♂":"1f46e-1f3fe-200d-2642-fe0f","👮🏿‍♂":"1f46e-1f3ff-200d-2642-fe0f","👮‍♀️":"1f46e-200d-2640-fe0f","👮🏻‍♀":"1f46e-1f3fb-200d-2640-fe0f","👮🏼‍♀":"1f46e-1f3fc-200d-2640-fe0f","👮🏽‍♀":"1f46e-1f3fd-200d-2640-fe0f","👮🏾‍♀":"1f46e-1f3fe-200d-2640-fe0f","👮🏿‍♀":"1f46e-1f3ff-200d-2640-fe0f","🕵‍♂️":"1f575-fe0f-200d-2642-fe0f","🕵️‍♂":"1f575-fe0f-200d-2642-fe0f","🕵🏻‍♂":"1f575-1f3fb-200d-2642-fe0f","🕵🏼‍♂":"1f575-1f3fc-200d-2642-fe0f","🕵🏽‍♂":"1f575-1f3fd-200d-2642-fe0f","🕵🏾‍♂":"1f575-1f3fe-200d-2642-fe0f","🕵🏿‍♂":"1f575-1f3ff-200d-2642-fe0f","🕵‍♀️":"1f575-fe0f-200d-2640-fe0f","🕵️‍♀":"1f575-fe0f-200d-2640-fe0f","🕵🏻‍♀":"1f575-1f3fb-200d-2640-fe0f","🕵🏼‍♀":"1f575-1f3fc-200d-2640-fe0f","🕵🏽‍♀":"1f575-1f3fd-200d-2640-fe0f","🕵🏾‍♀":"1f575-1f3fe-200d-2640-fe0f","🕵🏿‍♀":"1f575-1f3ff-200d-2640-fe0f","💂‍♂️":"1f482-200d-2642-fe0f","💂🏻‍♂":"1f482-1f3fb-200d-2642-fe0f","💂🏼‍♂":"1f482-1f3fc-200d-2642-fe0f","💂🏽‍♂":"1f482-1f3fd-200d-2642-fe0f","💂🏾‍♂":"1f482-1f3fe-200d-2642-fe0f","💂🏿‍♂":"1f482-1f3ff-200d-2642-fe0f","💂‍♀️":"1f482-200d-2640-fe0f","💂🏻‍♀":"1f482-1f3fb-200d-2640-fe0f","💂🏼‍♀":"1f482-1f3fc-200d-2640-fe0f","💂🏽‍♀":"1f482-1f3fd-200d-2640-fe0f","💂🏾‍♀":"1f482-1f3fe-200d-2640-fe0f","💂🏿‍♀":"1f482-1f3ff-200d-2640-fe0f","👷‍♂️":"1f477-200d-2642-fe0f","👷🏻‍♂":"1f477-1f3fb-200d-2642-fe0f","👷🏼‍♂":"1f477-1f3fc-200d-2642-fe0f","👷🏽‍♂":"1f477-1f3fd-200d-2642-fe0f","👷🏾‍♂":"1f477-1f3fe-200d-2642-fe0f","👷🏿‍♂":"1f477-1f3ff-200d-2642-fe0f","👷‍♀️":"1f477-200d-2640-fe0f","👷🏻‍♀":"1f477-1f3fb-200d-2640-fe0f","👷🏼‍♀":"1f477-1f3fc-200d-2640-fe0f","👷🏽‍♀":"1f477-1f3fd-200d-2640-fe0f","👷🏾‍♀":"1f477-1f3fe-200d-2640-fe0f","👷🏿‍♀":"1f477-1f3ff-200d-2640-fe0f","👳‍♂️":"1f473-200d-2642-fe0f","👳🏻‍♂":"1f473-1f3fb-200d-2642-fe0f","👳🏼‍♂":"1f473-1f3fc-200d-2642-fe0f","👳🏽‍♂":"1f473-1f3fd-200d-2642-fe0f","👳🏾‍♂":"1f473-1f3fe-200d-2642-fe0f","👳🏿‍♂":"1f473-1f3ff-200d-2642-fe0f","👳‍♀️":"1f473-200d-2640-fe0f","👳🏻‍♀":"1f473-1f3fb-200d-2640-fe0f","👳🏼‍♀":"1f473-1f3fc-200d-2640-fe0f","👳🏽‍♀":"1f473-1f3fd-200d-2640-fe0f","👳🏾‍♀":"1f473-1f3fe-200d-2640-fe0f","👳🏿‍♀":"1f473-1f3ff-200d-2640-fe0f","🤵‍♂️":"1f935-200d-2642-fe0f","🤵🏻‍♂":"1f935-1f3fb-200d-2642-fe0f","🤵🏼‍♂":"1f935-1f3fc-200d-2642-fe0f","🤵🏽‍♂":"1f935-1f3fd-200d-2642-fe0f","🤵🏾‍♂":"1f935-1f3fe-200d-2642-fe0f","🤵🏿‍♂":"1f935-1f3ff-200d-2642-fe0f","🤵‍♀️":"1f935-200d-2640-fe0f","🤵🏻‍♀":"1f935-1f3fb-200d-2640-fe0f","🤵🏼‍♀":"1f935-1f3fc-200d-2640-fe0f","🤵🏽‍♀":"1f935-1f3fd-200d-2640-fe0f","🤵🏾‍♀":"1f935-1f3fe-200d-2640-fe0f","🤵🏿‍♀":"1f935-1f3ff-200d-2640-fe0f","👰‍♂️":"1f470-200d-2642-fe0f","👰🏻‍♂":"1f470-1f3fb-200d-2642-fe0f","👰🏼‍♂":"1f470-1f3fc-200d-2642-fe0f","👰🏽‍♂":"1f470-1f3fd-200d-2642-fe0f","👰🏾‍♂":"1f470-1f3fe-200d-2642-fe0f","👰🏿‍♂":"1f470-1f3ff-200d-2642-fe0f","👰‍♀️":"1f470-200d-2640-fe0f","👰🏻‍♀":"1f470-1f3fb-200d-2640-fe0f","👰🏼‍♀":"1f470-1f3fc-200d-2640-fe0f","👰🏽‍♀":"1f470-1f3fd-200d-2640-fe0f","👰🏾‍♀":"1f470-1f3fe-200d-2640-fe0f","👰🏿‍♀":"1f470-1f3ff-200d-2640-fe0f","👩🏻‍🍼":"1f469-1f3fb-200d-1f37c","👩🏼‍🍼":"1f469-1f3fc-200d-1f37c","👩🏽‍🍼":"1f469-1f3fd-200d-1f37c","👩🏾‍🍼":"1f469-1f3fe-200d-1f37c","👩🏿‍🍼":"1f469-1f3ff-200d-1f37c","👨🏻‍🍼":"1f468-1f3fb-200d-1f37c","👨🏼‍🍼":"1f468-1f3fc-200d-1f37c","👨🏽‍🍼":"1f468-1f3fd-200d-1f37c","👨🏾‍🍼":"1f468-1f3fe-200d-1f37c","👨🏿‍🍼":"1f468-1f3ff-200d-1f37c","🧑🏻‍🍼":"1f9d1-1f3fb-200d-1f37c","🧑🏼‍🍼":"1f9d1-1f3fc-200d-1f37c","🧑🏽‍🍼":"1f9d1-1f3fd-200d-1f37c","🧑🏾‍🍼":"1f9d1-1f3fe-200d-1f37c","🧑🏿‍🍼":"1f9d1-1f3ff-200d-1f37c","🧑🏻‍🎄":"1f9d1-1f3fb-200d-1f384","🧑🏼‍🎄":"1f9d1-1f3fc-200d-1f384","🧑🏽‍🎄":"1f9d1-1f3fd-200d-1f384","🧑🏾‍🎄":"1f9d1-1f3fe-200d-1f384","🧑🏿‍🎄":"1f9d1-1f3ff-200d-1f384","🦸‍♂️":"1f9b8-200d-2642-fe0f","🦸🏻‍♂":"1f9b8-1f3fb-200d-2642-fe0f","🦸🏼‍♂":"1f9b8-1f3fc-200d-2642-fe0f","🦸🏽‍♂":"1f9b8-1f3fd-200d-2642-fe0f","🦸🏾‍♂":"1f9b8-1f3fe-200d-2642-fe0f","🦸🏿‍♂":"1f9b8-1f3ff-200d-2642-fe0f","🦸‍♀️":"1f9b8-200d-2640-fe0f","🦸🏻‍♀":"1f9b8-1f3fb-200d-2640-fe0f","🦸🏼‍♀":"1f9b8-1f3fc-200d-2640-fe0f","🦸🏽‍♀":"1f9b8-1f3fd-200d-2640-fe0f","🦸🏾‍♀":"1f9b8-1f3fe-200d-2640-fe0f","🦸🏿‍♀":"1f9b8-1f3ff-200d-2640-fe0f","🦹‍♂️":"1f9b9-200d-2642-fe0f","🦹🏻‍♂":"1f9b9-1f3fb-200d-2642-fe0f","🦹🏼‍♂":"1f9b9-1f3fc-200d-2642-fe0f","🦹🏽‍♂":"1f9b9-1f3fd-200d-2642-fe0f","🦹🏾‍♂":"1f9b9-1f3fe-200d-2642-fe0f","🦹🏿‍♂":"1f9b9-1f3ff-200d-2642-fe0f","🦹‍♀️":"1f9b9-200d-2640-fe0f","🦹🏻‍♀":"1f9b9-1f3fb-200d-2640-fe0f","🦹🏼‍♀":"1f9b9-1f3fc-200d-2640-fe0f","🦹🏽‍♀":"1f9b9-1f3fd-200d-2640-fe0f","🦹🏾‍♀":"1f9b9-1f3fe-200d-2640-fe0f","🦹🏿‍♀":"1f9b9-1f3ff-200d-2640-fe0f","🧙‍♂️":"1f9d9-200d-2642-fe0f","🧙🏻‍♂":"1f9d9-1f3fb-200d-2642-fe0f","🧙🏼‍♂":"1f9d9-1f3fc-200d-2642-fe0f","🧙🏽‍♂":"1f9d9-1f3fd-200d-2642-fe0f","🧙🏾‍♂":"1f9d9-1f3fe-200d-2642-fe0f","🧙🏿‍♂":"1f9d9-1f3ff-200d-2642-fe0f","🧙‍♀️":"1f9d9-200d-2640-fe0f","🧙🏻‍♀":"1f9d9-1f3fb-200d-2640-fe0f","🧙🏼‍♀":"1f9d9-1f3fc-200d-2640-fe0f","🧙🏽‍♀":"1f9d9-1f3fd-200d-2640-fe0f","🧙🏾‍♀":"1f9d9-1f3fe-200d-2640-fe0f","🧙🏿‍♀":"1f9d9-1f3ff-200d-2640-fe0f","🧚‍♂️":"1f9da-200d-2642-fe0f","🧚🏻‍♂":"1f9da-1f3fb-200d-2642-fe0f","🧚🏼‍♂":"1f9da-1f3fc-200d-2642-fe0f","🧚🏽‍♂":"1f9da-1f3fd-200d-2642-fe0f","🧚🏾‍♂":"1f9da-1f3fe-200d-2642-fe0f","🧚🏿‍♂":"1f9da-1f3ff-200d-2642-fe0f","🧚‍♀️":"1f9da-200d-2640-fe0f","🧚🏻‍♀":"1f9da-1f3fb-200d-2640-fe0f","🧚🏼‍♀":"1f9da-1f3fc-200d-2640-fe0f","🧚🏽‍♀":"1f9da-1f3fd-200d-2640-fe0f","🧚🏾‍♀":"1f9da-1f3fe-200d-2640-fe0f","🧚🏿‍♀":"1f9da-1f3ff-200d-2640-fe0f","🧛‍♂️":"1f9db-200d-2642-fe0f","🧛🏻‍♂":"1f9db-1f3fb-200d-2642-fe0f","🧛🏼‍♂":"1f9db-1f3fc-200d-2642-fe0f","🧛🏽‍♂":"1f9db-1f3fd-200d-2642-fe0f","🧛🏾‍♂":"1f9db-1f3fe-200d-2642-fe0f","🧛🏿‍♂":"1f9db-1f3ff-200d-2642-fe0f","🧛‍♀️":"1f9db-200d-2640-fe0f","🧛🏻‍♀":"1f9db-1f3fb-200d-2640-fe0f","🧛🏼‍♀":"1f9db-1f3fc-200d-2640-fe0f","🧛🏽‍♀":"1f9db-1f3fd-200d-2640-fe0f","🧛🏾‍♀":"1f9db-1f3fe-200d-2640-fe0f","🧛🏿‍♀":"1f9db-1f3ff-200d-2640-fe0f","🧜‍♂️":"1f9dc-200d-2642-fe0f","🧜🏻‍♂":"1f9dc-1f3fb-200d-2642-fe0f","🧜🏼‍♂":"1f9dc-1f3fc-200d-2642-fe0f","🧜🏽‍♂":"1f9dc-1f3fd-200d-2642-fe0f","🧜🏾‍♂":"1f9dc-1f3fe-200d-2642-fe0f","🧜🏿‍♂":"1f9dc-1f3ff-200d-2642-fe0f","🧜‍♀️":"1f9dc-200d-2640-fe0f","🧜🏻‍♀":"1f9dc-1f3fb-200d-2640-fe0f","🧜🏼‍♀":"1f9dc-1f3fc-200d-2640-fe0f","🧜🏽‍♀":"1f9dc-1f3fd-200d-2640-fe0f","🧜🏾‍♀":"1f9dc-1f3fe-200d-2640-fe0f","🧜🏿‍♀":"1f9dc-1f3ff-200d-2640-fe0f","🧝‍♂️":"1f9dd-200d-2642-fe0f","🧝🏻‍♂":"1f9dd-1f3fb-200d-2642-fe0f","🧝🏼‍♂":"1f9dd-1f3fc-200d-2642-fe0f","🧝🏽‍♂":"1f9dd-1f3fd-200d-2642-fe0f","🧝🏾‍♂":"1f9dd-1f3fe-200d-2642-fe0f","🧝🏿‍♂":"1f9dd-1f3ff-200d-2642-fe0f","🧝‍♀️":"1f9dd-200d-2640-fe0f","🧝🏻‍♀":"1f9dd-1f3fb-200d-2640-fe0f","🧝🏼‍♀":"1f9dd-1f3fc-200d-2640-fe0f","🧝🏽‍♀":"1f9dd-1f3fd-200d-2640-fe0f","🧝🏾‍♀":"1f9dd-1f3fe-200d-2640-fe0f","🧝🏿‍♀":"1f9dd-1f3ff-200d-2640-fe0f","🧞‍♂️":"1f9de-200d-2642-fe0f","🧞‍♀️":"1f9de-200d-2640-fe0f","🧟‍♂️":"1f9df-200d-2642-fe0f","🧟‍♀️":"1f9df-200d-2640-fe0f","💆‍♂️":"1f486-200d-2642-fe0f","💆🏻‍♂":"1f486-1f3fb-200d-2642-fe0f","💆🏼‍♂":"1f486-1f3fc-200d-2642-fe0f","💆🏽‍♂":"1f486-1f3fd-200d-2642-fe0f","💆🏾‍♂":"1f486-1f3fe-200d-2642-fe0f","💆🏿‍♂":"1f486-1f3ff-200d-2642-fe0f","💆‍♀️":"1f486-200d-2640-fe0f","💆🏻‍♀":"1f486-1f3fb-200d-2640-fe0f","💆🏼‍♀":"1f486-1f3fc-200d-2640-fe0f","💆🏽‍♀":"1f486-1f3fd-200d-2640-fe0f","💆🏾‍♀":"1f486-1f3fe-200d-2640-fe0f","💆🏿‍♀":"1f486-1f3ff-200d-2640-fe0f","💇‍♂️":"1f487-200d-2642-fe0f","💇🏻‍♂":"1f487-1f3fb-200d-2642-fe0f","💇🏼‍♂":"1f487-1f3fc-200d-2642-fe0f","💇🏽‍♂":"1f487-1f3fd-200d-2642-fe0f","💇🏾‍♂":"1f487-1f3fe-200d-2642-fe0f","💇🏿‍♂":"1f487-1f3ff-200d-2642-fe0f","💇‍♀️":"1f487-200d-2640-fe0f","💇🏻‍♀":"1f487-1f3fb-200d-2640-fe0f","💇🏼‍♀":"1f487-1f3fc-200d-2640-fe0f","💇🏽‍♀":"1f487-1f3fd-200d-2640-fe0f","💇🏾‍♀":"1f487-1f3fe-200d-2640-fe0f","💇🏿‍♀":"1f487-1f3ff-200d-2640-fe0f","🚶‍♂️":"1f6b6-200d-2642-fe0f","🚶🏻‍♂":"1f6b6-1f3fb-200d-2642-fe0f","🚶🏼‍♂":"1f6b6-1f3fc-200d-2642-fe0f","🚶🏽‍♂":"1f6b6-1f3fd-200d-2642-fe0f","🚶🏾‍♂":"1f6b6-1f3fe-200d-2642-fe0f","🚶🏿‍♂":"1f6b6-1f3ff-200d-2642-fe0f","🚶‍♀️":"1f6b6-200d-2640-fe0f","🚶🏻‍♀":"1f6b6-1f3fb-200d-2640-fe0f","🚶🏼‍♀":"1f6b6-1f3fc-200d-2640-fe0f","🚶🏽‍♀":"1f6b6-1f3fd-200d-2640-fe0f","🚶🏾‍♀":"1f6b6-1f3fe-200d-2640-fe0f","🚶🏿‍♀":"1f6b6-1f3ff-200d-2640-fe0f","🚶‍➡️":"1f6b6-200d-27a1-fe0f","🚶🏻‍➡":"1f6b6-1f3fb-200d-27a1-fe0f","🚶🏼‍➡":"1f6b6-1f3fc-200d-27a1-fe0f","🚶🏽‍➡":"1f6b6-1f3fd-200d-27a1-fe0f","🚶🏾‍➡":"1f6b6-1f3fe-200d-27a1-fe0f","🚶🏿‍➡":"1f6b6-1f3ff-200d-27a1-fe0f","🧍‍♂️":"1f9cd-200d-2642-fe0f","🧍🏻‍♂":"1f9cd-1f3fb-200d-2642-fe0f","🧍🏼‍♂":"1f9cd-1f3fc-200d-2642-fe0f","🧍🏽‍♂":"1f9cd-1f3fd-200d-2642-fe0f","🧍🏾‍♂":"1f9cd-1f3fe-200d-2642-fe0f","🧍🏿‍♂":"1f9cd-1f3ff-200d-2642-fe0f","🧍‍♀️":"1f9cd-200d-2640-fe0f","🧍🏻‍♀":"1f9cd-1f3fb-200d-2640-fe0f","🧍🏼‍♀":"1f9cd-1f3fc-200d-2640-fe0f","🧍🏽‍♀":"1f9cd-1f3fd-200d-2640-fe0f","🧍🏾‍♀":"1f9cd-1f3fe-200d-2640-fe0f","🧍🏿‍♀":"1f9cd-1f3ff-200d-2640-fe0f","🧎‍♂️":"1f9ce-200d-2642-fe0f","🧎🏻‍♂":"1f9ce-1f3fb-200d-2642-fe0f","🧎🏼‍♂":"1f9ce-1f3fc-200d-2642-fe0f","🧎🏽‍♂":"1f9ce-1f3fd-200d-2642-fe0f","🧎🏾‍♂":"1f9ce-1f3fe-200d-2642-fe0f","🧎🏿‍♂":"1f9ce-1f3ff-200d-2642-fe0f","🧎‍♀️":"1f9ce-200d-2640-fe0f","🧎🏻‍♀":"1f9ce-1f3fb-200d-2640-fe0f","🧎🏼‍♀":"1f9ce-1f3fc-200d-2640-fe0f","🧎🏽‍♀":"1f9ce-1f3fd-200d-2640-fe0f","🧎🏾‍♀":"1f9ce-1f3fe-200d-2640-fe0f","🧎🏿‍♀":"1f9ce-1f3ff-200d-2640-fe0f","🧎‍➡️":"1f9ce-200d-27a1-fe0f","🧎🏻‍➡":"1f9ce-1f3fb-200d-27a1-fe0f","🧎🏼‍➡":"1f9ce-1f3fc-200d-27a1-fe0f","🧎🏽‍➡":"1f9ce-1f3fd-200d-27a1-fe0f","🧎🏾‍➡":"1f9ce-1f3fe-200d-27a1-fe0f","🧎🏿‍➡":"1f9ce-1f3ff-200d-27a1-fe0f","🧑🏻‍🦯":"1f9d1-1f3fb-200d-1f9af","🧑🏼‍🦯":"1f9d1-1f3fc-200d-1f9af","🧑🏽‍🦯":"1f9d1-1f3fd-200d-1f9af","🧑🏾‍🦯":"1f9d1-1f3fe-200d-1f9af","🧑🏿‍🦯":"1f9d1-1f3ff-200d-1f9af","👨🏻‍🦯":"1f468-1f3fb-200d-1f9af","👨🏼‍🦯":"1f468-1f3fc-200d-1f9af","👨🏽‍🦯":"1f468-1f3fd-200d-1f9af","👨🏾‍🦯":"1f468-1f3fe-200d-1f9af","👨🏿‍🦯":"1f468-1f3ff-200d-1f9af","👩🏻‍🦯":"1f469-1f3fb-200d-1f9af","👩🏼‍🦯":"1f469-1f3fc-200d-1f9af","👩🏽‍🦯":"1f469-1f3fd-200d-1f9af","👩🏾‍🦯":"1f469-1f3fe-200d-1f9af","👩🏿‍🦯":"1f469-1f3ff-200d-1f9af","🧑🏻‍🦼":"1f9d1-1f3fb-200d-1f9bc","🧑🏼‍🦼":"1f9d1-1f3fc-200d-1f9bc","🧑🏽‍🦼":"1f9d1-1f3fd-200d-1f9bc","🧑🏾‍🦼":"1f9d1-1f3fe-200d-1f9bc","🧑🏿‍🦼":"1f9d1-1f3ff-200d-1f9bc","👨🏻‍🦼":"1f468-1f3fb-200d-1f9bc","👨🏼‍🦼":"1f468-1f3fc-200d-1f9bc","👨🏽‍🦼":"1f468-1f3fd-200d-1f9bc","👨🏾‍🦼":"1f468-1f3fe-200d-1f9bc","👨🏿‍🦼":"1f468-1f3ff-200d-1f9bc","👩🏻‍🦼":"1f469-1f3fb-200d-1f9bc","👩🏼‍🦼":"1f469-1f3fc-200d-1f9bc","👩🏽‍🦼":"1f469-1f3fd-200d-1f9bc","👩🏾‍🦼":"1f469-1f3fe-200d-1f9bc","👩🏿‍🦼":"1f469-1f3ff-200d-1f9bc","🧑🏻‍🦽":"1f9d1-1f3fb-200d-1f9bd","🧑🏼‍🦽":"1f9d1-1f3fc-200d-1f9bd","🧑🏽‍🦽":"1f9d1-1f3fd-200d-1f9bd","🧑🏾‍🦽":"1f9d1-1f3fe-200d-1f9bd","🧑🏿‍🦽":"1f9d1-1f3ff-200d-1f9bd","👨🏻‍🦽":"1f468-1f3fb-200d-1f9bd","👨🏼‍🦽":"1f468-1f3fc-200d-1f9bd","👨🏽‍🦽":"1f468-1f3fd-200d-1f9bd","👨🏾‍🦽":"1f468-1f3fe-200d-1f9bd","👨🏿‍🦽":"1f468-1f3ff-200d-1f9bd","👩🏻‍🦽":"1f469-1f3fb-200d-1f9bd","👩🏼‍🦽":"1f469-1f3fc-200d-1f9bd","👩🏽‍🦽":"1f469-1f3fd-200d-1f9bd","👩🏾‍🦽":"1f469-1f3fe-200d-1f9bd","👩🏿‍🦽":"1f469-1f3ff-200d-1f9bd","🏃‍♂️":"1f3c3-200d-2642-fe0f","🏃🏻‍♂":"1f3c3-1f3fb-200d-2642-fe0f","🏃🏼‍♂":"1f3c3-1f3fc-200d-2642-fe0f","🏃🏽‍♂":"1f3c3-1f3fd-200d-2642-fe0f","🏃🏾‍♂":"1f3c3-1f3fe-200d-2642-fe0f","🏃🏿‍♂":"1f3c3-1f3ff-200d-2642-fe0f","🏃‍♀️":"1f3c3-200d-2640-fe0f","🏃🏻‍♀":"1f3c3-1f3fb-200d-2640-fe0f","🏃🏼‍♀":"1f3c3-1f3fc-200d-2640-fe0f","🏃🏽‍♀":"1f3c3-1f3fd-200d-2640-fe0f","🏃🏾‍♀":"1f3c3-1f3fe-200d-2640-fe0f","🏃🏿‍♀":"1f3c3-1f3ff-200d-2640-fe0f","🏃‍➡️":"1f3c3-200d-27a1-fe0f","🏃🏻‍➡":"1f3c3-1f3fb-200d-27a1-fe0f","🏃🏼‍➡":"1f3c3-1f3fc-200d-27a1-fe0f","🏃🏽‍➡":"1f3c3-1f3fd-200d-27a1-fe0f","🏃🏾‍➡":"1f3c3-1f3fe-200d-27a1-fe0f","🏃🏿‍➡":"1f3c3-1f3ff-200d-27a1-fe0f","👯‍♂️":"1f46f-200d-2642-fe0f","👯‍♀️":"1f46f-200d-2640-fe0f","🧖‍♂️":"1f9d6-200d-2642-fe0f","🧖🏻‍♂":"1f9d6-1f3fb-200d-2642-fe0f","🧖🏼‍♂":"1f9d6-1f3fc-200d-2642-fe0f","🧖🏽‍♂":"1f9d6-1f3fd-200d-2642-fe0f","🧖🏾‍♂":"1f9d6-1f3fe-200d-2642-fe0f","🧖🏿‍♂":"1f9d6-1f3ff-200d-2642-fe0f","🧖‍♀️":"1f9d6-200d-2640-fe0f","🧖🏻‍♀":"1f9d6-1f3fb-200d-2640-fe0f","🧖🏼‍♀":"1f9d6-1f3fc-200d-2640-fe0f","🧖🏽‍♀":"1f9d6-1f3fd-200d-2640-fe0f","🧖🏾‍♀":"1f9d6-1f3fe-200d-2640-fe0f","🧖🏿‍♀":"1f9d6-1f3ff-200d-2640-fe0f","🧗‍♂️":"1f9d7-200d-2642-fe0f","🧗🏻‍♂":"1f9d7-1f3fb-200d-2642-fe0f","🧗🏼‍♂":"1f9d7-1f3fc-200d-2642-fe0f","🧗🏽‍♂":"1f9d7-1f3fd-200d-2642-fe0f","🧗🏾‍♂":"1f9d7-1f3fe-200d-2642-fe0f","🧗🏿‍♂":"1f9d7-1f3ff-200d-2642-fe0f","🧗‍♀️":"1f9d7-200d-2640-fe0f","🧗🏻‍♀":"1f9d7-1f3fb-200d-2640-fe0f","🧗🏼‍♀":"1f9d7-1f3fc-200d-2640-fe0f","🧗🏽‍♀":"1f9d7-1f3fd-200d-2640-fe0f","🧗🏾‍♀":"1f9d7-1f3fe-200d-2640-fe0f","🧗🏿‍♀":"1f9d7-1f3ff-200d-2640-fe0f","🏌‍♂️":"1f3cc-fe0f-200d-2642-fe0f","🏌️‍♂":"1f3cc-fe0f-200d-2642-fe0f","🏌🏻‍♂":"1f3cc-1f3fb-200d-2642-fe0f","🏌🏼‍♂":"1f3cc-1f3fc-200d-2642-fe0f","🏌🏽‍♂":"1f3cc-1f3fd-200d-2642-fe0f","🏌🏾‍♂":"1f3cc-1f3fe-200d-2642-fe0f","🏌🏿‍♂":"1f3cc-1f3ff-200d-2642-fe0f","🏌‍♀️":"1f3cc-fe0f-200d-2640-fe0f","🏌️‍♀":"1f3cc-fe0f-200d-2640-fe0f","🏌🏻‍♀":"1f3cc-1f3fb-200d-2640-fe0f","🏌🏼‍♀":"1f3cc-1f3fc-200d-2640-fe0f","🏌🏽‍♀":"1f3cc-1f3fd-200d-2640-fe0f","🏌🏾‍♀":"1f3cc-1f3fe-200d-2640-fe0f","🏌🏿‍♀":"1f3cc-1f3ff-200d-2640-fe0f","🏄‍♂️":"1f3c4-200d-2642-fe0f","🏄🏻‍♂":"1f3c4-1f3fb-200d-2642-fe0f","🏄🏼‍♂":"1f3c4-1f3fc-200d-2642-fe0f","🏄🏽‍♂":"1f3c4-1f3fd-200d-2642-fe0f","🏄🏾‍♂":"1f3c4-1f3fe-200d-2642-fe0f","🏄🏿‍♂":"1f3c4-1f3ff-200d-2642-fe0f","🏄‍♀️":"1f3c4-200d-2640-fe0f","🏄🏻‍♀":"1f3c4-1f3fb-200d-2640-fe0f","🏄🏼‍♀":"1f3c4-1f3fc-200d-2640-fe0f","🏄🏽‍♀":"1f3c4-1f3fd-200d-2640-fe0f","🏄🏾‍♀":"1f3c4-1f3fe-200d-2640-fe0f","🏄🏿‍♀":"1f3c4-1f3ff-200d-2640-fe0f","🚣‍♂️":"1f6a3-200d-2642-fe0f","🚣🏻‍♂":"1f6a3-1f3fb-200d-2642-fe0f","🚣🏼‍♂":"1f6a3-1f3fc-200d-2642-fe0f","🚣🏽‍♂":"1f6a3-1f3fd-200d-2642-fe0f","🚣🏾‍♂":"1f6a3-1f3fe-200d-2642-fe0f","🚣🏿‍♂":"1f6a3-1f3ff-200d-2642-fe0f","🚣‍♀️":"1f6a3-200d-2640-fe0f","🚣🏻‍♀":"1f6a3-1f3fb-200d-2640-fe0f","🚣🏼‍♀":"1f6a3-1f3fc-200d-2640-fe0f","🚣🏽‍♀":"1f6a3-1f3fd-200d-2640-fe0f","🚣🏾‍♀":"1f6a3-1f3fe-200d-2640-fe0f","🚣🏿‍♀":"1f6a3-1f3ff-200d-2640-fe0f","🏊‍♂️":"1f3ca-200d-2642-fe0f","🏊🏻‍♂":"1f3ca-1f3fb-200d-2642-fe0f","🏊🏼‍♂":"1f3ca-1f3fc-200d-2642-fe0f","🏊🏽‍♂":"1f3ca-1f3fd-200d-2642-fe0f","🏊🏾‍♂":"1f3ca-1f3fe-200d-2642-fe0f","🏊🏿‍♂":"1f3ca-1f3ff-200d-2642-fe0f","🏊‍♀️":"1f3ca-200d-2640-fe0f","🏊🏻‍♀":"1f3ca-1f3fb-200d-2640-fe0f","🏊🏼‍♀":"1f3ca-1f3fc-200d-2640-fe0f","🏊🏽‍♀":"1f3ca-1f3fd-200d-2640-fe0f","🏊🏾‍♀":"1f3ca-1f3fe-200d-2640-fe0f","🏊🏿‍♀":"1f3ca-1f3ff-200d-2640-fe0f","⛹‍♂️":"26f9-fe0f-200d-2642-fe0f","⛹️‍♂":"26f9-fe0f-200d-2642-fe0f","⛹🏻‍♂":"26f9-1f3fb-200d-2642-fe0f","⛹🏼‍♂":"26f9-1f3fc-200d-2642-fe0f","⛹🏽‍♂":"26f9-1f3fd-200d-2642-fe0f","⛹🏾‍♂":"26f9-1f3fe-200d-2642-fe0f","⛹🏿‍♂":"26f9-1f3ff-200d-2642-fe0f","⛹‍♀️":"26f9-fe0f-200d-2640-fe0f","⛹️‍♀":"26f9-fe0f-200d-2640-fe0f","⛹🏻‍♀":"26f9-1f3fb-200d-2640-fe0f","⛹🏼‍♀":"26f9-1f3fc-200d-2640-fe0f","⛹🏽‍♀":"26f9-1f3fd-200d-2640-fe0f","⛹🏾‍♀":"26f9-1f3fe-200d-2640-fe0f","⛹🏿‍♀":"26f9-1f3ff-200d-2640-fe0f","🏋‍♂️":"1f3cb-fe0f-200d-2642-fe0f","🏋️‍♂":"1f3cb-fe0f-200d-2642-fe0f","🏋🏻‍♂":"1f3cb-1f3fb-200d-2642-fe0f","🏋🏼‍♂":"1f3cb-1f3fc-200d-2642-fe0f","🏋🏽‍♂":"1f3cb-1f3fd-200d-2642-fe0f","🏋🏾‍♂":"1f3cb-1f3fe-200d-2642-fe0f","🏋🏿‍♂":"1f3cb-1f3ff-200d-2642-fe0f","🏋‍♀️":"1f3cb-fe0f-200d-2640-fe0f","🏋️‍♀":"1f3cb-fe0f-200d-2640-fe0f","🏋🏻‍♀":"1f3cb-1f3fb-200d-2640-fe0f","🏋🏼‍♀":"1f3cb-1f3fc-200d-2640-fe0f","🏋🏽‍♀":"1f3cb-1f3fd-200d-2640-fe0f","🏋🏾‍♀":"1f3cb-1f3fe-200d-2640-fe0f","🏋🏿‍♀":"1f3cb-1f3ff-200d-2640-fe0f","🚴‍♂️":"1f6b4-200d-2642-fe0f","🚴🏻‍♂":"1f6b4-1f3fb-200d-2642-fe0f","🚴🏼‍♂":"1f6b4-1f3fc-200d-2642-fe0f","🚴🏽‍♂":"1f6b4-1f3fd-200d-2642-fe0f","🚴🏾‍♂":"1f6b4-1f3fe-200d-2642-fe0f","🚴🏿‍♂":"1f6b4-1f3ff-200d-2642-fe0f","🚴‍♀️":"1f6b4-200d-2640-fe0f","🚴🏻‍♀":"1f6b4-1f3fb-200d-2640-fe0f","🚴🏼‍♀":"1f6b4-1f3fc-200d-2640-fe0f","🚴🏽‍♀":"1f6b4-1f3fd-200d-2640-fe0f","🚴🏾‍♀":"1f6b4-1f3fe-200d-2640-fe0f","🚴🏿‍♀":"1f6b4-1f3ff-200d-2640-fe0f","🚵‍♂️":"1f6b5-200d-2642-fe0f","🚵🏻‍♂":"1f6b5-1f3fb-200d-2642-fe0f","🚵🏼‍♂":"1f6b5-1f3fc-200d-2642-fe0f","🚵🏽‍♂":"1f6b5-1f3fd-200d-2642-fe0f","🚵🏾‍♂":"1f6b5-1f3fe-200d-2642-fe0f","🚵🏿‍♂":"1f6b5-1f3ff-200d-2642-fe0f","🚵‍♀️":"1f6b5-200d-2640-fe0f","🚵🏻‍♀":"1f6b5-1f3fb-200d-2640-fe0f","🚵🏼‍♀":"1f6b5-1f3fc-200d-2640-fe0f","🚵🏽‍♀":"1f6b5-1f3fd-200d-2640-fe0f","🚵🏾‍♀":"1f6b5-1f3fe-200d-2640-fe0f","🚵🏿‍♀":"1f6b5-1f3ff-200d-2640-fe0f","🤸‍♂️":"1f938-200d-2642-fe0f","🤸🏻‍♂":"1f938-1f3fb-200d-2642-fe0f","🤸🏼‍♂":"1f938-1f3fc-200d-2642-fe0f","🤸🏽‍♂":"1f938-1f3fd-200d-2642-fe0f","🤸🏾‍♂":"1f938-1f3fe-200d-2642-fe0f","🤸🏿‍♂":"1f938-1f3ff-200d-2642-fe0f","🤸‍♀️":"1f938-200d-2640-fe0f","🤸🏻‍♀":"1f938-1f3fb-200d-2640-fe0f","🤸🏼‍♀":"1f938-1f3fc-200d-2640-fe0f","🤸🏽‍♀":"1f938-1f3fd-200d-2640-fe0f","🤸🏾‍♀":"1f938-1f3fe-200d-2640-fe0f","🤸🏿‍♀":"1f938-1f3ff-200d-2640-fe0f","🤼‍♂️":"1f93c-200d-2642-fe0f","🤼‍♀️":"1f93c-200d-2640-fe0f","🤽‍♂️":"1f93d-200d-2642-fe0f","🤽🏻‍♂":"1f93d-1f3fb-200d-2642-fe0f","🤽🏼‍♂":"1f93d-1f3fc-200d-2642-fe0f","🤽🏽‍♂":"1f93d-1f3fd-200d-2642-fe0f","🤽🏾‍♂":"1f93d-1f3fe-200d-2642-fe0f","🤽🏿‍♂":"1f93d-1f3ff-200d-2642-fe0f","🤽‍♀️":"1f93d-200d-2640-fe0f","🤽🏻‍♀":"1f93d-1f3fb-200d-2640-fe0f","🤽🏼‍♀":"1f93d-1f3fc-200d-2640-fe0f","🤽🏽‍♀":"1f93d-1f3fd-200d-2640-fe0f","🤽🏾‍♀":"1f93d-1f3fe-200d-2640-fe0f","🤽🏿‍♀":"1f93d-1f3ff-200d-2640-fe0f","🤾‍♂️":"1f93e-200d-2642-fe0f","🤾🏻‍♂":"1f93e-1f3fb-200d-2642-fe0f","🤾🏼‍♂":"1f93e-1f3fc-200d-2642-fe0f","🤾🏽‍♂":"1f93e-1f3fd-200d-2642-fe0f","🤾🏾‍♂":"1f93e-1f3fe-200d-2642-fe0f","🤾🏿‍♂":"1f93e-1f3ff-200d-2642-fe0f","🤾‍♀️":"1f93e-200d-2640-fe0f","🤾🏻‍♀":"1f93e-1f3fb-200d-2640-fe0f","🤾🏼‍♀":"1f93e-1f3fc-200d-2640-fe0f","🤾🏽‍♀":"1f93e-1f3fd-200d-2640-fe0f","🤾🏾‍♀":"1f93e-1f3fe-200d-2640-fe0f","🤾🏿‍♀":"1f93e-1f3ff-200d-2640-fe0f","🤹‍♂️":"1f939-200d-2642-fe0f","🤹🏻‍♂":"1f939-1f3fb-200d-2642-fe0f","🤹🏼‍♂":"1f939-1f3fc-200d-2642-fe0f","🤹🏽‍♂":"1f939-1f3fd-200d-2642-fe0f","🤹🏾‍♂":"1f939-1f3fe-200d-2642-fe0f","🤹🏿‍♂":"1f939-1f3ff-200d-2642-fe0f","🤹‍♀️":"1f939-200d-2640-fe0f","🤹🏻‍♀":"1f939-1f3fb-200d-2640-fe0f","🤹🏼‍♀":"1f939-1f3fc-200d-2640-fe0f","🤹🏽‍♀":"1f939-1f3fd-200d-2640-fe0f","🤹🏾‍♀":"1f939-1f3fe-200d-2640-fe0f","🤹🏿‍♀":"1f939-1f3ff-200d-2640-fe0f","🧘‍♂️":"1f9d8-200d-2642-fe0f","🧘🏻‍♂":"1f9d8-1f3fb-200d-2642-fe0f","🧘🏼‍♂":"1f9d8-1f3fc-200d-2642-fe0f","🧘🏽‍♂":"1f9d8-1f3fd-200d-2642-fe0f","🧘🏾‍♂":"1f9d8-1f3fe-200d-2642-fe0f","🧘🏿‍♂":"1f9d8-1f3ff-200d-2642-fe0f","🧘‍♀️":"1f9d8-200d-2640-fe0f","🧘🏻‍♀":"1f9d8-1f3fb-200d-2640-fe0f","🧘🏼‍♀":"1f9d8-1f3fc-200d-2640-fe0f","🧘🏽‍♀":"1f9d8-1f3fd-200d-2640-fe0f","🧘🏾‍♀":"1f9d8-1f3fe-200d-2640-fe0f","🧘🏿‍♀":"1f9d8-1f3ff-200d-2640-fe0f","🐻‍❄️":"1f43b-200d-2744-fe0f","⛓️‍💥":"26d3-fe0f-200d-1f4a5","🏳️‍🌈":"1f3f3-fe0f-200d-1f308","🏳‍⚧️":"1f3f3-fe0f-200d-26a7-fe0f","🏳️‍⚧":"1f3f3-fe0f-200d-26a7-fe0f","🏴‍☠️":"1f3f4-200d-2620-fe0f","👁️‍🗨️":"1f441-200d-1f5e8","🫱🏻‍🫲🏼":"1faf1-1f3fb-200d-1faf2-1f3fc","🫱🏻‍🫲🏽":"1faf1-1f3fb-200d-1faf2-1f3fd","🫱🏻‍🫲🏾":"1faf1-1f3fb-200d-1faf2-1f3fe","🫱🏻‍🫲🏿":"1faf1-1f3fb-200d-1faf2-1f3ff","🫱🏼‍🫲🏻":"1faf1-1f3fc-200d-1faf2-1f3fb","🫱🏼‍🫲🏽":"1faf1-1f3fc-200d-1faf2-1f3fd","🫱🏼‍🫲🏾":"1faf1-1f3fc-200d-1faf2-1f3fe","🫱🏼‍🫲🏿":"1faf1-1f3fc-200d-1faf2-1f3ff","🫱🏽‍🫲🏻":"1faf1-1f3fd-200d-1faf2-1f3fb","🫱🏽‍🫲🏼":"1faf1-1f3fd-200d-1faf2-1f3fc","🫱🏽‍🫲🏾":"1faf1-1f3fd-200d-1faf2-1f3fe","🫱🏽‍🫲🏿":"1faf1-1f3fd-200d-1faf2-1f3ff","🫱🏾‍🫲🏻":"1faf1-1f3fe-200d-1faf2-1f3fb","🫱🏾‍🫲🏼":"1faf1-1f3fe-200d-1faf2-1f3fc","🫱🏾‍🫲🏽":"1faf1-1f3fe-200d-1faf2-1f3fd","🫱🏾‍🫲🏿":"1faf1-1f3fe-200d-1faf2-1f3ff","🫱🏿‍🫲🏻":"1faf1-1f3ff-200d-1faf2-1f3fb","🫱🏿‍🫲🏼":"1faf1-1f3ff-200d-1faf2-1f3fc","🫱🏿‍🫲🏽":"1faf1-1f3ff-200d-1faf2-1f3fd","🫱🏿‍🫲🏾":"1faf1-1f3ff-200d-1faf2-1f3fe","🧔🏻‍♂️":"1f9d4-1f3fb-200d-2642-fe0f","🧔🏼‍♂️":"1f9d4-1f3fc-200d-2642-fe0f","🧔🏽‍♂️":"1f9d4-1f3fd-200d-2642-fe0f","🧔🏾‍♂️":"1f9d4-1f3fe-200d-2642-fe0f","🧔🏿‍♂️":"1f9d4-1f3ff-200d-2642-fe0f","🧔🏻‍♀️":"1f9d4-1f3fb-200d-2640-fe0f","🧔🏼‍♀️":"1f9d4-1f3fc-200d-2640-fe0f","🧔🏽‍♀️":"1f9d4-1f3fd-200d-2640-fe0f","🧔🏾‍♀️":"1f9d4-1f3fe-200d-2640-fe0f","🧔🏿‍♀️":"1f9d4-1f3ff-200d-2640-fe0f","👱🏻‍♀️":"1f471-1f3fb-200d-2640-fe0f","👱🏼‍♀️":"1f471-1f3fc-200d-2640-fe0f","👱🏽‍♀️":"1f471-1f3fd-200d-2640-fe0f","👱🏾‍♀️":"1f471-1f3fe-200d-2640-fe0f","👱🏿‍♀️":"1f471-1f3ff-200d-2640-fe0f","👱🏻‍♂️":"1f471-1f3fb-200d-2642-fe0f","👱🏼‍♂️":"1f471-1f3fc-200d-2642-fe0f","👱🏽‍♂️":"1f471-1f3fd-200d-2642-fe0f","👱🏾‍♂️":"1f471-1f3fe-200d-2642-fe0f","👱🏿‍♂️":"1f471-1f3ff-200d-2642-fe0f","🙍🏻‍♂️":"1f64d-1f3fb-200d-2642-fe0f","🙍🏼‍♂️":"1f64d-1f3fc-200d-2642-fe0f","🙍🏽‍♂️":"1f64d-1f3fd-200d-2642-fe0f","🙍🏾‍♂️":"1f64d-1f3fe-200d-2642-fe0f","🙍🏿‍♂️":"1f64d-1f3ff-200d-2642-fe0f","🙍🏻‍♀️":"1f64d-1f3fb-200d-2640-fe0f","🙍🏼‍♀️":"1f64d-1f3fc-200d-2640-fe0f","🙍🏽‍♀️":"1f64d-1f3fd-200d-2640-fe0f","🙍🏾‍♀️":"1f64d-1f3fe-200d-2640-fe0f","🙍🏿‍♀️":"1f64d-1f3ff-200d-2640-fe0f","🙎🏻‍♂️":"1f64e-1f3fb-200d-2642-fe0f","🙎🏼‍♂️":"1f64e-1f3fc-200d-2642-fe0f","🙎🏽‍♂️":"1f64e-1f3fd-200d-2642-fe0f","🙎🏾‍♂️":"1f64e-1f3fe-200d-2642-fe0f","🙎🏿‍♂️":"1f64e-1f3ff-200d-2642-fe0f","🙎🏻‍♀️":"1f64e-1f3fb-200d-2640-fe0f","🙎🏼‍♀️":"1f64e-1f3fc-200d-2640-fe0f","🙎🏽‍♀️":"1f64e-1f3fd-200d-2640-fe0f","🙎🏾‍♀️":"1f64e-1f3fe-200d-2640-fe0f","🙎🏿‍♀️":"1f64e-1f3ff-200d-2640-fe0f","🙅🏻‍♂️":"1f645-1f3fb-200d-2642-fe0f","🙅🏼‍♂️":"1f645-1f3fc-200d-2642-fe0f","🙅🏽‍♂️":"1f645-1f3fd-200d-2642-fe0f","🙅🏾‍♂️":"1f645-1f3fe-200d-2642-fe0f","🙅🏿‍♂️":"1f645-1f3ff-200d-2642-fe0f","🙅🏻‍♀️":"1f645-1f3fb-200d-2640-fe0f","🙅🏼‍♀️":"1f645-1f3fc-200d-2640-fe0f","🙅🏽‍♀️":"1f645-1f3fd-200d-2640-fe0f","🙅🏾‍♀️":"1f645-1f3fe-200d-2640-fe0f","🙅🏿‍♀️":"1f645-1f3ff-200d-2640-fe0f","🙆🏻‍♂️":"1f646-1f3fb-200d-2642-fe0f","🙆🏼‍♂️":"1f646-1f3fc-200d-2642-fe0f","🙆🏽‍♂️":"1f646-1f3fd-200d-2642-fe0f","🙆🏾‍♂️":"1f646-1f3fe-200d-2642-fe0f","🙆🏿‍♂️":"1f646-1f3ff-200d-2642-fe0f","🙆🏻‍♀️":"1f646-1f3fb-200d-2640-fe0f","🙆🏼‍♀️":"1f646-1f3fc-200d-2640-fe0f","🙆🏽‍♀️":"1f646-1f3fd-200d-2640-fe0f","🙆🏾‍♀️":"1f646-1f3fe-200d-2640-fe0f","🙆🏿‍♀️":"1f646-1f3ff-200d-2640-fe0f","💁🏻‍♂️":"1f481-1f3fb-200d-2642-fe0f","💁🏼‍♂️":"1f481-1f3fc-200d-2642-fe0f","💁🏽‍♂️":"1f481-1f3fd-200d-2642-fe0f","💁🏾‍♂️":"1f481-1f3fe-200d-2642-fe0f","💁🏿‍♂️":"1f481-1f3ff-200d-2642-fe0f","💁🏻‍♀️":"1f481-1f3fb-200d-2640-fe0f","💁🏼‍♀️":"1f481-1f3fc-200d-2640-fe0f","💁🏽‍♀️":"1f481-1f3fd-200d-2640-fe0f","💁🏾‍♀️":"1f481-1f3fe-200d-2640-fe0f","💁🏿‍♀️":"1f481-1f3ff-200d-2640-fe0f","🙋🏻‍♂️":"1f64b-1f3fb-200d-2642-fe0f","🙋🏼‍♂️":"1f64b-1f3fc-200d-2642-fe0f","🙋🏽‍♂️":"1f64b-1f3fd-200d-2642-fe0f","🙋🏾‍♂️":"1f64b-1f3fe-200d-2642-fe0f","🙋🏿‍♂️":"1f64b-1f3ff-200d-2642-fe0f","🙋🏻‍♀️":"1f64b-1f3fb-200d-2640-fe0f","🙋🏼‍♀️":"1f64b-1f3fc-200d-2640-fe0f","🙋🏽‍♀️":"1f64b-1f3fd-200d-2640-fe0f","🙋🏾‍♀️":"1f64b-1f3fe-200d-2640-fe0f","🙋🏿‍♀️":"1f64b-1f3ff-200d-2640-fe0f","🧏🏻‍♂️":"1f9cf-1f3fb-200d-2642-fe0f","🧏🏼‍♂️":"1f9cf-1f3fc-200d-2642-fe0f","🧏🏽‍♂️":"1f9cf-1f3fd-200d-2642-fe0f","🧏🏾‍♂️":"1f9cf-1f3fe-200d-2642-fe0f","🧏🏿‍♂️":"1f9cf-1f3ff-200d-2642-fe0f","🧏🏻‍♀️":"1f9cf-1f3fb-200d-2640-fe0f","🧏🏼‍♀️":"1f9cf-1f3fc-200d-2640-fe0f","🧏🏽‍♀️":"1f9cf-1f3fd-200d-2640-fe0f","🧏🏾‍♀️":"1f9cf-1f3fe-200d-2640-fe0f","🧏🏿‍♀️":"1f9cf-1f3ff-200d-2640-fe0f","🙇🏻‍♂️":"1f647-1f3fb-200d-2642-fe0f","🙇🏼‍♂️":"1f647-1f3fc-200d-2642-fe0f","🙇🏽‍♂️":"1f647-1f3fd-200d-2642-fe0f","🙇🏾‍♂️":"1f647-1f3fe-200d-2642-fe0f","🙇🏿‍♂️":"1f647-1f3ff-200d-2642-fe0f","🙇🏻‍♀️":"1f647-1f3fb-200d-2640-fe0f","🙇🏼‍♀️":"1f647-1f3fc-200d-2640-fe0f","🙇🏽‍♀️":"1f647-1f3fd-200d-2640-fe0f","🙇🏾‍♀️":"1f647-1f3fe-200d-2640-fe0f","🙇🏿‍♀️":"1f647-1f3ff-200d-2640-fe0f","🤦🏻‍♂️":"1f926-1f3fb-200d-2642-fe0f","🤦🏼‍♂️":"1f926-1f3fc-200d-2642-fe0f","🤦🏽‍♂️":"1f926-1f3fd-200d-2642-fe0f","🤦🏾‍♂️":"1f926-1f3fe-200d-2642-fe0f","🤦🏿‍♂️":"1f926-1f3ff-200d-2642-fe0f","🤦🏻‍♀️":"1f926-1f3fb-200d-2640-fe0f","🤦🏼‍♀️":"1f926-1f3fc-200d-2640-fe0f","🤦🏽‍♀️":"1f926-1f3fd-200d-2640-fe0f","🤦🏾‍♀️":"1f926-1f3fe-200d-2640-fe0f","🤦🏿‍♀️":"1f926-1f3ff-200d-2640-fe0f","🤷🏻‍♂️":"1f937-1f3fb-200d-2642-fe0f","🤷🏼‍♂️":"1f937-1f3fc-200d-2642-fe0f","🤷🏽‍♂️":"1f937-1f3fd-200d-2642-fe0f","🤷🏾‍♂️":"1f937-1f3fe-200d-2642-fe0f","🤷🏿‍♂️":"1f937-1f3ff-200d-2642-fe0f","🤷🏻‍♀️":"1f937-1f3fb-200d-2640-fe0f","🤷🏼‍♀️":"1f937-1f3fc-200d-2640-fe0f","🤷🏽‍♀️":"1f937-1f3fd-200d-2640-fe0f","🤷🏾‍♀️":"1f937-1f3fe-200d-2640-fe0f","🤷🏿‍♀️":"1f937-1f3ff-200d-2640-fe0f","🧑🏻‍⚕️":"1f9d1-1f3fb-200d-2695-fe0f","🧑🏼‍⚕️":"1f9d1-1f3fc-200d-2695-fe0f","🧑🏽‍⚕️":"1f9d1-1f3fd-200d-2695-fe0f","🧑🏾‍⚕️":"1f9d1-1f3fe-200d-2695-fe0f","🧑🏿‍⚕️":"1f9d1-1f3ff-200d-2695-fe0f","👨🏻‍⚕️":"1f468-1f3fb-200d-2695-fe0f","👨🏼‍⚕️":"1f468-1f3fc-200d-2695-fe0f","👨🏽‍⚕️":"1f468-1f3fd-200d-2695-fe0f","👨🏾‍⚕️":"1f468-1f3fe-200d-2695-fe0f","👨🏿‍⚕️":"1f468-1f3ff-200d-2695-fe0f","👩🏻‍⚕️":"1f469-1f3fb-200d-2695-fe0f","👩🏼‍⚕️":"1f469-1f3fc-200d-2695-fe0f","👩🏽‍⚕️":"1f469-1f3fd-200d-2695-fe0f","👩🏾‍⚕️":"1f469-1f3fe-200d-2695-fe0f","👩🏿‍⚕️":"1f469-1f3ff-200d-2695-fe0f","🧑🏻‍⚖️":"1f9d1-1f3fb-200d-2696-fe0f","🧑🏼‍⚖️":"1f9d1-1f3fc-200d-2696-fe0f","🧑🏽‍⚖️":"1f9d1-1f3fd-200d-2696-fe0f","🧑🏾‍⚖️":"1f9d1-1f3fe-200d-2696-fe0f","🧑🏿‍⚖️":"1f9d1-1f3ff-200d-2696-fe0f","👨🏻‍⚖️":"1f468-1f3fb-200d-2696-fe0f","👨🏼‍⚖️":"1f468-1f3fc-200d-2696-fe0f","👨🏽‍⚖️":"1f468-1f3fd-200d-2696-fe0f","👨🏾‍⚖️":"1f468-1f3fe-200d-2696-fe0f","👨🏿‍⚖️":"1f468-1f3ff-200d-2696-fe0f","👩🏻‍⚖️":"1f469-1f3fb-200d-2696-fe0f","👩🏼‍⚖️":"1f469-1f3fc-200d-2696-fe0f","👩🏽‍⚖️":"1f469-1f3fd-200d-2696-fe0f","👩🏾‍⚖️":"1f469-1f3fe-200d-2696-fe0f","👩🏿‍⚖️":"1f469-1f3ff-200d-2696-fe0f","🧑🏻‍✈️":"1f9d1-1f3fb-200d-2708-fe0f","🧑🏼‍✈️":"1f9d1-1f3fc-200d-2708-fe0f","🧑🏽‍✈️":"1f9d1-1f3fd-200d-2708-fe0f","🧑🏾‍✈️":"1f9d1-1f3fe-200d-2708-fe0f","🧑🏿‍✈️":"1f9d1-1f3ff-200d-2708-fe0f","👨🏻‍✈️":"1f468-1f3fb-200d-2708-fe0f","👨🏼‍✈️":"1f468-1f3fc-200d-2708-fe0f","👨🏽‍✈️":"1f468-1f3fd-200d-2708-fe0f","👨🏾‍✈️":"1f468-1f3fe-200d-2708-fe0f","👨🏿‍✈️":"1f468-1f3ff-200d-2708-fe0f","👩🏻‍✈️":"1f469-1f3fb-200d-2708-fe0f","👩🏼‍✈️":"1f469-1f3fc-200d-2708-fe0f","👩🏽‍✈️":"1f469-1f3fd-200d-2708-fe0f","👩🏾‍✈️":"1f469-1f3fe-200d-2708-fe0f","👩🏿‍✈️":"1f469-1f3ff-200d-2708-fe0f","👮🏻‍♂️":"1f46e-1f3fb-200d-2642-fe0f","👮🏼‍♂️":"1f46e-1f3fc-200d-2642-fe0f","👮🏽‍♂️":"1f46e-1f3fd-200d-2642-fe0f","👮🏾‍♂️":"1f46e-1f3fe-200d-2642-fe0f","👮🏿‍♂️":"1f46e-1f3ff-200d-2642-fe0f","👮🏻‍♀️":"1f46e-1f3fb-200d-2640-fe0f","👮🏼‍♀️":"1f46e-1f3fc-200d-2640-fe0f","👮🏽‍♀️":"1f46e-1f3fd-200d-2640-fe0f","👮🏾‍♀️":"1f46e-1f3fe-200d-2640-fe0f","👮🏿‍♀️":"1f46e-1f3ff-200d-2640-fe0f","🕵️‍♂️":"1f575-fe0f-200d-2642-fe0f","🕵🏻‍♂️":"1f575-1f3fb-200d-2642-fe0f","🕵🏼‍♂️":"1f575-1f3fc-200d-2642-fe0f","🕵🏽‍♂️":"1f575-1f3fd-200d-2642-fe0f","🕵🏾‍♂️":"1f575-1f3fe-200d-2642-fe0f","🕵🏿‍♂️":"1f575-1f3ff-200d-2642-fe0f","🕵️‍♀️":"1f575-fe0f-200d-2640-fe0f","🕵🏻‍♀️":"1f575-1f3fb-200d-2640-fe0f","🕵🏼‍♀️":"1f575-1f3fc-200d-2640-fe0f","🕵🏽‍♀️":"1f575-1f3fd-200d-2640-fe0f","🕵🏾‍♀️":"1f575-1f3fe-200d-2640-fe0f","🕵🏿‍♀️":"1f575-1f3ff-200d-2640-fe0f","💂🏻‍♂️":"1f482-1f3fb-200d-2642-fe0f","💂🏼‍♂️":"1f482-1f3fc-200d-2642-fe0f","💂🏽‍♂️":"1f482-1f3fd-200d-2642-fe0f","💂🏾‍♂️":"1f482-1f3fe-200d-2642-fe0f","💂🏿‍♂️":"1f482-1f3ff-200d-2642-fe0f","💂🏻‍♀️":"1f482-1f3fb-200d-2640-fe0f","💂🏼‍♀️":"1f482-1f3fc-200d-2640-fe0f","💂🏽‍♀️":"1f482-1f3fd-200d-2640-fe0f","💂🏾‍♀️":"1f482-1f3fe-200d-2640-fe0f","💂🏿‍♀️":"1f482-1f3ff-200d-2640-fe0f","👷🏻‍♂️":"1f477-1f3fb-200d-2642-fe0f","👷🏼‍♂️":"1f477-1f3fc-200d-2642-fe0f","👷🏽‍♂️":"1f477-1f3fd-200d-2642-fe0f","👷🏾‍♂️":"1f477-1f3fe-200d-2642-fe0f","👷🏿‍♂️":"1f477-1f3ff-200d-2642-fe0f","👷🏻‍♀️":"1f477-1f3fb-200d-2640-fe0f","👷🏼‍♀️":"1f477-1f3fc-200d-2640-fe0f","👷🏽‍♀️":"1f477-1f3fd-200d-2640-fe0f","👷🏾‍♀️":"1f477-1f3fe-200d-2640-fe0f","👷🏿‍♀️":"1f477-1f3ff-200d-2640-fe0f","👳🏻‍♂️":"1f473-1f3fb-200d-2642-fe0f","👳🏼‍♂️":"1f473-1f3fc-200d-2642-fe0f","👳🏽‍♂️":"1f473-1f3fd-200d-2642-fe0f","👳🏾‍♂️":"1f473-1f3fe-200d-2642-fe0f","👳🏿‍♂️":"1f473-1f3ff-200d-2642-fe0f","👳🏻‍♀️":"1f473-1f3fb-200d-2640-fe0f","👳🏼‍♀️":"1f473-1f3fc-200d-2640-fe0f","👳🏽‍♀️":"1f473-1f3fd-200d-2640-fe0f","👳🏾‍♀️":"1f473-1f3fe-200d-2640-fe0f","👳🏿‍♀️":"1f473-1f3ff-200d-2640-fe0f","🤵🏻‍♂️":"1f935-1f3fb-200d-2642-fe0f","🤵🏼‍♂️":"1f935-1f3fc-200d-2642-fe0f","🤵🏽‍♂️":"1f935-1f3fd-200d-2642-fe0f","🤵🏾‍♂️":"1f935-1f3fe-200d-2642-fe0f","🤵🏿‍♂️":"1f935-1f3ff-200d-2642-fe0f","🤵🏻‍♀️":"1f935-1f3fb-200d-2640-fe0f","🤵🏼‍♀️":"1f935-1f3fc-200d-2640-fe0f","🤵🏽‍♀️":"1f935-1f3fd-200d-2640-fe0f","🤵🏾‍♀️":"1f935-1f3fe-200d-2640-fe0f","🤵🏿‍♀️":"1f935-1f3ff-200d-2640-fe0f","👰🏻‍♂️":"1f470-1f3fb-200d-2642-fe0f","👰🏼‍♂️":"1f470-1f3fc-200d-2642-fe0f","👰🏽‍♂️":"1f470-1f3fd-200d-2642-fe0f","👰🏾‍♂️":"1f470-1f3fe-200d-2642-fe0f","👰🏿‍♂️":"1f470-1f3ff-200d-2642-fe0f","👰🏻‍♀️":"1f470-1f3fb-200d-2640-fe0f","👰🏼‍♀️":"1f470-1f3fc-200d-2640-fe0f","👰🏽‍♀️":"1f470-1f3fd-200d-2640-fe0f","👰🏾‍♀️":"1f470-1f3fe-200d-2640-fe0f","👰🏿‍♀️":"1f470-1f3ff-200d-2640-fe0f","🦸🏻‍♂️":"1f9b8-1f3fb-200d-2642-fe0f","🦸🏼‍♂️":"1f9b8-1f3fc-200d-2642-fe0f","🦸🏽‍♂️":"1f9b8-1f3fd-200d-2642-fe0f","🦸🏾‍♂️":"1f9b8-1f3fe-200d-2642-fe0f","🦸🏿‍♂️":"1f9b8-1f3ff-200d-2642-fe0f","🦸🏻‍♀️":"1f9b8-1f3fb-200d-2640-fe0f","🦸🏼‍♀️":"1f9b8-1f3fc-200d-2640-fe0f","🦸🏽‍♀️":"1f9b8-1f3fd-200d-2640-fe0f","🦸🏾‍♀️":"1f9b8-1f3fe-200d-2640-fe0f","🦸🏿‍♀️":"1f9b8-1f3ff-200d-2640-fe0f","🦹🏻‍♂️":"1f9b9-1f3fb-200d-2642-fe0f","🦹🏼‍♂️":"1f9b9-1f3fc-200d-2642-fe0f","🦹🏽‍♂️":"1f9b9-1f3fd-200d-2642-fe0f","🦹🏾‍♂️":"1f9b9-1f3fe-200d-2642-fe0f","🦹🏿‍♂️":"1f9b9-1f3ff-200d-2642-fe0f","🦹🏻‍♀️":"1f9b9-1f3fb-200d-2640-fe0f","🦹🏼‍♀️":"1f9b9-1f3fc-200d-2640-fe0f","🦹🏽‍♀️":"1f9b9-1f3fd-200d-2640-fe0f","🦹🏾‍♀️":"1f9b9-1f3fe-200d-2640-fe0f","🦹🏿‍♀️":"1f9b9-1f3ff-200d-2640-fe0f","🧙🏻‍♂️":"1f9d9-1f3fb-200d-2642-fe0f","🧙🏼‍♂️":"1f9d9-1f3fc-200d-2642-fe0f","🧙🏽‍♂️":"1f9d9-1f3fd-200d-2642-fe0f","🧙🏾‍♂️":"1f9d9-1f3fe-200d-2642-fe0f","🧙🏿‍♂️":"1f9d9-1f3ff-200d-2642-fe0f","🧙🏻‍♀️":"1f9d9-1f3fb-200d-2640-fe0f","🧙🏼‍♀️":"1f9d9-1f3fc-200d-2640-fe0f","🧙🏽‍♀️":"1f9d9-1f3fd-200d-2640-fe0f","🧙🏾‍♀️":"1f9d9-1f3fe-200d-2640-fe0f","🧙🏿‍♀️":"1f9d9-1f3ff-200d-2640-fe0f","🧚🏻‍♂️":"1f9da-1f3fb-200d-2642-fe0f","🧚🏼‍♂️":"1f9da-1f3fc-200d-2642-fe0f","🧚🏽‍♂️":"1f9da-1f3fd-200d-2642-fe0f","🧚🏾‍♂️":"1f9da-1f3fe-200d-2642-fe0f","🧚🏿‍♂️":"1f9da-1f3ff-200d-2642-fe0f","🧚🏻‍♀️":"1f9da-1f3fb-200d-2640-fe0f","🧚🏼‍♀️":"1f9da-1f3fc-200d-2640-fe0f","🧚🏽‍♀️":"1f9da-1f3fd-200d-2640-fe0f","🧚🏾‍♀️":"1f9da-1f3fe-200d-2640-fe0f","🧚🏿‍♀️":"1f9da-1f3ff-200d-2640-fe0f","🧛🏻‍♂️":"1f9db-1f3fb-200d-2642-fe0f","🧛🏼‍♂️":"1f9db-1f3fc-200d-2642-fe0f","🧛🏽‍♂️":"1f9db-1f3fd-200d-2642-fe0f","🧛🏾‍♂️":"1f9db-1f3fe-200d-2642-fe0f","🧛🏿‍♂️":"1f9db-1f3ff-200d-2642-fe0f","🧛🏻‍♀️":"1f9db-1f3fb-200d-2640-fe0f","🧛🏼‍♀️":"1f9db-1f3fc-200d-2640-fe0f","🧛🏽‍♀️":"1f9db-1f3fd-200d-2640-fe0f","🧛🏾‍♀️":"1f9db-1f3fe-200d-2640-fe0f","🧛🏿‍♀️":"1f9db-1f3ff-200d-2640-fe0f","🧜🏻‍♂️":"1f9dc-1f3fb-200d-2642-fe0f","🧜🏼‍♂️":"1f9dc-1f3fc-200d-2642-fe0f","🧜🏽‍♂️":"1f9dc-1f3fd-200d-2642-fe0f","🧜🏾‍♂️":"1f9dc-1f3fe-200d-2642-fe0f","🧜🏿‍♂️":"1f9dc-1f3ff-200d-2642-fe0f","🧜🏻‍♀️":"1f9dc-1f3fb-200d-2640-fe0f","🧜🏼‍♀️":"1f9dc-1f3fc-200d-2640-fe0f","🧜🏽‍♀️":"1f9dc-1f3fd-200d-2640-fe0f","🧜🏾‍♀️":"1f9dc-1f3fe-200d-2640-fe0f","🧜🏿‍♀️":"1f9dc-1f3ff-200d-2640-fe0f","🧝🏻‍♂️":"1f9dd-1f3fb-200d-2642-fe0f","🧝🏼‍♂️":"1f9dd-1f3fc-200d-2642-fe0f","🧝🏽‍♂️":"1f9dd-1f3fd-200d-2642-fe0f","🧝🏾‍♂️":"1f9dd-1f3fe-200d-2642-fe0f","🧝🏿‍♂️":"1f9dd-1f3ff-200d-2642-fe0f","🧝🏻‍♀️":"1f9dd-1f3fb-200d-2640-fe0f","🧝🏼‍♀️":"1f9dd-1f3fc-200d-2640-fe0f","🧝🏽‍♀️":"1f9dd-1f3fd-200d-2640-fe0f","🧝🏾‍♀️":"1f9dd-1f3fe-200d-2640-fe0f","🧝🏿‍♀️":"1f9dd-1f3ff-200d-2640-fe0f","💆🏻‍♂️":"1f486-1f3fb-200d-2642-fe0f","💆🏼‍♂️":"1f486-1f3fc-200d-2642-fe0f","💆🏽‍♂️":"1f486-1f3fd-200d-2642-fe0f","💆🏾‍♂️":"1f486-1f3fe-200d-2642-fe0f","💆🏿‍♂️":"1f486-1f3ff-200d-2642-fe0f","💆🏻‍♀️":"1f486-1f3fb-200d-2640-fe0f","💆🏼‍♀️":"1f486-1f3fc-200d-2640-fe0f","💆🏽‍♀️":"1f486-1f3fd-200d-2640-fe0f","💆🏾‍♀️":"1f486-1f3fe-200d-2640-fe0f","💆🏿‍♀️":"1f486-1f3ff-200d-2640-fe0f","💇🏻‍♂️":"1f487-1f3fb-200d-2642-fe0f","💇🏼‍♂️":"1f487-1f3fc-200d-2642-fe0f","💇🏽‍♂️":"1f487-1f3fd-200d-2642-fe0f","💇🏾‍♂️":"1f487-1f3fe-200d-2642-fe0f","💇🏿‍♂️":"1f487-1f3ff-200d-2642-fe0f","💇🏻‍♀️":"1f487-1f3fb-200d-2640-fe0f","💇🏼‍♀️":"1f487-1f3fc-200d-2640-fe0f","💇🏽‍♀️":"1f487-1f3fd-200d-2640-fe0f","💇🏾‍♀️":"1f487-1f3fe-200d-2640-fe0f","💇🏿‍♀️":"1f487-1f3ff-200d-2640-fe0f","🚶🏻‍♂️":"1f6b6-1f3fb-200d-2642-fe0f","🚶🏼‍♂️":"1f6b6-1f3fc-200d-2642-fe0f","🚶🏽‍♂️":"1f6b6-1f3fd-200d-2642-fe0f","🚶🏾‍♂️":"1f6b6-1f3fe-200d-2642-fe0f","🚶🏿‍♂️":"1f6b6-1f3ff-200d-2642-fe0f","🚶🏻‍♀️":"1f6b6-1f3fb-200d-2640-fe0f","🚶🏼‍♀️":"1f6b6-1f3fc-200d-2640-fe0f","🚶🏽‍♀️":"1f6b6-1f3fd-200d-2640-fe0f","🚶🏾‍♀️":"1f6b6-1f3fe-200d-2640-fe0f","🚶🏿‍♀️":"1f6b6-1f3ff-200d-2640-fe0f","🚶🏻‍➡️":"1f6b6-1f3fb-200d-27a1-fe0f","🚶🏼‍➡️":"1f6b6-1f3fc-200d-27a1-fe0f","🚶🏽‍➡️":"1f6b6-1f3fd-200d-27a1-fe0f","🚶🏾‍➡️":"1f6b6-1f3fe-200d-27a1-fe0f","🚶🏿‍➡️":"1f6b6-1f3ff-200d-27a1-fe0f","🚶‍♀‍➡":"1f6b6-200d-2640-fe0f-200d-27a1-fe0f","🚶‍♂‍➡":"1f6b6-200d-2642-fe0f-200d-27a1-fe0f","🧍🏻‍♂️":"1f9cd-1f3fb-200d-2642-fe0f","🧍🏼‍♂️":"1f9cd-1f3fc-200d-2642-fe0f","🧍🏽‍♂️":"1f9cd-1f3fd-200d-2642-fe0f","🧍🏾‍♂️":"1f9cd-1f3fe-200d-2642-fe0f","🧍🏿‍♂️":"1f9cd-1f3ff-200d-2642-fe0f","🧍🏻‍♀️":"1f9cd-1f3fb-200d-2640-fe0f","🧍🏼‍♀️":"1f9cd-1f3fc-200d-2640-fe0f","🧍🏽‍♀️":"1f9cd-1f3fd-200d-2640-fe0f","🧍🏾‍♀️":"1f9cd-1f3fe-200d-2640-fe0f","🧍🏿‍♀️":"1f9cd-1f3ff-200d-2640-fe0f","🧎🏻‍♂️":"1f9ce-1f3fb-200d-2642-fe0f","🧎🏼‍♂️":"1f9ce-1f3fc-200d-2642-fe0f","🧎🏽‍♂️":"1f9ce-1f3fd-200d-2642-fe0f","🧎🏾‍♂️":"1f9ce-1f3fe-200d-2642-fe0f","🧎🏿‍♂️":"1f9ce-1f3ff-200d-2642-fe0f","🧎🏻‍♀️":"1f9ce-1f3fb-200d-2640-fe0f","🧎🏼‍♀️":"1f9ce-1f3fc-200d-2640-fe0f","🧎🏽‍♀️":"1f9ce-1f3fd-200d-2640-fe0f","🧎🏾‍♀️":"1f9ce-1f3fe-200d-2640-fe0f","🧎🏿‍♀️":"1f9ce-1f3ff-200d-2640-fe0f","🧎🏻‍➡️":"1f9ce-1f3fb-200d-27a1-fe0f","🧎🏼‍➡️":"1f9ce-1f3fc-200d-27a1-fe0f","🧎🏽‍➡️":"1f9ce-1f3fd-200d-27a1-fe0f","🧎🏾‍➡️":"1f9ce-1f3fe-200d-27a1-fe0f","🧎🏿‍➡️":"1f9ce-1f3ff-200d-27a1-fe0f","🧎‍♀‍➡":"1f9ce-200d-2640-fe0f-200d-27a1-fe0f","🧎‍♂‍➡":"1f9ce-200d-2642-fe0f-200d-27a1-fe0f","🧑‍🦯‍➡":"1f9d1-200d-1f9af-200d-27a1-fe0f","👨‍🦯‍➡":"1f468-200d-1f9af-200d-27a1-fe0f","👩‍🦯‍➡":"1f469-200d-1f9af-200d-27a1-fe0f","🧑‍🦼‍➡":"1f9d1-200d-1f9bc-200d-27a1-fe0f","👨‍🦼‍➡":"1f468-200d-1f9bc-200d-27a1-fe0f","👩‍🦼‍➡":"1f469-200d-1f9bc-200d-27a1-fe0f","🧑‍🦽‍➡":"1f9d1-200d-1f9bd-200d-27a1-fe0f","👨‍🦽‍➡":"1f468-200d-1f9bd-200d-27a1-fe0f","👩‍🦽‍➡":"1f469-200d-1f9bd-200d-27a1-fe0f","🏃🏻‍♂️":"1f3c3-1f3fb-200d-2642-fe0f","🏃🏼‍♂️":"1f3c3-1f3fc-200d-2642-fe0f","🏃🏽‍♂️":"1f3c3-1f3fd-200d-2642-fe0f","🏃🏾‍♂️":"1f3c3-1f3fe-200d-2642-fe0f","🏃🏿‍♂️":"1f3c3-1f3ff-200d-2642-fe0f","🏃🏻‍♀️":"1f3c3-1f3fb-200d-2640-fe0f","🏃🏼‍♀️":"1f3c3-1f3fc-200d-2640-fe0f","🏃🏽‍♀️":"1f3c3-1f3fd-200d-2640-fe0f","🏃🏾‍♀️":"1f3c3-1f3fe-200d-2640-fe0f","🏃🏿‍♀️":"1f3c3-1f3ff-200d-2640-fe0f","🏃🏻‍➡️":"1f3c3-1f3fb-200d-27a1-fe0f","🏃🏼‍➡️":"1f3c3-1f3fc-200d-27a1-fe0f","🏃🏽‍➡️":"1f3c3-1f3fd-200d-27a1-fe0f","🏃🏾‍➡️":"1f3c3-1f3fe-200d-27a1-fe0f","🏃🏿‍➡️":"1f3c3-1f3ff-200d-27a1-fe0f","🏃‍♀‍➡":"1f3c3-200d-2640-fe0f-200d-27a1-fe0f","🏃‍♂‍➡":"1f3c3-200d-2642-fe0f-200d-27a1-fe0f","🧖🏻‍♂️":"1f9d6-1f3fb-200d-2642-fe0f","🧖🏼‍♂️":"1f9d6-1f3fc-200d-2642-fe0f","🧖🏽‍♂️":"1f9d6-1f3fd-200d-2642-fe0f","🧖🏾‍♂️":"1f9d6-1f3fe-200d-2642-fe0f","🧖🏿‍♂️":"1f9d6-1f3ff-200d-2642-fe0f","🧖🏻‍♀️":"1f9d6-1f3fb-200d-2640-fe0f","🧖🏼‍♀️":"1f9d6-1f3fc-200d-2640-fe0f","🧖🏽‍♀️":"1f9d6-1f3fd-200d-2640-fe0f","🧖🏾‍♀️":"1f9d6-1f3fe-200d-2640-fe0f","🧖🏿‍♀️":"1f9d6-1f3ff-200d-2640-fe0f","🧗🏻‍♂️":"1f9d7-1f3fb-200d-2642-fe0f","🧗🏼‍♂️":"1f9d7-1f3fc-200d-2642-fe0f","🧗🏽‍♂️":"1f9d7-1f3fd-200d-2642-fe0f","🧗🏾‍♂️":"1f9d7-1f3fe-200d-2642-fe0f","🧗🏿‍♂️":"1f9d7-1f3ff-200d-2642-fe0f","🧗🏻‍♀️":"1f9d7-1f3fb-200d-2640-fe0f","🧗🏼‍♀️":"1f9d7-1f3fc-200d-2640-fe0f","🧗🏽‍♀️":"1f9d7-1f3fd-200d-2640-fe0f","🧗🏾‍♀️":"1f9d7-1f3fe-200d-2640-fe0f","🧗🏿‍♀️":"1f9d7-1f3ff-200d-2640-fe0f","🏌️‍♂️":"1f3cc-fe0f-200d-2642-fe0f","🏌🏻‍♂️":"1f3cc-1f3fb-200d-2642-fe0f","🏌🏼‍♂️":"1f3cc-1f3fc-200d-2642-fe0f","🏌🏽‍♂️":"1f3cc-1f3fd-200d-2642-fe0f","🏌🏾‍♂️":"1f3cc-1f3fe-200d-2642-fe0f","🏌🏿‍♂️":"1f3cc-1f3ff-200d-2642-fe0f","🏌️‍♀️":"1f3cc-fe0f-200d-2640-fe0f","🏌🏻‍♀️":"1f3cc-1f3fb-200d-2640-fe0f","🏌🏼‍♀️":"1f3cc-1f3fc-200d-2640-fe0f","🏌🏽‍♀️":"1f3cc-1f3fd-200d-2640-fe0f","🏌🏾‍♀️":"1f3cc-1f3fe-200d-2640-fe0f","🏌🏿‍♀️":"1f3cc-1f3ff-200d-2640-fe0f","🏄🏻‍♂️":"1f3c4-1f3fb-200d-2642-fe0f","🏄🏼‍♂️":"1f3c4-1f3fc-200d-2642-fe0f","🏄🏽‍♂️":"1f3c4-1f3fd-200d-2642-fe0f","🏄🏾‍♂️":"1f3c4-1f3fe-200d-2642-fe0f","🏄🏿‍♂️":"1f3c4-1f3ff-200d-2642-fe0f","🏄🏻‍♀️":"1f3c4-1f3fb-200d-2640-fe0f","🏄🏼‍♀️":"1f3c4-1f3fc-200d-2640-fe0f","🏄🏽‍♀️":"1f3c4-1f3fd-200d-2640-fe0f","🏄🏾‍♀️":"1f3c4-1f3fe-200d-2640-fe0f","🏄🏿‍♀️":"1f3c4-1f3ff-200d-2640-fe0f","🚣🏻‍♂️":"1f6a3-1f3fb-200d-2642-fe0f","🚣🏼‍♂️":"1f6a3-1f3fc-200d-2642-fe0f","🚣🏽‍♂️":"1f6a3-1f3fd-200d-2642-fe0f","🚣🏾‍♂️":"1f6a3-1f3fe-200d-2642-fe0f","🚣🏿‍♂️":"1f6a3-1f3ff-200d-2642-fe0f","🚣🏻‍♀️":"1f6a3-1f3fb-200d-2640-fe0f","🚣🏼‍♀️":"1f6a3-1f3fc-200d-2640-fe0f","🚣🏽‍♀️":"1f6a3-1f3fd-200d-2640-fe0f","🚣🏾‍♀️":"1f6a3-1f3fe-200d-2640-fe0f","🚣🏿‍♀️":"1f6a3-1f3ff-200d-2640-fe0f","🏊🏻‍♂️":"1f3ca-1f3fb-200d-2642-fe0f","🏊🏼‍♂️":"1f3ca-1f3fc-200d-2642-fe0f","🏊🏽‍♂️":"1f3ca-1f3fd-200d-2642-fe0f","🏊🏾‍♂️":"1f3ca-1f3fe-200d-2642-fe0f","🏊🏿‍♂️":"1f3ca-1f3ff-200d-2642-fe0f","🏊🏻‍♀️":"1f3ca-1f3fb-200d-2640-fe0f","🏊🏼‍♀️":"1f3ca-1f3fc-200d-2640-fe0f","🏊🏽‍♀️":"1f3ca-1f3fd-200d-2640-fe0f","🏊🏾‍♀️":"1f3ca-1f3fe-200d-2640-fe0f","🏊🏿‍♀️":"1f3ca-1f3ff-200d-2640-fe0f","⛹️‍♂️":"26f9-fe0f-200d-2642-fe0f","⛹🏻‍♂️":"26f9-1f3fb-200d-2642-fe0f","⛹🏼‍♂️":"26f9-1f3fc-200d-2642-fe0f","⛹🏽‍♂️":"26f9-1f3fd-200d-2642-fe0f","⛹🏾‍♂️":"26f9-1f3fe-200d-2642-fe0f","⛹🏿‍♂️":"26f9-1f3ff-200d-2642-fe0f","⛹️‍♀️":"26f9-fe0f-200d-2640-fe0f","⛹🏻‍♀️":"26f9-1f3fb-200d-2640-fe0f","⛹🏼‍♀️":"26f9-1f3fc-200d-2640-fe0f","⛹🏽‍♀️":"26f9-1f3fd-200d-2640-fe0f","⛹🏾‍♀️":"26f9-1f3fe-200d-2640-fe0f","⛹🏿‍♀️":"26f9-1f3ff-200d-2640-fe0f","🏋️‍♂️":"1f3cb-fe0f-200d-2642-fe0f","🏋🏻‍♂️":"1f3cb-1f3fb-200d-2642-fe0f","🏋🏼‍♂️":"1f3cb-1f3fc-200d-2642-fe0f","🏋🏽‍♂️":"1f3cb-1f3fd-200d-2642-fe0f","🏋🏾‍♂️":"1f3cb-1f3fe-200d-2642-fe0f","🏋🏿‍♂️":"1f3cb-1f3ff-200d-2642-fe0f","🏋️‍♀️":"1f3cb-fe0f-200d-2640-fe0f","🏋🏻‍♀️":"1f3cb-1f3fb-200d-2640-fe0f","🏋🏼‍♀️":"1f3cb-1f3fc-200d-2640-fe0f","🏋🏽‍♀️":"1f3cb-1f3fd-200d-2640-fe0f","🏋🏾‍♀️":"1f3cb-1f3fe-200d-2640-fe0f","🏋🏿‍♀️":"1f3cb-1f3ff-200d-2640-fe0f","🚴🏻‍♂️":"1f6b4-1f3fb-200d-2642-fe0f","🚴🏼‍♂️":"1f6b4-1f3fc-200d-2642-fe0f","🚴🏽‍♂️":"1f6b4-1f3fd-200d-2642-fe0f","🚴🏾‍♂️":"1f6b4-1f3fe-200d-2642-fe0f","🚴🏿‍♂️":"1f6b4-1f3ff-200d-2642-fe0f","🚴🏻‍♀️":"1f6b4-1f3fb-200d-2640-fe0f","🚴🏼‍♀️":"1f6b4-1f3fc-200d-2640-fe0f","🚴🏽‍♀️":"1f6b4-1f3fd-200d-2640-fe0f","🚴🏾‍♀️":"1f6b4-1f3fe-200d-2640-fe0f","🚴🏿‍♀️":"1f6b4-1f3ff-200d-2640-fe0f","🚵🏻‍♂️":"1f6b5-1f3fb-200d-2642-fe0f","🚵🏼‍♂️":"1f6b5-1f3fc-200d-2642-fe0f","🚵🏽‍♂️":"1f6b5-1f3fd-200d-2642-fe0f","🚵🏾‍♂️":"1f6b5-1f3fe-200d-2642-fe0f","🚵🏿‍♂️":"1f6b5-1f3ff-200d-2642-fe0f","🚵🏻‍♀️":"1f6b5-1f3fb-200d-2640-fe0f","🚵🏼‍♀️":"1f6b5-1f3fc-200d-2640-fe0f","🚵🏽‍♀️":"1f6b5-1f3fd-200d-2640-fe0f","🚵🏾‍♀️":"1f6b5-1f3fe-200d-2640-fe0f","🚵🏿‍♀️":"1f6b5-1f3ff-200d-2640-fe0f","🤸🏻‍♂️":"1f938-1f3fb-200d-2642-fe0f","🤸🏼‍♂️":"1f938-1f3fc-200d-2642-fe0f","🤸🏽‍♂️":"1f938-1f3fd-200d-2642-fe0f","🤸🏾‍♂️":"1f938-1f3fe-200d-2642-fe0f","🤸🏿‍♂️":"1f938-1f3ff-200d-2642-fe0f","🤸🏻‍♀️":"1f938-1f3fb-200d-2640-fe0f","🤸🏼‍♀️":"1f938-1f3fc-200d-2640-fe0f","🤸🏽‍♀️":"1f938-1f3fd-200d-2640-fe0f","🤸🏾‍♀️":"1f938-1f3fe-200d-2640-fe0f","🤸🏿‍♀️":"1f938-1f3ff-200d-2640-fe0f","🤽🏻‍♂️":"1f93d-1f3fb-200d-2642-fe0f","🤽🏼‍♂️":"1f93d-1f3fc-200d-2642-fe0f","🤽🏽‍♂️":"1f93d-1f3fd-200d-2642-fe0f","🤽🏾‍♂️":"1f93d-1f3fe-200d-2642-fe0f","🤽🏿‍♂️":"1f93d-1f3ff-200d-2642-fe0f","🤽🏻‍♀️":"1f93d-1f3fb-200d-2640-fe0f","🤽🏼‍♀️":"1f93d-1f3fc-200d-2640-fe0f","🤽🏽‍♀️":"1f93d-1f3fd-200d-2640-fe0f","🤽🏾‍♀️":"1f93d-1f3fe-200d-2640-fe0f","🤽🏿‍♀️":"1f93d-1f3ff-200d-2640-fe0f","🤾🏻‍♂️":"1f93e-1f3fb-200d-2642-fe0f","🤾🏼‍♂️":"1f93e-1f3fc-200d-2642-fe0f","🤾🏽‍♂️":"1f93e-1f3fd-200d-2642-fe0f","🤾🏾‍♂️":"1f93e-1f3fe-200d-2642-fe0f","🤾🏿‍♂️":"1f93e-1f3ff-200d-2642-fe0f","🤾🏻‍♀️":"1f93e-1f3fb-200d-2640-fe0f","🤾🏼‍♀️":"1f93e-1f3fc-200d-2640-fe0f","🤾🏽‍♀️":"1f93e-1f3fd-200d-2640-fe0f","🤾🏾‍♀️":"1f93e-1f3fe-200d-2640-fe0f","🤾🏿‍♀️":"1f93e-1f3ff-200d-2640-fe0f","🤹🏻‍♂️":"1f939-1f3fb-200d-2642-fe0f","🤹🏼‍♂️":"1f939-1f3fc-200d-2642-fe0f","🤹🏽‍♂️":"1f939-1f3fd-200d-2642-fe0f","🤹🏾‍♂️":"1f939-1f3fe-200d-2642-fe0f","🤹🏿‍♂️":"1f939-1f3ff-200d-2642-fe0f","🤹🏻‍♀️":"1f939-1f3fb-200d-2640-fe0f","🤹🏼‍♀️":"1f939-1f3fc-200d-2640-fe0f","🤹🏽‍♀️":"1f939-1f3fd-200d-2640-fe0f","🤹🏾‍♀️":"1f939-1f3fe-200d-2640-fe0f","🤹🏿‍♀️":"1f939-1f3ff-200d-2640-fe0f","🧘🏻‍♂️":"1f9d8-1f3fb-200d-2642-fe0f","🧘🏼‍♂️":"1f9d8-1f3fc-200d-2642-fe0f","🧘🏽‍♂️":"1f9d8-1f3fd-200d-2642-fe0f","🧘🏾‍♂️":"1f9d8-1f3fe-200d-2642-fe0f","🧘🏿‍♂️":"1f9d8-1f3ff-200d-2642-fe0f","🧘🏻‍♀️":"1f9d8-1f3fb-200d-2640-fe0f","🧘🏼‍♀️":"1f9d8-1f3fc-200d-2640-fe0f","🧘🏽‍♀️":"1f9d8-1f3fd-200d-2640-fe0f","🧘🏾‍♀️":"1f9d8-1f3fe-200d-2640-fe0f","🧘🏿‍♀️":"1f9d8-1f3ff-200d-2640-fe0f","🧑‍🤝‍🧑":"1f9d1-200d-1f91d-200d-1f9d1","👩‍❤‍👨":"1f469-200d-2764-fe0f-200d-1f468","👨‍❤‍👨":"1f468-200d-2764-fe0f-200d-1f468","👩‍❤‍👩":"1f469-200d-2764-fe0f-200d-1f469","👨‍👩‍👦":"1f468-200d-1f469-200d-1f466","👨‍👩‍👧":"1f468-200d-1f469-200d-1f467","👨‍👨‍👦":"1f468-200d-1f468-200d-1f466","👨‍👨‍👧":"1f468-200d-1f468-200d-1f467","👩‍👩‍👦":"1f469-200d-1f469-200d-1f466","👩‍👩‍👧":"1f469-200d-1f469-200d-1f467","👨‍👦‍👦":"1f468-200d-1f466-200d-1f466","👨‍👧‍👦":"1f468-200d-1f467-200d-1f466","👨‍👧‍👧":"1f468-200d-1f467-200d-1f467","👩‍👦‍👦":"1f469-200d-1f466-200d-1f466","👩‍👧‍👦":"1f469-200d-1f467-200d-1f466","👩‍👧‍👧":"1f469-200d-1f467-200d-1f467","🧑‍🧑‍🧒":"1f9d1-200d-1f9d1-200d-1f9d2","🧑‍🧒‍🧒":"1f9d1-200d-1f9d2-200d-1f9d2","🏳️‍⚧️":"1f3f3-fe0f-200d-26a7-fe0f","🚶‍♀‍➡️":"1f6b6-200d-2640-fe0f-200d-27a1-fe0f","🚶‍♀️‍➡":"1f6b6-200d-2640-fe0f-200d-27a1-fe0f","🚶🏻‍♀‍➡":"1f6b6-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🚶🏼‍♀‍➡":"1f6b6-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🚶🏽‍♀‍➡":"1f6b6-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🚶🏾‍♀‍➡":"1f6b6-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","🚶🏿‍♀‍➡":"1f6b6-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🚶‍♂‍➡️":"1f6b6-200d-2642-fe0f-200d-27a1-fe0f","🚶‍♂️‍➡":"1f6b6-200d-2642-fe0f-200d-27a1-fe0f","🚶🏻‍♂‍➡":"1f6b6-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🚶🏼‍♂‍➡":"1f6b6-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🚶🏽‍♂‍➡":"1f6b6-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🚶🏾‍♂‍➡":"1f6b6-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🚶🏿‍♂‍➡":"1f6b6-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","🧎‍♀‍➡️":"1f9ce-200d-2640-fe0f-200d-27a1-fe0f","🧎‍♀️‍➡":"1f9ce-200d-2640-fe0f-200d-27a1-fe0f","🧎🏻‍♀‍➡":"1f9ce-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🧎🏼‍♀‍➡":"1f9ce-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🧎🏽‍♀‍➡":"1f9ce-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🧎🏾‍♀‍➡":"1f9ce-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","🧎🏿‍♀‍➡":"1f9ce-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🧎‍♂‍➡️":"1f9ce-200d-2642-fe0f-200d-27a1-fe0f","🧎‍♂️‍➡":"1f9ce-200d-2642-fe0f-200d-27a1-fe0f","🧎🏻‍♂‍➡":"1f9ce-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🧎🏼‍♂‍➡":"1f9ce-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🧎🏽‍♂‍➡":"1f9ce-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🧎🏾‍♂‍➡":"1f9ce-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🧎🏿‍♂‍➡":"1f9ce-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","🧑‍🦯‍➡️":"1f9d1-200d-1f9af-200d-27a1-fe0f","🧑🏻‍🦯‍➡":"1f9d1-1f3fb-200d-1f9af-200d-27a1-fe0f","🧑🏼‍🦯‍➡":"1f9d1-1f3fc-200d-1f9af-200d-27a1-fe0f","🧑🏽‍🦯‍➡":"1f9d1-1f3fd-200d-1f9af-200d-27a1-fe0f","🧑🏾‍🦯‍➡":"1f9d1-1f3fe-200d-1f9af-200d-27a1-fe0f","🧑🏿‍🦯‍➡":"1f9d1-1f3ff-200d-1f9af-200d-27a1-fe0f","👨‍🦯‍➡️":"1f468-200d-1f9af-200d-27a1-fe0f","👨🏻‍🦯‍➡":"1f468-1f3fb-200d-1f9af-200d-27a1-fe0f","👨🏼‍🦯‍➡":"1f468-1f3fc-200d-1f9af-200d-27a1-fe0f","👨🏽‍🦯‍➡":"1f468-1f3fd-200d-1f9af-200d-27a1-fe0f","👨🏾‍🦯‍➡":"1f468-1f3fe-200d-1f9af-200d-27a1-fe0f","👨🏿‍🦯‍➡":"1f468-1f3ff-200d-1f9af-200d-27a1-fe0f","👩‍🦯‍➡️":"1f469-200d-1f9af-200d-27a1-fe0f","👩🏻‍🦯‍➡":"1f469-1f3fb-200d-1f9af-200d-27a1-fe0f","👩🏼‍🦯‍➡":"1f469-1f3fc-200d-1f9af-200d-27a1-fe0f","👩🏽‍🦯‍➡":"1f469-1f3fd-200d-1f9af-200d-27a1-fe0f","👩🏾‍🦯‍➡":"1f469-1f3fe-200d-1f9af-200d-27a1-fe0f","👩🏿‍🦯‍➡":"1f469-1f3ff-200d-1f9af-200d-27a1-fe0f","🧑‍🦼‍➡️":"1f9d1-200d-1f9bc-200d-27a1-fe0f","🧑🏻‍🦼‍➡":"1f9d1-1f3fb-200d-1f9bc-200d-27a1-fe0f","🧑🏼‍🦼‍➡":"1f9d1-1f3fc-200d-1f9bc-200d-27a1-fe0f","🧑🏽‍🦼‍➡":"1f9d1-1f3fd-200d-1f9bc-200d-27a1-fe0f","🧑🏾‍🦼‍➡":"1f9d1-1f3fe-200d-1f9bc-200d-27a1-fe0f","🧑🏿‍🦼‍➡":"1f9d1-1f3ff-200d-1f9bc-200d-27a1-fe0f","👨‍🦼‍➡️":"1f468-200d-1f9bc-200d-27a1-fe0f","👨🏻‍🦼‍➡":"1f468-1f3fb-200d-1f9bc-200d-27a1-fe0f","👨🏼‍🦼‍➡":"1f468-1f3fc-200d-1f9bc-200d-27a1-fe0f","👨🏽‍🦼‍➡":"1f468-1f3fd-200d-1f9bc-200d-27a1-fe0f","👨🏾‍🦼‍➡":"1f468-1f3fe-200d-1f9bc-200d-27a1-fe0f","👨🏿‍🦼‍➡":"1f468-1f3ff-200d-1f9bc-200d-27a1-fe0f","👩‍🦼‍➡️":"1f469-200d-1f9bc-200d-27a1-fe0f","👩🏻‍🦼‍➡":"1f469-1f3fb-200d-1f9bc-200d-27a1-fe0f","👩🏼‍🦼‍➡":"1f469-1f3fc-200d-1f9bc-200d-27a1-fe0f","👩🏽‍🦼‍➡":"1f469-1f3fd-200d-1f9bc-200d-27a1-fe0f","👩🏾‍🦼‍➡":"1f469-1f3fe-200d-1f9bc-200d-27a1-fe0f","👩🏿‍🦼‍➡":"1f469-1f3ff-200d-1f9bc-200d-27a1-fe0f","🧑‍🦽‍➡️":"1f9d1-200d-1f9bd-200d-27a1-fe0f","🧑🏻‍🦽‍➡":"1f9d1-1f3fb-200d-1f9bd-200d-27a1-fe0f","🧑🏼‍🦽‍➡":"1f9d1-1f3fc-200d-1f9bd-200d-27a1-fe0f","🧑🏽‍🦽‍➡":"1f9d1-1f3fd-200d-1f9bd-200d-27a1-fe0f","🧑🏾‍🦽‍➡":"1f9d1-1f3fe-200d-1f9bd-200d-27a1-fe0f","🧑🏿‍🦽‍➡":"1f9d1-1f3ff-200d-1f9bd-200d-27a1-fe0f","👨‍🦽‍➡️":"1f468-200d-1f9bd-200d-27a1-fe0f","👨🏻‍🦽‍➡":"1f468-1f3fb-200d-1f9bd-200d-27a1-fe0f","👨🏼‍🦽‍➡":"1f468-1f3fc-200d-1f9bd-200d-27a1-fe0f","👨🏽‍🦽‍➡":"1f468-1f3fd-200d-1f9bd-200d-27a1-fe0f","👨🏾‍🦽‍➡":"1f468-1f3fe-200d-1f9bd-200d-27a1-fe0f","👨🏿‍🦽‍➡":"1f468-1f3ff-200d-1f9bd-200d-27a1-fe0f","👩‍🦽‍➡️":"1f469-200d-1f9bd-200d-27a1-fe0f","👩🏻‍🦽‍➡":"1f469-1f3fb-200d-1f9bd-200d-27a1-fe0f","👩🏼‍🦽‍➡":"1f469-1f3fc-200d-1f9bd-200d-27a1-fe0f","👩🏽‍🦽‍➡":"1f469-1f3fd-200d-1f9bd-200d-27a1-fe0f","👩🏾‍🦽‍➡":"1f469-1f3fe-200d-1f9bd-200d-27a1-fe0f","👩🏿‍🦽‍➡":"1f469-1f3ff-200d-1f9bd-200d-27a1-fe0f","🏃‍♀‍➡️":"1f3c3-200d-2640-fe0f-200d-27a1-fe0f","🏃‍♀️‍➡":"1f3c3-200d-2640-fe0f-200d-27a1-fe0f","🏃🏻‍♀‍➡":"1f3c3-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🏃🏼‍♀‍➡":"1f3c3-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🏃🏽‍♀‍➡":"1f3c3-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🏃🏾‍♀‍➡":"1f3c3-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","🏃🏿‍♀‍➡":"1f3c3-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🏃‍♂‍➡️":"1f3c3-200d-2642-fe0f-200d-27a1-fe0f","🏃‍♂️‍➡":"1f3c3-200d-2642-fe0f-200d-27a1-fe0f","🏃🏻‍♂‍➡":"1f3c3-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🏃🏼‍♂‍➡":"1f3c3-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🏃🏽‍♂‍➡":"1f3c3-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🏃🏾‍♂‍➡":"1f3c3-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🏃🏿‍♂‍➡":"1f3c3-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","👩‍❤️‍👨":"1f469-200d-2764-fe0f-200d-1f468","👨‍❤️‍👨":"1f468-200d-2764-fe0f-200d-1f468","👩‍❤️‍👩":"1f469-200d-2764-fe0f-200d-1f469","🚶‍♀️‍➡️":"1f6b6-200d-2640-fe0f-200d-27a1-fe0f","🚶🏻‍♀‍➡️":"1f6b6-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🚶🏻‍♀️‍➡":"1f6b6-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🚶🏼‍♀‍➡️":"1f6b6-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🚶🏼‍♀️‍➡":"1f6b6-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🚶🏽‍♀‍➡️":"1f6b6-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🚶🏽‍♀️‍➡":"1f6b6-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🚶🏾‍♀‍➡️":"1f6b6-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","🚶🏾‍♀️‍➡":"1f6b6-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","🚶🏿‍♀‍➡️":"1f6b6-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🚶🏿‍♀️‍➡":"1f6b6-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🚶‍♂️‍➡️":"1f6b6-200d-2642-fe0f-200d-27a1-fe0f","🚶🏻‍♂‍➡️":"1f6b6-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🚶🏻‍♂️‍➡":"1f6b6-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🚶🏼‍♂‍➡️":"1f6b6-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🚶🏼‍♂️‍➡":"1f6b6-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🚶🏽‍♂‍➡️":"1f6b6-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🚶🏽‍♂️‍➡":"1f6b6-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🚶🏾‍♂‍➡️":"1f6b6-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🚶🏾‍♂️‍➡":"1f6b6-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🚶🏿‍♂‍➡️":"1f6b6-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","🚶🏿‍♂️‍➡":"1f6b6-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","🧎‍♀️‍➡️":"1f9ce-200d-2640-fe0f-200d-27a1-fe0f","🧎🏻‍♀‍➡️":"1f9ce-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🧎🏻‍♀️‍➡":"1f9ce-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🧎🏼‍♀‍➡️":"1f9ce-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🧎🏼‍♀️‍➡":"1f9ce-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🧎🏽‍♀‍➡️":"1f9ce-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🧎🏽‍♀️‍➡":"1f9ce-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🧎🏾‍♀‍➡️":"1f9ce-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","🧎🏾‍♀️‍➡":"1f9ce-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","🧎🏿‍♀‍➡️":"1f9ce-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🧎🏿‍♀️‍➡":"1f9ce-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🧎‍♂️‍➡️":"1f9ce-200d-2642-fe0f-200d-27a1-fe0f","🧎🏻‍♂‍➡️":"1f9ce-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🧎🏻‍♂️‍➡":"1f9ce-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🧎🏼‍♂‍➡️":"1f9ce-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🧎🏼‍♂️‍➡":"1f9ce-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🧎🏽‍♂‍➡️":"1f9ce-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🧎🏽‍♂️‍➡":"1f9ce-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🧎🏾‍♂‍➡️":"1f9ce-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🧎🏾‍♂️‍➡":"1f9ce-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🧎🏿‍♂‍➡️":"1f9ce-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","🧎🏿‍♂️‍➡":"1f9ce-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","🧑🏻‍🦯‍➡️":"1f9d1-1f3fb-200d-1f9af-200d-27a1-fe0f","🧑🏼‍🦯‍➡️":"1f9d1-1f3fc-200d-1f9af-200d-27a1-fe0f","🧑🏽‍🦯‍➡️":"1f9d1-1f3fd-200d-1f9af-200d-27a1-fe0f","🧑🏾‍🦯‍➡️":"1f9d1-1f3fe-200d-1f9af-200d-27a1-fe0f","🧑🏿‍🦯‍➡️":"1f9d1-1f3ff-200d-1f9af-200d-27a1-fe0f","👨🏻‍🦯‍➡️":"1f468-1f3fb-200d-1f9af-200d-27a1-fe0f","👨🏼‍🦯‍➡️":"1f468-1f3fc-200d-1f9af-200d-27a1-fe0f","👨🏽‍🦯‍➡️":"1f468-1f3fd-200d-1f9af-200d-27a1-fe0f","👨🏾‍🦯‍➡️":"1f468-1f3fe-200d-1f9af-200d-27a1-fe0f","👨🏿‍🦯‍➡️":"1f468-1f3ff-200d-1f9af-200d-27a1-fe0f","👩🏻‍🦯‍➡️":"1f469-1f3fb-200d-1f9af-200d-27a1-fe0f","👩🏼‍🦯‍➡️":"1f469-1f3fc-200d-1f9af-200d-27a1-fe0f","👩🏽‍🦯‍➡️":"1f469-1f3fd-200d-1f9af-200d-27a1-fe0f","👩🏾‍🦯‍➡️":"1f469-1f3fe-200d-1f9af-200d-27a1-fe0f","👩🏿‍🦯‍➡️":"1f469-1f3ff-200d-1f9af-200d-27a1-fe0f","🧑🏻‍🦼‍➡️":"1f9d1-1f3fb-200d-1f9bc-200d-27a1-fe0f","🧑🏼‍🦼‍➡️":"1f9d1-1f3fc-200d-1f9bc-200d-27a1-fe0f","🧑🏽‍🦼‍➡️":"1f9d1-1f3fd-200d-1f9bc-200d-27a1-fe0f","🧑🏾‍🦼‍➡️":"1f9d1-1f3fe-200d-1f9bc-200d-27a1-fe0f","🧑🏿‍🦼‍➡️":"1f9d1-1f3ff-200d-1f9bc-200d-27a1-fe0f","👨🏻‍🦼‍➡️":"1f468-1f3fb-200d-1f9bc-200d-27a1-fe0f","👨🏼‍🦼‍➡️":"1f468-1f3fc-200d-1f9bc-200d-27a1-fe0f","👨🏽‍🦼‍➡️":"1f468-1f3fd-200d-1f9bc-200d-27a1-fe0f","👨🏾‍🦼‍➡️":"1f468-1f3fe-200d-1f9bc-200d-27a1-fe0f","👨🏿‍🦼‍➡️":"1f468-1f3ff-200d-1f9bc-200d-27a1-fe0f","👩🏻‍🦼‍➡️":"1f469-1f3fb-200d-1f9bc-200d-27a1-fe0f","👩🏼‍🦼‍➡️":"1f469-1f3fc-200d-1f9bc-200d-27a1-fe0f","👩🏽‍🦼‍➡️":"1f469-1f3fd-200d-1f9bc-200d-27a1-fe0f","👩🏾‍🦼‍➡️":"1f469-1f3fe-200d-1f9bc-200d-27a1-fe0f","👩🏿‍🦼‍➡️":"1f469-1f3ff-200d-1f9bc-200d-27a1-fe0f","🧑🏻‍🦽‍➡️":"1f9d1-1f3fb-200d-1f9bd-200d-27a1-fe0f","🧑🏼‍🦽‍➡️":"1f9d1-1f3fc-200d-1f9bd-200d-27a1-fe0f","🧑🏽‍🦽‍➡️":"1f9d1-1f3fd-200d-1f9bd-200d-27a1-fe0f","🧑🏾‍🦽‍➡️":"1f9d1-1f3fe-200d-1f9bd-200d-27a1-fe0f","🧑🏿‍🦽‍➡️":"1f9d1-1f3ff-200d-1f9bd-200d-27a1-fe0f","👨🏻‍🦽‍➡️":"1f468-1f3fb-200d-1f9bd-200d-27a1-fe0f","👨🏼‍🦽‍➡️":"1f468-1f3fc-200d-1f9bd-200d-27a1-fe0f","👨🏽‍🦽‍➡️":"1f468-1f3fd-200d-1f9bd-200d-27a1-fe0f","👨🏾‍🦽‍➡️":"1f468-1f3fe-200d-1f9bd-200d-27a1-fe0f","👨🏿‍🦽‍➡️":"1f468-1f3ff-200d-1f9bd-200d-27a1-fe0f","👩🏻‍🦽‍➡️":"1f469-1f3fb-200d-1f9bd-200d-27a1-fe0f","👩🏼‍🦽‍➡️":"1f469-1f3fc-200d-1f9bd-200d-27a1-fe0f","👩🏽‍🦽‍➡️":"1f469-1f3fd-200d-1f9bd-200d-27a1-fe0f","👩🏾‍🦽‍➡️":"1f469-1f3fe-200d-1f9bd-200d-27a1-fe0f","👩🏿‍🦽‍➡️":"1f469-1f3ff-200d-1f9bd-200d-27a1-fe0f","🏃‍♀️‍➡️":"1f3c3-200d-2640-fe0f-200d-27a1-fe0f","🏃🏻‍♀‍➡️":"1f3c3-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🏃🏻‍♀️‍➡":"1f3c3-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🏃🏼‍♀‍➡️":"1f3c3-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🏃🏼‍♀️‍➡":"1f3c3-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🏃🏽‍♀‍➡️":"1f3c3-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🏃🏽‍♀️‍➡":"1f3c3-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🏃🏾‍♀‍➡️":"1f3c3-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","🏃🏾‍♀️‍➡":"1f3c3-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","🏃🏿‍♀‍➡️":"1f3c3-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🏃🏿‍♀️‍➡":"1f3c3-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🏃‍♂️‍➡️":"1f3c3-200d-2642-fe0f-200d-27a1-fe0f","🏃🏻‍♂‍➡️":"1f3c3-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🏃🏻‍♂️‍➡":"1f3c3-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🏃🏼‍♂‍➡️":"1f3c3-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🏃🏼‍♂️‍➡":"1f3c3-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🏃🏽‍♂‍➡️":"1f3c3-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🏃🏽‍♂️‍➡":"1f3c3-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🏃🏾‍♂‍➡️":"1f3c3-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🏃🏾‍♂️‍➡":"1f3c3-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🏃🏿‍♂‍➡️":"1f3c3-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","🏃🏿‍♂️‍➡":"1f3c3-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","🧑🏻‍🤝‍🧑🏻":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fb","🧑🏻‍🤝‍🧑🏼":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fc","🧑🏻‍🤝‍🧑🏽":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fd","🧑🏻‍🤝‍🧑🏾":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fe","🧑🏻‍🤝‍🧑🏿":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3ff","🧑🏼‍🤝‍🧑🏻":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fb","🧑🏼‍🤝‍🧑🏼":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fc","🧑🏼‍🤝‍🧑🏽":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fd","🧑🏼‍🤝‍🧑🏾":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fe","🧑🏼‍🤝‍🧑🏿":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3ff","🧑🏽‍🤝‍🧑🏻":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fb","🧑🏽‍🤝‍🧑🏼":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fc","🧑🏽‍🤝‍🧑🏽":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fd","🧑🏽‍🤝‍🧑🏾":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fe","🧑🏽‍🤝‍🧑🏿":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3ff","🧑🏾‍🤝‍🧑🏻":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fb","🧑🏾‍🤝‍🧑🏼":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fc","🧑🏾‍🤝‍🧑🏽":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fd","🧑🏾‍🤝‍🧑🏾":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fe","🧑🏾‍🤝‍🧑🏿":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3ff","🧑🏿‍🤝‍🧑🏻":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fb","🧑🏿‍🤝‍🧑🏼":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fc","🧑🏿‍🤝‍🧑🏽":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fd","🧑🏿‍🤝‍🧑🏾":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fe","🧑🏿‍🤝‍🧑🏿":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3ff","👩🏻‍🤝‍👩🏼":"1f469-1f3fb-200d-1f91d-200d-1f469-1f3fc","👩🏻‍🤝‍👩🏽":"1f469-1f3fb-200d-1f91d-200d-1f469-1f3fd","👩🏻‍🤝‍👩🏾":"1f469-1f3fb-200d-1f91d-200d-1f469-1f3fe","👩🏻‍🤝‍👩🏿":"1f469-1f3fb-200d-1f91d-200d-1f469-1f3ff","👩🏼‍🤝‍👩🏻":"1f469-1f3fc-200d-1f91d-200d-1f469-1f3fb","👩🏼‍🤝‍👩🏽":"1f469-1f3fc-200d-1f91d-200d-1f469-1f3fd","👩🏼‍🤝‍👩🏾":"1f469-1f3fc-200d-1f91d-200d-1f469-1f3fe","👩🏼‍🤝‍👩🏿":"1f469-1f3fc-200d-1f91d-200d-1f469-1f3ff","👩🏽‍🤝‍👩🏻":"1f469-1f3fd-200d-1f91d-200d-1f469-1f3fb","👩🏽‍🤝‍👩🏼":"1f469-1f3fd-200d-1f91d-200d-1f469-1f3fc","👩🏽‍🤝‍👩🏾":"1f469-1f3fd-200d-1f91d-200d-1f469-1f3fe","👩🏽‍🤝‍👩🏿":"1f469-1f3fd-200d-1f91d-200d-1f469-1f3ff","👩🏾‍🤝‍👩🏻":"1f469-1f3fe-200d-1f91d-200d-1f469-1f3fb","👩🏾‍🤝‍👩🏼":"1f469-1f3fe-200d-1f91d-200d-1f469-1f3fc","👩🏾‍🤝‍👩🏽":"1f469-1f3fe-200d-1f91d-200d-1f469-1f3fd","👩🏾‍🤝‍👩🏿":"1f469-1f3fe-200d-1f91d-200d-1f469-1f3ff","👩🏿‍🤝‍👩🏻":"1f469-1f3ff-200d-1f91d-200d-1f469-1f3fb","👩🏿‍🤝‍👩🏼":"1f469-1f3ff-200d-1f91d-200d-1f469-1f3fc","👩🏿‍🤝‍👩🏽":"1f469-1f3ff-200d-1f91d-200d-1f469-1f3fd","👩🏿‍🤝‍👩🏾":"1f469-1f3ff-200d-1f91d-200d-1f469-1f3fe","👩🏻‍🤝‍👨🏼":"1f469-1f3fb-200d-1f91d-200d-1f468-1f3fc","👩🏻‍🤝‍👨🏽":"1f469-1f3fb-200d-1f91d-200d-1f468-1f3fd","👩🏻‍🤝‍👨🏾":"1f469-1f3fb-200d-1f91d-200d-1f468-1f3fe","👩🏻‍🤝‍👨🏿":"1f469-1f3fb-200d-1f91d-200d-1f468-1f3ff","👩🏼‍🤝‍👨🏻":"1f469-1f3fc-200d-1f91d-200d-1f468-1f3fb","👩🏼‍🤝‍👨🏽":"1f469-1f3fc-200d-1f91d-200d-1f468-1f3fd","👩🏼‍🤝‍👨🏾":"1f469-1f3fc-200d-1f91d-200d-1f468-1f3fe","👩🏼‍🤝‍👨🏿":"1f469-1f3fc-200d-1f91d-200d-1f468-1f3ff","👩🏽‍🤝‍👨🏻":"1f469-1f3fd-200d-1f91d-200d-1f468-1f3fb","👩🏽‍🤝‍👨🏼":"1f469-1f3fd-200d-1f91d-200d-1f468-1f3fc","👩🏽‍🤝‍👨🏾":"1f469-1f3fd-200d-1f91d-200d-1f468-1f3fe","👩🏽‍🤝‍👨🏿":"1f469-1f3fd-200d-1f91d-200d-1f468-1f3ff","👩🏾‍🤝‍👨🏻":"1f469-1f3fe-200d-1f91d-200d-1f468-1f3fb","👩🏾‍🤝‍👨🏼":"1f469-1f3fe-200d-1f91d-200d-1f468-1f3fc","👩🏾‍🤝‍👨🏽":"1f469-1f3fe-200d-1f91d-200d-1f468-1f3fd","👩🏾‍🤝‍👨🏿":"1f469-1f3fe-200d-1f91d-200d-1f468-1f3ff","👩🏿‍🤝‍👨🏻":"1f469-1f3ff-200d-1f91d-200d-1f468-1f3fb","👩🏿‍🤝‍👨🏼":"1f469-1f3ff-200d-1f91d-200d-1f468-1f3fc","👩🏿‍🤝‍👨🏽":"1f469-1f3ff-200d-1f91d-200d-1f468-1f3fd","👩🏿‍🤝‍👨🏾":"1f469-1f3ff-200d-1f91d-200d-1f468-1f3fe","👨🏻‍🤝‍👨🏼":"1f468-1f3fb-200d-1f91d-200d-1f468-1f3fc","👨🏻‍🤝‍👨🏽":"1f468-1f3fb-200d-1f91d-200d-1f468-1f3fd","👨🏻‍🤝‍👨🏾":"1f468-1f3fb-200d-1f91d-200d-1f468-1f3fe","👨🏻‍🤝‍👨🏿":"1f468-1f3fb-200d-1f91d-200d-1f468-1f3ff","👨🏼‍🤝‍👨🏻":"1f468-1f3fc-200d-1f91d-200d-1f468-1f3fb","👨🏼‍🤝‍👨🏽":"1f468-1f3fc-200d-1f91d-200d-1f468-1f3fd","👨🏼‍🤝‍👨🏾":"1f468-1f3fc-200d-1f91d-200d-1f468-1f3fe","👨🏼‍🤝‍👨🏿":"1f468-1f3fc-200d-1f91d-200d-1f468-1f3ff","👨🏽‍🤝‍👨🏻":"1f468-1f3fd-200d-1f91d-200d-1f468-1f3fb","👨🏽‍🤝‍👨🏼":"1f468-1f3fd-200d-1f91d-200d-1f468-1f3fc","👨🏽‍🤝‍👨🏾":"1f468-1f3fd-200d-1f91d-200d-1f468-1f3fe","👨🏽‍🤝‍👨🏿":"1f468-1f3fd-200d-1f91d-200d-1f468-1f3ff","👨🏾‍🤝‍👨🏻":"1f468-1f3fe-200d-1f91d-200d-1f468-1f3fb","👨🏾‍🤝‍👨🏼":"1f468-1f3fe-200d-1f91d-200d-1f468-1f3fc","👨🏾‍🤝‍👨🏽":"1f468-1f3fe-200d-1f91d-200d-1f468-1f3fd","👨🏾‍🤝‍👨🏿":"1f468-1f3fe-200d-1f91d-200d-1f468-1f3ff","👨🏿‍🤝‍👨🏻":"1f468-1f3ff-200d-1f91d-200d-1f468-1f3fb","👨🏿‍🤝‍👨🏼":"1f468-1f3ff-200d-1f91d-200d-1f468-1f3fc","👨🏿‍🤝‍👨🏽":"1f468-1f3ff-200d-1f91d-200d-1f468-1f3fd","👨🏿‍🤝‍👨🏾":"1f468-1f3ff-200d-1f91d-200d-1f468-1f3fe","👩‍❤‍💋‍👨":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f468","👨‍❤‍💋‍👨":"1f468-200d-2764-fe0f-200d-1f48b-200d-1f468","👩‍❤‍💋‍👩":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f469","🧑🏻‍❤‍🧑🏼":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fc","🧑🏻‍❤‍🧑🏽":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fd","🧑🏻‍❤‍🧑🏾":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fe","🧑🏻‍❤‍🧑🏿":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3ff","🧑🏼‍❤‍🧑🏻":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fb","🧑🏼‍❤‍🧑🏽":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fd","🧑🏼‍❤‍🧑🏾":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fe","🧑🏼‍❤‍🧑🏿":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3ff","🧑🏽‍❤‍🧑🏻":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fb","🧑🏽‍❤‍🧑🏼":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fc","🧑🏽‍❤‍🧑🏾":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fe","🧑🏽‍❤‍🧑🏿":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3ff","🧑🏾‍❤‍🧑🏻":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fb","🧑🏾‍❤‍🧑🏼":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fc","🧑🏾‍❤‍🧑🏽":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fd","🧑🏾‍❤‍🧑🏿":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3ff","🧑🏿‍❤‍🧑🏻":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fb","🧑🏿‍❤‍🧑🏼":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fc","🧑🏿‍❤‍🧑🏽":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fd","🧑🏿‍❤‍🧑🏾":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fe","👩🏻‍❤‍👨🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb","👩🏻‍❤‍👨🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fc","👩🏻‍❤‍👨🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fd","👩🏻‍❤‍👨🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fe","👩🏻‍❤‍👨🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3ff","👩🏼‍❤‍👨🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fb","👩🏼‍❤‍👨🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc","👩🏼‍❤‍👨🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fd","👩🏼‍❤‍👨🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fe","👩🏼‍❤‍👨🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3ff","👩🏽‍❤‍👨🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fb","👩🏽‍❤‍👨🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fc","👩🏽‍❤‍👨🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd","👩🏽‍❤‍👨🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fe","👩🏽‍❤‍👨🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3ff","👩🏾‍❤‍👨🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fb","👩🏾‍❤‍👨🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fc","👩🏾‍❤‍👨🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fd","👩🏾‍❤‍👨🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe","👩🏾‍❤‍👨🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3ff","👩🏿‍❤‍👨🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fb","👩🏿‍❤‍👨🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fc","👩🏿‍❤‍👨🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fd","👩🏿‍❤‍👨🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fe","👩🏿‍❤‍👨🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff","👨🏻‍❤‍👨🏻":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb","👨🏻‍❤‍👨🏼":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fc","👨🏻‍❤‍👨🏽":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fd","👨🏻‍❤‍👨🏾":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fe","👨🏻‍❤‍👨🏿":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3ff","👨🏼‍❤‍👨🏻":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fb","👨🏼‍❤‍👨🏼":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc","👨🏼‍❤‍👨🏽":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fd","👨🏼‍❤‍👨🏾":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fe","👨🏼‍❤‍👨🏿":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3ff","👨🏽‍❤‍👨🏻":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fb","👨🏽‍❤‍👨🏼":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fc","👨🏽‍❤‍👨🏽":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd","👨🏽‍❤‍👨🏾":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fe","👨🏽‍❤‍👨🏿":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3ff","👨🏾‍❤‍👨🏻":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fb","👨🏾‍❤‍👨🏼":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fc","👨🏾‍❤‍👨🏽":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fd","👨🏾‍❤‍👨🏾":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe","👨🏾‍❤‍👨🏿":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3ff","👨🏿‍❤‍👨🏻":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fb","👨🏿‍❤‍👨🏼":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fc","👨🏿‍❤‍👨🏽":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fd","👨🏿‍❤‍👨🏾":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fe","👨🏿‍❤‍👨🏿":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff","👩🏻‍❤‍👩🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fb","👩🏻‍❤‍👩🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fc","👩🏻‍❤‍👩🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fd","👩🏻‍❤‍👩🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fe","👩🏻‍❤‍👩🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3ff","👩🏼‍❤‍👩🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fb","👩🏼‍❤‍👩🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fc","👩🏼‍❤‍👩🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fd","👩🏼‍❤‍👩🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fe","👩🏼‍❤‍👩🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3ff","👩🏽‍❤‍👩🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fb","👩🏽‍❤‍👩🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fc","👩🏽‍❤‍👩🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fd","👩🏽‍❤‍👩🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fe","👩🏽‍❤‍👩🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3ff","👩🏾‍❤‍👩🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fb","👩🏾‍❤‍👩🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fc","👩🏾‍❤‍👩🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fd","👩🏾‍❤‍👩🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fe","👩🏾‍❤‍👩🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3ff","👩🏿‍❤‍👩🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fb","👩🏿‍❤‍👩🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fc","👩🏿‍❤‍👩🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fd","👩🏿‍❤‍👩🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fe","👩🏿‍❤‍👩🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3ff","👨‍👩‍👧‍👦":"1f468-200d-1f469-200d-1f467-200d-1f466","👨‍👩‍👦‍👦":"1f468-200d-1f469-200d-1f466-200d-1f466","👨‍👩‍👧‍👧":"1f468-200d-1f469-200d-1f467-200d-1f467","👨‍👨‍👧‍👦":"1f468-200d-1f468-200d-1f467-200d-1f466","👨‍👨‍👦‍👦":"1f468-200d-1f468-200d-1f466-200d-1f466","👨‍👨‍👧‍👧":"1f468-200d-1f468-200d-1f467-200d-1f467","👩‍👩‍👧‍👦":"1f469-200d-1f469-200d-1f467-200d-1f466","👩‍👩‍👦‍👦":"1f469-200d-1f469-200d-1f466-200d-1f466","👩‍👩‍👧‍👧":"1f469-200d-1f469-200d-1f467-200d-1f467","🧑‍🧑‍🧒‍🧒":"1f9d1-200d-1f9d1-200d-1f9d2-200d-1f9d2","🏴󠁧󠁢󠁥󠁮󠁧󠁿":"1f3f4-e0067-e0062-e0065-e006e-e0067-e007f","🏴󠁧󠁢󠁳󠁣󠁴󠁿":"1f3f4-e0067-e0062-e0073-e0063-e0074-e007f","🏴󠁧󠁢󠁷󠁬󠁳󠁿":"1f3f4-e0067-e0062-e0077-e006c-e0073-e007f","🚶🏻‍♀️‍➡️":"1f6b6-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🚶🏼‍♀️‍➡️":"1f6b6-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🚶🏽‍♀️‍➡️":"1f6b6-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🚶🏾‍♀️‍➡️":"1f6b6-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","🚶🏿‍♀️‍➡️":"1f6b6-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🚶🏻‍♂️‍➡️":"1f6b6-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🚶🏼‍♂️‍➡️":"1f6b6-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🚶🏽‍♂️‍➡️":"1f6b6-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🚶🏾‍♂️‍➡️":"1f6b6-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🚶🏿‍♂️‍➡️":"1f6b6-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","🧎🏻‍♀️‍➡️":"1f9ce-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🧎🏼‍♀️‍➡️":"1f9ce-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🧎🏽‍♀️‍➡️":"1f9ce-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🧎🏾‍♀️‍➡️":"1f9ce-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","🧎🏿‍♀️‍➡️":"1f9ce-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🧎🏻‍♂️‍➡️":"1f9ce-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🧎🏼‍♂️‍➡️":"1f9ce-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🧎🏽‍♂️‍➡️":"1f9ce-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🧎🏾‍♂️‍➡️":"1f9ce-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🧎🏿‍♂️‍➡️":"1f9ce-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","🏃🏻‍♀️‍➡️":"1f3c3-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🏃🏼‍♀️‍➡️":"1f3c3-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🏃🏽‍♀️‍➡️":"1f3c3-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🏃🏾‍♀️‍➡️":"1f3c3-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","🏃🏿‍♀️‍➡️":"1f3c3-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🏃🏻‍♂️‍➡️":"1f3c3-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🏃🏼‍♂️‍➡️":"1f3c3-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🏃🏽‍♂️‍➡️":"1f3c3-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🏃🏾‍♂️‍➡️":"1f3c3-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🏃🏿‍♂️‍➡️":"1f3c3-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","👩‍❤️‍💋‍👨":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f468","👨‍❤️‍💋‍👨":"1f468-200d-2764-fe0f-200d-1f48b-200d-1f468","👩‍❤️‍💋‍👩":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f469","🧑🏻‍❤️‍🧑🏼":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fc","🧑🏻‍❤️‍🧑🏽":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fd","🧑🏻‍❤️‍🧑🏾":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fe","🧑🏻‍❤️‍🧑🏿":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3ff","🧑🏼‍❤️‍🧑🏻":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fb","🧑🏼‍❤️‍🧑🏽":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fd","🧑🏼‍❤️‍🧑🏾":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fe","🧑🏼‍❤️‍🧑🏿":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3ff","🧑🏽‍❤️‍🧑🏻":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fb","🧑🏽‍❤️‍🧑🏼":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fc","🧑🏽‍❤️‍🧑🏾":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fe","🧑🏽‍❤️‍🧑🏿":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3ff","🧑🏾‍❤️‍🧑🏻":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fb","🧑🏾‍❤️‍🧑🏼":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fc","🧑🏾‍❤️‍🧑🏽":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fd","🧑🏾‍❤️‍🧑🏿":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3ff","🧑🏿‍❤️‍🧑🏻":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fb","🧑🏿‍❤️‍🧑🏼":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fc","🧑🏿‍❤️‍🧑🏽":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fd","🧑🏿‍❤️‍🧑🏾":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fe","👩🏻‍❤️‍👨🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb","👩🏻‍❤️‍👨🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fc","👩🏻‍❤️‍👨🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fd","👩🏻‍❤️‍👨🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fe","👩🏻‍❤️‍👨🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3ff","👩🏼‍❤️‍👨🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fb","👩🏼‍❤️‍👨🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc","👩🏼‍❤️‍👨🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fd","👩🏼‍❤️‍👨🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fe","👩🏼‍❤️‍👨🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3ff","👩🏽‍❤️‍👨🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fb","👩🏽‍❤️‍👨🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fc","👩🏽‍❤️‍👨🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd","👩🏽‍❤️‍👨🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fe","👩🏽‍❤️‍👨🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3ff","👩🏾‍❤️‍👨🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fb","👩🏾‍❤️‍👨🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fc","👩🏾‍❤️‍👨🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fd","👩🏾‍❤️‍👨🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe","👩🏾‍❤️‍👨🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3ff","👩🏿‍❤️‍👨🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fb","👩🏿‍❤️‍👨🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fc","👩🏿‍❤️‍👨🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fd","👩🏿‍❤️‍👨🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fe","👩🏿‍❤️‍👨🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff","👨🏻‍❤️‍👨🏻":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb","👨🏻‍❤️‍👨🏼":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fc","👨🏻‍❤️‍👨🏽":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fd","👨🏻‍❤️‍👨🏾":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fe","👨🏻‍❤️‍👨🏿":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3ff","👨🏼‍❤️‍👨🏻":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fb","👨🏼‍❤️‍👨🏼":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc","👨🏼‍❤️‍👨🏽":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fd","👨🏼‍❤️‍👨🏾":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fe","👨🏼‍❤️‍👨🏿":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3ff","👨🏽‍❤️‍👨🏻":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fb","👨🏽‍❤️‍👨🏼":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fc","👨🏽‍❤️‍👨🏽":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd","👨🏽‍❤️‍👨🏾":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fe","👨🏽‍❤️‍👨🏿":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3ff","👨🏾‍❤️‍👨🏻":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fb","👨🏾‍❤️‍👨🏼":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fc","👨🏾‍❤️‍👨🏽":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fd","👨🏾‍❤️‍👨🏾":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe","👨🏾‍❤️‍👨🏿":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3ff","👨🏿‍❤️‍👨🏻":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fb","👨🏿‍❤️‍👨🏼":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fc","👨🏿‍❤️‍👨🏽":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fd","👨🏿‍❤️‍👨🏾":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fe","👨🏿‍❤️‍👨🏿":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff","👩🏻‍❤️‍👩🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fb","👩🏻‍❤️‍👩🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fc","👩🏻‍❤️‍👩🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fd","👩🏻‍❤️‍👩🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fe","👩🏻‍❤️‍👩🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3ff","👩🏼‍❤️‍👩🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fb","👩🏼‍❤️‍👩🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fc","👩🏼‍❤️‍👩🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fd","👩🏼‍❤️‍👩🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fe","👩🏼‍❤️‍👩🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3ff","👩🏽‍❤️‍👩🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fb","👩🏽‍❤️‍👩🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fc","👩🏽‍❤️‍👩🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fd","👩🏽‍❤️‍👩🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fe","👩🏽‍❤️‍👩🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3ff","👩🏾‍❤️‍👩🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fb","👩🏾‍❤️‍👩🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fc","👩🏾‍❤️‍👩🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fd","👩🏾‍❤️‍👩🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fe","👩🏾‍❤️‍👩🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3ff","👩🏿‍❤️‍👩🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fb","👩🏿‍❤️‍👩🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fc","👩🏿‍❤️‍👩🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fd","👩🏿‍❤️‍👩🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fe","👩🏿‍❤️‍👩🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3ff","🧑🏻‍❤‍💋‍🧑🏼":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","🧑🏻‍❤‍💋‍🧑🏽":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","🧑🏻‍❤‍💋‍🧑🏾":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","🧑🏻‍❤‍💋‍🧑🏿":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","🧑🏼‍❤‍💋‍🧑🏻":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","🧑🏼‍❤‍💋‍🧑🏽":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","🧑🏼‍❤‍💋‍🧑🏾":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","🧑🏼‍❤‍💋‍🧑🏿":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","🧑🏽‍❤‍💋‍🧑🏻":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","🧑🏽‍❤‍💋‍🧑🏼":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","🧑🏽‍❤‍💋‍🧑🏾":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","🧑🏽‍❤‍💋‍🧑🏿":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","🧑🏾‍❤‍💋‍🧑🏻":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","🧑🏾‍❤‍💋‍🧑🏼":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","🧑🏾‍❤‍💋‍🧑🏽":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","🧑🏾‍❤‍💋‍🧑🏿":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","🧑🏿‍❤‍💋‍🧑🏻":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","🧑🏿‍❤‍💋‍🧑🏼":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","🧑🏿‍❤‍💋‍🧑🏽":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","🧑🏿‍❤‍💋‍🧑🏾":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","👩🏻‍❤‍💋‍👨🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏻‍❤‍💋‍👨🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏻‍❤‍💋‍👨🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏻‍❤‍💋‍👨🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏻‍❤‍💋‍👨🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏼‍❤‍💋‍👨🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏼‍❤‍💋‍👨🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏼‍❤‍💋‍👨🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏼‍❤‍💋‍👨🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏼‍❤‍💋‍👨🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏽‍❤‍💋‍👨🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏽‍❤‍💋‍👨🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏽‍❤‍💋‍👨🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏽‍❤‍💋‍👨🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏽‍❤‍💋‍👨🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏾‍❤‍💋‍👨🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏾‍❤‍💋‍👨🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏾‍❤‍💋‍👨🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏾‍❤‍💋‍👨🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏾‍❤‍💋‍👨🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏿‍❤‍💋‍👨🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏿‍❤‍💋‍👨🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏿‍❤‍💋‍👨🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏿‍❤‍💋‍👨🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏿‍❤‍💋‍👨🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏻‍❤‍💋‍👨🏻":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏻‍❤‍💋‍👨🏼":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏻‍❤‍💋‍👨🏽":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏻‍❤‍💋‍👨🏾":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏻‍❤‍💋‍👨🏿":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏼‍❤‍💋‍👨🏻":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏼‍❤‍💋‍👨🏼":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏼‍❤‍💋‍👨🏽":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏼‍❤‍💋‍👨🏾":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏼‍❤‍💋‍👨🏿":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏽‍❤‍💋‍👨🏻":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏽‍❤‍💋‍👨🏼":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏽‍❤‍💋‍👨🏽":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏽‍❤‍💋‍👨🏾":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏽‍❤‍💋‍👨🏿":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏾‍❤‍💋‍👨🏻":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏾‍❤‍💋‍👨🏼":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏾‍❤‍💋‍👨🏽":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏾‍❤‍💋‍👨🏾":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏾‍❤‍💋‍👨🏿":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏿‍❤‍💋‍👨🏻":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏿‍❤‍💋‍👨🏼":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏿‍❤‍💋‍👨🏽":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏿‍❤‍💋‍👨🏾":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏿‍❤‍💋‍👨🏿":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏻‍❤‍💋‍👩🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏻‍❤‍💋‍👩🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏻‍❤‍💋‍👩🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏻‍❤‍💋‍👩🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏻‍❤‍💋‍👩🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏼‍❤‍💋‍👩🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏼‍❤‍💋‍👩🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏼‍❤‍💋‍👩🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏼‍❤‍💋‍👩🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏼‍❤‍💋‍👩🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏽‍❤‍💋‍👩🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏽‍❤‍💋‍👩🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏽‍❤‍💋‍👩🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏽‍❤‍💋‍👩🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏽‍❤‍💋‍👩🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏾‍❤‍💋‍👩🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏾‍❤‍💋‍👩🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏾‍❤‍💋‍👩🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏾‍❤‍💋‍👩🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏾‍❤‍💋‍👩🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏿‍❤‍💋‍👩🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏿‍❤‍💋‍👩🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏿‍❤‍💋‍👩🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏿‍❤‍💋‍👩🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏿‍❤‍💋‍👩🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","🧑🏻‍❤️‍💋‍🧑🏼":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","🧑🏻‍❤️‍💋‍🧑🏽":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","🧑🏻‍❤️‍💋‍🧑🏾":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","🧑🏻‍❤️‍💋‍🧑🏿":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","🧑🏼‍❤️‍💋‍🧑🏻":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","🧑🏼‍❤️‍💋‍🧑🏽":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","🧑🏼‍❤️‍💋‍🧑🏾":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","🧑🏼‍❤️‍💋‍🧑🏿":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","🧑🏽‍❤️‍💋‍🧑🏻":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","🧑🏽‍❤️‍💋‍🧑🏼":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","🧑🏽‍❤️‍💋‍🧑🏾":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","🧑🏽‍❤️‍💋‍🧑🏿":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","🧑🏾‍❤️‍💋‍🧑🏻":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","🧑🏾‍❤️‍💋‍🧑🏼":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","🧑🏾‍❤️‍💋‍🧑🏽":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","🧑🏾‍❤️‍💋‍🧑🏿":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","🧑🏿‍❤️‍💋‍🧑🏻":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","🧑🏿‍❤️‍💋‍🧑🏼":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","🧑🏿‍❤️‍💋‍🧑🏽":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","🧑🏿‍❤️‍💋‍🧑🏾":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","👩🏻‍❤️‍💋‍👨🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏻‍❤️‍💋‍👨🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏻‍❤️‍💋‍👨🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏻‍❤️‍💋‍👨🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏻‍❤️‍💋‍👨🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏼‍❤️‍💋‍👨🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏼‍❤️‍💋‍👨🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏼‍❤️‍💋‍👨🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏼‍❤️‍💋‍👨🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏼‍❤️‍💋‍👨🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏽‍❤️‍💋‍👨🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏽‍❤️‍💋‍👨🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏽‍❤️‍💋‍👨🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏽‍❤️‍💋‍👨🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏽‍❤️‍💋‍👨🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏾‍❤️‍💋‍👨🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏾‍❤️‍💋‍👨🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏾‍❤️‍💋‍👨🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏾‍❤️‍💋‍👨🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏾‍❤️‍💋‍👨🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏿‍❤️‍💋‍👨🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏿‍❤️‍💋‍👨🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏿‍❤️‍💋‍👨🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏿‍❤️‍💋‍👨🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏿‍❤️‍💋‍👨🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏻‍❤️‍💋‍👨🏻":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏻‍❤️‍💋‍👨🏼":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏻‍❤️‍💋‍👨🏽":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏻‍❤️‍💋‍👨🏾":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏻‍❤️‍💋‍👨🏿":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏼‍❤️‍💋‍👨🏻":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏼‍❤️‍💋‍👨🏼":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏼‍❤️‍💋‍👨🏽":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏼‍❤️‍💋‍👨🏾":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏼‍❤️‍💋‍👨🏿":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏽‍❤️‍💋‍👨🏻":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏽‍❤️‍💋‍👨🏼":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏽‍❤️‍💋‍👨🏽":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏽‍❤️‍💋‍👨🏾":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏽‍❤️‍💋‍👨🏿":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏾‍❤️‍💋‍👨🏻":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏾‍❤️‍💋‍👨🏼":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏾‍❤️‍💋‍👨🏽":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏾‍❤️‍💋‍👨🏾":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏾‍❤️‍💋‍👨🏿":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏿‍❤️‍💋‍👨🏻":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏿‍❤️‍💋‍👨🏼":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏿‍❤️‍💋‍👨🏽":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏿‍❤️‍💋‍👨🏾":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏿‍❤️‍💋‍👨🏿":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏻‍❤️‍💋‍👩🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏻‍❤️‍💋‍👩🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏻‍❤️‍💋‍👩🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏻‍❤️‍💋‍👩🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏻‍❤️‍💋‍👩🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏼‍❤️‍💋‍👩🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏼‍❤️‍💋‍👩🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏼‍❤️‍💋‍👩🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏼‍❤️‍💋‍👩🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏼‍❤️‍💋‍👩🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏽‍❤️‍💋‍👩🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏽‍❤️‍💋‍👩🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏽‍❤️‍💋‍👩🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏽‍❤️‍💋‍👩🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏽‍❤️‍💋‍👩🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏾‍❤️‍💋‍👩🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏾‍❤️‍💋‍👩🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏾‍❤️‍💋‍👩🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏾‍❤️‍💋‍👩🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏾‍❤️‍💋‍👩🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏿‍❤️‍💋‍👩🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏿‍❤️‍💋‍👩🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏿‍❤️‍💋‍👩🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏿‍❤️‍💋‍👩🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏿‍❤️‍💋‍👩🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff"} \ No newline at end of file +{"🩵":"1fa75","🦌":"1f98c","🦓":"1f993","🦄":"1f984","🐎":"1f40e","🫏":"1facf","🫎":"1face","🐴":"1f434","🐆":"1f406","🐅":"1f405","🐯":"1f42f","🦁":"1f981","🐈":"1f408","💜":"1f49c","🐱":"1f431","🦝":"1f99d","🦊":"1f98a","🐺":"1f43a","🐩":"1f429","🦮":"1f9ae","🐕":"1f415","🐶":"1f436","🦧":"1f9a7","🦍":"1f98d","🏄":"1f3c4","🆗":"1f197","🅾":"1f17e","🤷":"1f937","🫥":"1fae5","🆖":"1f196","🆕":"1f195","Ⓜ":"24c2","🆔":"1f194","ℹ":"2139","🆓":"1f193","🆒":"1f192","🆑":"1f191","🅱":"1f171","🆎":"1f18e","🅰":"1f170","🔤":"1f524","🔣":"1f523","🔢":"1f522","🔡":"1f521","🔠":"1f520","🔟":"1f51f","😃":"1f603","😏":"1f60f","™":"2122","®":"ae","©":"a9","❇":"2747","✴":"2734","✳":"2733","😒":"1f612","〽":"303d","➿":"27bf","➰":"27b0","❎":"274e","❌":"274c","✔":"2714","☑":"2611","🙄":"1f644","✅":"2705","⭕":"2b55","🔰":"1f530","📛":"1f4db","😬":"1f62c","🔱":"1f531","⚜":"269c","♻":"267b","😄":"1f604","⚕":"2695","💲":"1f4b2","💱":"1f4b1","〰":"3030","🤥":"1f925","❗":"2757","❕":"2755","❔":"2754","❓":"2753","🫨":"1fae8","⁉":"2049","‼":"203c","♾":"267e","🟰":"1f7f0","➗":"2797","➖":"2796","➕":"2795","✖":"2716","😁":"1f601","⚧":"26a7","♂":"2642","♀":"2640","📴":"1f4f4","📳":"1f4f3","🛜":"1f6dc","📶":"1f4f6","🔆":"1f506","🔅":"1f505","🎦":"1f3a6","⏏":"23cf","⏺":"23fa","⏹":"23f9","⏸":"23f8","😆":"1f606","⏬":"23ec","🔽":"1f53d","⏫":"23eb","🔼":"1f53c","⏮":"23ee","⏪":"23ea","◀":"25c0","⏯":"23ef","😌":"1f60c","⏭":"23ed","⏩":"23e9","▶":"25b6","😔":"1f614","🔂":"1f502","🔁":"1f501","🔀":"1f500","⛎":"26ce","😪":"1f62a","♓":"2653","♒":"2652","♑":"2651","♐":"2650","♏":"264f","🤤":"1f924","♎":"264e","♍":"264d","♌":"264c","♋":"264b","♊":"264a","😴":"1f634","♉":"2649","♈":"2648","🪯":"1faaf","🔯":"1f52f","🕎":"1f54e","😷":"1f637","☮":"262e","☪":"262a","☦":"2626","🤒":"1f912","✝":"271d","☯":"262f","🤕":"1f915","☸":"2638","✡":"2721","🕉":"1f549","🤢":"1f922","⚛":"269b","🛐":"1f6d0","🔝":"1f51d","🤮":"1f92e","🔜":"1f51c","🔛":"1f51b","🔚":"1f51a","🔙":"1f519","🔄":"1f504","🤧":"1f927","🔃":"1f503","⤵":"2935","⤴":"2934","🥵":"1f975","↪":"21aa","↩":"21a9","↔":"2194","🥶":"1f976","↕":"2195","↖":"2196","🥴":"1f974","⬅":"2b05","↙":"2199","⬇":"2b07","😵":"1f635","↘":"2198","➡":"27a1","😅":"1f605","↗":"2197","⬆":"2b06","☣":"2623","🤯":"1f92f","☢":"2622","🔞":"1f51e","📵":"1f4f5","🤠":"1f920","🚷":"1f6b7","🚱":"1f6b1","🚯":"1f6af","🚭":"1f6ad","🚳":"1f6b3","🥳":"1f973","🚫":"1f6ab","⛔":"26d4","🚸":"1f6b8","⚠":"26a0","🥸":"1f978","🛅":"1f6c5","🛄":"1f6c4","🛃":"1f6c3","🛂":"1f6c2","🚾":"1f6be","😎":"1f60e","🚼":"1f6bc","🚻":"1f6bb","🚺":"1f6ba","🚹":"1f6b9","♿":"267f","🤓":"1f913","🚰":"1f6b0","🚮":"1f6ae","🏧":"1f3e7","🪪":"1faaa","🪧":"1faa7","🧐":"1f9d0","🗿":"1f5ff","🪬":"1faac","🧿":"1f9ff","⚱":"26b1","😕":"1f615","🪦":"1faa6","⚰":"26b0","🚬":"1f6ac","🛒":"1f6d2","🫤":"1fae4","🧯":"1f9ef","🧽":"1f9fd","🪥":"1faa5","🫧":"1fae7","🧼":"1f9fc","😟":"1f61f","🪣":"1faa3","🧻":"1f9fb","🧺":"1f9fa","🧹":"1f9f9","🧷":"1f9f7","🙁":"1f641","🧴":"1f9f4","🪒":"1fa92","🪤":"1faa4","🛁":"1f6c1","🚿":"1f6bf","🪠":"1faa0","🚽":"1f6bd","🪑":"1fa91","🛋":"1f6cb","🛏":"1f6cf","🪟":"1fa9f","🪞":"1fa9e","🛗":"1f6d7","🚪":"1f6aa","🩻":"1fa7b","☹":"2639","🩺":"1fa7a","🩼":"1fa7c","🩹":"1fa79","💊":"1f48a","🩸":"1fa78","💉":"1f489","📡":"1f4e1","🔭":"1f52d","🔬":"1f52c","🧬":"1f9ec","🧫":"1f9eb","😮":"1f62e","🧪":"1f9ea","⚗":"2697","🪜":"1fa9c","🧲":"1f9f2","🧰":"1f9f0","🪝":"1fa9d","⛓":"26d3","🔗":"1f517","😯":"1f62f","🦯":"1f9af","⚖":"2696","🗜":"1f5dc","😲":"1f632","⚙":"2699","🔩":"1f529","🪛":"1fa9b","🔧":"1f527","😳":"1f633","🪚":"1fa9a","🛡":"1f6e1","🏹":"1f3f9","🪃":"1fa83","🥺":"1f97a","💣":"1f4a3","⚔":"2694","🗡":"1f5e1","🥹":"1f979","🛠":"1f6e0","⚒":"2692","⛏":"26cf","😦":"1f626","🪓":"1fa93","🔨":"1f528","🗝":"1f5dd","👮":"1f46e","🔑":"1f511","😧":"1f627","🔐":"1f510","🔏":"1f50f","🔓":"1f513","🔒":"1f512","🗑":"1f5d1","🗄":"1f5c4","🗃":"1f5c3","✂":"2702","😨":"1f628","📐":"1f4d0","📏":"1f4cf","🖇":"1f587","📎":"1f4ce","📍":"1f4cd","📌":"1f4cc","📋":"1f4cb","📊":"1f4ca","🕵":"1f575","📉":"1f4c9","📈":"1f4c8","📇":"1f4c7","😰":"1f630","🗓":"1f5d3","🗒":"1f5d2","📆":"1f4c6","📅":"1f4c5","🗂":"1f5c2","📂":"1f4c2","📁":"1f4c1","💼":"1f4bc","📝":"1f4dd","🖍":"1f58d","😥":"1f625","🖌":"1f58c","🖊":"1f58a","🖋":"1f58b","✒":"2712","✏":"270f","💂":"1f482","😢":"1f622","🗳":"1f5f3","📮":"1f4ee","📭":"1f4ed","📬":"1f4ec","📪":"1f4ea","📫":"1f4eb","📦":"1f4e6","📥":"1f4e5","📤":"1f4e4","📩":"1f4e9","😭":"1f62d","📨":"1f4e8","📧":"1f4e7","✉":"2709","💹":"1f4b9","🧾":"1f9fe","💳":"1f4b3","💸":"1f4b8","💷":"1f4b7","💶":"1f4b6","🥷":"1f977","👷":"1f477","💵":"1f4b5","😱":"1f631","💴":"1f4b4","🪙":"1fa99","💰":"1f4b0","🏷":"1f3f7","🔖":"1f516","📑":"1f4d1","🗞":"1f5de","📰":"1f4f0","📄":"1f4c4","😖":"1f616","📜":"1f4dc","📃":"1f4c3","📒":"1f4d2","📓":"1f4d3","📚":"1f4da","📙":"1f4d9","📘":"1f4d8","📗":"1f4d7","📖":"1f4d6","📕":"1f4d5","🫅":"1fac5","🤴":"1f934","👸":"1f478","👳":"1f473","📔":"1f4d4","😣":"1f623","🪔":"1fa94","🏮":"1f3ee","🔦":"1f526","💡":"1f4a1","🕯":"1f56f","🔎":"1f50e","🔍":"1f50d","📼":"1f4fc","📹":"1f4f9","📸":"1f4f8","😞":"1f61e","📷":"1f4f7","📺":"1f4fa","🎬":"1f3ac","📽":"1f4fd","🎞":"1f39e","🎥":"1f3a5","🧮":"1f9ee","📀":"1f4c0","👲":"1f472","🧕":"1f9d5","🤵":"1f935","💿":"1f4bf","😓":"1f613","💾":"1f4be","💽":"1f4bd","🖲":"1f5b2","🖱":"1f5b1","⌨":"2328","🖨":"1f5a8","🖥":"1f5a5","😩":"1f629","💻":"1f4bb","🔌":"1f50c","🪫":"1faab","🔋":"1f50b","📠":"1f4e0","📟":"1f4df","📞":"1f4de","☎":"260e","👰":"1f470","📲":"1f4f2","😫":"1f62b","📱":"1f4f1","🪈":"1fa88","🪇":"1fa87","🪘":"1fa98","🥁":"1f941","🪕":"1fa95","🎻":"1f3bb","🎺":"1f3ba","🎹":"1f3b9","🎸":"1f3b8","🪗":"1fa97","🥱":"1f971","🎷":"1f3b7","📻":"1f4fb","🎧":"1f3a7","🎤":"1f3a4","🎛":"1f39b","🎚":"1f39a","🎙":"1f399","🤰":"1f930","🫃":"1fac3","🫄":"1fac4","🤱":"1f931","😤":"1f624","🎶":"1f3b6","🎵":"1f3b5","🎼":"1f3bc","🔕":"1f515","🔔":"1f514","😡":"1f621","📯":"1f4ef","📣":"1f4e3","📢":"1f4e2","🔊":"1f50a","🔉":"1f509","😠":"1f620","🔈":"1f508","🔇":"1f507","💎":"1f48e","💍":"1f48d","💄":"1f484","👼":"1f47c","🎅":"1f385","🤶":"1f936","🤬":"1f92c","📿":"1f4ff","⛑":"26d1","🪖":"1fa96","🧢":"1f9e2","🦸":"1f9b8","🎓":"1f393","😈":"1f608","🎩":"1f3a9","👒":"1f452","👑":"1f451","🪮":"1faae","👢":"1f462","🩰":"1fa70","👡":"1f461","👠":"1f460","🥿":"1f97f","🥾":"1f97e","👟":"1f45f","👿":"1f47f","👞":"1f45e","🩴":"1fa74","🎒":"1f392","🛍":"1f6cd","👝":"1f45d","👜":"1f45c","👛":"1f45b","🪭":"1faad","👚":"1f45a","🦹":"1f9b9","👙":"1f459","💀":"1f480","🩳":"1fa73","🩲":"1fa72","🩱":"1fa71","🥻":"1f97b","👘":"1f458","👗":"1f457","🧦":"1f9e6","🧥":"1f9e5","🧤":"1f9e4","🧣":"1f9e3","👖":"1f456","👕":"1f455","👔":"1f454","🦺":"1f9ba","🥼":"1f97c","🥽":"1f97d","🕶":"1f576","👓":"1f453","🪢":"1faa2","🧶":"1f9f6","🧙":"1f9d9","🪡":"1faa1","☠":"2620","🧵":"1f9f5","🎨":"1f3a8","🖼":"1f5bc","🎭":"1f3ad","🎴":"1f3b4","🀄":"1f004","🃏":"1f0cf","♟":"265f","♣":"2663","💩":"1f4a9","♦":"2666","♥":"2665","♠":"2660","🪆":"1fa86","🪩":"1faa9","🪅":"1fa85","🧚":"1f9da","🧸":"1f9f8","🤡":"1f921","🧩":"1f9e9","🎲":"1f3b2","🎰":"1f3b0","🕹":"1f579","🎮":"1f3ae","🪄":"1fa84","🔮":"1f52e","🎱":"1f3b1","🔫":"1f52b","🪁":"1fa81","👹":"1f479","🪀":"1fa80","🎯":"1f3af","🥌":"1f94c","🛷":"1f6f7","🎿":"1f3bf","🎽":"1f3bd","🤿":"1f93f","🎣":"1f3a3","⛸":"26f8","🧛":"1f9db","⛳":"26f3","👺":"1f47a","🥅":"1f945","🥋":"1f94b","🥊":"1f94a","🏸":"1f3f8","🏓":"1f3d3","🥍":"1f94d","🏒":"1f3d2","🏑":"1f3d1","🏏":"1f3cf","🎳":"1f3b3","🥏":"1f94f","👻":"1f47b","🎾":"1f3be","🏉":"1f3c9","🏈":"1f3c8","🏐":"1f3d0","🏀":"1f3c0","🥎":"1f94e","⚾":"26be","⚽":"26bd","🥉":"1f949","🥈":"1f948","🧜":"1f9dc","🥇":"1f947","👽":"1f47d","🏅":"1f3c5","🏆":"1f3c6","🎖":"1f396","🎫":"1f3ab","🎟":"1f39f","🎗":"1f397","🎁":"1f381","🎀":"1f380","👾":"1f47e","🧧":"1f9e7","🎑":"1f391","🎐":"1f390","🎏":"1f38f","🎎":"1f38e","🎍":"1f38d","🎋":"1f38b","🎊":"1f38a","🎉":"1f389","🎈":"1f388","🧝":"1f9dd","✨":"2728","🤖":"1f916","🧨":"1f9e8","🎇":"1f387","🎆":"1f386","🎄":"1f384","🎃":"1f383","🌊":"1f30a","💧":"1f4a7","🔥":"1f525","☄":"2604","⛄":"26c4","😺":"1f63a","☃":"2603","❄":"2744","⚡":"26a1","⛱":"26f1","☔":"2614","☂":"2602","🧞":"1f9de","🌂":"1f302","😸":"1f638","🌈":"1f308","😹":"1f639","🧟":"1f9df","🌀":"1f300","😻":"1f63b","🌬":"1f32c","😼":"1f63c","🧌":"1f9cc","💆":"1f486","😽":"1f63d","🌫":"1f32b","🌪":"1f32a","🌩":"1f329","🌨":"1f328","🌧":"1f327","🌦":"1f326","🙀":"1f640","🌥":"1f325","🌤":"1f324","⛈":"26c8","⛅":"26c5","☁":"2601","💇":"1f487","🌌":"1f30c","😿":"1f63f","🌠":"1f320","🌟":"1f31f","⭐":"2b50","🪐":"1fa90","🌞":"1f31e","🌝":"1f31d","☀":"2600","🌡":"1f321","🌜":"1f31c","😾":"1f63e","🌛":"1f31b","🌚":"1f31a","🌙":"1f319","🌘":"1f318","🌗":"1f317","🌖":"1f316","🌕":"1f315","🌔":"1f314","🌓":"1f313","🌒":"1f312","🚶":"1f6b6","🌑":"1f311","🙈":"1f648","🕦":"1f566","🕚":"1f55a","🕥":"1f565","🕙":"1f559","🕤":"1f564","🕘":"1f558","🕣":"1f563","🕗":"1f557","🕢":"1f562","🕖":"1f556","🕡":"1f561","🙉":"1f649","🕕":"1f555","🕠":"1f560","🕔":"1f554","🕟":"1f55f","🕓":"1f553","🕞":"1f55e","🕒":"1f552","🕝":"1f55d","🕑":"1f551","🕜":"1f55c","🕐":"1f550","🙊":"1f64a","🕧":"1f567","🕛":"1f55b","🕰":"1f570","⏲":"23f2","⏱":"23f1","⏰":"23f0","⌚":"231a","⏳":"23f3","⌛":"231b","🧳":"1f9f3","🛎":"1f6ce","🛸":"1f6f8","🚀":"1f680","🛰":"1f6f0","🚡":"1f6a1","🚠":"1f6a0","🚟":"1f69f","🚁":"1f681","💺":"1f4ba","🪂":"1fa82","🛬":"1f6ec","🛫":"1f6eb","🛩":"1f6e9","✈":"2708","🚢":"1f6a2","🛥":"1f6e5","⛴":"26f4","🛳":"1f6f3","🚤":"1f6a4","🛶":"1f6f6","⛵":"26f5","🛟":"1f6df","⚓":"2693","🚧":"1f6a7","🛑":"1f6d1","🚦":"1f6a6","🚥":"1f6a5","🚨":"1f6a8","🛞":"1f6de","⛽":"26fd","🛢":"1f6e2","🛤":"1f6e4","🛣":"1f6e3","🚏":"1f68f","🛼":"1f6fc","🧍":"1f9cd","🛹":"1f6f9","💌":"1f48c","🛴":"1f6f4","🚲":"1f6b2","🛺":"1f6fa","🦼":"1f9bc","🦽":"1f9bd","🛵":"1f6f5","🏍":"1f3cd","🏎":"1f3ce","🚜":"1f69c","💘":"1f498","🚛":"1f69b","🚚":"1f69a","🛻":"1f6fb","🚙":"1f699","🚘":"1f698","🚗":"1f697","🚖":"1f696","🚕":"1f695","🚔":"1f694","🚓":"1f693","🧎":"1f9ce","🚒":"1f692","💝":"1f49d","🚑":"1f691","🚐":"1f690","🚎":"1f68e","🚍":"1f68d","🚌":"1f68c","🚋":"1f68b","🚞":"1f69e","🚝":"1f69d","🚊":"1f68a","🚉":"1f689","🚈":"1f688","💖":"1f496","🚇":"1f687","🚆":"1f686","🚅":"1f685","🚄":"1f684","🚃":"1f683","🚂":"1f682","🎪":"1f3aa","💈":"1f488","🎢":"1f3a2","🎡":"1f3a1","🛝":"1f6dd","💗":"1f497","🎠":"1f3a0","♨":"2668","🌉":"1f309","🌇":"1f307","🌆":"1f306","🌅":"1f305","🌄":"1f304","🏙":"1f3d9","🌃":"1f303","🌁":"1f301","⛺":"26fa","⛲":"26f2","🕋":"1f54b","⛩":"26e9","🕍":"1f54d","🛕":"1f6d5","🕌":"1f54c","⛪":"26ea","🗽":"1f5fd","🗼":"1f5fc","💒":"1f492","🏰":"1f3f0","🏯":"1f3ef","🏭":"1f3ed","🏬":"1f3ec","🏫":"1f3eb","🏪":"1f3ea","🏩":"1f3e9","🏨":"1f3e8","🏦":"1f3e6","🏥":"1f3e5","🏤":"1f3e4","🏣":"1f3e3","🏢":"1f3e2","🏡":"1f3e1","🏠":"1f3e0","🏚":"1f3da","🏘":"1f3d8","🛖":"1f6d6","🪵":"1fab5","🪨":"1faa8","🧱":"1f9f1","🏗":"1f3d7","🏛":"1f3db","🏟":"1f3df","🏞":"1f3de","🏝":"1f3dd","🏜":"1f3dc","💓":"1f493","🏖":"1f3d6","🏕":"1f3d5","🗻":"1f5fb","🌋":"1f30b","⛰":"26f0","🏔":"1f3d4","🧭":"1f9ed","🗾":"1f5fe","🗺":"1f5fa","🌐":"1f310","🌏":"1f30f","💞":"1f49e","🌎":"1f30e","🌍":"1f30d","🏺":"1f3fa","🫙":"1fad9","🔪":"1f52a","🥄":"1f944","🍴":"1f374","🍽":"1f37d","🥢":"1f962","🧊":"1f9ca","🧉":"1f9c9","🧃":"1f9c3","🧋":"1f9cb","🥤":"1f964","🫗":"1fad7","🥃":"1f943","💕":"1f495","🥂":"1f942","🍻":"1f37b","🍺":"1f37a","🍹":"1f379","🍸":"1f378","🍷":"1f377","🍾":"1f37e","🍶":"1f376","🍵":"1f375","🫖":"1fad6","☕":"2615","🥛":"1f95b","🍼":"1f37c","🍯":"1f36f","🍮":"1f36e","🍭":"1f36d","🍬":"1f36c","💟":"1f49f","🍫":"1f36b","🥧":"1f967","🧁":"1f9c1","🍰":"1f370","🎂":"1f382","🍪":"1f36a","🍩":"1f369","🍨":"1f368","🍧":"1f367","🍦":"1f366","🥡":"1f961","🥠":"1f960","🥟":"1f95f","🍡":"1f361","🥮":"1f96e","🍥":"1f365","🍤":"1f364","🍣":"1f363","🍢":"1f362","🍠":"1f360","🍝":"1f35d","🍜":"1f35c","🍛":"1f35b","🍚":"1f35a","🍙":"1f359","🍘":"1f358","🍱":"1f371","🥫":"1f96b","🧂":"1f9c2","🧈":"1f9c8","🍿":"1f37f","🥗":"1f957","🥣":"1f963","🫕":"1fad5","❣":"2763","🍲":"1f372","🥘":"1f958","🍳":"1f373","🥚":"1f95a","🧆":"1f9c6","🥙":"1f959","🫔":"1fad4","🌯":"1f32f","🌮":"1f32e","🥪":"1f96a","🌭":"1f32d","🍕":"1f355","🍟":"1f35f","🍔":"1f354","🥓":"1f953","🥩":"1f969","🍗":"1f357","💔":"1f494","🍖":"1f356","🧀":"1f9c0","🧇":"1f9c7","🥞":"1f95e","🥯":"1f96f","🥨":"1f968","🫓":"1fad3","🥖":"1f956","🥐":"1f950","🍞":"1f35e","🫛":"1fadb","🫚":"1fada","🌰":"1f330","🫘":"1fad8","🥜":"1f95c","🧅":"1f9c5","🧄":"1f9c4","🥦":"1f966","🥬":"1f96c","🥒":"1f952","🫑":"1fad1","🌶":"1f336","🌽":"1f33d","🥕":"1f955","🥔":"1f954","🍆":"1f346","🥑":"1f951","🥥":"1f965","🫒":"1fad2","🍅":"1f345","🥝":"1f95d","🫐":"1fad0","🍓":"1f353","🤣":"1f923","🍒":"1f352","🍑":"1f351","🍐":"1f350","🍏":"1f34f","🍎":"1f34e","🥭":"1f96d","🍍":"1f34d","🍌":"1f34c","🍋":"1f34b","🍊":"1f34a","🍉":"1f349","🍈":"1f348","🍇":"1f347","🍄":"1f344","🪺":"1faba","🪹":"1fab9","🍃":"1f343","🏃":"1f3c3","🍂":"1f342","🍁":"1f341","🍀":"1f340","☘":"2618","🌿":"1f33f","🌾":"1f33e","🌵":"1f335","🌴":"1f334","🌳":"1f333","🌲":"1f332","🪴":"1fab4","😂":"1f602","🌱":"1f331","🪻":"1fabb","🌷":"1f337","🌼":"1f33c","🌻":"1f33b","🌺":"1f33a","🥀":"1f940","🌹":"1f339","🏵":"1f3f5","🪷":"1fab7","💮":"1f4ae","🌸":"1f338","💐":"1f490","🦠":"1f9a0","🪱":"1fab1","🪰":"1fab0","🦟":"1f99f","🦂":"1f982","🕸":"1f578","🕷":"1f577","🪳":"1fab3","🦗":"1f997","🐞":"1f41e","🪲":"1fab2","🐝":"1f41d","🐜":"1f41c","🐛":"1f41b","🦋":"1f98b","🐌":"1f40c","🦪":"1f9aa","🦑":"1f991","🦐":"1f990","🦞":"1f99e","🦀":"1f980","🪼":"1fabc","🪸":"1fab8","🐚":"1f41a","🐙":"1f419","🦈":"1f988","🐡":"1f421","🐠":"1f420","🐟":"1f41f","🦭":"1f9ad","🐬":"1f42c","🐋":"1f40b","🐳":"1f433","🦖":"1f996","🦕":"1f995","🐉":"1f409","🐲":"1f432","🐍":"1f40d","🦎":"1f98e","🐢":"1f422","🐊":"1f40a","🐸":"1f438","🪿":"1fabf","🪽":"1fabd","🦜":"1f99c","🦚":"1f99a","🦩":"1f9a9","🪶":"1fab6","🦤":"1f9a4","🦉":"1f989","🦢":"1f9a2","🦆":"1f986","🦅":"1f985","💃":"1f483","🕺":"1f57a","🕴":"1f574","👯":"1f46f","🕊":"1f54a","❤":"2764","🩷":"1fa77","🧖":"1f9d6","🐧":"1f427","🧡":"1f9e1","🐦":"1f426","🐥":"1f425","🐤":"1f424","🐣":"1f423","🐓":"1f413","🐔":"1f414","🦃":"1f983","🐾":"1f43e","🦡":"1f9a1","🦘":"1f998","🦨":"1f9a8","💛":"1f49b","🦦":"1f9a6","🦥":"1f9a5","🐼":"1f43c","🐨":"1f428","🐻":"1f43b","🦇":"1f987","🦔":"1f994","🦫":"1f9ab","🐿":"1f43f","🧗":"1f9d7","🐇":"1f407","💚":"1f49a","🐰":"1f430","🐹":"1f439","🐀":"1f400","🐁":"1f401","🐭":"1f42d","🦛":"1f99b","🦏":"1f98f","🦣":"1f9a3","🐘":"1f418","🦒":"1f992","🦙":"1f999","💙":"1f499","🐫":"1f42b","🐪":"1f42a","🐐":"1f410","🐑":"1f411","🐏":"1f40f","🐽":"1f43d","🐗":"1f417","🐖":"1f416","🐷":"1f437","🐄":"1f404","🤺":"1f93a","🏇":"1f3c7","⛷":"26f7","🏂":"1f3c2","🏌":"1f3cc","🐃":"1f403","🐂":"1f402","🐮":"1f42e","🦬":"1f9ac","🐒":"1f412","🤎":"1f90e","🐵":"1f435","🦲":"1f3fb","🦳":"1f3fb","🦱":"1f3fb","🦰":"1f3fb","🏿":"1f3fb","🏾":"1f3fb","🏽":"1f3fb","🏼":"1f3fb","🏻":"1f3fb","🫆":"1f3fb","🖤":"1f5a4","👣":"1f463","👪":"1f46a","🫂":"1fac2","👥":"1f465","👤":"1f464","🗣":"1f5e3","🚣":"1f6a3","🩶":"1fa76","💑":"1f491","💏":"1f48f","🤍":"1f90d","👬":"1f46c","👫":"1f46b","🏊":"1f3ca","💋":"1f48b","👭":"1f46d","🛌":"1f6cc","💯":"1f4af","🛀":"1f6c0","⛹":"26f9","🧘":"1f9d8","💢":"1f4a2","🤹":"1f939","🤾":"1f93e","💥":"1f4a5","🤽":"1f93d","🤼":"1f93c","🤸":"1f938","🏋":"1f3cb","💫":"1f4ab","🚵":"1f6b5","🚴":"1f6b4","💦":"1f4a6","💨":"1f4a8","🕳":"1f573","💬":"1f4ac","🙂":"1f642","🗨":"1f5e8","🗯":"1f5ef","💭":"1f4ad","💤":"1f4a4","👋":"1f44b","🤚":"1f91a","🖐":"1f590","✋":"270b","🖖":"1f596","🫱":"1faf1","🫲":"1faf2","🫳":"1faf3","🫴":"1faf4","🫷":"1faf7","🫸":"1faf8","👌":"1f44c","🤌":"1f90c","🤏":"1f90f","✌":"270c","🤞":"1f91e","🫰":"1faf0","🤟":"1f91f","🤘":"1f918","🤙":"1f919","👈":"1f448","👉":"1f449","👆":"1f446","🖕":"1f595","👇":"1f447","☝":"261d","🫵":"1faf5","👍":"1f44d","👎":"1f44e","✊":"270a","👊":"1f44a","🤛":"1f91b","🤜":"1f91c","👏":"1f44f","🙌":"1f64c","🫶":"1faf6","👐":"1f450","🤲":"1f932","🤝":"1f91d","🙏":"1f64f","✍":"270d","💅":"1f485","🤳":"1f933","💪":"1f4aa","🦾":"1f9be","🦿":"1f9bf","🦵":"1f9b5","🦶":"1f9b6","👂":"1f442","🦻":"1f9bb","👃":"1f443","🧠":"1f9e0","🫀":"1fac0","🫁":"1fac1","🦷":"1f9b7","🦴":"1f9b4","👀":"1f440","👁":"1f441","👅":"1f445","👄":"1f444","🫦":"1fae6","👶":"1f476","🧒":"1f9d2","👦":"1f466","👧":"1f467","🧑":"1f9d1","👱":"1f471","👨":"1f468","🧔":"1f9d4","🙃":"1f643","🫠":"1fae0","😉":"1f609","😊":"1f60a","😇":"1f607","🥰":"1f970","👩":"1f469","😍":"1f60d","🤩":"1f929","😀":"1f600","😘":"1f618","😗":"1f617","☺":"263a","😚":"1f61a","😙":"1f619","🥲":"1f972","🧓":"1f9d3","👴":"1f474","👵":"1f475","🙍":"1f64d","😋":"1f60b","😛":"1f61b","🙎":"1f64e","😜":"1f61c","🤪":"1f92a","🙅":"1f645","😝":"1f61d","🤑":"1f911","🙆":"1f646","🤗":"1f917","🤭":"1f92d","💁":"1f481","🫢":"1fae2","🫣":"1fae3","🙋":"1f64b","🤫":"1f92b","🤔":"1f914","🏳":"1f3f3","🏴":"1f3f4","🎌":"1f38c","🚩":"1f6a9","🏁":"1f3c1","🧏":"1f9cf","🔲":"1f532","🫡":"1fae1","🔳":"1f533","🔘":"1f518","💠":"1f4a0","🔻":"1f53b","🔺":"1f53a","🔹":"1f539","🔸":"1f538","🔷":"1f537","🔶":"1f536","▫":"25ab","🤐":"1f910","▪":"25aa","◽":"25fd","◾":"25fe","◻":"25fb","◼":"25fc","⬜":"2b1c","⬛":"2b1b","🙇":"1f647","🟫":"1f7eb","🤨":"1f928","🟪":"1f7ea","🟦":"1f7e6","🟩":"1f7e9","🟨":"1f7e8","🟧":"1f7e7","🟥":"1f7e5","⚪":"26aa","⚫":"26ab","🟤":"1f7e4","🟣":"1f7e3","🔵":"1f535","😐":"1f610","🟢":"1f7e2","🟡":"1f7e1","🟠":"1f7e0","🔴":"1f534","🈵":"1f235","🈺":"1f23a","㊙":"3299","㊗":"3297","🤦":"1f926","🈳":"1f233","😑":"1f611","🈴":"1f234","🈸":"1f238","🉑":"1f251","🈲":"1f232","🈚":"1f21a","🈹":"1f239","🉐":"1f250","🈯":"1f22f","🈶":"1f236","🈷":"1f237","😶":"1f636","🈂":"1f202","🈁":"1f201","🆚":"1f19a","🆙":"1f199","🆘":"1f198","🅿":"1f17f","☯️":"262f","🏄🏼":"1f3c4-1f3fc","🏄🏽":"1f3c4-1f3fd","🏄🏾":"1f3c4-1f3fe","🏄🏿":"1f3c4-1f3ff","🧚🏿":"1f9da-1f3ff","🕯️":"1f56f","☸️":"2638","☁️":"2601","✡️":"2721","🛢️":"1f6e2","💇🏻":"1f487-1f3fb","🕉️":"1f549","💇🏼":"1f487-1f3fc","⚛️":"269b","💇🏽":"1f487-1f3fd","💇🏾":"1f487-1f3fe","🕹️":"1f579","💇🏿":"1f487-1f3ff","💃🏽":"1f483-1f3fd","🛤️":"1f6e4","📽️":"1f4fd","💃🏾":"1f483-1f3fe","🎞️":"1f39e","🛣️":"1f6e3","🗣️":"1f5e3","💑🏿":"1f491-1f3ff","💑🏾":"1f491-1f3fe","💑🏽":"1f491-1f3fd","💃🏿":"1f483-1f3ff","🚣🏻":"1f6a3-1f3fb","🚣🏼":"1f6a3-1f3fc","🚣🏽":"1f6a3-1f3fd","🚣🏾":"1f6a3-1f3fe","🚣🏿":"1f6a3-1f3ff","💑🏼":"1f491-1f3fc","⤵️":"2935","💑🏻":"1f491-1f3fb","🏇🏻":"1f3c7-1f3fb","💏🏿":"1f48f-1f3ff","💏🏾":"1f48f-1f3fe","💏🏽":"1f48f-1f3fd","💏🏼":"1f48f-1f3fc","💏🏻":"1f48f-1f3fb","⤴️":"2934","👬🏿":"1f46c-1f3ff","👬🏾":"1f46c-1f3fe","👬🏽":"1f46c-1f3fd","🕺🏻":"1f57a-1f3fb","👬🏼":"1f46c-1f3fc","👬🏻":"1f46c-1f3fb","👲🏻":"1f472-1f3fb","👫🏿":"1f46b-1f3ff","👫🏾":"1f46b-1f3fe","👫🏽":"1f46b-1f3fd","👫🏼":"1f46b-1f3fc","👫🏻":"1f46b-1f3fb","↪️":"21aa","👭🏿":"1f46d-1f3ff","👲🏼":"1f472-1f3fc","🏊🏻":"1f3ca-1f3fb","🏊🏼":"1f3ca-1f3fc","🏊🏽":"1f3ca-1f3fd","🏊🏾":"1f3ca-1f3fe","🏊🏿":"1f3ca-1f3ff","👭🏾":"1f46d-1f3fe","↩️":"21a9","👭🏽":"1f46d-1f3fd","👭🏼":"1f46d-1f3fc","👭🏻":"1f46d-1f3fb","👲🏽":"1f472-1f3fd","🛌🏿":"1f6cc-1f3ff","🛌🏾":"1f6cc-1f3fe","🛌🏽":"1f6cc-1f3fd","🛌🏼":"1f6cc-1f3fc","🛌🏻":"1f6cc-1f3fb","👲🏾":"1f472-1f3fe","🛀🏿":"1f6c0-1f3ff","↔️":"2194","🛀🏾":"1f6c0-1f3fe","🛀🏽":"1f6c0-1f3fd","🛀🏼":"1f6c0-1f3fc","🛀🏻":"1f6c0-1f3fb","👲🏿":"1f472-1f3ff","🧘🏿":"1f9d8-1f3ff","🧘🏾":"1f9d8-1f3fe","🧘🏽":"1f9d8-1f3fd","🧘🏼":"1f9d8-1f3fc","🧘🏻":"1f9d8-1f3fb","⛹️":"26f9","↕️":"2195","⛹🏻":"26f9-1f3fb","⛹🏼":"26f9-1f3fc","⛹🏽":"26f9-1f3fd","⛹🏾":"26f9-1f3fe","⛹🏿":"26f9-1f3ff","🧍🏻":"1f9cd-1f3fb","🤹🏿":"1f939-1f3ff","🤹🏾":"1f939-1f3fe","↖️":"2196","🤹🏽":"1f939-1f3fd","🤹🏼":"1f939-1f3fc","🤹🏻":"1f939-1f3fb","🧕🏻":"1f9d5-1f3fb","🤾🏿":"1f93e-1f3ff","🤾🏾":"1f93e-1f3fe","🤾🏽":"1f93e-1f3fd","🤾🏼":"1f93e-1f3fc","🤾🏻":"1f93e-1f3fb","🧕🏼":"1f9d5-1f3fc","🤽🏿":"1f93d-1f3ff","🤽🏾":"1f93d-1f3fe","🤽🏽":"1f93d-1f3fd","⬅️":"2b05","🤽🏼":"1f93d-1f3fc","🤽🏻":"1f93d-1f3fb","🧕🏽":"1f9d5-1f3fd","↙️":"2199","🤸🏿":"1f938-1f3ff","🤸🏾":"1f938-1f3fe","🤸🏽":"1f938-1f3fd","🤸🏼":"1f938-1f3fc","🤸🏻":"1f938-1f3fb","🧕🏾":"1f9d5-1f3fe","🏋️":"1f3cb","🧕🏿":"1f9d5-1f3ff","🏋🏻":"1f3cb-1f3fb","🏋🏼":"1f3cb-1f3fc","🏋🏽":"1f3cb-1f3fd","🏋🏾":"1f3cb-1f3fe","🏋🏿":"1f3cb-1f3ff","🚵🏿":"1f6b5-1f3ff","🚵🏾":"1f6b5-1f3fe","🚵🏽":"1f6b5-1f3fd","⬇️":"2b07","🚵🏼":"1f6b5-1f3fc","🚵🏻":"1f6b5-1f3fb","🧍🏼":"1f9cd-1f3fc","🚴🏿":"1f6b4-1f3ff","🚴🏾":"1f6b4-1f3fe","🚴🏽":"1f6b4-1f3fd","🚴🏼":"1f6b4-1f3fc","🚴🏻":"1f6b4-1f3fb","↘️":"2198","🤵🏻":"1f935-1f3fb","➡️":"27a1","🕳️":"1f573","🤵🏼":"1f935-1f3fc","🤵🏽":"1f935-1f3fd","🇽🇰":"1f1fd-1f1f0","🇼🇸":"1f1fc-1f1f8","🇼🇫":"1f1fc-1f1eb","↗️":"2197","🗨️":"1f5e8","🤵🏾":"1f935-1f3fe","🗯️":"1f5ef","⬆️":"2b06","🤵🏿":"1f935-1f3ff","☀️":"2600","☣️":"2623","👋🏻":"1f44b-1f3fb","👋🏼":"1f44b-1f3fc","👋🏽":"1f44b-1f3fd","👋🏾":"1f44b-1f3fe","👋🏿":"1f44b-1f3ff","🧍🏽":"1f9cd-1f3fd","🤚🏻":"1f91a-1f3fb","🤚🏼":"1f91a-1f3fc","🤚🏽":"1f91a-1f3fd","🤚🏾":"1f91a-1f3fe","🤚🏿":"1f91a-1f3ff","🖐️":"1f590","☢️":"2622","🖐🏻":"1f590-1f3fb","🖐🏼":"1f590-1f3fc","🖐🏽":"1f590-1f3fd","🖐🏾":"1f590-1f3fe","🖐🏿":"1f590-1f3ff","🌡️":"1f321","✋🏻":"270b-1f3fb","✋🏼":"270b-1f3fc","✋🏽":"270b-1f3fd","✋🏾":"270b-1f3fe","✋🏿":"270b-1f3ff","🧍🏾":"1f9cd-1f3fe","🖖🏻":"1f596-1f3fb","🖖🏼":"1f596-1f3fc","🖖🏽":"1f596-1f3fd","🖖🏾":"1f596-1f3fe","🖖🏿":"1f596-1f3ff","🧍🏿":"1f9cd-1f3ff","🫱🏻":"1faf1-1f3fb","🫱🏼":"1faf1-1f3fc","🫱🏽":"1faf1-1f3fd","🫱🏾":"1faf1-1f3fe","🫱🏿":"1faf1-1f3ff","🖲️":"1f5b2","🫲🏻":"1faf2-1f3fb","🫲🏼":"1faf2-1f3fc","🫲🏽":"1faf2-1f3fd","🫲🏾":"1faf2-1f3fe","🫲🏿":"1faf2-1f3ff","🕺🏼":"1f57a-1f3fc","🫳🏻":"1faf3-1f3fb","🫳🏼":"1faf3-1f3fc","🫳🏽":"1faf3-1f3fd","🫳🏾":"1faf3-1f3fe","🫳🏿":"1faf3-1f3ff","🖱️":"1f5b1","🫴🏻":"1faf4-1f3fb","🫴🏼":"1faf4-1f3fc","🫴🏽":"1faf4-1f3fd","🫴🏾":"1faf4-1f3fe","🫴🏿":"1faf4-1f3ff","⛸️":"26f8","🫷🏻":"1faf7-1f3fb","🫷🏼":"1faf7-1f3fc","🫷🏽":"1faf7-1f3fd","🫷🏾":"1faf7-1f3fe","🫷🏿":"1faf7-1f3ff","⌨️":"2328","🫸🏻":"1faf8-1f3fb","🫸🏼":"1faf8-1f3fc","🫸🏽":"1faf8-1f3fd","🫸🏾":"1faf8-1f3fe","🫸🏿":"1faf8-1f3ff","🕺🏽":"1f57a-1f3fd","👌🏻":"1f44c-1f3fb","👌🏼":"1f44c-1f3fc","👌🏽":"1f44c-1f3fd","👌🏾":"1f44c-1f3fe","👌🏿":"1f44c-1f3ff","🖨️":"1f5a8","🤌🏻":"1f90c-1f3fb","🤌🏼":"1f90c-1f3fc","🤌🏽":"1f90c-1f3fd","🤌🏾":"1f90c-1f3fe","🤌🏿":"1f90c-1f3ff","🧛🏻":"1f9db-1f3fb","🤏🏻":"1f90f-1f3fb","🤏🏼":"1f90f-1f3fc","🤏🏽":"1f90f-1f3fd","🤏🏾":"1f90f-1f3fe","🤏🏿":"1f90f-1f3ff","✌️":"270c","🧛🏼":"1f9db-1f3fc","✌🏻":"270c-1f3fb","✌🏼":"270c-1f3fc","✌🏽":"270c-1f3fd","✌🏾":"270c-1f3fe","✌🏿":"270c-1f3ff","🖥️":"1f5a5","🤞🏻":"1f91e-1f3fb","🤞🏼":"1f91e-1f3fc","🤞🏽":"1f91e-1f3fd","🤞🏾":"1f91e-1f3fe","🤞🏿":"1f91e-1f3ff","⚠️":"26a0","🫰🏻":"1faf0-1f3fb","🫰🏼":"1faf0-1f3fc","🫰🏽":"1faf0-1f3fd","🫰🏾":"1faf0-1f3fe","🫰🏿":"1faf0-1f3ff","🧛🏽":"1f9db-1f3fd","🤟🏻":"1f91f-1f3fb","🤟🏼":"1f91f-1f3fc","🤟🏽":"1f91f-1f3fd","🤟🏾":"1f91f-1f3fe","🤟🏿":"1f91f-1f3ff","🧛🏾":"1f9db-1f3fe","🤘🏻":"1f918-1f3fb","🤘🏼":"1f918-1f3fc","🤘🏽":"1f918-1f3fd","🤘🏾":"1f918-1f3fe","🤘🏿":"1f918-1f3ff","🧛🏿":"1f9db-1f3ff","🤙🏻":"1f919-1f3fb","🤙🏼":"1f919-1f3fc","🤙🏽":"1f919-1f3fd","🤙🏾":"1f919-1f3fe","🤙🏿":"1f919-1f3ff","🕺🏾":"1f57a-1f3fe","👈🏻":"1f448-1f3fb","👈🏼":"1f448-1f3fc","👈🏽":"1f448-1f3fd","👈🏾":"1f448-1f3fe","👈🏿":"1f448-1f3ff","🕺🏿":"1f57a-1f3ff","👉🏻":"1f449-1f3fb","👉🏼":"1f449-1f3fc","👉🏽":"1f449-1f3fd","👉🏾":"1f449-1f3fe","👉🏿":"1f449-1f3ff","🏃🏻":"1f3c3-1f3fb","👆🏻":"1f446-1f3fb","👆🏼":"1f446-1f3fc","👆🏽":"1f446-1f3fd","👆🏾":"1f446-1f3fe","👆🏿":"1f446-1f3ff","🏃🏼":"1f3c3-1f3fc","🖕🏻":"1f595-1f3fb","🖕🏼":"1f595-1f3fc","🖕🏽":"1f595-1f3fd","🖕🏾":"1f595-1f3fe","🖕🏿":"1f595-1f3ff","🏃🏽":"1f3c3-1f3fd","👇🏻":"1f447-1f3fb","👇🏼":"1f447-1f3fc","👇🏽":"1f447-1f3fd","👇🏾":"1f447-1f3fe","👇🏿":"1f447-1f3ff","☝️":"261d","☎️":"260e","☝🏻":"261d-1f3fb","☝🏼":"261d-1f3fc","☝🏽":"261d-1f3fd","☝🏾":"261d-1f3fe","☝🏿":"261d-1f3ff","🏃🏾":"1f3c3-1f3fe","🫵🏻":"1faf5-1f3fb","🫵🏼":"1faf5-1f3fc","🫵🏽":"1faf5-1f3fd","🫵🏾":"1faf5-1f3fe","🫵🏿":"1faf5-1f3ff","👰🏻":"1f470-1f3fb","👍🏻":"1f44d-1f3fb","👍🏼":"1f44d-1f3fc","👍🏽":"1f44d-1f3fd","👍🏾":"1f44d-1f3fe","👍🏿":"1f44d-1f3ff","👰🏼":"1f470-1f3fc","👎🏻":"1f44e-1f3fb","👎🏼":"1f44e-1f3fc","👎🏽":"1f44e-1f3fd","👎🏾":"1f44e-1f3fe","👎🏿":"1f44e-1f3ff","👰🏽":"1f470-1f3fd","✊🏻":"270a-1f3fb","✊🏼":"270a-1f3fc","✊🏽":"270a-1f3fd","✊🏾":"270a-1f3fe","✊🏿":"270a-1f3ff","👰🏾":"1f470-1f3fe","👊🏻":"1f44a-1f3fb","👊🏼":"1f44a-1f3fc","👊🏽":"1f44a-1f3fd","👊🏾":"1f44a-1f3fe","👊🏿":"1f44a-1f3ff","👰🏿":"1f470-1f3ff","🤛🏻":"1f91b-1f3fb","🤛🏼":"1f91b-1f3fc","🤛🏽":"1f91b-1f3fd","🤛🏾":"1f91b-1f3fe","🤛🏿":"1f91b-1f3ff","🏃🏿":"1f3c3-1f3ff","🤜🏻":"1f91c-1f3fb","🤜🏼":"1f91c-1f3fc","🤜🏽":"1f91c-1f3fd","🤜🏾":"1f91c-1f3fe","🤜🏿":"1f91c-1f3ff","🏍️":"1f3cd","👏🏻":"1f44f-1f3fb","👏🏼":"1f44f-1f3fc","👏🏽":"1f44f-1f3fd","👏🏾":"1f44f-1f3fe","👏🏿":"1f44f-1f3ff","🕴️":"1f574","🙌🏻":"1f64c-1f3fb","🙌🏼":"1f64c-1f3fc","🙌🏽":"1f64c-1f3fd","🙌🏾":"1f64c-1f3fe","🙌🏿":"1f64c-1f3ff","🚶🏻":"1f6b6-1f3fb","🫶🏻":"1faf6-1f3fb","🫶🏼":"1faf6-1f3fc","🫶🏽":"1faf6-1f3fd","🫶🏾":"1faf6-1f3fe","🫶🏿":"1faf6-1f3ff","🚶🏼":"1f6b6-1f3fc","👐🏻":"1f450-1f3fb","👐🏼":"1f450-1f3fc","👐🏽":"1f450-1f3fd","👐🏾":"1f450-1f3fe","👐🏿":"1f450-1f3ff","🚶🏽":"1f6b6-1f3fd","🤲🏻":"1f932-1f3fb","🤲🏼":"1f932-1f3fc","🤲🏽":"1f932-1f3fd","🤲🏾":"1f932-1f3fe","🤲🏿":"1f932-1f3ff","🚶🏾":"1f6b6-1f3fe","🤝🏻":"1f91d-1f3fb","🤝🏼":"1f91d-1f3fc","🤝🏽":"1f91d-1f3fd","🤝🏾":"1f91d-1f3fe","🤝🏿":"1f91d-1f3ff","🇻🇺":"1f1fb-1f1fa","🇻🇳":"1f1fb-1f1f3","🇻🇮":"1f1fb-1f1ee","🇻🇬":"1f1fb-1f1ec","🇻🇪":"1f1fb-1f1ea","🇻🇨":"1f1fb-1f1e8","🇻🇦":"1f1fb-1f1e6","🇺🇿":"1f1fa-1f1ff","🇺🇾":"1f1fa-1f1fe","🇺🇸":"1f1fa-1f1f8","🇺🇳":"1f1fa-1f1f3","🇺🇲":"1f1fa-1f1f2","🇺🇬":"1f1fa-1f1ec","🇺🇦":"1f1fa-1f1e6","🇹🇿":"1f1f9-1f1ff","🇹🇼":"1f1f9-1f1fc","🇹🇻":"1f1f9-1f1fb","🇹🇹":"1f1f9-1f1f9","🇹🇷":"1f1f9-1f1f7","🇹🇴":"1f1f9-1f1f4","🚶🏿":"1f6b6-1f3ff","🙏🏻":"1f64f-1f3fb","🙏🏼":"1f64f-1f3fc","🙏🏽":"1f64f-1f3fd","🙏🏾":"1f64f-1f3fe","🙏🏿":"1f64f-1f3ff","✍️":"270d","⚱️":"26b1","✍🏻":"270d-1f3fb","✍🏼":"270d-1f3fc","✍🏽":"270d-1f3fd","✍🏾":"270d-1f3fe","✍🏿":"270d-1f3ff","🏎️":"1f3ce","💅🏻":"1f485-1f3fb","💅🏼":"1f485-1f3fc","💅🏽":"1f485-1f3fd","💅🏾":"1f485-1f3fe","💅🏿":"1f485-1f3ff","🇾🇪":"1f1fe-1f1ea","🤳🏻":"1f933-1f3fb","🤳🏼":"1f933-1f3fc","🤳🏽":"1f933-1f3fd","🤳🏾":"1f933-1f3fe","🤳🏿":"1f933-1f3ff","🏇🏼":"1f3c7-1f3fc","💪🏻":"1f4aa-1f3fb","💪🏼":"1f4aa-1f3fc","💪🏽":"1f4aa-1f3fd","💪🏾":"1f4aa-1f3fe","💪🏿":"1f4aa-1f3ff","⚰️":"26b0","🕴🏻":"1f574-1f3fb","🏚️":"1f3da","🦵🏻":"1f9b5-1f3fb","🦵🏼":"1f9b5-1f3fc","🦵🏽":"1f9b5-1f3fd","🦵🏾":"1f9b5-1f3fe","🦵🏿":"1f9b5-1f3ff","🕴🏼":"1f574-1f3fc","🦶🏻":"1f9b6-1f3fb","🦶🏼":"1f9b6-1f3fc","🦶🏽":"1f9b6-1f3fd","🦶🏾":"1f9b6-1f3fe","🦶🏿":"1f9b6-1f3ff","🏘️":"1f3d8","👂🏻":"1f442-1f3fb","👂🏼":"1f442-1f3fc","👂🏽":"1f442-1f3fd","👂🏾":"1f442-1f3fe","👂🏿":"1f442-1f3ff","☘️":"2618","🦻🏻":"1f9bb-1f3fb","🦻🏼":"1f9bb-1f3fc","🦻🏽":"1f9bb-1f3fd","🦻🏾":"1f9bb-1f3fe","🦻🏿":"1f9bb-1f3ff","🕴🏽":"1f574-1f3fd","👃🏻":"1f443-1f3fb","👃🏼":"1f443-1f3fc","👃🏽":"1f443-1f3fd","👃🏾":"1f443-1f3fe","👃🏿":"1f443-1f3ff","🕴🏾":"1f574-1f3fe","🕴🏿":"1f574-1f3ff","🎛️":"1f39b","🧜🏻":"1f9dc-1f3fb","🎚️":"1f39a","🧜🏼":"1f9dc-1f3fc","👁️":"1f441","🎙️":"1f399","🧜🏽":"1f9dc-1f3fd","🤰🏻":"1f930-1f3fb","🤰🏼":"1f930-1f3fc","🤰🏽":"1f930-1f3fd","👶🏻":"1f476-1f3fb","👶🏼":"1f476-1f3fc","👶🏽":"1f476-1f3fd","👶🏾":"1f476-1f3fe","👶🏿":"1f476-1f3ff","🤰🏾":"1f930-1f3fe","🧒🏻":"1f9d2-1f3fb","🧒🏼":"1f9d2-1f3fc","🧒🏽":"1f9d2-1f3fd","🧒🏾":"1f9d2-1f3fe","🧒🏿":"1f9d2-1f3ff","🤰🏿":"1f930-1f3ff","👦🏻":"1f466-1f3fb","👦🏼":"1f466-1f3fc","👦🏽":"1f466-1f3fd","👦🏾":"1f466-1f3fe","👦🏿":"1f466-1f3ff","🧜🏾":"1f9dc-1f3fe","👧🏻":"1f467-1f3fb","👧🏼":"1f467-1f3fc","👧🏽":"1f467-1f3fd","👧🏾":"1f467-1f3fe","👧🏿":"1f467-1f3ff","🫃🏻":"1fac3-1f3fb","🧑🏻":"1f9d1-1f3fb","🧑🏼":"1f9d1-1f3fc","🧑🏽":"1f9d1-1f3fd","🧑🏾":"1f9d1-1f3fe","🧑🏿":"1f9d1-1f3ff","☹️":"2639","👱🏻":"1f471-1f3fb","👱🏼":"1f471-1f3fc","👱🏽":"1f471-1f3fd","👱🏾":"1f471-1f3fe","👱🏿":"1f471-1f3ff","🫃🏼":"1fac3-1f3fc","👨🏻":"1f468-1f3fb","👨🏼":"1f468-1f3fc","👨🏽":"1f468-1f3fd","👨🏾":"1f468-1f3fe","👨🏿":"1f468-1f3ff","🫃🏽":"1fac3-1f3fd","🧔🏻":"1f9d4-1f3fb","🧔🏼":"1f9d4-1f3fc","🧔🏽":"1f9d4-1f3fd","🧔🏾":"1f9d4-1f3fe","🧔🏿":"1f9d4-1f3ff","🇹🇳":"1f1f9-1f1f3","🫃🏾":"1fac3-1f3fe","🇹🇲":"1f1f9-1f1f2","🇹🇱":"1f1f9-1f1f1","🇹🇰":"1f1f9-1f1f0","🇹🇯":"1f1f9-1f1ef","🇹🇭":"1f1f9-1f1ed","🇹🇬":"1f1f9-1f1ec","🇹🇫":"1f1f9-1f1eb","🇹🇩":"1f1f9-1f1e9","🇹🇨":"1f1f9-1f1e8","🇹🇦":"1f1f9-1f1e6","🇸🇿":"1f1f8-1f1ff","🛋️":"1f6cb","🇸🇾":"1f1f8-1f1fe","🇸🇽":"1f1f8-1f1fd","🇸🇻":"1f1f8-1f1fb","🇸🇹":"1f1f8-1f1f9","🇸🇸":"1f1f8-1f1f8","🇸🇷":"1f1f8-1f1f7","🇸🇴":"1f1f8-1f1f4","🇸🇳":"1f1f8-1f1f3","🇸🇲":"1f1f8-1f1f2","🇸🇱":"1f1f8-1f1f1","🫃🏿":"1fac3-1f3ff","🇸🇰":"1f1f8-1f1f0","🇸🇯":"1f1f8-1f1ef","🇸🇮":"1f1f8-1f1ee","🇸🇭":"1f1f8-1f1ed","🇸🇬":"1f1f8-1f1ec","🛏️":"1f6cf","🇸🇪":"1f1f8-1f1ea","🇸🇩":"1f1f8-1f1e9","🇸🇨":"1f1f8-1f1e8","🇸🇧":"1f1f8-1f1e7","🇸🇦":"1f1f8-1f1e6","🧜🏿":"1f9dc-1f3ff","🇷🇼":"1f1f7-1f1fc","🇷🇺":"1f1f7-1f1fa","🇷🇸":"1f1f7-1f1f8","🇷🇴":"1f1f7-1f1f4","🇷🇪":"1f1f7-1f1ea","🫄🏻":"1fac4-1f3fb","🇶🇦":"1f1f6-1f1e6","🇵🇾":"1f1f5-1f1fe","🇵🇼":"1f1f5-1f1fc","🇵🇹":"1f1f5-1f1f9","🇵🇸":"1f1f5-1f1f8","🫄🏼":"1fac4-1f3fc","👩🏻":"1f469-1f3fb","👩🏼":"1f469-1f3fc","👩🏽":"1f469-1f3fd","👩🏾":"1f469-1f3fe","👩🏿":"1f469-1f3ff","🫄🏽":"1fac4-1f3fd","🇵🇷":"1f1f5-1f1f7","🇵🇳":"1f1f5-1f1f3","🇵🇲":"1f1f5-1f1f2","🇵🇱":"1f1f5-1f1f1","🇵🇰":"1f1f5-1f1f0","🫄🏾":"1fac4-1f3fe","🇵🇭":"1f1f5-1f1ed","🇵🇬":"1f1f5-1f1ec","🇵🇫":"1f1f5-1f1eb","🇵🇪":"1f1f5-1f1ea","🇵🇦":"1f1f5-1f1e6","🫄🏿":"1fac4-1f3ff","🇴🇲":"1f1f4-1f1f2","🇳🇿":"1f1f3-1f1ff","🇳🇺":"1f1f3-1f1fa","🇳🇷":"1f1f3-1f1f7","🇳🇵":"1f1f3-1f1f5","🏇🏽":"1f3c7-1f3fd","🇳🇴":"1f1f3-1f1f4","🇳🇱":"1f1f3-1f1f1","🇳🇮":"1f1f3-1f1ee","🇳🇬":"1f1f3-1f1ec","🇳🇫":"1f1f3-1f1eb","🤱🏻":"1f931-1f3fb","🇳🇪":"1f1f3-1f1ea","🇳🇨":"1f1f3-1f1e8","🇳🇦":"1f1f3-1f1e6","🇲🇿":"1f1f2-1f1ff","🇲🇾":"1f1f2-1f1fe","☺️":"263a","🇲🇽":"1f1f2-1f1fd","🇲🇼":"1f1f2-1f1fc","🇲🇻":"1f1f2-1f1fb","🇲🇺":"1f1f2-1f1fa","🇲🇹":"1f1f2-1f1f9","🤱🏼":"1f931-1f3fc","🇲🇸":"1f1f2-1f1f8","🇲🇷":"1f1f2-1f1f7","🇲🇶":"1f1f2-1f1f6","🇲🇵":"1f1f2-1f1f5","🇲🇴":"1f1f2-1f1f4","🤱🏽":"1f931-1f3fd","🇲🇳":"1f1f2-1f1f3","🇲🇲":"1f1f2-1f1f2","🇲🇱":"1f1f2-1f1f1","🇲🇰":"1f1f2-1f1f0","🇲🇭":"1f1f2-1f1ed","🇲🇬":"1f1f2-1f1ec","🤱🏾":"1f931-1f3fe","🇲🇫":"1f1f2-1f1eb","🇲🇪":"1f1f2-1f1ea","🇲🇩":"1f1f2-1f1e9","🇲🇨":"1f1f2-1f1e8","🇲🇦":"1f1f2-1f1e6","🇱🇾":"1f1f1-1f1fe","🇱🇻":"1f1f1-1f1fb","🇱🇺":"1f1f1-1f1fa","🇱🇹":"1f1f1-1f1f9","🇱🇸":"1f1f1-1f1f8","🇱🇷":"1f1f1-1f1f7","🤱🏿":"1f931-1f3ff","🇱🇰":"1f1f1-1f1f0","🇱🇮":"1f1f1-1f1ee","🇱🇨":"1f1f1-1f1e8","🇱🇧":"1f1f1-1f1e7","🇱🇦":"1f1f1-1f1e6","🇰🇿":"1f1f0-1f1ff","🇰🇾":"1f1f0-1f1fe","🇰🇼":"1f1f0-1f1fc","🇰🇷":"1f1f0-1f1f7","🇰🇵":"1f1f0-1f1f5","🏗️":"1f3d7","🧓🏻":"1f9d3-1f3fb","🧓🏼":"1f9d3-1f3fc","🧓🏽":"1f9d3-1f3fd","🧓🏾":"1f9d3-1f3fe","🧓🏿":"1f9d3-1f3ff","🏇🏾":"1f3c7-1f3fe","👴🏻":"1f474-1f3fb","👴🏼":"1f474-1f3fc","👴🏽":"1f474-1f3fd","👴🏾":"1f474-1f3fe","👴🏿":"1f474-1f3ff","🧎🏻":"1f9ce-1f3fb","👵🏻":"1f475-1f3fb","👵🏼":"1f475-1f3fc","👵🏽":"1f475-1f3fd","👵🏾":"1f475-1f3fe","👵🏿":"1f475-1f3ff","🧎🏼":"1f9ce-1f3fc","🙍🏻":"1f64d-1f3fb","🙍🏼":"1f64d-1f3fc","🙍🏽":"1f64d-1f3fd","🙍🏾":"1f64d-1f3fe","🙍🏿":"1f64d-1f3ff","🇰🇳":"1f1f0-1f1f3","🎖️":"1f396","🇰🇲":"1f1f0-1f1f2","🇰🇮":"1f1f0-1f1ee","🇰🇭":"1f1f0-1f1ed","🇰🇬":"1f1f0-1f1ec","🇰🇪":"1f1f0-1f1ea","🇯🇵":"1f1ef-1f1f5","🇯🇴":"1f1ef-1f1f4","🇯🇲":"1f1ef-1f1f2","🇯🇪":"1f1ef-1f1ea","🇮🇹":"1f1ee-1f1f9","🇮🇸":"1f1ee-1f1f8","🧎🏽":"1f9ce-1f3fd","🇮🇷":"1f1ee-1f1f7","🇮🇶":"1f1ee-1f1f6","🇮🇴":"1f1ee-1f1f4","🇮🇳":"1f1ee-1f1f3","🇮🇲":"1f1ee-1f1f2","🇮🇱":"1f1ee-1f1f1","🇮🇪":"1f1ee-1f1ea","🇮🇩":"1f1ee-1f1e9","🇮🇨":"1f1ee-1f1e8","🇭🇺":"1f1ed-1f1fa","🧎🏾":"1f9ce-1f3fe","🙎🏻":"1f64e-1f3fb","🙎🏼":"1f64e-1f3fc","🙎🏽":"1f64e-1f3fd","🙎🏾":"1f64e-1f3fe","🙎🏿":"1f64e-1f3ff","🇭🇹":"1f1ed-1f1f9","🎟️":"1f39f","🇭🇷":"1f1ed-1f1f7","🇭🇳":"1f1ed-1f1f3","🇭🇲":"1f1ed-1f1f2","🇭🇰":"1f1ed-1f1f0","🇬🇾":"1f1ec-1f1fe","🇬🇼":"1f1ec-1f1fc","🇬🇺":"1f1ec-1f1fa","🇬🇹":"1f1ec-1f1f9","🇬🇸":"1f1ec-1f1f8","🇬🇷":"1f1ec-1f1f7","🇬🇶":"1f1ec-1f1f6","⚗️":"2697","🇬🇵":"1f1ec-1f1f5","🇬🇳":"1f1ec-1f1f3","🇬🇲":"1f1ec-1f1f2","🇬🇱":"1f1ec-1f1f1","🇬🇮":"1f1ec-1f1ee","🇬🇭":"1f1ec-1f1ed","🇬🇬":"1f1ec-1f1ec","🇬🇫":"1f1ec-1f1eb","🇬🇪":"1f1ec-1f1ea","🇬🇩":"1f1ec-1f1e9","🧎🏿":"1f9ce-1f3ff","🙅🏻":"1f645-1f3fb","🙅🏼":"1f645-1f3fc","🙅🏽":"1f645-1f3fd","🙅🏾":"1f645-1f3fe","🙅🏿":"1f645-1f3ff","🇬🇧":"1f1ec-1f1e7","🎗️":"1f397","🇬🇦":"1f1ec-1f1e6","🇫🇷":"1f1eb-1f1f7","🇫🇴":"1f1eb-1f1f4","🇫🇲":"1f1eb-1f1f2","🇫🇰":"1f1eb-1f1f0","🇫🇯":"1f1eb-1f1ef","🇫🇮":"1f1eb-1f1ee","🇪🇺":"1f1ea-1f1fa","🇪🇹":"1f1ea-1f1f9","🇪🇸":"1f1ea-1f1f8","🇪🇷":"1f1ea-1f1f7","🏛️":"1f3db","🇪🇭":"1f1ea-1f1ed","🇪🇬":"1f1ea-1f1ec","🇪🇪":"1f1ea-1f1ea","🇪🇨":"1f1ea-1f1e8","🇪🇦":"1f1ea-1f1e6","🇩🇿":"1f1e9-1f1ff","🇩🇴":"1f1e9-1f1f4","🇩🇲":"1f1e9-1f1f2","🇩🇰":"1f1e9-1f1f0","🇩🇯":"1f1e9-1f1ef","🏇🏿":"1f3c7-1f3ff","🙆🏻":"1f646-1f3fb","🙆🏼":"1f646-1f3fc","🙆🏽":"1f646-1f3fd","🙆🏾":"1f646-1f3fe","🙆🏿":"1f646-1f3ff","🇩🇬":"1f1e9-1f1ec","🏟️":"1f3df","🇩🇪":"1f1e9-1f1ea","🇨🇿":"1f1e8-1f1ff","🇨🇾":"1f1e8-1f1fe","🇨🇽":"1f1e8-1f1fd","🇨🇼":"1f1e8-1f1fc","🇨🇻":"1f1e8-1f1fb","🇨🇺":"1f1e8-1f1fa","🇨🇷":"1f1e8-1f1f7","🇨🇵":"1f1e8-1f1f5","🇨🇴":"1f1e8-1f1f4","🇨🇳":"1f1e8-1f1f3","⛓️":"26d3","🇨🇲":"1f1e8-1f1f2","🇨🇱":"1f1e8-1f1f1","🇨🇰":"1f1e8-1f1f0","🇨🇮":"1f1e8-1f1ee","🇨🇭":"1f1e8-1f1ed","🇨🇬":"1f1e8-1f1ec","🇨🇫":"1f1e8-1f1eb","🇨🇩":"1f1e8-1f1e9","🇨🇨":"1f1e8-1f1e8","🇨🇦":"1f1e8-1f1e6","🕊️":"1f54a","💁🏻":"1f481-1f3fb","💁🏼":"1f481-1f3fc","💁🏽":"1f481-1f3fd","💁🏾":"1f481-1f3fe","💁🏿":"1f481-1f3ff","🇧🇿":"1f1e7-1f1ff","🏞️":"1f3de","🇧🇾":"1f1e7-1f1fe","🇧🇼":"1f1e7-1f1fc","🇧🇻":"1f1e7-1f1fb","🇧🇹":"1f1e7-1f1f9","🇧🇸":"1f1e7-1f1f8","🇧🇷":"1f1e7-1f1f7","🇧🇶":"1f1e7-1f1f6","🇧🇴":"1f1e7-1f1f4","🇧🇳":"1f1e7-1f1f3","🇧🇲":"1f1e7-1f1f2","🇧🇱":"1f1e7-1f1f1","⛷️":"26f7","🇧🇯":"1f1e7-1f1ef","🇧🇮":"1f1e7-1f1ee","🇧🇭":"1f1e7-1f1ed","🇧🇬":"1f1e7-1f1ec","🇧🇫":"1f1e7-1f1eb","🇧🇪":"1f1e7-1f1ea","🇧🇩":"1f1e7-1f1e9","🇧🇧":"1f1e7-1f1e7","🇧🇦":"1f1e7-1f1e6","🇦🇿":"1f1e6-1f1ff","🏝️":"1f3dd","🙋🏻":"1f64b-1f3fb","🙋🏼":"1f64b-1f3fc","🙋🏽":"1f64b-1f3fd","🙋🏾":"1f64b-1f3fe","🙋🏿":"1f64b-1f3ff","🇦🇽":"1f1e6-1f1fd","⚖️":"2696","🇦🇼":"1f1e6-1f1fc","🇦🇺":"1f1e6-1f1fa","🇦🇹":"1f1e6-1f1f9","🇦🇸":"1f1e6-1f1f8","🇦🇷":"1f1e6-1f1f7","🇦🇶":"1f1e6-1f1f6","🇦🇴":"1f1e6-1f1f4","🇦🇲":"1f1e6-1f1f2","🇦🇱":"1f1e6-1f1f1","🇦🇮":"1f1e6-1f1ee","🇦🇬":"1f1e6-1f1ec","🏌🏽":"1f3cc-1f3fd","🇦🇫":"1f1e6-1f1eb","🇦🇪":"1f1e6-1f1ea","🇦🇩":"1f1e6-1f1e9","🇦🇨":"1f1e6-1f1e8","🗜️":"1f5dc","🏳️":"1f3f3","🧖🏻":"1f9d6-1f3fb","👼🏻":"1f47c-1f3fb","⚙️":"2699","👼🏼":"1f47c-1f3fc","👼🏽":"1f47c-1f3fd","🧏🏻":"1f9cf-1f3fb","🧏🏼":"1f9cf-1f3fc","🧏🏽":"1f9cf-1f3fd","🧏🏾":"1f9cf-1f3fe","🧏🏿":"1f9cf-1f3ff","👼🏾":"1f47c-1f3fe","👼🏿":"1f47c-1f3ff","🏜️":"1f3dc","🎅🏻":"1f385-1f3fb","🛡️":"1f6e1","🎅🏼":"1f385-1f3fc","🎅🏽":"1f385-1f3fd","🎅🏾":"1f385-1f3fe","🎅🏿":"1f385-1f3ff","🕰️":"1f570","⚔️":"2694","🤶🏻":"1f936-1f3fb","▫️":"25ab","🗡️":"1f5e1","🤶🏼":"1f936-1f3fc","▪️":"25aa","🤶🏽":"1f936-1f3fd","🛠️":"1f6e0","🤶🏾":"1f936-1f3fe","◻️":"25fb","⚒️":"2692","◼️":"25fc","🤶🏿":"1f936-1f3ff","🇾🇹":"1f1fe-1f1f9","⛏️":"26cf","🙇🏻":"1f647-1f3fb","🙇🏼":"1f647-1f3fc","🙇🏽":"1f647-1f3fd","🙇🏾":"1f647-1f3fe","🙇🏿":"1f647-1f3ff","⏲️":"23f2","🏖️":"1f3d6","⛑️":"26d1","🗝️":"1f5dd","🧝🏻":"1f9dd-1f3fb","👮🏻":"1f46e-1f3fb","👮🏼":"1f46e-1f3fc","👮🏽":"1f46e-1f3fd","👮🏾":"1f46e-1f3fe","👮🏿":"1f46e-1f3ff","🧝🏼":"1f9dd-1f3fc","🧝🏽":"1f9dd-1f3fd","🦸🏻":"1f9b8-1f3fb","🦸🏼":"1f9b8-1f3fc","🦸🏽":"1f9b8-1f3fd","🦸🏾":"1f9b8-1f3fe","🦸🏿":"1f9b8-1f3ff","🗑️":"1f5d1","🧝🏾":"1f9dd-1f3fe","🗄️":"1f5c4","🧝🏿":"1f9dd-1f3ff","㊙️":"3299","🗃️":"1f5c3","㊗️":"3297","⏱️":"23f1","🤦🏻":"1f926-1f3fb","🤦🏼":"1f926-1f3fc","🤦🏽":"1f926-1f3fd","🤦🏾":"1f926-1f3fe","🤦🏿":"1f926-1f3ff","🧖🏼":"1f9d6-1f3fc","✂️":"2702","🏕️":"1f3d5","🧖🏽":"1f9d6-1f3fd","🧖🏾":"1f9d6-1f3fe","🖇️":"1f587","🧖🏿":"1f9d6-1f3ff","⛰️":"26f0","🛎️":"1f6ce","❣️":"2763","🏔️":"1f3d4","🕵️":"1f575","🈷️":"1f237","🐿️":"1f43f","🕵🏻":"1f575-1f3fb","🈂️":"1f202","🕵🏼":"1f575-1f3fc","🕵🏽":"1f575-1f3fd","🕵🏾":"1f575-1f3fe","🕵🏿":"1f575-1f3ff","☄️":"2604","🅿️":"1f17f","🛰️":"1f6f0","🌶️":"1f336","🏌🏾":"1f3cc-1f3fe","🤷🏻":"1f937-1f3fb","🤷🏼":"1f937-1f3fc","🤷🏽":"1f937-1f3fd","🤷🏾":"1f937-1f3fe","🤷🏿":"1f937-1f3ff","🅾️":"1f17e","☃️":"2603","🗓️":"1f5d3","🛍️":"1f6cd","🗒️":"1f5d2","Ⓜ️":"24c2","🗺️":"1f5fa","❄️":"2744","ℹ️":"2139","🧗🏻":"1f9d7-1f3fb","🗂️":"1f5c2","🏵️":"1f3f5","⛱️":"26f1","🇿🇼":"1f1ff-1f1fc","🅱️":"1f171","🧗🏼":"1f9d7-1f3fc","🦹🏻":"1f9b9-1f3fb","🅰️":"1f170","🦹🏼":"1f9b9-1f3fc","🦹🏽":"1f9b9-1f3fd","🖍️":"1f58d","🦹🏾":"1f9b9-1f3fe","🖌️":"1f58c","🦹🏿":"1f9b9-1f3ff","9⃣":"39-20e3","🖊️":"1f58a","8⃣":"38-20e3","7⃣":"37-20e3","6⃣":"36-20e3","5⃣":"35-20e3","4⃣":"34-20e3","3⃣":"33-20e3","2⃣":"32-20e3","1⃣":"31-20e3","0⃣":"30-20e3","*⃣":"2a-20e3","#⃣":"23-20e3","❤️":"2764","🖋️":"1f58b","™️":"2122","☂️":"2602","®️":"ae","✒️":"2712","©️":"a9","♨️":"2668","❇️":"2747","🧗🏽":"1f9d7-1f3fd","✴️":"2734","💂🏻":"1f482-1f3fb","💂🏼":"1f482-1f3fc","✳️":"2733","💂🏽":"1f482-1f3fd","〽️":"303d","💂🏾":"1f482-1f3fe","💂🏿":"1f482-1f3ff","✏️":"270f","🛩️":"1f6e9","🧗🏾":"1f9d7-1f3fe","✔️":"2714","🗳️":"1f5f3","✈️":"2708","☑️":"2611","🧗🏿":"1f9d7-1f3ff","🏂🏻":"1f3c2-1f3fb","🛥️":"1f6e5","🏂🏼":"1f3c2-1f3fc","⛴️":"26f4","🏂🏽":"1f3c2-1f3fd","☠️":"2620","⚜️":"269c","🛳️":"1f6f3","♻️":"267b","💆🏻":"1f486-1f3fb","💆🏼":"1f486-1f3fc","⚕️":"2695","💆🏽":"1f486-1f3fd","💆🏾":"1f486-1f3fe","✉️":"2709","💆🏿":"1f486-1f3ff","〰️":"3030","🕶️":"1f576","🌬️":"1f32c","🏙️":"1f3d9","🏂🏾":"1f3c2-1f3fe","🌫️":"1f32b","🧙🏻":"1f9d9-1f3fb","⁉️":"2049","🥷🏻":"1f977-1f3fb","‼️":"203c","🥷🏼":"1f977-1f3fc","🇿🇲":"1f1ff-1f1f2","♾️":"267e","🥷🏽":"1f977-1f3fd","🥷🏾":"1f977-1f3fe","🥷🏿":"1f977-1f3ff","🧙🏼":"1f9d9-1f3fc","👷🏻":"1f477-1f3fb","👷🏼":"1f477-1f3fc","✖️":"2716","👷🏽":"1f477-1f3fd","⚧️":"26a7","👷🏾":"1f477-1f3fe","♂️":"2642","👷🏿":"1f477-1f3ff","♀️":"2640","🧙🏽":"1f9d9-1f3fd","🧙🏾":"1f9d9-1f3fe","🧙🏿":"1f9d9-1f3ff","🍽️":"1f37d","🇿🇦":"1f1ff-1f1e6","🌪️":"1f32a","🏂🏿":"1f3c2-1f3ff","🏷️":"1f3f7","🌩️":"1f329","⏏️":"23cf","🏌️":"1f3cc","⏺️":"23fa","🖼️":"1f5bc","⏹️":"23f9","🗞️":"1f5de","⏸️":"23f8","🌨️":"1f328","🕸️":"1f578","🌧️":"1f327","🏌🏿":"1f3cc-1f3ff","⛩️":"26e9","♟️":"265f","⏮️":"23ee","🌦️":"1f326","🕷️":"1f577","◀️":"25c0","♣️":"2663","🌥️":"1f325","⏯️":"23ef","♦️":"2666","⏭️":"23ed","🏌🏻":"1f3cc-1f3fb","♥️":"2665","🌤️":"1f324","▶️":"25b6","🫅🏻":"1fac5-1f3fb","🫅🏼":"1fac5-1f3fc","🫅🏽":"1fac5-1f3fd","🫅🏾":"1fac5-1f3fe","🫅🏿":"1fac5-1f3ff","♠️":"2660","🤴🏻":"1f934-1f3fb","🤴🏼":"1f934-1f3fc","🤴🏽":"1f934-1f3fd","🤴🏾":"1f934-1f3fe","🤴🏿":"1f934-1f3ff","🏌🏼":"1f3cc-1f3fc","👸🏻":"1f478-1f3fb","👸🏼":"1f478-1f3fc","👸🏽":"1f478-1f3fd","👸🏾":"1f478-1f3fe","👸🏿":"1f478-1f3ff","⛈️":"26c8","👳🏻":"1f473-1f3fb","👳🏼":"1f473-1f3fc","👳🏽":"1f473-1f3fd","👳🏾":"1f473-1f3fe","👳🏿":"1f473-1f3ff","💃🏻":"1f483-1f3fb","☮️":"262e","💃🏼":"1f483-1f3fc","☪️":"262a","🧚🏻":"1f9da-1f3fb","🧚🏼":"1f9da-1f3fc","☦️":"2626","🧚🏽":"1f9da-1f3fd","✝️":"271d","🧚🏾":"1f9da-1f3fe","🏄🏻":"1f3c4-1f3fb","👩‍🦱":"1f469-200d-1f9b1","😶‍🌫":"1f636-200d-1f32b-fe0f","😮‍💨":"1f62e-200d-1f4a8","🙂‍↔":"1f642-200d-2194-fe0f","🙂‍↕":"1f642-200d-2195-fe0f","😵‍💫":"1f635-200d-1f4ab","❤‍🔥":"2764-fe0f-200d-1f525","❤‍🩹":"2764-fe0f-200d-1fa79","👁‍🗨":"1f441-200d-1f5e8","🧔‍♂":"1f9d4-200d-2642-fe0f","🧔‍♀":"1f9d4-200d-2640-fe0f","👨‍🦰":"1f468-200d-1f9b0","👨‍🦱":"1f468-200d-1f9b1","👨‍🦳":"1f468-200d-1f9b3","👨‍🦲":"1f468-200d-1f9b2","👩‍🦰":"1f469-200d-1f9b0","🧑‍🦰":"1f9d1-200d-1f9b0","🧑‍🦱":"1f9d1-200d-1f9b1","👩‍🦳":"1f469-200d-1f9b3","🧑‍🦳":"1f9d1-200d-1f9b3","👩‍🦲":"1f469-200d-1f9b2","🧑‍🦲":"1f9d1-200d-1f9b2","👱‍♀":"1f471-200d-2640-fe0f","👱‍♂":"1f471-200d-2642-fe0f","🙍‍♂":"1f64d-200d-2642-fe0f","🙍‍♀":"1f64d-200d-2640-fe0f","🙎‍♂":"1f64e-200d-2642-fe0f","🙎‍♀":"1f64e-200d-2640-fe0f","🙅‍♂":"1f645-200d-2642-fe0f","🙅‍♀":"1f645-200d-2640-fe0f","🙆‍♂":"1f646-200d-2642-fe0f","🙆‍♀":"1f646-200d-2640-fe0f","💁‍♂":"1f481-200d-2642-fe0f","💁‍♀":"1f481-200d-2640-fe0f","🙋‍♂":"1f64b-200d-2642-fe0f","🙋‍♀":"1f64b-200d-2640-fe0f","🧏‍♂":"1f9cf-200d-2642-fe0f","🧏‍♀":"1f9cf-200d-2640-fe0f","🙇‍♂":"1f647-200d-2642-fe0f","🙇‍♀":"1f647-200d-2640-fe0f","🤦‍♂":"1f926-200d-2642-fe0f","🤦‍♀":"1f926-200d-2640-fe0f","🤷‍♂":"1f937-200d-2642-fe0f","🤷‍♀":"1f937-200d-2640-fe0f","🧑‍⚕":"1f9d1-200d-2695-fe0f","👨‍⚕":"1f468-200d-2695-fe0f","👩‍⚕":"1f469-200d-2695-fe0f","🧑‍🎓":"1f9d1-200d-1f393","👨‍🎓":"1f468-200d-1f393","👩‍🎓":"1f469-200d-1f393","🧑‍🏫":"1f9d1-200d-1f3eb","👨‍🏫":"1f468-200d-1f3eb","👩‍🏫":"1f469-200d-1f3eb","🧑‍⚖":"1f9d1-200d-2696-fe0f","👨‍⚖":"1f468-200d-2696-fe0f","👩‍⚖":"1f469-200d-2696-fe0f","🧑‍🌾":"1f9d1-200d-1f33e","👨‍🌾":"1f468-200d-1f33e","👩‍🌾":"1f469-200d-1f33e","🧑‍🍳":"1f9d1-200d-1f373","👨‍🍳":"1f468-200d-1f373","👩‍🍳":"1f469-200d-1f373","🧑‍🔧":"1f9d1-200d-1f527","👨‍🔧":"1f468-200d-1f527","👩‍🔧":"1f469-200d-1f527","🧑‍🏭":"1f9d1-200d-1f3ed","👨‍🏭":"1f468-200d-1f3ed","👩‍🏭":"1f469-200d-1f3ed","🧑‍💼":"1f9d1-200d-1f4bc","👨‍💼":"1f468-200d-1f4bc","👩‍💼":"1f469-200d-1f4bc","🧑‍🔬":"1f9d1-200d-1f52c","👨‍🔬":"1f468-200d-1f52c","👩‍🔬":"1f469-200d-1f52c","🧑‍💻":"1f9d1-200d-1f4bb","👨‍💻":"1f468-200d-1f4bb","👩‍💻":"1f469-200d-1f4bb","🧑‍🎤":"1f9d1-200d-1f3a4","👨‍🎤":"1f468-200d-1f3a4","👩‍🎤":"1f469-200d-1f3a4","🧑‍🎨":"1f9d1-200d-1f3a8","👨‍🎨":"1f468-200d-1f3a8","👩‍🎨":"1f469-200d-1f3a8","🧑‍✈":"1f9d1-200d-2708-fe0f","👨‍✈":"1f468-200d-2708-fe0f","👩‍✈":"1f469-200d-2708-fe0f","🧑‍🚀":"1f9d1-200d-1f680","👨‍🚀":"1f468-200d-1f680","👩‍🚀":"1f469-200d-1f680","🧑‍🚒":"1f9d1-200d-1f692","👨‍🚒":"1f468-200d-1f692","👩‍🚒":"1f469-200d-1f692","👮‍♂":"1f46e-200d-2642-fe0f","👮‍♀":"1f46e-200d-2640-fe0f","🕵‍♂":"1f575-fe0f-200d-2642-fe0f","🕵‍♀":"1f575-fe0f-200d-2640-fe0f","💂‍♂":"1f482-200d-2642-fe0f","💂‍♀":"1f482-200d-2640-fe0f","👷‍♂":"1f477-200d-2642-fe0f","👷‍♀":"1f477-200d-2640-fe0f","👳‍♂":"1f473-200d-2642-fe0f","👳‍♀":"1f473-200d-2640-fe0f","🤵‍♂":"1f935-200d-2642-fe0f","🤵‍♀":"1f935-200d-2640-fe0f","👰‍♂":"1f470-200d-2642-fe0f","👰‍♀":"1f470-200d-2640-fe0f","👩‍🍼":"1f469-200d-1f37c","👨‍🍼":"1f468-200d-1f37c","🧑‍🍼":"1f9d1-200d-1f37c","🧑‍🎄":"1f9d1-200d-1f384","🦸‍♂":"1f9b8-200d-2642-fe0f","🦸‍♀":"1f9b8-200d-2640-fe0f","🦹‍♂":"1f9b9-200d-2642-fe0f","🦹‍♀":"1f9b9-200d-2640-fe0f","🧙‍♂":"1f9d9-200d-2642-fe0f","🧙‍♀":"1f9d9-200d-2640-fe0f","🧚‍♂":"1f9da-200d-2642-fe0f","🧚‍♀":"1f9da-200d-2640-fe0f","🧛‍♂":"1f9db-200d-2642-fe0f","🧛‍♀":"1f9db-200d-2640-fe0f","🧜‍♂":"1f9dc-200d-2642-fe0f","🧜‍♀":"1f9dc-200d-2640-fe0f","🧝‍♂":"1f9dd-200d-2642-fe0f","🧝‍♀":"1f9dd-200d-2640-fe0f","🧞‍♂":"1f9de-200d-2642-fe0f","🧞‍♀":"1f9de-200d-2640-fe0f","🧟‍♂":"1f9df-200d-2642-fe0f","🧟‍♀":"1f9df-200d-2640-fe0f","💆‍♂":"1f486-200d-2642-fe0f","💆‍♀":"1f486-200d-2640-fe0f","💇‍♂":"1f487-200d-2642-fe0f","💇‍♀":"1f487-200d-2640-fe0f","🚶‍♂":"1f6b6-200d-2642-fe0f","🚶‍♀":"1f6b6-200d-2640-fe0f","🚶‍➡":"1f6b6-200d-27a1-fe0f","🧍‍♂":"1f9cd-200d-2642-fe0f","🧍‍♀":"1f9cd-200d-2640-fe0f","🧎‍♂":"1f9ce-200d-2642-fe0f","🧎‍♀":"1f9ce-200d-2640-fe0f","🧎‍➡":"1f9ce-200d-27a1-fe0f","🧑‍🦯":"1f9d1-200d-1f9af","👨‍🦯":"1f468-200d-1f9af","👩‍🦯":"1f469-200d-1f9af","🧑‍🦼":"1f9d1-200d-1f9bc","👨‍🦼":"1f468-200d-1f9bc","👩‍🦼":"1f469-200d-1f9bc","🧑‍🦽":"1f9d1-200d-1f9bd","👨‍🦽":"1f468-200d-1f9bd","👩‍🦽":"1f469-200d-1f9bd","🏃‍♂":"1f3c3-200d-2642-fe0f","🏃‍♀":"1f3c3-200d-2640-fe0f","🏃‍➡":"1f3c3-200d-27a1-fe0f","👯‍♂":"1f46f-200d-2642-fe0f","👯‍♀":"1f46f-200d-2640-fe0f","🧖‍♂":"1f9d6-200d-2642-fe0f","🧖‍♀":"1f9d6-200d-2640-fe0f","🧗‍♂":"1f9d7-200d-2642-fe0f","🧗‍♀":"1f9d7-200d-2640-fe0f","🏌‍♂":"1f3cc-fe0f-200d-2642-fe0f","🏌‍♀":"1f3cc-fe0f-200d-2640-fe0f","🏄‍♂":"1f3c4-200d-2642-fe0f","🏄‍♀":"1f3c4-200d-2640-fe0f","🚣‍♂":"1f6a3-200d-2642-fe0f","🚣‍♀":"1f6a3-200d-2640-fe0f","🏊‍♂":"1f3ca-200d-2642-fe0f","🏊‍♀":"1f3ca-200d-2640-fe0f","⛹‍♂":"26f9-fe0f-200d-2642-fe0f","⛹‍♀":"26f9-fe0f-200d-2640-fe0f","🏋‍♂":"1f3cb-fe0f-200d-2642-fe0f","🏋‍♀":"1f3cb-fe0f-200d-2640-fe0f","🚴‍♂":"1f6b4-200d-2642-fe0f","🚴‍♀":"1f6b4-200d-2640-fe0f","🚵‍♂":"1f6b5-200d-2642-fe0f","🚵‍♀":"1f6b5-200d-2640-fe0f","🤸‍♂":"1f938-200d-2642-fe0f","🤸‍♀":"1f938-200d-2640-fe0f","🤼‍♂":"1f93c-200d-2642-fe0f","🤼‍♀":"1f93c-200d-2640-fe0f","🤽‍♂":"1f93d-200d-2642-fe0f","🤽‍♀":"1f93d-200d-2640-fe0f","🤾‍♂":"1f93e-200d-2642-fe0f","🤾‍♀":"1f93e-200d-2640-fe0f","🤹‍♂":"1f939-200d-2642-fe0f","🤹‍♀":"1f939-200d-2640-fe0f","🧘‍♂":"1f9d8-200d-2642-fe0f","🧘‍♀":"1f9d8-200d-2640-fe0f","👨‍👦":"1f468-200d-1f466","👨‍👧":"1f468-200d-1f467","👩‍👦":"1f469-200d-1f466","👩‍👧":"1f469-200d-1f467","🧑‍🧒":"1f9d1-200d-1f9d2","🐕‍🦺":"1f415-200d-1f9ba","🐈‍⬛":"1f408-200d-2b1b","🐻‍❄":"1f43b-200d-2744-fe0f","🐦‍⬛":"1f426-200d-2b1b","🐦‍🔥":"1f426-200d-1f525","🍋‍🟩":"1f34b-200d-1f7e9","🍄‍🟫":"1f344-200d-1f7eb","⛓‍💥":"26d3-fe0f-200d-1f4a5","#️⃣":"23-20e3","*️⃣":"2a-20e3","0️⃣":"30-20e3","1️⃣":"31-20e3","2️⃣":"32-20e3","3️⃣":"33-20e3","4️⃣":"34-20e3","5️⃣":"35-20e3","6️⃣":"36-20e3","7️⃣":"37-20e3","8️⃣":"38-20e3","9️⃣":"39-20e3","🏳‍🌈":"1f3f3-fe0f-200d-1f308","🏳‍⚧":"1f3f3-fe0f-200d-26a7-fe0f","🏴‍☠":"1f3f4-200d-2620-fe0f","👨🏾‍🍼":"1f468-1f3fe-200d-1f37c","🧑🏻‍🍼":"1f9d1-1f3fb-200d-1f37c","🧑🏼‍🍼":"1f9d1-1f3fc-200d-1f37c","🧑🏽‍🍼":"1f9d1-1f3fd-200d-1f37c","🧑🏾‍🍼":"1f9d1-1f3fe-200d-1f37c","🧑🏿‍🍼":"1f9d1-1f3ff-200d-1f37c","🧑🏻‍🎄":"1f9d1-1f3fb-200d-1f384","🤽🏽‍♀":"1f93d-1f3fd-200d-2640-fe0f","🧑🏼‍🎄":"1f9d1-1f3fc-200d-1f384","👨🏿‍🦼":"1f468-1f3ff-200d-1f9bc","👨🏾‍🦼":"1f468-1f3fe-200d-1f9bc","👨🏽‍🦼":"1f468-1f3fd-200d-1f9bc","👨🏼‍🦼":"1f468-1f3fc-200d-1f9bc","👨🏻‍🦼":"1f468-1f3fb-200d-1f9bc","🧑🏽‍🎄":"1f9d1-1f3fd-200d-1f384","🧑🏾‍🎄":"1f9d1-1f3fe-200d-1f384","🧑🏿‍🎄":"1f9d1-1f3ff-200d-1f384","🦸‍♂️":"1f9b8-200d-2642-fe0f","🦸🏻‍♂":"1f9b8-1f3fb-200d-2642-fe0f","🦸🏼‍♂":"1f9b8-1f3fc-200d-2642-fe0f","🦸🏽‍♂":"1f9b8-1f3fd-200d-2642-fe0f","🦸🏾‍♂":"1f9b8-1f3fe-200d-2642-fe0f","🦸🏿‍♂":"1f9b8-1f3ff-200d-2642-fe0f","🦸‍♀️":"1f9b8-200d-2640-fe0f","🤸🏼‍♂":"1f938-1f3fc-200d-2642-fe0f","🦸🏻‍♀":"1f9b8-1f3fb-200d-2640-fe0f","🧑🏿‍🦼":"1f9d1-1f3ff-200d-1f9bc","🧑🏾‍🦼":"1f9d1-1f3fe-200d-1f9bc","🧑🏽‍🦼":"1f9d1-1f3fd-200d-1f9bc","🧑🏼‍🦼":"1f9d1-1f3fc-200d-1f9bc","🧑🏻‍🦼":"1f9d1-1f3fb-200d-1f9bc","🦸🏼‍♀":"1f9b8-1f3fc-200d-2640-fe0f","🦸🏽‍♀":"1f9b8-1f3fd-200d-2640-fe0f","🦸🏾‍♀":"1f9b8-1f3fe-200d-2640-fe0f","🦸🏿‍♀":"1f9b8-1f3ff-200d-2640-fe0f","🦹‍♂️":"1f9b9-200d-2642-fe0f","🦹🏻‍♂":"1f9b9-1f3fb-200d-2642-fe0f","🦹🏼‍♂":"1f9b9-1f3fc-200d-2642-fe0f","🦹🏽‍♂":"1f9b9-1f3fd-200d-2642-fe0f","🦹🏾‍♂":"1f9b9-1f3fe-200d-2642-fe0f","🦹🏿‍♂":"1f9b9-1f3ff-200d-2642-fe0f","🚴🏿‍♀":"1f6b4-1f3ff-200d-2640-fe0f","🦹‍♀️":"1f9b9-200d-2640-fe0f","👩🏿‍🦯":"1f469-1f3ff-200d-1f9af","👩🏾‍🦯":"1f469-1f3fe-200d-1f9af","👩🏽‍🦯":"1f469-1f3fd-200d-1f9af","👩🏼‍🦯":"1f469-1f3fc-200d-1f9af","👩🏻‍🦯":"1f469-1f3fb-200d-1f9af","🦹🏻‍♀":"1f9b9-1f3fb-200d-2640-fe0f","🦹🏼‍♀":"1f9b9-1f3fc-200d-2640-fe0f","🦹🏽‍♀":"1f9b9-1f3fd-200d-2640-fe0f","🦹🏾‍♀":"1f9b9-1f3fe-200d-2640-fe0f","🦹🏿‍♀":"1f9b9-1f3ff-200d-2640-fe0f","🧙‍♂️":"1f9d9-200d-2642-fe0f","🧙🏻‍♂":"1f9d9-1f3fb-200d-2642-fe0f","🧙🏼‍♂":"1f9d9-1f3fc-200d-2642-fe0f","🧙🏽‍♂":"1f9d9-1f3fd-200d-2642-fe0f","🧙🏾‍♂":"1f9d9-1f3fe-200d-2642-fe0f","🚴🏾‍♀":"1f6b4-1f3fe-200d-2640-fe0f","🧙🏿‍♂":"1f9d9-1f3ff-200d-2642-fe0f","👨🏿‍🦯":"1f468-1f3ff-200d-1f9af","👨🏾‍🦯":"1f468-1f3fe-200d-1f9af","👨🏽‍🦯":"1f468-1f3fd-200d-1f9af","👨🏼‍🦯":"1f468-1f3fc-200d-1f9af","👨🏻‍🦯":"1f468-1f3fb-200d-1f9af","🧙‍♀️":"1f9d9-200d-2640-fe0f","🧙🏻‍♀":"1f9d9-1f3fb-200d-2640-fe0f","🧙🏼‍♀":"1f9d9-1f3fc-200d-2640-fe0f","🧙🏽‍♀":"1f9d9-1f3fd-200d-2640-fe0f","🧙🏾‍♀":"1f9d9-1f3fe-200d-2640-fe0f","🧙🏿‍♀":"1f9d9-1f3ff-200d-2640-fe0f","🧚‍♂️":"1f9da-200d-2642-fe0f","🧚🏻‍♂":"1f9da-1f3fb-200d-2642-fe0f","🧚🏼‍♂":"1f9da-1f3fc-200d-2642-fe0f","🧚🏽‍♂":"1f9da-1f3fd-200d-2642-fe0f","🚴🏽‍♀":"1f6b4-1f3fd-200d-2640-fe0f","🧚🏾‍♂":"1f9da-1f3fe-200d-2642-fe0f","🧑🏿‍🦯":"1f9d1-1f3ff-200d-1f9af","🧑🏾‍🦯":"1f9d1-1f3fe-200d-1f9af","🧑🏽‍🦯":"1f9d1-1f3fd-200d-1f9af","🧑🏼‍🦯":"1f9d1-1f3fc-200d-1f9af","🧑🏻‍🦯":"1f9d1-1f3fb-200d-1f9af","🧚🏿‍♂":"1f9da-1f3ff-200d-2642-fe0f","🧚‍♀️":"1f9da-200d-2640-fe0f","🧚🏻‍♀":"1f9da-1f3fb-200d-2640-fe0f","🧚🏼‍♀":"1f9da-1f3fc-200d-2640-fe0f","🧚🏽‍♀":"1f9da-1f3fd-200d-2640-fe0f","🧚🏾‍♀":"1f9da-1f3fe-200d-2640-fe0f","🧚🏿‍♀":"1f9da-1f3ff-200d-2640-fe0f","🧛‍♂️":"1f9db-200d-2642-fe0f","🧛🏻‍♂":"1f9db-1f3fb-200d-2642-fe0f","🧛🏼‍♂":"1f9db-1f3fc-200d-2642-fe0f","🧛🏽‍♂":"1f9db-1f3fd-200d-2642-fe0f","🧛🏾‍♂":"1f9db-1f3fe-200d-2642-fe0f","🧛🏿‍♂":"1f9db-1f3ff-200d-2642-fe0f","🧛‍♀️":"1f9db-200d-2640-fe0f","🧛🏻‍♀":"1f9db-1f3fb-200d-2640-fe0f","🧛🏼‍♀":"1f9db-1f3fc-200d-2640-fe0f","🧛🏽‍♀":"1f9db-1f3fd-200d-2640-fe0f","🧛🏾‍♀":"1f9db-1f3fe-200d-2640-fe0f","🧛🏿‍♀":"1f9db-1f3ff-200d-2640-fe0f","🧜‍♂️":"1f9dc-200d-2642-fe0f","🚴🏼‍♀":"1f6b4-1f3fc-200d-2640-fe0f","🧜🏻‍♂":"1f9dc-1f3fb-200d-2642-fe0f","🧜🏼‍♂":"1f9dc-1f3fc-200d-2642-fe0f","🧜🏽‍♂":"1f9dc-1f3fd-200d-2642-fe0f","🧜🏾‍♂":"1f9dc-1f3fe-200d-2642-fe0f","🧜🏿‍♂":"1f9dc-1f3ff-200d-2642-fe0f","🧜‍♀️":"1f9dc-200d-2640-fe0f","🧜🏻‍♀":"1f9dc-1f3fb-200d-2640-fe0f","🧜🏼‍♀":"1f9dc-1f3fc-200d-2640-fe0f","🧜🏽‍♀":"1f9dc-1f3fd-200d-2640-fe0f","🧜🏾‍♀":"1f9dc-1f3fe-200d-2640-fe0f","🧜🏿‍♀":"1f9dc-1f3ff-200d-2640-fe0f","🧝‍♂️":"1f9dd-200d-2642-fe0f","🧝🏻‍♂":"1f9dd-1f3fb-200d-2642-fe0f","🧝🏼‍♂":"1f9dd-1f3fc-200d-2642-fe0f","🧝🏽‍♂":"1f9dd-1f3fd-200d-2642-fe0f","🧝🏾‍♂":"1f9dd-1f3fe-200d-2642-fe0f","🧝🏿‍♂":"1f9dd-1f3ff-200d-2642-fe0f","🧝‍♀️":"1f9dd-200d-2640-fe0f","🧝🏻‍♀":"1f9dd-1f3fb-200d-2640-fe0f","🧝🏼‍♀":"1f9dd-1f3fc-200d-2640-fe0f","🧝🏽‍♀":"1f9dd-1f3fd-200d-2640-fe0f","🧝🏾‍♀":"1f9dd-1f3fe-200d-2640-fe0f","🧝🏿‍♀":"1f9dd-1f3ff-200d-2640-fe0f","🚴🏻‍♀":"1f6b4-1f3fb-200d-2640-fe0f","🧞‍♂️":"1f9de-200d-2642-fe0f","🧞‍♀️":"1f9de-200d-2640-fe0f","🧟‍♂️":"1f9df-200d-2642-fe0f","🧎🏿‍➡":"1f9ce-1f3ff-200d-27a1-fe0f","🧔‍♀️":"1f9d4-200d-2640-fe0f","🧎🏾‍➡":"1f9ce-1f3fe-200d-27a1-fe0f","🚴‍♀️":"1f6b4-200d-2640-fe0f","🧎🏽‍➡":"1f9ce-1f3fd-200d-27a1-fe0f","🚴🏿‍♂":"1f6b4-1f3ff-200d-2642-fe0f","🧎🏼‍➡":"1f9ce-1f3fc-200d-27a1-fe0f","🚴🏾‍♂":"1f6b4-1f3fe-200d-2642-fe0f","🧎🏻‍➡":"1f9ce-1f3fb-200d-27a1-fe0f","🚴🏽‍♂":"1f6b4-1f3fd-200d-2642-fe0f","🧎‍➡️":"1f9ce-200d-27a1-fe0f","🧎🏿‍♀":"1f9ce-1f3ff-200d-2640-fe0f","🚴🏼‍♂":"1f6b4-1f3fc-200d-2642-fe0f","🧎🏾‍♀":"1f9ce-1f3fe-200d-2640-fe0f","🚴🏻‍♂":"1f6b4-1f3fb-200d-2642-fe0f","🧎🏽‍♀":"1f9ce-1f3fd-200d-2640-fe0f","🚴‍♂️":"1f6b4-200d-2642-fe0f","🧎🏼‍♀":"1f9ce-1f3fc-200d-2640-fe0f","🏋🏼‍♂":"1f3cb-1f3fc-200d-2642-fe0f","🧎🏻‍♀":"1f9ce-1f3fb-200d-2640-fe0f","🏋🏽‍♂":"1f3cb-1f3fd-200d-2642-fe0f","🧎‍♀️":"1f9ce-200d-2640-fe0f","🧎🏿‍♂":"1f9ce-1f3ff-200d-2642-fe0f","🏋🏾‍♂":"1f3cb-1f3fe-200d-2642-fe0f","🧎🏾‍♂":"1f9ce-1f3fe-200d-2642-fe0f","🏋🏿‍♀":"1f3cb-1f3ff-200d-2640-fe0f","🧎🏽‍♂":"1f9ce-1f3fd-200d-2642-fe0f","🏋🏾‍♀":"1f3cb-1f3fe-200d-2640-fe0f","🧎🏼‍♂":"1f9ce-1f3fc-200d-2642-fe0f","🏋🏽‍♀":"1f3cb-1f3fd-200d-2640-fe0f","🧎🏻‍♂":"1f9ce-1f3fb-200d-2642-fe0f","🏋🏼‍♀":"1f3cb-1f3fc-200d-2640-fe0f","🧎‍♂️":"1f9ce-200d-2642-fe0f","🧍🏿‍♀":"1f9cd-1f3ff-200d-2640-fe0f","🏋🏻‍♀":"1f3cb-1f3fb-200d-2640-fe0f","🧍🏾‍♀":"1f9cd-1f3fe-200d-2640-fe0f","🧔🏻‍♀":"1f9d4-1f3fb-200d-2640-fe0f","🧍🏽‍♀":"1f9cd-1f3fd-200d-2640-fe0f","🏋️‍♀":"1f3cb-fe0f-200d-2640-fe0f","🧍🏼‍♀":"1f9cd-1f3fc-200d-2640-fe0f","🏋‍♀️":"1f3cb-fe0f-200d-2640-fe0f","🧍🏻‍♀":"1f9cd-1f3fb-200d-2640-fe0f","🏋🏿‍♂":"1f3cb-1f3ff-200d-2642-fe0f","🧍‍♀️":"1f9cd-200d-2640-fe0f","🧍🏿‍♂":"1f9cd-1f3ff-200d-2642-fe0f","🏊🏼‍♂":"1f3ca-1f3fc-200d-2642-fe0f","🧍🏾‍♂":"1f9cd-1f3fe-200d-2642-fe0f","🤽🏾‍♀":"1f93d-1f3fe-200d-2640-fe0f","🧍🏽‍♂":"1f9cd-1f3fd-200d-2642-fe0f","🧔🏼‍♀":"1f9d4-1f3fc-200d-2640-fe0f","🧍🏼‍♂":"1f9cd-1f3fc-200d-2642-fe0f","🧔🏽‍♀":"1f9d4-1f3fd-200d-2640-fe0f","🧍🏻‍♂":"1f9cd-1f3fb-200d-2642-fe0f","🧔🏾‍♀":"1f9d4-1f3fe-200d-2640-fe0f","🧍‍♂️":"1f9cd-200d-2642-fe0f","🧟‍♀️":"1f9df-200d-2640-fe0f","💆‍♂️":"1f486-200d-2642-fe0f","💆🏻‍♂":"1f486-1f3fb-200d-2642-fe0f","💆🏼‍♂":"1f486-1f3fc-200d-2642-fe0f","💆🏽‍♂":"1f486-1f3fd-200d-2642-fe0f","💆🏾‍♂":"1f486-1f3fe-200d-2642-fe0f","💆🏿‍♂":"1f486-1f3ff-200d-2642-fe0f","💆‍♀️":"1f486-200d-2640-fe0f","💆🏻‍♀":"1f486-1f3fb-200d-2640-fe0f","💆🏼‍♀":"1f486-1f3fc-200d-2640-fe0f","💆🏽‍♀":"1f486-1f3fd-200d-2640-fe0f","💆🏾‍♀":"1f486-1f3fe-200d-2640-fe0f","💆🏿‍♀":"1f486-1f3ff-200d-2640-fe0f","💇‍♂️":"1f487-200d-2642-fe0f","💇🏻‍♂":"1f487-1f3fb-200d-2642-fe0f","💇🏼‍♂":"1f487-1f3fc-200d-2642-fe0f","💇🏽‍♂":"1f487-1f3fd-200d-2642-fe0f","💇🏾‍♂":"1f487-1f3fe-200d-2642-fe0f","💇🏿‍♂":"1f487-1f3ff-200d-2642-fe0f","💇‍♀️":"1f487-200d-2640-fe0f","🧔🏿‍♀":"1f9d4-1f3ff-200d-2640-fe0f","💇🏻‍♀":"1f487-1f3fb-200d-2640-fe0f","💇🏼‍♀":"1f487-1f3fc-200d-2640-fe0f","💇🏽‍♀":"1f487-1f3fd-200d-2640-fe0f","💇🏾‍♀":"1f487-1f3fe-200d-2640-fe0f","💇🏿‍♀":"1f487-1f3ff-200d-2640-fe0f","🚶‍♂️":"1f6b6-200d-2642-fe0f","🚶🏻‍♂":"1f6b6-1f3fb-200d-2642-fe0f","🚶🏼‍♂":"1f6b6-1f3fc-200d-2642-fe0f","🚶🏽‍♂":"1f6b6-1f3fd-200d-2642-fe0f","🚶🏾‍♂":"1f6b6-1f3fe-200d-2642-fe0f","🚶🏿‍♂":"1f6b6-1f3ff-200d-2642-fe0f","🚶‍♀️":"1f6b6-200d-2640-fe0f","🚶🏻‍♀":"1f6b6-1f3fb-200d-2640-fe0f","🚶🏼‍♀":"1f6b6-1f3fc-200d-2640-fe0f","🚶🏽‍♀":"1f6b6-1f3fd-200d-2640-fe0f","🚶🏾‍♀":"1f6b6-1f3fe-200d-2640-fe0f","🚶🏿‍♀":"1f6b6-1f3ff-200d-2640-fe0f","🚶‍➡️":"1f6b6-200d-27a1-fe0f","🚶🏻‍➡":"1f6b6-1f3fb-200d-27a1-fe0f","🚶🏼‍➡":"1f6b6-1f3fc-200d-27a1-fe0f","🚶🏽‍➡":"1f6b6-1f3fd-200d-27a1-fe0f","🚶🏾‍➡":"1f6b6-1f3fe-200d-27a1-fe0f","🚶🏿‍➡":"1f6b6-1f3ff-200d-27a1-fe0f","👨🏻‍🦰":"1f468-1f3fb-200d-1f9b0","🏊🏻‍♂":"1f3ca-1f3fb-200d-2642-fe0f","👨🏼‍🦰":"1f468-1f3fc-200d-1f9b0","👨🏽‍🦰":"1f468-1f3fd-200d-1f9b0","👨🏾‍🦰":"1f468-1f3fe-200d-1f9b0","👨🏿‍🦰":"1f468-1f3ff-200d-1f9b0","👨🏻‍🦱":"1f468-1f3fb-200d-1f9b1","🏋️‍♂":"1f3cb-fe0f-200d-2642-fe0f","👨🏼‍🦱":"1f468-1f3fc-200d-1f9b1","👨🏽‍🦱":"1f468-1f3fd-200d-1f9b1","👨🏾‍🦱":"1f468-1f3fe-200d-1f9b1","👨🏿‍🦱":"1f468-1f3ff-200d-1f9b1","👨🏻‍🦳":"1f468-1f3fb-200d-1f9b3","🏊‍♂️":"1f3ca-200d-2642-fe0f","👨🏼‍🦳":"1f468-1f3fc-200d-1f9b3","👨🏽‍🦳":"1f468-1f3fd-200d-1f9b3","👨🏾‍🦳":"1f468-1f3fe-200d-1f9b3","👨🏿‍🦳":"1f468-1f3ff-200d-1f9b3","👨🏻‍🦲":"1f468-1f3fb-200d-1f9b2","🚣🏿‍♀":"1f6a3-1f3ff-200d-2640-fe0f","🤽🏿‍♀":"1f93d-1f3ff-200d-2640-fe0f","🚣🏾‍♀":"1f6a3-1f3fe-200d-2640-fe0f","👨🏼‍🦲":"1f468-1f3fc-200d-1f9b2","👨🏽‍🦲":"1f468-1f3fd-200d-1f9b2","👨🏾‍🦲":"1f468-1f3fe-200d-1f9b2","👨🏿‍🦲":"1f468-1f3ff-200d-1f9b2","👩🏻‍🦰":"1f469-1f3fb-200d-1f9b0","⛹🏿‍♂":"26f9-1f3ff-200d-2642-fe0f","👩🏼‍🦰":"1f469-1f3fc-200d-1f9b0","👩🏽‍🦰":"1f469-1f3fd-200d-1f9b0","👩🏾‍🦰":"1f469-1f3fe-200d-1f9b0","👩🏿‍🦰":"1f469-1f3ff-200d-1f9b0","🧑🏻‍🦰":"1f9d1-1f3fb-200d-1f9b0","🚣🏽‍♀":"1f6a3-1f3fd-200d-2640-fe0f","🧑🏼‍🦰":"1f9d1-1f3fc-200d-1f9b0","🧑🏽‍🦰":"1f9d1-1f3fd-200d-1f9b0","🧑🏾‍🦰":"1f9d1-1f3fe-200d-1f9b0","🧑🏿‍🦰":"1f9d1-1f3ff-200d-1f9b0","👩🏻‍🦱":"1f469-1f3fb-200d-1f9b1","🤸🏽‍♂":"1f938-1f3fd-200d-2642-fe0f","👩🏼‍🦱":"1f469-1f3fc-200d-1f9b1","👩🏽‍🦱":"1f469-1f3fd-200d-1f9b1","👩🏾‍🦱":"1f469-1f3fe-200d-1f9b1","👩🏿‍🦱":"1f469-1f3ff-200d-1f9b1","🧑🏻‍🦱":"1f9d1-1f3fb-200d-1f9b1","🚣🏼‍♀":"1f6a3-1f3fc-200d-2640-fe0f","⛹🏾‍♂":"26f9-1f3fe-200d-2642-fe0f","🚣🏻‍♀":"1f6a3-1f3fb-200d-2640-fe0f","🧑🏼‍🦱":"1f9d1-1f3fc-200d-1f9b1","🧑🏽‍🦱":"1f9d1-1f3fd-200d-1f9b1","🧑🏾‍🦱":"1f9d1-1f3fe-200d-1f9b1","🧑🏿‍🦱":"1f9d1-1f3ff-200d-1f9b1","👩🏻‍🦳":"1f469-1f3fb-200d-1f9b3","🚵🏻‍♂":"1f6b5-1f3fb-200d-2642-fe0f","👩🏼‍🦳":"1f469-1f3fc-200d-1f9b3","👩🏽‍🦳":"1f469-1f3fd-200d-1f9b3","👩🏾‍🦳":"1f469-1f3fe-200d-1f9b3","👩🏿‍🦳":"1f469-1f3ff-200d-1f9b3","🧑🏻‍🦳":"1f9d1-1f3fb-200d-1f9b3","🚣‍♀️":"1f6a3-200d-2640-fe0f","🧑🏼‍🦳":"1f9d1-1f3fc-200d-1f9b3","🧑🏽‍🦳":"1f9d1-1f3fd-200d-1f9b3","🧑🏾‍🦳":"1f9d1-1f3fe-200d-1f9b3","🧑🏿‍🦳":"1f9d1-1f3ff-200d-1f9b3","👩🏻‍🦲":"1f469-1f3fb-200d-1f9b2","🚣🏿‍♂":"1f6a3-1f3ff-200d-2642-fe0f","👩🏼‍🦲":"1f469-1f3fc-200d-1f9b2","👩🏽‍🦲":"1f469-1f3fd-200d-1f9b2","👩🏾‍🦲":"1f469-1f3fe-200d-1f9b2","👩🏿‍🦲":"1f469-1f3ff-200d-1f9b2","🧑🏻‍🦲":"1f9d1-1f3fb-200d-1f9b2","⛹🏽‍♂":"26f9-1f3fd-200d-2642-fe0f","🚣🏾‍♂":"1f6a3-1f3fe-200d-2642-fe0f","🤸🏾‍♂":"1f938-1f3fe-200d-2642-fe0f","🚣🏽‍♂":"1f6a3-1f3fd-200d-2642-fe0f","🤾‍♂️":"1f93e-200d-2642-fe0f","🚣🏼‍♂":"1f6a3-1f3fc-200d-2642-fe0f","🧔🏻‍♂":"1f9d4-1f3fb-200d-2642-fe0f","🧑🏼‍🦲":"1f9d1-1f3fc-200d-1f9b2","🧑🏽‍🦲":"1f9d1-1f3fd-200d-1f9b2","🧑🏾‍🦲":"1f9d1-1f3fe-200d-1f9b2","🧑🏿‍🦲":"1f9d1-1f3ff-200d-1f9b2","👱‍♀️":"1f471-200d-2640-fe0f","👱🏻‍♀":"1f471-1f3fb-200d-2640-fe0f","👱🏼‍♀":"1f471-1f3fc-200d-2640-fe0f","👱🏽‍♀":"1f471-1f3fd-200d-2640-fe0f","👱🏾‍♀":"1f471-1f3fe-200d-2640-fe0f","👱🏿‍♀":"1f471-1f3ff-200d-2640-fe0f","👱‍♂️":"1f471-200d-2642-fe0f","👱🏻‍♂":"1f471-1f3fb-200d-2642-fe0f","👱🏼‍♂":"1f471-1f3fc-200d-2642-fe0f","👱🏽‍♂":"1f471-1f3fd-200d-2642-fe0f","👱🏾‍♂":"1f471-1f3fe-200d-2642-fe0f","👱🏿‍♂":"1f471-1f3ff-200d-2642-fe0f","🙍‍♂️":"1f64d-200d-2642-fe0f","🙍🏻‍♂":"1f64d-1f3fb-200d-2642-fe0f","🙍🏼‍♂":"1f64d-1f3fc-200d-2642-fe0f","🙍🏽‍♂":"1f64d-1f3fd-200d-2642-fe0f","🙍🏾‍♂":"1f64d-1f3fe-200d-2642-fe0f","🙍🏿‍♂":"1f64d-1f3ff-200d-2642-fe0f","🙍‍♀️":"1f64d-200d-2640-fe0f","🙍🏻‍♀":"1f64d-1f3fb-200d-2640-fe0f","🙍🏼‍♀":"1f64d-1f3fc-200d-2640-fe0f","🙍🏽‍♀":"1f64d-1f3fd-200d-2640-fe0f","🙍🏾‍♀":"1f64d-1f3fe-200d-2640-fe0f","🙍🏿‍♀":"1f64d-1f3ff-200d-2640-fe0f","🙎‍♂️":"1f64e-200d-2642-fe0f","🙎🏻‍♂":"1f64e-1f3fb-200d-2642-fe0f","🙎🏼‍♂":"1f64e-1f3fc-200d-2642-fe0f","🙎🏽‍♂":"1f64e-1f3fd-200d-2642-fe0f","🙎🏾‍♂":"1f64e-1f3fe-200d-2642-fe0f","🙎🏿‍♂":"1f64e-1f3ff-200d-2642-fe0f","🙎‍♀️":"1f64e-200d-2640-fe0f","🙎🏻‍♀":"1f64e-1f3fb-200d-2640-fe0f","🙎🏼‍♀":"1f64e-1f3fc-200d-2640-fe0f","🙎🏽‍♀":"1f64e-1f3fd-200d-2640-fe0f","🙎🏾‍♀":"1f64e-1f3fe-200d-2640-fe0f","🙎🏿‍♀":"1f64e-1f3ff-200d-2640-fe0f","🙅‍♂️":"1f645-200d-2642-fe0f","🙅🏻‍♂":"1f645-1f3fb-200d-2642-fe0f","🙅🏼‍♂":"1f645-1f3fc-200d-2642-fe0f","🙅🏽‍♂":"1f645-1f3fd-200d-2642-fe0f","🙅🏾‍♂":"1f645-1f3fe-200d-2642-fe0f","🙅🏿‍♂":"1f645-1f3ff-200d-2642-fe0f","🙅‍♀️":"1f645-200d-2640-fe0f","🙅🏻‍♀":"1f645-1f3fb-200d-2640-fe0f","🙅🏼‍♀":"1f645-1f3fc-200d-2640-fe0f","🙅🏽‍♀":"1f645-1f3fd-200d-2640-fe0f","🙅🏾‍♀":"1f645-1f3fe-200d-2640-fe0f","🙅🏿‍♀":"1f645-1f3ff-200d-2640-fe0f","🙆‍♂️":"1f646-200d-2642-fe0f","🙆🏻‍♂":"1f646-1f3fb-200d-2642-fe0f","🙆🏼‍♂":"1f646-1f3fc-200d-2642-fe0f","🙆🏽‍♂":"1f646-1f3fd-200d-2642-fe0f","🙆🏾‍♂":"1f646-1f3fe-200d-2642-fe0f","🙆🏿‍♂":"1f646-1f3ff-200d-2642-fe0f","🙆‍♀️":"1f646-200d-2640-fe0f","🙆🏻‍♀":"1f646-1f3fb-200d-2640-fe0f","🙆🏼‍♀":"1f646-1f3fc-200d-2640-fe0f","🙆🏽‍♀":"1f646-1f3fd-200d-2640-fe0f","🙆🏾‍♀":"1f646-1f3fe-200d-2640-fe0f","🙆🏿‍♀":"1f646-1f3ff-200d-2640-fe0f","💁‍♂️":"1f481-200d-2642-fe0f","💁🏻‍♂":"1f481-1f3fb-200d-2642-fe0f","💁🏼‍♂":"1f481-1f3fc-200d-2642-fe0f","💁🏽‍♂":"1f481-1f3fd-200d-2642-fe0f","💁🏾‍♂":"1f481-1f3fe-200d-2642-fe0f","💁🏿‍♂":"1f481-1f3ff-200d-2642-fe0f","💁‍♀️":"1f481-200d-2640-fe0f","💁🏻‍♀":"1f481-1f3fb-200d-2640-fe0f","💁🏼‍♀":"1f481-1f3fc-200d-2640-fe0f","💁🏽‍♀":"1f481-1f3fd-200d-2640-fe0f","💁🏾‍♀":"1f481-1f3fe-200d-2640-fe0f","💁🏿‍♀":"1f481-1f3ff-200d-2640-fe0f","🙋‍♂️":"1f64b-200d-2642-fe0f","🙋🏻‍♂":"1f64b-1f3fb-200d-2642-fe0f","🙋🏼‍♂":"1f64b-1f3fc-200d-2642-fe0f","🙋🏽‍♂":"1f64b-1f3fd-200d-2642-fe0f","🙋🏾‍♂":"1f64b-1f3fe-200d-2642-fe0f","🙋🏿‍♂":"1f64b-1f3ff-200d-2642-fe0f","🙋‍♀️":"1f64b-200d-2640-fe0f","🙋🏻‍♀":"1f64b-1f3fb-200d-2640-fe0f","🙋🏼‍♀":"1f64b-1f3fc-200d-2640-fe0f","🏴‍☠️":"1f3f4-200d-2620-fe0f","🏳️‍⚧":"1f3f3-fe0f-200d-26a7-fe0f","🏳‍⚧️":"1f3f3-fe0f-200d-26a7-fe0f","🏳️‍🌈":"1f3f3-fe0f-200d-1f308","🙋🏽‍♀":"1f64b-1f3fd-200d-2640-fe0f","🙋🏾‍♀":"1f64b-1f3fe-200d-2640-fe0f","🙋🏿‍♀":"1f64b-1f3ff-200d-2640-fe0f","🧏‍♂️":"1f9cf-200d-2642-fe0f","🧏🏻‍♂":"1f9cf-1f3fb-200d-2642-fe0f","🧏🏼‍♂":"1f9cf-1f3fc-200d-2642-fe0f","🧏🏽‍♂":"1f9cf-1f3fd-200d-2642-fe0f","🧏🏾‍♂":"1f9cf-1f3fe-200d-2642-fe0f","🧏🏿‍♂":"1f9cf-1f3ff-200d-2642-fe0f","🧏‍♀️":"1f9cf-200d-2640-fe0f","🧏🏻‍♀":"1f9cf-1f3fb-200d-2640-fe0f","🧏🏼‍♀":"1f9cf-1f3fc-200d-2640-fe0f","🧏🏽‍♀":"1f9cf-1f3fd-200d-2640-fe0f","🧏🏾‍♀":"1f9cf-1f3fe-200d-2640-fe0f","🧏🏿‍♀":"1f9cf-1f3ff-200d-2640-fe0f","🙇‍♂️":"1f647-200d-2642-fe0f","🙇🏻‍♂":"1f647-1f3fb-200d-2642-fe0f","🙇🏼‍♂":"1f647-1f3fc-200d-2642-fe0f","🙇🏽‍♂":"1f647-1f3fd-200d-2642-fe0f","🙇🏾‍♂":"1f647-1f3fe-200d-2642-fe0f","🙇🏿‍♂":"1f647-1f3ff-200d-2642-fe0f","🙇‍♀️":"1f647-200d-2640-fe0f","🙇🏻‍♀":"1f647-1f3fb-200d-2640-fe0f","🙇🏼‍♀":"1f647-1f3fc-200d-2640-fe0f","🙇🏽‍♀":"1f647-1f3fd-200d-2640-fe0f","🙇🏾‍♀":"1f647-1f3fe-200d-2640-fe0f","🙇🏿‍♀":"1f647-1f3ff-200d-2640-fe0f","🤦‍♂️":"1f926-200d-2642-fe0f","🤦🏻‍♂":"1f926-1f3fb-200d-2642-fe0f","🤦🏼‍♂":"1f926-1f3fc-200d-2642-fe0f","🤦🏽‍♂":"1f926-1f3fd-200d-2642-fe0f","🤦🏾‍♂":"1f926-1f3fe-200d-2642-fe0f","🤦🏿‍♂":"1f926-1f3ff-200d-2642-fe0f","🤦‍♀️":"1f926-200d-2640-fe0f","🤦🏻‍♀":"1f926-1f3fb-200d-2640-fe0f","🤦🏼‍♀":"1f926-1f3fc-200d-2640-fe0f","🤦🏽‍♀":"1f926-1f3fd-200d-2640-fe0f","🤦🏾‍♀":"1f926-1f3fe-200d-2640-fe0f","🤦🏿‍♀":"1f926-1f3ff-200d-2640-fe0f","🤷‍♂️":"1f937-200d-2642-fe0f","🤷🏻‍♂":"1f937-1f3fb-200d-2642-fe0f","🤷🏼‍♂":"1f937-1f3fc-200d-2642-fe0f","🤷🏽‍♂":"1f937-1f3fd-200d-2642-fe0f","🤷🏾‍♂":"1f937-1f3fe-200d-2642-fe0f","🤷🏿‍♂":"1f937-1f3ff-200d-2642-fe0f","🤷‍♀️":"1f937-200d-2640-fe0f","🤷🏻‍♀":"1f937-1f3fb-200d-2640-fe0f","🤷🏼‍♀":"1f937-1f3fc-200d-2640-fe0f","🤷🏽‍♀":"1f937-1f3fd-200d-2640-fe0f","🤷🏾‍♀":"1f937-1f3fe-200d-2640-fe0f","🤷🏿‍♀":"1f937-1f3ff-200d-2640-fe0f","🧑‍⚕️":"1f9d1-200d-2695-fe0f","😶‍🌫️":"1f636-200d-1f32b-fe0f","🙂‍↔️":"1f642-200d-2194-fe0f","🧑🏻‍⚕":"1f9d1-1f3fb-200d-2695-fe0f","🙂‍↕️":"1f642-200d-2195-fe0f","❤️‍🔥":"2764-fe0f-200d-1f525","🧑🏼‍⚕":"1f9d1-1f3fc-200d-2695-fe0f","❤️‍🩹":"2764-fe0f-200d-1fa79","🧑🏽‍⚕":"1f9d1-1f3fd-200d-2695-fe0f","👁‍🗨️":"1f441-200d-1f5e8","👁️‍🗨":"1f441-200d-1f5e8","🧑🏾‍⚕":"1f9d1-1f3fe-200d-2695-fe0f","🧑🏿‍⚕":"1f9d1-1f3ff-200d-2695-fe0f","👨‍⚕️":"1f468-200d-2695-fe0f","👨🏻‍⚕":"1f468-1f3fb-200d-2695-fe0f","👨🏼‍⚕":"1f468-1f3fc-200d-2695-fe0f","👨🏽‍⚕":"1f468-1f3fd-200d-2695-fe0f","👨🏾‍⚕":"1f468-1f3fe-200d-2695-fe0f","👨🏿‍⚕":"1f468-1f3ff-200d-2695-fe0f","👩‍⚕️":"1f469-200d-2695-fe0f","👩🏻‍⚕":"1f469-1f3fb-200d-2695-fe0f","👩🏼‍⚕":"1f469-1f3fc-200d-2695-fe0f","👩🏽‍⚕":"1f469-1f3fd-200d-2695-fe0f","👩🏾‍⚕":"1f469-1f3fe-200d-2695-fe0f","👩🏿‍⚕":"1f469-1f3ff-200d-2695-fe0f","🧑🏻‍🎓":"1f9d1-1f3fb-200d-1f393","🧑🏼‍🎓":"1f9d1-1f3fc-200d-1f393","🧑🏽‍🎓":"1f9d1-1f3fd-200d-1f393","🧑🏾‍🎓":"1f9d1-1f3fe-200d-1f393","🧑🏿‍🎓":"1f9d1-1f3ff-200d-1f393","👨🏻‍🎓":"1f468-1f3fb-200d-1f393","👨🏼‍🎓":"1f468-1f3fc-200d-1f393","👨🏽‍🎓":"1f468-1f3fd-200d-1f393","👨🏾‍🎓":"1f468-1f3fe-200d-1f393","👨🏿‍🎓":"1f468-1f3ff-200d-1f393","👩🏻‍🎓":"1f469-1f3fb-200d-1f393","👩🏼‍🎓":"1f469-1f3fc-200d-1f393","👩🏽‍🎓":"1f469-1f3fd-200d-1f393","👩🏾‍🎓":"1f469-1f3fe-200d-1f393","👩🏿‍🎓":"1f469-1f3ff-200d-1f393","🧑🏻‍🏫":"1f9d1-1f3fb-200d-1f3eb","🧑🏼‍🏫":"1f9d1-1f3fc-200d-1f3eb","🧑🏽‍🏫":"1f9d1-1f3fd-200d-1f3eb","🧑🏾‍🏫":"1f9d1-1f3fe-200d-1f3eb","🧑🏿‍🏫":"1f9d1-1f3ff-200d-1f3eb","👨🏻‍🏫":"1f468-1f3fb-200d-1f3eb","👨🏼‍🏫":"1f468-1f3fc-200d-1f3eb","👨🏽‍🏫":"1f468-1f3fd-200d-1f3eb","👨🏾‍🏫":"1f468-1f3fe-200d-1f3eb","👨🏿‍🏫":"1f468-1f3ff-200d-1f3eb","👩🏻‍🏫":"1f469-1f3fb-200d-1f3eb","👩🏼‍🏫":"1f469-1f3fc-200d-1f3eb","👩🏽‍🏫":"1f469-1f3fd-200d-1f3eb","👩🏾‍🏫":"1f469-1f3fe-200d-1f3eb","👩🏿‍🏫":"1f469-1f3ff-200d-1f3eb","🧑‍⚖️":"1f9d1-200d-2696-fe0f","🚣🏻‍♂":"1f6a3-1f3fb-200d-2642-fe0f","🚵🏻‍♀":"1f6b5-1f3fb-200d-2640-fe0f","🚣‍♂️":"1f6a3-200d-2642-fe0f","🏄🏿‍♀":"1f3c4-1f3ff-200d-2640-fe0f","🤾🏻‍♂":"1f93e-1f3fb-200d-2642-fe0f","🏄🏾‍♀":"1f3c4-1f3fe-200d-2640-fe0f","🧑🏻‍⚖":"1f9d1-1f3fb-200d-2696-fe0f","🧑🏼‍⚖":"1f9d1-1f3fc-200d-2696-fe0f","🧑🏽‍⚖":"1f9d1-1f3fd-200d-2696-fe0f","🧑🏾‍⚖":"1f9d1-1f3fe-200d-2696-fe0f","🧑🏿‍⚖":"1f9d1-1f3ff-200d-2696-fe0f","👨‍⚖️":"1f468-200d-2696-fe0f","👨🏻‍⚖":"1f468-1f3fb-200d-2696-fe0f","👨🏼‍⚖":"1f468-1f3fc-200d-2696-fe0f","👨🏽‍⚖":"1f468-1f3fd-200d-2696-fe0f","👨🏾‍⚖":"1f468-1f3fe-200d-2696-fe0f","👨🏿‍⚖":"1f468-1f3ff-200d-2696-fe0f","👩‍⚖️":"1f469-200d-2696-fe0f","👩🏻‍⚖":"1f469-1f3fb-200d-2696-fe0f","👩🏼‍⚖":"1f469-1f3fc-200d-2696-fe0f","👩🏽‍⚖":"1f469-1f3fd-200d-2696-fe0f","👩🏾‍⚖":"1f469-1f3fe-200d-2696-fe0f","👩🏿‍⚖":"1f469-1f3ff-200d-2696-fe0f","🧑🏻‍🌾":"1f9d1-1f3fb-200d-1f33e","🧑🏼‍🌾":"1f9d1-1f3fc-200d-1f33e","🧑🏽‍🌾":"1f9d1-1f3fd-200d-1f33e","🧑🏾‍🌾":"1f9d1-1f3fe-200d-1f33e","🧑🏿‍🌾":"1f9d1-1f3ff-200d-1f33e","👨🏻‍🌾":"1f468-1f3fb-200d-1f33e","👨🏼‍🌾":"1f468-1f3fc-200d-1f33e","👨🏽‍🌾":"1f468-1f3fd-200d-1f33e","👨🏾‍🌾":"1f468-1f3fe-200d-1f33e","👨🏿‍🌾":"1f468-1f3ff-200d-1f33e","👩🏻‍🌾":"1f469-1f3fb-200d-1f33e","👩🏼‍🌾":"1f469-1f3fc-200d-1f33e","👩🏽‍🌾":"1f469-1f3fd-200d-1f33e","👩🏾‍🌾":"1f469-1f3fe-200d-1f33e","👩🏿‍🌾":"1f469-1f3ff-200d-1f33e","🧑🏻‍🍳":"1f9d1-1f3fb-200d-1f373","🧑🏼‍🍳":"1f9d1-1f3fc-200d-1f373","🧑🏽‍🍳":"1f9d1-1f3fd-200d-1f373","🧑🏾‍🍳":"1f9d1-1f3fe-200d-1f373","🧑🏿‍🍳":"1f9d1-1f3ff-200d-1f373","👨🏻‍🍳":"1f468-1f3fb-200d-1f373","👨🏼‍🍳":"1f468-1f3fc-200d-1f373","👨🏽‍🍳":"1f468-1f3fd-200d-1f373","👨🏾‍🍳":"1f468-1f3fe-200d-1f373","🤸🏿‍♂":"1f938-1f3ff-200d-2642-fe0f","👨🏿‍🍳":"1f468-1f3ff-200d-1f373","👩🏻‍🍳":"1f469-1f3fb-200d-1f373","👩🏼‍🍳":"1f469-1f3fc-200d-1f373","👩🏽‍🍳":"1f469-1f3fd-200d-1f373","👩🏾‍🍳":"1f469-1f3fe-200d-1f373","👩🏿‍🍳":"1f469-1f3ff-200d-1f373","🧑🏻‍🔧":"1f9d1-1f3fb-200d-1f527","🧑🏼‍🔧":"1f9d1-1f3fc-200d-1f527","🧑🏽‍🔧":"1f9d1-1f3fd-200d-1f527","🧑🏾‍🔧":"1f9d1-1f3fe-200d-1f527","🧑🏿‍🔧":"1f9d1-1f3ff-200d-1f527","👨🏻‍🔧":"1f468-1f3fb-200d-1f527","👨🏼‍🔧":"1f468-1f3fc-200d-1f527","👨🏽‍🔧":"1f468-1f3fd-200d-1f527","👨🏾‍🔧":"1f468-1f3fe-200d-1f527","👨🏿‍🔧":"1f468-1f3ff-200d-1f527","👩🏻‍🔧":"1f469-1f3fb-200d-1f527","👩🏼‍🔧":"1f469-1f3fc-200d-1f527","👩🏽‍🔧":"1f469-1f3fd-200d-1f527","👩🏾‍🔧":"1f469-1f3fe-200d-1f527","👩🏿‍🔧":"1f469-1f3ff-200d-1f527","🧑🏻‍🏭":"1f9d1-1f3fb-200d-1f3ed","🧑🏼‍🏭":"1f9d1-1f3fc-200d-1f3ed","🧑🏽‍🏭":"1f9d1-1f3fd-200d-1f3ed","🧑🏾‍🏭":"1f9d1-1f3fe-200d-1f3ed","🧑🏿‍🏭":"1f9d1-1f3ff-200d-1f3ed","👨🏻‍🏭":"1f468-1f3fb-200d-1f3ed","👨🏼‍🏭":"1f468-1f3fc-200d-1f3ed","👨🏽‍🏭":"1f468-1f3fd-200d-1f3ed","👨🏾‍🏭":"1f468-1f3fe-200d-1f3ed","👨🏿‍🏭":"1f468-1f3ff-200d-1f3ed","👩🏻‍🏭":"1f469-1f3fb-200d-1f3ed","👩🏼‍🏭":"1f469-1f3fc-200d-1f3ed","👩🏽‍🏭":"1f469-1f3fd-200d-1f3ed","👩🏾‍🏭":"1f469-1f3fe-200d-1f3ed","👩🏿‍🏭":"1f469-1f3ff-200d-1f3ed","🧑🏻‍💼":"1f9d1-1f3fb-200d-1f4bc","🧑🏼‍💼":"1f9d1-1f3fc-200d-1f4bc","🧑🏽‍💼":"1f9d1-1f3fd-200d-1f4bc","🧑🏾‍💼":"1f9d1-1f3fe-200d-1f4bc","🧑🏿‍💼":"1f9d1-1f3ff-200d-1f4bc","👨🏻‍💼":"1f468-1f3fb-200d-1f4bc","👨🏼‍💼":"1f468-1f3fc-200d-1f4bc","👨🏽‍💼":"1f468-1f3fd-200d-1f4bc","👨🏾‍💼":"1f468-1f3fe-200d-1f4bc","👨🏿‍💼":"1f468-1f3ff-200d-1f4bc","👩🏻‍💼":"1f469-1f3fb-200d-1f4bc","👩🏼‍💼":"1f469-1f3fc-200d-1f4bc","👩🏽‍💼":"1f469-1f3fd-200d-1f4bc","👩🏾‍💼":"1f469-1f3fe-200d-1f4bc","👩🏿‍💼":"1f469-1f3ff-200d-1f4bc","🤾🏼‍♂":"1f93e-1f3fc-200d-2642-fe0f","🧑🏻‍🔬":"1f9d1-1f3fb-200d-1f52c","🧑🏼‍🔬":"1f9d1-1f3fc-200d-1f52c","🧑🏽‍🔬":"1f9d1-1f3fd-200d-1f52c","🧑🏾‍🔬":"1f9d1-1f3fe-200d-1f52c","🧑🏿‍🔬":"1f9d1-1f3ff-200d-1f52c","👨🏻‍🔬":"1f468-1f3fb-200d-1f52c","👨🏼‍🔬":"1f468-1f3fc-200d-1f52c","👨🏽‍🔬":"1f468-1f3fd-200d-1f52c","👨🏾‍🔬":"1f468-1f3fe-200d-1f52c","👨🏿‍🔬":"1f468-1f3ff-200d-1f52c","👩🏻‍🔬":"1f469-1f3fb-200d-1f52c","👩🏼‍🔬":"1f469-1f3fc-200d-1f52c","👩🏽‍🔬":"1f469-1f3fd-200d-1f52c","👩🏾‍🔬":"1f469-1f3fe-200d-1f52c","👩🏿‍🔬":"1f469-1f3ff-200d-1f52c","🧑🏻‍💻":"1f9d1-1f3fb-200d-1f4bb","🧑🏼‍💻":"1f9d1-1f3fc-200d-1f4bb","🧑🏽‍💻":"1f9d1-1f3fd-200d-1f4bb","🧑🏾‍💻":"1f9d1-1f3fe-200d-1f4bb","🧑🏿‍💻":"1f9d1-1f3ff-200d-1f4bb","👨🏻‍💻":"1f468-1f3fb-200d-1f4bb","👨🏼‍💻":"1f468-1f3fc-200d-1f4bb","👨🏽‍💻":"1f468-1f3fd-200d-1f4bb","👨🏾‍💻":"1f468-1f3fe-200d-1f4bb","👨🏿‍💻":"1f468-1f3ff-200d-1f4bb","👩🏻‍💻":"1f469-1f3fb-200d-1f4bb","👩🏼‍💻":"1f469-1f3fc-200d-1f4bb","👩🏽‍💻":"1f469-1f3fd-200d-1f4bb","👩🏾‍💻":"1f469-1f3fe-200d-1f4bb","👩🏿‍💻":"1f469-1f3ff-200d-1f4bb","🧑🏻‍🎤":"1f9d1-1f3fb-200d-1f3a4","🧑🏼‍🎤":"1f9d1-1f3fc-200d-1f3a4","🧑🏽‍🎤":"1f9d1-1f3fd-200d-1f3a4","🧑🏾‍🎤":"1f9d1-1f3fe-200d-1f3a4","🧑🏿‍🎤":"1f9d1-1f3ff-200d-1f3a4","👨🏻‍🎤":"1f468-1f3fb-200d-1f3a4","👨🏼‍🎤":"1f468-1f3fc-200d-1f3a4","👨🏽‍🎤":"1f468-1f3fd-200d-1f3a4","👨🏾‍🎤":"1f468-1f3fe-200d-1f3a4","👨🏿‍🎤":"1f468-1f3ff-200d-1f3a4","👩🏻‍🎤":"1f469-1f3fb-200d-1f3a4","👩🏼‍🎤":"1f469-1f3fc-200d-1f3a4","👩🏽‍🎤":"1f469-1f3fd-200d-1f3a4","👩🏾‍🎤":"1f469-1f3fe-200d-1f3a4","👩🏿‍🎤":"1f469-1f3ff-200d-1f3a4","🧑🏻‍🎨":"1f9d1-1f3fb-200d-1f3a8","🧑🏼‍🎨":"1f9d1-1f3fc-200d-1f3a8","🧑🏽‍🎨":"1f9d1-1f3fd-200d-1f3a8","🧑🏾‍🎨":"1f9d1-1f3fe-200d-1f3a8","🧑🏿‍🎨":"1f9d1-1f3ff-200d-1f3a8","👨🏻‍🎨":"1f468-1f3fb-200d-1f3a8","🤸‍♀️":"1f938-200d-2640-fe0f","👨🏼‍🎨":"1f468-1f3fc-200d-1f3a8","👨🏽‍🎨":"1f468-1f3fd-200d-1f3a8","👨🏾‍🎨":"1f468-1f3fe-200d-1f3a8","👨🏿‍🎨":"1f468-1f3ff-200d-1f3a8","👩🏻‍🎨":"1f469-1f3fb-200d-1f3a8","👩🏼‍🎨":"1f469-1f3fc-200d-1f3a8","👩🏽‍🎨":"1f469-1f3fd-200d-1f3a8","👩🏾‍🎨":"1f469-1f3fe-200d-1f3a8","👩🏿‍🎨":"1f469-1f3ff-200d-1f3a8","🧑‍✈️":"1f9d1-200d-2708-fe0f","🧑🏻‍✈":"1f9d1-1f3fb-200d-2708-fe0f","🧑🏼‍✈":"1f9d1-1f3fc-200d-2708-fe0f","🧑🏽‍✈":"1f9d1-1f3fd-200d-2708-fe0f","🧑🏾‍✈":"1f9d1-1f3fe-200d-2708-fe0f","🧑🏿‍✈":"1f9d1-1f3ff-200d-2708-fe0f","👨‍✈️":"1f468-200d-2708-fe0f","👨🏻‍✈":"1f468-1f3fb-200d-2708-fe0f","👨🏼‍✈":"1f468-1f3fc-200d-2708-fe0f","👨🏽‍✈":"1f468-1f3fd-200d-2708-fe0f","👨🏾‍✈":"1f468-1f3fe-200d-2708-fe0f","👨🏿‍✈":"1f468-1f3ff-200d-2708-fe0f","👩‍✈️":"1f469-200d-2708-fe0f","👩🏻‍✈":"1f469-1f3fb-200d-2708-fe0f","👩🏼‍✈":"1f469-1f3fc-200d-2708-fe0f","👩🏽‍✈":"1f469-1f3fd-200d-2708-fe0f","👩🏾‍✈":"1f469-1f3fe-200d-2708-fe0f","⛓️‍💥":"26d3-fe0f-200d-1f4a5","👩🏿‍✈":"1f469-1f3ff-200d-2708-fe0f","🧑🏻‍🚀":"1f9d1-1f3fb-200d-1f680","🧑🏼‍🚀":"1f9d1-1f3fc-200d-1f680","🧑🏽‍🚀":"1f9d1-1f3fd-200d-1f680","🧑🏾‍🚀":"1f9d1-1f3fe-200d-1f680","🧑🏿‍🚀":"1f9d1-1f3ff-200d-1f680","👨🏻‍🚀":"1f468-1f3fb-200d-1f680","👨🏼‍🚀":"1f468-1f3fc-200d-1f680","👨🏽‍🚀":"1f468-1f3fd-200d-1f680","👨🏾‍🚀":"1f468-1f3fe-200d-1f680","👨🏿‍🚀":"1f468-1f3ff-200d-1f680","👩🏻‍🚀":"1f469-1f3fb-200d-1f680","👩🏼‍🚀":"1f469-1f3fc-200d-1f680","👩🏽‍🚀":"1f469-1f3fd-200d-1f680","👩🏾‍🚀":"1f469-1f3fe-200d-1f680","👩🏿‍🚀":"1f469-1f3ff-200d-1f680","🧑🏻‍🚒":"1f9d1-1f3fb-200d-1f692","🧑🏼‍🚒":"1f9d1-1f3fc-200d-1f692","🧑🏽‍🚒":"1f9d1-1f3fd-200d-1f692","🧑🏾‍🚒":"1f9d1-1f3fe-200d-1f692","🧑🏿‍🚒":"1f9d1-1f3ff-200d-1f692","👨🏻‍🚒":"1f468-1f3fb-200d-1f692","👨🏼‍🚒":"1f468-1f3fc-200d-1f692","🤾🏽‍♂":"1f93e-1f3fd-200d-2642-fe0f","🏋‍♂️":"1f3cb-fe0f-200d-2642-fe0f","👨🏽‍🚒":"1f468-1f3fd-200d-1f692","👨🏾‍🚒":"1f468-1f3fe-200d-1f692","👨🏿‍🚒":"1f468-1f3ff-200d-1f692","🤾🏾‍♂":"1f93e-1f3fe-200d-2642-fe0f","🚵🏼‍♀":"1f6b5-1f3fc-200d-2640-fe0f","👩🏻‍🚒":"1f469-1f3fb-200d-1f692","👩🏼‍🚒":"1f469-1f3fc-200d-1f692","👩🏽‍🚒":"1f469-1f3fd-200d-1f692","🤾🏿‍♂":"1f93e-1f3ff-200d-2642-fe0f","🤾‍♀️":"1f93e-200d-2640-fe0f","👩🏾‍🚒":"1f469-1f3fe-200d-1f692","👩🏿‍🚒":"1f469-1f3ff-200d-1f692","👮‍♂️":"1f46e-200d-2642-fe0f","🤸🏻‍♀":"1f938-1f3fb-200d-2640-fe0f","🚵🏼‍♂":"1f6b5-1f3fc-200d-2642-fe0f","🤾🏻‍♀":"1f93e-1f3fb-200d-2640-fe0f","🤸🏼‍♀":"1f938-1f3fc-200d-2640-fe0f","🤾🏼‍♀":"1f93e-1f3fc-200d-2640-fe0f","🚵🏽‍♀":"1f6b5-1f3fd-200d-2640-fe0f","🤾🏽‍♀":"1f93e-1f3fd-200d-2640-fe0f","🤸🏽‍♀":"1f938-1f3fd-200d-2640-fe0f","🤾🏾‍♀":"1f93e-1f3fe-200d-2640-fe0f","🏋🏻‍♂":"1f3cb-1f3fb-200d-2642-fe0f","🤾🏿‍♀":"1f93e-1f3ff-200d-2640-fe0f","🏄🏽‍♀":"1f3c4-1f3fd-200d-2640-fe0f","⛹🏼‍♂":"26f9-1f3fc-200d-2642-fe0f","🏄🏼‍♀":"1f3c4-1f3fc-200d-2640-fe0f","🤸🏾‍♀":"1f938-1f3fe-200d-2640-fe0f","🏄🏻‍♀":"1f3c4-1f3fb-200d-2640-fe0f","⛹🏻‍♂":"26f9-1f3fb-200d-2642-fe0f","👮🏻‍♂":"1f46e-1f3fb-200d-2642-fe0f","🚵🏾‍♀":"1f6b5-1f3fe-200d-2640-fe0f","⛹️‍♂":"26f9-fe0f-200d-2642-fe0f","⛹‍♂️":"26f9-fe0f-200d-2642-fe0f","🏄‍♀️":"1f3c4-200d-2640-fe0f","🏄🏿‍♂":"1f3c4-1f3ff-200d-2642-fe0f","🤹‍♂️":"1f939-200d-2642-fe0f","🏄🏾‍♂":"1f3c4-1f3fe-200d-2642-fe0f","🧔‍♂️":"1f9d4-200d-2642-fe0f","🏄🏽‍♂":"1f3c4-1f3fd-200d-2642-fe0f","🤸🏿‍♀":"1f938-1f3ff-200d-2640-fe0f","🏄🏼‍♂":"1f3c4-1f3fc-200d-2642-fe0f","🤹🏻‍♂":"1f939-1f3fb-200d-2642-fe0f","🏄🏻‍♂":"1f3c4-1f3fb-200d-2642-fe0f","⛹🏼‍♀":"26f9-1f3fc-200d-2640-fe0f","🏄‍♂️":"1f3c4-200d-2642-fe0f","🏌🏿‍♀":"1f3cc-1f3ff-200d-2640-fe0f","🤹🏼‍♂":"1f939-1f3fc-200d-2642-fe0f","🏌🏾‍♀":"1f3cc-1f3fe-200d-2640-fe0f","🤼‍♂️":"1f93c-200d-2642-fe0f","🏌🏽‍♀":"1f3cc-1f3fd-200d-2640-fe0f","🤹🏽‍♂":"1f939-1f3fd-200d-2642-fe0f","🧔🏽‍♂":"1f9d4-1f3fd-200d-2642-fe0f","🏌🏼‍♀":"1f3cc-1f3fc-200d-2640-fe0f","🤹🏾‍♂":"1f939-1f3fe-200d-2642-fe0f","🏌🏻‍♀":"1f3cc-1f3fb-200d-2640-fe0f","🤼‍♀️":"1f93c-200d-2640-fe0f","🏌️‍♀":"1f3cc-fe0f-200d-2640-fe0f","🤹🏿‍♂":"1f939-1f3ff-200d-2642-fe0f","🏌‍♀️":"1f3cc-fe0f-200d-2640-fe0f","🤹‍♀️":"1f939-200d-2640-fe0f","🏌🏿‍♂":"1f3cc-1f3ff-200d-2642-fe0f","🚵🏽‍♂":"1f6b5-1f3fd-200d-2642-fe0f","🏌🏾‍♂":"1f3cc-1f3fe-200d-2642-fe0f","🚵🏿‍♀":"1f6b5-1f3ff-200d-2640-fe0f","🏌🏽‍♂":"1f3cc-1f3fd-200d-2642-fe0f","🤹🏻‍♀":"1f939-1f3fb-200d-2640-fe0f","🏌🏼‍♂":"1f3cc-1f3fc-200d-2642-fe0f","⛹🏻‍♀":"26f9-1f3fb-200d-2640-fe0f","🏌🏻‍♂":"1f3cc-1f3fb-200d-2642-fe0f","🤹🏼‍♀":"1f939-1f3fc-200d-2640-fe0f","🏌️‍♂":"1f3cc-fe0f-200d-2642-fe0f","🏌‍♂️":"1f3cc-fe0f-200d-2642-fe0f","⛹🏿‍♀":"26f9-1f3ff-200d-2640-fe0f","🧗🏿‍♀":"1f9d7-1f3ff-200d-2640-fe0f","🤹🏽‍♀":"1f939-1f3fd-200d-2640-fe0f","🧗🏾‍♀":"1f9d7-1f3fe-200d-2640-fe0f","⛹️‍♀":"26f9-fe0f-200d-2640-fe0f","🧗🏽‍♀":"1f9d7-1f3fd-200d-2640-fe0f","🤹🏾‍♀":"1f939-1f3fe-200d-2640-fe0f","🧗🏼‍♀":"1f9d7-1f3fc-200d-2640-fe0f","⛹‍♀️":"26f9-fe0f-200d-2640-fe0f","🧗🏻‍♀":"1f9d7-1f3fb-200d-2640-fe0f","🤹🏿‍♀":"1f939-1f3ff-200d-2640-fe0f","🧗‍♀️":"1f9d7-200d-2640-fe0f","🧗🏿‍♂":"1f9d7-1f3ff-200d-2642-fe0f","🚵‍♂️":"1f6b5-200d-2642-fe0f","🧗🏾‍♂":"1f9d7-1f3fe-200d-2642-fe0f","🏊🏿‍♀":"1f3ca-1f3ff-200d-2640-fe0f","🧗🏽‍♂":"1f9d7-1f3fd-200d-2642-fe0f","🤽‍♂️":"1f93d-200d-2642-fe0f","🧗🏼‍♂":"1f9d7-1f3fc-200d-2642-fe0f","🏊🏾‍♀":"1f3ca-1f3fe-200d-2640-fe0f","🧗🏻‍♂":"1f9d7-1f3fb-200d-2642-fe0f","🧔🏼‍♂":"1f9d4-1f3fc-200d-2642-fe0f","🧗‍♂️":"1f9d7-200d-2642-fe0f","🧖🏿‍♀":"1f9d6-1f3ff-200d-2640-fe0f","🏊🏽‍♀":"1f3ca-1f3fd-200d-2640-fe0f","🧖🏾‍♀":"1f9d6-1f3fe-200d-2640-fe0f","🧘‍♂️":"1f9d8-200d-2642-fe0f","🧖🏽‍♀":"1f9d6-1f3fd-200d-2640-fe0f","⛹🏾‍♀":"26f9-1f3fe-200d-2640-fe0f","🐻‍❄️":"1f43b-200d-2744-fe0f","🤽🏻‍♂":"1f93d-1f3fb-200d-2642-fe0f","🧖🏼‍♀":"1f9d6-1f3fc-200d-2640-fe0f","🧘🏻‍♂":"1f9d8-1f3fb-200d-2642-fe0f","🧖🏻‍♀":"1f9d6-1f3fb-200d-2640-fe0f","🚵🏾‍♂":"1f6b5-1f3fe-200d-2642-fe0f","🧖‍♀️":"1f9d6-200d-2640-fe0f","🧖🏿‍♂":"1f9d6-1f3ff-200d-2642-fe0f","🧘🏼‍♂":"1f9d8-1f3fc-200d-2642-fe0f","🧖🏾‍♂":"1f9d6-1f3fe-200d-2642-fe0f","🤽🏼‍♂":"1f93d-1f3fc-200d-2642-fe0f","🧖🏽‍♂":"1f9d6-1f3fd-200d-2642-fe0f","🧘🏽‍♂":"1f9d8-1f3fd-200d-2642-fe0f","🧖🏼‍♂":"1f9d6-1f3fc-200d-2642-fe0f","⛹🏽‍♀":"26f9-1f3fd-200d-2640-fe0f","🧖🏻‍♂":"1f9d6-1f3fb-200d-2642-fe0f","🧘🏾‍♂":"1f9d8-1f3fe-200d-2642-fe0f","🧖‍♂️":"1f9d6-200d-2642-fe0f","👯‍♀️":"1f46f-200d-2640-fe0f","👯‍♂️":"1f46f-200d-2642-fe0f","👮🏼‍♂":"1f46e-1f3fc-200d-2642-fe0f","👮🏽‍♂":"1f46e-1f3fd-200d-2642-fe0f","👮🏾‍♂":"1f46e-1f3fe-200d-2642-fe0f","👮🏿‍♂":"1f46e-1f3ff-200d-2642-fe0f","👮‍♀️":"1f46e-200d-2640-fe0f","👮🏻‍♀":"1f46e-1f3fb-200d-2640-fe0f","👮🏼‍♀":"1f46e-1f3fc-200d-2640-fe0f","👮🏽‍♀":"1f46e-1f3fd-200d-2640-fe0f","👮🏾‍♀":"1f46e-1f3fe-200d-2640-fe0f","👮🏿‍♀":"1f46e-1f3ff-200d-2640-fe0f","🤽🏽‍♂":"1f93d-1f3fd-200d-2642-fe0f","🕵‍♂️":"1f575-fe0f-200d-2642-fe0f","🧘🏿‍♂":"1f9d8-1f3ff-200d-2642-fe0f","🕵️‍♂":"1f575-fe0f-200d-2642-fe0f","🕵🏻‍♂":"1f575-1f3fb-200d-2642-fe0f","🕵🏼‍♂":"1f575-1f3fc-200d-2642-fe0f","🕵🏽‍♂":"1f575-1f3fd-200d-2642-fe0f","🕵🏾‍♂":"1f575-1f3fe-200d-2642-fe0f","🕵🏿‍♂":"1f575-1f3ff-200d-2642-fe0f","🕵‍♀️":"1f575-fe0f-200d-2640-fe0f","🕵️‍♀":"1f575-fe0f-200d-2640-fe0f","🕵🏻‍♀":"1f575-1f3fb-200d-2640-fe0f","🧘‍♀️":"1f9d8-200d-2640-fe0f","🕵🏼‍♀":"1f575-1f3fc-200d-2640-fe0f","🕵🏽‍♀":"1f575-1f3fd-200d-2640-fe0f","🕵🏾‍♀":"1f575-1f3fe-200d-2640-fe0f","🕵🏿‍♀":"1f575-1f3ff-200d-2640-fe0f","💂‍♂️":"1f482-200d-2642-fe0f","💂🏻‍♂":"1f482-1f3fb-200d-2642-fe0f","💂🏼‍♂":"1f482-1f3fc-200d-2642-fe0f","💂🏽‍♂":"1f482-1f3fd-200d-2642-fe0f","💂🏾‍♂":"1f482-1f3fe-200d-2642-fe0f","💂🏿‍♂":"1f482-1f3ff-200d-2642-fe0f","💂‍♀️":"1f482-200d-2640-fe0f","💂🏻‍♀":"1f482-1f3fb-200d-2640-fe0f","💂🏼‍♀":"1f482-1f3fc-200d-2640-fe0f","💂🏽‍♀":"1f482-1f3fd-200d-2640-fe0f","💂🏾‍♀":"1f482-1f3fe-200d-2640-fe0f","💂🏿‍♀":"1f482-1f3ff-200d-2640-fe0f","👷‍♂️":"1f477-200d-2642-fe0f","👷🏻‍♂":"1f477-1f3fb-200d-2642-fe0f","👷🏼‍♂":"1f477-1f3fc-200d-2642-fe0f","👷🏽‍♂":"1f477-1f3fd-200d-2642-fe0f","👷🏾‍♂":"1f477-1f3fe-200d-2642-fe0f","👷🏿‍♂":"1f477-1f3ff-200d-2642-fe0f","👷‍♀️":"1f477-200d-2640-fe0f","🧔🏿‍♂":"1f9d4-1f3ff-200d-2642-fe0f","👷🏻‍♀":"1f477-1f3fb-200d-2640-fe0f","👷🏼‍♀":"1f477-1f3fc-200d-2640-fe0f","👷🏽‍♀":"1f477-1f3fd-200d-2640-fe0f","🏃🏿‍➡":"1f3c3-1f3ff-200d-27a1-fe0f","🤽🏾‍♂":"1f93d-1f3fe-200d-2642-fe0f","🏃🏾‍➡":"1f3c3-1f3fe-200d-27a1-fe0f","🧘🏻‍♀":"1f9d8-1f3fb-200d-2640-fe0f","🏃🏽‍➡":"1f3c3-1f3fd-200d-27a1-fe0f","🤸‍♂️":"1f938-200d-2642-fe0f","🏃🏼‍➡":"1f3c3-1f3fc-200d-27a1-fe0f","🧘🏼‍♀":"1f9d8-1f3fc-200d-2640-fe0f","🏃🏻‍➡":"1f3c3-1f3fb-200d-27a1-fe0f","🤽🏿‍♂":"1f93d-1f3ff-200d-2642-fe0f","🏃‍➡️":"1f3c3-200d-27a1-fe0f","🏃🏿‍♀":"1f3c3-1f3ff-200d-2640-fe0f","🧘🏽‍♀":"1f9d8-1f3fd-200d-2640-fe0f","🏃🏾‍♀":"1f3c3-1f3fe-200d-2640-fe0f","🤽‍♀️":"1f93d-200d-2640-fe0f","🏃🏽‍♀":"1f3c3-1f3fd-200d-2640-fe0f","🧘🏾‍♀":"1f9d8-1f3fe-200d-2640-fe0f","🏃🏼‍♀":"1f3c3-1f3fc-200d-2640-fe0f","🧔🏾‍♂":"1f9d4-1f3fe-200d-2642-fe0f","🏃🏻‍♀":"1f3c3-1f3fb-200d-2640-fe0f","🧘🏿‍♀":"1f9d8-1f3ff-200d-2640-fe0f","🏃‍♀️":"1f3c3-200d-2640-fe0f","🏃🏿‍♂":"1f3c3-1f3ff-200d-2642-fe0f","🚵🏿‍♂":"1f6b5-1f3ff-200d-2642-fe0f","🏃🏾‍♂":"1f3c3-1f3fe-200d-2642-fe0f","🏊🏼‍♀":"1f3ca-1f3fc-200d-2640-fe0f","🏃🏽‍♂":"1f3c3-1f3fd-200d-2642-fe0f","🤽🏻‍♀":"1f93d-1f3fb-200d-2640-fe0f","🏃🏼‍♂":"1f3c3-1f3fc-200d-2642-fe0f","🏊🏻‍♀":"1f3ca-1f3fb-200d-2640-fe0f","🏃🏻‍♂":"1f3c3-1f3fb-200d-2642-fe0f","🤸🏻‍♂":"1f938-1f3fb-200d-2642-fe0f","🏃‍♂️":"1f3c3-200d-2642-fe0f","👷🏾‍♀":"1f477-1f3fe-200d-2640-fe0f","👷🏿‍♀":"1f477-1f3ff-200d-2640-fe0f","👳‍♂️":"1f473-200d-2642-fe0f","👳🏻‍♂":"1f473-1f3fb-200d-2642-fe0f","👳🏼‍♂":"1f473-1f3fc-200d-2642-fe0f","👳🏽‍♂":"1f473-1f3fd-200d-2642-fe0f","👳🏾‍♂":"1f473-1f3fe-200d-2642-fe0f","👳🏿‍♂":"1f473-1f3ff-200d-2642-fe0f","👳‍♀️":"1f473-200d-2640-fe0f","🏊‍♀️":"1f3ca-200d-2640-fe0f","👳🏻‍♀":"1f473-1f3fb-200d-2640-fe0f","🏊🏿‍♂":"1f3ca-1f3ff-200d-2642-fe0f","👳🏼‍♀":"1f473-1f3fc-200d-2640-fe0f","👩🏿‍🦽":"1f469-1f3ff-200d-1f9bd","👩🏾‍🦽":"1f469-1f3fe-200d-1f9bd","👩🏽‍🦽":"1f469-1f3fd-200d-1f9bd","👩🏼‍🦽":"1f469-1f3fc-200d-1f9bd","👩🏻‍🦽":"1f469-1f3fb-200d-1f9bd","👳🏽‍♀":"1f473-1f3fd-200d-2640-fe0f","👳🏾‍♀":"1f473-1f3fe-200d-2640-fe0f","👳🏿‍♀":"1f473-1f3ff-200d-2640-fe0f","🤵‍♂️":"1f935-200d-2642-fe0f","🤵🏻‍♂":"1f935-1f3fb-200d-2642-fe0f","🤵🏼‍♂":"1f935-1f3fc-200d-2642-fe0f","🤵🏽‍♂":"1f935-1f3fd-200d-2642-fe0f","🤵🏾‍♂":"1f935-1f3fe-200d-2642-fe0f","🤵🏿‍♂":"1f935-1f3ff-200d-2642-fe0f","🤵‍♀️":"1f935-200d-2640-fe0f","🤽🏼‍♀":"1f93d-1f3fc-200d-2640-fe0f","🤵🏻‍♀":"1f935-1f3fb-200d-2640-fe0f","👨🏿‍🦽":"1f468-1f3ff-200d-1f9bd","👨🏾‍🦽":"1f468-1f3fe-200d-1f9bd","👨🏽‍🦽":"1f468-1f3fd-200d-1f9bd","👨🏼‍🦽":"1f468-1f3fc-200d-1f9bd","👨🏻‍🦽":"1f468-1f3fb-200d-1f9bd","🤵🏼‍♀":"1f935-1f3fc-200d-2640-fe0f","🤵🏽‍♀":"1f935-1f3fd-200d-2640-fe0f","🤵🏾‍♀":"1f935-1f3fe-200d-2640-fe0f","🤵🏿‍♀":"1f935-1f3ff-200d-2640-fe0f","👰‍♂️":"1f470-200d-2642-fe0f","👰🏻‍♂":"1f470-1f3fb-200d-2642-fe0f","👰🏼‍♂":"1f470-1f3fc-200d-2642-fe0f","🏊🏾‍♂":"1f3ca-1f3fe-200d-2642-fe0f","👰🏽‍♂":"1f470-1f3fd-200d-2642-fe0f","👰🏾‍♂":"1f470-1f3fe-200d-2642-fe0f","👰🏿‍♂":"1f470-1f3ff-200d-2642-fe0f","🚵‍♀️":"1f6b5-200d-2640-fe0f","👰‍♀️":"1f470-200d-2640-fe0f","🧑🏿‍🦽":"1f9d1-1f3ff-200d-1f9bd","🧑🏾‍🦽":"1f9d1-1f3fe-200d-1f9bd","🧑🏽‍🦽":"1f9d1-1f3fd-200d-1f9bd","🧑🏼‍🦽":"1f9d1-1f3fc-200d-1f9bd","🧑🏻‍🦽":"1f9d1-1f3fb-200d-1f9bd","👰🏻‍♀":"1f470-1f3fb-200d-2640-fe0f","👰🏼‍♀":"1f470-1f3fc-200d-2640-fe0f","👰🏽‍♀":"1f470-1f3fd-200d-2640-fe0f","👰🏾‍♀":"1f470-1f3fe-200d-2640-fe0f","👰🏿‍♀":"1f470-1f3ff-200d-2640-fe0f","👩🏻‍🍼":"1f469-1f3fb-200d-1f37c","👩🏼‍🍼":"1f469-1f3fc-200d-1f37c","👩🏽‍🍼":"1f469-1f3fd-200d-1f37c","👩🏾‍🍼":"1f469-1f3fe-200d-1f37c","👩🏿‍🍼":"1f469-1f3ff-200d-1f37c","🏊🏽‍♂":"1f3ca-1f3fd-200d-2642-fe0f","👨🏻‍🍼":"1f468-1f3fb-200d-1f37c","👩🏿‍🦼":"1f469-1f3ff-200d-1f9bc","👩🏾‍🦼":"1f469-1f3fe-200d-1f9bc","👩🏽‍🦼":"1f469-1f3fd-200d-1f9bc","👩🏼‍🦼":"1f469-1f3fc-200d-1f9bc","👩🏻‍🦼":"1f469-1f3fb-200d-1f9bc","👨🏼‍🍼":"1f468-1f3fc-200d-1f37c","👨🏽‍🍼":"1f468-1f3fd-200d-1f37c","👨🏿‍🍼":"1f468-1f3ff-200d-1f37c","🦸🏼‍♀️":"1f9b8-1f3fc-200d-2640-fe0f","🏋🏻‍♂️":"1f3cb-1f3fb-200d-2642-fe0f","🏋️‍♂️":"1f3cb-fe0f-200d-2642-fe0f","🚵🏻‍♂️":"1f6b5-1f3fb-200d-2642-fe0f","🚵🏼‍♂️":"1f6b5-1f3fc-200d-2642-fe0f","🚵🏽‍♂️":"1f6b5-1f3fd-200d-2642-fe0f","🚵🏾‍♂️":"1f6b5-1f3fe-200d-2642-fe0f","🚵🏿‍♂️":"1f6b5-1f3ff-200d-2642-fe0f","🧔🏿‍♂️":"1f9d4-1f3ff-200d-2642-fe0f","🚵🏻‍♀️":"1f6b5-1f3fb-200d-2640-fe0f","🚵🏼‍♀️":"1f6b5-1f3fc-200d-2640-fe0f","🚵🏽‍♀️":"1f6b5-1f3fd-200d-2640-fe0f","🚵🏾‍♀️":"1f6b5-1f3fe-200d-2640-fe0f","🚵🏿‍♀️":"1f6b5-1f3ff-200d-2640-fe0f","⛹🏿‍♀️":"26f9-1f3ff-200d-2640-fe0f","⛹🏾‍♀️":"26f9-1f3fe-200d-2640-fe0f","⛹🏽‍♀️":"26f9-1f3fd-200d-2640-fe0f","🤸🏻‍♂️":"1f938-1f3fb-200d-2642-fe0f","🤸🏼‍♂️":"1f938-1f3fc-200d-2642-fe0f","🤸🏽‍♂️":"1f938-1f3fd-200d-2642-fe0f","🤸🏾‍♂️":"1f938-1f3fe-200d-2642-fe0f","🤸🏿‍♂️":"1f938-1f3ff-200d-2642-fe0f","🧔🏾‍♂️":"1f9d4-1f3fe-200d-2642-fe0f","🤸🏻‍♀️":"1f938-1f3fb-200d-2640-fe0f","🤸🏼‍♀️":"1f938-1f3fc-200d-2640-fe0f","🤸🏽‍♀️":"1f938-1f3fd-200d-2640-fe0f","🤸🏾‍♀️":"1f938-1f3fe-200d-2640-fe0f","🤸🏿‍♀️":"1f938-1f3ff-200d-2640-fe0f","🧔🏽‍♂️":"1f9d4-1f3fd-200d-2642-fe0f","⛹🏼‍♀️":"26f9-1f3fc-200d-2640-fe0f","⛹🏻‍♀️":"26f9-1f3fb-200d-2640-fe0f","⛹️‍♀️":"26f9-fe0f-200d-2640-fe0f","🤽🏻‍♂️":"1f93d-1f3fb-200d-2642-fe0f","🤽🏼‍♂️":"1f93d-1f3fc-200d-2642-fe0f","🤽🏽‍♂️":"1f93d-1f3fd-200d-2642-fe0f","🤽🏾‍♂️":"1f93d-1f3fe-200d-2642-fe0f","🤽🏿‍♂️":"1f93d-1f3ff-200d-2642-fe0f","🧔🏼‍♂️":"1f9d4-1f3fc-200d-2642-fe0f","🤽🏻‍♀️":"1f93d-1f3fb-200d-2640-fe0f","🤽🏼‍♀️":"1f93d-1f3fc-200d-2640-fe0f","🤽🏽‍♀️":"1f93d-1f3fd-200d-2640-fe0f","🤽🏾‍♀️":"1f93d-1f3fe-200d-2640-fe0f","🤽🏿‍♀️":"1f93d-1f3ff-200d-2640-fe0f","⛹🏿‍♂️":"26f9-1f3ff-200d-2642-fe0f","⛹🏾‍♂️":"26f9-1f3fe-200d-2642-fe0f","⛹🏽‍♂️":"26f9-1f3fd-200d-2642-fe0f","🤾🏻‍♂️":"1f93e-1f3fb-200d-2642-fe0f","🤾🏼‍♂️":"1f93e-1f3fc-200d-2642-fe0f","🤾🏽‍♂️":"1f93e-1f3fd-200d-2642-fe0f","🤾🏾‍♂️":"1f93e-1f3fe-200d-2642-fe0f","🤾🏿‍♂️":"1f93e-1f3ff-200d-2642-fe0f","🧔🏻‍♂️":"1f9d4-1f3fb-200d-2642-fe0f","🤾🏻‍♀️":"1f93e-1f3fb-200d-2640-fe0f","🤾🏼‍♀️":"1f93e-1f3fc-200d-2640-fe0f","🤾🏽‍♀️":"1f93e-1f3fd-200d-2640-fe0f","🤾🏾‍♀️":"1f93e-1f3fe-200d-2640-fe0f","🤾🏿‍♀️":"1f93e-1f3ff-200d-2640-fe0f","⛹🏼‍♂️":"26f9-1f3fc-200d-2642-fe0f","⛹🏻‍♂️":"26f9-1f3fb-200d-2642-fe0f","🤹🏻‍♂️":"1f939-1f3fb-200d-2642-fe0f","🤹🏼‍♂️":"1f939-1f3fc-200d-2642-fe0f","🤹🏽‍♂️":"1f939-1f3fd-200d-2642-fe0f","🤹🏾‍♂️":"1f939-1f3fe-200d-2642-fe0f","🤹🏿‍♂️":"1f939-1f3ff-200d-2642-fe0f","🫱🏿‍🫲🏾":"1faf1-1f3ff-200d-1faf2-1f3fe","🤹🏻‍♀️":"1f939-1f3fb-200d-2640-fe0f","🤹🏼‍♀️":"1f939-1f3fc-200d-2640-fe0f","🤹🏽‍♀️":"1f939-1f3fd-200d-2640-fe0f","🤹🏾‍♀️":"1f939-1f3fe-200d-2640-fe0f","🤹🏿‍♀️":"1f939-1f3ff-200d-2640-fe0f","⛹️‍♂️":"26f9-fe0f-200d-2642-fe0f","🏊🏿‍♀️":"1f3ca-1f3ff-200d-2640-fe0f","🏊🏾‍♀️":"1f3ca-1f3fe-200d-2640-fe0f","🫱🏿‍🫲🏽":"1faf1-1f3ff-200d-1faf2-1f3fd","🧘🏻‍♂️":"1f9d8-1f3fb-200d-2642-fe0f","🧘🏼‍♂️":"1f9d8-1f3fc-200d-2642-fe0f","🧘🏽‍♂️":"1f9d8-1f3fd-200d-2642-fe0f","🧘🏾‍♂️":"1f9d8-1f3fe-200d-2642-fe0f","🧘🏿‍♂️":"1f9d8-1f3ff-200d-2642-fe0f","🫱🏿‍🫲🏼":"1faf1-1f3ff-200d-1faf2-1f3fc","🧘🏻‍♀️":"1f9d8-1f3fb-200d-2640-fe0f","🧘🏼‍♀️":"1f9d8-1f3fc-200d-2640-fe0f","🧘🏽‍♀️":"1f9d8-1f3fd-200d-2640-fe0f","🧘🏾‍♀️":"1f9d8-1f3fe-200d-2640-fe0f","🧘🏿‍♀️":"1f9d8-1f3ff-200d-2640-fe0f","🏊🏽‍♀️":"1f3ca-1f3fd-200d-2640-fe0f","🏊🏼‍♀️":"1f3ca-1f3fc-200d-2640-fe0f","🏊🏻‍♀️":"1f3ca-1f3fb-200d-2640-fe0f","🏊🏿‍♂️":"1f3ca-1f3ff-200d-2642-fe0f","🏊🏾‍♂️":"1f3ca-1f3fe-200d-2642-fe0f","🏊🏽‍♂️":"1f3ca-1f3fd-200d-2642-fe0f","🧑‍🤝‍🧑":"1f9d1-200d-1f91d-200d-1f9d1","🏊🏼‍♂️":"1f3ca-1f3fc-200d-2642-fe0f","🏊🏻‍♂️":"1f3ca-1f3fb-200d-2642-fe0f","🚣🏿‍♀️":"1f6a3-1f3ff-200d-2640-fe0f","🚣🏾‍♀️":"1f6a3-1f3fe-200d-2640-fe0f","🚣🏽‍♀️":"1f6a3-1f3fd-200d-2640-fe0f","🚣🏼‍♀️":"1f6a3-1f3fc-200d-2640-fe0f","🚣🏻‍♀️":"1f6a3-1f3fb-200d-2640-fe0f","🚣🏿‍♂️":"1f6a3-1f3ff-200d-2642-fe0f","🚣🏾‍♂️":"1f6a3-1f3fe-200d-2642-fe0f","🚣🏽‍♂️":"1f6a3-1f3fd-200d-2642-fe0f","🚣🏼‍♂️":"1f6a3-1f3fc-200d-2642-fe0f","🚣🏻‍♂️":"1f6a3-1f3fb-200d-2642-fe0f","🏄🏿‍♀️":"1f3c4-1f3ff-200d-2640-fe0f","👩‍❤‍👨":"1f469-200d-2764-fe0f-200d-1f468","👨‍❤‍👨":"1f468-200d-2764-fe0f-200d-1f468","👩‍❤‍👩":"1f469-200d-2764-fe0f-200d-1f469","👨‍👩‍👦":"1f468-200d-1f469-200d-1f466","👨‍👩‍👧":"1f468-200d-1f469-200d-1f467","👨‍👨‍👦":"1f468-200d-1f468-200d-1f466","👨‍👨‍👧":"1f468-200d-1f468-200d-1f467","👩‍👩‍👦":"1f469-200d-1f469-200d-1f466","👩‍👩‍👧":"1f469-200d-1f469-200d-1f467","🫱🏿‍🫲🏻":"1faf1-1f3ff-200d-1faf2-1f3fb","👨‍👦‍👦":"1f468-200d-1f466-200d-1f466","🫱🏾‍🫲🏿":"1faf1-1f3fe-200d-1faf2-1f3ff","👨‍👧‍👦":"1f468-200d-1f467-200d-1f466","👨‍👧‍👧":"1f468-200d-1f467-200d-1f467","🫱🏾‍🫲🏽":"1faf1-1f3fe-200d-1faf2-1f3fd","👩‍👦‍👦":"1f469-200d-1f466-200d-1f466","🫱🏾‍🫲🏼":"1faf1-1f3fe-200d-1faf2-1f3fc","👩‍👧‍👦":"1f469-200d-1f467-200d-1f466","👩‍👧‍👧":"1f469-200d-1f467-200d-1f467","🏄🏾‍♀️":"1f3c4-1f3fe-200d-2640-fe0f","🏄🏽‍♀️":"1f3c4-1f3fd-200d-2640-fe0f","🏄🏼‍♀️":"1f3c4-1f3fc-200d-2640-fe0f","🧑‍🧑‍🧒":"1f9d1-200d-1f9d1-200d-1f9d2","🫱🏾‍🫲🏻":"1faf1-1f3fe-200d-1faf2-1f3fb","🧑‍🧒‍🧒":"1f9d1-200d-1f9d2-200d-1f9d2","🏄🏻‍♀️":"1f3c4-1f3fb-200d-2640-fe0f","🏄🏿‍♂️":"1f3c4-1f3ff-200d-2642-fe0f","🏄🏾‍♂️":"1f3c4-1f3fe-200d-2642-fe0f","🏄🏽‍♂️":"1f3c4-1f3fd-200d-2642-fe0f","🏄🏼‍♂️":"1f3c4-1f3fc-200d-2642-fe0f","🏄🏻‍♂️":"1f3c4-1f3fb-200d-2642-fe0f","🏌🏿‍♀️":"1f3cc-1f3ff-200d-2640-fe0f","🏌🏾‍♀️":"1f3cc-1f3fe-200d-2640-fe0f","🫱🏽‍🫲🏿":"1faf1-1f3fd-200d-1faf2-1f3ff","🏌🏽‍♀️":"1f3cc-1f3fd-200d-2640-fe0f","🏌🏼‍♀️":"1f3cc-1f3fc-200d-2640-fe0f","🏌🏻‍♀️":"1f3cc-1f3fb-200d-2640-fe0f","🫱🏽‍🫲🏾":"1faf1-1f3fd-200d-1faf2-1f3fe","🏌️‍♀️":"1f3cc-fe0f-200d-2640-fe0f","🏌🏿‍♂️":"1f3cc-1f3ff-200d-2642-fe0f","🏌🏾‍♂️":"1f3cc-1f3fe-200d-2642-fe0f","🏌🏽‍♂️":"1f3cc-1f3fd-200d-2642-fe0f","🏌🏼‍♂️":"1f3cc-1f3fc-200d-2642-fe0f","🏌🏻‍♂️":"1f3cc-1f3fb-200d-2642-fe0f","🏌️‍♂️":"1f3cc-fe0f-200d-2642-fe0f","🧗🏿‍♀️":"1f9d7-1f3ff-200d-2640-fe0f","🧗🏾‍♀️":"1f9d7-1f3fe-200d-2640-fe0f","🧗🏽‍♀️":"1f9d7-1f3fd-200d-2640-fe0f","🧗🏼‍♀️":"1f9d7-1f3fc-200d-2640-fe0f","🧗🏻‍♀️":"1f9d7-1f3fb-200d-2640-fe0f","🧗🏿‍♂️":"1f9d7-1f3ff-200d-2642-fe0f","🧗🏾‍♂️":"1f9d7-1f3fe-200d-2642-fe0f","🧗🏽‍♂️":"1f9d7-1f3fd-200d-2642-fe0f","🧗🏼‍♂️":"1f9d7-1f3fc-200d-2642-fe0f","🧗🏻‍♂️":"1f9d7-1f3fb-200d-2642-fe0f","🧖🏿‍♀️":"1f9d6-1f3ff-200d-2640-fe0f","🧖🏾‍♀️":"1f9d6-1f3fe-200d-2640-fe0f","🧖🏽‍♀️":"1f9d6-1f3fd-200d-2640-fe0f","🫱🏽‍🫲🏼":"1faf1-1f3fd-200d-1faf2-1f3fc","🧖🏼‍♀️":"1f9d6-1f3fc-200d-2640-fe0f","🧖🏻‍♀️":"1f9d6-1f3fb-200d-2640-fe0f","🧖🏿‍♂️":"1f9d6-1f3ff-200d-2642-fe0f","🧖🏾‍♂️":"1f9d6-1f3fe-200d-2642-fe0f","🧖🏽‍♂️":"1f9d6-1f3fd-200d-2642-fe0f","🧖🏼‍♂️":"1f9d6-1f3fc-200d-2642-fe0f","🧖🏻‍♂️":"1f9d6-1f3fb-200d-2642-fe0f","🫱🏽‍🫲🏻":"1faf1-1f3fd-200d-1faf2-1f3fb","🫱🏼‍🫲🏿":"1faf1-1f3fc-200d-1faf2-1f3ff","🏃‍♂‍➡":"1f3c3-200d-2642-fe0f-200d-27a1-fe0f","🏃‍♀‍➡":"1f3c3-200d-2640-fe0f-200d-27a1-fe0f","🏃🏿‍➡️":"1f3c3-1f3ff-200d-27a1-fe0f","🏃🏾‍➡️":"1f3c3-1f3fe-200d-27a1-fe0f","🏃🏽‍➡️":"1f3c3-1f3fd-200d-27a1-fe0f","🏃🏼‍➡️":"1f3c3-1f3fc-200d-27a1-fe0f","🏃🏻‍➡️":"1f3c3-1f3fb-200d-27a1-fe0f","🏃🏿‍♀️":"1f3c3-1f3ff-200d-2640-fe0f","🏃🏾‍♀️":"1f3c3-1f3fe-200d-2640-fe0f","🏃🏽‍♀️":"1f3c3-1f3fd-200d-2640-fe0f","🏃🏼‍♀️":"1f3c3-1f3fc-200d-2640-fe0f","🏃🏻‍♀️":"1f3c3-1f3fb-200d-2640-fe0f","🏃🏿‍♂️":"1f3c3-1f3ff-200d-2642-fe0f","🏃🏾‍♂️":"1f3c3-1f3fe-200d-2642-fe0f","🏃🏽‍♂️":"1f3c3-1f3fd-200d-2642-fe0f","🏃🏼‍♂️":"1f3c3-1f3fc-200d-2642-fe0f","🏃🏻‍♂️":"1f3c3-1f3fb-200d-2642-fe0f","🫱🏼‍🫲🏾":"1faf1-1f3fc-200d-1faf2-1f3fe","👩‍🦽‍➡":"1f469-200d-1f9bd-200d-27a1-fe0f","👨‍🦽‍➡":"1f468-200d-1f9bd-200d-27a1-fe0f","🫱🏼‍🫲🏽":"1faf1-1f3fc-200d-1faf2-1f3fd","🧑‍🦽‍➡":"1f9d1-200d-1f9bd-200d-27a1-fe0f","👩‍🦼‍➡":"1f469-200d-1f9bc-200d-27a1-fe0f","👨‍🦼‍➡":"1f468-200d-1f9bc-200d-27a1-fe0f","🧑‍🦼‍➡":"1f9d1-200d-1f9bc-200d-27a1-fe0f","👩‍🦯‍➡":"1f469-200d-1f9af-200d-27a1-fe0f","👨‍🦯‍➡":"1f468-200d-1f9af-200d-27a1-fe0f","🧑‍🦯‍➡":"1f9d1-200d-1f9af-200d-27a1-fe0f","🧎‍♂‍➡":"1f9ce-200d-2642-fe0f-200d-27a1-fe0f","🧎‍♀‍➡":"1f9ce-200d-2640-fe0f-200d-27a1-fe0f","🧎🏿‍➡️":"1f9ce-1f3ff-200d-27a1-fe0f","🧎🏾‍➡️":"1f9ce-1f3fe-200d-27a1-fe0f","🧎🏽‍➡️":"1f9ce-1f3fd-200d-27a1-fe0f","🧎🏼‍➡️":"1f9ce-1f3fc-200d-27a1-fe0f","🧎🏻‍➡️":"1f9ce-1f3fb-200d-27a1-fe0f","🧎🏿‍♀️":"1f9ce-1f3ff-200d-2640-fe0f","🧎🏾‍♀️":"1f9ce-1f3fe-200d-2640-fe0f","🧎🏽‍♀️":"1f9ce-1f3fd-200d-2640-fe0f","🧎🏼‍♀️":"1f9ce-1f3fc-200d-2640-fe0f","🧎🏻‍♀️":"1f9ce-1f3fb-200d-2640-fe0f","🧎🏿‍♂️":"1f9ce-1f3ff-200d-2642-fe0f","🧎🏾‍♂️":"1f9ce-1f3fe-200d-2642-fe0f","🧎🏽‍♂️":"1f9ce-1f3fd-200d-2642-fe0f","🧎🏼‍♂️":"1f9ce-1f3fc-200d-2642-fe0f","🧎🏻‍♂️":"1f9ce-1f3fb-200d-2642-fe0f","🧍🏿‍♀️":"1f9cd-1f3ff-200d-2640-fe0f","🧍🏾‍♀️":"1f9cd-1f3fe-200d-2640-fe0f","🧍🏽‍♀️":"1f9cd-1f3fd-200d-2640-fe0f","🧍🏼‍♀️":"1f9cd-1f3fc-200d-2640-fe0f","🧍🏻‍♀️":"1f9cd-1f3fb-200d-2640-fe0f","🧍🏿‍♂️":"1f9cd-1f3ff-200d-2642-fe0f","🧍🏾‍♂️":"1f9cd-1f3fe-200d-2642-fe0f","🧍🏽‍♂️":"1f9cd-1f3fd-200d-2642-fe0f","🧍🏼‍♂️":"1f9cd-1f3fc-200d-2642-fe0f","🧍🏻‍♂️":"1f9cd-1f3fb-200d-2642-fe0f","🚶‍♂‍➡":"1f6b6-200d-2642-fe0f-200d-27a1-fe0f","🚶‍♀‍➡":"1f6b6-200d-2640-fe0f-200d-27a1-fe0f","🚶🏿‍➡️":"1f6b6-1f3ff-200d-27a1-fe0f","🚶🏾‍➡️":"1f6b6-1f3fe-200d-27a1-fe0f","🚶🏽‍➡️":"1f6b6-1f3fd-200d-27a1-fe0f","🚶🏼‍➡️":"1f6b6-1f3fc-200d-27a1-fe0f","🚶🏻‍➡️":"1f6b6-1f3fb-200d-27a1-fe0f","🚶🏿‍♀️":"1f6b6-1f3ff-200d-2640-fe0f","🚶🏾‍♀️":"1f6b6-1f3fe-200d-2640-fe0f","🚶🏽‍♀️":"1f6b6-1f3fd-200d-2640-fe0f","🚶🏼‍♀️":"1f6b6-1f3fc-200d-2640-fe0f","🚶🏻‍♀️":"1f6b6-1f3fb-200d-2640-fe0f","🚶🏿‍♂️":"1f6b6-1f3ff-200d-2642-fe0f","🚶🏾‍♂️":"1f6b6-1f3fe-200d-2642-fe0f","🚶🏽‍♂️":"1f6b6-1f3fd-200d-2642-fe0f","🚶🏼‍♂️":"1f6b6-1f3fc-200d-2642-fe0f","🚶🏻‍♂️":"1f6b6-1f3fb-200d-2642-fe0f","💇🏿‍♀️":"1f487-1f3ff-200d-2640-fe0f","💇🏾‍♀️":"1f487-1f3fe-200d-2640-fe0f","💇🏽‍♀️":"1f487-1f3fd-200d-2640-fe0f","💇🏼‍♀️":"1f487-1f3fc-200d-2640-fe0f","💇🏻‍♀️":"1f487-1f3fb-200d-2640-fe0f","💇🏿‍♂️":"1f487-1f3ff-200d-2642-fe0f","💇🏾‍♂️":"1f487-1f3fe-200d-2642-fe0f","💇🏽‍♂️":"1f487-1f3fd-200d-2642-fe0f","💇🏼‍♂️":"1f487-1f3fc-200d-2642-fe0f","💇🏻‍♂️":"1f487-1f3fb-200d-2642-fe0f","💆🏿‍♀️":"1f486-1f3ff-200d-2640-fe0f","💆🏾‍♀️":"1f486-1f3fe-200d-2640-fe0f","💆🏽‍♀️":"1f486-1f3fd-200d-2640-fe0f","💆🏼‍♀️":"1f486-1f3fc-200d-2640-fe0f","💆🏻‍♀️":"1f486-1f3fb-200d-2640-fe0f","💆🏿‍♂️":"1f486-1f3ff-200d-2642-fe0f","💆🏾‍♂️":"1f486-1f3fe-200d-2642-fe0f","💆🏽‍♂️":"1f486-1f3fd-200d-2642-fe0f","💆🏼‍♂️":"1f486-1f3fc-200d-2642-fe0f","💆🏻‍♂️":"1f486-1f3fb-200d-2642-fe0f","🧝🏿‍♀️":"1f9dd-1f3ff-200d-2640-fe0f","🧝🏾‍♀️":"1f9dd-1f3fe-200d-2640-fe0f","🧝🏽‍♀️":"1f9dd-1f3fd-200d-2640-fe0f","🧝🏼‍♀️":"1f9dd-1f3fc-200d-2640-fe0f","🧝🏻‍♀️":"1f9dd-1f3fb-200d-2640-fe0f","🧝🏿‍♂️":"1f9dd-1f3ff-200d-2642-fe0f","🧝🏾‍♂️":"1f9dd-1f3fe-200d-2642-fe0f","🧝🏽‍♂️":"1f9dd-1f3fd-200d-2642-fe0f","🧝🏼‍♂️":"1f9dd-1f3fc-200d-2642-fe0f","🧝🏻‍♂️":"1f9dd-1f3fb-200d-2642-fe0f","🧜🏿‍♀️":"1f9dc-1f3ff-200d-2640-fe0f","🧜🏾‍♀️":"1f9dc-1f3fe-200d-2640-fe0f","🧜🏽‍♀️":"1f9dc-1f3fd-200d-2640-fe0f","🧜🏼‍♀️":"1f9dc-1f3fc-200d-2640-fe0f","🧜🏻‍♀️":"1f9dc-1f3fb-200d-2640-fe0f","🧜🏿‍♂️":"1f9dc-1f3ff-200d-2642-fe0f","🧜🏾‍♂️":"1f9dc-1f3fe-200d-2642-fe0f","🧜🏽‍♂️":"1f9dc-1f3fd-200d-2642-fe0f","🧜🏼‍♂️":"1f9dc-1f3fc-200d-2642-fe0f","🧜🏻‍♂️":"1f9dc-1f3fb-200d-2642-fe0f","🧛🏿‍♀️":"1f9db-1f3ff-200d-2640-fe0f","🧛🏾‍♀️":"1f9db-1f3fe-200d-2640-fe0f","🧛🏽‍♀️":"1f9db-1f3fd-200d-2640-fe0f","🧛🏼‍♀️":"1f9db-1f3fc-200d-2640-fe0f","🧛🏻‍♀️":"1f9db-1f3fb-200d-2640-fe0f","🧛🏿‍♂️":"1f9db-1f3ff-200d-2642-fe0f","🧛🏾‍♂️":"1f9db-1f3fe-200d-2642-fe0f","🧛🏽‍♂️":"1f9db-1f3fd-200d-2642-fe0f","🧛🏼‍♂️":"1f9db-1f3fc-200d-2642-fe0f","🧛🏻‍♂️":"1f9db-1f3fb-200d-2642-fe0f","🧚🏿‍♀️":"1f9da-1f3ff-200d-2640-fe0f","🧚🏾‍♀️":"1f9da-1f3fe-200d-2640-fe0f","🧚🏽‍♀️":"1f9da-1f3fd-200d-2640-fe0f","🧚🏼‍♀️":"1f9da-1f3fc-200d-2640-fe0f","🧚🏻‍♀️":"1f9da-1f3fb-200d-2640-fe0f","🧚🏿‍♂️":"1f9da-1f3ff-200d-2642-fe0f","🧚🏾‍♂️":"1f9da-1f3fe-200d-2642-fe0f","🧚🏽‍♂️":"1f9da-1f3fd-200d-2642-fe0f","🧚🏼‍♂️":"1f9da-1f3fc-200d-2642-fe0f","🧚🏻‍♂️":"1f9da-1f3fb-200d-2642-fe0f","🧙🏿‍♀️":"1f9d9-1f3ff-200d-2640-fe0f","🧙🏾‍♀️":"1f9d9-1f3fe-200d-2640-fe0f","🧙🏽‍♀️":"1f9d9-1f3fd-200d-2640-fe0f","🧙🏼‍♀️":"1f9d9-1f3fc-200d-2640-fe0f","🧙🏻‍♀️":"1f9d9-1f3fb-200d-2640-fe0f","🧙🏿‍♂️":"1f9d9-1f3ff-200d-2642-fe0f","🧙🏾‍♂️":"1f9d9-1f3fe-200d-2642-fe0f","🧙🏽‍♂️":"1f9d9-1f3fd-200d-2642-fe0f","🧙🏼‍♂️":"1f9d9-1f3fc-200d-2642-fe0f","🧙🏻‍♂️":"1f9d9-1f3fb-200d-2642-fe0f","🦹🏿‍♀️":"1f9b9-1f3ff-200d-2640-fe0f","🦹🏾‍♀️":"1f9b9-1f3fe-200d-2640-fe0f","🦹🏽‍♀️":"1f9b9-1f3fd-200d-2640-fe0f","🦹🏼‍♀️":"1f9b9-1f3fc-200d-2640-fe0f","🦹🏻‍♀️":"1f9b9-1f3fb-200d-2640-fe0f","🦹🏿‍♂️":"1f9b9-1f3ff-200d-2642-fe0f","🦹🏾‍♂️":"1f9b9-1f3fe-200d-2642-fe0f","🦹🏽‍♂️":"1f9b9-1f3fd-200d-2642-fe0f","🦹🏼‍♂️":"1f9b9-1f3fc-200d-2642-fe0f","🦹🏻‍♂️":"1f9b9-1f3fb-200d-2642-fe0f","🦸🏿‍♀️":"1f9b8-1f3ff-200d-2640-fe0f","🦸🏾‍♀️":"1f9b8-1f3fe-200d-2640-fe0f","🦸🏽‍♀️":"1f9b8-1f3fd-200d-2640-fe0f","🏋🏼‍♂️":"1f3cb-1f3fc-200d-2642-fe0f","🦸🏻‍♀️":"1f9b8-1f3fb-200d-2640-fe0f","🦸🏿‍♂️":"1f9b8-1f3ff-200d-2642-fe0f","🦸🏾‍♂️":"1f9b8-1f3fe-200d-2642-fe0f","🦸🏽‍♂️":"1f9b8-1f3fd-200d-2642-fe0f","🦸🏼‍♂️":"1f9b8-1f3fc-200d-2642-fe0f","🦸🏻‍♂️":"1f9b8-1f3fb-200d-2642-fe0f","👰🏿‍♀️":"1f470-1f3ff-200d-2640-fe0f","👰🏾‍♀️":"1f470-1f3fe-200d-2640-fe0f","👰🏽‍♀️":"1f470-1f3fd-200d-2640-fe0f","👰🏼‍♀️":"1f470-1f3fc-200d-2640-fe0f","👰🏻‍♀️":"1f470-1f3fb-200d-2640-fe0f","👰🏿‍♂️":"1f470-1f3ff-200d-2642-fe0f","👰🏾‍♂️":"1f470-1f3fe-200d-2642-fe0f","👰🏽‍♂️":"1f470-1f3fd-200d-2642-fe0f","👰🏼‍♂️":"1f470-1f3fc-200d-2642-fe0f","👰🏻‍♂️":"1f470-1f3fb-200d-2642-fe0f","🤵🏿‍♀️":"1f935-1f3ff-200d-2640-fe0f","🤵🏾‍♀️":"1f935-1f3fe-200d-2640-fe0f","🤵🏽‍♀️":"1f935-1f3fd-200d-2640-fe0f","🤵🏼‍♀️":"1f935-1f3fc-200d-2640-fe0f","🤵🏻‍♀️":"1f935-1f3fb-200d-2640-fe0f","🤵🏿‍♂️":"1f935-1f3ff-200d-2642-fe0f","🤵🏾‍♂️":"1f935-1f3fe-200d-2642-fe0f","🤵🏽‍♂️":"1f935-1f3fd-200d-2642-fe0f","🤵🏼‍♂️":"1f935-1f3fc-200d-2642-fe0f","🤵🏻‍♂️":"1f935-1f3fb-200d-2642-fe0f","👳🏿‍♀️":"1f473-1f3ff-200d-2640-fe0f","👳🏾‍♀️":"1f473-1f3fe-200d-2640-fe0f","👳🏽‍♀️":"1f473-1f3fd-200d-2640-fe0f","👳🏼‍♀️":"1f473-1f3fc-200d-2640-fe0f","👳🏻‍♀️":"1f473-1f3fb-200d-2640-fe0f","👳🏿‍♂️":"1f473-1f3ff-200d-2642-fe0f","👳🏾‍♂️":"1f473-1f3fe-200d-2642-fe0f","👳🏽‍♂️":"1f473-1f3fd-200d-2642-fe0f","👳🏼‍♂️":"1f473-1f3fc-200d-2642-fe0f","👳🏻‍♂️":"1f473-1f3fb-200d-2642-fe0f","👷🏿‍♀️":"1f477-1f3ff-200d-2640-fe0f","👷🏾‍♀️":"1f477-1f3fe-200d-2640-fe0f","👷🏽‍♀️":"1f477-1f3fd-200d-2640-fe0f","👷🏼‍♀️":"1f477-1f3fc-200d-2640-fe0f","👷🏻‍♀️":"1f477-1f3fb-200d-2640-fe0f","👷🏿‍♂️":"1f477-1f3ff-200d-2642-fe0f","👷🏾‍♂️":"1f477-1f3fe-200d-2642-fe0f","👷🏽‍♂️":"1f477-1f3fd-200d-2642-fe0f","👷🏼‍♂️":"1f477-1f3fc-200d-2642-fe0f","👷🏻‍♂️":"1f477-1f3fb-200d-2642-fe0f","💂🏿‍♀️":"1f482-1f3ff-200d-2640-fe0f","💂🏾‍♀️":"1f482-1f3fe-200d-2640-fe0f","💂🏽‍♀️":"1f482-1f3fd-200d-2640-fe0f","💂🏼‍♀️":"1f482-1f3fc-200d-2640-fe0f","💂🏻‍♀️":"1f482-1f3fb-200d-2640-fe0f","💂🏿‍♂️":"1f482-1f3ff-200d-2642-fe0f","💂🏾‍♂️":"1f482-1f3fe-200d-2642-fe0f","💂🏽‍♂️":"1f482-1f3fd-200d-2642-fe0f","💂🏼‍♂️":"1f482-1f3fc-200d-2642-fe0f","💂🏻‍♂️":"1f482-1f3fb-200d-2642-fe0f","🕵🏿‍♀️":"1f575-1f3ff-200d-2640-fe0f","🕵🏾‍♀️":"1f575-1f3fe-200d-2640-fe0f","🕵🏽‍♀️":"1f575-1f3fd-200d-2640-fe0f","🕵🏼‍♀️":"1f575-1f3fc-200d-2640-fe0f","🕵🏻‍♀️":"1f575-1f3fb-200d-2640-fe0f","🕵️‍♀️":"1f575-fe0f-200d-2640-fe0f","🕵🏿‍♂️":"1f575-1f3ff-200d-2642-fe0f","🕵🏾‍♂️":"1f575-1f3fe-200d-2642-fe0f","🕵🏽‍♂️":"1f575-1f3fd-200d-2642-fe0f","🕵🏼‍♂️":"1f575-1f3fc-200d-2642-fe0f","🕵🏻‍♂️":"1f575-1f3fb-200d-2642-fe0f","🕵️‍♂️":"1f575-fe0f-200d-2642-fe0f","👮🏿‍♀️":"1f46e-1f3ff-200d-2640-fe0f","👮🏾‍♀️":"1f46e-1f3fe-200d-2640-fe0f","👮🏽‍♀️":"1f46e-1f3fd-200d-2640-fe0f","👮🏼‍♀️":"1f46e-1f3fc-200d-2640-fe0f","👮🏻‍♀️":"1f46e-1f3fb-200d-2640-fe0f","👮🏿‍♂️":"1f46e-1f3ff-200d-2642-fe0f","👮🏾‍♂️":"1f46e-1f3fe-200d-2642-fe0f","👮🏽‍♂️":"1f46e-1f3fd-200d-2642-fe0f","👮🏼‍♂️":"1f46e-1f3fc-200d-2642-fe0f","👮🏻‍♂️":"1f46e-1f3fb-200d-2642-fe0f","🫱🏼‍🫲🏻":"1faf1-1f3fc-200d-1faf2-1f3fb","👩🏿‍✈️":"1f469-1f3ff-200d-2708-fe0f","👩🏾‍✈️":"1f469-1f3fe-200d-2708-fe0f","👩🏽‍✈️":"1f469-1f3fd-200d-2708-fe0f","👩🏼‍✈️":"1f469-1f3fc-200d-2708-fe0f","👩🏻‍✈️":"1f469-1f3fb-200d-2708-fe0f","👨🏿‍✈️":"1f468-1f3ff-200d-2708-fe0f","👨🏾‍✈️":"1f468-1f3fe-200d-2708-fe0f","👨🏽‍✈️":"1f468-1f3fd-200d-2708-fe0f","👨🏼‍✈️":"1f468-1f3fc-200d-2708-fe0f","👨🏻‍✈️":"1f468-1f3fb-200d-2708-fe0f","🧑🏿‍✈️":"1f9d1-1f3ff-200d-2708-fe0f","🧑🏾‍✈️":"1f9d1-1f3fe-200d-2708-fe0f","🧑🏽‍✈️":"1f9d1-1f3fd-200d-2708-fe0f","🧑🏼‍✈️":"1f9d1-1f3fc-200d-2708-fe0f","🧑🏻‍✈️":"1f9d1-1f3fb-200d-2708-fe0f","👩🏿‍⚖️":"1f469-1f3ff-200d-2696-fe0f","👩🏾‍⚖️":"1f469-1f3fe-200d-2696-fe0f","👩🏽‍⚖️":"1f469-1f3fd-200d-2696-fe0f","👩🏼‍⚖️":"1f469-1f3fc-200d-2696-fe0f","👩🏻‍⚖️":"1f469-1f3fb-200d-2696-fe0f","👨🏿‍⚖️":"1f468-1f3ff-200d-2696-fe0f","👨🏾‍⚖️":"1f468-1f3fe-200d-2696-fe0f","👨🏽‍⚖️":"1f468-1f3fd-200d-2696-fe0f","👨🏼‍⚖️":"1f468-1f3fc-200d-2696-fe0f","👨🏻‍⚖️":"1f468-1f3fb-200d-2696-fe0f","🧑🏿‍⚖️":"1f9d1-1f3ff-200d-2696-fe0f","🧑🏾‍⚖️":"1f9d1-1f3fe-200d-2696-fe0f","🧑🏽‍⚖️":"1f9d1-1f3fd-200d-2696-fe0f","🧑🏼‍⚖️":"1f9d1-1f3fc-200d-2696-fe0f","🧑🏻‍⚖️":"1f9d1-1f3fb-200d-2696-fe0f","👩🏿‍⚕️":"1f469-1f3ff-200d-2695-fe0f","👩🏾‍⚕️":"1f469-1f3fe-200d-2695-fe0f","👩🏽‍⚕️":"1f469-1f3fd-200d-2695-fe0f","👩🏼‍⚕️":"1f469-1f3fc-200d-2695-fe0f","👩🏻‍⚕️":"1f469-1f3fb-200d-2695-fe0f","👨🏿‍⚕️":"1f468-1f3ff-200d-2695-fe0f","👨🏾‍⚕️":"1f468-1f3fe-200d-2695-fe0f","👨🏽‍⚕️":"1f468-1f3fd-200d-2695-fe0f","👨🏼‍⚕️":"1f468-1f3fc-200d-2695-fe0f","👨🏻‍⚕️":"1f468-1f3fb-200d-2695-fe0f","🫱🏻‍🫲🏿":"1faf1-1f3fb-200d-1faf2-1f3ff","🫱🏻‍🫲🏾":"1faf1-1f3fb-200d-1faf2-1f3fe","🫱🏻‍🫲🏽":"1faf1-1f3fb-200d-1faf2-1f3fd","🧑🏿‍⚕️":"1f9d1-1f3ff-200d-2695-fe0f","🫱🏻‍🫲🏼":"1faf1-1f3fb-200d-1faf2-1f3fc","🧑🏾‍⚕️":"1f9d1-1f3fe-200d-2695-fe0f","👁️‍🗨️":"1f441-200d-1f5e8","🧑🏽‍⚕️":"1f9d1-1f3fd-200d-2695-fe0f","🧑🏼‍⚕️":"1f9d1-1f3fc-200d-2695-fe0f","🧑🏻‍⚕️":"1f9d1-1f3fb-200d-2695-fe0f","🤷🏿‍♀️":"1f937-1f3ff-200d-2640-fe0f","🤷🏾‍♀️":"1f937-1f3fe-200d-2640-fe0f","🤷🏽‍♀️":"1f937-1f3fd-200d-2640-fe0f","🤷🏼‍♀️":"1f937-1f3fc-200d-2640-fe0f","🤷🏻‍♀️":"1f937-1f3fb-200d-2640-fe0f","🤷🏿‍♂️":"1f937-1f3ff-200d-2642-fe0f","🤷🏾‍♂️":"1f937-1f3fe-200d-2642-fe0f","🤷🏽‍♂️":"1f937-1f3fd-200d-2642-fe0f","🤷🏼‍♂️":"1f937-1f3fc-200d-2642-fe0f","🤷🏻‍♂️":"1f937-1f3fb-200d-2642-fe0f","🤦🏿‍♀️":"1f926-1f3ff-200d-2640-fe0f","🤦🏾‍♀️":"1f926-1f3fe-200d-2640-fe0f","🤦🏽‍♀️":"1f926-1f3fd-200d-2640-fe0f","🤦🏼‍♀️":"1f926-1f3fc-200d-2640-fe0f","🤦🏻‍♀️":"1f926-1f3fb-200d-2640-fe0f","🤦🏿‍♂️":"1f926-1f3ff-200d-2642-fe0f","🤦🏾‍♂️":"1f926-1f3fe-200d-2642-fe0f","🤦🏽‍♂️":"1f926-1f3fd-200d-2642-fe0f","🤦🏼‍♂️":"1f926-1f3fc-200d-2642-fe0f","🤦🏻‍♂️":"1f926-1f3fb-200d-2642-fe0f","🙇🏿‍♀️":"1f647-1f3ff-200d-2640-fe0f","🙇🏾‍♀️":"1f647-1f3fe-200d-2640-fe0f","🙇🏽‍♀️":"1f647-1f3fd-200d-2640-fe0f","🙇🏼‍♀️":"1f647-1f3fc-200d-2640-fe0f","🙇🏻‍♀️":"1f647-1f3fb-200d-2640-fe0f","🙇🏿‍♂️":"1f647-1f3ff-200d-2642-fe0f","🙇🏾‍♂️":"1f647-1f3fe-200d-2642-fe0f","🙇🏽‍♂️":"1f647-1f3fd-200d-2642-fe0f","🙇🏼‍♂️":"1f647-1f3fc-200d-2642-fe0f","🙇🏻‍♂️":"1f647-1f3fb-200d-2642-fe0f","🧏🏿‍♀️":"1f9cf-1f3ff-200d-2640-fe0f","🧏🏾‍♀️":"1f9cf-1f3fe-200d-2640-fe0f","🧏🏽‍♀️":"1f9cf-1f3fd-200d-2640-fe0f","🧏🏼‍♀️":"1f9cf-1f3fc-200d-2640-fe0f","🧏🏻‍♀️":"1f9cf-1f3fb-200d-2640-fe0f","🧏🏿‍♂️":"1f9cf-1f3ff-200d-2642-fe0f","🧏🏾‍♂️":"1f9cf-1f3fe-200d-2642-fe0f","🧏🏽‍♂️":"1f9cf-1f3fd-200d-2642-fe0f","🧏🏼‍♂️":"1f9cf-1f3fc-200d-2642-fe0f","🧏🏻‍♂️":"1f9cf-1f3fb-200d-2642-fe0f","🙋🏿‍♀️":"1f64b-1f3ff-200d-2640-fe0f","🙋🏾‍♀️":"1f64b-1f3fe-200d-2640-fe0f","🙋🏽‍♀️":"1f64b-1f3fd-200d-2640-fe0f","🏳️‍⚧️":"1f3f3-fe0f-200d-26a7-fe0f","🙋🏼‍♀️":"1f64b-1f3fc-200d-2640-fe0f","🙋🏻‍♀️":"1f64b-1f3fb-200d-2640-fe0f","🙋🏿‍♂️":"1f64b-1f3ff-200d-2642-fe0f","🙋🏾‍♂️":"1f64b-1f3fe-200d-2642-fe0f","🙋🏽‍♂️":"1f64b-1f3fd-200d-2642-fe0f","🙋🏼‍♂️":"1f64b-1f3fc-200d-2642-fe0f","🙋🏻‍♂️":"1f64b-1f3fb-200d-2642-fe0f","💁🏿‍♀️":"1f481-1f3ff-200d-2640-fe0f","💁🏾‍♀️":"1f481-1f3fe-200d-2640-fe0f","💁🏽‍♀️":"1f481-1f3fd-200d-2640-fe0f","💁🏼‍♀️":"1f481-1f3fc-200d-2640-fe0f","💁🏻‍♀️":"1f481-1f3fb-200d-2640-fe0f","💁🏿‍♂️":"1f481-1f3ff-200d-2642-fe0f","💁🏾‍♂️":"1f481-1f3fe-200d-2642-fe0f","💁🏽‍♂️":"1f481-1f3fd-200d-2642-fe0f","💁🏼‍♂️":"1f481-1f3fc-200d-2642-fe0f","💁🏻‍♂️":"1f481-1f3fb-200d-2642-fe0f","🙆🏿‍♀️":"1f646-1f3ff-200d-2640-fe0f","🙆🏾‍♀️":"1f646-1f3fe-200d-2640-fe0f","🙆🏽‍♀️":"1f646-1f3fd-200d-2640-fe0f","🙆🏼‍♀️":"1f646-1f3fc-200d-2640-fe0f","🙆🏻‍♀️":"1f646-1f3fb-200d-2640-fe0f","🙆🏿‍♂️":"1f646-1f3ff-200d-2642-fe0f","🙆🏾‍♂️":"1f646-1f3fe-200d-2642-fe0f","🙆🏽‍♂️":"1f646-1f3fd-200d-2642-fe0f","🙆🏼‍♂️":"1f646-1f3fc-200d-2642-fe0f","🙆🏻‍♂️":"1f646-1f3fb-200d-2642-fe0f","🙅🏿‍♀️":"1f645-1f3ff-200d-2640-fe0f","🙅🏾‍♀️":"1f645-1f3fe-200d-2640-fe0f","🙅🏽‍♀️":"1f645-1f3fd-200d-2640-fe0f","🙅🏼‍♀️":"1f645-1f3fc-200d-2640-fe0f","🙅🏻‍♀️":"1f645-1f3fb-200d-2640-fe0f","🙅🏿‍♂️":"1f645-1f3ff-200d-2642-fe0f","🙅🏾‍♂️":"1f645-1f3fe-200d-2642-fe0f","🙅🏽‍♂️":"1f645-1f3fd-200d-2642-fe0f","🙅🏼‍♂️":"1f645-1f3fc-200d-2642-fe0f","🙅🏻‍♂️":"1f645-1f3fb-200d-2642-fe0f","🙎🏿‍♀️":"1f64e-1f3ff-200d-2640-fe0f","🙎🏾‍♀️":"1f64e-1f3fe-200d-2640-fe0f","🙎🏽‍♀️":"1f64e-1f3fd-200d-2640-fe0f","🙎🏼‍♀️":"1f64e-1f3fc-200d-2640-fe0f","🙎🏻‍♀️":"1f64e-1f3fb-200d-2640-fe0f","🙎🏿‍♂️":"1f64e-1f3ff-200d-2642-fe0f","🙎🏾‍♂️":"1f64e-1f3fe-200d-2642-fe0f","🙎🏽‍♂️":"1f64e-1f3fd-200d-2642-fe0f","🙎🏼‍♂️":"1f64e-1f3fc-200d-2642-fe0f","🙎🏻‍♂️":"1f64e-1f3fb-200d-2642-fe0f","🙍🏿‍♀️":"1f64d-1f3ff-200d-2640-fe0f","🙍🏾‍♀️":"1f64d-1f3fe-200d-2640-fe0f","🙍🏽‍♀️":"1f64d-1f3fd-200d-2640-fe0f","🙍🏼‍♀️":"1f64d-1f3fc-200d-2640-fe0f","🙍🏻‍♀️":"1f64d-1f3fb-200d-2640-fe0f","🙍🏿‍♂️":"1f64d-1f3ff-200d-2642-fe0f","🙍🏾‍♂️":"1f64d-1f3fe-200d-2642-fe0f","🙍🏽‍♂️":"1f64d-1f3fd-200d-2642-fe0f","🙍🏼‍♂️":"1f64d-1f3fc-200d-2642-fe0f","🙍🏻‍♂️":"1f64d-1f3fb-200d-2642-fe0f","👱🏿‍♂️":"1f471-1f3ff-200d-2642-fe0f","👱🏾‍♂️":"1f471-1f3fe-200d-2642-fe0f","👱🏽‍♂️":"1f471-1f3fd-200d-2642-fe0f","👱🏼‍♂️":"1f471-1f3fc-200d-2642-fe0f","👱🏻‍♂️":"1f471-1f3fb-200d-2642-fe0f","👱🏿‍♀️":"1f471-1f3ff-200d-2640-fe0f","👱🏾‍♀️":"1f471-1f3fe-200d-2640-fe0f","👱🏽‍♀️":"1f471-1f3fd-200d-2640-fe0f","👱🏼‍♀️":"1f471-1f3fc-200d-2640-fe0f","👱🏻‍♀️":"1f471-1f3fb-200d-2640-fe0f","🧔🏿‍♀️":"1f9d4-1f3ff-200d-2640-fe0f","🧔🏾‍♀️":"1f9d4-1f3fe-200d-2640-fe0f","🧔🏽‍♀️":"1f9d4-1f3fd-200d-2640-fe0f","🧔🏼‍♀️":"1f9d4-1f3fc-200d-2640-fe0f","🏋️‍♀️":"1f3cb-fe0f-200d-2640-fe0f","🏋🏻‍♀️":"1f3cb-1f3fb-200d-2640-fe0f","🏋🏼‍♀️":"1f3cb-1f3fc-200d-2640-fe0f","🏋🏽‍♀️":"1f3cb-1f3fd-200d-2640-fe0f","🏋🏾‍♀️":"1f3cb-1f3fe-200d-2640-fe0f","🏋🏿‍♀️":"1f3cb-1f3ff-200d-2640-fe0f","🏋🏿‍♂️":"1f3cb-1f3ff-200d-2642-fe0f","🏋🏾‍♂️":"1f3cb-1f3fe-200d-2642-fe0f","🏋🏽‍♂️":"1f3cb-1f3fd-200d-2642-fe0f","🧔🏻‍♀️":"1f9d4-1f3fb-200d-2640-fe0f","🚴🏻‍♂️":"1f6b4-1f3fb-200d-2642-fe0f","🚴🏼‍♂️":"1f6b4-1f3fc-200d-2642-fe0f","🚴🏽‍♂️":"1f6b4-1f3fd-200d-2642-fe0f","🚴🏾‍♂️":"1f6b4-1f3fe-200d-2642-fe0f","🚴🏿‍♂️":"1f6b4-1f3ff-200d-2642-fe0f","🚴🏻‍♀️":"1f6b4-1f3fb-200d-2640-fe0f","🚴🏼‍♀️":"1f6b4-1f3fc-200d-2640-fe0f","🚴🏽‍♀️":"1f6b4-1f3fd-200d-2640-fe0f","🚴🏾‍♀️":"1f6b4-1f3fe-200d-2640-fe0f","🚴🏿‍♀️":"1f6b4-1f3ff-200d-2640-fe0f","🚶‍♂️‍➡":"1f6b6-200d-2642-fe0f-200d-27a1-fe0f","🧎🏿‍♀‍➡":"1f9ce-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","👨‍🦽‍➡️":"1f468-200d-1f9bd-200d-27a1-fe0f","🧑🏿‍🦽‍➡":"1f9d1-1f3ff-200d-1f9bd-200d-27a1-fe0f","🏃🏻‍♂‍➡":"1f3c3-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🏃🏿‍♀‍➡":"1f3c3-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🧑🏾‍🦽‍➡":"1f9d1-1f3fe-200d-1f9bd-200d-27a1-fe0f","👨🏻‍🦯‍➡":"1f468-1f3fb-200d-1f9af-200d-27a1-fe0f","🧑🏽‍🦽‍➡":"1f9d1-1f3fd-200d-1f9bd-200d-27a1-fe0f","🏃🏼‍♀‍➡":"1f3c3-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🧎🏾‍♂‍➡":"1f9ce-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🧑🏼‍🦽‍➡":"1f9d1-1f3fc-200d-1f9bd-200d-27a1-fe0f","🧎🏼‍♀‍➡":"1f9ce-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","👩🏽‍🦯‍➡":"1f469-1f3fd-200d-1f9af-200d-27a1-fe0f","🧑🏼‍🦯‍➡":"1f9d1-1f3fc-200d-1f9af-200d-27a1-fe0f","🧑🏻‍🦽‍➡":"1f9d1-1f3fb-200d-1f9bd-200d-27a1-fe0f","🏃‍♂‍➡️":"1f3c3-200d-2642-fe0f-200d-27a1-fe0f","🧎‍♂‍➡️":"1f9ce-200d-2642-fe0f-200d-27a1-fe0f","🏃🏼‍♂‍➡":"1f3c3-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🧑‍🦽‍➡️":"1f9d1-200d-1f9bd-200d-27a1-fe0f","👩🏿‍🦼‍➡":"1f469-1f3ff-200d-1f9bc-200d-27a1-fe0f","🏃‍♀‍➡️":"1f3c3-200d-2640-fe0f-200d-27a1-fe0f","🧎‍♂️‍➡":"1f9ce-200d-2642-fe0f-200d-27a1-fe0f","🧎🏿‍♂‍➡":"1f9ce-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","👩🏾‍🦼‍➡":"1f469-1f3fe-200d-1f9bc-200d-27a1-fe0f","👨‍🦯‍➡️":"1f468-200d-1f9af-200d-27a1-fe0f","🏃🏿‍♂‍➡":"1f3c3-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","🧑‍🦯‍➡️":"1f9d1-200d-1f9af-200d-27a1-fe0f","👩🏽‍🦼‍➡":"1f469-1f3fd-200d-1f9bc-200d-27a1-fe0f","🏃‍♂️‍➡":"1f3c3-200d-2642-fe0f-200d-27a1-fe0f","🧎‍♀‍➡️":"1f9ce-200d-2640-fe0f-200d-27a1-fe0f","👩🏼‍🦼‍➡":"1f469-1f3fc-200d-1f9bc-200d-27a1-fe0f","🧎‍♀️‍➡":"1f9ce-200d-2640-fe0f-200d-27a1-fe0f","👩🏻‍🦼‍➡":"1f469-1f3fb-200d-1f9bc-200d-27a1-fe0f","🧑🏿‍🦯‍➡":"1f9d1-1f3ff-200d-1f9af-200d-27a1-fe0f","🧑🏻‍🦯‍➡":"1f9d1-1f3fb-200d-1f9af-200d-27a1-fe0f","👩🏿‍🦽‍➡":"1f469-1f3ff-200d-1f9bd-200d-27a1-fe0f","👩‍🦼‍➡️":"1f469-200d-1f9bc-200d-27a1-fe0f","👨🏿‍🦼‍➡":"1f468-1f3ff-200d-1f9bc-200d-27a1-fe0f","🏃🏽‍♀‍➡":"1f3c3-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","👨🏾‍🦼‍➡":"1f468-1f3fe-200d-1f9bc-200d-27a1-fe0f","👩🏼‍🦯‍➡":"1f469-1f3fc-200d-1f9af-200d-27a1-fe0f","🚶🏽‍♂‍➡":"1f6b6-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","👨🏽‍🦼‍➡":"1f468-1f3fd-200d-1f9bc-200d-27a1-fe0f","🧎🏻‍♂‍➡":"1f9ce-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","👨🏼‍🦼‍➡":"1f468-1f3fc-200d-1f9bc-200d-27a1-fe0f","👨🏽‍🦯‍➡":"1f468-1f3fd-200d-1f9af-200d-27a1-fe0f","👩‍❤️‍👩":"1f469-200d-2764-fe0f-200d-1f469","🧎🏽‍♀‍➡":"1f9ce-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🚶🏾‍♂‍➡":"1f6b6-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","👨🏻‍🦼‍➡":"1f468-1f3fb-200d-1f9bc-200d-27a1-fe0f","👩🏾‍🦽‍➡":"1f469-1f3fe-200d-1f9bd-200d-27a1-fe0f","👨‍🦼‍➡️":"1f468-200d-1f9bc-200d-27a1-fe0f","🧑🏿‍🦼‍➡":"1f9d1-1f3ff-200d-1f9bc-200d-27a1-fe0f","🚶‍♀️‍➡":"1f6b6-200d-2640-fe0f-200d-27a1-fe0f","🚶‍♀‍➡️":"1f6b6-200d-2640-fe0f-200d-27a1-fe0f","👩🏻‍🦯‍➡":"1f469-1f3fb-200d-1f9af-200d-27a1-fe0f","🏃🏽‍♂‍➡":"1f3c3-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🧑🏾‍🦯‍➡":"1f9d1-1f3fe-200d-1f9af-200d-27a1-fe0f","🏃‍♀️‍➡":"1f3c3-200d-2640-fe0f-200d-27a1-fe0f","🧑🏾‍🦼‍➡":"1f9d1-1f3fe-200d-1f9bc-200d-27a1-fe0f","👨‍❤️‍👨":"1f468-200d-2764-fe0f-200d-1f468","🧎🏼‍♂‍➡":"1f9ce-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🚶🏻‍♀‍➡":"1f6b6-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","👨🏿‍🦯‍➡":"1f468-1f3ff-200d-1f9af-200d-27a1-fe0f","👩‍❤️‍👨":"1f469-200d-2764-fe0f-200d-1f468","🧑🏽‍🦼‍➡":"1f9d1-1f3fd-200d-1f9bc-200d-27a1-fe0f","👩🏽‍🦽‍➡":"1f469-1f3fd-200d-1f9bd-200d-27a1-fe0f","🧎🏻‍♀‍➡":"1f9ce-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","👩🏾‍🦯‍➡":"1f469-1f3fe-200d-1f9af-200d-27a1-fe0f","🚶🏼‍♀‍➡":"1f6b6-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","👩🏼‍🦽‍➡":"1f469-1f3fc-200d-1f9bd-200d-27a1-fe0f","🧎🏾‍♀‍➡":"1f9ce-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","👨🏼‍🦯‍➡":"1f468-1f3fc-200d-1f9af-200d-27a1-fe0f","👩🏻‍🦽‍➡":"1f469-1f3fb-200d-1f9bd-200d-27a1-fe0f","🧑🏼‍🦼‍➡":"1f9d1-1f3fc-200d-1f9bc-200d-27a1-fe0f","🚶🏼‍♂‍➡":"1f6b6-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","👩‍🦽‍➡️":"1f469-200d-1f9bd-200d-27a1-fe0f","🚶🏽‍♀‍➡":"1f6b6-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🧑🏽‍🦯‍➡":"1f9d1-1f3fd-200d-1f9af-200d-27a1-fe0f","👨🏿‍🦽‍➡":"1f468-1f3ff-200d-1f9bd-200d-27a1-fe0f","🧎🏽‍♂‍➡":"1f9ce-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🧑🏻‍🦼‍➡":"1f9d1-1f3fb-200d-1f9bc-200d-27a1-fe0f","👨🏾‍🦯‍➡":"1f468-1f3fe-200d-1f9af-200d-27a1-fe0f","👨🏾‍🦽‍➡":"1f468-1f3fe-200d-1f9bd-200d-27a1-fe0f","🚶🏾‍♀‍➡":"1f6b6-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","👩‍🦯‍➡️":"1f469-200d-1f9af-200d-27a1-fe0f","👨🏽‍🦽‍➡":"1f468-1f3fd-200d-1f9bd-200d-27a1-fe0f","🏃🏾‍♀‍➡":"1f3c3-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","🚶🏻‍♂‍➡":"1f6b6-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","👨🏼‍🦽‍➡":"1f468-1f3fc-200d-1f9bd-200d-27a1-fe0f","🧑‍🦼‍➡️":"1f9d1-200d-1f9bc-200d-27a1-fe0f","🚶🏿‍♀‍➡":"1f6b6-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🚶🏿‍♂‍➡":"1f6b6-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","🏃🏾‍♂‍➡":"1f3c3-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","👩🏿‍🦯‍➡":"1f469-1f3ff-200d-1f9af-200d-27a1-fe0f","🚶‍♂‍➡️":"1f6b6-200d-2642-fe0f-200d-27a1-fe0f","👨🏻‍🦽‍➡":"1f468-1f3fb-200d-1f9bd-200d-27a1-fe0f","🏃🏻‍♀‍➡":"1f3c3-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🏃🏾‍♂‍➡️":"1f3c3-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","👨🏾‍❤‍👨🏾":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe","👩🏻‍🤝‍👨🏾":"1f469-1f3fb-200d-1f91d-200d-1f468-1f3fe","👨🏾‍❤‍👨🏽":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fd","🏃🏾‍♂️‍➡":"1f3c3-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","👨🏾‍❤‍👨🏼":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fc","👩🏽‍🤝‍👩🏾":"1f469-1f3fd-200d-1f91d-200d-1f469-1f3fe","👨🏾‍❤‍👨🏻":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fb","👨🏽‍❤‍👨🏿":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3ff","👨🏽‍❤‍👨🏾":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fe","👨🏽‍❤‍👨🏽":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd","🏃🏿‍♂‍➡️":"1f3c3-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","👨🏽‍❤‍👨🏼":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fc","🏃🏼‍♀️‍➡":"1f3c3-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","👨🏽‍❤‍👨🏻":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fb","🏃🏿‍♂️‍➡":"1f3c3-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","👨🏼‍❤‍👨🏿":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3ff","👩🏽‍🤝‍👩🏼":"1f469-1f3fd-200d-1f91d-200d-1f469-1f3fc","👨🏼‍❤‍👨🏾":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fe","👨🏼‍❤‍👨🏽":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fd","🏃🏼‍♀‍➡️":"1f3c3-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","👨🏼‍❤‍👨🏼":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc","🧑‍🧑‍🧒‍🧒":"1f9d1-200d-1f9d1-200d-1f9d2-200d-1f9d2","👨🏼‍❤‍👨🏻":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fb","👩🏽‍🤝‍👩🏻":"1f469-1f3fd-200d-1f91d-200d-1f469-1f3fb","👨🏻‍❤‍👨🏿":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3ff","👩‍👩‍👧‍👧":"1f469-200d-1f469-200d-1f467-200d-1f467","👨🏻‍❤‍👨🏾":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fe","👩‍👩‍👦‍👦":"1f469-200d-1f469-200d-1f466-200d-1f466","👨🏻‍❤‍👨🏽":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fd","👩‍👩‍👧‍👦":"1f469-200d-1f469-200d-1f467-200d-1f466","👨🏻‍❤‍👨🏼":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fc","👨‍👨‍👧‍👧":"1f468-200d-1f468-200d-1f467-200d-1f467","👨🏻‍❤‍👨🏻":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb","👨‍👨‍👦‍👦":"1f468-200d-1f468-200d-1f466-200d-1f466","👩🏿‍❤‍👨🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff","👨‍👨‍👧‍👦":"1f468-200d-1f468-200d-1f467-200d-1f466","👩🏿‍❤‍👨🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fe","👨‍👩‍👧‍👧":"1f468-200d-1f469-200d-1f467-200d-1f467","👩🏿‍❤‍👨🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fd","👨‍👩‍👦‍👦":"1f468-200d-1f469-200d-1f466-200d-1f466","👩🏿‍❤‍👨🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fc","👨‍👩‍👧‍👦":"1f468-200d-1f469-200d-1f467-200d-1f466","👩🏿‍❤‍👨🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fb","👩🏿‍❤‍👩🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3ff","👩🏾‍❤‍👨🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3ff","👩🏾‍❤‍👨🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe","👩🏿‍❤‍👩🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fe","👩🏾‍❤‍👨🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fd","🏃‍♂️‍➡️":"1f3c3-200d-2642-fe0f-200d-27a1-fe0f","👩🏾‍❤‍👨🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fc","👩🏿‍❤‍👩🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fd","👩🏾‍❤‍👨🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fb","👩🏿‍🤝‍👩🏼":"1f469-1f3ff-200d-1f91d-200d-1f469-1f3fc","👩🏽‍❤‍👨🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3ff","👩🏿‍❤‍👩🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fc","👩🏽‍❤‍👨🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fe","👩🏽‍❤‍👨🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd","👩🏿‍❤‍👩🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fb","👩🏽‍❤‍👨🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fc","🏃🏾‍♀️‍➡":"1f3c3-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","👩🏽‍❤‍👨🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fb","👩🏾‍❤‍👩🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3ff","👩🏼‍❤‍👨🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3ff","👩🏼‍❤‍👨🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fe","👩🏾‍❤‍👩🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fe","👩🏼‍❤‍👨🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fd","👩🏿‍🤝‍👩🏻":"1f469-1f3ff-200d-1f91d-200d-1f469-1f3fb","👩🏼‍❤‍👨🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc","👩🏾‍❤‍👩🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fd","👩🏼‍❤‍👨🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fb","👩🏿‍🤝‍👩🏾":"1f469-1f3ff-200d-1f91d-200d-1f469-1f3fe","👩🏻‍❤‍👨🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3ff","👩🏾‍❤‍👩🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fc","👩🏻‍❤‍👨🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fe","🏃🏾‍♀‍➡️":"1f3c3-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","👩🏻‍❤‍👨🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fd","👩🏾‍❤‍👩🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fb","👩🏻‍❤‍👨🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fc","🏃🏻‍♂‍➡️":"1f3c3-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","👩🏻‍❤‍👨🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb","👩🏽‍❤‍👩🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3ff","🧑🏿‍❤‍🧑🏾":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fe","🏃🏻‍♂️‍➡":"1f3c3-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🧑🏿‍❤‍🧑🏽":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fd","👩🏽‍❤‍👩🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fe","🧑🏿‍❤‍🧑🏼":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fc","👩🏾‍🤝‍👩🏿":"1f469-1f3fe-200d-1f91d-200d-1f469-1f3ff","🧑🏿‍❤‍🧑🏻":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fb","👩🏻‍🤝‍👨🏿":"1f469-1f3fb-200d-1f91d-200d-1f468-1f3ff","🧑🏾‍❤‍🧑🏿":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3ff","👩🏼‍🤝‍👩🏿":"1f469-1f3fc-200d-1f91d-200d-1f469-1f3ff","🧑🏾‍❤‍🧑🏽":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fd","👩🏽‍❤‍👩🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fd","🧑🏾‍❤‍🧑🏼":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fc","🧑🏾‍❤‍🧑🏻":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fb","🧑🏽‍❤‍🧑🏿":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3ff","👩🏼‍🤝‍👩🏾":"1f469-1f3fc-200d-1f91d-200d-1f469-1f3fe","🧑🏽‍❤‍🧑🏾":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fe","👩🏽‍❤‍👩🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fc","🧑🏽‍❤‍🧑🏼":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fc","👩‍❤‍💋‍👨":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f468","🧑🏽‍❤‍🧑🏻":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fb","👩🏻‍🤝‍👨🏼":"1f469-1f3fb-200d-1f91d-200d-1f468-1f3fc","🧑🏼‍🤝‍🧑🏻":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fb","🧑🏼‍❤‍🧑🏿":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3ff","👩🏼‍🤝‍👩🏽":"1f469-1f3fc-200d-1f91d-200d-1f469-1f3fd","🏃‍♀️‍➡️":"1f3c3-200d-2640-fe0f-200d-27a1-fe0f","🧑🏼‍❤‍🧑🏾":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fe","👩🏻‍🤝‍👩🏽":"1f469-1f3fb-200d-1f91d-200d-1f469-1f3fd","👩🏽‍❤‍👩🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fb","👩🏼‍🤝‍👨🏾":"1f469-1f3fc-200d-1f91d-200d-1f468-1f3fe","🧑🏼‍❤‍🧑🏽":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fd","🏃🏿‍♀‍➡️":"1f3c3-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🧑🏻‍🤝‍🧑🏽":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fd","🧑🏼‍❤‍🧑🏻":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fb","👩🏻‍🤝‍👩🏼":"1f469-1f3fb-200d-1f91d-200d-1f469-1f3fc","🧑🏻‍❤‍🧑🏿":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3ff","👩🏿‍🦽‍➡️":"1f469-1f3ff-200d-1f9bd-200d-27a1-fe0f","👩🏼‍❤‍👩🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3ff","🧑🏽‍🤝‍🧑🏼":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fc","🧑🏻‍❤‍🧑🏾":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fe","👩🏼‍🤝‍👨🏻":"1f469-1f3fc-200d-1f91d-200d-1f468-1f3fb","🧑🏻‍❤‍🧑🏽":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fd","🧑🏿‍🤝‍🧑🏿":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3ff","🏃🏼‍♂‍➡️":"1f3c3-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","👩🏼‍🤝‍👨🏿":"1f469-1f3fc-200d-1f91d-200d-1f468-1f3ff","🧑🏻‍❤‍🧑🏼":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fc","👩🏾‍🦽‍➡️":"1f469-1f3fe-200d-1f9bd-200d-27a1-fe0f","👩🏼‍🤝‍👩🏻":"1f469-1f3fc-200d-1f91d-200d-1f469-1f3fb","🧑🏻‍🤝‍🧑🏿":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3ff","👩🏽‍🤝‍👨🏻":"1f469-1f3fd-200d-1f91d-200d-1f468-1f3fb","👩🏽‍🦽‍➡️":"1f469-1f3fd-200d-1f9bd-200d-27a1-fe0f","👩🏽‍🤝‍👨🏼":"1f469-1f3fd-200d-1f91d-200d-1f468-1f3fc","👩🏼‍🦽‍➡️":"1f469-1f3fc-200d-1f9bd-200d-27a1-fe0f","👩🏻‍🦽‍➡️":"1f469-1f3fb-200d-1f9bd-200d-27a1-fe0f","👩🏽‍🤝‍👨🏾":"1f469-1f3fd-200d-1f91d-200d-1f468-1f3fe","👨🏿‍🦽‍➡️":"1f468-1f3ff-200d-1f9bd-200d-27a1-fe0f","👩🏽‍🤝‍👨🏿":"1f469-1f3fd-200d-1f91d-200d-1f468-1f3ff","👨🏾‍🦽‍➡️":"1f468-1f3fe-200d-1f9bd-200d-27a1-fe0f","👩🏾‍🤝‍👨🏻":"1f469-1f3fe-200d-1f91d-200d-1f468-1f3fb","👨🏽‍🦽‍➡️":"1f468-1f3fd-200d-1f9bd-200d-27a1-fe0f","👩🏾‍🤝‍👨🏼":"1f469-1f3fe-200d-1f91d-200d-1f468-1f3fc","👨🏼‍🦽‍➡️":"1f468-1f3fc-200d-1f9bd-200d-27a1-fe0f","👨🏻‍🦽‍➡️":"1f468-1f3fb-200d-1f9bd-200d-27a1-fe0f","👩🏾‍🤝‍👨🏽":"1f469-1f3fe-200d-1f91d-200d-1f468-1f3fd","🧑🏿‍🦽‍➡️":"1f9d1-1f3ff-200d-1f9bd-200d-27a1-fe0f","👩🏾‍🤝‍👨🏿":"1f469-1f3fe-200d-1f91d-200d-1f468-1f3ff","🧑🏾‍🦽‍➡️":"1f9d1-1f3fe-200d-1f9bd-200d-27a1-fe0f","👩🏿‍🤝‍👨🏻":"1f469-1f3ff-200d-1f91d-200d-1f468-1f3fb","🧑🏽‍🦽‍➡️":"1f9d1-1f3fd-200d-1f9bd-200d-27a1-fe0f","🧑🏿‍🤝‍🧑🏾":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fe","👩🏿‍🤝‍👨🏼":"1f469-1f3ff-200d-1f91d-200d-1f468-1f3fc","🧑🏼‍🦽‍➡️":"1f9d1-1f3fc-200d-1f9bd-200d-27a1-fe0f","🧑🏿‍🤝‍🧑🏽":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fd","👩🏿‍🤝‍👨🏽":"1f469-1f3ff-200d-1f91d-200d-1f468-1f3fd","🧑🏻‍🦽‍➡️":"1f9d1-1f3fb-200d-1f9bd-200d-27a1-fe0f","🧑🏿‍🤝‍🧑🏼":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fc","👩🏿‍🤝‍👨🏾":"1f469-1f3ff-200d-1f91d-200d-1f468-1f3fe","👩🏿‍🦼‍➡️":"1f469-1f3ff-200d-1f9bc-200d-27a1-fe0f","🧑🏽‍🤝‍🧑🏻":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fb","👩🏾‍🦼‍➡️":"1f469-1f3fe-200d-1f9bc-200d-27a1-fe0f","👨🏻‍🤝‍👨🏼":"1f468-1f3fb-200d-1f91d-200d-1f468-1f3fc","👩🏽‍🦼‍➡️":"1f469-1f3fd-200d-1f9bc-200d-27a1-fe0f","🧑🏿‍🤝‍🧑🏻":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fb","👩🏼‍🦼‍➡️":"1f469-1f3fc-200d-1f9bc-200d-27a1-fe0f","🧑🏻‍🤝‍🧑🏻":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fb","👩🏻‍🦼‍➡️":"1f469-1f3fb-200d-1f9bc-200d-27a1-fe0f","🧑🏾‍🤝‍🧑🏿":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3ff","👨🏿‍🦼‍➡️":"1f468-1f3ff-200d-1f9bc-200d-27a1-fe0f","👨🏾‍🦼‍➡️":"1f468-1f3fe-200d-1f9bc-200d-27a1-fe0f","🧑🏼‍🤝‍🧑🏿":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3ff","👨🏽‍🦼‍➡️":"1f468-1f3fd-200d-1f9bc-200d-27a1-fe0f","👩‍❤‍💋‍👩":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f469","🧑🏾‍🤝‍🧑🏾":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fe","👨🏻‍🤝‍👨🏾":"1f468-1f3fb-200d-1f91d-200d-1f468-1f3fe","👨🏼‍🦼‍➡️":"1f468-1f3fc-200d-1f9bc-200d-27a1-fe0f","👨🏻‍🦼‍➡️":"1f468-1f3fb-200d-1f9bc-200d-27a1-fe0f","🚶‍♀️‍➡️":"1f6b6-200d-2640-fe0f-200d-27a1-fe0f","🏃🏻‍♀️‍➡":"1f3c3-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🧑🏿‍🦼‍➡️":"1f9d1-1f3ff-200d-1f9bc-200d-27a1-fe0f","🚶🏻‍♀‍➡️":"1f6b6-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🚶🏻‍♀️‍➡":"1f6b6-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🧑🏾‍🦼‍➡️":"1f9d1-1f3fe-200d-1f9bc-200d-27a1-fe0f","🧑🏻‍🤝‍🧑🏾":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fe","👩🏾‍🤝‍👩🏽":"1f469-1f3fe-200d-1f91d-200d-1f469-1f3fd","🚶🏼‍♀‍➡️":"1f6b6-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🚶🏼‍♀️‍➡":"1f6b6-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","👨🏻‍🤝‍👨🏽":"1f468-1f3fb-200d-1f91d-200d-1f468-1f3fd","🧑🏽‍🦼‍➡️":"1f9d1-1f3fd-200d-1f9bc-200d-27a1-fe0f","👩🏻‍🤝‍👩🏿":"1f469-1f3fb-200d-1f91d-200d-1f469-1f3ff","🧑🏾‍🤝‍🧑🏽":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fd","🚶🏽‍♀‍➡️":"1f6b6-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🚶🏽‍♀️‍➡":"1f6b6-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🏴󠁧󠁢󠁷󠁬󠁳󠁿":"1f3f4-e0067-e0062-e0077-e006c-e0073-e007f","👩🏼‍❤‍👩🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fd","🧑🏼‍🦼‍➡️":"1f9d1-1f3fc-200d-1f9bc-200d-27a1-fe0f","🚶🏾‍♀‍➡️":"1f6b6-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","🚶🏾‍♀️‍➡":"1f6b6-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","🧑🏼‍🤝‍🧑🏾":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fe","🧑🏻‍🦼‍➡️":"1f9d1-1f3fb-200d-1f9bc-200d-27a1-fe0f","🏃🏼‍♂️‍➡":"1f3c3-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🏴󠁧󠁢󠁳󠁣󠁴󠁿":"1f3f4-e0067-e0062-e0073-e0063-e0074-e007f","🚶🏿‍♀‍➡️":"1f6b6-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🚶🏿‍♀️‍➡":"1f6b6-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🧑🏾‍🤝‍🧑🏼":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fc","🚶‍♂️‍➡️":"1f6b6-200d-2642-fe0f-200d-27a1-fe0f","👩🏿‍🦯‍➡️":"1f469-1f3ff-200d-1f9af-200d-27a1-fe0f","👩🏼‍❤‍👩🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fe","🏃🏻‍♀‍➡️":"1f3c3-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🚶🏻‍♂‍➡️":"1f6b6-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🧑🏻‍🤝‍🧑🏼":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fc","🚶🏻‍♂️‍➡":"1f6b6-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🏴󠁧󠁢󠁥󠁮󠁧󠁿":"1f3f4-e0067-e0062-e0065-e006e-e0067-e007f","👩🏼‍❤‍👩🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fc","👩🏾‍🦯‍➡️":"1f469-1f3fe-200d-1f9af-200d-27a1-fe0f","🚶🏼‍♂‍➡️":"1f6b6-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🚶🏼‍♂️‍➡":"1f6b6-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","👩🏽‍🦯‍➡️":"1f469-1f3fd-200d-1f9af-200d-27a1-fe0f","🧑🏽‍🤝‍🧑🏽":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fd","👨🏻‍🤝‍👨🏿":"1f468-1f3fb-200d-1f91d-200d-1f468-1f3ff","🚶🏽‍♂‍➡️":"1f6b6-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🧑🏾‍🤝‍🧑🏻":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fb","🚶🏽‍♂️‍➡":"1f6b6-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","👩🏼‍🦯‍➡️":"1f469-1f3fc-200d-1f9af-200d-27a1-fe0f","🏃🏽‍♀️‍➡":"1f3c3-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🚶🏾‍♂‍➡️":"1f6b6-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","👨🏼‍🤝‍👨🏻":"1f468-1f3fc-200d-1f91d-200d-1f468-1f3fb","🚶🏾‍♂️‍➡":"1f6b6-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🧑🏼‍🤝‍🧑🏽":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fd","👩🏻‍🦯‍➡️":"1f469-1f3fb-200d-1f9af-200d-27a1-fe0f","🚶🏿‍♂‍➡️":"1f6b6-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","🚶🏿‍♂️‍➡":"1f6b6-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","🧎‍♀️‍➡️":"1f9ce-200d-2640-fe0f-200d-27a1-fe0f","👩🏼‍❤‍👩🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fb","👨🏼‍🤝‍👨🏽":"1f468-1f3fc-200d-1f91d-200d-1f468-1f3fd","🧎🏻‍♀‍➡️":"1f9ce-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🧑🏽‍🤝‍🧑🏿":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3ff","🧎🏻‍♀️‍➡":"1f9ce-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","👨🏿‍🦯‍➡️":"1f468-1f3ff-200d-1f9af-200d-27a1-fe0f","👩🏻‍🤝‍👩🏾":"1f469-1f3fb-200d-1f91d-200d-1f469-1f3fe","🧎🏼‍♀‍➡️":"1f9ce-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🧎🏼‍♀️‍➡":"1f9ce-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🧑🏼‍🤝‍🧑🏼":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fc","👨🏼‍🤝‍👨🏾":"1f468-1f3fc-200d-1f91d-200d-1f468-1f3fe","👨🏾‍🦯‍➡️":"1f468-1f3fe-200d-1f9af-200d-27a1-fe0f","🧎🏽‍♀‍➡️":"1f9ce-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","👨‍❤‍💋‍👨":"1f468-200d-2764-fe0f-200d-1f48b-200d-1f468","🧎🏽‍♀️‍➡":"1f9ce-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","👩🏼‍🤝‍👨🏽":"1f469-1f3fc-200d-1f91d-200d-1f468-1f3fd","🧑🏽‍🤝‍🧑🏾":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fe","🧎🏾‍♀‍➡️":"1f9ce-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","👨🏽‍🦯‍➡️":"1f468-1f3fd-200d-1f9af-200d-27a1-fe0f","🧎🏾‍♀️‍➡":"1f9ce-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","👨🏼‍🤝‍👨🏿":"1f468-1f3fc-200d-1f91d-200d-1f468-1f3ff","👩🏻‍❤‍👩🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3ff","👨🏼‍🦯‍➡️":"1f468-1f3fc-200d-1f9af-200d-27a1-fe0f","🧎🏿‍♀‍➡️":"1f9ce-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🧎🏿‍♀️‍➡":"1f9ce-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","👨🏽‍🤝‍👨🏻":"1f468-1f3fd-200d-1f91d-200d-1f468-1f3fb","🧎‍♂️‍➡️":"1f9ce-200d-2642-fe0f-200d-27a1-fe0f","👨🏻‍🦯‍➡️":"1f468-1f3fb-200d-1f9af-200d-27a1-fe0f","👨🏽‍🤝‍👨🏼":"1f468-1f3fd-200d-1f91d-200d-1f468-1f3fc","👩🏾‍🤝‍👩🏼":"1f469-1f3fe-200d-1f91d-200d-1f469-1f3fc","🧎🏻‍♂‍➡️":"1f9ce-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🧎🏻‍♂️‍➡":"1f9ce-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🧑🏿‍🦯‍➡️":"1f9d1-1f3ff-200d-1f9af-200d-27a1-fe0f","👨🏽‍🤝‍👨🏾":"1f468-1f3fd-200d-1f91d-200d-1f468-1f3fe","👩🏻‍❤‍👩🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fe","👨🏿‍🤝‍👨🏾":"1f468-1f3ff-200d-1f91d-200d-1f468-1f3fe","🧎🏼‍♂‍➡️":"1f9ce-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🧎🏼‍♂️‍➡":"1f9ce-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","👨🏿‍🤝‍👨🏽":"1f468-1f3ff-200d-1f91d-200d-1f468-1f3fd","👩🏿‍🤝‍👩🏽":"1f469-1f3ff-200d-1f91d-200d-1f469-1f3fd","🧑🏾‍🦯‍➡️":"1f9d1-1f3fe-200d-1f9af-200d-27a1-fe0f","🧎🏽‍♂‍➡️":"1f9ce-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","👨🏿‍🤝‍👨🏼":"1f468-1f3ff-200d-1f91d-200d-1f468-1f3fc","🧎🏽‍♂️‍➡":"1f9ce-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","👨🏿‍🤝‍👨🏻":"1f468-1f3ff-200d-1f91d-200d-1f468-1f3fb","👩🏻‍❤‍👩🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fd","🧎🏾‍♂‍➡️":"1f9ce-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","👨🏽‍🤝‍👨🏿":"1f468-1f3fd-200d-1f91d-200d-1f468-1f3ff","🧎🏾‍♂️‍➡":"1f9ce-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🧑🏽‍🦯‍➡️":"1f9d1-1f3fd-200d-1f9af-200d-27a1-fe0f","👨🏾‍🤝‍👨🏿":"1f468-1f3fe-200d-1f91d-200d-1f468-1f3ff","🏃🏽‍♂‍➡️":"1f3c3-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🧎🏿‍♂‍➡️":"1f9ce-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","👨🏾‍🤝‍👨🏽":"1f468-1f3fe-200d-1f91d-200d-1f468-1f3fd","🧎🏿‍♂️‍➡":"1f9ce-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","🧑🏼‍🦯‍➡️":"1f9d1-1f3fc-200d-1f9af-200d-27a1-fe0f","👨🏾‍🤝‍👨🏼":"1f468-1f3fe-200d-1f91d-200d-1f468-1f3fc","🧑🏻‍🦯‍➡️":"1f9d1-1f3fb-200d-1f9af-200d-27a1-fe0f","👨🏾‍🤝‍👨🏻":"1f468-1f3fe-200d-1f91d-200d-1f468-1f3fb","👩🏻‍❤‍👩🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fc","👩🏻‍🤝‍👨🏽":"1f469-1f3fb-200d-1f91d-200d-1f468-1f3fd","👩🏻‍❤‍👩🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fb","🏃🏽‍♂️‍➡":"1f3c3-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","👨🏿‍❤‍👨🏿":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff","👩🏾‍🤝‍👩🏻":"1f469-1f3fe-200d-1f91d-200d-1f469-1f3fb","👨🏿‍❤‍👨🏾":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fe","👨🏿‍❤‍👨🏽":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fd","🏃🏽‍♀‍➡️":"1f3c3-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","👨🏿‍❤‍👨🏼":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fc","🏃🏿‍♀️‍➡":"1f3c3-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","👨🏿‍❤‍👨🏻":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fb","👩🏽‍🤝‍👩🏿":"1f469-1f3fd-200d-1f91d-200d-1f469-1f3ff","👨🏾‍❤‍👨🏿":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3ff","🧑🏻‍❤️‍🧑🏿":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3ff","🏃🏻‍♂️‍➡️":"1f3c3-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🏃🏼‍♂️‍➡️":"1f3c3-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🏃🏽‍♂️‍➡️":"1f3c3-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🏃🏾‍♂️‍➡️":"1f3c3-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🏃🏿‍♂️‍➡️":"1f3c3-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","👩🏿‍❤️‍👩🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3ff","👩🏿‍❤️‍👩🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fe","👩🏿‍❤️‍👩🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fd","👩🏿‍❤️‍👩🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fc","👩🏿‍❤️‍👩🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fb","👩🏾‍❤️‍👩🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3ff","👩🏾‍❤️‍👩🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fe","👩🏾‍❤️‍👩🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fd","👩🏾‍❤️‍👩🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fc","👩🏾‍❤️‍👩🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fb","👩🏽‍❤️‍👩🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3ff","👩🏽‍❤️‍👩🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fe","👩🏽‍❤️‍👩🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fd","👩🏽‍❤️‍👩🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fc","👩🏽‍❤️‍👩🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fb","👩‍❤️‍💋‍👨":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f468","👩🏼‍❤️‍👩🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3ff","👩🏼‍❤️‍👩🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fe","👩🏼‍❤️‍👩🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fd","👩🏼‍❤️‍👩🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fc","👩🏼‍❤️‍👩🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fb","👩🏻‍❤️‍👩🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3ff","👩🏻‍❤️‍👩🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fe","👩🏻‍❤️‍👩🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fd","👩🏻‍❤️‍👩🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fc","👩🏻‍❤️‍👩🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fb","👨🏿‍❤️‍👨🏿":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff","👨🏿‍❤️‍👨🏾":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fe","👨🏿‍❤️‍👨🏽":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fd","👨🏿‍❤️‍👨🏼":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fc","👨🏿‍❤️‍👨🏻":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fb","👨🏾‍❤️‍👨🏿":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3ff","👨🏾‍❤️‍👨🏾":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe","👨🏾‍❤️‍👨🏽":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fd","👨🏾‍❤️‍👨🏼":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fc","👨🏾‍❤️‍👨🏻":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fb","👨🏽‍❤️‍👨🏿":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3ff","👨🏽‍❤️‍👨🏾":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fe","👨🏽‍❤️‍👨🏽":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd","👨🏽‍❤️‍👨🏼":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fc","👨🏽‍❤️‍👨🏻":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fb","👨🏼‍❤️‍👨🏿":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3ff","👨🏼‍❤️‍👨🏾":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fe","👨🏼‍❤️‍👨🏽":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fd","👨🏼‍❤️‍👨🏼":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc","👨🏼‍❤️‍👨🏻":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fb","👨🏻‍❤️‍👨🏿":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3ff","👨🏻‍❤️‍👨🏾":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fe","👨🏻‍❤️‍👨🏽":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fd","👨🏻‍❤️‍👨🏼":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fc","👨🏻‍❤️‍👨🏻":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb","👩🏿‍❤️‍👨🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff","👩🏿‍❤️‍👨🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fe","👩🏿‍❤️‍👨🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fd","👩🏿‍❤️‍👨🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fc","👩🏿‍❤️‍👨🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fb","👩🏾‍❤️‍👨🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3ff","👩🏾‍❤️‍👨🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe","👩🏾‍❤️‍👨🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fd","👩🏾‍❤️‍👨🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fc","👩🏾‍❤️‍👨🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fb","👩🏽‍❤️‍👨🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3ff","👩🏽‍❤️‍👨🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fe","👩🏽‍❤️‍👨🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd","👩🏽‍❤️‍👨🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fc","👩🏽‍❤️‍👨🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fb","👩🏼‍❤️‍👨🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3ff","👩🏼‍❤️‍👨🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fe","👩🏼‍❤️‍👨🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fd","👩🏼‍❤️‍👨🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc","👩🏼‍❤️‍👨🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fb","👩🏻‍❤️‍👨🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3ff","👩🏻‍❤️‍👨🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fe","👩🏻‍❤️‍👨🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fd","👩🏻‍❤️‍👨🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fc","👩🏻‍❤️‍👨🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb","🧑🏿‍❤️‍🧑🏾":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fe","🧑🏿‍❤️‍🧑🏽":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fd","🧑🏿‍❤️‍🧑🏼":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fc","🧑🏿‍❤️‍🧑🏻":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fb","🧑🏾‍❤️‍🧑🏿":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3ff","🧑🏾‍❤️‍🧑🏽":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fd","🧑🏾‍❤️‍🧑🏼":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fc","🧑🏾‍❤️‍🧑🏻":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fb","🧑🏽‍❤️‍🧑🏿":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3ff","🧑🏽‍❤️‍🧑🏾":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fe","🧑🏽‍❤️‍🧑🏼":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fc","🧑🏽‍❤️‍🧑🏻":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fb","🧑🏼‍❤️‍🧑🏿":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3ff","🧑🏼‍❤️‍🧑🏾":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fe","🧑🏼‍❤️‍🧑🏽":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fd","🧑🏼‍❤️‍🧑🏻":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fb","🏃🏿‍♀️‍➡️":"1f3c3-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🧑🏻‍❤️‍🧑🏾":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fe","🧑🏻‍❤️‍🧑🏽":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fd","🧑🏻‍❤️‍🧑🏼":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fc","👩‍❤️‍💋‍👩":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f469","🚶🏻‍♀️‍➡️":"1f6b6-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🚶🏼‍♀️‍➡️":"1f6b6-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🚶🏽‍♀️‍➡️":"1f6b6-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🚶🏾‍♀️‍➡️":"1f6b6-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","🚶🏿‍♀️‍➡️":"1f6b6-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🚶🏻‍♂️‍➡️":"1f6b6-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🚶🏼‍♂️‍➡️":"1f6b6-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🚶🏽‍♂️‍➡️":"1f6b6-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🚶🏾‍♂️‍➡️":"1f6b6-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🚶🏿‍♂️‍➡️":"1f6b6-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","🧎🏻‍♀️‍➡️":"1f9ce-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🧎🏼‍♀️‍➡️":"1f9ce-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🧎🏽‍♀️‍➡️":"1f9ce-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🧎🏾‍♀️‍➡️":"1f9ce-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","🧎🏿‍♀️‍➡️":"1f9ce-1f3ff-200d-2640-fe0f-200d-27a1-fe0f","🧎🏻‍♂️‍➡️":"1f9ce-1f3fb-200d-2642-fe0f-200d-27a1-fe0f","🧎🏼‍♂️‍➡️":"1f9ce-1f3fc-200d-2642-fe0f-200d-27a1-fe0f","🧎🏽‍♂️‍➡️":"1f9ce-1f3fd-200d-2642-fe0f-200d-27a1-fe0f","🧎🏾‍♂️‍➡️":"1f9ce-1f3fe-200d-2642-fe0f-200d-27a1-fe0f","🧎🏿‍♂️‍➡️":"1f9ce-1f3ff-200d-2642-fe0f-200d-27a1-fe0f","👨‍❤️‍💋‍👨":"1f468-200d-2764-fe0f-200d-1f48b-200d-1f468","🏃🏻‍♀️‍➡️":"1f3c3-1f3fb-200d-2640-fe0f-200d-27a1-fe0f","🏃🏼‍♀️‍➡️":"1f3c3-1f3fc-200d-2640-fe0f-200d-27a1-fe0f","🏃🏽‍♀️‍➡️":"1f3c3-1f3fd-200d-2640-fe0f-200d-27a1-fe0f","🏃🏾‍♀️‍➡️":"1f3c3-1f3fe-200d-2640-fe0f-200d-27a1-fe0f","👨🏻‍❤‍💋‍👨🏿":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏻‍❤‍💋‍👨🏻":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏻‍❤‍💋‍👨🏼":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏼‍❤‍💋‍👩🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏾‍❤‍💋‍👨🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","🧑🏾‍❤‍💋‍🧑🏻":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","👨🏾‍❤‍💋‍👨🏾":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏼‍❤‍💋‍👩🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👨🏼‍❤‍💋‍👨🏼":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏿‍❤‍💋‍👨🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏾‍❤‍💋‍👨🏿":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏼‍❤‍💋‍👩🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏿‍❤‍💋‍👨🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏾‍❤‍💋‍👨🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏽‍❤‍💋‍👩🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👨🏽‍❤‍💋‍👨🏽":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏽‍❤‍💋‍👩🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","🧑🏼‍❤‍💋‍🧑🏾":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","👨🏿‍❤‍💋‍👨🏻":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏾‍❤‍💋‍👨🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏽‍❤‍💋‍👩🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","🧑🏻‍❤‍💋‍🧑🏽":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","👩🏽‍❤‍💋‍👩🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","🧑🏻‍❤‍💋‍🧑🏿":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","👨🏽‍❤‍💋‍👨🏾":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏽‍❤‍💋‍👩🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👨🏿‍❤‍💋‍👨🏼":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏾‍❤‍💋‍👩🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏾‍❤‍💋‍👨🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏿‍❤‍💋‍👨🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏾‍❤‍💋‍👩🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👨🏼‍❤‍💋‍👨🏽":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","🧑🏼‍❤‍💋‍🧑🏿":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","👩🏾‍❤‍💋‍👩🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👨🏿‍❤‍💋‍👨🏽":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏾‍❤‍💋‍👩🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👨🏼‍❤‍💋‍👨🏿":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏽‍❤‍💋‍👨🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏾‍❤‍💋‍👩🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👨🏿‍❤‍💋‍👨🏾":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏿‍❤‍💋‍👩🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👨🏽‍❤‍💋‍👨🏿":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏼‍❤‍💋‍👨🏻":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏽‍❤‍💋‍👨🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏿‍❤‍💋‍👩🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👨🏿‍❤‍💋‍👨🏿":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏿‍❤‍💋‍👩🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏿‍❤‍💋‍👨🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏻‍❤‍💋‍👩🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏿‍❤‍💋‍👩🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","🧑🏽‍❤‍💋‍🧑🏻":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","👩🏿‍❤‍💋‍👩🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏽‍❤‍💋‍👨🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","🧑🏾‍❤‍💋‍🧑🏼":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","🧑🏻‍❤‍💋‍🧑🏼":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","🧑🏾‍❤‍💋‍🧑🏽":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","👩🏻‍❤‍💋‍👩🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👨🏾‍❤‍💋‍👨🏻":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","🧑🏾‍❤‍💋‍🧑🏿":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","🧑🏼‍❤‍💋‍🧑🏻":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","🧑🏿‍❤‍💋‍🧑🏻":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","👩🏽‍❤‍💋‍👨🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏻‍❤‍💋‍👩🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","🧑🏿‍❤‍💋‍🧑🏼":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","🧑🏽‍❤‍💋‍🧑🏼":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","👨🏽‍❤‍💋‍👨🏻":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏼‍❤‍💋‍👨🏾":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","🧑🏿‍❤‍💋‍🧑🏽":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","👩🏽‍❤‍💋‍👨🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏻‍❤‍💋‍👩🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","🧑🏿‍❤‍💋‍🧑🏾":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","🧑🏽‍❤‍💋‍🧑🏾":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","🧑🏻‍❤‍💋‍🧑🏾":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","👩🏼‍❤‍💋‍👨🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏻‍❤‍💋‍👨🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏾‍❤‍💋‍👨🏼":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏼‍❤‍💋‍👨🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏻‍❤‍💋‍👨🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏻‍❤‍💋‍👩🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏻‍❤‍💋‍👨🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏼‍❤‍💋‍👨🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏽‍❤‍💋‍👨🏼":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏾‍❤‍💋‍👨🏽":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏻‍❤‍💋‍👨🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏾‍❤‍💋‍👨🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏼‍❤‍💋‍👨🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏻‍❤‍💋‍👨🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","🧑🏽‍❤‍💋‍🧑🏿":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","👩🏼‍❤‍💋‍👩🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏼‍❤‍💋‍👨🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","🧑🏼‍❤‍💋‍🧑🏽":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","👨🏻‍❤‍💋‍👨🏾":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏻‍❤‍💋‍👨🏽":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏿‍❤‍💋‍👨🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏼‍❤‍💋‍👩🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏻‍❤️‍💋‍👨🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏻‍❤️‍💋‍👨🏽":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏼‍❤️‍💋‍👨🏼":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏼‍❤️‍💋‍👨🏽":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","🧑🏻‍❤️‍💋‍🧑🏼":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","👨🏻‍❤️‍💋‍👨🏼":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏼‍❤️‍💋‍👨🏾":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","🧑🏻‍❤️‍💋‍🧑🏽":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","👨🏼‍❤️‍💋‍👨🏿":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏻‍❤️‍💋‍👨🏻":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","🧑🏻‍❤️‍💋‍🧑🏾":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","👨🏽‍❤️‍💋‍👨🏻":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏽‍❤️‍💋‍👨🏼":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏽‍❤️‍💋‍👨🏽":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏼‍❤️‍💋‍👨🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","🧑🏻‍❤️‍💋‍🧑🏿":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","👨🏽‍❤️‍💋‍👨🏾":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏿‍❤️‍💋‍👨🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏽‍❤️‍💋‍👨🏿":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","🧑🏼‍❤️‍💋‍🧑🏻":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","👨🏾‍❤️‍💋‍👨🏻":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏿‍❤️‍💋‍👨🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏾‍❤️‍💋‍👨🏼":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","🧑🏼‍❤️‍💋‍🧑🏽":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","👨🏾‍❤️‍💋‍👨🏽":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏾‍❤️‍💋‍👨🏾":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏿‍❤️‍💋‍👨🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","🧑🏼‍❤️‍💋‍🧑🏾":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","👨🏾‍❤️‍💋‍👨🏿":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏿‍❤️‍💋‍👨🏻":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏿‍❤️‍💋‍👨🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏿‍❤️‍💋‍👨🏼":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","🧑🏼‍❤️‍💋‍🧑🏿":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","👨🏿‍❤️‍💋‍👨🏽":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏿‍❤️‍💋‍👨🏾":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","🧑🏽‍❤️‍💋‍🧑🏻":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","👨🏿‍❤️‍💋‍👨🏿":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏻‍❤️‍💋‍👩🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏻‍❤️‍💋‍👩🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","🧑🏽‍❤️‍💋‍🧑🏼":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","👩🏻‍❤️‍💋‍👩🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏻‍❤️‍💋‍👩🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","🧑🏽‍❤️‍💋‍🧑🏾":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","👩🏻‍❤️‍💋‍👩🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","🧑🏽‍❤️‍💋‍🧑🏿":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","👩🏿‍❤️‍💋‍👨🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏼‍❤️‍💋‍👩🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏼‍❤️‍💋‍👩🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏾‍❤️‍💋‍👨🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","🧑🏾‍❤️‍💋‍🧑🏻":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","👩🏼‍❤️‍💋‍👩🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏼‍❤️‍💋‍👩🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏾‍❤️‍💋‍👨🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","🧑🏾‍❤️‍💋‍🧑🏼":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","👩🏼‍❤️‍💋‍👩🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏽‍❤️‍💋‍👩🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏽‍❤️‍💋‍👩🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏾‍❤️‍💋‍👨🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏽‍❤️‍💋‍👩🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏽‍❤️‍💋‍👩🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏽‍❤️‍💋‍👩🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏾‍❤️‍💋‍👨🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏾‍❤️‍💋‍👩🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏾‍❤️‍💋‍👩🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏾‍❤️‍💋‍👩🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏾‍❤️‍💋‍👨🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏾‍❤️‍💋‍👩🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏾‍❤️‍💋‍👩🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏿‍❤️‍💋‍👩🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏽‍❤️‍💋‍👨🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏿‍❤️‍💋‍👩🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏿‍❤️‍💋‍👩🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏿‍❤️‍💋‍👩🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏽‍❤️‍💋‍👨🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏿‍❤️‍💋‍👩🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","🧑🏾‍❤️‍💋‍🧑🏽":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","🧑🏾‍❤️‍💋‍🧑🏿":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","👩🏽‍❤️‍💋‍👨🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","🧑🏿‍❤️‍💋‍🧑🏻":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","🧑🏿‍❤️‍💋‍🧑🏼":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","🧑🏿‍❤️‍💋‍🧑🏽":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","👩🏽‍❤️‍💋‍👨🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏼‍❤️‍💋‍👨🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","🧑🏿‍❤️‍💋‍🧑🏾":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","👨🏼‍❤️‍💋‍👨🏻":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏽‍❤️‍💋‍👨🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏻‍❤️‍💋‍👨🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏻‍❤️‍💋‍👨🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏼‍❤️‍💋‍👨🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏻‍❤️‍💋‍👨🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏻‍❤️‍💋‍👨🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏼‍❤️‍💋‍👨🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏼‍❤️‍💋‍👨🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏻‍❤️‍💋‍👨🏾":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏻‍❤️‍💋‍👨🏿":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff"} \ No newline at end of file diff --git a/app/javascript/mastodon/features/emoji/emoji_mart_data_light.ts b/app/javascript/mastodon/features/emoji/emoji_mart_data_light.ts index 1315518179b889..59d995e25fff4f 100644 --- a/app/javascript/mastodon/features/emoji/emoji_mart_data_light.ts +++ b/app/javascript/mastodon/features/emoji/emoji_mart_data_light.ts @@ -9,7 +9,7 @@ import type { ShortCodesToEmojiData, } from 'virtual:mastodon-emoji-compressed'; -import { unicodeToUnifiedName } from './unicode_to_unified_name'; +import { unicodeToUnifiedName } from './unicode_utils'; type Emojis = Record< NonNullable, @@ -23,7 +23,7 @@ type Emojis = Record< const [ shortCodesToEmojiData, - skins, + _skins, categories, short_names, _emojisWithoutShortCodes, @@ -47,4 +47,4 @@ Object.keys(shortCodesToEmojiData).forEach((shortCode) => { }; }); -export { emojis, skins, categories, short_names }; +export { emojis, categories, short_names }; diff --git a/app/javascript/mastodon/features/emoji/emoji_mart_search_light.js b/app/javascript/mastodon/features/emoji/emoji_mart_search_light.js index 83e154b0b287e5..038dd120c9a5a8 100644 --- a/app/javascript/mastodon/features/emoji/emoji_mart_search_light.js +++ b/app/javascript/mastodon/features/emoji/emoji_mart_search_light.js @@ -1,7 +1,7 @@ // This code is largely borrowed from: // https://github.com/missive/emoji-mart/blob/5f2ffcc/src/utils/emoji-index.js -import * as data from './emoji_mart_data_light'; +import { emojis, categories } from './emoji_mart_data_light'; import { getData, getSanitizedData, uniq, intersect } from './emoji_utils'; let originalPool = {}; @@ -10,8 +10,8 @@ let emojisList = {}; let emoticonsList = {}; let customEmojisList = []; -for (let emoji in data.emojis) { - let emojiData = data.emojis[emoji]; +for (let emoji in emojis) { + let emojiData = emojis[emoji]; let { short_names, emoticons } = emojiData; let id = short_names[0]; @@ -84,14 +84,14 @@ function search(value, { emojisToShowFilter, maxResults, include, exclude, custo if (include.length || exclude.length) { pool = {}; - data.categories.forEach(category => { + categories.forEach(category => { let isIncluded = include && include.length ? include.indexOf(category.name.toLowerCase()) > -1 : true; let isExcluded = exclude && exclude.length ? exclude.indexOf(category.name.toLowerCase()) > -1 : false; if (!isIncluded || isExcluded) { return; } - category.emojis.forEach(emojiId => pool[emojiId] = data.emojis[emojiId]); + category.emojis.forEach(emojiId => pool[emojiId] = emojis[emojiId]); }); if (custom.length) { @@ -171,7 +171,7 @@ function search(value, { emojisToShowFilter, maxResults, include, exclude, custo if (results) { if (emojisToShowFilter) { - results = results.filter((result) => emojisToShowFilter(data.emojis[result.id])); + results = results.filter((result) => emojisToShowFilter(emojis[result.id])); } if (results && results.length > maxResults) { diff --git a/app/javascript/mastodon/features/emoji/emoji_picker.tsx b/app/javascript/mastodon/features/emoji/emoji_picker.tsx index f5300c9fecef6f..37fc94dde7aa0c 100644 --- a/app/javascript/mastodon/features/emoji/emoji_picker.tsx +++ b/app/javascript/mastodon/features/emoji/emoji_picker.tsx @@ -4,9 +4,11 @@ import PickerRaw from 'emoji-mart/dist-es/components/picker/nimble-picker'; import { assetHost } from 'mastodon/utils/config'; +import { EMOJI_MODE_NATIVE } from './constants'; import EmojiData from './emoji_data.json'; +import { useEmojiAppState } from './mode'; -const backgroundImageFnDefault = () => `${assetHost}/emoji/sheet_15_1.png`; +const backgroundImageFnDefault = () => `${assetHost}/emoji/sheet_16_0.png`; const Emoji = ({ set = 'twitter', @@ -16,6 +18,7 @@ const Emoji = ({ backgroundImageFn = backgroundImageFnDefault, ...props }: EmojiProps) => { + const { mode } = useEmojiAppState(); return ( @@ -37,6 +41,7 @@ const Picker = ({ backgroundImageFn = backgroundImageFnDefault, ...props }: PickerProps) => { + const { mode } = useEmojiAppState(); return ( ); diff --git a/app/javascript/mastodon/features/emoji/emoji_unicode_mapping_light.ts b/app/javascript/mastodon/features/emoji/emoji_unicode_mapping_light.ts index a53496be2a8e2d..ecf36e3ea85ceb 100644 --- a/app/javascript/mastodon/features/emoji/emoji_unicode_mapping_light.ts +++ b/app/javascript/mastodon/features/emoji/emoji_unicode_mapping_light.ts @@ -8,7 +8,7 @@ import type { ShortCodesToEmojiDataKey, } from 'virtual:mastodon-emoji-compressed'; -import { unicodeToFilename } from './unicode_to_filename'; +import { unicodeToFilename } from './unicode_utils'; type UnicodeMapping = Record< FilenameData[number][0], diff --git a/app/javascript/mastodon/features/emoji/emoji_utils.js b/app/javascript/mastodon/features/emoji/emoji_utils.js index c13d25056706a4..75b2acafa5c853 100644 --- a/app/javascript/mastodon/features/emoji/emoji_utils.js +++ b/app/javascript/mastodon/features/emoji/emoji_utils.js @@ -209,50 +209,9 @@ function intersect(a, b) { return uniqA.filter(item => uniqB.indexOf(item) >= 0); } -function deepMerge(a, b) { - let o = {}; - - for (let key in a) { - let originalValue = a[key], - value = originalValue; - - if (Object.hasOwn(b, key)) { - value = b[key]; - } - - if (typeof value === 'object') { - value = deepMerge(originalValue, value); - } - - o[key] = value; - } - - return o; -} - -// https://github.com/sonicdoe/measure-scrollbar -function measureScrollbar() { - const div = document.createElement('div'); - - div.style.width = '100px'; - div.style.height = '100px'; - div.style.overflow = 'scroll'; - div.style.position = 'absolute'; - div.style.top = '-9999px'; - - document.body.appendChild(div); - const scrollbarWidth = div.offsetWidth - div.clientWidth; - document.body.removeChild(div); - - return scrollbarWidth; -} - export { getData, getSanitizedData, uniq, intersect, - deepMerge, - unifiedToNative, - measureScrollbar, }; diff --git a/app/javascript/mastodon/features/emoji/handlers.ts b/app/javascript/mastodon/features/emoji/handlers.ts deleted file mode 100644 index 3b02028f3c3608..00000000000000 --- a/app/javascript/mastodon/features/emoji/handlers.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { autoPlayGif } from '@/mastodon/initial_state'; - -const PARENT_MAX_DEPTH = 10; - -export function handleAnimateGif(event: MouseEvent) { - // We already check this in ui/index.jsx, but just to be sure. - if (autoPlayGif) { - return; - } - - const { target, type } = event; - const animate = type === 'mouseover'; // Mouse over = animate, mouse out = don't animate. - - if (target instanceof HTMLImageElement) { - setAnimateGif(target, animate); - } else if (!(target instanceof HTMLElement) || target === document.body) { - return; - } - - let parent: HTMLElement | null = null; - let iter = 0; - - if (target.classList.contains('animate-parent')) { - parent = target; - } else { - // Iterate up to PARENT_MAX_DEPTH levels up the DOM tree to find a parent with the class 'animate-parent'. - let current: HTMLElement | null = target; - while (current) { - if (iter >= PARENT_MAX_DEPTH) { - return; // We can just exit right now. - } - current = current.parentElement; - if (current?.classList.contains('animate-parent')) { - parent = current; - break; - } - iter++; - } - } - - // Affect all animated children within the parent. - if (parent) { - const animatedChildren = - parent.querySelectorAll('img.custom-emoji'); - for (const child of animatedChildren) { - setAnimateGif(child, animate); - } - } -} - -function setAnimateGif(image: HTMLImageElement, animate: boolean) { - const { classList, dataset } = image; - if ( - !classList.contains('custom-emoji') || - !dataset.static || - !dataset.original - ) { - return; - } - image.src = animate ? dataset.original : dataset.static; -} diff --git a/app/javascript/mastodon/features/emoji/hooks.ts b/app/javascript/mastodon/features/emoji/hooks.ts deleted file mode 100644 index 7e91486780a763..00000000000000 --- a/app/javascript/mastodon/features/emoji/hooks.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { useCallback, useLayoutEffect, useMemo, useState } from 'react'; - -import { isList } from 'immutable'; - -import type { ApiCustomEmojiJSON } from '@/mastodon/api_types/custom_emoji'; -import { useAppSelector } from '@/mastodon/store'; -import { isModernEmojiEnabled } from '@/mastodon/utils/environment'; - -import { toSupportedLocale } from './locale'; -import { determineEmojiMode } from './mode'; -import { emojifyElement, emojifyText } from './render'; -import type { - CustomEmojiMapArg, - EmojiAppState, - ExtraCustomEmojiMap, -} from './types'; -import { stringHasAnyEmoji } from './utils'; - -interface UseEmojifyOptions { - text: string; - extraEmojis?: CustomEmojiMapArg; - deep?: boolean; -} - -export function useEmojify({ - text, - extraEmojis, - deep = true, -}: UseEmojifyOptions) { - const [emojifiedText, setEmojifiedText] = useState(null); - - const appState = useEmojiAppState(); - const extra: ExtraCustomEmojiMap = useMemo(() => { - if (!extraEmojis) { - return {}; - } - if (isList(extraEmojis)) { - return ( - extraEmojis.toJS() as ApiCustomEmojiJSON[] - ).reduce( - (acc, emoji) => ({ ...acc, [emoji.shortcode]: emoji }), - {}, - ); - } - return extraEmojis; - }, [extraEmojis]); - - const emojify = useCallback( - async (input: string) => { - let result: string | null = null; - if (deep) { - const wrapper = document.createElement('div'); - wrapper.innerHTML = input; - if (await emojifyElement(wrapper, appState, extra)) { - result = wrapper.innerHTML; - } - } else { - result = await emojifyText(text, appState, extra); - } - if (result) { - setEmojifiedText(result); - } else { - setEmojifiedText(input); - } - }, - [appState, deep, extra, text], - ); - useLayoutEffect(() => { - if (isModernEmojiEnabled() && !!text.trim() && stringHasAnyEmoji(text)) { - void emojify(text); - } else { - // If no emoji or we don't want to render, fall back. - setEmojifiedText(text); - } - }, [emojify, text]); - - return emojifiedText; -} - -export function useEmojiAppState(): EmojiAppState { - const locale = useAppSelector((state) => - toSupportedLocale(state.meta.get('locale') as string), - ); - const mode = useAppSelector((state) => - determineEmojiMode(state.meta.get('emoji_style') as string), - ); - - return { - currentLocale: locale, - locales: [locale], - mode, - darkTheme: document.body.classList.contains('theme-default'), - }; -} diff --git a/app/javascript/mastodon/features/emoji/index.ts b/app/javascript/mastodon/features/emoji/index.ts index 99c16fe361c7fe..b134d884415e03 100644 --- a/app/javascript/mastodon/features/emoji/index.ts +++ b/app/javascript/mastodon/features/emoji/index.ts @@ -1,7 +1,8 @@ -import initialState from '@/mastodon/initial_state'; -import { loadWorker } from '@/mastodon/utils/workers'; +import { initialState } from '@/mastodon/initial_state'; +import type { EMOJI_DB_NAME_SHORTCODES } from './constants'; import { toSupportedLocale } from './locale'; +import type { LocaleOrCustom } from './types'; import { emojiLogger } from './utils'; const userLocale = toSupportedLocale(initialState?.meta.locale ?? 'en'); @@ -10,64 +11,94 @@ let worker: Worker | null = null; const log = emojiLogger('index'); -export function initializeEmoji() { +// This is too short, but better to fallback quickly than wait. +const WORKER_TIMEOUT = 1_000; + +export async function initializeEmoji() { log('initializing emojis'); + + // Create a temp worker, and assign it to the module-level worker once we know it's ready. + let tempWorker: Worker | null = null; if (!worker && 'Worker' in window) { try { - worker = loadWorker(new URL('./worker', import.meta.url), { - type: 'module', - }); + const { default: EmojiWorker } = await import('./worker?worker&inline'); + tempWorker = new EmojiWorker(); } catch (err) { console.warn('Error creating web worker:', err); } } - if (worker) { - // Assign worker to const to make TS happy inside the event listener. - const thisWorker = worker; - const timeoutId = setTimeout(() => { - log('worker is not ready after timeout'); - worker = null; - void fallbackLoad(); - }, 500); - thisWorker.addEventListener('message', (event: MessageEvent) => { - const { data: message } = event; - if (message === 'ready') { - log('worker ready, loading data'); - clearTimeout(timeoutId); - thisWorker.postMessage('custom'); - void loadEmojiLocale(userLocale); - // Load English locale as well, because people are still used to - // using it from before we supported other locales. - if (userLocale !== 'en') { - void loadEmojiLocale('en'); - } - } else { - log('got worker message: %s', message); - } - }); - } else { + if (!tempWorker) { void fallbackLoad(); + return; } + + const timeoutId = setTimeout(() => { + log('worker is not ready after timeout'); + void fallbackLoad(); + }, WORKER_TIMEOUT); + + tempWorker.addEventListener('message', (event: MessageEvent) => { + const { data: message } = event; + + worker ??= tempWorker; + + if (message === 'ready') { + log('worker ready, loading data'); + clearTimeout(timeoutId); + messageWorker('shortcodes'); + void loadCustomEmoji(); + void loadEmojiLocale(userLocale); + } else { + log('got worker message: %s', message); + } + }); } async function fallbackLoad() { log('falling back to main thread for loading'); - const { importCustomEmojiData } = await import('./loader'); - await importCustomEmojiData(); - await loadEmojiLocale(userLocale); - if (userLocale !== 'en') { - await loadEmojiLocale('en'); + + await loadCustomEmoji(); + const { importLegacyShortcodes } = await import('./loader'); + const shortcodes = await importLegacyShortcodes(); + if (shortcodes?.length) { + log('loaded %d legacy shortcodes', shortcodes.length); } + await loadEmojiLocale(userLocale); } -export async function loadEmojiLocale(localeString: string) { +async function loadEmojiLocale(localeString: string) { const locale = toSupportedLocale(localeString); + const { importEmojiData } = await import('./loader'); if (worker) { - worker.postMessage(locale); + log('asking worker to load locale %s', locale); + messageWorker(locale); } else { - const { importEmojiData } = await import('./loader'); - await importEmojiData(locale); + const emojis = await importEmojiData(locale); + if (emojis) { + log('loaded %d emojis to locale %s', emojis.length, locale); + } + } +} + +export async function loadCustomEmoji() { + if (worker) { + messageWorker('custom'); + } else { + const { importCustomEmojiData } = await import('./loader'); + const emojis = await importCustomEmojiData(); + if (emojis && emojis.length > 0) { + log('loaded %d custom emojis', emojis.length); + } + } +} + +function messageWorker( + locale: LocaleOrCustom | typeof EMOJI_DB_NAME_SHORTCODES, +) { + if (!worker) { + return; } + worker.postMessage({ locale }); } diff --git a/app/javascript/mastodon/features/emoji/loader.ts b/app/javascript/mastodon/features/emoji/loader.ts index 72f57b6f6c0f1f..c6b64fe29c0d4d 100644 --- a/app/javascript/mastodon/features/emoji/loader.ts +++ b/app/javascript/mastodon/features/emoji/loader.ts @@ -1,56 +1,145 @@ import { flattenEmojiData } from 'emojibase'; -import type { CompactEmoji, FlatCompactEmoji } from 'emojibase'; - -import type { ApiCustomEmojiJSON } from '@/mastodon/api_types/custom_emoji'; +import type { + CompactEmoji, + FlatCompactEmoji, + Locale, + ShortcodesDataset, +} from 'emojibase'; import { putEmojiData, putCustomEmojiData, loadLatestEtag, putLatestEtag, + putLegacyShortcodes, } from './database'; -import { toSupportedLocale, toSupportedLocaleOrCustom } from './locale'; -import type { LocaleOrCustom } from './types'; +import { toSupportedLocale, toValidEtagName } from './locale'; +import type { CustomEmojiData } from './types'; import { emojiLogger } from './utils'; const log = emojiLogger('loader'); -export async function importEmojiData(localeString: string) { +export async function importEmojiData(localeString: string, shortcodes = true) { const locale = toSupportedLocale(localeString); - const emojis = await fetchAndCheckEtag(locale); + + log( + 'importing emoji data for locale %s%s', + locale, + shortcodes ? ' and shortcodes' : '', + ); + + const emojis = await fetchAndCheckEtag({ + etagString: locale, + path: localeToEmojiPath(locale), + }); if (!emojis) { return; } - const flattenedEmojis: FlatCompactEmoji[] = flattenEmojiData(emojis); - log('loaded %d for %s locale', flattenedEmojis.length, locale); + + const shortcodesData: ShortcodesDataset[] = []; + if (shortcodes) { + const shortcodesResponse = await fetchAndCheckEtag({ + etagString: `${locale}-shortcodes`, + path: localeToShortcodesPath(locale), + }); + if (shortcodesResponse) { + shortcodesData.push(shortcodesResponse); + } else { + throw new Error(`No shortcodes data found for locale ${locale}`); + } + } + + const flattenedEmojis: FlatCompactEmoji[] = flattenEmojiData( + emojis, + shortcodesData, + ); await putEmojiData(flattenedEmojis, locale); + return flattenedEmojis; } export async function importCustomEmojiData() { - const emojis = await fetchAndCheckEtag('custom'); + const emojis = await fetchAndCheckEtag({ + etagString: 'custom', + path: '/api/v1/custom_emojis', + }); if (!emojis) { return; } - log('loaded %d custom emojis', emojis.length); - await putCustomEmojiData(emojis); + await putCustomEmojiData({ emojis, clear: true }); + return emojis; +} + +export async function importLegacyShortcodes() { + const globPaths = import.meta.glob( + // We use import.meta.glob to eagerly load the URL, as the regular import() doesn't work inside the Web Worker. + '../../../../../node_modules/emojibase-data/en/shortcodes/iamcal.json', + { eager: true, import: 'default', query: '?url' }, + ); + const path = Object.values(globPaths)[0]; + if (!path) { + throw new Error('IAMCAL shortcodes path not found'); + } + const shortcodesData = await fetchAndCheckEtag({ + checkEtag: true, + etagString: 'shortcodes', + path, + }); + if (!shortcodesData) { + return; + } + await putLegacyShortcodes(shortcodesData); + return Object.keys(shortcodesData); } -async function fetchAndCheckEtag( - localeOrCustom: LocaleOrCustom, -): Promise { - const locale = toSupportedLocaleOrCustom(localeOrCustom); +function localeToEmojiPath(locale: Locale) { + const key = `../../../../../node_modules/emojibase-data/${locale}/compact.json`; + const emojiModules = import.meta.glob( + '../../../../../node_modules/emojibase-data/**/compact.json', + { + query: '?url', + import: 'default', + eager: true, + }, + ); + const path = emojiModules[key]; + if (!path) { + throw new Error(`Unsupported locale: ${locale}`); + } + return path; +} - // Use location.origin as this script may be loaded from a CDN domain. - const url = new URL(location.origin); - if (locale === 'custom') { - url.pathname = '/api/v1/custom_emojis'; - } else { - // This doesn't use isDevelopment() as that module loads initial state - // which breaks workers, as they cannot access the DOM. - url.pathname = `/packs${import.meta.env.DEV ? '-dev' : ''}/emoji/${locale}.json`; +function localeToShortcodesPath(locale: Locale) { + const key = `../../../../../node_modules/emojibase-data/${locale}/shortcodes/cldr.json`; + const shortcodesModules = import.meta.glob( + '../../../../../node_modules/emojibase-data/**/shortcodes/cldr.json', + { + query: '?url', + import: 'default', + eager: true, + }, + ); + const path = shortcodesModules[key]; + if (!path) { + throw new Error(`Unsupported locale for shortcodes: ${locale}`); } + return path; +} - const oldEtag = await loadLatestEtag(locale); +async function fetchAndCheckEtag({ + etagString, + path, + checkEtag = false, +}: { + etagString: string; + path: string; + checkEtag?: boolean; +}): Promise { + const etagName = toValidEtagName(etagString); + + // Use location.origin as this script may be loaded from a CDN domain. + const url = new URL(path, location.origin); + + const oldEtag = checkEtag ? await loadLatestEtag(etagName) : null; const response = await fetch(url, { headers: { 'Content-Type': 'application/json', @@ -63,21 +152,17 @@ async function fetchAndCheckEtag( } if (!response.ok) { throw new Error( - `Failed to fetch emoji data for ${localeOrCustom}: ${response.statusText}`, + `Failed to fetch emoji data for ${etagName}: ${response.statusText}`, ); } const data = (await response.json()) as ResultType; - if (!Array.isArray(data)) { - throw new Error( - `Unexpected data format for ${localeOrCustom}: expected an array`, - ); - } // Store the ETag for future requests const etag = response.headers.get('ETag'); - if (etag) { - await putLatestEtag(etag, localeOrCustom); + if (etag && checkEtag) { + log(`storing new etag for ${etagName}: ${etag}`); + await putLatestEtag(etag, etagName); } return data; diff --git a/app/javascript/mastodon/features/emoji/locale.ts b/app/javascript/mastodon/features/emoji/locale.ts index 8ff23f5161a1a4..f39b56d47c22a6 100644 --- a/app/javascript/mastodon/features/emoji/locale.ts +++ b/app/javascript/mastodon/features/emoji/locale.ts @@ -1,7 +1,8 @@ import type { Locale } from 'emojibase'; import { SUPPORTED_LOCALES } from 'emojibase'; -import type { LocaleOrCustom } from './types'; +import { EMOJI_DB_NAME_SHORTCODES, EMOJI_TYPE_CUSTOM } from './constants'; +import type { EtagTypes, LocaleOrCustom, LocaleWithShortcodes } from './types'; export function toSupportedLocale(localeBase: string): Locale { const locale = localeBase.toLowerCase(); @@ -12,12 +13,35 @@ export function toSupportedLocale(localeBase: string): Locale { } export function toSupportedLocaleOrCustom(locale: string): LocaleOrCustom { - if (locale.toLowerCase() === 'custom') { - return 'custom'; + if (locale.toLowerCase() === EMOJI_TYPE_CUSTOM) { + return EMOJI_TYPE_CUSTOM; } return toSupportedLocale(locale); } +export function toValidEtagName(input: string): EtagTypes { + const lower = input.toLowerCase(); + if (lower === EMOJI_TYPE_CUSTOM || lower === EMOJI_DB_NAME_SHORTCODES) { + return lower; + } + + if (isLocaleWithShortcodes(lower)) { + return lower; + } + + return toSupportedLocale(lower); +} + function isSupportedLocale(locale: string): locale is Locale { - return SUPPORTED_LOCALES.includes(locale.toLowerCase() as Locale); + return SUPPORTED_LOCALES.includes(locale as Locale); +} + +function isLocaleWithShortcodes(input: string): input is LocaleWithShortcodes { + const [baseLocale, shortcodes] = input.split('-'); + return ( + !!baseLocale && + !!shortcodes && + isSupportedLocale(baseLocale) && + shortcodes === EMOJI_DB_NAME_SHORTCODES + ); } diff --git a/app/javascript/mastodon/features/emoji/mode.ts b/app/javascript/mastodon/features/emoji/mode.ts index 0f581d8b504b92..6950626375e28e 100644 --- a/app/javascript/mastodon/features/emoji/mode.ts +++ b/app/javascript/mastodon/features/emoji/mode.ts @@ -1,14 +1,36 @@ // Credit to Nolan Lawson for the original implementation. // See: https://github.com/nolanlawson/emoji-picker-element/blob/master/src/picker/utils/testColorEmojiSupported.js +import { createAppSelector, useAppSelector } from '@/mastodon/store'; import { isDevelopment } from '@/mastodon/utils/environment'; +import { isDarkMode } from '@/mastodon/utils/theme'; import { EMOJI_MODE_NATIVE, EMOJI_MODE_NATIVE_WITH_FLAGS, EMOJI_MODE_TWEMOJI, } from './constants'; -import type { EmojiMode } from './types'; +import { toSupportedLocale } from './locale'; +import type { EmojiAppState, EmojiMode } from './types'; + +const modeSelector = createAppSelector( + [(state) => state.meta.get('emoji_style') as string], + (emoji_style) => determineEmojiMode(emoji_style), +); + +export function useEmojiAppState(): EmojiAppState { + const locale = useAppSelector((state) => + toSupportedLocale(state.meta.get('locale') as string), + ); + const mode = useAppSelector(modeSelector); + + return { + currentLocale: locale, + locales: [locale], + mode, + darkTheme: isDarkMode(), + }; +} type Feature = Uint8ClampedArray; @@ -55,7 +77,7 @@ function testEmojiSupport(text: string) { return compareFeatures(feature1, feature2); } -const EMOJI_VERSION_TEST_EMOJI = '🫨'; // shaking head, from v15 +const EMOJI_VERSION_TEST_EMOJI = '🫩'; // face with bags under eyes, from Unicode 16.0. const EMOJI_FLAG_TEST_EMOJI = '🇨🇭'; export function determineEmojiMode(style: string): EmojiMode { diff --git a/app/javascript/mastodon/features/emoji/normalize.test.ts b/app/javascript/mastodon/features/emoji/normalize.test.ts index f0ea140590b068..8222ab81e588eb 100644 --- a/app/javascript/mastodon/features/emoji/normalize.test.ts +++ b/app/javascript/mastodon/features/emoji/normalize.test.ts @@ -5,11 +5,8 @@ import { flattenEmojiData } from 'emojibase'; import unicodeRawEmojis from 'emojibase-data/en/data.json'; import { - twemojiHasBorder, twemojiToUnicodeInfo, unicodeToTwemojiHex, - CODES_WITH_DARK_BORDER, - CODES_WITH_LIGHT_BORDER, emojiToUnicodeHex, } from './normalize'; @@ -36,6 +33,7 @@ describe('emojiToUnicodeHex', () => { ['⚫', '26AB'], ['🖤', '1F5A4'], ['💀', '1F480'], + ['❤️', '2764'], // Checks for trailing variation selector removal. ['💂‍♂️', '1F482-200D-2642-FE0F'], ] as const)( 'emojiToUnicodeHex converts %s to %s', @@ -57,26 +55,6 @@ describe('unicodeToTwemojiHex', () => { }); }); -describe('twemojiHasBorder', () => { - test.concurrent.for( - svgFileNames - .filter((file) => file.endsWith('_border')) - .map((file) => { - const hexCode = file.replace('_border', ''); - return [ - hexCode, - CODES_WITH_LIGHT_BORDER.includes(hexCode.toUpperCase()), - CODES_WITH_DARK_BORDER.includes(hexCode.toUpperCase()), - ] as const; - }), - )('twemojiHasBorder for %s', ([hexCode, isLight, isDark], { expect }) => { - const result = twemojiHasBorder(hexCode); - expect(result).toHaveProperty('hexCode', hexCode); - expect(result).toHaveProperty('hasLightBorder', isLight); - expect(result).toHaveProperty('hasDarkBorder', isDark); - }); -}); - describe('twemojiToUnicodeInfo', () => { const unicodeCodeSet = new Set(unicodeEmojis.map((emoji) => emoji.hexcode)); diff --git a/app/javascript/mastodon/features/emoji/normalize.ts b/app/javascript/mastodon/features/emoji/normalize.ts index 6a64c3b8bfabc2..24df808ae1fd6a 100644 --- a/app/javascript/mastodon/features/emoji/normalize.ts +++ b/app/javascript/mastodon/features/emoji/normalize.ts @@ -1,3 +1,7 @@ +import { isList } from 'immutable'; + +import { assetHost } from '@/mastodon/utils/config'; + import { VARIATION_SELECTOR_CODE, KEYCAP_CODE, @@ -6,8 +10,10 @@ import { SKIN_TONE_CODES, EMOJIS_WITH_DARK_BORDER, EMOJIS_WITH_LIGHT_BORDER, + EMOJIS_REQUIRING_INVERSION_IN_LIGHT_MODE, + EMOJIS_REQUIRING_INVERSION_IN_DARK_MODE, } from './constants'; -import type { TwemojiBorderInfo } from './types'; +import type { CustomEmojiMapArg, ExtraCustomEmojiMap } from './types'; // Misc codes that have special handling const SKIER_CODE = 0x26f7; @@ -26,6 +32,12 @@ export function emojiToUnicodeHex(emoji: string): string { codes.push(code); } } + + // Handles how Emojibase removes the variation selector for single code emojis. + // See: https://emojibase.dev/docs/spec/#merged-variation-selectors + if (codes.at(1) === VARIATION_SELECTOR_CODE && codes.length === 2) { + codes.pop(); + } return hexNumbersToString(codes); } @@ -61,21 +73,17 @@ export const CODES_WITH_DARK_BORDER = export const CODES_WITH_LIGHT_BORDER = EMOJIS_WITH_LIGHT_BORDER.map(emojiToUnicodeHex); -export function twemojiHasBorder(twemojiHex: string): TwemojiBorderInfo { - const normalizedHex = twemojiHex.toUpperCase(); - let hasLightBorder = false; - let hasDarkBorder = false; - if (CODES_WITH_LIGHT_BORDER.includes(normalizedHex)) { - hasLightBorder = true; +export function unicodeHexToUrl(unicodeHex: string, darkMode: boolean): string { + const normalizedHex = unicodeToTwemojiHex(unicodeHex); + let url = `${assetHost}/emoji/${normalizedHex}`; + if (darkMode && CODES_WITH_LIGHT_BORDER.includes(normalizedHex)) { + url += '_border'; } if (CODES_WITH_DARK_BORDER.includes(normalizedHex)) { - hasDarkBorder = true; + url += '_border'; } - return { - hexCode: twemojiHex, - hasLightBorder, - hasDarkBorder, - }; + url += '.svg'; + return url; } interface TwemojiSpecificEmoji { @@ -150,6 +158,37 @@ export function twemojiToUnicodeInfo( return hexNumbersToString(mappedCodes); } +export function emojiToInversionClassName(emoji: string): string | null { + if (EMOJIS_REQUIRING_INVERSION_IN_DARK_MODE.includes(emoji)) { + return 'invert-on-dark'; + } + if (EMOJIS_REQUIRING_INVERSION_IN_LIGHT_MODE.includes(emoji)) { + return 'invert-on-light'; + } + return null; +} + +export function cleanExtraEmojis(extraEmojis?: CustomEmojiMapArg) { + if (!extraEmojis) { + return null; + } + if (Array.isArray(extraEmojis)) { + return extraEmojis.reduce( + (acc, emoji) => ({ ...acc, [emoji.shortcode]: emoji }), + {}, + ); + } + if (isList(extraEmojis)) { + return extraEmojis + .toJS() + .reduce( + (acc, emoji) => ({ ...acc, [emoji.shortcode]: emoji }), + {}, + ); + } + return extraEmojis; +} + function hexStringToNumbers(hexString: string): number[] { return hexString .split('-') diff --git a/app/javascript/mastodon/features/emoji/render.test.ts b/app/javascript/mastodon/features/emoji/render.test.ts index e9609e15dc5060..dffebd1f8c576c 100644 --- a/app/javascript/mastodon/features/emoji/render.test.ts +++ b/app/javascript/mastodon/features/emoji/render.test.ts @@ -1,192 +1,15 @@ import { customEmojiFactory, unicodeEmojiFactory } from '@/testing/factories'; -import { - EMOJI_MODE_NATIVE, - EMOJI_MODE_NATIVE_WITH_FLAGS, - EMOJI_MODE_TWEMOJI, -} from './constants'; import * as db from './database'; +import * as loader from './loader'; import { - emojifyElement, - emojifyText, - testCacheClear, + loadEmojiDataToState, + stringToEmojiState, tokenizeText, } from './render'; -import type { EmojiAppState, ExtraCustomEmojiMap } from './types'; - -function mockDatabase() { - return { - searchCustomEmojisByShortcodes: vi - .spyOn(db, 'searchCustomEmojisByShortcodes') - .mockResolvedValue([customEmojiFactory()]), - searchEmojisByHexcodes: vi - .spyOn(db, 'searchEmojisByHexcodes') - .mockResolvedValue([ - unicodeEmojiFactory({ - hexcode: '1F60A', - label: 'smiling face with smiling eyes', - unicode: '😊', - }), - unicodeEmojiFactory({ - hexcode: '1F1EA-1F1FA', - label: 'flag-eu', - unicode: '🇪🇺', - }), - ]), - }; -} - -const expectedSmileImage = - '😊'; -const expectedFlagImage = - '🇪🇺'; -const expectedCustomEmojiImage = - ':custom:'; -const expectedRemoteCustomEmojiImage = - ':remote:'; - -const mockExtraCustom: ExtraCustomEmojiMap = { - remote: { - shortcode: 'remote', - static_url: 'remote.social/static', - url: 'remote.social/custom', - }, -}; - -function testAppState(state: Partial = {}) { - return { - locales: ['en'], - mode: EMOJI_MODE_TWEMOJI, - currentLocale: 'en', - darkTheme: false, - ...state, - } satisfies EmojiAppState; -} - -describe('emojifyElement', () => { - function testElement(text = '

Hello 😊🇪🇺!

:custom:

') { - const testElement = document.createElement('div'); - testElement.innerHTML = text; - return testElement; - } - - afterEach(() => { - testCacheClear(); - vi.restoreAllMocks(); - }); - - test('caches element rendering results', async () => { - const { searchCustomEmojisByShortcodes, searchEmojisByHexcodes } = - mockDatabase(); - await emojifyElement(testElement(), testAppState()); - await emojifyElement(testElement(), testAppState()); - await emojifyElement(testElement(), testAppState()); - expect(searchEmojisByHexcodes).toHaveBeenCalledExactlyOnceWith( - ['1F1EA-1F1FA', '1F60A'], - 'en', - ); - expect(searchCustomEmojisByShortcodes).toHaveBeenCalledExactlyOnceWith([ - 'custom', - ]); - }); - - test('emojifies custom emoji in native mode', async () => { - const { searchEmojisByHexcodes } = mockDatabase(); - const actual = await emojifyElement( - testElement(), - testAppState({ mode: EMOJI_MODE_NATIVE }), - ); - assert(actual); - expect(actual.innerHTML).toBe( - `

Hello 😊🇪🇺!

${expectedCustomEmojiImage}

`, - ); - expect(searchEmojisByHexcodes).not.toHaveBeenCalled(); - }); - - test('emojifies flag emoji in native-with-flags mode', async () => { - const { searchEmojisByHexcodes } = mockDatabase(); - const actual = await emojifyElement( - testElement(), - testAppState({ mode: EMOJI_MODE_NATIVE_WITH_FLAGS }), - ); - assert(actual); - expect(actual.innerHTML).toBe( - `

Hello 😊${expectedFlagImage}!

${expectedCustomEmojiImage}

`, - ); - expect(searchEmojisByHexcodes).toHaveBeenCalledOnce(); - }); - - test('emojifies everything in twemoji mode', async () => { - const { searchCustomEmojisByShortcodes, searchEmojisByHexcodes } = - mockDatabase(); - const actual = await emojifyElement(testElement(), testAppState()); - assert(actual); - expect(actual.innerHTML).toBe( - `

Hello ${expectedSmileImage}${expectedFlagImage}!

${expectedCustomEmojiImage}

`, - ); - expect(searchEmojisByHexcodes).toHaveBeenCalledOnce(); - expect(searchCustomEmojisByShortcodes).toHaveBeenCalledOnce(); - }); - - test('emojifies with provided custom emoji', async () => { - const { searchCustomEmojisByShortcodes, searchEmojisByHexcodes } = - mockDatabase(); - const actual = await emojifyElement( - testElement('

hi :remote:

'), - testAppState(), - mockExtraCustom, - ); - assert(actual); - expect(actual.innerHTML).toBe( - `

hi ${expectedRemoteCustomEmojiImage}

`, - ); - expect(searchEmojisByHexcodes).not.toHaveBeenCalled(); - expect(searchCustomEmojisByShortcodes).not.toHaveBeenCalled(); - }); - - test('returns null when no emoji are found', async () => { - mockDatabase(); - const actual = await emojifyElement( - testElement('

here is just text :)

'), - testAppState(), - ); - expect(actual).toBeNull(); - }); -}); - -describe('emojifyText', () => { - test('returns original input when no emoji are in string', async () => { - const actual = await emojifyText('nothing here', testAppState()); - expect(actual).toBe('nothing here'); - }); - - test('renders Unicode emojis to twemojis', async () => { - mockDatabase(); - const actual = await emojifyText('Hello 😊🇪🇺!', testAppState()); - expect(actual).toBe(`Hello ${expectedSmileImage}${expectedFlagImage}!`); - }); - - test('renders custom emojis', async () => { - mockDatabase(); - const actual = await emojifyText('Hello :custom:!', testAppState()); - expect(actual).toBe(`Hello ${expectedCustomEmojiImage}!`); - }); - - test('renders provided extra emojis', async () => { - const actual = await emojifyText( - 'remote emoji :remote:', - testAppState(), - mockExtraCustom, - ); - expect(actual).toBe(`remote emoji ${expectedRemoteCustomEmojiImage}`); - }); -}); +import type { EmojiStateCustom, EmojiStateUnicode } from './types'; describe('tokenizeText', () => { - test('returns empty array for string with only whitespace', () => { - expect(tokenizeText(' \n')).toEqual([]); - }); - test('returns an array of text to be a single token', () => { expect(tokenizeText('Hello')).toEqual(['Hello']); }); @@ -212,7 +35,7 @@ describe('tokenizeText', () => { 'Hello ', { type: 'custom', - code: 'smile', + code: ':smile:', }, '!!', ]); @@ -223,7 +46,7 @@ describe('tokenizeText', () => { 'Hello ', { type: 'custom', - code: 'smile_123', + code: ':smile_123:', }, '!!', ]); @@ -239,7 +62,7 @@ describe('tokenizeText', () => { ' ', { type: 'custom', - code: 'smile', + code: ':smile:', }, '!!', ]); @@ -251,3 +74,131 @@ describe('tokenizeText', () => { ]); }); }); + +describe('stringToEmojiState', () => { + test('returns unicode emoji state for valid unicode emoji', () => { + expect(stringToEmojiState('😊')).toEqual({ + type: 'unicode', + code: '1F60A', + }); + }); + + test('returns null for custom emoji without data', () => { + expect(stringToEmojiState(':smile:')).toBeNull(); + }); + + test('returns custom emoji state with data when provided', () => { + const customEmoji = { + smile: customEmojiFactory({ + shortcode: 'smile', + url: 'https://example.com/smile.png', + static_url: 'https://example.com/smile_static.png', + }), + }; + expect(stringToEmojiState(':smile:', customEmoji)).toEqual({ + type: 'custom', + code: 'smile', + data: customEmoji.smile, + }); + }); + + test('returns null for invalid emoji strings', () => { + expect(stringToEmojiState('notanemoji')).toBeNull(); + }); +}); + +describe('loadEmojiDataToState', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + test('loads unicode data into state', async () => { + const dbCall = vi + .spyOn(db, 'loadEmojiByHexcode') + .mockResolvedValue(unicodeEmojiFactory()); + const dbLegacyCall = vi + .spyOn(db, 'loadLegacyShortcodesByShortcode') + .mockResolvedValueOnce({ + shortcodes: ['legacy_code'], + hexcode: '1F60A', + }); + const unicodeState = { + type: 'unicode', + code: '1F60A', + } as const satisfies EmojiStateUnicode; + const result = await loadEmojiDataToState(unicodeState, 'en'); + expect(dbCall).toHaveBeenCalledWith('1F60A', 'en'); + expect(dbLegacyCall).toHaveBeenCalledWith('1F60A'); + expect(result).toEqual({ + type: 'unicode', + code: '1F60A', + data: unicodeEmojiFactory(), + shortcode: 'legacy_code', + }); + }); + + test('returns null for custom emoji without data', async () => { + const customState = { + type: 'custom', + code: 'smile', + } as const satisfies EmojiStateCustom; + const result = await loadEmojiDataToState(customState, 'en'); + expect(result).toBeNull(); + }); + + test('loads unicode data using legacy shortcode', async () => { + const dbLegacyCall = vi + .spyOn(db, 'loadLegacyShortcodesByShortcode') + .mockResolvedValueOnce({ + shortcodes: ['test'], + hexcode: 'test', + }); + const dbUnicodeCall = vi + .spyOn(db, 'loadEmojiByHexcode') + .mockResolvedValue(unicodeEmojiFactory()); + const unicodeState = { + type: 'unicode', + code: 'test', + } as const satisfies EmojiStateUnicode; + const result = await loadEmojiDataToState(unicodeState, 'en'); + expect(dbLegacyCall).toHaveBeenCalledWith('test'); + expect(dbUnicodeCall).toHaveBeenCalledWith('test', 'en'); + expect(result).toEqual({ + type: 'unicode', + code: 'test', + data: unicodeEmojiFactory(), + shortcode: 'test', + }); + }); + + test('returns null if unicode emoji not found in database', async () => { + vi.spyOn(db, 'loadEmojiByHexcode').mockResolvedValueOnce(undefined); + const unicodeState = { + type: 'unicode', + code: '1F60A', + } as const satisfies EmojiStateUnicode; + const result = await loadEmojiDataToState(unicodeState, 'en'); + expect(result).toBeNull(); + }); + + test('retries loading emoji data once if initial load fails', async () => { + const dbCall = vi + .spyOn(db, 'loadEmojiByHexcode') + .mockRejectedValue(new db.LocaleNotLoadedError('en')); + vi.spyOn(loader, 'importEmojiData').mockResolvedValueOnce(undefined); + const consoleCall = vi + .spyOn(console, 'warn') + .mockImplementationOnce(() => null); + + const unicodeState = { + type: 'unicode', + code: '1F60A', + } as const satisfies EmojiStateUnicode; + const result = await loadEmojiDataToState(unicodeState, 'en'); + + expect(dbCall).toHaveBeenCalledTimes(2); + expect(loader.importEmojiData).toHaveBeenCalledWith('en'); + expect(consoleCall).toHaveBeenCalled(); + expect(result).toBeNull(); + }); +}); diff --git a/app/javascript/mastodon/features/emoji/render.ts b/app/javascript/mastodon/features/emoji/render.ts index 8d2299fd89e682..8fe311014a95c7 100644 --- a/app/javascript/mastodon/features/emoji/render.ts +++ b/app/javascript/mastodon/features/emoji/render.ts @@ -1,212 +1,38 @@ -import { autoPlayGif } from '@/mastodon/initial_state'; -import { createLimitedCache } from '@/mastodon/utils/cache'; -import { assetHost } from '@/mastodon/utils/config'; -import * as perf from '@/mastodon/utils/performance'; - import { EMOJI_MODE_NATIVE, EMOJI_MODE_NATIVE_WITH_FLAGS, EMOJI_TYPE_UNICODE, EMOJI_TYPE_CUSTOM, - EMOJI_STATE_MISSING, } from './constants'; -import { - searchCustomEmojisByShortcodes, - searchEmojisByHexcodes, -} from './database'; -import { - emojiToUnicodeHex, - twemojiHasBorder, - unicodeToTwemojiHex, -} from './normalize'; +import { emojiToUnicodeHex } from './normalize'; import type { - CustomEmojiToken, - EmojiAppState, EmojiLoadedState, EmojiMode, EmojiState, - EmojiStateMap, - EmojiToken, + EmojiStateCustom, + EmojiStateUnicode, ExtraCustomEmojiMap, - LocaleOrCustom, - UnicodeEmojiToken, } from './types'; import { anyEmojiRegex, emojiLogger, - stringHasAnyEmoji, + isCustomEmoji, + isUnicodeEmoji, stringHasUnicodeFlags, } from './utils'; const log = emojiLogger('render'); +type TokenizedText = (string | EmojiState)[]; + /** - * Emojifies an element. This modifies the element in place, replacing text nodes with emojified versions. + * Tokenizes text into strings and emoji states. + * @param text Text to tokenize. + * @returns Array of strings and emoji states. */ -export async function emojifyElement( - element: Element, - appState: EmojiAppState, - extraEmojis: ExtraCustomEmojiMap = {}, -): Promise { - const cacheKey = createCacheKey(element, appState, extraEmojis); - const cached = getCached(cacheKey); - if (cached !== undefined) { - log('Cache hit on %s', element.outerHTML); - if (cached === null) { - return null; - } - element.innerHTML = cached; - return element; - } - if (!stringHasAnyEmoji(element.innerHTML)) { - updateCache(cacheKey, null); - return null; - } - perf.start('emojifyElement()'); - const queue: (HTMLElement | Text)[] = [element]; - while (queue.length > 0) { - const current = queue.shift(); - if ( - !current || - current instanceof HTMLScriptElement || - current instanceof HTMLStyleElement - ) { - continue; - } - - if ( - current.textContent && - (current instanceof Text || !current.hasChildNodes()) - ) { - const renderedContent = await textToElementArray( - current.textContent, - appState, - extraEmojis, - ); - if (renderedContent) { - if (!(current instanceof Text)) { - current.textContent = null; // Clear the text content if it's not a Text node. - } - current.replaceWith(renderedToHTML(renderedContent)); - } - continue; - } - - for (const child of current.childNodes) { - if (child instanceof HTMLElement || child instanceof Text) { - queue.push(child); - } - } - } - updateCache(cacheKey, element.innerHTML); - perf.stop('emojifyElement()'); - return element; -} - -export async function emojifyText( - text: string, - appState: EmojiAppState, - extraEmojis: ExtraCustomEmojiMap = {}, -): Promise { - const cacheKey = createCacheKey(text, appState, extraEmojis); - const cached = getCached(cacheKey); - if (cached !== undefined) { - log('Cache hit on %s', text); - return cached ?? text; - } - if (!stringHasAnyEmoji(text)) { - updateCache(cacheKey, null); - return text; - } - const eleArray = await textToElementArray(text, appState, extraEmojis); - if (!eleArray) { - updateCache(cacheKey, null); - return text; - } - const rendered = renderedToHTML(eleArray, document.createElement('div')); - updateCache(cacheKey, rendered.innerHTML); - return rendered.innerHTML; -} - -// Private functions - -const { - set: updateCache, - get: getCached, - clear: cacheClear, -} = createLimitedCache({ log: log.extend('cache') }); - -function createCacheKey( - input: HTMLElement | string, - appState: EmojiAppState, - extraEmojis: ExtraCustomEmojiMap, -) { - return JSON.stringify([ - input instanceof HTMLElement ? input.outerHTML : input, - appState, - extraEmojis, - ]); -} - -type EmojifiedTextArray = (string | HTMLImageElement)[]; - -async function textToElementArray( - text: string, - appState: EmojiAppState, - extraEmojis: ExtraCustomEmojiMap = {}, -): Promise { - // Exit if no text to convert. - if (!text.trim()) { - return null; - } - - const tokens = tokenizeText(text); - - // If only one token and it's a string, exit early. - if (tokens.length === 1 && typeof tokens[0] === 'string') { - return null; - } - - // Get all emoji from the state map, loading any missing ones. - await loadMissingEmojiIntoCache(tokens, appState, extraEmojis); - - const renderedFragments: EmojifiedTextArray = []; - for (const token of tokens) { - if (typeof token !== 'string' && shouldRenderImage(token, appState.mode)) { - let state: EmojiState | undefined; - if (token.type === EMOJI_TYPE_CUSTOM) { - const extraEmojiData = extraEmojis[token.code]; - if (extraEmojiData) { - state = { type: EMOJI_TYPE_CUSTOM, data: extraEmojiData }; - } else { - state = emojiForLocale(token.code, EMOJI_TYPE_CUSTOM); - } - } else { - state = emojiForLocale( - emojiToUnicodeHex(token.code), - appState.currentLocale, - ); - } - - // If the state is valid, create an image element. Otherwise, just append as text. - if (state && typeof state !== 'string') { - const image = stateToImage(state, appState); - renderedFragments.push(image); - continue; - } - } - const text = typeof token === 'string' ? token : token.code; - renderedFragments.push(text); - } - - return renderedFragments; -} - -type TokenizedText = (string | EmojiToken)[]; - export function tokenizeText(text: string): TokenizedText { if (!text.trim()) { - return []; + return [text]; } const tokens = []; @@ -222,14 +48,14 @@ export function tokenizeText(text: string): TokenizedText { // Custom emoji tokens.push({ type: EMOJI_TYPE_CUSTOM, - code: code.slice(1, -1), // Remove the colons - } satisfies CustomEmojiToken); + code, + } satisfies EmojiStateCustom); } else { // Unicode emoji tokens.push({ type: EMOJI_TYPE_UNICODE, code: code, - } satisfies UnicodeEmojiToken); + } satisfies EmojiStateUnicode); } lastIndex = match.index + code.length; } @@ -239,107 +65,124 @@ export function tokenizeText(text: string): TokenizedText { return tokens; } -const localeCacheMap = new Map([ - [ - EMOJI_TYPE_CUSTOM, - createLimitedCache({ log: log.extend('custom') }), - ], -]); - -function cacheForLocale(locale: LocaleOrCustom): EmojiStateMap { - return ( - localeCacheMap.get(locale) ?? - createLimitedCache({ log: log.extend(locale) }) - ); -} - -function emojiForLocale( +/** + * Parses emoji string to extract emoji state. + * @param code Hex code or custom shortcode. + * @param customEmoji Extra custom emojis. + */ +export function stringToEmojiState( code: string, - locale: LocaleOrCustom, -): EmojiState | undefined { - const cache = cacheForLocale(locale); - return cache.get(code); -} - -async function loadMissingEmojiIntoCache( - tokens: TokenizedText, - { mode, currentLocale }: EmojiAppState, - extraEmojis: ExtraCustomEmojiMap, -) { - const missingUnicodeEmoji = new Set(); - const missingCustomEmoji = new Set(); - - // Iterate over tokens and check if they are in the cache already. - for (const token of tokens) { - if (typeof token === 'string') { - continue; // Skip plain strings. + customEmoji: ExtraCustomEmojiMap = {}, +): EmojiStateUnicode | Required | null { + if (isUnicodeEmoji(code)) { + return { + type: EMOJI_TYPE_UNICODE, + code: emojiToUnicodeHex(code), + }; + } + + if (isCustomEmoji(code)) { + const shortCode = code.slice(1, -1); + if (customEmoji[shortCode]) { + return { + type: EMOJI_TYPE_CUSTOM, + code: shortCode, + data: customEmoji[shortCode], + }; } + } - // If this is a custom emoji, check it separately. - if (token.type === EMOJI_TYPE_CUSTOM) { - const code = token.code; - if (code in extraEmojis) { - continue; // We don't care about extra emoji. - } - const emojiState = emojiForLocale(code, EMOJI_TYPE_CUSTOM); - if (!emojiState) { - missingCustomEmoji.add(code); - } - // Otherwise this is a unicode emoji, so check it against all locales. - } else if (shouldRenderImage(token, mode)) { - const code = emojiToUnicodeHex(token.code); - if (missingUnicodeEmoji.has(code)) { - continue; // Already marked as missing. - } - const emojiState = emojiForLocale(code, currentLocale); - if (!emojiState) { - // If it's missing in one locale, we consider it missing for all. - missingUnicodeEmoji.add(code); - } - } + return null; +} + +/** + * Loads emoji data into the given state if not already loaded. + * @param state Emoji state to load data for. + * @param locale Locale to load data for. Only for Unicode emoji. + * @param retry Internal. Whether this is a retry after loading the locale. + */ +export async function loadEmojiDataToState( + state: EmojiState, + locale: string, + retry = false, +): Promise { + if (isStateLoaded(state)) { + return state; } - if (missingUnicodeEmoji.size > 0) { - const missingEmojis = Array.from(missingUnicodeEmoji).toSorted(); - const emojis = await searchEmojisByHexcodes(missingEmojis, currentLocale); - const cache = cacheForLocale(currentLocale); - for (const emoji of emojis) { - cache.set(emoji.hexcode, { type: EMOJI_TYPE_UNICODE, data: emoji }); - } - const notFoundEmojis = missingEmojis.filter((code) => - emojis.every((emoji) => emoji.hexcode !== code), - ); - for (const code of notFoundEmojis) { - cache.set(code, EMOJI_STATE_MISSING); // Mark as missing if not found, as it's probably not a valid emoji. - } - localeCacheMap.set(currentLocale, cache); + // Don't try to load data for custom emoji. + if (state.type === EMOJI_TYPE_CUSTOM) { + return null; } - if (missingCustomEmoji.size > 0) { - const missingEmojis = Array.from(missingCustomEmoji).toSorted(); - const emojis = await searchCustomEmojisByShortcodes(missingEmojis); - const cache = cacheForLocale(EMOJI_TYPE_CUSTOM); - for (const emoji of emojis) { - cache.set(emoji.shortcode, { type: EMOJI_TYPE_CUSTOM, data: emoji }); - } - const notFoundEmojis = missingEmojis.filter((code) => - emojis.every((emoji) => emoji.shortcode !== code), + const { + loadLegacyShortcodesByShortcode, + loadEmojiByHexcode, + LocaleNotLoadedError, + } = await import('./database'); + + // First, try to load the data from IndexedDB. + try { + const legacyCode = await loadLegacyShortcodesByShortcode(state.code); + // This is duplicative, but that's because TS can't distinguish the state type easily. + const data = await loadEmojiByHexcode( + legacyCode?.hexcode ?? state.code, + locale, ); - for (const code of notFoundEmojis) { - cache.set(code, EMOJI_STATE_MISSING); // Mark as missing if not found, as it's probably not a valid emoji. + if (data) { + return { + ...state, + type: EMOJI_TYPE_UNICODE, + data, + // TODO: Use CLDR shortcodes when the picker supports them. + shortcode: legacyCode?.shortcodes.at(0), + }; + } + + // If not found, assume it's not an emoji and return null. + log( + 'Could not find emoji %s of type %s for locale %s', + state.code, + state.type, + locale, + ); + return null; + } catch (err: unknown) { + // If the locale is not loaded, load it and retry once. + if (!retry && err instanceof LocaleNotLoadedError) { + log( + 'Error loading emoji %s for locale %s, loading locale and retrying.', + state.code, + locale, + ); + const { importEmojiData } = await import('./loader'); + await importEmojiData(locale); // Use this from the loader file as it can be awaited. + return loadEmojiDataToState(state, locale, true); } - localeCacheMap.set(EMOJI_TYPE_CUSTOM, cache); + + console.warn('Error loading emoji data, not retrying:', state, locale, err); + return null; } } -function shouldRenderImage(token: EmojiToken, mode: EmojiMode): boolean { - if (token.type === EMOJI_TYPE_UNICODE) { +export function isStateLoaded(state: EmojiState): state is EmojiLoadedState { + return !!state.data; +} + +/** + * Determines if the given token should be rendered as an image based on the emoji mode. + * @param state Emoji state to parse. + * @param mode Rendering mode. + * @returns Whether to render as an image. + */ +export function shouldRenderImage(state: EmojiState, mode: EmojiMode): boolean { + if (state.type === EMOJI_TYPE_UNICODE) { // If the mode is native or native with flags for non-flag emoji // we can just append the text node directly. if ( mode === EMOJI_MODE_NATIVE || (mode === EMOJI_MODE_NATIVE_WITH_FLAGS && - !stringHasUnicodeFlags(token.code)) + !stringHasUnicodeFlags(state.code)) ) { return false; } @@ -347,61 +190,3 @@ function shouldRenderImage(token: EmojiToken, mode: EmojiMode): boolean { return true; } - -function stateToImage(state: EmojiLoadedState, appState: EmojiAppState) { - const image = document.createElement('img'); - image.draggable = false; - image.classList.add('emojione'); - - if (state.type === EMOJI_TYPE_UNICODE) { - const emojiInfo = twemojiHasBorder(unicodeToTwemojiHex(state.data.hexcode)); - let fileName = emojiInfo.hexCode; - if ( - (appState.darkTheme && emojiInfo.hasDarkBorder) || - (!appState.darkTheme && emojiInfo.hasLightBorder) - ) { - fileName = `${emojiInfo.hexCode}_border`; - } - - image.alt = state.data.unicode; - image.title = state.data.label; - image.src = `${assetHost}/emoji/${fileName}.svg`; - } else { - // Custom emoji - const shortCode = `:${state.data.shortcode}:`; - image.classList.add('custom-emoji'); - image.alt = shortCode; - image.title = shortCode; - image.src = autoPlayGif ? state.data.url : state.data.static_url; - image.dataset.original = state.data.url; - image.dataset.static = state.data.static_url; - } - - return image; -} - -function renderedToHTML(renderedArray: EmojifiedTextArray): DocumentFragment; -function renderedToHTML( - renderedArray: EmojifiedTextArray, - parent: ParentType, -): ParentType; -function renderedToHTML( - renderedArray: EmojifiedTextArray, - parent: ParentNode | null = null, -) { - const fragment = parent ?? document.createDocumentFragment(); - for (const fragmentItem of renderedArray) { - if (typeof fragmentItem === 'string') { - fragment.appendChild(document.createTextNode(fragmentItem)); - } else if (fragmentItem instanceof HTMLImageElement) { - fragment.appendChild(fragmentItem); - } - } - return fragment; -} - -// Testing helpers -export const testCacheClear = () => { - cacheClear(); - localeCacheMap.clear(); -}; diff --git a/app/javascript/mastodon/features/emoji/types.ts b/app/javascript/mastodon/features/emoji/types.ts index 85bbe6d1a56a8f..03002dda6453a4 100644 --- a/app/javascript/mastodon/features/emoji/types.ts +++ b/app/javascript/mastodon/features/emoji/types.ts @@ -4,13 +4,13 @@ import type { FlatCompactEmoji, Locale } from 'emojibase'; import type { ApiCustomEmojiJSON } from '@/mastodon/api_types/custom_emoji'; import type { CustomEmoji } from '@/mastodon/models/custom_emoji'; -import type { LimitedCache } from '@/mastodon/utils/cache'; +import type { RequiredExcept } from '@/mastodon/utils/types'; import type { + EMOJI_DB_NAME_SHORTCODES, EMOJI_MODE_NATIVE, EMOJI_MODE_NATIVE_WITH_FLAGS, EMOJI_MODE_TWEMOJI, - EMOJI_STATE_MISSING, EMOJI_TYPE_CUSTOM, EMOJI_TYPE_UNICODE, } from './constants'; @@ -21,6 +21,11 @@ export type EmojiMode = | typeof EMOJI_MODE_TWEMOJI; export type LocaleOrCustom = Locale | typeof EMOJI_TYPE_CUSTOM; +export type LocaleWithShortcodes = `${Locale}-shortcodes`; +export type EtagTypes = + | LocaleOrCustom + | typeof EMOJI_DB_NAME_SHORTCODES + | LocaleWithShortcodes; export interface EmojiAppState { locales: Locale[]; @@ -29,48 +34,39 @@ export interface EmojiAppState { darkTheme: boolean; } -export interface UnicodeEmojiToken { - type: typeof EMOJI_TYPE_UNICODE; - code: string; -} -export interface CustomEmojiToken { - type: typeof EMOJI_TYPE_CUSTOM; - code: string; -} -export type EmojiToken = UnicodeEmojiToken | CustomEmojiToken; - export type CustomEmojiData = ApiCustomEmojiJSON; export type UnicodeEmojiData = FlatCompactEmoji; export type AnyEmojiData = CustomEmojiData | UnicodeEmojiData; -export type EmojiStateMissing = typeof EMOJI_STATE_MISSING; +type CustomEmojiRenderFields = Pick< + CustomEmojiData, + 'shortcode' | 'static_url' | 'url' +>; + export interface EmojiStateUnicode { type: typeof EMOJI_TYPE_UNICODE; - data: UnicodeEmojiData; + code: string; + data?: UnicodeEmojiData; + shortcode?: string; } export interface EmojiStateCustom { type: typeof EMOJI_TYPE_CUSTOM; - data: CustomEmojiRenderFields; + code: string; + data?: CustomEmojiRenderFields; } -export type EmojiState = - | EmojiStateMissing - | EmojiStateUnicode - | EmojiStateCustom; -export type EmojiLoadedState = EmojiStateUnicode | EmojiStateCustom; +export type EmojiState = EmojiStateUnicode | EmojiStateCustom; -export type EmojiStateMap = LimitedCache; +export type EmojiLoadedState = + | RequiredExcept + | Required; export type CustomEmojiMapArg = | ExtraCustomEmojiMap - | ImmutableList; -export type CustomEmojiRenderFields = Pick< - CustomEmojiData, - 'shortcode' | 'static_url' | 'url' ->; -export type ExtraCustomEmojiMap = Record; + | ImmutableList + | CustomEmoji[] + | ApiCustomEmojiJSON[]; -export interface TwemojiBorderInfo { - hexCode: string; - hasLightBorder: boolean; - hasDarkBorder: boolean; -} +export type ExtraCustomEmojiMap = Record< + string, + Pick +>; diff --git a/app/javascript/mastodon/features/emoji/unicode_to_filename.js b/app/javascript/mastodon/features/emoji/unicode_to_filename.js deleted file mode 100644 index cfe5539c7b9887..00000000000000 --- a/app/javascript/mastodon/features/emoji/unicode_to_filename.js +++ /dev/null @@ -1,26 +0,0 @@ -// taken from: -// https://github.com/twitter/twemoji/blob/47732c7/twemoji-generator.js#L848-L866 -export const unicodeToFilename = (str) => { - let result = ''; - let charCode = 0; - let p = 0; - let i = 0; - while (i < str.length) { - charCode = str.charCodeAt(i++); - if (p) { - if (result.length > 0) { - result += '-'; - } - result += (0x10000 + ((p - 0xD800) << 10) + (charCode - 0xDC00)).toString(16); - p = 0; - } else if (0xD800 <= charCode && charCode <= 0xDBFF) { - p = charCode; - } else { - if (result.length > 0) { - result += '-'; - } - result += charCode.toString(16); - } - } - return result; -}; diff --git a/app/javascript/mastodon/features/emoji/unicode_to_unified_name.js b/app/javascript/mastodon/features/emoji/unicode_to_unified_name.js deleted file mode 100644 index 15f60aa7c30e68..00000000000000 --- a/app/javascript/mastodon/features/emoji/unicode_to_unified_name.js +++ /dev/null @@ -1,21 +0,0 @@ -function padLeft(str, num) { - while (str.length < num) { - str = '0' + str; - } - - return str; -} - -export const unicodeToUnifiedName = (str) => { - let output = ''; - - for (let i = 0; i < str.length; i += 2) { - if (i > 0) { - output += '-'; - } - - output += padLeft(str.codePointAt(i).toString(16).toUpperCase(), 4); - } - - return output; -}; diff --git a/app/javascript/mastodon/features/emoji/unicode_utils.ts b/app/javascript/mastodon/features/emoji/unicode_utils.ts new file mode 100644 index 00000000000000..04175ee9f903ec --- /dev/null +++ b/app/javascript/mastodon/features/emoji/unicode_utils.ts @@ -0,0 +1,43 @@ +// taken from: +// https://github.com/twitter/twemoji/blob/47732c7/twemoji-generator.js#L848-L866 +export function unicodeToFilename(str: string) { + let result = ''; + let charCode = 0; + let p = 0; + let i = 0; + while (i < str.length) { + charCode = str.charCodeAt(i++); + if (p) { + if (result.length > 0) { + result += '-'; + } + result += (0x10000 + ((p - 0xd800) << 10) + (charCode - 0xdc00)).toString( + 16, + ); + p = 0; + } else if (0xd800 <= charCode && charCode <= 0xdbff) { + p = charCode; + } else { + if (result.length > 0) { + result += '-'; + } + result += charCode.toString(16); + } + } + return result; +} + +export function unicodeToUnifiedName(str: string) { + let output = ''; + + for (let i = 0; i < str.length; i += 2) { + if (i > 0) { + output += '-'; + } + + output += + str.codePointAt(i)?.toString(16).toUpperCase().padStart(4, '0') ?? ''; + } + + return output; +} diff --git a/app/javascript/mastodon/features/emoji/utils.test.ts b/app/javascript/mastodon/features/emoji/utils.test.ts index b9062294c47b14..3844c814a10166 100644 --- a/app/javascript/mastodon/features/emoji/utils.test.ts +++ b/app/javascript/mastodon/features/emoji/utils.test.ts @@ -1,35 +1,31 @@ -import { - stringHasAnyEmoji, - stringHasCustomEmoji, - stringHasUnicodeEmoji, - stringHasUnicodeFlags, -} from './utils'; +import { isCustomEmoji, isUnicodeEmoji, stringHasUnicodeFlags } from './utils'; -describe('stringHasUnicodeEmoji', () => { +describe('isUnicodeEmoji', () => { test.concurrent.for([ - ['only text', false], - ['text with non-emoji symbols ™©', false], - ['text with emoji 😀', true], - ['multiple emojis 😀😃😄', true], - ['emoji with skin tone 👍🏽', true], - ['emoji with ZWJ 👩‍❤️‍👨', true], - ['emoji with variation selector ✊️', true], - ['emoji with keycap 1️⃣', true], - ['emoji with flags 🇺🇸', true], - ['emoji with regional indicators 🇦🇺', true], - ['emoji with gender 👩‍⚕️', true], - ['emoji with family 👨‍👩‍👧‍👦', true], - ['emoji with zero width joiner 👩‍🔬', true], - ['emoji with non-BMP codepoint 🧑‍🚀', true], - ['emoji with combining marks 👨‍👩‍👧‍👦', true], - ['emoji with enclosing keycap #️⃣', true], - ['emoji with no visible glyph \u200D', false], - ] as const)( - 'stringHasUnicodeEmoji has emojis in "%s": %o', - ([text, expected], { expect }) => { - expect(stringHasUnicodeEmoji(text)).toBe(expected); - }, - ); + ['😊', true], + ['🇿🇼', true], + ['🏴‍☠️', true], + ['🏳️‍🌈', true], + ['foo', false], + [':smile:', false], + ['😊foo', false], + ] as const)('isUnicodeEmoji("%s") is %o', ([input, expected], { expect }) => { + expect(isUnicodeEmoji(input)).toBe(expected); + }); +}); + +describe('isCustomEmoji', () => { + test.concurrent.for([ + [':smile:', true], + [':smile_123:', true], + [':SMILE:', true], + ['😊', false], + ['foo', false], + [':smile', false], + ['smile:', false], + ] as const)('isCustomEmoji("%s") is %o', ([input, expected], { expect }) => { + expect(isCustomEmoji(input)).toBe(expected); + }); }); describe('stringHasUnicodeFlags', () => { @@ -51,27 +47,3 @@ describe('stringHasUnicodeFlags', () => { }, ); }); - -describe('stringHasCustomEmoji', () => { - test('string with custom emoji returns true', () => { - expect(stringHasCustomEmoji(':custom: :test:')).toBeTruthy(); - }); - test('string without custom emoji returns false', () => { - expect(stringHasCustomEmoji('🏳️‍🌈 :🏳️‍🌈: text ™')).toBeFalsy(); - }); -}); - -describe('stringHasAnyEmoji', () => { - test('string without any emoji or characters', () => { - expect(stringHasAnyEmoji('normal text. 12356?!')).toBeFalsy(); - }); - test('string with non-emoji characters', () => { - expect(stringHasAnyEmoji('™©')).toBeFalsy(); - }); - test('has unicode emoji', () => { - expect(stringHasAnyEmoji('🏳️‍🌈🔥🇸🇹 👩‍🔬')).toBeTruthy(); - }); - test('has custom emoji', () => { - expect(stringHasAnyEmoji(':test: :custom:')).toBeTruthy(); - }); -}); diff --git a/app/javascript/mastodon/features/emoji/utils.ts b/app/javascript/mastodon/features/emoji/utils.ts index ce3591992967d8..c567afc2cccd5c 100644 --- a/app/javascript/mastodon/features/emoji/utils.ts +++ b/app/javascript/mastodon/features/emoji/utils.ts @@ -6,8 +6,11 @@ export function emojiLogger(segment: string) { return debug(`emojis:${segment}`); } -export function stringHasUnicodeEmoji(input: string): boolean { - return new RegExp(EMOJI_REGEX, supportedFlags()).test(input); +export function isUnicodeEmoji(input: string): boolean { + return ( + input.length > 0 && + new RegExp(`^(${EMOJI_REGEX})+$`, supportedFlags()).test(input) + ); } export function stringHasUnicodeFlags(input: string): boolean { @@ -27,12 +30,11 @@ export function stringHasUnicodeFlags(input: string): boolean { // Constant as this is supported by all browsers. const CUSTOM_EMOJI_REGEX = /:([a-z0-9_]+):/i; -export function stringHasCustomEmoji(input: string) { - return CUSTOM_EMOJI_REGEX.test(input); -} +// Use the polyfill regex or the Unicode property escapes if supported. +const EMOJI_REGEX = emojiRegexPolyfill?.source ?? '\\p{RGI_Emoji}'; -export function stringHasAnyEmoji(input: string) { - return stringHasUnicodeEmoji(input) || stringHasCustomEmoji(input); +export function isCustomEmoji(input: string): boolean { + return new RegExp(`^${CUSTOM_EMOJI_REGEX.source}$`, 'i').test(input); } export function anyEmojiRegex() { @@ -52,5 +54,3 @@ function supportedFlags(flags = '') { } return flags; } - -const EMOJI_REGEX = emojiRegexPolyfill?.source ?? '\\p{RGI_Emoji}'; diff --git a/app/javascript/mastodon/features/emoji/worker.ts b/app/javascript/mastodon/features/emoji/worker.ts index 6fb7d36e93624b..5602577dbe9ace 100644 --- a/app/javascript/mastodon/features/emoji/worker.ts +++ b/app/javascript/mastodon/features/emoji/worker.ts @@ -1,18 +1,31 @@ -import { importEmojiData, importCustomEmojiData } from './loader'; +import { EMOJI_DB_NAME_SHORTCODES, EMOJI_TYPE_CUSTOM } from './constants'; +import { + importCustomEmojiData, + importEmojiData, + importLegacyShortcodes, +} from './loader'; addEventListener('message', handleMessage); self.postMessage('ready'); // After the worker is ready, notify the main thread -function handleMessage(event: MessageEvent) { - const { data: locale } = event; +function handleMessage(event: MessageEvent<{ locale: string }>) { + const { + data: { locale }, + } = event; void loadData(locale); } async function loadData(locale: string) { - if (locale !== 'custom') { - await importEmojiData(locale); + let importCount: number | undefined; + if (locale === EMOJI_TYPE_CUSTOM) { + importCount = (await importCustomEmojiData())?.length; + } else if (locale === EMOJI_DB_NAME_SHORTCODES) { + importCount = (await importLegacyShortcodes())?.length; } else { - await importCustomEmojiData(); + importCount = (await importEmojiData(locale))?.length; + } + + if (importCount) { + self.postMessage(`loaded ${importCount} emojis into ${locale}`); } - self.postMessage(`loaded ${locale}`); } diff --git a/app/javascript/mastodon/features/explore/components/author_link.jsx b/app/javascript/mastodon/features/explore/components/author_link.jsx deleted file mode 100644 index cf92ebc78b36a0..00000000000000 --- a/app/javascript/mastodon/features/explore/components/author_link.jsx +++ /dev/null @@ -1,23 +0,0 @@ -import PropTypes from 'prop-types'; - -import { Avatar } from 'mastodon/components/avatar'; -import { useAppSelector } from 'mastodon/store'; -import { LinkedDisplayName } from '@/mastodon/components/display_name'; - -export const AuthorLink = ({ accountId }) => { - const account = useAppSelector(state => state.getIn(['accounts', accountId])); - - if (!account) { - return null; - } - - return ( - - - - ); -}; - -AuthorLink.propTypes = { - accountId: PropTypes.string.isRequired, -}; diff --git a/app/javascript/mastodon/features/explore/components/author_link.tsx b/app/javascript/mastodon/features/explore/components/author_link.tsx new file mode 100644 index 00000000000000..a4667693a59ad5 --- /dev/null +++ b/app/javascript/mastodon/features/explore/components/author_link.tsx @@ -0,0 +1,22 @@ +import type { FC } from 'react'; + +import { LinkedDisplayName } from '@/mastodon/components/display_name'; +import { Avatar } from 'mastodon/components/avatar'; +import { useAppSelector } from 'mastodon/store'; + +export const AuthorLink: FC<{ accountId: string }> = ({ accountId }) => { + const account = useAppSelector((state) => state.accounts.get(accountId)); + + if (!account) { + return null; + } + + return ( + + + + ); +}; diff --git a/app/javascript/mastodon/features/favourites/index.jsx b/app/javascript/mastodon/features/favourites/index.jsx index c5c6abe0818ada..245954ef806081 100644 --- a/app/javascript/mastodon/features/favourites/index.jsx +++ b/app/javascript/mastodon/features/favourites/index.jsx @@ -41,7 +41,7 @@ class Favourites extends ImmutablePureComponent { intl: PropTypes.object.isRequired, }; - UNSAFE_componentWillMount () { + componentDidMount () { if (!this.props.accountIds) { this.props.dispatch(fetchFavourites(this.props.params.statusId)); } diff --git a/app/javascript/mastodon/features/firehose/index.jsx b/app/javascript/mastodon/features/firehose/index.jsx index ff80aac2200358..ca3dd7ce38f5c5 100644 --- a/app/javascript/mastodon/features/firehose/index.jsx +++ b/app/javascript/mastodon/features/firehose/index.jsx @@ -13,7 +13,8 @@ import { changeSetting } from 'mastodon/actions/settings'; import { connectPublicStream, connectCommunityStream } from 'mastodon/actions/streaming'; import { expandPublicTimeline, expandCommunityTimeline } from 'mastodon/actions/timelines'; import { DismissableBanner } from 'mastodon/components/dismissable_banner'; -import { domain } from 'mastodon/initial_state'; +import { localLiveFeedAccess, remoteLiveFeedAccess, domain } from 'mastodon/initial_state'; +import { canViewFeed } from 'mastodon/permissions'; import { useAppDispatch, useAppSelector } from 'mastodon/store'; import Column from '../../components/column'; @@ -23,6 +24,14 @@ import StatusListContainer from '../ui/containers/status_list_container'; const messages = defineMessages({ title: { id: 'column.firehose', defaultMessage: 'Live feeds' }, + title_local: { + id: 'column.firehose_local', + defaultMessage: 'Live feed for this server', + }, + title_singular: { + id: 'column.firehose_singular', + defaultMessage: 'Live feed', + }, }); const ColumnSettings = () => { @@ -52,7 +61,7 @@ const ColumnSettings = () => { const Firehose = ({ feedType, multiColumn }) => { const dispatch = useAppDispatch(); const intl = useIntl(); - const { signedIn } = useIdentity(); + const { signedIn, permissions } = useIdentity(); const columnRef = useRef(null); const onlyMedia = useAppSelector((state) => state.getIn(['settings', 'firehose', 'onlyMedia'], false)); @@ -151,13 +160,32 @@ const Firehose = ({ feedType, multiColumn }) => { /> ); + const canViewSelectedFeed = canViewFeed(signedIn, permissions, feedType === 'community' ? localLiveFeedAccess : remoteLiveFeedAccess); + + const disabledTimelineMessage = ( + + ); + + let title; + + if (canViewFeed(signedIn, permissions, localLiveFeedAccess) && canViewFeed(signedIn, permissions, remoteLiveFeedAccess)) { + title = messages.title; + } else if (canViewFeed(signedIn, permissions, localLiveFeedAccess)) { + title = messages.title_local; + } else { + title = messages.title_singular; + } + return ( { -
- - - + {(canViewFeed(signedIn, permissions, localLiveFeedAccess) && canViewFeed(signedIn, permissions, remoteLiveFeedAccess)) && ( +
+ + + - - - + + + - - - -
+ + + +
+ )} { onLoadMore={handleLoadMore} trackScroll scrollKey='firehose' - emptyMessage={emptyMessage} + emptyMessage={canViewSelectedFeed ? emptyMessage : disabledTimelineMessage} bindToDocument={!multiColumn} /> diff --git a/app/javascript/mastodon/features/follow_requests/components/account_authorize.jsx b/app/javascript/mastodon/features/follow_requests/components/account_authorize.jsx index dd308c87cb1fc3..e865b606fe38e8 100644 --- a/app/javascript/mastodon/features/follow_requests/components/account_authorize.jsx +++ b/app/javascript/mastodon/features/follow_requests/components/account_authorize.jsx @@ -10,9 +10,10 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import CheckIcon from '@/material-icons/400-24px/check.svg?react'; import CloseIcon from '@/material-icons/400-24px/close.svg?react'; -import { Avatar } from '../../../components/avatar'; -import { DisplayName } from '../../../components/display_name'; -import { IconButton } from '../../../components/icon_button'; +import { Avatar } from '@/mastodon/components/avatar'; +import { DisplayName } from '@/mastodon/components/display_name'; +import { IconButton } from '@/mastodon/components/icon_button'; +import { EmojiHTML } from '@/mastodon/components/emoji/html'; const messages = defineMessages({ authorize: { id: 'follow_request.authorize', defaultMessage: 'Authorize' }, @@ -30,7 +31,6 @@ class AccountAuthorize extends ImmutablePureComponent { render () { const { intl, account, onAuthorize, onReject } = this.props; - const content = { __html: account.get('note_emojified') }; return (
@@ -40,7 +40,11 @@ class AccountAuthorize extends ImmutablePureComponent { -
+
diff --git a/app/javascript/mastodon/features/follow_requests/index.jsx b/app/javascript/mastodon/features/follow_requests/index.jsx index 7d651f2ca69c24..91648412b55799 100644 --- a/app/javascript/mastodon/features/follow_requests/index.jsx +++ b/app/javascript/mastodon/features/follow_requests/index.jsx @@ -45,7 +45,7 @@ class FollowRequests extends ImmutablePureComponent { multiColumn: PropTypes.bool, }; - UNSAFE_componentWillMount () { + componentDidMount () { this.props.dispatch(fetchFollowRequests()); } diff --git a/app/javascript/mastodon/features/getting_started/components/announcements.jsx b/app/javascript/mastodon/features/getting_started/components/announcements.jsx deleted file mode 100644 index 96bd995d2b10e7..00000000000000 --- a/app/javascript/mastodon/features/getting_started/components/announcements.jsx +++ /dev/null @@ -1,458 +0,0 @@ -import PropTypes from 'prop-types'; -import { PureComponent, useCallback, useMemo } from 'react'; - -import { defineMessages, injectIntl, FormattedMessage, FormattedDate } from 'react-intl'; - -import classNames from 'classnames'; -import { withRouter } from 'react-router-dom'; - -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; - -import { animated, useTransition } from '@react-spring/web'; -import ReactSwipeableViews from 'react-swipeable-views'; - -import elephantUIPlane from '@/images/elephant_ui_plane.svg'; -import AddIcon from '@/material-icons/400-24px/add.svg?react'; -import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react'; -import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'; -import { AnimatedNumber } from 'mastodon/components/animated_number'; -import { Icon } from 'mastodon/components/icon'; -import { IconButton } from 'mastodon/components/icon_button'; -import EmojiPickerDropdown from 'mastodon/features/compose/containers/emoji_picker_dropdown_container'; -import { unicodeMapping } from 'mastodon/features/emoji/emoji_unicode_mapping_light'; -import { autoPlayGif, reduceMotion, disableSwiping, mascot } from 'mastodon/initial_state'; -import { assetHost } from 'mastodon/utils/config'; -import { WithRouterPropTypes } from 'mastodon/utils/react_router'; - -const messages = defineMessages({ - close: { id: 'lightbox.close', defaultMessage: 'Close' }, - previous: { id: 'lightbox.previous', defaultMessage: 'Previous' }, - next: { id: 'lightbox.next', defaultMessage: 'Next' }, -}); - -class ContentWithRouter extends ImmutablePureComponent { - static propTypes = { - announcement: ImmutablePropTypes.map.isRequired, - ...WithRouterPropTypes, - }; - - setRef = c => { - this.node = c; - }; - - componentDidMount () { - this._updateLinks(); - } - - componentDidUpdate () { - this._updateLinks(); - } - - _updateLinks () { - const node = this.node; - - if (!node) { - return; - } - - const links = node.querySelectorAll('a'); - - for (var i = 0; i < links.length; ++i) { - let link = links[i]; - - if (link.classList.contains('status-link')) { - continue; - } - - link.classList.add('status-link'); - - let mention = this.props.announcement.get('mentions').find(item => link.href === item.get('url')); - - if (mention) { - link.addEventListener('click', this.onMentionClick.bind(this, mention), false); - link.setAttribute('title', mention.get('acct')); - } else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) { - link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false); - } else { - let status = this.props.announcement.get('statuses').find(item => link.href === item.get('url')); - if (status) { - link.addEventListener('click', this.onStatusClick.bind(this, status), false); - } - link.setAttribute('title', link.href); - link.classList.add('unhandled-link'); - } - - link.setAttribute('target', '_blank'); - link.setAttribute('rel', 'noopener'); - } - } - - onMentionClick = (mention, e) => { - if (this.props.history && e.button === 0 && !(e.ctrlKey || e.metaKey)) { - e.preventDefault(); - this.props.history.push(`/@${mention.get('acct')}`); - } - }; - - onHashtagClick = (hashtag, e) => { - hashtag = hashtag.replace(/^#/, ''); - - if (this.props.history&& e.button === 0 && !(e.ctrlKey || e.metaKey)) { - e.preventDefault(); - this.props.history.push(`/tags/${hashtag}`); - } - }; - - onStatusClick = (status, e) => { - if (this.props.history && e.button === 0 && !(e.ctrlKey || e.metaKey)) { - e.preventDefault(); - this.props.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`); - } - }; - - render () { - const { announcement } = this.props; - - return ( -
- ); - } - -} - -const Content = withRouter(ContentWithRouter); - -class Emoji extends PureComponent { - - static propTypes = { - emoji: PropTypes.string.isRequired, - emojiMap: ImmutablePropTypes.map.isRequired, - hovered: PropTypes.bool.isRequired, - }; - - render () { - const { emoji, emojiMap, hovered } = this.props; - - if (unicodeMapping[emoji]) { - const { filename, shortCode } = unicodeMapping[this.props.emoji]; - const title = shortCode ? `:${shortCode}:` : ''; - - return ( - {emoji} - ); - } else if (emojiMap.get(emoji)) { - const filename = (autoPlayGif || hovered) ? emojiMap.getIn([emoji, 'url']) : emojiMap.getIn([emoji, 'static_url']); - const shortCode = `:${emoji}:`; - - return ( - {shortCode} - ); - } else { - return null; - } - } - -} - -class Reaction extends ImmutablePureComponent { - - static propTypes = { - announcementId: PropTypes.string.isRequired, - reaction: ImmutablePropTypes.map.isRequired, - addReaction: PropTypes.func.isRequired, - removeReaction: PropTypes.func.isRequired, - emojiMap: ImmutablePropTypes.map.isRequired, - style: PropTypes.object, - }; - - state = { - hovered: false, - }; - - handleClick = () => { - const { reaction, announcementId, addReaction, removeReaction } = this.props; - - if (reaction.get('me')) { - removeReaction(announcementId, reaction.get('name')); - } else { - addReaction(announcementId, reaction.get('name')); - } - }; - - handleMouseEnter = () => this.setState({ hovered: true }); - - handleMouseLeave = () => this.setState({ hovered: false }); - - render () { - const { reaction } = this.props; - - let shortCode = reaction.get('name'); - - if (unicodeMapping[shortCode]) { - shortCode = unicodeMapping[shortCode].shortCode; - } - - return ( - - - - - - - - - ); - } - -} - -const ReactionsBar = ({ - announcementId, - reactions, - emojiMap, - addReaction, - removeReaction, -}) => { - const visibleReactions = useMemo(() => reactions.filter(x => x.get('count') > 0).toArray(), [reactions]); - - const handleEmojiPick = useCallback((emoji) => { - addReaction(announcementId, emoji.native.replaceAll(/:/g, '')); - }, [addReaction, announcementId]); - - const transitions = useTransition(visibleReactions, { - from: { - scale: 0, - }, - enter: { - scale: 1, - }, - leave: { - scale: 0, - }, - keys: visibleReactions.map(x => x.get('name')), - }); - - return ( -
- {transitions(({ scale }, reaction) => ( - `scale(${s})`) }} - addReaction={addReaction} - removeReaction={removeReaction} - announcementId={announcementId} - emojiMap={emojiMap} - /> - ))} - - {visibleReactions.length < 8 && ( - } - /> - )} -
- ); -}; -ReactionsBar.propTypes = { - announcementId: PropTypes.string.isRequired, - reactions: ImmutablePropTypes.list.isRequired, - addReaction: PropTypes.func.isRequired, - removeReaction: PropTypes.func.isRequired, - emojiMap: ImmutablePropTypes.map.isRequired, -}; - -class Announcement extends ImmutablePureComponent { - - static propTypes = { - announcement: ImmutablePropTypes.map.isRequired, - emojiMap: ImmutablePropTypes.map.isRequired, - addReaction: PropTypes.func.isRequired, - removeReaction: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - selected: PropTypes.bool, - }; - - state = { - unread: !this.props.announcement.get('read'), - }; - - componentDidUpdate () { - const { selected, announcement } = this.props; - if (!selected && this.state.unread !== !announcement.get('read')) { - this.setState({ unread: !announcement.get('read') }); - } - } - - render () { - const { announcement } = this.props; - const { unread } = this.state; - const startsAt = announcement.get('starts_at') && new Date(announcement.get('starts_at')); - const endsAt = announcement.get('ends_at') && new Date(announcement.get('ends_at')); - const now = new Date(); - const hasTimeRange = startsAt && endsAt; - const skipTime = announcement.get('all_day'); - - let timestamp = null; - if (hasTimeRange) { - const skipYear = startsAt.getFullYear() === endsAt.getFullYear() && endsAt.getFullYear() === now.getFullYear(); - const skipEndDate = startsAt.getDate() === endsAt.getDate() && startsAt.getMonth() === endsAt.getMonth() && startsAt.getFullYear() === endsAt.getFullYear(); - timestamp = ( - <> - - - - ); - } else { - const publishedAt = new Date(announcement.get('published_at')); - timestamp = ( - - ); - } - - return ( -
- - - · {timestamp} - - - - - - - {unread && } -
- ); - } - -} - -class Announcements extends ImmutablePureComponent { - - static propTypes = { - announcements: ImmutablePropTypes.list, - emojiMap: ImmutablePropTypes.map.isRequired, - dismissAnnouncement: PropTypes.func.isRequired, - addReaction: PropTypes.func.isRequired, - removeReaction: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - }; - - state = { - index: 0, - }; - - static getDerivedStateFromProps(props, state) { - if (props.announcements.size > 0 && state.index >= props.announcements.size) { - return { index: props.announcements.size - 1 }; - } else { - return null; - } - } - - componentDidMount () { - this._markAnnouncementAsRead(); - } - - componentDidUpdate () { - this._markAnnouncementAsRead(); - } - - _markAnnouncementAsRead () { - const { dismissAnnouncement, announcements } = this.props; - const { index } = this.state; - const announcement = announcements.get(announcements.size - 1 - index); - if (!announcement.get('read')) dismissAnnouncement(announcement.get('id')); - } - - handleChangeIndex = index => { - this.setState({ index: index % this.props.announcements.size }); - }; - - handleNextClick = () => { - this.setState({ index: (this.state.index + 1) % this.props.announcements.size }); - }; - - handlePrevClick = () => { - this.setState({ index: (this.props.announcements.size + this.state.index - 1) % this.props.announcements.size }); - }; - - render () { - const { announcements, intl } = this.props; - const { index } = this.state; - - if (announcements.isEmpty()) { - return null; - } - - return ( -
- - -
- - {announcements.map((announcement, idx) => ( - - )).reverse()} - - - {announcements.size > 1 && ( -
- - {index + 1} / {announcements.size} - -
- )} -
-
- ); - } - -} - -export default injectIntl(Announcements); diff --git a/app/javascript/mastodon/features/getting_started/containers/announcements_container.js b/app/javascript/mastodon/features/getting_started/containers/announcements_container.js deleted file mode 100644 index 3bb1b8e8d182c7..00000000000000 --- a/app/javascript/mastodon/features/getting_started/containers/announcements_container.js +++ /dev/null @@ -1,23 +0,0 @@ -import { createSelector } from '@reduxjs/toolkit'; -import { Map as ImmutableMap } from 'immutable'; -import { connect } from 'react-redux'; - - -import { addReaction, removeReaction, dismissAnnouncement } from 'mastodon/actions/announcements'; - -import Announcements from '../components/announcements'; - -const customEmojiMap = createSelector([state => state.get('custom_emojis')], items => items.reduce((map, emoji) => map.set(emoji.get('shortcode'), emoji), ImmutableMap())); - -const mapStateToProps = state => ({ - announcements: state.getIn(['announcements', 'items']), - emojiMap: customEmojiMap(state), -}); - -const mapDispatchToProps = dispatch => ({ - dismissAnnouncement: id => dispatch(dismissAnnouncement(id)), - addReaction: (id, name) => dispatch(addReaction(id, name)), - removeReaction: (id, name) => dispatch(removeReaction(id, name)), -}); - -export default connect(mapStateToProps, mapDispatchToProps)(Announcements); diff --git a/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_header.tsx b/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_header.tsx index c11874e7d4201b..fafe186cb41b92 100644 --- a/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_header.tsx +++ b/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_header.tsx @@ -197,13 +197,16 @@ export const HashtagHeader: React.FC<{ /> )} -
diff --git a/app/javascript/mastodon/features/hashtag_timeline/index.jsx b/app/javascript/mastodon/features/hashtag_timeline/index.jsx index ab3c32e6a7bf63..36049d4331da51 100644 --- a/app/javascript/mastodon/features/hashtag_timeline/index.jsx +++ b/app/javascript/mastodon/features/hashtag_timeline/index.jsx @@ -16,15 +16,23 @@ import { expandHashtagTimeline, clearTimeline } from 'mastodon/actions/timelines import Column from 'mastodon/components/column'; import ColumnHeader from 'mastodon/components/column_header'; import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; +import { remoteTopicFeedAccess, me, localTopicFeedAccess } from 'mastodon/initial_state'; import StatusListContainer from '../ui/containers/status_list_container'; import { HashtagHeader } from './components/hashtag_header'; import ColumnSettingsContainer from './containers/column_settings_container'; -const mapStateToProps = (state, props) => ({ - hasUnread: state.getIn(['timelines', `hashtag:${props.params.id}${props.params.local ? ':local' : ''}`, 'unread']) > 0, -}); +const mapStateToProps = (state, props) => { + const local = props.params.local || (!me && remoteTopicFeedAccess !== 'public'); + const hasFeedAccess = !!me || localTopicFeedAccess === 'public'; + + return ({ + local, + hasFeedAccess, + hasUnread: state.getIn(['timelines', `hashtag:${props.params.id}${local ? ':local' : ''}`, 'unread']) > 0, + }); +}; class HashtagTimeline extends PureComponent { disconnects = []; @@ -113,19 +121,21 @@ class HashtagTimeline extends PureComponent { } _unload () { - const { dispatch } = this.props; - const { id, local } = this.props.params; + const { dispatch, local } = this.props; + const { id } = this.props.params; this._unsubscribe(); dispatch(clearTimeline(`hashtag:${id}${local ? ':local' : ''}`)); } _load() { - const { dispatch } = this.props; - const { id, tags, local } = this.props.params; + const { dispatch, local, hasFeedAccess } = this.props; + const { id, tags } = this.props.params; - this._subscribe(dispatch, id, tags, local); - dispatch(expandHashtagTimeline(id, { tags, local })); + if (hasFeedAccess) { + this._subscribe(dispatch, id, tags, local); + dispatch(expandHashtagTimeline(id, { tags, local })); + } } componentDidMount () { @@ -133,10 +143,10 @@ class HashtagTimeline extends PureComponent { } componentDidUpdate (prevProps) { - const { params } = this.props; - const { id, tags, local } = prevProps.params; + const { params, local } = this.props; + const { id, tags } = prevProps.params; - if (id !== params.id || !isEqual(tags, params.tags) || !isEqual(local, params.local)) { + if (id !== params.id || !isEqual(tags, params.tags) || !isEqual(local, prevProps.local)) { this._unload(); this._load(); } @@ -151,15 +161,15 @@ class HashtagTimeline extends PureComponent { }; handleLoadMore = maxId => { - const { dispatch, params } = this.props; - const { id, tags, local } = params; + const { dispatch, params, local } = this.props; + const { id, tags } = params; dispatch(expandHashtagTimeline(id, { maxId, tags, local })); }; render () { - const { hasUnread, columnId, multiColumn } = this.props; - const { id, local } = this.props.params; + const { hasUnread, columnId, multiColumn, local, hasFeedAccess } = this.props; + const { id } = this.props.params; const pinned = !!columnId; return ( @@ -186,7 +196,20 @@ class HashtagTimeline extends PureComponent { scrollKey={`hashtag_timeline-${columnId}`} timelineId={`hashtag:${id}${local ? ':local' : ''}`} onLoadMore={this.handleLoadMore} - emptyMessage={} + initialLoadingState={hasFeedAccess} + emptyMessage={ + hasFeedAccess ? ( + + ) : ( + + ) + } bindToDocument={!multiColumn} /> diff --git a/app/javascript/mastodon/features/home_timeline/components/announcements/announcement.tsx b/app/javascript/mastodon/features/home_timeline/components/announcements/announcement.tsx new file mode 100644 index 00000000000000..75be0ffe1b4910 --- /dev/null +++ b/app/javascript/mastodon/features/home_timeline/components/announcements/announcement.tsx @@ -0,0 +1,136 @@ +import type { FC } from 'react'; +import { useEffect, useState } from 'react'; + +import { FormattedDate, FormattedMessage } from 'react-intl'; + +import { dismissAnnouncement } from '@/mastodon/actions/announcements'; +import type { ApiAnnouncementJSON } from '@/mastodon/api_types/announcements'; +import { AnimateEmojiProvider } from '@/mastodon/components/emoji/context'; +import { EmojiHTML } from '@/mastodon/components/emoji/html'; +import { useAppDispatch } from '@/mastodon/store'; + +import { ReactionsBar } from './reactions'; + +export interface IAnnouncement extends ApiAnnouncementJSON { + contentHtml: string; +} + +interface AnnouncementProps { + announcement: IAnnouncement; + active?: boolean; +} + +export const Announcement: FC = ({ + announcement, + active, +}) => { + const { read, id } = announcement; + + // Dismiss announcement when it becomes active. + const dispatch = useAppDispatch(); + useEffect(() => { + if (active && !read) { + dispatch(dismissAnnouncement(id)); + } + }, [active, id, dispatch, read]); + + // But visually show the announcement as read only when it goes out of view. + const [isVisuallyRead, setIsVisuallyRead] = useState(read); + const [previousActive, setPreviousActive] = useState(active); + if (active !== previousActive) { + setPreviousActive(active); + + // This marks the announcement as read in the UI only after it + // went from active to inactive. + if (!active && isVisuallyRead !== read) { + setIsVisuallyRead(read); + } + } + + return ( + + + + + {' · '} + + + + + + + + + {!isVisuallyRead && } + + ); +}; + +const Timestamp: FC> = ({ + announcement, +}) => { + const startsAt = announcement.starts_at && new Date(announcement.starts_at); + const endsAt = announcement.ends_at && new Date(announcement.ends_at); + const now = new Date(); + const hasTimeRange = startsAt && endsAt; + const skipTime = announcement.all_day; + + if (hasTimeRange) { + const skipYear = + startsAt.getFullYear() === endsAt.getFullYear() && + endsAt.getFullYear() === now.getFullYear(); + const skipEndDate = + startsAt.getDate() === endsAt.getDate() && + startsAt.getMonth() === endsAt.getMonth() && + startsAt.getFullYear() === endsAt.getFullYear(); + return ( + <> + {' '} + -{' '} + + + ); + } + const publishedAt = new Date(announcement.published_at); + return ( + + ); +}; diff --git a/app/javascript/mastodon/features/home_timeline/components/announcements/index.tsx b/app/javascript/mastodon/features/home_timeline/components/announcements/index.tsx new file mode 100644 index 00000000000000..cb44e1d075c0c7 --- /dev/null +++ b/app/javascript/mastodon/features/home_timeline/components/announcements/index.tsx @@ -0,0 +1,64 @@ +import { useCallback } from 'react'; +import type { FC } from 'react'; + +import type { Map, List } from 'immutable'; + +import elephantUIPlane from '@/images/elephant_ui_plane.svg'; +import type { RenderSlideFn } from '@/mastodon/components/carousel'; +import { Carousel } from '@/mastodon/components/carousel'; +import { CustomEmojiProvider } from '@/mastodon/components/emoji/context'; +import { mascot } from '@/mastodon/initial_state'; +import { createAppSelector, useAppSelector } from '@/mastodon/store'; + +import type { IAnnouncement } from './announcement'; +import { Announcement } from './announcement'; + +const announcementSelector = createAppSelector( + [(state) => state.announcements as Map>>], + (announcements) => + ((announcements.get('items')?.toJS() as IAnnouncement[] | undefined) ?? []) + .map((announcement) => ({ announcement, id: announcement.id })) + .toReversed(), +); + +export const Announcements: FC = () => { + const announcements = useAppSelector(announcementSelector); + const emojis = useAppSelector((state) => state.custom_emojis); + + const renderSlide: RenderSlideFn<{ + id: string; + announcement: IAnnouncement; + }> = useCallback( + (item, active) => ( + + ), + [], + ); + + if (announcements.length === 0) { + return null; + } + + return ( +
+ + + + + +
+ ); +}; diff --git a/app/javascript/mastodon/features/home_timeline/components/announcements/reactions.tsx b/app/javascript/mastodon/features/home_timeline/components/announcements/reactions.tsx new file mode 100644 index 00000000000000..481e87f190bcf9 --- /dev/null +++ b/app/javascript/mastodon/features/home_timeline/components/announcements/reactions.tsx @@ -0,0 +1,108 @@ +import { useCallback, useMemo } from 'react'; +import type { FC, HTMLAttributes } from 'react'; + +import classNames from 'classnames'; + +import type { AnimatedProps } from '@react-spring/web'; +import { animated, useTransition } from '@react-spring/web'; + +import { addReaction, removeReaction } from '@/mastodon/actions/announcements'; +import type { ApiAnnouncementReactionJSON } from '@/mastodon/api_types/announcements'; +import { AnimatedNumber } from '@/mastodon/components/animated_number'; +import { Emoji } from '@/mastodon/components/emoji'; +import { Icon } from '@/mastodon/components/icon'; +import EmojiPickerDropdown from '@/mastodon/features/compose/containers/emoji_picker_dropdown_container'; +import { isUnicodeEmoji } from '@/mastodon/features/emoji/utils'; +import { useAppDispatch } from '@/mastodon/store'; +import AddIcon from '@/material-icons/400-24px/add.svg?react'; + +export const ReactionsBar: FC<{ + reactions: ApiAnnouncementReactionJSON[]; + id: string; +}> = ({ reactions, id }) => { + const visibleReactions = useMemo( + () => reactions.filter((x) => x.count > 0), + [reactions], + ); + + const dispatch = useAppDispatch(); + const handleEmojiPick = useCallback( + (emoji: { native: string }) => { + dispatch(addReaction(id, emoji.native.replaceAll(/:/g, ''))); + }, + [dispatch, id], + ); + + const transitions = useTransition(visibleReactions, { + from: { + scale: 0, + }, + enter: { + scale: 1, + }, + leave: { + scale: 0, + }, + keys: visibleReactions.map((x) => x.name), + }); + + return ( +
+ {transitions(({ scale }, reaction) => ( + `scale(${s})`) }} + id={id} + /> + ))} + + {visibleReactions.length < 8 && ( + } + /> + )} +
+ ); +}; + +const Reaction: FC<{ + reaction: ApiAnnouncementReactionJSON; + id: string; + style: AnimatedProps>['style']; +}> = ({ id, reaction, style }) => { + const dispatch = useAppDispatch(); + const handleClick = useCallback(() => { + if (reaction.me) { + dispatch(removeReaction(id, reaction.name)); + } else { + dispatch(addReaction(id, reaction.name)); + } + }, [dispatch, id, reaction.me, reaction.name]); + + const code = isUnicodeEmoji(reaction.name) + ? reaction.name + : `:${reaction.name}:`; + + return ( + + + + + + + + + ); +}; diff --git a/app/javascript/mastodon/features/home_timeline/components/critical_update_banner.tsx b/app/javascript/mastodon/features/home_timeline/components/critical_update_banner.tsx index d0dd2b6acda66f..b57231132f9be3 100644 --- a/app/javascript/mastodon/features/home_timeline/components/critical_update_banner.tsx +++ b/app/javascript/mastodon/features/home_timeline/components/critical_update_banner.tsx @@ -1,26 +1,34 @@ +import type { FC } from 'react'; + import { FormattedMessage } from 'react-intl'; -export const CriticalUpdateBanner = () => ( -
-
-

+import { criticalUpdatesPending } from '@/mastodon/initial_state'; + +export const CriticalUpdateBanner: FC = () => { + if (!criticalUpdatesPending) { + return null; + } + return ( +
+
-

-

- {' '} - +

- -

+ id='home.pending_critical_update.body' + defaultMessage='Please update your Mastodon server as soon as possible!' + />{' '} + + + +

+
-
-); + ); +}; diff --git a/app/javascript/mastodon/features/home_timeline/components/inline_follow_suggestions.tsx b/app/javascript/mastodon/features/home_timeline/components/inline_follow_suggestions.tsx index 3df6d67ecf6d34..d297d7cee592e8 100644 --- a/app/javascript/mastodon/features/home_timeline/components/inline_follow_suggestions.tsx +++ b/app/javascript/mastodon/features/home_timeline/components/inline_follow_suggestions.tsx @@ -25,8 +25,6 @@ import { domain } from 'mastodon/initial_state'; import { useAppDispatch, useAppSelector } from 'mastodon/store'; const messages = defineMessages({ - follow: { id: 'account.follow', defaultMessage: 'Follow' }, - unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, previous: { id: 'lightbox.previous', defaultMessage: 'Previous' }, next: { id: 'lightbox.next', defaultMessage: 'Next' }, dismiss: { @@ -272,7 +270,7 @@ export const InlineFollowSuggestions: React.FC<{ hidden?: boolean }> = ({
- +
+ + ); +}; + +const NewListWrapper: React.FC<{ + multiColumn?: boolean; +}> = ({ multiColumn }) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + const { id } = useParams<{ id?: string }>(); + const list = useAppSelector((state) => + id ? state.lists.get(id) : undefined, + ); + + useEffect(() => { + if (id) { + dispatch(fetchList(id)); + } + }, [dispatch, id]); + + const isLoading = id && !list; + return (
-
-
-
-
- - -
- -
-
-
-
- -
-
-
- - -
- -
-
-
-
- - {id && ( -
- -
- )} - -
- {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */} - -
- -
- -
-
+ {isLoading ? : }
@@ -298,4 +306,4 @@ const NewList: React.FC<{ }; // eslint-disable-next-line import/no-default-export -export default NewList; +export default NewListWrapper; diff --git a/app/javascript/mastodon/features/mutes/index.jsx b/app/javascript/mastodon/features/mutes/index.jsx index 5a5711da58291b..28c76a04e201b2 100644 --- a/app/javascript/mastodon/features/mutes/index.jsx +++ b/app/javascript/mastodon/features/mutes/index.jsx @@ -40,7 +40,7 @@ class Mutes extends ImmutablePureComponent { multiColumn: PropTypes.bool, }; - UNSAFE_componentWillMount () { + componentDidMount () { this.props.dispatch(fetchMutes()); } diff --git a/app/javascript/mastodon/features/navigation_panel/components/disabled_account_banner.tsx b/app/javascript/mastodon/features/navigation_panel/components/disabled_account_banner.tsx index 9103170fa751dd..6cb450d1d61d1c 100644 --- a/app/javascript/mastodon/features/navigation_panel/components/disabled_account_banner.tsx +++ b/app/javascript/mastodon/features/navigation_panel/components/disabled_account_banner.tsx @@ -75,7 +75,7 @@ export const DisabledAccountBanner: React.FC = () => {
diff --git a/app/javascript/mastodon/features/navigation_panel/index.tsx b/app/javascript/mastodon/features/navigation_panel/index.tsx index 66f8a657edf5c8..0dbe94cc21e415 100644 --- a/app/javascript/mastodon/features/navigation_panel/index.tsx +++ b/app/javascript/mastodon/features/navigation_panel/index.tsx @@ -35,11 +35,19 @@ import { Search } from 'mastodon/features/compose/components/search'; import { ColumnLink } from 'mastodon/features/ui/components/column_link'; import { useBreakpoint } from 'mastodon/features/ui/hooks/useBreakpoint'; import { useIdentity } from 'mastodon/identity_context'; -import { timelinePreview, trendsEnabled, me } from 'mastodon/initial_state'; +import { + localLiveFeedAccess, + remoteLiveFeedAccess, + trendsEnabled, + me, +} from 'mastodon/initial_state'; import { transientSingleColumn } from 'mastodon/is_mobile'; +import { canViewFeed } from 'mastodon/permissions'; import { selectUnreadNotificationGroupsCount } from 'mastodon/selectors/notifications'; import { useAppSelector, useAppDispatch } from 'mastodon/store'; +import { AnnualReportNavItem } from '../annual_report/nav_item'; + import { DisabledAccountBanner } from './components/disabled_account_banner'; import { FollowedTagsPanel } from './components/followed_tags_panel'; import { ListPanel } from './components/list_panel'; @@ -55,6 +63,10 @@ const messages = defineMessages({ }, explore: { id: 'explore.title', defaultMessage: 'Trending' }, firehose: { id: 'column.firehose', defaultMessage: 'Live feeds' }, + firehose_singular: { + id: 'column.firehose_singular', + defaultMessage: 'Live feed', + }, direct: { id: 'navigation_bar.direct', defaultMessage: 'Private mentions' }, favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favorites' }, bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' }, @@ -189,7 +201,7 @@ export const NavigationPanel: React.FC<{ multiColumn?: boolean }> = ({ multiColumn = false, }) => { const intl = useIntl(); - const { signedIn, disabledAccountId } = useIdentity(); + const { signedIn, permissions, disabledAccountId } = useIdentity(); const location = useLocation(); const showSearch = useBreakpoint('full') && !multiColumn; @@ -257,14 +269,24 @@ export const NavigationPanel: React.FC<{ multiColumn?: boolean }> = ({ /> )} - {(signedIn || timelinePreview) && ( + {(canViewFeed(signedIn, permissions, localLiveFeedAccess) || + canViewFeed(signedIn, permissions, remoteLiveFeedAccess)) && ( )} @@ -274,6 +296,8 @@ export const NavigationPanel: React.FC<{ multiColumn?: boolean }> = ({ + +
diff --git a/app/javascript/mastodon/features/notifications/components/filtered_notifications_banner.tsx b/app/javascript/mastodon/features/notifications/components/filtered_notifications_banner.tsx index d2241af38964f4..a1192867f5b36c 100644 --- a/app/javascript/mastodon/features/notifications/components/filtered_notifications_banner.tsx +++ b/app/javascript/mastodon/features/notifications/components/filtered_notifications_banner.tsx @@ -45,6 +45,7 @@ export const FilteredNotificationsIconButton: React.FC<{ title={intl.formatMessage(messages.filteredNotifications)} onClick={handleClick} className={className} + type='button' > diff --git a/app/javascript/mastodon/features/notifications_v2/components/embedded_status.tsx b/app/javascript/mastodon/features/notifications_v2/components/embedded_status.tsx index a17425169b857c..49bf364f05526f 100644 --- a/app/javascript/mastodon/features/notifications_v2/components/embedded_status.tsx +++ b/app/javascript/mastodon/features/notifications_v2/components/embedded_status.tsx @@ -6,6 +6,8 @@ import { useHistory } from 'react-router-dom'; import type { List as ImmutableList, RecordOf } from 'immutable'; +import type { ApiMentionJSON } from '@/mastodon/api_types/statuses'; +import { AnimateEmojiProvider } from '@/mastodon/components/emoji/context'; import BarChart4BarsIcon from '@/material-icons/400-24px/bar_chart_4_bars.svg?react'; import PhotoLibraryIcon from '@/material-icons/400-24px/photo_library.svg?react'; import { toggleStatusSpoilers } from 'mastodon/actions/statuses'; @@ -17,7 +19,7 @@ import { useAppSelector, useAppDispatch } from 'mastodon/store'; import { EmbeddedStatusContent } from './embedded_status_content'; -export type Mention = RecordOf<{ url: string; acct: string }>; +export type Mention = RecordOf; export const EmbeddedStatus: React.FC<{ statusId: string }> = ({ statusId, @@ -85,19 +87,16 @@ export const EmbeddedStatus: React.FC<{ statusId: string }> = ({ } // Assign status attributes to variables with a forced type, as status is not yet properly typed - const contentHtml = status.get('contentHtml') as string; - const contentWarning = status.get('spoilerHtml') as string; + const hasContentWarning = !!status.get('spoiler_text'); const poll = status.get('poll'); - const language = status.get('language') as string; - const mentions = status.get('mentions') as ImmutableList; - const expanded = !status.get('hidden') || !contentWarning; + const expanded = !status.get('hidden') || !hasContentWarning; const mediaAttachmentsSize = ( status.get('media_attachments') as ImmutableList ).size; return ( -
= ({
- {contentWarning && ( - - )} + - {(!contentWarning || expanded) && ( + {(!hasContentWarning || expanded) && ( )} @@ -148,6 +143,6 @@ export const EmbeddedStatus: React.FC<{ statusId: string }> = ({ )}
)} -
+ ); }; diff --git a/app/javascript/mastodon/features/notifications_v2/components/embedded_status_content.tsx b/app/javascript/mastodon/features/notifications_v2/components/embedded_status_content.tsx index 1a38be536ba6a8..7312a94a302b1d 100644 --- a/app/javascript/mastodon/features/notifications_v2/components/embedded_status_content.tsx +++ b/app/javascript/mastodon/features/notifications_v2/components/embedded_status_content.tsx @@ -1,93 +1,40 @@ -import { useCallback } from 'react'; - -import { useHistory } from 'react-router-dom'; +import { useCallback, useMemo } from 'react'; import type { List } from 'immutable'; -import type { History } from 'history'; +import { EmojiHTML } from '@/mastodon/components/emoji/html'; +import { useElementHandledLink } from '@/mastodon/components/status/handled_link'; +import type { CustomEmoji } from '@/mastodon/models/custom_emoji'; +import type { Status } from '@/mastodon/models/status'; import type { Mention } from './embedded_status'; -const handleMentionClick = ( - history: History, - mention: Mention, - e: MouseEvent, -) => { - if (e.button === 0 && !(e.ctrlKey || e.metaKey)) { - e.preventDefault(); - history.push(`/@${mention.get('acct')}`); - } -}; - -const handleHashtagClick = ( - history: History, - hashtag: string, - e: MouseEvent, -) => { - if (e.button === 0 && !(e.ctrlKey || e.metaKey)) { - e.preventDefault(); - history.push(`/tags/${hashtag.replace(/^#/, '')}`); - } -}; - export const EmbeddedStatusContent: React.FC<{ - content: string; - mentions: List; - language: string; + status: Status; className?: string; -}> = ({ content, mentions, language, className }) => { - const history = useHistory(); - - const handleContentRef = useCallback( - (node: HTMLDivElement | null) => { - if (!node) { - return; - } - - const links = node.querySelectorAll('a'); - - for (const link of links) { - if (link.classList.contains('status-link')) { - continue; - } - - link.classList.add('status-link'); - - const mention = mentions.find((item) => link.href === item.get('url')); - - if (mention) { - link.addEventListener( - 'click', - handleMentionClick.bind(null, history, mention), - false, - ); - link.setAttribute('title', `@${mention.get('acct')}`); - link.setAttribute('href', `/@${mention.get('acct')}`); - } else if ( - link.textContent?.[0] === '#' || - link.previousSibling?.textContent?.endsWith('#') - ) { - link.addEventListener( - 'click', - handleHashtagClick.bind(null, history, link.text), - false, - ); - link.setAttribute('href', `/tags/${link.text.replace(/^#/, '')}`); - } else { - link.setAttribute('title', link.href); - link.classList.add('unhandled-link'); - } - } +}> = ({ status, className }) => { + const mentions = useMemo( + () => (status.get('mentions') as List).toJS(), + [status], + ); + const hrefToMention = useCallback( + (href: string) => { + return mentions.find((item) => item.url === href); }, - [mentions, history], + [mentions], ); + const htmlHandlers = useElementHandledLink({ + hashtagAccountId: status.get('account') as string | undefined, + hrefToMention, + }); return ( -
} /> ); }; diff --git a/app/javascript/mastodon/features/notifications_v2/components/notification_annual_report.tsx b/app/javascript/mastodon/features/notifications_v2/components/notification_annual_report.tsx index 8b92f31add50a6..4541592fbd97aa 100644 --- a/app/javascript/mastodon/features/notifications_v2/components/notification_annual_report.tsx +++ b/app/javascript/mastodon/features/notifications_v2/components/notification_annual_report.tsx @@ -47,7 +47,7 @@ export const NotificationAnnualReport: React.FC<{ values={{ year }} />

- diff --git a/app/javascript/mastodon/features/notifications_v2/index.tsx b/app/javascript/mastodon/features/notifications_v2/index.tsx index 7c73d602fc7397..3fca9eb44ee500 100644 --- a/app/javascript/mastodon/features/notifications_v2/index.tsx +++ b/app/javascript/mastodon/features/notifications_v2/index.tsx @@ -245,6 +245,7 @@ export const Notifications: React.FC<{ title={intl.formatMessage(messages.markAsRead)} onClick={handleMarkAsRead} className='column-header__button' + type='button' > diff --git a/app/javascript/mastodon/features/onboarding/profile.tsx b/app/javascript/mastodon/features/onboarding/profile.tsx index d9b394acfbb85d..7e725e97cc3861 100644 --- a/app/javascript/mastodon/features/onboarding/profile.tsx +++ b/app/javascript/mastodon/features/onboarding/profile.tsx @@ -54,9 +54,7 @@ export const Profile: React.FC<{ me ? state.accounts.get(me) : undefined, ); const [displayName, setDisplayName] = useState(account?.display_name ?? ''); - const [note, setNote] = useState( - account ? (unescapeHTML(account.note) ?? '') : '', - ); + const [note, setNote] = useState(account ? unescapeHTML(account.note) : ''); const [avatar, setAvatar] = useState(); const [header, setHeader] = useState(); const [discoverable, setDiscoverable] = useState( diff --git a/app/javascript/mastodon/features/picture_in_picture/components/footer.tsx b/app/javascript/mastodon/features/picture_in_picture/components/footer.tsx index d1671b4bda45a9..e848e6929950a4 100644 --- a/app/javascript/mastodon/features/picture_in_picture/components/footer.tsx +++ b/app/javascript/mastodon/features/picture_in_picture/components/footer.tsx @@ -2,25 +2,19 @@ import { useCallback, useMemo } from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import classNames from 'classnames'; import { useHistory } from 'react-router-dom'; import OpenInNewIcon from '@/material-icons/400-24px/open_in_new.svg?react'; -import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react'; import ReplyIcon from '@/material-icons/400-24px/reply.svg?react'; import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react'; import StarIcon from '@/material-icons/400-24px/star-fill.svg?react'; import StarBorderIcon from '@/material-icons/400-24px/star.svg?react'; -import RepeatActiveIcon from '@/svg-icons/repeat_active.svg?react'; -import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react'; -import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react'; -import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react'; import { replyCompose } from 'mastodon/actions/compose'; -import { toggleReblog, toggleFavourite } from 'mastodon/actions/interactions'; +import { toggleFavourite } from 'mastodon/actions/interactions'; import { openModal } from 'mastodon/actions/modal'; import { IconButton } from 'mastodon/components/icon_button'; +import { BoostButton } from 'mastodon/components/status/boost_button'; import { useIdentity } from 'mastodon/identity_context'; -import { me } from 'mastodon/initial_state'; import type { Account } from 'mastodon/models/account'; import type { Status } from 'mastodon/models/status'; import { makeGetStatus } from 'mastodon/selectors'; @@ -120,29 +114,6 @@ export const Footer: React.FC<{ } }, [dispatch, status, signedIn]); - const handleReblogClick = useCallback( - (e: React.MouseEvent) => { - if (!status) { - return; - } - - if (signedIn) { - dispatch(toggleReblog(status.get('id'), e.shiftKey)); - } else { - dispatch( - openModal({ - modalType: 'INTERACTION', - modalProps: { - accountId: status.getIn(['account', 'id']), - url: status.get('uri'), - }, - }), - ); - } - }, - [dispatch, status, signedIn], - ); - const handleOpenClick = useCallback( (e: React.MouseEvent) => { if (e.button !== 0 || !status) { @@ -160,13 +131,6 @@ export const Footer: React.FC<{ return null; } - const publicStatus = ['public', 'unlisted'].includes( - status.get('visibility') as string, - ); - const reblogPrivate = - status.getIn(['account', 'id']) === me && - status.get('visibility') === 'private'; - let replyIcon, replyIconComponent, replyTitle; if (status.get('in_reply_to_id', null) === null) { @@ -179,24 +143,6 @@ export const Footer: React.FC<{ replyTitle = intl.formatMessage(messages.replyAll); } - let reblogTitle, reblogIconComponent; - - if (status.get('reblogged')) { - reblogTitle = intl.formatMessage(messages.cancel_reblog_private); - reblogIconComponent = publicStatus - ? RepeatActiveIcon - : RepeatPrivateActiveIcon; - } else if (publicStatus) { - reblogTitle = intl.formatMessage(messages.reblog); - reblogIconComponent = RepeatIcon; - } else if (reblogPrivate) { - reblogTitle = intl.formatMessage(messages.reblog_private); - reblogIconComponent = RepeatPrivateIcon; - } else { - reblogTitle = intl.formatMessage(messages.cannot_reblog); - reblogIconComponent = RepeatDisabledIcon; - } - const favouriteTitle = intl.formatMessage( status.get('favourited') ? messages.removeFavourite : messages.favourite, ); @@ -222,19 +168,7 @@ export const Footer: React.FC<{ counter={status.get('replies_count') as number} /> - + + ) : ( + + ); + return ( } + emptyMessage={emptyMessage} bindToDocument={!multiColumn} /> diff --git a/app/javascript/mastodon/features/quotes/index.tsx b/app/javascript/mastodon/features/quotes/index.tsx index d0290e11185b40..56cf2f71f11c63 100644 --- a/app/javascript/mastodon/features/quotes/index.tsx +++ b/app/javascript/mastodon/features/quotes/index.tsx @@ -12,6 +12,8 @@ import { ColumnHeader } from 'mastodon/components/column_header'; import { Icon } from 'mastodon/components/icon'; import { LoadingIndicator } from 'mastodon/components/loading_indicator'; import StatusList from 'mastodon/components/status_list'; +import { useIdentity } from 'mastodon/identity_context'; +import { domain } from 'mastodon/initial_state'; import { useAppDispatch, useAppSelector } from 'mastodon/store'; import Column from '../ui/components/column'; @@ -31,9 +33,18 @@ export const Quotes: React.FC<{ const statusId = params?.statusId; + const { accountId: me } = useIdentity(); + const isCorrectStatusId: boolean = useAppSelector( (state) => state.status_lists.getIn(['quotes', 'statusId']) === statusId, ); + const quotedAccountId = useAppSelector( + (state) => + state.statuses.getIn([statusId, 'account']) as string | undefined, + ); + const quotedAccount = useAppSelector((state) => + quotedAccountId ? state.accounts.get(quotedAccountId) : undefined, + ); const statusIds = useAppSelector((state) => state.status_lists.getIn(['quotes', 'items'], emptyList), ); @@ -74,6 +85,32 @@ export const Quotes: React.FC<{ /> ); + let prependMessage; + + if (me === quotedAccountId) { + prependMessage = null; + } else if (quotedAccount?.username === quotedAccount?.acct) { + // Local account, we know this to be exhaustive + prependMessage = ( +
+ +
+ ); + } else { + prependMessage = ( +
+ {domain} }} + /> +
+ ); + } + return ( diff --git a/app/javascript/mastodon/features/reblogs/index.jsx b/app/javascript/mastodon/features/reblogs/index.jsx index 8bde919a0f3cbe..24786b62f065d0 100644 --- a/app/javascript/mastodon/features/reblogs/index.jsx +++ b/app/javascript/mastodon/features/reblogs/index.jsx @@ -42,7 +42,7 @@ class Reblogs extends ImmutablePureComponent { intl: PropTypes.object.isRequired, }; - UNSAFE_componentWillMount () { + componentDidMount () { if (!this.props.accountIds) { this.props.dispatch(fetchReblogs(this.props.params.statusId)); } diff --git a/app/javascript/mastodon/features/search/components/search_section.tsx b/app/javascript/mastodon/features/search/components/search_section.tsx index ae0c1296766c9d..c59d0c2fe4df98 100644 --- a/app/javascript/mastodon/features/search/components/search_section.tsx +++ b/app/javascript/mastodon/features/search/components/search_section.tsx @@ -9,7 +9,7 @@ export const SearchSection: React.FC<{

{title}

{onClickMore && ( -
- ); - - spoilerButton = ( -
- {spoilerButton} -
- ); - - if (interactive) { - if (embedded) { - embed = this.renderVideo(); - } else { - embed = ( -
- {canvas} - {thumbnail} - - {revealed ? ( -
-
- - -
-
- ) : spoilerButton} -
- ); - } - - return ( -
- {embed} - {description} -
- ); - } else if (card.get('image')) { - embed = ( -
- {canvas} - {thumbnail} -
- ); - } else { - embed = ( -
- -
- ); - } - - return ( - <> - - {embed} - {description} - - - {showAuthor && } - - ); - } - -} diff --git a/app/javascript/mastodon/features/status/components/card.tsx b/app/javascript/mastodon/features/status/components/card.tsx new file mode 100644 index 00000000000000..d060d35c2cdd7c --- /dev/null +++ b/app/javascript/mastodon/features/status/components/card.tsx @@ -0,0 +1,318 @@ +import { useCallback, useId, useState } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import classNames from 'classnames'; + +import punycode from 'punycode/'; + +import DescriptionIcon from '@/material-icons/400-24px/description-fill.svg?react'; +import OpenInNewIcon from '@/material-icons/400-24px/open_in_new.svg?react'; +import PlayArrowIcon from '@/material-icons/400-24px/play_arrow-fill.svg?react'; +import { Blurhash } from 'mastodon/components/blurhash'; +import { Icon } from 'mastodon/components/icon'; +import { MoreFromAuthor } from 'mastodon/components/more_from_author'; +import { RelativeTimestamp } from 'mastodon/components/relative_timestamp'; +import { useBlurhash } from 'mastodon/initial_state'; +import type { Card as CardType } from 'mastodon/models/status'; + +const IDNA_PREFIX = 'xn--'; + +const decodeIDNA = (domain: string) => { + return domain + .split('.') + .map((part) => + part.startsWith(IDNA_PREFIX) + ? punycode.decode(part.slice(IDNA_PREFIX.length)) + : part, + ) + .join('.'); +}; + +const getHostname = (url: string) => { + const parser = document.createElement('a'); + parser.href = url; + return parser.hostname; +}; + +const domParser = new DOMParser(); + +const handleIframeUrl = (html: string, url: string, providerName: string) => { + const document = domParser.parseFromString(html, 'text/html').documentElement; + const iframe = document.querySelector('iframe'); + const startTime = new URL(url).searchParams.get('t'); + + if (iframe) { + const iframeUrl = new URL(iframe.src); + + iframeUrl.searchParams.set('autoplay', '1'); + iframeUrl.searchParams.set('auto_play', '1'); + + if (providerName === 'YouTube') { + iframeUrl.searchParams.set('start', startTime ?? ''); + iframe.referrerPolicy = 'strict-origin-when-cross-origin'; + } + + iframe.src = iframeUrl.href; + + // DOM parser creates html/body elements around original HTML fragment, + // so we need to get innerHTML out of the body and not the entire document + return document.querySelector('body')?.innerHTML ?? ''; + } + + return html; +}; + +interface CardProps { + card: CardType | null; + sensitive?: boolean; +} + +const CardVideo: React.FC> = ({ card }) => ( +
+); + +const Card: React.FC = ({ card, sensitive }) => { + const [previewLoaded, setPreviewLoaded] = useState(false); + const [embedded, setEmbedded] = useState(false); + const [revealed, setRevealed] = useState(!sensitive); + + const handleEmbedClick = useCallback(() => { + setEmbedded(true); + }, []); + + const handleExternalLinkClick = useCallback((e: React.MouseEvent) => { + e.stopPropagation(); + }, []); + + const handleImageLoad = useCallback(() => { + setPreviewLoaded(true); + }, []); + + const handleReveal = useCallback((e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + setRevealed(true); + }, []); + + const spoilerButtonId = useId(); + + if (card === null) { + return null; + } + + const provider = + card.get('provider_name').length === 0 + ? decodeIDNA(getHostname(card.get('url'))) + : card.get('provider_name'); + const interactive = card.get('type') === 'video'; + const language = card.get('language') || ''; + const hasImage = (card.get('image')?.length ?? 0) > 0; + const largeImage = + (hasImage && card.get('width') > card.get('height')) || interactive; + const showAuthor = !!card.getIn(['authors', 0, 'accountId']); + + const description = ( +
+ + {provider} + {card.get('published_at') && ( + <> + {' '} + · + + )} + + + + {card.get('title')} + + + {!showAuthor && + (card.get('author_name').length > 0 ? ( + + {card.get('author_name')} }} + /> + + ) : ( + + {card.get('description')} + + ))} +
+ ); + + const thumbnailStyle: React.CSSProperties = { + visibility: revealed ? undefined : 'hidden', + aspectRatio: '1', + }; + + if (largeImage && card.get('type') === 'video') { + thumbnailStyle.aspectRatio = `16 / 9`; + } else if (largeImage) { + thumbnailStyle.aspectRatio = '1.91 / 1'; + } + + let embed; + + const canvas = ( + + ); + + const thumbnailDescription = card.get('image_description'); + const thumbnail = ( + {thumbnailDescription} + ); + + const spoilerButton = ( +
+ +
+ ); + + if (interactive) { + if (embedded) { + embed = ; + } else { + embed = ( +
+ {canvas} + {thumbnail} + + {revealed ? ( +
+
+ + + + +
+
+ ) : ( + spoilerButton + )} +
+ ); + } + + return ( +
+ {embed} + + {description} + +
+ ); + } else if (card.get('image')) { + embed = ( +
+ {canvas} + {thumbnail} +
+ ); + } else { + embed = ( +
+ +
+ ); + } + + return ( + <> + + {embed} + {description} + + + {showAuthor && ( + + )} + + ); +}; + +// eslint-disable-next-line import/no-default-export +export default Card; diff --git a/app/javascript/mastodon/features/status/components/detailed_status.tsx b/app/javascript/mastodon/features/status/components/detailed_status.tsx index 957364fd7a7dd1..1dee2e51477a99 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.tsx +++ b/app/javascript/mastodon/features/status/components/detailed_status.tsx @@ -4,7 +4,7 @@ @typescript-eslint/no-unsafe-assignment */ import type { CSSProperties } from 'react'; -import { useState, useRef, useCallback } from 'react'; +import { useState, useRef, useCallback, useEffect } from 'react'; import { FormattedMessage } from 'react-intl'; @@ -31,7 +31,7 @@ import { VisibilityIcon } from 'mastodon/components/visibility_icon'; import { Audio } from 'mastodon/features/audio'; import scheduleIdleTask from 'mastodon/features/ui/util/schedule_idle_task'; import { Video } from 'mastodon/features/video'; -import { me } from 'mastodon/initial_state'; +import { useIdentity } from 'mastodon/identity_context'; import Card from './card'; @@ -55,6 +55,8 @@ export const DetailedStatus: React.FC<{ pictureInPicture: any; onToggleHidden?: (status: any) => void; onToggleMediaVisibility?: () => void; + ancestors?: number; + multiColumn?: boolean; }> = ({ status, onOpenMedia, @@ -69,12 +71,16 @@ export const DetailedStatus: React.FC<{ pictureInPicture, onToggleMediaVisibility, onToggleHidden, + ancestors = 0, + multiColumn = false, }) => { const properStatus = status?.get('reblog') ?? status; const [height, setHeight] = useState(0); const [showDespiteFilter, setShowDespiteFilter] = useState(false); const nodeRef = useRef(); + const { signedIn } = useIdentity(); + const handleOpenVideo = useCallback( (options: VideoModalOptions) => { const lang = (status.getIn(['translation', 'language']) || @@ -121,6 +127,30 @@ export const DetailedStatus: React.FC<{ if (onTranslate) onTranslate(status); }, [onTranslate, status]); + // The component is managed and will change if the status changes + // Ancestors can increase when loading a thread, in which case we want to scroll, + // or decrease if a post is deleted, in which case we don't want to mess with it + const previousAncestors = useRef(-1); + useEffect(() => { + if (nodeRef.current && previousAncestors.current < ancestors) { + nodeRef.current.scrollIntoView(true); + + // In the single-column interface, `scrollIntoView` will put the post behind the header, so compensate for that. + if (!multiColumn) { + const offset = document + .querySelector('.column-header__wrapper') + ?.getBoundingClientRect().bottom; + + if (offset) { + const scrollingElement = document.scrollingElement ?? document.body; + scrollingElement.scrollBy(0, -offset); + } + } + } + + previousAncestors.current = ancestors; + }, [ancestors, multiColumn]); + if (!properStatus) { return null; } @@ -232,8 +262,8 @@ export const DetailedStatus: React.FC<{ } else if (status.get('card') && !status.get('quote')) { media = ( ); @@ -269,13 +299,17 @@ export const DetailedStatus: React.FC<{ to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}/reblogs`} className='detailed-status__link' > - - - + + + ), + }} /> ); @@ -283,32 +317,40 @@ export const DetailedStatus: React.FC<{ if (['private', 'direct'].includes(status.get('visibility') as string)) { quotesLink = ''; - } else if (status.getIn(['account', 'id']) === me) { + } else if (signedIn) { quotesLink = ( - - - + + + ), + }} /> ); } else { quotesLink = ( - - - + + + ), + }} /> ); @@ -319,13 +361,17 @@ export const DetailedStatus: React.FC<{ to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}/favourites`} className='detailed-status__link' > - - - + + + ), + }} /> ); @@ -392,17 +438,13 @@ export const DetailedStatus: React.FC<{ /> )} - {status.get('spoiler_text').length > 0 && - (!matchedFilters || showDespiteFilter) && ( - - )} + {(!matchedFilters || showDespiteFilter) && ( + + )} {expanded && ( <> @@ -419,6 +461,7 @@ export const DetailedStatus: React.FC<{ )} diff --git a/app/javascript/mastodon/features/status/components/refresh_controller.tsx b/app/javascript/mastodon/features/status/components/refresh_controller.tsx index 34faaf1d5d2ac1..e12414b7205f07 100644 --- a/app/javascript/mastodon/features/status/components/refresh_controller.tsx +++ b/app/javascript/mastodon/features/status/components/refresh_controller.tsx @@ -1,16 +1,22 @@ -import { useEffect, useState, useCallback } from 'react'; +import { useEffect, useState, useCallback, useMemo } from 'react'; import { useIntl, defineMessages } from 'react-intl'; +import { useDebouncedCallback } from 'use-debounce'; + import { fetchContext, completeContextRefresh, + showPendingReplies, + clearPendingReplies, } from 'mastodon/actions/statuses'; import type { AsyncRefreshHeader } from 'mastodon/api'; import { apiGetAsyncRefresh } from 'mastodon/api/async_refreshes'; import { Alert } from 'mastodon/components/alert'; import { ExitAnimationWrapper } from 'mastodon/components/exit_animation_wrapper'; import { LoadingIndicator } from 'mastodon/components/loading_indicator'; +import { useInterval } from 'mastodon/hooks/useInterval'; +import { useIsDocumentVisible } from 'mastodon/hooks/useIsDocumentVisible'; import { useAppSelector, useAppDispatch } from 'mastodon/store'; const AnimatedAlert: React.FC< @@ -34,13 +40,9 @@ const messages = defineMessages({ id: 'status.context.loading', defaultMessage: 'Loading', }, - loadingMore: { - id: 'status.context.loading_more', - defaultMessage: 'Loading more replies', - }, success: { id: 'status.context.loading_success', - defaultMessage: 'All replies loaded', + defaultMessage: 'New replies loaded', }, error: { id: 'status.context.loading_error', @@ -52,80 +54,224 @@ const messages = defineMessages({ }, }); -type LoadingState = - | 'idle' - | 'more-available' - | 'loading-initial' - | 'loading-more' - | 'success' - | 'error'; +type LoadingState = 'idle' | 'more-available' | 'loading' | 'success' | 'error'; -export const RefreshController: React.FC<{ - statusId: string; -}> = ({ statusId }) => { - const refresh = useAppSelector( - (state) => state.contexts.refreshing[statusId], - ); - const currentReplyCount = useAppSelector( - (state) => state.contexts.replies[statusId]?.length ?? 0, - ); - const autoRefresh = !currentReplyCount; - const dispatch = useAppDispatch(); - const intl = useIntl(); +/** + * Age of thread below which we consider it new & fetch + * replies more frequently + */ +const NEW_THREAD_AGE_THRESHOLD = 30 * 60_000; +/** + * Interval at which we check for new replies for old threads + */ +const LONG_AUTO_FETCH_REPLIES_INTERVAL = 5 * 60_000; +/** + * Interval at which we check for new replies for new threads. + * Also used as a threshold to throttle repeated fetch calls + */ +const SHORT_AUTO_FETCH_REPLIES_INTERVAL = 60_000; +/** + * Number of refresh_async checks at which an early fetch + * will be triggered if there are results + */ +const LONG_RUNNING_FETCH_THRESHOLD = 3; - const [loadingState, setLoadingState] = useState( - refresh && autoRefresh ? 'loading-initial' : 'idle', - ); +/** + * Returns whether the thread is new, based on NEW_THREAD_AGE_THRESHOLD + */ +function getIsThreadNew(statusCreatedAt: string) { + const now = new Date(); + const newThreadThreshold = new Date(now.getTime() - NEW_THREAD_AGE_THRESHOLD); - const [wasDismissed, setWasDismissed] = useState(false); - const dismissPrompt = useCallback(() => { - setWasDismissed(true); - setLoadingState('idle'); - }, []); + return new Date(statusCreatedAt) > newThreadThreshold; +} + +/** + * This hook kicks off a background check for the async refresh job + * and loads any newly found replies once the job has finished, + * and when LONG_RUNNING_FETCH_THRESHOLD was reached and replies were found + */ +function useCheckForRemoteReplies({ + statusId, + refreshHeader, + isEnabled, + onChangeLoadingState, +}: { + statusId: string; + refreshHeader?: AsyncRefreshHeader; + isEnabled: boolean; + onChangeLoadingState: React.Dispatch>; +}) { + const dispatch = useAppDispatch(); useEffect(() => { let timeoutId: ReturnType; - const scheduleRefresh = (refresh: AsyncRefreshHeader) => { + const scheduleRefresh = ( + refresh: AsyncRefreshHeader, + iteration: number, + ) => { timeoutId = setTimeout(() => { void apiGetAsyncRefresh(refresh.id).then((result) => { - if (result.async_refresh.status === 'finished') { + const { status, result_count } = result.async_refresh; + + // At three scheduled refreshes, we consider the job + // long-running and attempt to fetch any new replies so far + const isLongRunning = iteration === LONG_RUNNING_FETCH_THRESHOLD; + + // If the refresh status is not finished and not long-running, + // we just schedule another refresh and exit + if (status === 'running' && !isLongRunning) { + scheduleRefresh(refresh, iteration + 1); + return; + } + + // If refresh status is finished, clear `refreshHeader` + // (we don't want to do this if it's just a long-running job) + if (status === 'finished') { dispatch(completeContextRefresh({ statusId })); + } - if (result.async_refresh.result_count > 0) { - if (autoRefresh) { - void dispatch(fetchContext({ statusId })).then(() => { - setLoadingState('idle'); - }); - } else { - setLoadingState('more-available'); - } + // Exit if there's nothing to fetch + if (result_count === 0) { + if (status === 'finished') { + onChangeLoadingState('idle'); } else { - setLoadingState('idle'); + scheduleRefresh(refresh, iteration + 1); } - } else { - scheduleRefresh(refresh); + return; } + + // A positive result count means there _might_ be new replies, + // so we fetch the context in the background to check if there + // are any new replies. + // If so, they will populate `contexts.pendingReplies[statusId]` + void dispatch(fetchContext({ statusId, prefetchOnly: true })) + .then(() => { + // Reset loading state to `idle`. If the fetch has + // resulted in new pending replies, the `hasPendingReplies` + // flag will switch the loading state to 'more-available' + if (status === 'finished') { + onChangeLoadingState('idle'); + } else { + // Keep background fetch going if `isLongRunning` is true + scheduleRefresh(refresh, iteration + 1); + } + }) + .catch(() => { + // Show an error if the fetch failed + onChangeLoadingState('error'); + }); }); }, refresh.retry * 1000); }; - if (refresh && !wasDismissed) { - scheduleRefresh(refresh); - setLoadingState('loading-initial'); + // Initialise a refresh + if (refreshHeader && isEnabled) { + scheduleRefresh(refreshHeader, 1); + onChangeLoadingState('loading'); } return () => { clearTimeout(timeoutId); }; - }, [dispatch, statusId, refresh, autoRefresh, wasDismissed]); + }, [onChangeLoadingState, dispatch, statusId, refreshHeader, isEnabled]); +} + +/** + * This component fetches new post replies in the background + * and gives users the option to show them. + * + * The following three scenarios are handled: + * + * 1. When the browser tab is visible, replies are refetched periodically + * (more frequently for new posts, less frequently for old ones) + * 2. Replies are refetched when the browser tab is refocused + * after it was hidden or minimised + * 3. For remote posts, remote replies that might not yet be known to the + * server are imported & fetched using the AsyncRefresh API. + */ +export const RefreshController: React.FC<{ + statusId: string; + statusCreatedAt: string; + isLocal: boolean; +}> = ({ statusId, statusCreatedAt, isLocal }) => { + const dispatch = useAppDispatch(); + const intl = useIntl(); + + const refreshHeader = useAppSelector((state) => + isLocal ? undefined : state.contexts.refreshing[statusId], + ); + const hasPendingReplies = useAppSelector( + (state) => !!state.contexts.pendingReplies[statusId]?.length, + ); + const [partialLoadingState, setLoadingState] = useState( + refreshHeader ? 'loading' : 'idle', + ); + const loadingState = hasPendingReplies + ? 'more-available' + : partialLoadingState; + + const [wasDismissed, setWasDismissed] = useState(false); + const dismissPrompt = useCallback(() => { + setWasDismissed(true); + setLoadingState('idle'); + dispatch(clearPendingReplies({ statusId })); + }, [dispatch, statusId]); + + // Prevent too-frequent context calls + const debouncedFetchContext = useDebouncedCallback( + () => { + void dispatch(fetchContext({ statusId, prefetchOnly: true })); + }, + // Ensure the debounce is a bit shorter than the auto-fetch interval + SHORT_AUTO_FETCH_REPLIES_INTERVAL - 500, + { + leading: true, + trailing: false, + }, + ); + + const isDocumentVisible = useIsDocumentVisible({ + onChange: (isVisible) => { + // Auto-fetch new replies when the page is refocused + if (isVisible && partialLoadingState !== 'loading' && !wasDismissed) { + debouncedFetchContext(); + } + }, + }); + + // Check for remote replies + useCheckForRemoteReplies({ + statusId, + refreshHeader, + isEnabled: isDocumentVisible && !isLocal && !wasDismissed, + onChangeLoadingState: setLoadingState, + }); + + // Only auto-fetch new replies if there's no ongoing remote replies check + const shouldAutoFetchReplies = + isDocumentVisible && partialLoadingState !== 'loading' && !wasDismissed; + + const autoFetchInterval = useMemo( + () => + getIsThreadNew(statusCreatedAt) + ? SHORT_AUTO_FETCH_REPLIES_INTERVAL + : LONG_AUTO_FETCH_REPLIES_INTERVAL, + [statusCreatedAt], + ); + + useInterval(debouncedFetchContext, { + delay: autoFetchInterval, + isEnabled: shouldAutoFetchReplies, + }); useEffect(() => { // Hide success message after a short delay if (loadingState === 'success') { const timeoutId = setTimeout(() => { setLoadingState('idle'); - }, 3000); + }, 2500); return () => { clearTimeout(timeoutId); @@ -134,23 +280,22 @@ export const RefreshController: React.FC<{ return () => ''; }, [loadingState]); - const handleClick = useCallback(() => { - setLoadingState('loading-more'); - - dispatch(fetchContext({ statusId })) - .then(() => { - setLoadingState('success'); - return ''; - }) - .catch(() => { - setLoadingState('error'); - }); + useEffect(() => { + // Clear pending replies on unmount + return () => { + dispatch(clearPendingReplies({ statusId })); + }; + }, [dispatch, statusId]); + + const showPending = useCallback(() => { + dispatch(showPendingReplies({ statusId })); + setLoadingState('success'); }, [dispatch, statusId]); - if (loadingState === 'loading-initial') { + if (loadingState === 'loading') { return (
- diff --git a/app/javascript/mastodon/features/status/index.jsx b/app/javascript/mastodon/features/status/index.jsx index 404faf609e4ef1..77e8af4b679f44 100644 --- a/app/javascript/mastodon/features/status/index.jsx +++ b/app/javascript/mastodon/features/status/index.jsx @@ -16,7 +16,7 @@ import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?reac import { Hotkeys } from 'mastodon/components/hotkeys'; import { Icon } from 'mastodon/components/icon'; import { LoadingIndicator } from 'mastodon/components/loading_indicator'; -import ScrollContainer from 'mastodon/containers/scroll_container'; +import { ScrollContainer } from 'mastodon/containers/scroll_container'; import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error'; import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; import { WithRouterPropTypes } from 'mastodon/utils/react_router'; @@ -159,18 +159,16 @@ class Status extends ImmutablePureComponent { }; UNSAFE_componentWillMount () { - this.props.dispatch(fetchStatus(this.props.params.statusId)); + this.props.dispatch(fetchStatus(this.props.params.statusId, { forceFetch: true })); } componentDidMount () { attachFullscreenListener(this.onFullScreenChange); - - this._scrollStatusIntoView(); } UNSAFE_componentWillReceiveProps (nextProps) { if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) { - this.props.dispatch(fetchStatus(nextProps.params.statusId)); + this.props.dispatch(fetchStatus(nextProps.params.statusId, { forceFetch: true })); } if (nextProps.status && nextProps.status.get('id') !== this.state.loadedStatusId) { @@ -299,6 +297,12 @@ class Status extends ImmutablePureComponent { dispatch(openModal({ modalType: 'COMPOSE_PRIVACY', modalProps: { statusId, onChange: handleChange } })); }; + handleQuote = (status) => { + const { dispatch } = this.props; + + dispatch(quoteComposeById(status.get('id'))); + }; + handleEditClick = (status) => { const { dispatch, askReplyConfirmation } = this.props; @@ -481,35 +485,13 @@ class Status extends ImmutablePureComponent { this.statusNode = c; }; - _scrollStatusIntoView () { - const { status, multiColumn } = this.props; - - if (status) { - requestIdleCallback(() => { - this.statusNode?.scrollIntoView(true); - - // In the single-column interface, `scrollIntoView` will put the post behind the header, - // so compensate for that. - if (!multiColumn) { - const offset = document.querySelector('.column-header__wrapper')?.getBoundingClientRect()?.bottom; - if (offset) { - const scrollingElement = document.scrollingElement || document.body; - scrollingElement.scrollBy(0, -offset); - } - } - }); - } - } - componentDidUpdate (prevProps) { - const { status, ancestorsIds, descendantsIds } = this.props; + const { status, descendantsIds } = this.props; - if (status && (ancestorsIds.length > prevProps.ancestorsIds.length || prevProps.status?.get('id') !== status.get('id'))) { - this._scrollStatusIntoView(); - } + const isSameStatus = status && (prevProps.status?.get('id') === status.get('id')); // Only highlight replies after the initial load - if (prevProps.descendantsIds.length) { + if (prevProps.descendantsIds.length && isSameStatus) { const newRepliesIds = difference(descendantsIds, prevProps.descendantsIds); if (newRepliesIds.length) { @@ -526,9 +508,9 @@ class Status extends ImmutablePureComponent { this.setState({ fullscreen: isFullscreen() }); }; - shouldUpdateScroll = (prevRouterProps, { location }) => { + shouldUpdateScroll = (prevLocation, location) => { // Do not change scroll when opening a modal - if (location.state?.mastodonModalKey !== prevRouterProps?.location?.state?.mastodonModalKey) { + if (location.state?.mastodonModalKey !== prevLocation?.state?.mastodonModalKey) { return false; } @@ -571,14 +553,6 @@ class Status extends ImmutablePureComponent { const isLocal = status.getIn(['account', 'acct'], '').indexOf('@') === -1; const isIndexable = !status.getIn(['account', 'noindex']); - if (!isLocal) { - remoteHint = ( - - ); - } - const handlers = { reply: this.handleHotkeyReply, favourite: this.handleHotkeyFavourite, @@ -598,12 +572,12 @@ class Status extends ImmutablePureComponent { showBackButton multiColumn={multiColumn} extraButton={( - + )} /> - -
+ +
{ancestors} @@ -619,6 +593,8 @@ class Status extends ImmutablePureComponent { showMedia={this.state.showMedia} onToggleMediaVisibility={this.handleToggleMediaVisibility} pictureInPicture={pictureInPicture} + ancestors={this.props.ancestorsIds.length} + multiColumn={multiColumn} /> {descendants} - {remoteHint} + +
diff --git a/app/javascript/mastodon/features/ui/components/actions_modal.tsx b/app/javascript/mastodon/features/ui/components/actions_modal.tsx index 2577b21a17cc85..13deedcd1a88a4 100644 --- a/app/javascript/mastodon/features/ui/components/actions_modal.tsx +++ b/app/javascript/mastodon/features/ui/components/actions_modal.tsx @@ -25,7 +25,12 @@ export const ActionsModal: React.FC<{ if (isActionItem(option)) { element = ( - ); diff --git a/app/javascript/mastodon/features/ui/components/annual_report_modal.tsx b/app/javascript/mastodon/features/ui/components/annual_report_modal.tsx deleted file mode 100644 index 8c39c0b3aa07a2..00000000000000 --- a/app/javascript/mastodon/features/ui/components/annual_report_modal.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { useEffect } from 'react'; - -import { AnnualReport } from 'mastodon/features/annual_report'; - -const AnnualReportModal: React.FC<{ - year: string; - onChangeBackgroundColor: (arg0: string) => void; -}> = ({ year, onChangeBackgroundColor }) => { - useEffect(() => { - onChangeBackgroundColor('var(--indigo-1)'); - }, [onChangeBackgroundColor]); - - return ( -
- -
- ); -}; - -// eslint-disable-next-line import/no-default-export -export default AnnualReportModal; diff --git a/app/javascript/mastodon/features/ui/components/boost_modal.tsx b/app/javascript/mastodon/features/ui/components/boost_modal.tsx index 1963189194414f..7e45f5749177dd 100644 --- a/app/javascript/mastodon/features/ui/components/boost_modal.tsx +++ b/app/javascript/mastodon/features/ui/components/boost_modal.tsx @@ -52,7 +52,10 @@ export const BoostModal: React.FC<{ }, [onClose]); const findContainer = useCallback( - () => document.getElementsByClassName('modal-root__container')[0], + () => + document.getElementsByClassName( + 'modal-root__container', + )[0] as HTMLDivElement, [], ); @@ -113,7 +116,7 @@ export const BoostModal: React.FC<{
- } {errorType === 'error' && } - +
diff --git a/app/javascript/mastodon/features/ui/components/columns_area.jsx b/app/javascript/mastodon/features/ui/components/columns_area.jsx index 77b95d526a840e..753f7e9ac38efe 100644 --- a/app/javascript/mastodon/features/ui/components/columns_area.jsx +++ b/app/javascript/mastodon/features/ui/components/columns_area.jsx @@ -5,7 +5,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { scrollRight } from '../../../scroll'; -import BundleContainer from '../containers/bundle_container'; import { Compose, Notifications, @@ -21,6 +20,7 @@ import { } from '../util/async-components'; import { useColumnsContext } from '../util/columns_context'; +import Bundle from './bundle'; import BundleColumnError from './bundle_column_error'; import { ColumnLoading } from './column_loading'; import { ComposePanel, RedirectToMobileComposeIfNeeded } from './compose_panel'; @@ -145,9 +145,9 @@ export default class ColumnsArea extends ImmutablePureComponent { const other = params && params.other ? params.other : {}; return ( - + {SpecificComponent => } - + ); })} diff --git a/app/javascript/mastodon/features/ui/components/compare_history_modal.jsx b/app/javascript/mastodon/features/ui/components/compare_history_modal.jsx index 4227c741317ffd..ae4c4ed4f7a38b 100644 --- a/app/javascript/mastodon/features/ui/components/compare_history_modal.jsx +++ b/app/javascript/mastodon/features/ui/components/compare_history_modal.jsx @@ -14,7 +14,8 @@ import { IconButton } from 'mastodon/components/icon_button'; import InlineAccount from 'mastodon/components/inline_account'; import MediaAttachments from 'mastodon/components/media_attachments'; import { RelativeTimestamp } from 'mastodon/components/relative_timestamp'; -import emojify from 'mastodon/features/emoji/emoji'; +import { EmojiHTML } from '@/mastodon/components/emoji/html'; +import { CustomEmojiProvider } from '@/mastodon/components/emoji/context'; const mapStateToProps = (state, { statusId }) => ({ language: state.getIn(['statuses', statusId, 'language']), @@ -46,13 +47,8 @@ class CompareHistoryModal extends PureComponent { const { index, versions, language, onClose } = this.props; const currentVersion = versions.get(index); - const emojiMap = currentVersion.get('emojis').reduce((obj, emoji) => { - obj[`:${emoji.get('shortcode')}:`] = emoji.toJS(); - return obj; - }, {}); - - const content = { __html: emojify(currentVersion.get('content'), emojiMap) }; - const spoilerContent = { __html: emojify(escapeTextContentForBrowser(currentVersion.get('spoiler_text')), emojiMap) }; + const content = currentVersion.get('content'); + const spoilerContent = escapeTextContentForBrowser(currentVersion.get('spoiler_text')); const formattedDate = ; const formattedName = ; @@ -65,43 +61,52 @@ class CompareHistoryModal extends PureComponent { return (
-
- - {label} -
- -
-
- {currentVersion.get('spoiler_text').length > 0 && ( - <> -
-
- - )} - -
- - {!!currentVersion.get('poll') && ( -
-
    - {currentVersion.getIn(['poll', 'options']).map(option => ( -
  • - - - -
  • - ))} -
-
- )} - - + +
+ + {label} +
+ +
+
+ {currentVersion.get('spoiler_text').length > 0 && ( + <> + +
+ + )} + + + + {!!currentVersion.get('poll') && ( +
+
    + {currentVersion.getIn(['poll', 'options']).map(option => ( +
  • + +
  • + ))} +
+
+ )} + + +
-
+
); } diff --git a/app/javascript/mastodon/features/ui/components/confirmation_modals/confirmation_modal.tsx b/app/javascript/mastodon/features/ui/components/confirmation_modals/confirmation_modal.tsx index 19ffe2bae52f1d..04831ee1dd1c08 100644 --- a/app/javascript/mastodon/features/ui/components/confirmation_modals/confirmation_modal.tsx +++ b/app/javascript/mastodon/features/ui/components/confirmation_modals/confirmation_modal.tsx @@ -11,13 +11,14 @@ export interface BaseConfirmationModalProps { export const ConfirmationModal: React.FC< { title: React.ReactNode; - message: React.ReactNode; + message?: React.ReactNode; confirm: React.ReactNode; cancel?: React.ReactNode; secondary?: React.ReactNode; onSecondary?: () => void; onConfirm: () => void; closeWhenConfirm?: boolean; + extraContent?: React.ReactNode; } & BaseConfirmationModalProps > = ({ title, @@ -29,6 +30,7 @@ export const ConfirmationModal: React.FC< secondary, onSecondary, closeWhenConfirm = true, + extraContent, }) => { const handleClick = useCallback(() => { if (closeWhenConfirm) { @@ -48,13 +50,15 @@ export const ConfirmationModal: React.FC<

{title}

-

{message}

+ {message &&

{message}

} + + {extraContent}
- diff --git a/app/javascript/mastodon/features/ui/components/confirmation_modals/index.ts b/app/javascript/mastodon/features/ui/components/confirmation_modals/index.ts index 63c93b54b65cc3..9aff30eeac16a9 100644 --- a/app/javascript/mastodon/features/ui/components/confirmation_modals/index.ts +++ b/app/javascript/mastodon/features/ui/components/confirmation_modals/index.ts @@ -5,7 +5,9 @@ export { ConfirmReplyModal, ConfirmEditStatusModal, } from './discard_draft_confirmation'; +export { ConfirmWithdrawRequestModal } from './withdraw_follow_request'; export { ConfirmUnfollowModal } from './unfollow'; +export { ConfirmUnblockModal } from './unblock'; export { ConfirmClearNotificationsModal } from './clear_notifications'; export { ConfirmLogOutModal } from './log_out'; export { ConfirmFollowToListModal } from './follow_to_list'; diff --git a/app/javascript/mastodon/features/ui/components/confirmation_modals/private_quote_notify.tsx b/app/javascript/mastodon/features/ui/components/confirmation_modals/private_quote_notify.tsx new file mode 100644 index 00000000000000..ef917a10270fe2 --- /dev/null +++ b/app/javascript/mastodon/features/ui/components/confirmation_modals/private_quote_notify.tsx @@ -0,0 +1,88 @@ +import { forwardRef, useCallback, useState } from 'react'; + +import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; + +import { submitCompose } from '@/mastodon/actions/compose'; +import { changeSetting } from '@/mastodon/actions/settings'; +import { CheckBox } from '@/mastodon/components/check_box'; +import { useAppDispatch } from '@/mastodon/store'; + +import { ConfirmationModal } from './confirmation_modal'; +import type { BaseConfirmationModalProps } from './confirmation_modal'; +import classes from './styles.module.css'; + +export const PRIVATE_QUOTE_MODAL_ID = 'quote/private_notify'; + +const messages = defineMessages({ + title: { + id: 'confirmations.private_quote_notify.title', + defaultMessage: 'Share with followers and mentioned users?', + }, + message: { + id: 'confirmations.private_quote_notify.message', + defaultMessage: + 'The person you are quoting and other mentions ' + + "will be notified and will be able to view your post, even if they're not following you.", + }, + confirm: { + id: 'confirmations.private_quote_notify.confirm', + defaultMessage: 'Publish post', + }, + cancel: { + id: 'confirmations.private_quote_notify.cancel', + defaultMessage: 'Back to editing', + }, +}); + +export const PrivateQuoteNotify = forwardRef< + HTMLDivElement, + BaseConfirmationModalProps +>( + ( + { onClose }, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _ref, + ) => { + const intl = useIntl(); + + const [dismiss, setDismissed] = useState(false); + const handleDismissToggle = useCallback(() => { + setDismissed((prev) => !prev); + }, []); + + const dispatch = useAppDispatch(); + const handleConfirm = useCallback(() => { + dispatch(submitCompose()); + if (dismiss) { + dispatch( + changeSetting(['dismissed_banners', PRIVATE_QUOTE_MODAL_ID], true), + ); + } + }, [dismiss, dispatch]); + + return ( + + {' '} + + + } + /> + ); + }, +); +PrivateQuoteNotify.displayName = 'PrivateQuoteNotify'; diff --git a/app/javascript/mastodon/features/ui/components/confirmation_modals/styles.module.css b/app/javascript/mastodon/features/ui/components/confirmation_modals/styles.module.css new file mode 100644 index 00000000000000..f685c4525f1ecd --- /dev/null +++ b/app/javascript/mastodon/features/ui/components/confirmation_modals/styles.module.css @@ -0,0 +1,7 @@ +.checkbox_wrapper { + display: flex; + align-items: center; + gap: 0.5rem; + margin: 1rem 0; + cursor: pointer; +} diff --git a/app/javascript/mastodon/features/ui/components/confirmation_modals/unblock.tsx b/app/javascript/mastodon/features/ui/components/confirmation_modals/unblock.tsx new file mode 100644 index 00000000000000..e2154ef48df009 --- /dev/null +++ b/app/javascript/mastodon/features/ui/components/confirmation_modals/unblock.tsx @@ -0,0 +1,45 @@ +import { useCallback } from 'react'; + +import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; + +import { unblockAccount } from 'mastodon/actions/accounts'; +import type { Account } from 'mastodon/models/account'; +import { useAppDispatch } from 'mastodon/store'; + +import type { BaseConfirmationModalProps } from './confirmation_modal'; +import { ConfirmationModal } from './confirmation_modal'; + +const messages = defineMessages({ + unblockConfirm: { + id: 'confirmations.unblock.confirm', + defaultMessage: 'Unblock', + }, +}); + +export const ConfirmUnblockModal: React.FC< + { + account: Account; + } & BaseConfirmationModalProps +> = ({ account, onClose }) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + + const onConfirm = useCallback(() => { + dispatch(unblockAccount(account.id)); + }, [dispatch, account.id]); + + return ( + + } + confirm={intl.formatMessage(messages.unblockConfirm)} + onConfirm={onConfirm} + onClose={onClose} + /> + ); +}; diff --git a/app/javascript/mastodon/features/ui/components/confirmation_modals/unfollow.tsx b/app/javascript/mastodon/features/ui/components/confirmation_modals/unfollow.tsx index 58e39da07bf95a..45b8d458b10597 100644 --- a/app/javascript/mastodon/features/ui/components/confirmation_modals/unfollow.tsx +++ b/app/javascript/mastodon/features/ui/components/confirmation_modals/unfollow.tsx @@ -10,10 +10,6 @@ import type { BaseConfirmationModalProps } from './confirmation_modal'; import { ConfirmationModal } from './confirmation_modal'; const messages = defineMessages({ - unfollowTitle: { - id: 'confirmations.unfollow.title', - defaultMessage: 'Unfollow user?', - }, unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow', @@ -34,12 +30,11 @@ export const ConfirmUnfollowModal: React.FC< return ( @{account.acct} }} + id='confirmations.unfollow.title' + defaultMessage='Unfollow {name}?' + values={{ name: `@${account.acct}` }} /> } confirm={intl.formatMessage(messages.unfollowConfirm)} diff --git a/app/javascript/mastodon/features/ui/components/confirmation_modals/withdraw_follow_request.tsx b/app/javascript/mastodon/features/ui/components/confirmation_modals/withdraw_follow_request.tsx new file mode 100644 index 00000000000000..a0bd236637439d --- /dev/null +++ b/app/javascript/mastodon/features/ui/components/confirmation_modals/withdraw_follow_request.tsx @@ -0,0 +1,45 @@ +import { useCallback } from 'react'; + +import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; + +import { unfollowAccount } from 'mastodon/actions/accounts'; +import type { Account } from 'mastodon/models/account'; +import { useAppDispatch } from 'mastodon/store'; + +import type { BaseConfirmationModalProps } from './confirmation_modal'; +import { ConfirmationModal } from './confirmation_modal'; + +const messages = defineMessages({ + withdrawConfirm: { + id: 'confirmations.withdraw_request.confirm', + defaultMessage: 'Withdraw request', + }, +}); + +export const ConfirmWithdrawRequestModal: React.FC< + { + account: Account; + } & BaseConfirmationModalProps +> = ({ account, onClose }) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + + const onConfirm = useCallback(() => { + dispatch(unfollowAccount(account.id)); + }, [dispatch, account.id]); + + return ( + + } + confirm={intl.formatMessage(messages.withdrawConfirm)} + onConfirm={onConfirm} + onClose={onClose} + /> + ); +}; diff --git a/app/javascript/mastodon/features/ui/components/domain_block_modal.tsx b/app/javascript/mastodon/features/ui/components/domain_block_modal.tsx index ec0899114415a3..eb2ccbb0352ad2 100644 --- a/app/javascript/mastodon/features/ui/components/domain_block_modal.tsx +++ b/app/javascript/mastodon/features/ui/components/domain_block_modal.tsx @@ -53,8 +53,6 @@ export const DomainBlockModal: React.FC<{ }, [dispatch]); useEffect(() => { - setLoading(true); - apiRequest('GET', 'v1/domain_blocks/preview', { params: { domain }, timeout: 5000, @@ -68,7 +66,7 @@ export const DomainBlockModal: React.FC<{ setPreview('error'); setLoading(false); }); - }, [setPreview, setLoading, domain]); + }, [domain]); return (
@@ -196,7 +194,7 @@ export const DomainBlockModal: React.FC<{
- ; - const rightNav = media.size > 1 && ; - - const content = media.map((image, idx) => { - const width = image.getIn(['meta', 'original', 'width']) || null; - const height = image.getIn(['meta', 'original', 'height']) || null; - const description = image.getIn(['translation', 'description']) || image.get('description'); - - if (image.get('type') === 'image') { - return ( - - ); - } else if (image.get('type') === 'video') { - const { currentTime, autoPlay, volume } = this.props; - - return ( -
+ + {isQuotePost && visibility === 'direct' && ( +
+ + +
+ )}
ist.", + "domain_pill.who_they_are": "Adressen teilen mit, wer jemand ist und wo sich jemand im Fediverse aufhält. Daher kannst du mit Leuten im gesamten Social Web interagieren, wenn es eine durch ist.", "domain_pill.who_you_are": "Deine Adresse teilt mit, wer du bist und wo du dich aufhältst. Daher können andere Leute im gesamten Social Web mit dir interagieren, wenn es eine durch ist.", "domain_pill.your_handle": "Deine Adresse:", "domain_pill.your_server": "Deine digitale Heimat. Hier „leben“ alle Beiträge von dir. Falls es dir hier nicht gefällt, kannst du jederzeit den Server wechseln und ebenso deine Follower übertragen.", "domain_pill.your_username": "Deine eindeutige Identität auf diesem Server. Es ist möglich, Profile mit dem gleichen Profilnamen auf verschiedenen Servern zu finden.", "dropdown.empty": "Option auswählen", - "embed.instructions": "Du kannst diesen Beitrag auf deiner Website einbetten, indem du den nachfolgenden Code kopierst.", + "embed.instructions": "Du kannst diesen Beitrag außerhalb des Fediverse (z. B. in deine Website) einbetten, indem du diesen Code kopierst und dort einfügst.", "embed.preview": "Vorschau:", "emoji_button.activity": "Aktivitäten", "emoji_button.clear": "Leeren", "emoji_button.custom": "Spezielle Emojis dieses Servers", "emoji_button.flags": "Flaggen", "emoji_button.food": "Essen & Trinken", - "emoji_button.label": "Emoji einfügen", + "emoji_button.label": "Emoji hinzufügen", "emoji_button.nature": "Natur", "emoji_button.not_found": "Keine passenden Emojis gefunden", "emoji_button.objects": "Gegenstände", @@ -318,26 +363,28 @@ "empty_column.account_featured.other": "{acct} hat bisher noch nichts vorgestellt. Wusstest du, dass du deine häufig verwendeten Hashtags und sogar Profile von Freund*innen vorstellen kannst?", "empty_column.account_featured_other.unknown": "Dieses Profil hat bisher noch nichts vorgestellt.", "empty_column.account_hides_collections": "Das Konto hat sich dazu entschieden, diese Information nicht zu veröffentlichen", - "empty_column.account_suspended": "Konto gesperrt", + "empty_column.account_suspended": "Konto dauerhaft gesperrt", "empty_column.account_timeline": "Keine Beiträge vorhanden!", "empty_column.account_unavailable": "Profil nicht verfügbar", "empty_column.blocks": "Du hast bisher keine Profile blockiert.", "empty_column.bookmarked_statuses": "Du hast bisher keine Beiträge als Lesezeichen abgelegt. Sobald du einen Beitrag als Lesezeichen speicherst, wird er hier erscheinen.", "empty_column.community": "Die lokale Timeline ist leer. Schreibe einen öffentlichen Beitrag, um den Stein ins Rollen zu bringen!", "empty_column.direct": "Du hast noch keine privaten Erwähnungen. Sobald du eine sendest oder erhältst, wird sie hier erscheinen.", - "empty_column.domain_blocks": "Du hast noch keine Domains blockiert.", - "empty_column.explore_statuses": "Momentan ist nichts im Trend. Schau später wieder vorbei!", + "empty_column.disabled_feed": "Diesen Feed haben deine Server-Administrator*innen deaktiviert.", + "empty_column.domain_blocks": "Du hast bisher keine Domains blockiert.", + "empty_column.explore_statuses": "Momentan trendet nichts. Schau später wieder vorbei!", "empty_column.favourited_statuses": "Du hast noch keine Beiträge favorisiert. Sobald du einen favorisierst, wird er hier erscheinen.", "empty_column.favourites": "Diesen Beitrag hat bisher noch niemand favorisiert. Sobald es jemand tut, wird das Profil hier erscheinen.", "empty_column.follow_requests": "Es liegen derzeit keine Follower-Anfragen vor. Sobald du eine erhältst, wird sie hier erscheinen.", - "empty_column.followed_tags": "Du folgst noch keinen Hashtags. Wenn du dies tust, werden sie hier erscheinen.", + "empty_column.followed_tags": "Du folgst noch keinen Hashtags. Sobald du Hashtags abonniert hast, werden sie hier angezeigt.", "empty_column.hashtag": "Unter diesem Hashtag gibt es noch nichts.", "empty_column.home": "Die Timeline deiner Startseite ist leer! Folge mehr Leuten, um sie zu füllen.", "empty_column.list": "Diese Liste ist derzeit leer. Wenn Konten auf dieser Liste neue Beiträge veröffentlichen, werden sie hier erscheinen.", "empty_column.mutes": "Du hast keine Profile stummgeschaltet.", "empty_column.notification_requests": "Alles klar! Hier gibt es nichts. Wenn Sie neue Mitteilungen erhalten, werden diese entsprechend Ihren Einstellungen hier angezeigt.", "empty_column.notifications": "Du hast noch keine Benachrichtigungen. Sobald andere Personen mit dir interagieren, wirst du hier darüber informiert.", - "empty_column.public": "Hier ist nichts zu sehen! Schreibe etwas öffentlich oder folge Profilen von anderen Servern, um die Timeline aufzufüllen", + "empty_column.public": "Hier ist nichts zu sehen! Schreibe einen öffentlichen Beitrag oder folge Profilen von anderen Servern im Fediverse, um die Timeline zu füllen", + "error.no_hashtag_feed_access": "Registriere dich oder melde dich an, um den Hashtag anzusehen und ihm zu folgen.", "error.unexpected_crash.explanation": "Wegen eines Fehlers in unserem Code oder aufgrund einer Browser-Inkompatibilität kann diese Seite nicht korrekt angezeigt werden.", "error.unexpected_crash.explanation_addons": "Diese Seite konnte nicht korrekt angezeigt werden. Dieser Fehler wird wahrscheinlich durch ein Browser-Add-on oder automatische Übersetzungswerkzeuge verursacht.", "error.unexpected_crash.next_steps": "Versuche, die Seite neu zu laden. Wenn das nicht helfen sollte, kannst du das Webinterface von Mastodon vermutlich über einen anderen Browser erreichen – oder du verwendest eine mobile (native) App.", @@ -345,15 +392,13 @@ "errors.unexpected_crash.copy_stacktrace": "Fehlerdiagnose in die Zwischenablage kopieren", "errors.unexpected_crash.report_issue": "Fehler melden", "explore.suggested_follows": "Profile", - "explore.title": "Angesagt", - "explore.trending_links": "Neuigkeiten", + "explore.title": "Im Trend", + "explore.trending_links": "Artikel", "explore.trending_statuses": "Beiträge", "explore.trending_tags": "Hashtags", + "featured_carousel.current": "Beitrag {current, number}/{max, number}", "featured_carousel.header": "{count, plural, one {Angehefteter Beitrag} other {Angeheftete Beiträge}}", - "featured_carousel.next": "Vor", - "featured_carousel.post": "Beitrag", - "featured_carousel.previous": "Zurück", - "featured_carousel.slide": "{index} von {total}", + "featured_carousel.slide": "Beitrag {current, number} von {max, number}", "filter_modal.added.context_mismatch_explanation": "Diese Filterkategorie gilt nicht für den Kontext, in welchem du auf diesen Beitrag zugegriffen hast. Wenn der Beitrag auch in diesem Kontext gefiltert werden soll, musst du den Filter bearbeiten.", "filter_modal.added.context_mismatch_title": "Kontext stimmt nicht überein!", "filter_modal.added.expired_explanation": "Diese Filterkategorie ist abgelaufen. Du musst das Ablaufdatum für diese Kategorie ändern.", @@ -370,7 +415,7 @@ "filter_modal.select_filter.subtitle": "Einem vorhandenen Filter hinzufügen oder einen neuen erstellen", "filter_modal.select_filter.title": "Diesen Beitrag filtern", "filter_modal.title.status": "Beitrag per Filter ausblenden", - "filter_warning.matches_filter": "Ausgeblendet wegen des Filters „{title}“", + "filter_warning.matches_filter": "Ausgeblendet wegen deines Filters „{title}“", "filtered_notifications_banner.pending_requests": "Von {count, plural, =0 {keinem Profil, das dir möglicherweise bekannt ist} one {einem Profil, das dir möglicherweise bekannt ist} other {# Profilen, die dir möglicherweise bekannt sind}}", "filtered_notifications_banner.title": "Gefilterte Benachrichtigungen", "firehose.all": "Alle Server", @@ -391,14 +436,17 @@ "follow_suggestions.personalized_suggestion": "Persönliche Empfehlung", "follow_suggestions.popular_suggestion": "Beliebte Empfehlung", "follow_suggestions.popular_suggestion_longer": "Beliebt auf {domain}", - "follow_suggestions.similar_to_recently_followed_longer": "Ähnlich zu Profilen, denen du seit kurzem folgst", + "follow_suggestions.similar_to_recently_followed_longer": "Ähnelt deinen kürzlich gefolgten Profilen", "follow_suggestions.view_all": "Alle anzeigen", - "follow_suggestions.who_to_follow": "Empfohlene Profile", - "followed_tags": "Gefolgte Hashtags", + "follow_suggestions.who_to_follow": "Wem folgen?", + "followed_tags": "Abonnierte Hashtags", "footer.about": "Über", + "footer.about_mastodon": "Über Mastodon", + "footer.about_server": "Über {domain}", + "footer.about_this_server": "Über", "footer.directory": "Profilverzeichnis", "footer.get_app": "App herunterladen", - "footer.keyboard_shortcuts": "Tastenkombinationen", + "footer.keyboard_shortcuts": "Tastaturkürzel", "footer.privacy_policy": "Datenschutzerklärung", "footer.source_code": "Quellcode anzeigen", "footer.status": "Status", @@ -421,10 +469,10 @@ "hashtag.counter_by_uses": "{count, plural, one {{counter} Beitrag} other {{counter} Beiträge}}", "hashtag.counter_by_uses_today": "{count, plural, one {{counter} Beitrag} other {{counter} Beiträge}} heute", "hashtag.feature": "Im Profil vorstellen", - "hashtag.follow": "Hashtag folgen", + "hashtag.follow": "Abonnieren", "hashtag.mute": "#{hashtag} stummschalten", "hashtag.unfeature": "Im Profil nicht mehr vorstellen", - "hashtag.unfollow": "Hashtag entfolgen", + "hashtag.unfollow": "Abbestellen", "hashtags.and_other": "… und {count, plural, one{# weiterer} other {# weitere}}", "hints.profiles.followers_may_be_missing": "Möglicherweise werden für dieses Profil nicht alle Follower angezeigt.", "hints.profiles.follows_may_be_missing": "Möglicherweise werden für dieses Profil nicht alle gefolgten Profile angezeigt.", @@ -434,7 +482,7 @@ "hints.profiles.see_more_posts": "Weitere Beiträge auf {domain} ansehen", "home.column_settings.show_quotes": "Zitierte Beiträge anzeigen", "home.column_settings.show_reblogs": "Geteilte Beiträge anzeigen", - "home.column_settings.show_replies": "Antworten anzeigen", + "home.column_settings.show_replies": "Antworten zu Beiträgen anzeigen", "home.hide_announcements": "Ankündigungen ausblenden", "home.pending_critical_update.body": "Bitte aktualisiere deinen Mastodon-Server so schnell wie möglich!", "home.pending_critical_update.link": "Updates ansehen", @@ -452,54 +500,55 @@ "ignore_notifications_modal.not_following_title": "Benachrichtigungen von Profilen ignorieren, denen du nicht folgst?", "ignore_notifications_modal.private_mentions_title": "Benachrichtigungen von unerwünschten privaten Erwähnungen ignorieren?", "info_button.label": "Hilfe", - "info_button.what_is_alt_text": "

Was ist Alt-Text?

Alt-Text bietet Bildbeschreibungen für Personen mit einer Sehschwäche, einer schlechten Internetverbindung und für alle, die zusätzlichen Kontext möchten.

Du kannst die Zugänglichkeit und die Verständlichkeit für alle verbessern, indem du eine klare, genaue und objektive Bildbeschreibung hinzufügst.

  • Erfasse wichtige Elemente
  • Fasse Text in Bildern zusammen
  • Verwende einen korrekten Satzbau
  • Vermeide unwichtige Informationen
  • Konzentriere dich bei komplexen Darstellungen (z. B. Diagramme oder Karten) auf Trends und wichtige Erkenntnisse
", + "info_button.what_is_alt_text": "

Was ist Alt-Text?

Der Alt-Text bietet Bildbeschreibungen für blinde und sehbehinderte Menschen, aber auch für alle mit einer schlechten Internetverbindung und wer einen zusätzlichen Kontext möchten.

Du kannst die Barrierefreiheit und Verständlichkeit für alle verbessern, indem du eine klare, genaue und objektive Bildbeschreibung erstellst.

  • Erfasse wichtige Elemente
  • Fasse Texte bildlich zusammen
  • Verwende einen korrekten Satzbau
  • Vermeide unwichtige und überflüssige Informationen
  • Konzentriere dich bei komplexen Darstellungen (z. B. bei Diagrammen oder Karten) auf Veränderungen und Schlüsselwörter
", "interaction_modal.action": "Melde dich auf deinem Mastodon-Server an, damit du mit dem Beitrag von {name} interagieren kannst.", "interaction_modal.go": "Los", "interaction_modal.no_account_yet": "Du hast noch kein Konto?", - "interaction_modal.on_another_server": "Auf einem anderen Server", + "interaction_modal.on_another_server": "Auf anderem Server", "interaction_modal.on_this_server": "Auf diesem Server", "interaction_modal.title": "Melde dich an, um fortzufahren", - "interaction_modal.username_prompt": "z. B. {example}", + "interaction_modal.username_prompt": "Z. B. {example}", "intervals.full.days": "{number, plural, one {# Tag} other {# Tage}}", "intervals.full.hours": "{number, plural, one {# Stunde} other {# Stunden}}", "intervals.full.minutes": "{number, plural, one {# Minute} other {# Minuten}}", "keyboard_shortcuts.back": "Zurücknavigieren", - "keyboard_shortcuts.blocked": "Liste blockierter Profile öffnen", + "keyboard_shortcuts.blocked": "Blockierte Profile öffnen", "keyboard_shortcuts.boost": "Beitrag teilen", - "keyboard_shortcuts.column": "Auf die aktuelle Spalte fokussieren", + "keyboard_shortcuts.column": "Aktuelle Spalte fokussieren", "keyboard_shortcuts.compose": "Eingabefeld fokussieren", "keyboard_shortcuts.description": "Beschreibung", "keyboard_shortcuts.direct": "Private Erwähnungen öffnen", - "keyboard_shortcuts.down": "Ansicht nach unten bewegen", + "keyboard_shortcuts.down": "Auswahl nach unten bewegen", "keyboard_shortcuts.enter": "Beitrag öffnen", "keyboard_shortcuts.favourite": "Beitrag favorisieren", "keyboard_shortcuts.favourites": "Favoriten öffnen", "keyboard_shortcuts.federated": "Föderierte Timeline öffnen", - "keyboard_shortcuts.heading": "Tastenkombinationen", + "keyboard_shortcuts.heading": "Tastenkürzel", "keyboard_shortcuts.home": "Startseite öffnen", "keyboard_shortcuts.hotkey": "Tastenkürzel", - "keyboard_shortcuts.legend": "Tastenkombinationen anzeigen", + "keyboard_shortcuts.legend": "Tastenkürzel anzeigen (diese Seite)", "keyboard_shortcuts.load_more": "Schaltfläche „Mehr laden“ fokussieren", "keyboard_shortcuts.local": "Lokale Timeline öffnen", "keyboard_shortcuts.mention": "Profil erwähnen", - "keyboard_shortcuts.muted": "Liste stummgeschalteter Profile öffnen", - "keyboard_shortcuts.my_profile": "Eigenes Profil aufrufen", - "keyboard_shortcuts.notifications": "Benachrichtigungen aufrufen", - "keyboard_shortcuts.open_media": "Medieninhalt öffnen", + "keyboard_shortcuts.muted": "Stummgeschaltete Profile öffnen", + "keyboard_shortcuts.my_profile": "Eigenes Profil öffnen", + "keyboard_shortcuts.notifications": "Benachrichtigungen öffnen", + "keyboard_shortcuts.open_media": "Medien öffnen", "keyboard_shortcuts.pinned": "Liste angehefteter Beiträge öffnen", "keyboard_shortcuts.profile": "Profil aufrufen", "keyboard_shortcuts.quote": "Beitrag zitieren", - "keyboard_shortcuts.reply": "Auf Beitrag antworten", - "keyboard_shortcuts.requests": "Liste der Follower-Anfragen aufrufen", - "keyboard_shortcuts.search": "Suchleiste fokussieren", - "keyboard_shortcuts.spoilers": "Feld für Inhaltswarnung anzeigen/ausblenden", + "keyboard_shortcuts.reply": "Beitrag beantworten", + "keyboard_shortcuts.requests": "Follower-Anfragen aufrufen", + "keyboard_shortcuts.search": "Eingabefeld / Suche fokussieren", + "keyboard_shortcuts.spoilers": "Feld für Inhaltswarnung anzeigen / ausblenden", "keyboard_shortcuts.start": "„Auf gehts!“ öffnen", - "keyboard_shortcuts.toggle_hidden": "Beitragstext hinter der Inhaltswarnung anzeigen/ausblenden", - "keyboard_shortcuts.toggle_sensitivity": "Medien anzeigen/ausblenden", + "keyboard_shortcuts.toggle_hidden": "Beitrag hinter Inhaltswarnung anzeigen / ausblenden", + "keyboard_shortcuts.toggle_sensitivity": "Medien anzeigen / ausblenden", "keyboard_shortcuts.toot": "Neuen Beitrag erstellen", + "keyboard_shortcuts.top": "Zum Listenanfang springen", "keyboard_shortcuts.translate": "Beitrag übersetzen", - "keyboard_shortcuts.unfocus": "Eingabefeld/Suche nicht mehr fokussieren", - "keyboard_shortcuts.up": "Ansicht nach oben bewegen", + "keyboard_shortcuts.unfocus": "Eingabefeld / Suche nicht mehr fokussieren", + "keyboard_shortcuts.up": "Auswahl nach oben bewegen", "learn_more_link.got_it": "Verstanden", "learn_more_link.learn_more": "Mehr erfahren", "lightbox.close": "Schließen", @@ -522,7 +571,7 @@ "lists.done": "Fertig", "lists.edit": "Liste bearbeiten", "lists.exclusive": "Mitglieder auf der Startseite ausblenden", - "lists.exclusive_hint": "Profile, die sich auf dieser Liste befinden, werden nicht auf deiner Startseite angezeigt, damit deren Beiträge nicht doppelt erscheinen.", + "lists.exclusive_hint": "Profile, die sich auf dieser Liste befinden, werden nicht im Feed deiner Startseite angezeigt, damit deren Beiträge nicht doppelt erscheinen.", "lists.find_users_to_add": "Suche nach Profilen, um sie hinzuzufügen", "lists.list_members_count": "{count, plural, one {# Mitglied} other {# Mitglieder}}", "lists.list_name": "Titel der Liste", @@ -531,19 +580,19 @@ "lists.no_members_yet": "Keine Mitglieder vorhanden.", "lists.no_results_found": "Keine Suchergebnisse.", "lists.remove_member": "Entfernen", - "lists.replies_policy.followed": "Alle folgenden Profile", + "lists.replies_policy.followed": "alle folgenden Profile", "lists.replies_policy.list": "Mitglieder der Liste", - "lists.replies_policy.none": "Niemanden", + "lists.replies_policy.none": "niemanden", "lists.save": "Speichern", "lists.search": "Suchen", "lists.show_replies_to": "Antworten von Listenmitgliedern einbeziehen an …", "load_pending": "{count, plural, one {# neuer Beitrag} other {# neue Beiträge}}", - "loading_indicator.label": "Wird geladen …", + "loading_indicator.label": "Lädt …", "media_gallery.hide": "Ausblenden", "moved_to_account_banner.text": "Dein Konto {disabledAccount} ist derzeit deaktiviert, weil du zu {movedToAccount} umgezogen bist.", - "mute_modal.hide_from_notifications": "Benachrichtigungen ausblenden", - "mute_modal.hide_options": "Einstellungen ausblenden", - "mute_modal.indefinite": "Bis ich die Stummschaltung aufhebe", + "mute_modal.hide_from_notifications": "Auch aus den Benachrichtigungen entfernen", + "mute_modal.hide_options": "Optionen ausblenden", + "mute_modal.indefinite": "Dauerhaft, bis ich die Stummschaltung aufhebe", "mute_modal.show_options": "Optionen anzeigen", "mute_modal.they_can_mention_and_follow": "Das Profil wird dich weiterhin erwähnen und dir folgen können, aber du wirst davon nichts sehen.", "mute_modal.they_wont_know": "Das Profil wird nicht erkennen können, dass du es stummgeschaltet hast.", @@ -553,7 +602,7 @@ "navigation_bar.about": "Über", "navigation_bar.account_settings": "Passwort und Sicherheit", "navigation_bar.administration": "Administration", - "navigation_bar.advanced_interface": "Im erweiterten Webinterface öffnen", + "navigation_bar.advanced_interface": "Erweitertes Webinterface öffnen", "navigation_bar.automated_deletion": "Automatisiertes Löschen", "navigation_bar.blocks": "Blockierte Profile", "navigation_bar.bookmarks": "Lesezeichen", @@ -562,24 +611,24 @@ "navigation_bar.favourites": "Favoriten", "navigation_bar.filters": "Stummgeschaltete Wörter", "navigation_bar.follow_requests": "Follower-Anfragen", - "navigation_bar.followed_tags": "Gefolgte Hashtags", - "navigation_bar.follows_and_followers": "Follower und Folge ich", - "navigation_bar.import_export": "Importieren und exportieren", + "navigation_bar.followed_tags": "Abonnierte Hashtags", + "navigation_bar.follows_and_followers": "Follower & Folge ich", + "navigation_bar.import_export": "Importieren & exportieren", "navigation_bar.lists": "Listen", - "navigation_bar.live_feed_local": "Live-Feed (lokal)", - "navigation_bar.live_feed_public": "Live-Feed (öffentlich)", + "navigation_bar.live_feed_local": "Live-Feed (Dieser Server)", + "navigation_bar.live_feed_public": "Live-Feed (Alle Server)", "navigation_bar.logout": "Abmelden", "navigation_bar.moderation": "Moderation", "navigation_bar.more": "Mehr", "navigation_bar.mutes": "Stummgeschaltete Profile", "navigation_bar.opened_in_classic_interface": "Beiträge, Konten und andere bestimmte Seiten werden standardmäßig im klassischen Webinterface geöffnet.", "navigation_bar.preferences": "Einstellungen", - "navigation_bar.privacy_and_reach": "Datenschutz und Reichweite", + "navigation_bar.privacy_and_reach": "Datenschutz & Reichweite", "navigation_bar.search": "Suche", - "navigation_bar.search_trends": "Suche / Angesagt", - "navigation_panel.collapse_followed_tags": "Menü für gefolgte Hashtags schließen", + "navigation_bar.search_trends": "Suche / Trends", + "navigation_panel.collapse_followed_tags": "Menü für abonnierte Hashtags schließen", "navigation_panel.collapse_lists": "Listen-Menü schließen", - "navigation_panel.expand_followed_tags": "Menü für gefolgte Hashtags öffnen", + "navigation_panel.expand_followed_tags": "Menü für abonnierte Hashtags öffnen", "navigation_panel.expand_lists": "Listen-Menü öffnen", "not_signed_in_indicator.not_signed_in": "Du musst dich anmelden, um auf diesen Inhalt zugreifen zu können.", "notification.admin.report": "{name} meldete {target}", @@ -610,7 +659,7 @@ "notification.moderation_warning": "Du wurdest von den Moderator*innen verwarnt", "notification.moderation_warning.action_delete_statuses": "Einige deiner Beiträge sind entfernt worden.", "notification.moderation_warning.action_disable": "Dein Konto wurde deaktiviert.", - "notification.moderation_warning.action_mark_statuses_as_sensitive": "Einige deiner Beiträge wurden mit einer Inhaltswarnung versehen.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Einige deiner Beiträge haben eine Inhaltswarnung erhalten.", "notification.moderation_warning.action_none": "Dein Konto ist von den Moderator*innen verwarnt worden.", "notification.moderation_warning.action_sensitive": "Deine zukünftigen Beiträge werden mit einer Inhaltswarnung versehen.", "notification.moderation_warning.action_silence": "Dein Konto wurde eingeschränkt.", @@ -625,7 +674,7 @@ "notification.relationships_severance_event.domain_block": "Ein Admin von {from} hat {target} blockiert – darunter {followersCount} deiner Follower und {followingCount, plural, one {# Konto, dem} other {# Konten, denen}} du folgst.", "notification.relationships_severance_event.learn_more": "Mehr erfahren", "notification.relationships_severance_event.user_domain_block": "Du hast {target} blockiert – {followersCount} deiner Follower und {followingCount, plural, one {# Konto, dem} other {# Konten, denen}} du folgst, wurden entfernt.", - "notification.status": "{name} postete …", + "notification.status": "{name} veröffentlichte …", "notification.update": "{name} bearbeitete einen Beitrag", "notification_requests.accept": "Akzeptieren", "notification_requests.accept_multiple": "{count, plural, one {# Anfrage akzeptieren …} other {# Anfragen akzeptieren …}}", @@ -661,9 +710,9 @@ "notifications.column_settings.mention": "Erwähnungen:", "notifications.column_settings.poll": "Umfrageergebnisse:", "notifications.column_settings.push": "Push-Benachrichtigungen", - "notifications.column_settings.quote": "Zitate:", + "notifications.column_settings.quote": "Zitierte Beiträge:", "notifications.column_settings.reblog": "Geteilte Beiträge:", - "notifications.column_settings.show": "In dieser Spalte anzeigen", + "notifications.column_settings.show": "Im Feed „Benachrichtigungen“ anzeigen", "notifications.column_settings.sound": "Ton abspielen", "notifications.column_settings.status": "Neue Beiträge:", "notifications.column_settings.unread_notifications.category": "Ungelesene Benachrichtigungen", @@ -672,10 +721,10 @@ "notifications.filter.all": "Alles", "notifications.filter.boosts": "Geteilte Beiträge", "notifications.filter.favourites": "Favoriten", - "notifications.filter.follows": "Folgt", + "notifications.filter.follows": "Neue Follower", "notifications.filter.mentions": "Erwähnungen", "notifications.filter.polls": "Umfrageergebnisse", - "notifications.filter.statuses": "Neue Beiträge von Personen, denen du folgst", + "notifications.filter.statuses": "Neue Beiträge von abonnierten Profilen", "notifications.grant_permission": "Berechtigung erteilen.", "notifications.group": "{count} Benachrichtigungen", "notifications.mark_as_read": "Alle Benachrichtigungen als gelesen markieren", @@ -685,18 +734,18 @@ "notifications.policy.accept": "Akzeptieren", "notifications.policy.accept_hint": "In Benachrichtigungen anzeigen", "notifications.policy.drop": "Ignorieren", - "notifications.policy.drop_hint": "In die Leere senden und nie wieder sehen", + "notifications.policy.drop_hint": "Ins Nirwana befördern und auf Nimmerwiedersehen!", "notifications.policy.filter": "Filtern", - "notifications.policy.filter_hint": "An gefilterte Benachrichtigungen im Posteingang senden", - "notifications.policy.filter_limited_accounts_hint": "Durch Server-Moderator*innen eingeschränkt", - "notifications.policy.filter_limited_accounts_title": "moderierten Konten", - "notifications.policy.filter_new_accounts.hint": "Innerhalb {days, plural, one {des letzten Tages} other {der letzten # Tagen}} erstellt", - "notifications.policy.filter_new_accounts_title": "neuen Konten", - "notifications.policy.filter_not_followers_hint": "Einschließlich Profilen, die dir seit weniger als {days, plural, one {einem Tag} other {# Tagen}} folgen", + "notifications.policy.filter_hint": "Im separaten Feed „Gefilterte Benachrichtigungen“ anzeigen", + "notifications.policy.filter_limited_accounts_hint": "Durch Server-Moderator*innen eingeschränkte Profile", + "notifications.policy.filter_limited_accounts_title": "eingeschränkten Konten", + "notifications.policy.filter_new_accounts.hint": "Konto {days, plural, one {seit gestern} other {in den vergangenen # Tagen}} registriert", + "notifications.policy.filter_new_accounts_title": "neuen Profilen", + "notifications.policy.filter_not_followers_hint": "Einschließlich Profilen, die mir seit weniger als {days, plural, one {einem Tag} other {# Tagen}} folgen", "notifications.policy.filter_not_followers_title": "Profilen, die mir nicht folgen", - "notifications.policy.filter_not_following_hint": "Bis du sie manuell genehmigst", + "notifications.policy.filter_not_following_hint": "… bis ich sie manuell genehmige", "notifications.policy.filter_not_following_title": "Profilen, denen ich nicht folge", - "notifications.policy.filter_private_mentions_hint": "Solange sie keine Antwort auf deine Erwähnung ist oder du dem Profil nicht folgst", + "notifications.policy.filter_private_mentions_hint": "… solange sie keine Antwort auf meine Erwähnungen sind – oder ich den Profilen nicht folge", "notifications.policy.filter_private_mentions_title": "unerwünschten privaten Erwähnungen", "notifications.policy.title": "Benachrichtigungen verwalten von …", "notifications_permission_banner.enable": "Aktiviere Desktop-Benachrichtigungen", @@ -731,22 +780,24 @@ "poll_button.add_poll": "Umfrage erstellen", "poll_button.remove_poll": "Umfrage entfernen", "privacy.change": "Sichtbarkeit anpassen", - "privacy.direct.long": "Alle in diesem Beitrag erwähnten Profile", + "privacy.direct.long": "Nur in diesem Beitrag erwähnte Profile", "privacy.direct.short": "Private Erwähnung", - "privacy.private.long": "Nur deine Follower", + "privacy.private.long": "Nur deine eigenen Follower", "privacy.private.short": "Follower", - "privacy.public.long": "Alle in und außerhalb von Mastodon", + "privacy.public.long": "Alle innerhalb und außerhalb von Mastodon", "privacy.public.short": "Öffentlich", "privacy.quote.anyone": "{visibility} – alle dürfen zitieren", - "privacy.quote.disabled": "{visibility} – niemand darf zitieren", - "privacy.quote.limited": "{visibility} – eingeschränktes Zitieren", + "privacy.quote.disabled": "{visibility} – Zitieren deaktiviert", + "privacy.quote.limited": "{visibility} – nur Follower", "privacy.unlisted.additional": "Das Verhalten ist wie bei „Öffentlich“, jedoch gibt es einige Einschränkungen. Der Beitrag wird nicht in „Live-Feeds“, „Erkunden“, Hashtags oder über die Mastodon-Suchfunktion auffindbar sein – selbst wenn die zugehörige Einstellung aktiviert wurde.", - "privacy.unlisted.long": "Wird nicht in den Suchergebnissen, Trends oder öffentlichen Timelines von Mastodon angezeigt", + "privacy.unlisted.long": "Verborgen vor Suchen, Trends und öffentlichen Timelines", "privacy.unlisted.short": "Öffentlich (still)", "privacy_policy.last_updated": "Stand: {date}", "privacy_policy.title": "Datenschutzerklärung", - "quote_error.poll": "Zitieren ist bei Umfragen nicht gestattet.", - "quote_error.quote": "Es ist jeweils nur ein Zitat zulässig.", + "quote_error.edit": "Beim Bearbeiten eines vorhandenen Beitrags können keine Zitate hinzugefügt werden.", + "quote_error.poll": "Zitieren ist bei Umfragen nicht erlaubt.", + "quote_error.private_mentions": "Zitieren ist bei privaten Erwähnungen nicht erlaubt.", + "quote_error.quote": "Es darf nur ein Beitrag zitiert werden.", "quote_error.unauthorized": "Du bist nicht berechtigt, diesen Beitrag zu zitieren.", "quote_error.upload": "Zitieren ist mit Medien-Anhängen nicht möglich.", "recommended": "Empfohlen", @@ -756,7 +807,7 @@ "relative_time.days": "{number} T.", "relative_time.full.days": "vor {number, plural, one {# Tag} other {# Tagen}}", "relative_time.full.hours": "vor {number, plural, one {# Stunde} other {# Stunden}}", - "relative_time.full.just_now": "gerade eben", + "relative_time.full.just_now": "soeben", "relative_time.full.minutes": "vor {number, plural, one {# Minute} other {# Minuten}}", "relative_time.full.seconds": "vor {number, plural, one {1 Sekunde} other {# Sekunden}}", "relative_time.hours": "{number} Std.", @@ -766,13 +817,13 @@ "relative_time.today": "heute", "remove_quote_hint.button_label": "Verstanden", "remove_quote_hint.message": "Klicke dafür im Beitrag auf „{icon} Mehr“.", - "remove_quote_hint.title": "Möchtest du aus dem zitierten Beitrag entfernt werden?", + "remove_quote_hint.title": "Deinen zitierten Beitrag aus diesem Beitrag entfernen?", "reply_indicator.attachments": "{count, plural, one {# Anhang} other {# Anhänge}}", "reply_indicator.cancel": "Abbrechen", "reply_indicator.poll": "Umfrage", "report.block": "Blockieren", "report.block_explanation": "Du wirst keine Beiträge mehr von diesem Konto sehen. Das blockierte Konto wird deine Beiträge nicht mehr sehen oder dir folgen können. Die Person könnte mitbekommen, dass du sie blockiert hast.", - "report.categories.legal": "Rechtlich", + "report.categories.legal": "Rechtliches", "report.categories.other": "Andere", "report.categories.spam": "Spam", "report.categories.violation": "Der Inhalt verletzt eine oder mehrere Serverregeln", @@ -781,9 +832,9 @@ "report.category.title_account": "Profil", "report.category.title_status": "Beitrag", "report.close": "Fertig", - "report.comment.title": "Gibt es etwas anderes, was wir wissen sollten?", - "report.forward": "Meldung zusätzlich an {target} weiterleiten", - "report.forward_hint": "Dieses Konto gehört zu einem anderen Server. Soll eine anonymisierte Kopie der Meldung auch dorthin gesendet werden?", + "report.comment.title": "Gibt es noch etwas, das wir wissen sollten?", + "report.forward": "Meldung auch an den externen Server {target} weiterleiten", + "report.forward_hint": "Das gemeldete Konto befindet sich auf einem anderen Server. Soll zusätzlich eine anonymisierte Kopie deiner Meldung an diesen Server geschickt werden?", "report.mute": "Stummschalten", "report.mute_explanation": "Du wirst keine Beiträge mehr von diesem Konto sehen. Das stummgeschaltete Konto wird dir weiterhin folgen und deine Beiträge sehen können. Die Person wird nicht mitbekommen, dass du sie stummgeschaltet hast.", "report.next": "Weiter", @@ -799,9 +850,9 @@ "report.reasons.violation": "Das verstößt gegen Serverregeln", "report.reasons.violation_description": "Du bist dir sicher, dass eine bestimmte Regel gebrochen wurde", "report.rules.subtitle": "Wähle alle zutreffenden Inhalte aus", - "report.rules.title": "Welche Regeln werden verletzt?", + "report.rules.title": "Gegen welche Regeln wurde verstoßen?", "report.statuses.subtitle": "Wähle alle zutreffenden Inhalte aus", - "report.statuses.title": "Gibt es Beiträge, die diese Meldung bekräftigen?", + "report.statuses.title": "Gibt es Beiträge, die diese Meldung stützen?", "report.submit": "Senden", "report.target": "{target} melden", "report.thanks.take_action": "Das sind deine Möglichkeiten zu bestimmen, was du auf Mastodon sehen möchtest:", @@ -818,7 +869,7 @@ "report_notification.categories.spam": "Spam", "report_notification.categories.spam_sentence": "Spam", "report_notification.categories.violation": "Regelverstoß", - "report_notification.categories.violation_sentence": "Regelverletzung", + "report_notification.categories.violation_sentence": "Verstoß gegen die Serverregeln", "report_notification.open": "Meldung öffnen", "search.clear": "Suchanfrage löschen", "search.no_recent_searches": "Keine früheren Suchanfragen", @@ -828,7 +879,7 @@ "search.quick_action.go_to_hashtag": "Hashtag {x} aufrufen", "search.quick_action.open_url": "URL in Mastodon öffnen", "search.quick_action.status_search": "Beiträge passend zu {x}", - "search.search_or_paste": "Suchen oder URL einfügen", + "search.search_or_paste": "Suche eingeben oder URL einfügen", "search_popout.full_text_search_disabled_message": "Auf {domain} nicht verfügbar.", "search_popout.full_text_search_logged_out_message": "Nur verfügbar, wenn angemeldet.", "search_popout.language_code": "ISO-Sprachcode", @@ -858,15 +909,19 @@ "status.admin_account": "@{name} moderieren", "status.admin_domain": "{domain} moderieren", "status.admin_status": "Beitrag moderieren", - "status.all_disabled": "Teilen und Zitieren von Beiträgen ist deaktiviert", + "status.all_disabled": "Teilen und Zitieren sind deaktiviert", "status.block": "@{name} blockieren", "status.bookmark": "Lesezeichen setzen", "status.cancel_reblog_private": "Beitrag nicht mehr teilen", - "status.cannot_quote": "Dir ist es nicht gestattet, diesen Beitrag zu zitieren", + "status.cannot_quote": "Diesen Beitrag darfst du nicht zitieren", "status.cannot_reblog": "Dieser Beitrag kann nicht geteilt werden", - "status.contains_quote": "Enthält Zitat", - "status.context.load_new_replies": "Neue Antworten verfügbar", - "status.context.loading": "Weitere Antworten werden abgerufen", + "status.contains_quote": "Enthält zitierten Beitrag", + "status.context.loading": "Weitere Antworten laden", + "status.context.loading_error": "Weitere Antworten konnten nicht geladen werden", + "status.context.loading_success": "Neue Antworten geladen", + "status.context.more_replies_found": "Weitere Antworten verfügbar", + "status.context.retry": "Erneut versuchen", + "status.context.show": "Anzeigen", "status.continued_thread": "Fortgeführter Thread", "status.copy": "Link zum Beitrag kopieren", "status.delete": "Beitrag löschen", @@ -876,10 +931,10 @@ "status.direct_indicator": "Private Erwähnung", "status.edit": "Beitrag bearbeiten", "status.edited": "Zuletzt am {date} bearbeitet", - "status.edited_x_times": "{count, plural, one {{count}-mal} other {{count}-mal}} bearbeitet", + "status.edited_x_times": "{count, plural, one {{count} ×} other {{count} ×}} bearbeitet", "status.embed": "Code zum Einbetten", "status.favourite": "Favorisieren", - "status.favourites": "{count, plural, one {Mal favorisiert} other {Mal favorisiert}}", + "status.favourites_count": "{count, plural, one {{counter} × favorisiert} other {{counter} × favorisiert}}", "status.filter": "Beitrag filtern", "status.history.created": "{name} erstellte {date}", "status.history.edited": "{name} bearbeitete {date}", @@ -894,32 +949,37 @@ "status.open": "Beitrag öffnen", "status.pin": "Im Profil anheften", "status.quote": "Zitieren", - "status.quote.cancel": "Zitat abbrechen", + "status.quote.cancel": "Zitat entfernen", + "status.quote_error.blocked_account_hint.title": "Dieser Beitrag wurde ausgeblendet, weil du @{name} blockiert hast.", + "status.quote_error.blocked_domain_hint.title": "Dieser Beitrag wurde ausgeblendet, weil du {domain} blockiert hast.", "status.quote_error.filtered": "Ausgeblendet wegen eines deiner Filter", "status.quote_error.limited_account_hint.action": "Trotzdem anzeigen", "status.quote_error.limited_account_hint.title": "Dieses Profil wurde von den Moderator*innen von {domain} ausgeblendet.", + "status.quote_error.muted_account_hint.title": "Dieser Beitrag wurde ausgeblendet, weil du @{name} stummgeschaltet hast.", "status.quote_error.not_available": "Beitrag nicht verfügbar", - "status.quote_error.pending_approval": "Beitragsveröffentlichung ausstehend", - "status.quote_error.pending_approval_popout.body": "Auf Mastodon kann festgelegt werden, ob man zitiert werden möchte. Wir warten auf die Genehmigung des ursprünglichen Profils. Bis dahin steht deine Beitragsveröffentlichung noch aus.", + "status.quote_error.pending_approval": "Veröffentlichung ausstehend", + "status.quote_error.pending_approval_popout.body": "Auf Mastodon kannst du selbst bestimmen, ob du von anderen zitiert werden darfst oder nicht – oder nur nach individueller Genehmigung. Wir warten in diesem Fall noch auf die Genehmigung des ursprünglichen Profils. Bis dahin steht die Veröffentlichung deines Beitrags mit dem zitierten Post noch aus.", "status.quote_error.revoked": "Beitrag durch Autor*in entfernt", "status.quote_followers_only": "Nur Follower können diesen Beitrag zitieren", - "status.quote_manual_review": "Zitierte*r überprüft manuell", + "status.quote_manual_review": "Autor*in wird deine Anfrage manuell überprüfen", "status.quote_noun": "Zitat", "status.quote_policy_change": "Ändern, wer zitieren darf", - "status.quote_post_author": "Zitierte einen Beitrag von @{name}", + "status.quote_post_author": "Zitierter Beitrag von @{name}", "status.quote_private": "Private Beiträge können nicht zitiert werden", - "status.quotes": "{count, plural, one {Mal zitiert} other {Mal zitiert}}", "status.quotes.empty": "Diesen Beitrag hat bisher noch niemand zitiert. Sobald es jemand tut, wird das Profil hier erscheinen.", + "status.quotes.local_other_disclaimer": "Durch Autor*in abgelehnte Zitate werden nicht angezeigt.", + "status.quotes.remote_other_disclaimer": "Nur Zitate von {domain} werden hier garantiert angezeigt. Durch Autor*in abgelehnte Zitate werden nicht angezeigt.", + "status.quotes_count": "{count, plural, one {{counter} × zitiert} other {{counter} × zitiert}}", "status.read_more": "Gesamten Beitrag anschauen", "status.reblog": "Teilen", "status.reblog_or_quote": "Teilen oder zitieren", "status.reblog_private": "Erneut mit deinen Followern teilen", "status.reblogged_by": "{name} teilte", - "status.reblogs": "{count, plural, one {Mal geteilt} other {Mal geteilt}}", "status.reblogs.empty": "Diesen Beitrag hat bisher noch niemand geteilt. Sobald es jemand tut, wird das Profil hier erscheinen.", + "status.reblogs_count": "{count, plural, one {{counter} × geteilt} other {{counter} × geteilt}}", "status.redraft": "Löschen und neu erstellen", "status.remove_bookmark": "Lesezeichen entfernen", - "status.remove_favourite": "Aus Favoriten entfernen", + "status.remove_favourite": "Favorisierung entfernen", "status.remove_quote": "Entfernen", "status.replied_in_thread": "Antwortete im Thread", "status.replied_to": "Antwortete {name}", @@ -927,7 +987,7 @@ "status.replyAll": "Allen antworten", "status.report": "@{name} melden", "status.request_quote": "Anfrage zum Zitieren", - "status.revoke_quote": "Meinen zitierten Beitrag aus dem Beitrag von @{name} entfernen", + "status.revoke_quote": "Zitat bei @{name} entfernen", "status.sensitive_warning": "Inhaltswarnung", "status.share": "Teilen", "status.show_less_all": "Alles einklappen", @@ -936,7 +996,7 @@ "status.title.with_attachments": "{user} veröffentlichte {attachmentCount, plural, one {ein Medium} other {{attachmentCount} Medien}}", "status.translate": "Übersetzen", "status.translated_from_with": "Aus {lang} mittels {provider} übersetzt", - "status.uncached_media_warning": "Vorschau nicht verfügbar", + "status.uncached_media_warning": "Vorschau noch nicht verfügbar", "status.unmute_conversation": "Stummschaltung der Unterhaltung aufheben", "status.unpin": "Vom Profil lösen", "subscribed_languages.lead": "Nach der Änderung werden nur noch Beiträge in den ausgewählten Sprachen in den Timelines deiner Startseite und deiner Listen angezeigt. Wähle keine Sprache aus, um alle Beiträge zu sehen.", @@ -972,8 +1032,8 @@ "upload_form.drag_and_drop.on_drag_over": "Der Medienanhang {item} wurde bewegt.", "upload_form.drag_and_drop.on_drag_start": "Der Medienanhang {item} wurde aufgenommen.", "upload_form.edit": "Bearbeiten", - "upload_progress.label": "Wird hochgeladen …", - "upload_progress.processing": "Wird verarbeitet…", + "upload_progress.label": "Upload läuft …", + "upload_progress.processing": "Wird verarbeitet …", "username.taken": "Dieser Profilname ist vergeben. Versuche einen anderen", "video.close": "Video schließen", "video.download": "Datei herunterladen", @@ -990,7 +1050,9 @@ "video.volume_down": "Leiser", "video.volume_up": "Lauter", "visibility_modal.button_title": "Sichtbarkeit festlegen", - "visibility_modal.header": "Sichtbarkeit und Interaktion", + "visibility_modal.direct_quote_warning.text": "Wenn diese Einstellungen gespeichert werden, wird das eingebettete Zitat in einen Link umgewandelt.", + "visibility_modal.direct_quote_warning.title": "Zitate können in privaten Erwähnungen nicht eingebettet werden", + "visibility_modal.header": "Sichtbarkeit und Zitate", "visibility_modal.helper.direct_quoting": "Private Erwähnungen, die auf Mastodon verfasst wurden, können nicht von anderen zitiert werden.", "visibility_modal.helper.privacy_editing": "Die Sichtbarkeit eines bereits veröffentlichten Beitrags kann nachträglich nicht mehr geändert werden.", "visibility_modal.helper.privacy_private_self_quote": "Beiträge mit privaten Erwähnungen können öffentlich nicht zitiert werden.", @@ -998,9 +1060,9 @@ "visibility_modal.helper.unlisted_quoting": "Sollten dich andere zitieren, werden ihre zitierten Beiträge ebenfalls nicht in den Trends und öffentlichen Timelines angezeigt.", "visibility_modal.instructions": "Lege fest, wer mit diesem Beitrag interagieren darf. Du hast auch die Möglichkeit, diese Einstellung auf alle zukünftigen Beiträge anzuwenden. Gehe zu: Einstellungen > Standardeinstellungen für Beiträge", "visibility_modal.privacy_label": "Sichtbarkeit", - "visibility_modal.quote_followers": "Nur Follower", - "visibility_modal.quote_label": "Wer zitieren darf", - "visibility_modal.quote_nobody": "Nur ich", - "visibility_modal.quote_public": "Alle", + "visibility_modal.quote_followers": "Nur meine Follower dürfen mich zitieren", + "visibility_modal.quote_label": "Wer darf mich zitieren?", + "visibility_modal.quote_nobody": "Niemand darf mich zitieren", + "visibility_modal.quote_public": "Alle dürfen mich zitieren", "visibility_modal.save": "Speichern" } diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index feefaabeea6fa7..1e07e97b5152a2 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Σταμάτα να με ειδοποιείς όταν δημοσιεύει ο @{name}", "account.domain_blocking": "Αποκλείεται ο τομέας", "account.edit_profile": "Επεξεργασία προφίλ", + "account.edit_profile_short": "Επεξεργασία", "account.enable_notifications": "Ειδοποίησέ με όταν δημοσιεύει ο @{name}", "account.endorse": "Προβολή στο προφίλ", "account.familiar_followers_many": "Ακολουθείται από {name1}, {name2}, και {othersCount, plural, one {ένας ακόμα που ξέρεις} other {# ακόμα που ξέρεις}}", @@ -40,13 +41,18 @@ "account.featured_tags.last_status_never": "Καμία ανάρτηση", "account.follow": "Ακολούθησε", "account.follow_back": "Ακολούθησε και εσύ", + "account.follow_back_short": "Ακολούθησε και εσύ", + "account.follow_request": "Αίτημα για ακολούθηση", + "account.follow_request_cancel": "Ακύρωση αιτήματος", + "account.follow_request_cancel_short": "Ακύρωση", + "account.follow_request_short": "Αίτημα", "account.followers": "Ακόλουθοι", - "account.followers.empty": "Κανείς δεν ακολουθεί αυτόν τον χρήστη ακόμα.", + "account.followers.empty": "Κανείς δεν ακολουθεί αυτόν τον χρήστη ακόμη.", "account.followers_counter": "{count, plural, one {{counter} ακόλουθος} other {{counter} ακόλουθοι}}", "account.followers_you_know_counter": "{counter} που ξέρεις", "account.following": "Ακολουθείτε", "account.following_counter": "{count, plural, one {{counter} ακολουθεί} other {{counter} ακολουθούν}}", - "account.follows.empty": "Αυτός ο χρήστης δεν ακολουθεί κανέναν ακόμα.", + "account.follows.empty": "Αυτός ο χρήστης δεν ακολουθεί κανέναν ακόμη.", "account.follows_you": "Σε ακολουθεί", "account.go_to_profile": "Μετάβαση στο προφίλ", "account.hide_reblogs": "Απόκρυψη ενισχύσεων από @{name}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Τουτ και απαντήσεις", "account.remove_from_followers": "Κατάργηση {name} από τους ακόλουθους", "account.report": "Κατάγγειλε @{name}", - "account.requested": "Εκκρεμεί έγκριση. Κάνε κλικ για να ακυρώσεις το αίτημα παρακολούθησης", "account.requested_follow": "Ο/Η {name} αιτήθηκε να σε ακολουθήσει", "account.requests_to_follow_you": "Αιτήματα για να σε ακολουθήσουν", "account.share": "Κοινοποίηση του προφίλ @{name}", @@ -108,25 +113,52 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Περιέγραψε αυτό για άτομα με προβλήματα όρασης…", "alt_text_modal.done": "Ολοκληρώθηκε", "announcement.announcement": "Ανακοίνωση", - "annual_report.summary.archetype.booster": "Ο κυνηγός των φοβερών", - "annual_report.summary.archetype.lurker": "Ο διακριτικός", - "annual_report.summary.archetype.oracle": "Η Πυθία", - "annual_report.summary.archetype.pollster": "Ο δημοσκόπος", - "annual_report.summary.archetype.replier": "Η κοινωνική πεταλούδα", - "annual_report.summary.followers.followers": "ακόλουθοι", - "annual_report.summary.followers.total": "{count} συνολικά", - "annual_report.summary.here_it_is": "Εδώ είναι το {year} σου σε ανασκόπηση:", - "annual_report.summary.highlighted_post.by_favourites": "πιο αγαπημένη ανάρτηση", - "annual_report.summary.highlighted_post.by_reblogs": "πιο ενισχυμένη ανάρτηση", - "annual_report.summary.highlighted_post.by_replies": "ανάρτηση με τις περισσότερες απαντήσεις", - "annual_report.summary.highlighted_post.possessive": "του χρήστη {name}", + "annual_report.announcement.action_build": "Φτιάξε το Wrapstodon μου", + "annual_report.announcement.action_dismiss": "Όχι, ευχαριστώ", + "annual_report.announcement.action_view": "Προβολή του Wrapstodon μου", + "annual_report.announcement.description": "Ανακαλύψτε περισσότερα για την αλληλεπίδραση σας στο Mastodon κατά τη διάρκεια του περασμένου έτους.", + "annual_report.announcement.title": "Το Wrapstodon του {year} έφτασε", + "annual_report.nav_item.badge": "Νέο", + "annual_report.shared_page.donate": "Δωρεά", + "annual_report.shared_page.footer": "Παράχθηκε με {heart} από την ομάδα του Mastodon", + "annual_report.shared_page.footer_server_info": "Ο/Η {username} χρησιμοποιεί το {domain}, μία από τις πολλές κοινότητες που βασίζονται στο Mastodon.", + "annual_report.summary.archetype.booster.desc_public": "Ο/Η {name} έμεινε στο κυνήγι για αναρτήσεις για να τις ενισχύσει, ενισχύοντας άλλους δημιουργούς με τέλειο στόχο.", + "annual_report.summary.archetype.booster.desc_self": "Έμεινες στο κυνήγι για αναρτήσεις για να τις ενισχύσεις, ενισχύοντας άλλους δημιουργούς με τέλειο στόχο.", + "annual_report.summary.archetype.booster.name": "Ο Τοξότης", + "annual_report.summary.archetype.die_drei_fragezeichen": "???", + "annual_report.summary.archetype.lurker.desc_public": "Ξέρουμε ότι ο/η {name} ήταν εκεί έξω, κάπου, απολαμβάνοντας το Mastodon με τον δικό του/της ήσυχο τρόπο.", + "annual_report.summary.archetype.lurker.desc_self": "Ξέρουμε ότι ήσουν εκεί έξω, κάπου, απολαμβάνοντας το Mastodon με τον δικό σου ήσυχο τρόπο.", + "annual_report.summary.archetype.lurker.name": "Ο Στωϊκός", + "annual_report.summary.archetype.oracle.desc_public": "O/H {name} δημιούργησε νέες αναρτήσεις περισσότερο από ότι απαντήσεις, κρατώντας το Mastodon φρέσκο και κοιτώντας προς το μέλλον.", + "annual_report.summary.archetype.oracle.desc_self": "Δημιούργησες νέες αναρτήσεις περισσότερο από ότι απαντήσεις, κρατώντας το Mastodon φρέσκο και κοιτώντας προς το μέλλον.", + "annual_report.summary.archetype.oracle.name": "Η Πυθία", + "annual_report.summary.archetype.pollster.desc_public": "Ο/Η {name} δημιούργησε περισσότερες δημοσκοπήσεις από άλλα είδη αναρτήσεων, καλλιεργώντας περιέργεια στο Mastodon.", + "annual_report.summary.archetype.pollster.desc_self": "Δημιούργησες περισσότερες δημοσκοπήσεις από άλλα είδη αναρτήσεων, καλλιεργώντας περιέργεια στο Mastodon.", + "annual_report.summary.archetype.pollster.name": "Ο Θαυμαστής", + "annual_report.summary.archetype.replier.desc_public": "Ο/Η {name} συχνά απαντούσε σε αναρτήσεις άλλων ανθρώπων, επικονίαζοντας το Mastodon με νέες συζητήσεις.", + "annual_report.summary.archetype.replier.desc_self": "Συχνά απαντούσες σε αναρτήσεις άλλων ανθρώπων, επικονίαζοντας το Mastodon με νέες συζητήσεις.", + "annual_report.summary.archetype.replier.name": "Η Πεταλούδα", + "annual_report.summary.archetype.reveal": "Αποκάλυψε το αρχέτυπο μου", + "annual_report.summary.archetype.reveal_description": "Ευχαριστούμε που είσαι μέρος του Mastodon! Ώρα να μάθεις ποιο αρχέτυπο έχεις ενσαρκώσει το {year}.", + "annual_report.summary.archetype.title_public": "Αρχέτυπο του/της {name}", + "annual_report.summary.archetype.title_self": "Το αρχέτυπο σου", + "annual_report.summary.close": "Κλείσιμο", + "annual_report.summary.copy_link": "Αντιγραφή συνδέσμου", + "annual_report.summary.followers.new_followers": "{count, plural, one {νέος ακόλουθος} other {νέοι ακόλουθοι}}", + "annual_report.summary.highlighted_post.boost_count": "Αυτή η ανάρτηση ενισχύθηκε {count, plural, one {μια φορά} other {# φορές}}.", + "annual_report.summary.highlighted_post.favourite_count": "Αυτή η ανάρτηση αγαπήθηκε {count, plural, one {μια φορά} other {# φορές}}.", + "annual_report.summary.highlighted_post.reply_count": "Αυτή η ανάρτηση πήρε {count, plural, one {μια απάντηση} other {# απαντήσεις}}.", + "annual_report.summary.highlighted_post.title": "Πιο δημοφιλής ανάρτηση", "annual_report.summary.most_used_app.most_used_app": "πιο χρησιμοποιημένη εφαρμογή", "annual_report.summary.most_used_hashtag.most_used_hashtag": "πιο χρησιμοποιημένη ετικέτα", - "annual_report.summary.most_used_hashtag.none": "Κανένα", + "annual_report.summary.most_used_hashtag.used_count": "Έχεις συμπεριλάβει αυτήν την ετικέτα σε {count, plural, one {μια ανάρτηση} other {# αναρτήσεις}}.", + "annual_report.summary.most_used_hashtag.used_count_public": "Ο/Η {name} έχει συμπεριλάβει αυτήν την ετικέτα σε {count, plural, one {μια ανάρτηση} other {# αναρτήσεις}}.", "annual_report.summary.new_posts.new_posts": "νέες αναρτήσεις", "annual_report.summary.percentile.text": "Αυτό σε βάζει στο των κορυφαίων χρηστών του {domain}.", "annual_report.summary.percentile.we_wont_tell_bernie": "Δεν θα το πούμε στον Bernie.", - "annual_report.summary.thanks": "Ευχαριστούμε που συμμετέχεις στο Mastodon!", + "annual_report.summary.share_elsewhere": "Κοινοποίηση αλλού", + "annual_report.summary.share_message": "Πήρα το αρχέτυπο {archetype}!", + "annual_report.summary.share_on_mastodon": "Κοινοποίηση στο Mastodon", "attachments_list.unprocessed": "(μη επεξεργασμένο)", "audio.hide": "Απόκρυψη αρχείου ήχου", "block_modal.remote_users_caveat": "Θα ζητήσουμε από τον διακομιστή {domain} να σεβαστεί την απόφασή σου. Ωστόσο, η συμμόρφωση δεν είναι εγγυημένη δεδομένου ότι ορισμένοι διακομιστές ενδέχεται να χειρίζονται τους αποκλεισμούς διαφορετικά. Οι δημόσιες αναρτήσεις ενδέχεται να είναι ορατές σε μη συνδεδεμένους χρήστες.", @@ -152,6 +184,8 @@ "bundle_modal_error.close": "Κλείσιμο", "bundle_modal_error.message": "Κάτι πήγε στραβά κατά τη φόρτωση αυτής της οθόνης.", "bundle_modal_error.retry": "Δοκίμασε ξανά", + "carousel.current": "Διαφάνεια {current, number} / {max, number}", + "carousel.slide": "Διαφάνεια {current, number} από {max, number}", "closed_registrations.other_server_instructions": "Καθώς το Mastodon είναι αποκεντρωμένο, μπορείς να δημιουργήσεις λογαριασμό σε άλλον διακομιστή αλλά να συνεχίσεις να αλληλεπιδράς με αυτόν.", "closed_registrations_modal.description": "Η δημιουργία λογαριασμού στον {domain} προς το παρόν δεν είναι δυνατή, αλλά λάβε υπόψη ότι δεν χρειάζεσαι λογαριασμό ειδικά στον {domain} για να χρησιμοποιήσεις το Mastodon.", "closed_registrations_modal.find_another_server": "Βρες άλλον διακομιστή", @@ -168,6 +202,8 @@ "column.edit_list": "Επεξεργασία λίστας", "column.favourites": "Αγαπημένα", "column.firehose": "Ζωντανές ροές", + "column.firehose_local": "Ζωντανή ροή για αυτόν τον διακομιστή", + "column.firehose_singular": "Ζωντανή ροή", "column.follow_requests": "Αιτήματα ακολούθησης", "column.home": "Αρχική", "column.list_members": "Διαχείριση μελών λίστας", @@ -187,6 +223,7 @@ "community.column_settings.local_only": "Τοπικά μόνο", "community.column_settings.media_only": "Μόνο πολυμέσα", "community.column_settings.remote_only": "Απομακρυσμένα μόνο", + "compose.error.blank_post": "Η ανάρτηση δεν μπορεί να είναι κενή.", "compose.language.change": "Αλλαγή γλώσσας", "compose.language.search": "Αναζήτηση γλωσσών...", "compose.published.body": "Η ανάρτηση δημοσιεύτηκε.", @@ -239,6 +276,11 @@ "confirmations.missing_alt_text.secondary": "Δημοσίευση όπως και να ΄χει", "confirmations.missing_alt_text.title": "Προσθήκη εναλλακτικού κειμένου;", "confirmations.mute.confirm": "Αποσιώπηση", + "confirmations.private_quote_notify.cancel": "Πίσω στην επεξεργασία", + "confirmations.private_quote_notify.confirm": "Δημοσίευση ανάρτησης", + "confirmations.private_quote_notify.do_not_show_again": "Να μην εμφανιστεί ξανά αυτό το μήνυμα", + "confirmations.private_quote_notify.message": "Το άτομο που παραθέτετε και άλλες επισημάνσεις θα ειδοποιηθούν και θα μπορούν να δουν την ανάρτησή σας, ακόμη και αν δεν σας ακολουθούν.", + "confirmations.private_quote_notify.title": "Κοινοποίηση με τους ακολούθους και τους επισημασμένους χρήστες;", "confirmations.quiet_post_quote_info.dismiss": "Μη μου το ξαναθυμίσεις", "confirmations.quiet_post_quote_info.got_it": "Το κατάλαβα", "confirmations.quiet_post_quote_info.message": "Όταν παραθέτετε μια ήσυχη δημόσια ανάρτηση, η ανάρτηση σας θα είναι κρυμμένη από τις δημοφιλείς ροές.", @@ -252,9 +294,12 @@ "confirmations.revoke_quote.confirm": "Αφαίρεση ανάρτησης", "confirmations.revoke_quote.message": "Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.", "confirmations.revoke_quote.title": "Αφαίρεση ανάρτησης;", + "confirmations.unblock.confirm": "Άρση αποκλεισμού", + "confirmations.unblock.title": "Άρση αποκλεισμού {name};", "confirmations.unfollow.confirm": "Άρση ακολούθησης", - "confirmations.unfollow.message": "Σίγουρα θες να πάψεις να ακολουθείς τον/την {name};", - "confirmations.unfollow.title": "Άρση ακολούθησης;", + "confirmations.unfollow.title": "Κατάργηση ακολούθησης του/της {name};", + "confirmations.withdraw_request.confirm": "Απόσυρση αιτήματος", + "confirmations.withdraw_request.title": "Απόσυρση αιτήματος για να ακολουθήσετε τον/την {name};", "content_warning.hide": "Απόκρυψη ανάρτησης", "content_warning.show": "Εμφάνιση ούτως ή άλλως", "content_warning.show_more": "Εμφάνιση περισσότερων", @@ -314,30 +359,32 @@ "emoji_button.search_results": "Αποτελέσματα αναζήτησης", "emoji_button.symbols": "Σύμβολα", "emoji_button.travel": "Ταξίδια & Τοποθεσίες", - "empty_column.account_featured.me": "Δεν έχεις αναδείξει τίποτα ακόμα. Γνώριζες ότι μπορείς να αναδείξεις τις ετικέτες που χρησιμοποιείς περισσότερο, ακόμη και τους λογαριασμούς των φίλων σου στο προφίλ σου;", - "empty_column.account_featured.other": "Ο λογαριασμός {acct} δεν αναδείξει τίποτα ακόμα. Γνώριζες ότι μπορείς να αναδείξεις τις ετικέτες που χρησιμοποιείς περισσότερο, ακόμη και τους λογαριασμούς των φίλων σου στο προφίλ σου;", - "empty_column.account_featured_other.unknown": "Αυτός ο λογαριασμός δεν έχει αναδείξει τίποτα ακόμα.", + "empty_column.account_featured.me": "Δεν έχεις αναδείξει τίποτα ακόμη. Γνώριζες ότι μπορείς να αναδείξεις τις ετικέτες που χρησιμοποιείς περισσότερο, ακόμη και τους λογαριασμούς των φίλων σου στο προφίλ σου;", + "empty_column.account_featured.other": "Ο/Η {acct} δεν έχει αναδείξει τίποτα ακόμη. Γνώριζες ότι μπορείς να αναδείξεις τις ετικέτες που χρησιμοποιείς περισσότερο, ακόμη και τους λογαριασμούς των φίλων σου στο προφίλ σου;", + "empty_column.account_featured_other.unknown": "Αυτός ο λογαριασμός δεν έχει αναδείξει τίποτα ακόμη.", "empty_column.account_hides_collections": "Αυτός ο χρήστης έχει επιλέξει να μην καταστήσει αυτές τις πληροφορίες διαθέσιμες", "empty_column.account_suspended": "Λογαριασμός σε αναστολή", "empty_column.account_timeline": "Δεν έχει αναρτήσεις εδώ!", "empty_column.account_unavailable": "Μη διαθέσιμο προφίλ", - "empty_column.blocks": "Δεν έχεις αποκλείσει κανέναν χρήστη ακόμα.", - "empty_column.bookmarked_statuses": "Δεν έχεις καμία ανάρτηση με σελιδοδείκτη ακόμα. Μόλις βάλεις κάποιον, θα εμφανιστεί εδώ.", + "empty_column.blocks": "Δεν έχεις αποκλείσει κανέναν χρήστη ακόμη.", + "empty_column.bookmarked_statuses": "Δεν έχεις καμία ανάρτηση με σελιδοδείκτη ακόμη. Μόλις βάλεις κάποιον, θα εμφανιστεί εδώ.", "empty_column.community": "Η τοπική ροή είναι κενή. Γράψε κάτι δημόσια για να αρχίσει να κυλά η μπάλα!", - "empty_column.direct": "Δεν έχεις καμία προσωπική επισήμανση ακόμα. Όταν στείλεις ή λάβεις μία, θα εμφανιστεί εδώ.", - "empty_column.domain_blocks": "Δεν υπάρχουν αποκλεισμένοι τομείς ακόμα.", + "empty_column.direct": "Δεν έχεις καμία προσωπική επισήμανση ακόμη. Όταν στείλεις ή λάβεις μία, θα εμφανιστεί εδώ.", + "empty_column.disabled_feed": "Αυτή η ροή έχει απενεργοποιηθεί από τους διαχειριστές του διακομιστή σας.", + "empty_column.domain_blocks": "Δεν υπάρχουν αποκλεισμένοι τομείς ακόμη.", "empty_column.explore_statuses": "Τίποτα δεν βρίσκεται στις τάσεις αυτή τη στιγμή. Έλεγξε αργότερα!", - "empty_column.favourited_statuses": "Δεν έχεις καμία αγαπημένη ανάρτηση ακόμα. Μόλις αγαπήσεις κάποια, θα εμφανιστεί εδώ.", - "empty_column.favourites": "Κανείς δεν έχει αγαπήσει αυτή την ανάρτηση ακόμα. Μόλις το κάνει κάποιος, θα εμφανιστεί εδώ.", - "empty_column.follow_requests": "Δεν έχεις κανένα αίτημα παρακολούθησης ακόμα. Μόλις λάβεις κάποιο, θα εμφανιστεί εδώ.", - "empty_column.followed_tags": "Δεν έχετε ακολουθήσει ακόμα καμία ετικέτα. Όταν το κάνετε, θα εμφανιστούν εδώ.", - "empty_column.hashtag": "Δεν υπάρχει ακόμα κάτι για αυτή την ετικέτα.", + "empty_column.favourited_statuses": "Δεν έχεις καμία αγαπημένη ανάρτηση ακόμη. Μόλις αγαπήσεις κάποια, θα εμφανιστεί εδώ.", + "empty_column.favourites": "Κανείς δεν έχει αγαπήσει αυτή την ανάρτηση ακόμη. Μόλις το κάνει κάποιος, θα εμφανιστεί εδώ.", + "empty_column.follow_requests": "Δεν έχεις κανένα αίτημα παρακολούθησης ακόμη. Μόλις λάβεις κάποιο, θα εμφανιστεί εδώ.", + "empty_column.followed_tags": "Δεν έχεις ακολουθήσει καμία ετικέτα ακόμη. Όταν το κάνεις, θα εμφανιστούν εδώ.", + "empty_column.hashtag": "Δεν υπάρχει τίποτα σε αυτή την ετικέτα ακόμη.", "empty_column.home": "Η τοπική σου ροή είναι κενή! Πήγαινε στο {public} ή κάνε αναζήτηση για να ξεκινήσεις και να γνωρίσεις άλλους χρήστες.", - "empty_column.list": "Δεν υπάρχει τίποτα σε αυτή τη λίστα ακόμα. Όταν τα μέλη της δημοσιεύσουν νέες καταστάσεις, θα εμφανιστούν εδώ.", - "empty_column.mutes": "Δεν έχεις κανένα χρήστη σε σίγαση ακόμα.", + "empty_column.list": "Δεν υπάρχει τίποτα σε αυτή τη λίστα ακόμη. Όταν τα μέλη της δημοσιεύσουν νέες αναρτήσεις, θα εμφανιστούν εδώ.", + "empty_column.mutes": "Δεν έχεις κανένα χρήστη σε σίγαση ακόμη.", "empty_column.notification_requests": "Όλα καθαρά! Δεν υπάρχει τίποτα εδώ. Όταν λαμβάνεις νέες ειδοποιήσεις, αυτές θα εμφανίζονται εδώ σύμφωνα με τις ρυθμίσεις σου.", - "empty_column.notifications": "Δεν έχεις ειδοποιήσεις ακόμα. Όταν άλλα άτομα αλληλεπιδράσουν μαζί σου, θα το δεις εδώ.", + "empty_column.notifications": "Δεν έχεις ειδοποιήσεις ακόμη. Όταν άλλα άτομα αλληλεπιδράσουν μαζί σου, θα το δεις εδώ.", "empty_column.public": "Δεν υπάρχει τίποτα εδώ! Γράψε κάτι δημόσιο ή ακολούθησε χειροκίνητα χρήστες από άλλους διακομιστές για να τη γεμίσεις", + "error.no_hashtag_feed_access": "Γίνετε μέλος ή συνδεθείτε για να δείτε και να ακολουθήσετε αυτήν την ετικέτα.", "error.unexpected_crash.explanation": "Είτε λόγω σφάλματος στον κώδικά μας ή λόγω ασυμβατότητας με τον περιηγητή, η σελίδα δε μπόρεσε να εμφανιστεί σωστά.", "error.unexpected_crash.explanation_addons": "Η σελίδα δεν μπόρεσε να εμφανιστεί σωστά. Το πρόβλημα οφείλεται πιθανόν σε κάποια επέκταση του φυλλομετρητή ή σε κάποιο αυτόματο εργαλείο μετάφρασης.", "error.unexpected_crash.next_steps": "Δοκίμασε να ανανεώσεις τη σελίδα. Αν αυτό δε βοηθήσει, ίσως να μπορέσεις να χρησιμοποιήσεις το Mastodon μέσω διαφορετικού περιηγητή ή κάποιας εφαρμογής.", @@ -349,11 +396,9 @@ "explore.trending_links": "Νέα", "explore.trending_statuses": "Αναρτήσεις", "explore.trending_tags": "Ετικέτες", + "featured_carousel.current": "Ανάρτηση {current, number} / {max, number}", "featured_carousel.header": "{count, plural, one {Καρφιτσωμένη Ανάρτηση} other {Καρφιτσωμένες Αναρτήσεις}}", - "featured_carousel.next": "Επόμενο", - "featured_carousel.post": "Ανάρτηση", - "featured_carousel.previous": "Προηγούμενο", - "featured_carousel.slide": "{index} από {total}", + "featured_carousel.slide": "Ανάρτηση {current, number} από {max, number}", "filter_modal.added.context_mismatch_explanation": "Αυτή η κατηγορία φίλτρων δεν ισχύει για το περιεχόμενο εντός του οποίου προσπελάσατε αυτή την ανάρτηση. Αν θέλετε να φιλτραριστεί η ανάρτηση και εντός αυτού του πλαισίου, θα πρέπει να τροποποιήσετε το φίλτρο.", "filter_modal.added.context_mismatch_title": "Ασυμφωνία περιεχομένου!", "filter_modal.added.expired_explanation": "Αυτή η κατηγορία φίλτρων έχει λήξει, πρέπει να αλλάξετε την ημερομηνία λήξης για να ισχύσει.", @@ -396,6 +441,9 @@ "follow_suggestions.who_to_follow": "Ποιον να ακολουθήσεις", "followed_tags": "Ακολουθούμενες ετικέτες", "footer.about": "Σχετικά με", + "footer.about_mastodon": "Σχετικά με το Mastodon", + "footer.about_server": "Σχετικά με το {domain}", + "footer.about_this_server": "Σχετικά με", "footer.directory": "Κατάλογος προφίλ", "footer.get_app": "Αποκτήστε την εφαρμογή", "footer.keyboard_shortcuts": "Συντομεύσεις πληκτρολογίου", @@ -425,7 +473,7 @@ "hashtag.mute": "Σίγαση #{hashtag}", "hashtag.unfeature": "Να μην αναδεικνύεται στο προφίλ", "hashtag.unfollow": "Διακοπή παρακολούθησης ετικέτας", - "hashtags.and_other": "…και {count, plural, other {# ακόμη}}", + "hashtags.and_other": "…και {count, plural, other {# ακόμα}}", "hints.profiles.followers_may_be_missing": "Μπορεί να λείπουν ακόλουθοι για αυτό το προφίλ.", "hints.profiles.follows_may_be_missing": "Άτομα που ακολουθούνται μπορεί να λείπουν απ' αυτό το προφίλ.", "hints.profiles.posts_may_be_missing": "Κάποιες αναρτήσεις από αυτό το προφίλ μπορεί να λείπουν.", @@ -442,7 +490,7 @@ "home.show_announcements": "Εμφάνιση ανακοινώσεων", "ignore_notifications_modal.disclaimer": "Το Mastodon δε μπορεί να ενημερώσει τους χρήστες ότι αγνόησες τις ειδοποιήσεις του. Η αγνόηση ειδοποιήσεων δεν θα εμποδίσει την αποστολή των ίδιων των μηνυμάτων.", "ignore_notifications_modal.filter_instead": "Φίλτρο αντ' αυτού", - "ignore_notifications_modal.filter_to_act_users": "Θα μπορείς ακόμα να αποδεχθείς, να απορρίψεις ή να αναφέρεις χρήστες", + "ignore_notifications_modal.filter_to_act_users": "Θα μπορείς ακόμη να αποδεχθείς, να απορρίψεις ή να αναφέρεις χρήστες", "ignore_notifications_modal.filter_to_avoid_confusion": "Το φιλτράρισμα βοηθά στην αποφυγή πιθανής σύγχυσης", "ignore_notifications_modal.filter_to_review_separately": "Μπορείς να δεις τις φιλτραρισμένες ειδοποιήσεις ξεχωριστά", "ignore_notifications_modal.ignore": "Αγνόηση ειδοποιήσεων", @@ -497,6 +545,7 @@ "keyboard_shortcuts.toggle_hidden": "Εμφάνιση/απόκρυψη κειμένου πίσω από το CW", "keyboard_shortcuts.toggle_sensitivity": "Εμφάνιση/απόκρυψη πολυμέσων", "keyboard_shortcuts.toot": "Δημιουργία νέας ανάρτησης", + "keyboard_shortcuts.top": "Μετακίνηση στην κορυφή της λίστας", "keyboard_shortcuts.translate": "για να μεταφραστεί μια ανάρτηση", "keyboard_shortcuts.unfocus": "Αποεστίαση του πεδίου σύνθεσης/αναζήτησης", "keyboard_shortcuts.up": "Μετακίνηση προς τα πάνω στη λίστα", @@ -527,8 +576,8 @@ "lists.list_members_count": "{count, plural, one {# μέλος} other {# μέλη}}", "lists.list_name": "Όνομα λίστας", "lists.new_list_name": "Νέο όνομα λίστας", - "lists.no_lists_yet": "Δεν υπάρχουν λίστες ακόμα.", - "lists.no_members_yet": "Κανένα μέλος ακόμα.", + "lists.no_lists_yet": "Καμία λίστα ακόμη.", + "lists.no_members_yet": "Κανένα μέλος ακόμη.", "lists.no_results_found": "Δεν βρέθηκαν αποτελέσματα.", "lists.remove_member": "Αφαίρεση", "lists.replies_policy.followed": "Οποιοσδήποτε χρήστης που ακολουθείς", @@ -588,15 +637,15 @@ "notification.admin.report_statuses": "Ο χρήστης {name} ανέφερε τον χρήστη {target} για {category}", "notification.admin.report_statuses_other": "Ο χρήστης {name} ανέφερε τον χρήστη {target}", "notification.admin.sign_up": "{name} έχει εγγραφεί", - "notification.admin.sign_up.name_and_others": "{name} και {count, plural, one {# ακόμη} other {# ακόμη}} έχουν εγγραφεί", + "notification.admin.sign_up.name_and_others": "{name} και {count, plural, one {# ακόμα} other {# ακόμα}} έχουν εγγραφεί", "notification.annual_report.message": "Το #Wrapstodon {year} σε περιμένει! Αποκάλυψε τα στιγμιότυπα της χρονιάς και αξέχαστες στιγμές σου στο Mastodon!", "notification.annual_report.view": "Προβολή #Wrapstodon", "notification.favourite": "{name} αγάπησε την ανάρτηση σου", - "notification.favourite.name_and_others_with_link": "{name} και {count, plural, one {# ακόμη} other {# ακόμη}} αγάπησαν την ανάρτησή σου", + "notification.favourite.name_and_others_with_link": "{name} και {count, plural, one {# ακόμα} other {# ακόμα}} αγάπησαν την ανάρτησή σου", "notification.favourite_pm": "Ο χρήστης {name} αγάπησε την ιδιωτική σου επισήμανση", - "notification.favourite_pm.name_and_others_with_link": "Ο χρήστης {name} και {count, plural, one {# ακόμη} other {# ακόμη}} αγάπησαν την ιδωτική σου επισήμανση", + "notification.favourite_pm.name_and_others_with_link": "Ο χρήστης {name} και {count, plural, one {# ακόμα} other {# ακόμα}} αγάπησαν την ιδωτική σου επισήμανση", "notification.follow": "Ο χρήστης {name} σε ακολούθησε", - "notification.follow.name_and_others": "Ο χρήστης {name} και {count, plural, one {# ακόμη} other {# ακόμη}} σε ακολούθησαν", + "notification.follow.name_and_others": "Ο χρήστης {name} και {count, plural, one {# ακόμα} other {# ακόμα}} σε ακολούθησαν", "notification.follow_request": "Ο/H {name} ζήτησε να σε ακολουθήσει", "notification.follow_request.name_and_others": "{name} και {count, plural, one {# άλλος} other {# άλλοι}} ζήτησαν να σε ακολουθήσουν", "notification.label.mention": "Επισήμανση", @@ -619,7 +668,7 @@ "notification.poll": "Μία ψηφοφορία στην οποία συμμετείχες έχει τελειώσει", "notification.quoted_update": "Ο χρήστης {name} επεξεργάστηκε μία ανάρτηση που παρέθεσες", "notification.reblog": "Ο/Η {name} ενίσχυσε την ανάρτηση σου", - "notification.reblog.name_and_others_with_link": "{name} και {count, plural, one {# ακόμη} other {# ακόμη}} ενίσχυσαν την ανάρτησή σου", + "notification.reblog.name_and_others_with_link": "{name} και {count, plural, one {# ακόμα} other {# ακόμα}} ενίσχυσαν την ανάρτησή σου", "notification.relationships_severance_event": "Χάθηκε η σύνδεση με το {name}", "notification.relationships_severance_event.account_suspension": "Ένας διαχειριστής από το {from} ανέστειλε το {target}, πράγμα που σημαίνει ότι δεν μπορείς πλέον να λαμβάνεις ενημερώσεις από αυτούς ή να αλληλεπιδράς μαζί τους.", "notification.relationships_severance_event.domain_block": "Ένας διαχειριστής από {from} έχει μπλοκάρει το {target}, συμπεριλαμβανομένων {followersCount} από τους ακόλουθούς σου και {followingCount, plural, one {# λογαριασμό} other {# λογαριασμοί}} που ακολουθείς.", @@ -712,7 +761,7 @@ "onboarding.profile.display_name": "Εμφανιζόμενο όνομα", "onboarding.profile.display_name_hint": "Το πλήρες ή το διασκεδαστικό σου όνομα…", "onboarding.profile.note": "Βιογραφικό", - "onboarding.profile.note_hint": "Μπορείτε να @αναφέρετε άλλα άτομα ή #hashtags…", + "onboarding.profile.note_hint": "Μπορείς να @επισημάνεις άλλα άτομα ή #ετικέτες…", "onboarding.profile.save_and_continue": "Αποθήκευση και συνέχεια", "onboarding.profile.title": "Ρύθμιση προφίλ", "onboarding.profile.upload_avatar": "Μεταφόρτωση εικόνας προφίλ", @@ -731,7 +780,7 @@ "poll_button.add_poll": "Προσθήκη δημοσκόπησης", "poll_button.remove_poll": "Αφαίρεση δημοσκόπησης", "privacy.change": "Προσαρμογή ιδιωτικότητας ανάρτησης", - "privacy.direct.long": "Όλοι όσοι αναφέρθηκαν στην ανάρτηση", + "privacy.direct.long": "Όλοι όσοι επισημάνθηκαν στην ανάρτηση", "privacy.direct.short": "Ιδιωτική επισήμανση", "privacy.private.long": "Μόνο οι ακόλουθοί σας", "privacy.private.short": "Ακόλουθοι", @@ -745,11 +794,13 @@ "privacy.unlisted.short": "Ήσυχα δημόσια", "privacy_policy.last_updated": "Τελευταία ενημέρωση {date}", "privacy_policy.title": "Πολιτική Απορρήτου", + "quote_error.edit": "Δεν μπορούν να προστεθούν παραθέσεις κατά την επεξεργασία μιας ανάρτησης.", "quote_error.poll": "Η παράθεση δεν επιτρέπεται με δημοσκοπήσεις.", + "quote_error.private_mentions": "Η παράθεση δεν επιτρέπεται με άμεσες επισημάνσεις.", "quote_error.quote": "Επιτρέπεται μόνο μία παράθεση τη φορά.", "quote_error.unauthorized": "Δεν είστε εξουσιοδοτημένοι να παραθέσετε αυτή την ανάρτηση.", "quote_error.upload": "Η παράθεση δεν επιτρέπεται με συνημμένα πολυμέσων.", - "recommended": "Προτεινόμενα", + "recommended": "Προτείνεται", "refresh": "Ανανέωση", "regeneration_indicator.please_stand_by": "Παρακαλούμε περίμενε.", "regeneration_indicator.preparing_your_home_feed": "Ετοιμάζουμε την αρχική σου ροή…", @@ -785,7 +836,7 @@ "report.forward": "Προώθηση προς {target}", "report.forward_hint": "Ο λογαριασμός είναι από διαφορετικό διακομιστή. Να σταλεί ανώνυμο αντίγραφο της αναφοράς και εκεί;", "report.mute": "Σίγαση", - "report.mute_explanation": "Δεν θα βλέπεις τις αναρτήσεις του. Εκείνοι μπορούν ακόμα να σε ακολουθούν και να βλέπουν τις αναρτήσεις σου χωρίς να γνωρίζουν ότι είναι σε σίγαση.", + "report.mute_explanation": "Δεν θα βλέπεις τις αναρτήσεις τους. Εκείνοι μπορούν ακόμη να σε ακολουθούν και να βλέπουν τις αναρτήσεις σου χωρίς να γνωρίζουν ότι είναι σε σίγαση.", "report.next": "Επόμενο", "report.placeholder": "Επιπλέον σχόλια", "report.reasons.dislike": "Δεν μου αρέσει", @@ -865,8 +916,12 @@ "status.cannot_quote": "Δε σας επιτρέπετε να παραθέσετε αυτή την ανάρτηση", "status.cannot_reblog": "Αυτή η ανάρτηση δεν μπορεί να ενισχυθεί", "status.contains_quote": "Περιέχει παράθεση", - "status.context.load_new_replies": "Νέες απαντήσεις διαθέσιμες", - "status.context.loading": "Γίνεται έλεγχος για περισσότερες απαντήσεις", + "status.context.loading": "Φόρτωση περισσότερων απαντήσεων", + "status.context.loading_error": "Αδυναμία φόρτωσης νέων απαντήσεων", + "status.context.loading_success": "Νέες απαντήσεις φορτώθηκαν", + "status.context.more_replies_found": "Βρέθηκαν περισσότερες απαντήσεις", + "status.context.retry": "Επανάληψη", + "status.context.show": "Εμφάνιση", "status.continued_thread": "Συνεχιζόμενο νήματος", "status.copy": "Αντιγραφή συνδέσμου ανάρτησης", "status.delete": "Διαγραφή", @@ -879,7 +934,7 @@ "status.edited_x_times": "Επεξεργάστηκε {count, plural, one {{count} φορά} other {{count} φορές}}", "status.embed": "Απόκτηση κώδικα ενσωμάτωσης", "status.favourite": "Αγαπημένα", - "status.favourites": "{count, plural, one {αγαπημένο} other {αγαπημένα}}", + "status.favourites_count": "{count, plural, one {{counter} αγαπημένο} other {{counter} αγαπημένα}}", "status.filter": "Φιλτράρισμα αυτής της ανάρτησης", "status.history.created": "{name} δημιούργησε στις {date}", "status.history.edited": "{name} επεξεργάστηκε στις {date}", @@ -895,9 +950,12 @@ "status.pin": "Καρφίτσωσε στο προφίλ", "status.quote": "Παράθεση", "status.quote.cancel": "Ακύρωση παράθεσης", + "status.quote_error.blocked_account_hint.title": "Αυτή η ανάρτηση είναι κρυμμένη επειδή έχετε μπλοκάρει τον/την @{name}.", + "status.quote_error.blocked_domain_hint.title": "Αυτή η ανάρτηση είναι κρυμμένη επειδή έχετε μπλοκάρει το {domain}.", "status.quote_error.filtered": "Κρυφό λόγω ενός από τα φίλτρα σου", "status.quote_error.limited_account_hint.action": "Εμφάνιση ούτως ή άλλως", "status.quote_error.limited_account_hint.title": "Αυτό το προφίλ έχει αποκρυφτεί από τους διαχειριστές του διακομιστή {domain}.", + "status.quote_error.muted_account_hint.title": "Αυτή η ανάρτηση είναι κρυμμένη επειδή έχετε κάνει σίγαση τον/την @{name}.", "status.quote_error.not_available": "Ανάρτηση μη διαθέσιμη", "status.quote_error.pending_approval": "Ανάρτηση σε αναμονή", "status.quote_error.pending_approval_popout.body": "Στο Mastodon, μπορείς να ελέγξεις αν κάποιος μπορεί να σε παραθέσει. Αυτή η ανάρτηση εκκρεμεί ενώ λαμβάνουμε την έγκριση του αρχικού συντάκτη.", @@ -908,15 +966,17 @@ "status.quote_policy_change": "Αλλάξτε ποιός μπορεί να κάνει παράθεση", "status.quote_post_author": "Παρατίθεται μια ανάρτηση από @{name}", "status.quote_private": "Ιδιωτικές αναρτήσεις δεν μπορούν να παρατεθούν", - "status.quotes": "{count, plural, one {# παράθεση} other {# παραθέσεις}}", - "status.quotes.empty": "Κανείς δεν έχει παραθέσει αυτή την ανάρτηση ακόμα. Μόλις το κάνει, θα εμφανιστεί εδώ.", + "status.quotes.empty": "Κανείς δεν έχει παραθέσει αυτή την ανάρτηση ακόμη. Μόλις το κάνει, θα εμφανιστεί εδώ.", + "status.quotes.local_other_disclaimer": "Οι παραθέσεις που απορρίφθηκαν από τον συντάκτη δε θα εμφανιστούν.", + "status.quotes.remote_other_disclaimer": "Μόνο παραθέσεις από το {domain} είναι εγγυημένες ότι θα εμφανιστούν εδώ. Παραθέσεις που απορρίφθηκαν από τον συντάκτη δε θα εμφανιστούν.", + "status.quotes_count": "{count, plural, one {{counter} παράθεση} other {{counter} παραθέσεις}}", "status.read_more": "Διάβασε περισότερα", "status.reblog": "Ενίσχυση", "status.reblog_or_quote": "Ενίσχυση ή παράθεση", "status.reblog_private": "Μοιράσου ξανά με τους ακόλουθούς σου", "status.reblogged_by": "{name} προώθησε", - "status.reblogs": "{count, plural, one {ενίσχυση} other {ενισχύσεις}}", - "status.reblogs.empty": "Κανείς δεν ενίσχυσε αυτή την ανάρτηση ακόμα. Μόλις το κάνει κάποιος, θα εμφανιστεί εδώ.", + "status.reblogs.empty": "Κανείς δεν ενίσχυσε αυτή την ανάρτηση ακόμη. Μόλις το κάνει κάποιος, θα εμφανιστεί εδώ.", + "status.reblogs_count": "{count, plural, one {{counter} ενίσχυση} other {{counter} ενισχύσεις}}", "status.redraft": "Σβήσε & ξαναγράψε", "status.remove_bookmark": "Αφαίρεση σελιδοδείκτη", "status.remove_favourite": "Κατάργηση από τα αγαπημένα", @@ -990,6 +1050,8 @@ "video.volume_down": "Μείωση έντασης", "video.volume_up": "Αύξηση έντασης", "visibility_modal.button_title": "Ορισμός ορατότητας", + "visibility_modal.direct_quote_warning.text": "Εάν αποθηκεύσετε τις τρέχουσες ρυθμίσεις, η ενσωματωμένη παράθεση θα μετατραπεί σε σύνδεσμο.", + "visibility_modal.direct_quote_warning.title": "Οι παραθέσεις δεν μπορούν να ενσωματωθούν σε ιδιωτικές επισημάνσεις", "visibility_modal.header": "Ορατότητα και αλληλεπίδραση", "visibility_modal.helper.direct_quoting": "Ιδιωτικές αναφορές που έχουν συνταχθεί στο Mastodon δεν μπορούν να γίνουν παράθεση από άλλους.", "visibility_modal.helper.privacy_editing": "Η ορατότητα δεν μπορεί να αλλάξει μετά τη δημοσίευση μιας ανάρτησης.", diff --git a/app/javascript/mastodon/locales/en-GB.json b/app/javascript/mastodon/locales/en-GB.json index 60f4e0d8f9d82d..9b4a79d32cc5e5 100644 --- a/app/javascript/mastodon/locales/en-GB.json +++ b/app/javascript/mastodon/locales/en-GB.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Stop notifying me when @{name} posts", "account.domain_blocking": "Blocking domain", "account.edit_profile": "Edit profile", + "account.edit_profile_short": "Edit", "account.enable_notifications": "Notify me when @{name} posts", "account.endorse": "Feature on profile", "account.familiar_followers_many": "Followed by {name1}, {name2}, and {othersCount, plural, one {one other you know} other {# others you know}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "No posts", "account.follow": "Follow", "account.follow_back": "Follow back", + "account.follow_back_short": "Follow back", + "account.follow_request": "Request to follow", + "account.follow_request_cancel": "Cancel request", + "account.follow_request_cancel_short": "Cancel", + "account.follow_request_short": "Request", "account.followers": "Followers", "account.followers.empty": "No one follows this user yet.", "account.followers_counter": "{count, plural, one {{counter} follower} other {{counter} followers}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Posts and replies", "account.remove_from_followers": "Remove {name} from followers", "account.report": "Report @{name}", - "account.requested": "Awaiting approval. Click to cancel follow request", "account.requested_follow": "{name} has requested to follow you", "account.requests_to_follow_you": "Requests to follow you", "account.share": "Share @{name}'s profile", @@ -108,25 +113,52 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Describe this for people with visual impairments…", "alt_text_modal.done": "Done", "announcement.announcement": "Announcement", - "annual_report.summary.archetype.booster": "The cool-hunter", - "annual_report.summary.archetype.lurker": "The lurker", - "annual_report.summary.archetype.oracle": "The oracle", - "annual_report.summary.archetype.pollster": "The pollster", - "annual_report.summary.archetype.replier": "The social butterfly", - "annual_report.summary.followers.followers": "followers", - "annual_report.summary.followers.total": "{count} total", - "annual_report.summary.here_it_is": "Here is your {year} in review:", - "annual_report.summary.highlighted_post.by_favourites": "most favourited post", - "annual_report.summary.highlighted_post.by_reblogs": "most boosted post", - "annual_report.summary.highlighted_post.by_replies": "post with the most replies", - "annual_report.summary.highlighted_post.possessive": "{name}'s", + "annual_report.announcement.action_build": "Build my Wrapstodon", + "annual_report.announcement.action_dismiss": "No thanks", + "annual_report.announcement.action_view": "View my Wrapstodon", + "annual_report.announcement.description": "Discover more about your engagement on Mastodon over the past year.", + "annual_report.announcement.title": "Wrapstodon {year} has arrived", + "annual_report.nav_item.badge": "New", + "annual_report.shared_page.donate": "Donate", + "annual_report.shared_page.footer": "Generated with {heart} by the Mastodon team", + "annual_report.shared_page.footer_server_info": "{username} uses {domain}, one of many communities powered by Mastodon.", + "annual_report.summary.archetype.booster.desc_public": "{name} stayed on the hunt for posts to boost, amplifying other creators with perfect aim.", + "annual_report.summary.archetype.booster.desc_self": "You stayed on the hunt for posts to boost, amplifying other creators with perfect aim.", + "annual_report.summary.archetype.booster.name": "The Archer", + "annual_report.summary.archetype.die_drei_fragezeichen": "???", + "annual_report.summary.archetype.lurker.desc_public": "We know {name} was out there, somewhere, enjoying Mastodon in their own quiet way.", + "annual_report.summary.archetype.lurker.desc_self": "We know you were out there, somewhere, enjoying Mastodon in your own quiet way.", + "annual_report.summary.archetype.lurker.name": "The Stoic", + "annual_report.summary.archetype.oracle.desc_public": "{name} created new posts more than replies, keeping Mastodon fresh and future-facing.", + "annual_report.summary.archetype.oracle.desc_self": "You created new posts more than replies, keeping Mastodon fresh and future-facing.", + "annual_report.summary.archetype.oracle.name": "The Oracle", + "annual_report.summary.archetype.pollster.desc_public": "{name} created more polls than other post types, cultivating curiosity on Mastodon.", + "annual_report.summary.archetype.pollster.desc_self": "You created more polls than other post types, cultivating curiosity on Mastodon.", + "annual_report.summary.archetype.pollster.name": "The Wonderer", + "annual_report.summary.archetype.replier.desc_public": "{name} frequently replied to other people’s posts, pollinating Mastodon with new discussions.", + "annual_report.summary.archetype.replier.desc_self": "You frequently replied to other people’s posts, pollinating Mastodon with new discussions.", + "annual_report.summary.archetype.replier.name": "The Butterfly", + "annual_report.summary.archetype.reveal": "Reveal my archetype", + "annual_report.summary.archetype.reveal_description": "Thanks for being part of Mastodon! Time to find out which archetype you embodied in {year}.", + "annual_report.summary.archetype.title_public": "{name}'s archetype", + "annual_report.summary.archetype.title_self": "Your archetype", + "annual_report.summary.close": "Close", + "annual_report.summary.copy_link": "Copy link", + "annual_report.summary.followers.new_followers": "{count, plural, one {new follower} other {new followers}}", + "annual_report.summary.highlighted_post.boost_count": "This post was boosted {count, plural, one {once} other {# times}}.", + "annual_report.summary.highlighted_post.favourite_count": "This post was favourited {count, plural, one {once} other {# times}}.", + "annual_report.summary.highlighted_post.reply_count": "This post got {count, plural, one {one reply} other {# replies}}.", + "annual_report.summary.highlighted_post.title": "Most popular post", "annual_report.summary.most_used_app.most_used_app": "most used app", "annual_report.summary.most_used_hashtag.most_used_hashtag": "most used hashtag", - "annual_report.summary.most_used_hashtag.none": "None", + "annual_report.summary.most_used_hashtag.used_count": "You included this hashtag in {count, plural, one {one post} other {# posts}}.", + "annual_report.summary.most_used_hashtag.used_count_public": "{name} included this hashtag in {count, plural, one {one post} other {# posts}}.", "annual_report.summary.new_posts.new_posts": "new posts", "annual_report.summary.percentile.text": "That puts you in the topof {domain} users.", "annual_report.summary.percentile.we_wont_tell_bernie": "We won't tell Bernie.", - "annual_report.summary.thanks": "Thanks for being part of Mastodon!", + "annual_report.summary.share_elsewhere": "Share elsewhere", + "annual_report.summary.share_message": "I got the {archetype} archetype!", + "annual_report.summary.share_on_mastodon": "Share on Mastodon", "attachments_list.unprocessed": "(unprocessed)", "audio.hide": "Hide audio", "block_modal.remote_users_caveat": "We will ask the server {domain} to respect your decision. However, compliance is not guaranteed since some servers may handle blocks differently. Public posts may still be visible to non-logged-in users.", @@ -152,6 +184,8 @@ "bundle_modal_error.close": "Close", "bundle_modal_error.message": "Something went wrong while loading this screen.", "bundle_modal_error.retry": "Try again", + "carousel.current": "Slide {current, number} / {max, number}", + "carousel.slide": "Slide {current, number} of {max, number}", "closed_registrations.other_server_instructions": "Since Mastodon is decentralised, you can create an account on another server and still interact with this one.", "closed_registrations_modal.description": "Creating an account on {domain} is currently not possible, but please keep in mind that you do not need an account specifically on {domain} to use Mastodon.", "closed_registrations_modal.find_another_server": "Find another server", @@ -168,6 +202,8 @@ "column.edit_list": "Edit list", "column.favourites": "Favourites", "column.firehose": "Live feeds", + "column.firehose_local": "Live feed for this server", + "column.firehose_singular": "Live feed", "column.follow_requests": "Follow requests", "column.home": "Home", "column.list_members": "Manage list members", @@ -187,6 +223,7 @@ "community.column_settings.local_only": "Local only", "community.column_settings.media_only": "Media Only", "community.column_settings.remote_only": "Remote only", + "compose.error.blank_post": "Post can't be blank.", "compose.language.change": "Change language", "compose.language.search": "Search languages...", "compose.published.body": "Post published.", @@ -239,15 +276,30 @@ "confirmations.missing_alt_text.secondary": "Post anyway", "confirmations.missing_alt_text.title": "Add alt text?", "confirmations.mute.confirm": "Mute", + "confirmations.private_quote_notify.cancel": "Back to editing", + "confirmations.private_quote_notify.confirm": "Publish post", + "confirmations.private_quote_notify.do_not_show_again": "Don't show me this message again", + "confirmations.private_quote_notify.message": "The person you are quoting and other mentions will be notified and will be able to view your post, even if they're not following you.", + "confirmations.private_quote_notify.title": "Share with followers and mentioned users?", + "confirmations.quiet_post_quote_info.dismiss": "Don't remind me again", + "confirmations.quiet_post_quote_info.got_it": "Got it", + "confirmations.quiet_post_quote_info.message": "When quoting a quiet public post, your post will be hidden from trending timelines.", + "confirmations.quiet_post_quote_info.title": "Quoting quiet public posts", "confirmations.redraft.confirm": "Delete & redraft", "confirmations.redraft.message": "Are you sure you want to delete this post and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.", - "confirmations.redraft.title": "Delete & redraft post?", + "confirmations.redraft.title": "Delete and redraft post?", "confirmations.remove_from_followers.confirm": "Remove follower", "confirmations.remove_from_followers.message": "{name} will stop following you. Are you sure you want to proceed?", "confirmations.remove_from_followers.title": "Remove follower?", + "confirmations.revoke_quote.confirm": "Remove post", + "confirmations.revoke_quote.message": "This action cannot be undone.", + "confirmations.revoke_quote.title": "Remove post?", + "confirmations.unblock.confirm": "Unblock", + "confirmations.unblock.title": "Unblock {name}?", "confirmations.unfollow.confirm": "Unfollow", - "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?", - "confirmations.unfollow.title": "Unfollow user?", + "confirmations.unfollow.title": "Unfollow {name}?", + "confirmations.withdraw_request.confirm": "Withdraw request", + "confirmations.withdraw_request.title": "Withdraw request to follow {name}?", "content_warning.hide": "Hide post", "content_warning.show": "Show anyway", "content_warning.show_more": "Show more", @@ -276,7 +328,7 @@ "domain_block_modal.you_will_lose_num_followers": "You will lose {followersCount, plural, one {{followersCountDisplay} follower} other {{followersCountDisplay} followers}} and {followingCount, plural, one {{followingCountDisplay} person you follow} other {{followingCountDisplay} people you follow}}.", "domain_block_modal.you_will_lose_relationships": "You will lose all followers and people you follow from this server.", "domain_block_modal.you_wont_see_posts": "You won't see posts or notifications from users on this server.", - "domain_pill.activitypub_lets_connect": "It lets you connect and interact with people not just on Mastodon, but across different social apps too.", + "domain_pill.activitypub_lets_connect": "It lets you connect and interact with people, not just on Mastodon, but across different social apps too.", "domain_pill.activitypub_like_language": "ActivityPub is like the language Mastodon speaks with other social networks.", "domain_pill.server": "Server", "domain_pill.their_handle": "Their handle:", @@ -287,8 +339,9 @@ "domain_pill.who_they_are": "Since handles say who someone is and where they are, you can interact with people across the social web of .", "domain_pill.who_you_are": "Because your handle says who you are and where you are, people can interact with you across the social web of .", "domain_pill.your_handle": "Your handle:", - "domain_pill.your_server": "Your digital home, where all of your posts live. Don’t like this one? Transfer servers at any time and bring your followers, too.", + "domain_pill.your_server": "Your digital home, where all of your posts live. Don’t like this one? Transfer servers at any time and bring your followers too.", "domain_pill.your_username": "Your unique identifier on this server. It’s possible to find users with the same username on different servers.", + "dropdown.empty": "Select an option", "embed.instructions": "Embed this post on your website by copying the code below.", "embed.preview": "Here is what it will look like:", "emoji_button.activity": "Activity", @@ -309,7 +362,7 @@ "empty_column.account_featured.me": "You have not featured anything yet. Did you know that you can feature your hashtags you use the most, and even your friend’s accounts on your profile?", "empty_column.account_featured.other": "{acct} has not featured anything yet. Did you know that you can feature your hashtags you use the most, and even your friend’s accounts on your profile?", "empty_column.account_featured_other.unknown": "This account has not featured anything yet.", - "empty_column.account_hides_collections": "This user has chosen to not make this information available", + "empty_column.account_hides_collections": "This user has chosen not to make this information available", "empty_column.account_suspended": "Account suspended", "empty_column.account_timeline": "No posts here!", "empty_column.account_unavailable": "Profile unavailable", @@ -317,6 +370,7 @@ "empty_column.bookmarked_statuses": "You don't have any bookmarked posts yet. When you bookmark one, it will show up here.", "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!", "empty_column.direct": "You don't have any private mentions yet. When you send or receive one, it will show up here.", + "empty_column.disabled_feed": "This feed has been disabled by your server administrators.", "empty_column.domain_blocks": "There are no blocked domains yet.", "empty_column.explore_statuses": "Nothing is trending right now. Check back later!", "empty_column.favourited_statuses": "You don't have any favourite posts yet. When you favourite one, it will show up here.", @@ -327,9 +381,10 @@ "empty_column.home": "Your home timeline is empty! Follow more people to fill it up.", "empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.", "empty_column.mutes": "You haven't muted any users yet.", - "empty_column.notification_requests": "All clear! There is nothing here. When you receive new notifications, they will appear here according to your settings.", + "empty_column.notification_requests": "All clear! There is nothing here. When you receive new notifications, they will appear here, according to your settings.", "empty_column.notifications": "You don't have any notifications yet. When other people interact with you, you will see it here.", "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other servers to fill it up", + "error.no_hashtag_feed_access": "Join or log in to view and follow this hashtag.", "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.", "error.unexpected_crash.explanation_addons": "This page could not be displayed correctly. This error is likely caused by a browser add-on or automatic translation tools.", "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.", @@ -341,11 +396,9 @@ "explore.trending_links": "News", "explore.trending_statuses": "Posts", "explore.trending_tags": "Hashtags", + "featured_carousel.current": "Post {current, number} / {max, number}", "featured_carousel.header": "{count, plural, one {Pinned Post} other {Pinned Posts}}", - "featured_carousel.next": "Next", - "featured_carousel.post": "Post", - "featured_carousel.previous": "Previous", - "featured_carousel.slide": "{index} of {total}", + "featured_carousel.slide": "Post {current, number} of {max, number}", "filter_modal.added.context_mismatch_explanation": "This filter category does not apply to the context in which you have accessed this post. If you want the post to be filtered in this context too, you will have to edit the filter.", "filter_modal.added.context_mismatch_title": "Context mismatch!", "filter_modal.added.expired_explanation": "This filter category has expired, you will need to change the expiration date for it to apply.", @@ -388,6 +441,9 @@ "follow_suggestions.who_to_follow": "Who to follow", "followed_tags": "Followed hashtags", "footer.about": "About", + "footer.about_mastodon": "About Mastodon", + "footer.about_server": "About {domain}", + "footer.about_this_server": "About", "footer.directory": "Profiles directory", "footer.get_app": "Get the app", "footer.keyboard_shortcuts": "Keyboard shortcuts", @@ -445,11 +501,13 @@ "ignore_notifications_modal.private_mentions_title": "Ignore notifications from unsolicited Private Mentions?", "info_button.label": "Help", "info_button.what_is_alt_text": "

What is alt text?

Alt text provides image descriptions for people with vision impairments, low-bandwidth connections, or those seeking extra context.

You can improve accessibility and understanding for everyone by writing clear, concise, and objective alt text.

  • Capture important elements
  • Summarise text in images
  • Use regular sentence structure
  • Avoid redundant information
  • Focus on trends and key findings in complex visuals (like diagrams or maps)
", + "interaction_modal.action": "To interact with {name}'s post, you need to sign into your account on whatever Mastodon server you use.", "interaction_modal.go": "Go", "interaction_modal.no_account_yet": "Don't have an account yet?", "interaction_modal.on_another_server": "On a different server", "interaction_modal.on_this_server": "On this server", - "interaction_modal.username_prompt": "E.g. {example}", + "interaction_modal.title": "Sign in to continue", + "interaction_modal.username_prompt": "Eg {example}", "intervals.full.days": "{number, plural, one {# day} other {# days}}", "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}", "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}", @@ -469,6 +527,7 @@ "keyboard_shortcuts.home": "Open home timeline", "keyboard_shortcuts.hotkey": "Hotkey", "keyboard_shortcuts.legend": "to display this legend", + "keyboard_shortcuts.load_more": "Focus \"Load more\" button", "keyboard_shortcuts.local": "to open local timeline", "keyboard_shortcuts.mention": "to mention author", "keyboard_shortcuts.muted": "to open muted users list", @@ -477,6 +536,7 @@ "keyboard_shortcuts.open_media": "to open media", "keyboard_shortcuts.pinned": "to open pinned posts list", "keyboard_shortcuts.profile": "to open author's profile", + "keyboard_shortcuts.quote": "Quote post", "keyboard_shortcuts.reply": "to reply", "keyboard_shortcuts.requests": "to open follow requests list", "keyboard_shortcuts.search": "to focus search", @@ -485,9 +545,12 @@ "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toggle_sensitivity": "Show/hide media", "keyboard_shortcuts.toot": "to start a brand new post", + "keyboard_shortcuts.top": "Move to top of list", "keyboard_shortcuts.translate": "to translate a post", "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", "keyboard_shortcuts.up": "Move up in the list", + "learn_more_link.got_it": "Got it", + "learn_more_link.learn_more": "Learn more", "lightbox.close": "Close", "lightbox.next": "Next", "lightbox.previous": "Previous", @@ -562,7 +625,7 @@ "navigation_bar.preferences": "Preferences", "navigation_bar.privacy_and_reach": "Privacy and reach", "navigation_bar.search": "Search", - "navigation_bar.search_trends": "Search / Trending", + "navigation_bar.search_trends": "Search/Trending", "navigation_panel.collapse_followed_tags": "Collapse followed hashtags menu", "navigation_panel.collapse_lists": "Collapse list menu", "navigation_panel.expand_followed_tags": "Expand followed hashtags menu", @@ -588,6 +651,7 @@ "notification.label.mention": "Mention", "notification.label.private_mention": "Private mention", "notification.label.private_reply": "Private reply", + "notification.label.quote": "{name} quoted your post", "notification.label.reply": "Reply", "notification.mention": "Mention", "notification.mentioned_you": "{name} mentioned you", @@ -601,7 +665,8 @@ "notification.moderation_warning.action_silence": "Your account has been limited.", "notification.moderation_warning.action_suspend": "Your account has been suspended.", "notification.own_poll": "Your poll has ended", - "notification.poll": "A poll you voted in has ended", + "notification.poll": "A poll in which you voted has ended", + "notification.quoted_update": "{name} edited a post you have quoted", "notification.reblog": "{name} boosted your post", "notification.reblog.name_and_others_with_link": "{name} and {count, plural, one {# other} other {# others}} boosted your post", "notification.relationships_severance_event": "Lost connections with {name}", @@ -626,7 +691,7 @@ "notification_requests.explainer_for_limited_account": "Notifications from this account have been filtered because the account has been limited by a moderator.", "notification_requests.explainer_for_limited_remote_account": "Notifications from this account have been filtered because the account or its server has been limited by a moderator.", "notification_requests.maximize": "Maximise", - "notification_requests.minimize_banner": "Minimize filtered notifications banner", + "notification_requests.minimize_banner": "Minimise filtered notifications banner", "notification_requests.notifications_from": "Notifications from {name}", "notification_requests.title": "Filtered notifications", "notification_requests.view": "View notifications", @@ -645,6 +710,7 @@ "notifications.column_settings.mention": "Mentions:", "notifications.column_settings.poll": "Poll results:", "notifications.column_settings.push": "Push notifications", + "notifications.column_settings.quote": "Quotes:", "notifications.column_settings.reblog": "Boosts:", "notifications.column_settings.show": "Show in column", "notifications.column_settings.sound": "Play sound", @@ -675,11 +741,11 @@ "notifications.policy.filter_limited_accounts_title": "Moderated accounts", "notifications.policy.filter_new_accounts.hint": "Created within the past {days, plural, one {one day} other {# days}}", "notifications.policy.filter_new_accounts_title": "New accounts", - "notifications.policy.filter_not_followers_hint": "Including people who have been following you fewer than {days, plural, one {one day} other {# days}}", + "notifications.policy.filter_not_followers_hint": "Including people who have been following you for fewer than {days, plural, one {one day} other {# days}}", "notifications.policy.filter_not_followers_title": "People not following you", "notifications.policy.filter_not_following_hint": "Until you manually approve them", "notifications.policy.filter_not_following_title": "People you don't follow", - "notifications.policy.filter_private_mentions_hint": "Filtered unless it's in reply to your own mention or if you follow the sender", + "notifications.policy.filter_private_mentions_hint": "Filtered, unless it's in reply to your own mention, or if you follow the sender", "notifications.policy.filter_private_mentions_title": "Unsolicited private mentions", "notifications.policy.title": "Manage notifications from…", "notifications_permission_banner.enable": "Enable desktop notifications", @@ -720,10 +786,20 @@ "privacy.private.short": "Followers", "privacy.public.long": "Anyone on and off Mastodon", "privacy.public.short": "Public", + "privacy.quote.anyone": "{visibility}, anyone can quote", + "privacy.quote.disabled": "{visibility}, quotes disabled", + "privacy.quote.limited": "{visibility}, quotes limited", "privacy.unlisted.additional": "This behaves exactly like public, except the post will not appear in live feeds or hashtags, explore, or Mastodon search, even if you are opted-in account-wide.", + "privacy.unlisted.long": "Hidden from Mastodon search results, trending, and public timelines", "privacy.unlisted.short": "Quiet public", "privacy_policy.last_updated": "Last updated {date}", "privacy_policy.title": "Privacy Policy", + "quote_error.edit": "Quotes cannot be added when editing a post.", + "quote_error.poll": "Quoting is not allowed with polls.", + "quote_error.private_mentions": "Quoting is not allowed with direct mentions.", + "quote_error.quote": "Only one quote at a time is allowed.", + "quote_error.unauthorized": "You are not authorised to quote this post.", + "quote_error.upload": "Quoting is not allowed with media attachments.", "recommended": "Recommended", "refresh": "Refresh", "regeneration_indicator.please_stand_by": "Please stand by.", @@ -739,6 +815,9 @@ "relative_time.minutes": "{number}m", "relative_time.seconds": "{number}s", "relative_time.today": "today", + "remove_quote_hint.button_label": "Got it", + "remove_quote_hint.message": "You can do so from the {icon} options menu.", + "remove_quote_hint.title": "Want to remove your quoted post?", "reply_indicator.attachments": "{count, plural, one {# attachment} other {# attachments}}", "reply_indicator.cancel": "Cancel", "reply_indicator.poll": "Poll", @@ -813,30 +892,40 @@ "search_results.all": "All", "search_results.hashtags": "Hashtags", "search_results.no_results": "No results.", - "search_results.no_search_yet": "Try searching for posts, profiles or hashtags.", + "search_results.no_search_yet": "Try searching for posts, profiles, or hashtags.", "search_results.see_all": "See all", "search_results.statuses": "Posts", "search_results.title": "Search for \"{q}\"", "server_banner.about_active_users": "People using this server during the last 30 days (Monthly Active Users)", "server_banner.active_users": "active users", "server_banner.administered_by": "Administered by:", - "server_banner.is_one_of_many": "{domain} is one of the many independent Mastodon servers you can use to participate in the fediverse.", + "server_banner.is_one_of_many": "{domain} is one of the many independent Mastodon servers you can use to participate in the Fediverse.", "server_banner.server_stats": "Server stats:", "sign_in_banner.create_account": "Create account", - "sign_in_banner.follow_anyone": "Follow anyone across the fediverse and see it all in chronological order. No algorithms, ads, or clickbait in sight.", + "sign_in_banner.follow_anyone": "Follow anyone across the Fediverse and see it all in chronological order. No algorithms, ads, or clickbait in sight.", "sign_in_banner.mastodon_is": "Mastodon is the best way to keep up with what's happening.", "sign_in_banner.sign_in": "Sign in", "sign_in_banner.sso_redirect": "Login or Register", "status.admin_account": "Open moderation interface for @{name}", "status.admin_domain": "Open moderation interface for {domain}", "status.admin_status": "Open this post in the moderation interface", + "status.all_disabled": "Boosts and quotes are disabled", "status.block": "Block @{name}", "status.bookmark": "Bookmark", "status.cancel_reblog_private": "Unboost", + "status.cannot_quote": "You are not allowed to quote this post", "status.cannot_reblog": "This post cannot be boosted", + "status.contains_quote": "Contains quote", + "status.context.loading": "Loading more replies", + "status.context.loading_error": "Couldn't load new replies", + "status.context.loading_success": "New replies loaded", + "status.context.more_replies_found": "More replies found", + "status.context.retry": "Retry", + "status.context.show": "Show", "status.continued_thread": "Continued thread", "status.copy": "Copy link to status", "status.delete": "Delete", + "status.delete.success": "Post deleted", "status.detailed_status": "Detailed conversation view", "status.direct": "Privately mention @{name}", "status.direct_indicator": "Private mention", @@ -845,7 +934,7 @@ "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}", "status.embed": "Get embed code", "status.favourite": "Favourite", - "status.favourites": "{count, plural, one {favourite} other {favourites}}", + "status.favourites_count": "{count, plural, one {{counter} favourite} other {{counter} favourites}}", "status.filter": "Filter this post", "status.history.created": "{name} created {date}", "status.history.edited": "{name} edited {date}", @@ -859,20 +948,46 @@ "status.mute_conversation": "Mute conversation", "status.open": "Expand this post", "status.pin": "Pin on profile", + "status.quote": "Quote", + "status.quote.cancel": "Cancel quote", + "status.quote_error.blocked_account_hint.title": "This post is hidden because you've blocked @{name}.", + "status.quote_error.blocked_domain_hint.title": "This post is hidden because you've blocked {domain}.", "status.quote_error.filtered": "Hidden due to one of your filters", + "status.quote_error.limited_account_hint.action": "Show anyway", + "status.quote_error.limited_account_hint.title": "This account has been hidden by the moderators of {domain}.", + "status.quote_error.muted_account_hint.title": "This post is hidden because you've muted @{name}.", + "status.quote_error.not_available": "Post unavailable", + "status.quote_error.pending_approval": "Post pending", + "status.quote_error.pending_approval_popout.body": "On Mastodon, you can control whether someone can quote you. This post is pending while we're getting the original author's approval.", + "status.quote_error.revoked": "Post removed by author", + "status.quote_followers_only": "Only followers can quote this post", + "status.quote_manual_review": "Author will manually review", + "status.quote_noun": "Quote", + "status.quote_policy_change": "Change who can quote", + "status.quote_post_author": "Quoted a post by @{name}", + "status.quote_private": "Private posts cannot be quoted", + "status.quotes.empty": "No one has quoted this post yet. When someone does, it will show up here.", + "status.quotes.local_other_disclaimer": "Quotes rejected by the author will not be shown.", + "status.quotes.remote_other_disclaimer": "Only quotes from {domain} are guaranteed to be shown here. Quotes rejected by the author will not be shown.", + "status.quotes_count": "{count, plural, one {{counter} quote} other {{counter} quotes}}", "status.read_more": "Read more", "status.reblog": "Boost", + "status.reblog_or_quote": "Boost or quote", + "status.reblog_private": "Share again with your followers", "status.reblogged_by": "{name} boosted", - "status.reblogs": "{count, plural, one {boost} other {boosts}}", "status.reblogs.empty": "No one has boosted this post yet. When someone does, they will show up here.", + "status.reblogs_count": "{count, plural, one {{counter} boost} other {{counter} boosts}}", "status.redraft": "Delete & re-draft", "status.remove_bookmark": "Remove bookmark", "status.remove_favourite": "Remove from favourites", + "status.remove_quote": "Remove", "status.replied_in_thread": "Replied in thread", "status.replied_to": "Replied to {name}", "status.reply": "Reply", "status.replyAll": "Reply to thread", "status.report": "Report @{name}", + "status.request_quote": "Request to quote", + "status.revoke_quote": "Remove my post from @{name}’s post", "status.sensitive_warning": "Sensitive content", "status.share": "Share", "status.show_less_all": "Show less for all", @@ -910,6 +1025,7 @@ "upload_button.label": "Add images, a video or an audio file", "upload_error.limit": "File upload limit exceeded.", "upload_error.poll": "File upload not allowed with polls.", + "upload_error.quote": "File upload not allowed with quotes.", "upload_form.drag_and_drop.instructions": "To pick up a media attachment, press space or enter. While dragging, use the arrow keys to move the media attachment in any given direction. Press space or enter again to drop the media attachment in its new position, or press escape to cancel.", "upload_form.drag_and_drop.on_drag_cancel": "Dragging was cancelled. Media attachment {item} was dropped.", "upload_form.drag_and_drop.on_drag_end": "Media attachment {item} was dropped.", @@ -928,9 +1044,25 @@ "video.mute": "Mute", "video.pause": "Pause", "video.play": "Play", - "video.skip_backward": "Skip backward", + "video.skip_backward": "Skip backwards", "video.skip_forward": "Skip forward", "video.unmute": "Unmute", "video.volume_down": "Volume down", - "video.volume_up": "Volume up" + "video.volume_up": "Volume up", + "visibility_modal.button_title": "Set visibility", + "visibility_modal.direct_quote_warning.text": "If you save the current settings, the embedded quote will be converted to a link.", + "visibility_modal.direct_quote_warning.title": "Quotes can't be embedded in private mentions", + "visibility_modal.header": "Visibility and interaction", + "visibility_modal.helper.direct_quoting": "Private mentions authored on Mastodon can't be quoted by others.", + "visibility_modal.helper.privacy_editing": "Visibility can't be changed after a post is published.", + "visibility_modal.helper.privacy_private_self_quote": "Self-quotes of private posts cannot be made public.", + "visibility_modal.helper.private_quoting": "Follower-only posts authored on Mastodon can't be quoted by others.", + "visibility_modal.helper.unlisted_quoting": "When people quote you, their post will also be hidden from trending timelines.", + "visibility_modal.instructions": "Control who can interact with this post. You can also apply settings to all future posts by navigating to Preferences > Posting defaults.", + "visibility_modal.privacy_label": "Visibility", + "visibility_modal.quote_followers": "Followers only", + "visibility_modal.quote_label": "Who can quote", + "visibility_modal.quote_nobody": "Just me", + "visibility_modal.quote_public": "Anyone", + "visibility_modal.save": "Save" } diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index f949c3033963a5..00b3029587b479 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Stop notifying me when @{name} posts", "account.domain_blocking": "Blocking domain", "account.edit_profile": "Edit profile", + "account.edit_profile_short": "Edit", "account.enable_notifications": "Notify me when @{name} posts", "account.endorse": "Feature on profile", "account.familiar_followers_many": "Followed by {name1}, {name2}, and {othersCount, plural, one {one other you know} other {# others you know}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "No posts", "account.follow": "Follow", "account.follow_back": "Follow back", + "account.follow_back_short": "Follow back", + "account.follow_request": "Request to follow", + "account.follow_request_cancel": "Cancel request", + "account.follow_request_cancel_short": "Cancel", + "account.follow_request_short": "Request", "account.followers": "Followers", "account.followers.empty": "No one follows this user yet.", "account.followers_counter": "{count, plural, one {{counter} follower} other {{counter} followers}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Posts and replies", "account.remove_from_followers": "Remove {name} from followers", "account.report": "Report @{name}", - "account.requested": "Awaiting approval. Click to cancel follow request", "account.requested_follow": "{name} has requested to follow you", "account.requests_to_follow_you": "Requests to follow you", "account.share": "Share @{name}'s profile", @@ -108,25 +113,52 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Describe this for people with visual impairments…", "alt_text_modal.done": "Done", "announcement.announcement": "Announcement", - "annual_report.summary.archetype.booster": "The cool-hunter", - "annual_report.summary.archetype.lurker": "The lurker", - "annual_report.summary.archetype.oracle": "The oracle", - "annual_report.summary.archetype.pollster": "The pollster", - "annual_report.summary.archetype.replier": "The social butterfly", - "annual_report.summary.followers.followers": "followers", - "annual_report.summary.followers.total": "{count} total", - "annual_report.summary.here_it_is": "Here is your {year} in review:", - "annual_report.summary.highlighted_post.by_favourites": "most favourited post", - "annual_report.summary.highlighted_post.by_reblogs": "most boosted post", - "annual_report.summary.highlighted_post.by_replies": "post with the most replies", - "annual_report.summary.highlighted_post.possessive": "{name}'s", + "annual_report.announcement.action_build": "Build my Wrapstodon", + "annual_report.announcement.action_dismiss": "No thanks", + "annual_report.announcement.action_view": "View my Wrapstodon", + "annual_report.announcement.description": "Discover more about your engagement on Mastodon over the past year.", + "annual_report.announcement.title": "Wrapstodon {year} has arrived", + "annual_report.nav_item.badge": "New", + "annual_report.shared_page.donate": "Donate", + "annual_report.shared_page.footer": "Generated with {heart} by the Mastodon team", + "annual_report.shared_page.footer_server_info": "{username} uses {domain}, one of many communities powered by Mastodon.", + "annual_report.summary.archetype.booster.desc_public": "{name} stayed on the hunt for posts to boost, amplifying other creators with perfect aim.", + "annual_report.summary.archetype.booster.desc_self": "You stayed on the hunt for posts to boost, amplifying other creators with perfect aim.", + "annual_report.summary.archetype.booster.name": "The Archer", + "annual_report.summary.archetype.die_drei_fragezeichen": "???", + "annual_report.summary.archetype.lurker.desc_public": "We know {name} was out there, somewhere, enjoying Mastodon in their own quiet way.", + "annual_report.summary.archetype.lurker.desc_self": "We know you were out there, somewhere, enjoying Mastodon in your own quiet way.", + "annual_report.summary.archetype.lurker.name": "The Stoic", + "annual_report.summary.archetype.oracle.desc_public": "{name} created new posts more than replies, keeping Mastodon fresh and future-facing.", + "annual_report.summary.archetype.oracle.desc_self": "You created new posts more than replies, keeping Mastodon fresh and future-facing.", + "annual_report.summary.archetype.oracle.name": "The Oracle", + "annual_report.summary.archetype.pollster.desc_public": "{name} created more polls than other post types, cultivating curiosity on Mastodon.", + "annual_report.summary.archetype.pollster.desc_self": "You created more polls than other post types, cultivating curiosity on Mastodon.", + "annual_report.summary.archetype.pollster.name": "The Wonderer", + "annual_report.summary.archetype.replier.desc_public": "{name} frequently replied to other people’s posts, pollinating Mastodon with new discussions.", + "annual_report.summary.archetype.replier.desc_self": "You frequently replied to other people’s posts, pollinating Mastodon with new discussions.", + "annual_report.summary.archetype.replier.name": "The Butterfly", + "annual_report.summary.archetype.reveal": "Reveal my archetype", + "annual_report.summary.archetype.reveal_description": "Thanks for being part of Mastodon! Time to find out which archetype you embodied in {year}.", + "annual_report.summary.archetype.title_public": "{name}'s archetype", + "annual_report.summary.archetype.title_self": "Your archetype", + "annual_report.summary.close": "Close", + "annual_report.summary.copy_link": "Copy link", + "annual_report.summary.followers.new_followers": "{count, plural, one {new follower} other {new followers}}", + "annual_report.summary.highlighted_post.boost_count": "This post was boosted {count, plural, one {once} other {# times}}.", + "annual_report.summary.highlighted_post.favourite_count": "This post was favorited {count, plural, one {once} other {# times}}.", + "annual_report.summary.highlighted_post.reply_count": "This post got {count, plural, one {one reply} other {# replies}}.", + "annual_report.summary.highlighted_post.title": "Most popular post", "annual_report.summary.most_used_app.most_used_app": "most used app", "annual_report.summary.most_used_hashtag.most_used_hashtag": "most used hashtag", - "annual_report.summary.most_used_hashtag.none": "None", + "annual_report.summary.most_used_hashtag.used_count": "You included this hashtag in {count, plural, one {one post} other {# posts}}.", + "annual_report.summary.most_used_hashtag.used_count_public": "{name} included this hashtag in {count, plural, one {one post} other {# posts}}.", "annual_report.summary.new_posts.new_posts": "new posts", "annual_report.summary.percentile.text": "That puts you in the topof {domain} users.", "annual_report.summary.percentile.we_wont_tell_bernie": "We won't tell Bernie.", - "annual_report.summary.thanks": "Thanks for being part of Mastodon!", + "annual_report.summary.share_elsewhere": "Share elsewhere", + "annual_report.summary.share_message": "I got the {archetype} archetype!", + "annual_report.summary.share_on_mastodon": "Share on Mastodon", "attachments_list.unprocessed": "(unprocessed)", "audio.hide": "Hide audio", "block_modal.remote_users_caveat": "We will ask the server {domain} to respect your decision. However, compliance is not guaranteed since some servers may handle blocks differently. Public posts may still be visible to non-logged-in users.", @@ -152,6 +184,8 @@ "bundle_modal_error.close": "Close", "bundle_modal_error.message": "Something went wrong while loading this screen.", "bundle_modal_error.retry": "Try again", + "carousel.current": "Slide {current, number} / {max, number}", + "carousel.slide": "Slide {current, number} of {max, number}", "closed_registrations.other_server_instructions": "Since Mastodon is decentralized, you can create an account on another server and still interact with this one.", "closed_registrations_modal.description": "Creating an account on {domain} is currently not possible, but please keep in mind that you do not need an account specifically on {domain} to use Mastodon.", "closed_registrations_modal.find_another_server": "Find another server", @@ -168,6 +202,8 @@ "column.edit_list": "Edit list", "column.favourites": "Favorites", "column.firehose": "Live feeds", + "column.firehose_local": "Live feed for this server", + "column.firehose_singular": "Live feed", "column.follow_requests": "Follow requests", "column.home": "Home", "column.list_members": "Manage list members", @@ -187,6 +223,7 @@ "community.column_settings.local_only": "Local only", "community.column_settings.media_only": "Media Only", "community.column_settings.remote_only": "Remote only", + "compose.error.blank_post": "Post can't be blank.", "compose.language.change": "Change language", "compose.language.search": "Search languages...", "compose.published.body": "Post published.", @@ -239,6 +276,11 @@ "confirmations.missing_alt_text.secondary": "Post anyway", "confirmations.missing_alt_text.title": "Add alt text?", "confirmations.mute.confirm": "Mute", + "confirmations.private_quote_notify.cancel": "Back to editing", + "confirmations.private_quote_notify.confirm": "Publish post", + "confirmations.private_quote_notify.do_not_show_again": "Don't show me this message again", + "confirmations.private_quote_notify.message": "The person you are quoting and other mentions will be notified and will be able to view your post, even if they're not following you.", + "confirmations.private_quote_notify.title": "Share with followers and mentioned users?", "confirmations.quiet_post_quote_info.dismiss": "Don't remind me again", "confirmations.quiet_post_quote_info.got_it": "Got it", "confirmations.quiet_post_quote_info.message": "When quoting a quiet public post, your post will be hidden from trending timelines.", @@ -252,9 +294,12 @@ "confirmations.revoke_quote.confirm": "Remove post", "confirmations.revoke_quote.message": "This action cannot be undone.", "confirmations.revoke_quote.title": "Remove post?", + "confirmations.unblock.confirm": "Unblock", + "confirmations.unblock.title": "Unblock {name}?", "confirmations.unfollow.confirm": "Unfollow", - "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?", - "confirmations.unfollow.title": "Unfollow user?", + "confirmations.unfollow.title": "Unfollow {name}?", + "confirmations.withdraw_request.confirm": "Withdraw request", + "confirmations.withdraw_request.title": "Withdraw request to follow {name}?", "content_warning.hide": "Hide post", "content_warning.show": "Show anyway", "content_warning.show_more": "Show more", @@ -325,6 +370,7 @@ "empty_column.bookmarked_statuses": "You don't have any bookmarked posts yet. When you bookmark one, it will show up here.", "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!", "empty_column.direct": "You don't have any private mentions yet. When you send or receive one, it will show up here.", + "empty_column.disabled_feed": "This feed has been disabled by your server administrators.", "empty_column.domain_blocks": "There are no blocked domains yet.", "empty_column.explore_statuses": "Nothing is trending right now. Check back later!", "empty_column.favourited_statuses": "You don't have any favorite posts yet. When you favorite one, it will show up here.", @@ -338,6 +384,7 @@ "empty_column.notification_requests": "All clear! There is nothing here. When you receive new notifications, they will appear here according to your settings.", "empty_column.notifications": "You don't have any notifications yet. When other people interact with you, you will see it here.", "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other servers to fill it up", + "error.no_hashtag_feed_access": "Join or log in to view and follow this hashtag.", "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.", "error.unexpected_crash.explanation_addons": "This page could not be displayed correctly. This error is likely caused by a browser add-on or automatic translation tools.", "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.", @@ -349,11 +396,9 @@ "explore.trending_links": "News", "explore.trending_statuses": "Posts", "explore.trending_tags": "Hashtags", + "featured_carousel.current": "Post {current, number} / {max, number}", "featured_carousel.header": "{count, plural, one {Pinned Post} other {Pinned Posts}}", - "featured_carousel.next": "Next", - "featured_carousel.post": "Post", - "featured_carousel.previous": "Previous", - "featured_carousel.slide": "{index} of {total}", + "featured_carousel.slide": "Post {current, number} of {max, number}", "filter_modal.added.context_mismatch_explanation": "This filter category does not apply to the context in which you have accessed this post. If you want the post to be filtered in this context too, you will have to edit the filter.", "filter_modal.added.context_mismatch_title": "Context mismatch!", "filter_modal.added.expired_explanation": "This filter category has expired, you will need to change the expiration date for it to apply.", @@ -396,6 +441,9 @@ "follow_suggestions.who_to_follow": "Who to follow", "followed_tags": "Followed hashtags", "footer.about": "About", + "footer.about_mastodon": "About Mastodon", + "footer.about_server": "About {domain}", + "footer.about_this_server": "About", "footer.directory": "Profiles directory", "footer.get_app": "Get the app", "footer.keyboard_shortcuts": "Keyboard shortcuts", @@ -497,6 +545,7 @@ "keyboard_shortcuts.toggle_hidden": "Show/hide text behind CW", "keyboard_shortcuts.toggle_sensitivity": "Show/hide media", "keyboard_shortcuts.toot": "Start a new post", + "keyboard_shortcuts.top": "Move to top of list", "keyboard_shortcuts.translate": "to translate a post", "keyboard_shortcuts.unfocus": "Unfocus compose textarea/search", "keyboard_shortcuts.up": "Move up in the list", @@ -745,7 +794,9 @@ "privacy.unlisted.short": "Quiet public", "privacy_policy.last_updated": "Last updated {date}", "privacy_policy.title": "Privacy Policy", + "quote_error.edit": "Quotes cannot be added when editing a post.", "quote_error.poll": "Quoting is not allowed with polls.", + "quote_error.private_mentions": "Quoting is not allowed with direct mentions.", "quote_error.quote": "Only one quote at a time is allowed.", "quote_error.unauthorized": "You are not authorized to quote this post.", "quote_error.upload": "Quoting is not allowed with media attachments.", @@ -867,8 +918,7 @@ "status.contains_quote": "Contains quote", "status.context.loading": "Loading more replies", "status.context.loading_error": "Couldn't load new replies", - "status.context.loading_more": "Loading more replies", - "status.context.loading_success": "All replies loaded", + "status.context.loading_success": "New replies loaded", "status.context.more_replies_found": "More replies found", "status.context.retry": "Retry", "status.context.show": "Show", @@ -884,7 +934,7 @@ "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}", "status.embed": "Get embed code", "status.favourite": "Favorite", - "status.favourites": "{count, plural, one {favorite} other {favorites}}", + "status.favourites_count": "{count, plural, one {{counter} favorite} other {{counter} favorites}}", "status.filter": "Filter this post", "status.history.created": "{name} created {date}", "status.history.edited": "{name} edited {date}", @@ -900,9 +950,12 @@ "status.pin": "Pin on profile", "status.quote": "Quote", "status.quote.cancel": "Cancel quote", + "status.quote_error.blocked_account_hint.title": "This post is hidden because you've blocked @{name}.", + "status.quote_error.blocked_domain_hint.title": "This post is hidden because you've blocked {domain}.", "status.quote_error.filtered": "Hidden due to one of your filters", "status.quote_error.limited_account_hint.action": "Show anyway", "status.quote_error.limited_account_hint.title": "This account has been hidden by the moderators of {domain}.", + "status.quote_error.muted_account_hint.title": "This post is hidden because you've muted @{name}.", "status.quote_error.not_available": "Post unavailable", "status.quote_error.pending_approval": "Post pending", "status.quote_error.pending_approval_popout.body": "On Mastodon, you can control whether someone can quote you. This post is pending while we're getting the original author's approval.", @@ -913,15 +966,17 @@ "status.quote_policy_change": "Change who can quote", "status.quote_post_author": "Quoted a post by @{name}", "status.quote_private": "Private posts cannot be quoted", - "status.quotes": "{count, plural, one {quote} other {quotes}}", "status.quotes.empty": "No one has quoted this post yet. When someone does, it will show up here.", + "status.quotes.local_other_disclaimer": "Quotes rejected by the author will not be shown.", + "status.quotes.remote_other_disclaimer": "Only quotes from {domain} are guaranteed to be shown here. Quotes rejected by the author will not be shown.", + "status.quotes_count": "{count, plural, one {{counter} quote} other {{counter} quotes}}", "status.read_more": "Read more", "status.reblog": "Boost", "status.reblog_or_quote": "Boost or quote", "status.reblog_private": "Share again with your followers", "status.reblogged_by": "{name} boosted", - "status.reblogs": "{count, plural, one {boost} other {boosts}}", "status.reblogs.empty": "No one has boosted this post yet. When someone does, they will show up here.", + "status.reblogs_count": "{count, plural, one {{counter} boost} other {{counter} boosts}}", "status.redraft": "Delete & re-draft", "status.remove_bookmark": "Remove bookmark", "status.remove_favourite": "Remove from favorites", @@ -995,6 +1050,8 @@ "video.volume_down": "Volume down", "video.volume_up": "Volume up", "visibility_modal.button_title": "Set visibility", + "visibility_modal.direct_quote_warning.text": "If you save the current settings, the embedded quote will be converted to a link.", + "visibility_modal.direct_quote_warning.title": "Quotes can't be embedded in private mentions", "visibility_modal.header": "Visibility and interaction", "visibility_modal.helper.direct_quoting": "Private mentions authored on Mastodon can't be quoted by others.", "visibility_modal.helper.privacy_editing": "Visibility can't be changed after a post is published.", diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json index 34967d20b09b5a..f5904154538b50 100644 --- a/app/javascript/mastodon/locales/eo.json +++ b/app/javascript/mastodon/locales/eo.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Ĉesu sciigi min kiam @{name} afiŝas", "account.domain_blocking": "Blokas domajnon", "account.edit_profile": "Redakti la profilon", + "account.edit_profile_short": "Redakti", "account.enable_notifications": "Sciigu min kiam @{name} afiŝos", "account.endorse": "Montri en profilo", "account.familiar_followers_one": "Sekvita de {name1}", @@ -39,6 +40,8 @@ "account.featured_tags.last_status_never": "Neniu afiŝo", "account.follow": "Sekvi", "account.follow_back": "Sekvu reen", + "account.follow_back_short": "Sekvu reen", + "account.follow_request_cancel_short": "Nuligi", "account.followers": "Sekvantoj", "account.followers.empty": "Ankoraŭ neniu sekvas ĉi tiun uzanton.", "account.followers_counter": "{count, plural, one{{counter} sekvanto} other {{counter} sekvantoj}}", @@ -69,7 +72,6 @@ "account.posts_with_replies": "Afiŝoj kaj respondoj", "account.remove_from_followers": "Forigi {name}-n de sekvantoj", "account.report": "Raporti @{name}", - "account.requested": "Atendo de aprobo. Klaku por nuligi la peton por sekvado", "account.requested_follow": "{name} petis sekvi vin", "account.requests_to_follow_you": "Petoj sekvi vin", "account.share": "Diskonigi la profilon de @{name}", @@ -107,25 +109,16 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Priskribi ĉi tion por homoj kun vidaj malkapabloj…", "alt_text_modal.done": "Farita", "announcement.announcement": "Anonco", - "annual_report.summary.archetype.booster": "La ĉasanto de mojoso", - "annual_report.summary.archetype.lurker": "La vidanto", - "annual_report.summary.archetype.oracle": "La orakolo", - "annual_report.summary.archetype.pollster": "La balotenketisto", - "annual_report.summary.archetype.replier": "La plej societema", - "annual_report.summary.followers.followers": "sekvantoj", - "annual_report.summary.followers.total": "{count} tute", - "annual_report.summary.here_it_is": "Jen via resumo de {year}:", - "annual_report.summary.highlighted_post.by_favourites": "plej ŝatata afiŝo", - "annual_report.summary.highlighted_post.by_reblogs": "plej diskonigita afiŝo", - "annual_report.summary.highlighted_post.by_replies": "afiŝo kun la plej multaj respondoj", - "annual_report.summary.highlighted_post.possessive": "de {name}", + "annual_report.announcement.action_dismiss": "Ne, dankon", + "annual_report.nav_item.badge": "Nova", + "annual_report.shared_page.donate": "Donaci", + "annual_report.summary.copy_link": "Kopii ligilon", + "annual_report.summary.highlighted_post.title": "Plej populara afiŝo", "annual_report.summary.most_used_app.most_used_app": "plej uzita aplikaĵo", "annual_report.summary.most_used_hashtag.most_used_hashtag": "plej uzata kradvorto", - "annual_report.summary.most_used_hashtag.none": "Nenio", "annual_report.summary.new_posts.new_posts": "novaj afiŝoj", "annual_report.summary.percentile.text": "Tion metas vin en la plejde {domain} uzantoj.", "annual_report.summary.percentile.we_wont_tell_bernie": "Ni ne diros al Zamenhof.", - "annual_report.summary.thanks": "Dankon pro esti parto de Mastodon!", "attachments_list.unprocessed": "(neprilaborita)", "audio.hide": "Kaŝi aŭdion", "block_modal.remote_users_caveat": "Ni petos al la servilo {domain} respekti vian elekton. Tamen, plenumo ne estas garantiita ĉar iuj serviloj eble manipulas blokojn malsame. Publikaj afiŝoj eble ankoraŭ estas videbla por ne-ensalutintaj uzantoj.", @@ -249,8 +242,6 @@ "confirmations.revoke_quote.message": "Ĉi tiu ago ne povas esti malfarita.", "confirmations.revoke_quote.title": "Ĉu forigi afiŝon?", "confirmations.unfollow.confirm": "Ne plu sekvi", - "confirmations.unfollow.message": "Ĉu vi certas, ke vi volas ĉesi sekvi {name}?", - "confirmations.unfollow.title": "Ĉu ĉesi sekvi uzanton?", "content_warning.hide": "Kaŝi afiŝon", "content_warning.show": "Montri ĉiukaze", "content_warning.show_more": "Montri pli", @@ -345,10 +336,6 @@ "explore.trending_statuses": "Afiŝoj", "explore.trending_tags": "Kradvortoj", "featured_carousel.header": "{count, plural, one {Alpinglita afiŝo} other {Alpinglitaj afiŝoj}}", - "featured_carousel.next": "Antaŭen", - "featured_carousel.post": "Afiŝi", - "featured_carousel.previous": "Malantaŭen", - "featured_carousel.slide": "{index} de {total}", "filter_modal.added.context_mismatch_explanation": "Ĉi tiu filtrilkategorio ne kongruas kun la kunteksto en kiu vi akcesis ĉi tiun afiŝon. Se vi volas ke la afiŝo estas ankaŭ filtrita en ĉi tiu kunteksto, vi devus redakti la filtrilon.", "filter_modal.added.context_mismatch_title": "Ne kongruas la kunteksto!", "filter_modal.added.expired_explanation": "Ĉi tiu filtrilkategorio eksvalidiĝis, vu bezonos ŝanĝi la eksvaliddaton por ĝi.", @@ -849,8 +836,6 @@ "status.cancel_reblog_private": "Ne plu diskonigi", "status.cannot_quote": "Vi ne rajtas citi ĉi tiun afiŝon", "status.cannot_reblog": "Ĉi tiun afiŝon ne eblas diskonigi", - "status.context.load_new_replies": "Disponeblaj novaj respondoj", - "status.context.loading": "Serĉante pliajn respondojn", "status.continued_thread": "Daŭrigis fadenon", "status.copy": "Kopii la ligilon al la afiŝo", "status.delete": "Forigi", @@ -863,7 +848,6 @@ "status.edited_x_times": "Redactita {count, plural, one {{count} fojon} other {{count} fojojn}}", "status.embed": "Akiri enkorpigan kodon", "status.favourite": "Ŝatata", - "status.favourites": "{count, plural, one {plej ŝatata} other {plej ŝatataj}}", "status.filter": "Filtri ĉi tiun afiŝon", "status.history.created": "{name} kreis {date}", "status.history.edited": "{name} redaktis {date}", @@ -889,14 +873,12 @@ "status.quote_policy_change": "Ŝanĝi kiu povas citi", "status.quote_post_author": "Citis afiŝon de @{name}", "status.quote_private": "Privataj afiŝoj ne povas esti cititaj", - "status.quotes": "{count, plural,one {citaĵo} other {citaĵoj}}", "status.quotes.empty": "Neniu citis ĉi tiun afiŝon ankoraŭ. Kiam iu faros tion, ĝi aperos ĉi tie.", "status.read_more": "Legi pli", "status.reblog": "Diskonigi", "status.reblog_or_quote": "Diskonigi aŭ citi", "status.reblog_private": "Kunhavigu denove kun viaj sekvantoj", "status.reblogged_by": "{name} diskonigis", - "status.reblogs": "{count, plural, one {diskonigo} other {diskonigoj}}", "status.reblogs.empty": "Ankoraŭ neniu diskonigis tiun afiŝon. Kiam iu faras tion, ri aperos ĉi tie.", "status.redraft": "Forigi kaj reskribi", "status.remove_bookmark": "Forigi legosignon", diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json index 3f582452d51bda..65c8e7f6411fbb 100644 --- a/app/javascript/mastodon/locales/es-AR.json +++ b/app/javascript/mastodon/locales/es-AR.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Dejar de notificarme cuando @{name} envíe mensajes", "account.domain_blocking": "Dominio bloqueado", "account.edit_profile": "Editar perfil", + "account.edit_profile_short": "Editar", "account.enable_notifications": "Notificarme cuando @{name} envíe mensajes", "account.endorse": "Destacar en el perfil", "account.familiar_followers_many": "Seguido por {name1}, {name2} y {othersCount, plural, one {# cuenta más que conocés} other {# cuentas más que conocés}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Sin mensajes", "account.follow": "Seguir", "account.follow_back": "Seguir", + "account.follow_back_short": "Seguir", + "account.follow_request": "Solicitud para seguir", + "account.follow_request_cancel": "Cancelar solicitud", + "account.follow_request_cancel_short": "Cancelar", + "account.follow_request_short": "Solicitar", "account.followers": "Seguidores", "account.followers.empty": "Todavía nadie sigue a este usuario.", "account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Mnsjs y resp. públicas", "account.remove_from_followers": "Quitar a {name} de tus seguidores", "account.report": "Denunciar a @{name}", - "account.requested": "Esperando aprobación. Hacé clic para cancelar la solicitud de seguimiento", "account.requested_follow": "{name} solicitó seguirte", "account.requests_to_follow_you": "Solicita seguirte", "account.share": "Compartir el perfil de @{name}", @@ -108,25 +113,52 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Describí esto para personas con dificultades visuales…", "alt_text_modal.done": "Listo", "announcement.announcement": "Anuncio", - "annual_report.summary.archetype.booster": "Corrió la voz", - "annual_report.summary.archetype.lurker": "El acechador", - "annual_report.summary.archetype.oracle": "El oráculo", - "annual_report.summary.archetype.pollster": "Estuvo consultando", - "annual_report.summary.archetype.replier": "Respondió un montón", - "annual_report.summary.followers.followers": "seguidores", - "annual_report.summary.followers.total": "{count} en total", - "annual_report.summary.here_it_is": "Acá está tu resumen de {year}:", - "annual_report.summary.highlighted_post.by_favourites": "el mensaje más veces marcado como favorito", - "annual_report.summary.highlighted_post.by_reblogs": "el mensaje que más adhesiones recibió", - "annual_report.summary.highlighted_post.by_replies": "el mensaje que más respuestas recibió", - "annual_report.summary.highlighted_post.possessive": "{name}", + "annual_report.announcement.action_build": "Construir mi MastodonAnual", + "annual_report.announcement.action_dismiss": "No, gracias", + "annual_report.announcement.action_view": "Ver mi MastodonAnual", + "annual_report.announcement.description": "Descubrí más sobre tu participación en Mastodon durante el último año.", + "annual_report.announcement.title": "Llegó MastodonAnual {year}", + "annual_report.nav_item.badge": "Nueva", + "annual_report.shared_page.donate": "Donar", + "annual_report.shared_page.footer": "Generado con {heart} por el equipo de Mastodon", + "annual_report.shared_page.footer_server_info": "{username} usa {domain}, una de las tantas comunidades de Mastodon.", + "annual_report.summary.archetype.booster.desc_public": "{name} permaneció al acecho en busca de mensajes para adherir, apuntando a otros creadores con perfecta puntería.", + "annual_report.summary.archetype.booster.desc_self": "Permaneciste al acecho en busca de mensajes para adherir, apuntando a otros creadores con perfecta puntería.", + "annual_report.summary.archetype.booster.name": "El arquero", + "annual_report.summary.archetype.die_drei_fragezeichen": "¿¿¿???", + "annual_report.summary.archetype.lurker.desc_public": "Sabemos que {name} estuvo afuera, en algún lado, disfrutando de Mastodon a su propio modo silencioso.", + "annual_report.summary.archetype.lurker.desc_self": "Sabemos que estuviste afuera, en algún lado, disfrutando de Mastodon a tu propio modo silencioso.", + "annual_report.summary.archetype.lurker.name": "El estoico", + "annual_report.summary.archetype.oracle.desc_public": "{name} creó más mensajes nuevos que respuestas, manteniendo a Mastodon fresco y de cara al futuro.", + "annual_report.summary.archetype.oracle.desc_self": "Creaste más mensajes nuevos que respuestas, manteniendo a Mastodon fresco y de cara al futuro.", + "annual_report.summary.archetype.oracle.name": "El oráculo", + "annual_report.summary.archetype.pollster.desc_public": "{name} creó más encuestas que mensajes de otro tipo, cultivando curiosidad en Mastodon.", + "annual_report.summary.archetype.pollster.desc_self": "Creaste más encuestas que mensajes de otro tipo, cultivando curiosidad en Mastodon.", + "annual_report.summary.archetype.pollster.name": "El maravillado", + "annual_report.summary.archetype.replier.desc_public": "{name} respondió frecuentemente a los mensajes de otras cuentas, polinizando con nuevos debates.", + "annual_report.summary.archetype.replier.desc_self": "Respondiste frecuentemente a los mensajes de otras cuentas, polinizando con nuevos debates.", + "annual_report.summary.archetype.replier.name": "La mariposa", + "annual_report.summary.archetype.reveal": "Revelar mi arquetipo", + "annual_report.summary.archetype.reveal_description": "¡Gracias por ser parte de Mastodon! Es hora de descubrir qué arquetipo encarnaste en {year}.", + "annual_report.summary.archetype.title_public": "El arquetipo de {name}", + "annual_report.summary.archetype.title_self": "Tu arquetipo", + "annual_report.summary.close": "Cerrar", + "annual_report.summary.copy_link": "Copiar enlace", + "annual_report.summary.followers.new_followers": "{count, plural, one {nuevo seguidor} other {nuevos seguidores}}", + "annual_report.summary.highlighted_post.boost_count": "Este mensaje recibió adhesiones {count, plural, one {una vez} other {# veces}}.", + "annual_report.summary.highlighted_post.favourite_count": "Este mensaje fue marcado como favorito {count, plural, one {una vez} other {# veces}}.", + "annual_report.summary.highlighted_post.reply_count": "Este mensaje recibió {count, plural, one {una respuesta} other {# respuestas}}.", + "annual_report.summary.highlighted_post.title": "Mensaje más popular", "annual_report.summary.most_used_app.most_used_app": "la aplicación más usada", "annual_report.summary.most_used_hashtag.most_used_hashtag": "la etiqueta más usada", - "annual_report.summary.most_used_hashtag.none": "Ninguna", + "annual_report.summary.most_used_hashtag.used_count": "Incluiste esta etiqueta en {count, plural, one {un mensaje} other {# mensajes}}.", + "annual_report.summary.most_used_hashtag.used_count_public": "{name} incluyó esta etiqueta en {count, plural, one {un mensaje} other {# mensajes}}.", "annual_report.summary.new_posts.new_posts": "nuevos mensajes", "annual_report.summary.percentile.text": "Eso te coloca en la cima delde los usuarios de {domain}.", "annual_report.summary.percentile.we_wont_tell_bernie": "Queda entre nos.", - "annual_report.summary.thanks": "¡Gracias por ser parte de Mastodon!", + "annual_report.summary.share_elsewhere": "Compartir en otro lado", + "annual_report.summary.share_message": "¡Conseguí el arquetipo {archetype}!", + "annual_report.summary.share_on_mastodon": "Compartir en Mastodon", "attachments_list.unprocessed": "[sin procesar]", "audio.hide": "Ocultar audio", "block_modal.remote_users_caveat": "Le pediremos al servidor {domain} que respete tu decisión. Sin embargo, el cumplimiento no está garantizado, ya que algunos servidores pueden manejar los bloqueos de forma diferente. Los mensajes públicos todavía podrían estar visibles para los usuarios no conectados.", @@ -152,6 +184,8 @@ "bundle_modal_error.close": "Cerrar", "bundle_modal_error.message": "Algo salió mal al cargar esta pantalla.", "bundle_modal_error.retry": "Intentá de nuevo", + "carousel.current": "Diapositiva {current, number} / {max, number}", + "carousel.slide": "Diapositiva {current, number} de {max, number}", "closed_registrations.other_server_instructions": "Ya que Mastodon es descentralizado, podés crearte una cuenta en otro servidor y todavía interactuar con éste.", "closed_registrations_modal.description": "Actualmente no es posible crearte una cuenta en {domain}. pero recordá que no necesitás tener una cuenta puntualmente dentro de {domain} para poder usar Mastodon.", "closed_registrations_modal.find_another_server": "Buscar otro servidor", @@ -168,6 +202,8 @@ "column.edit_list": "Editar lista", "column.favourites": "Favoritos", "column.firehose": "Líneas temporales en vivo", + "column.firehose_local": "Línea temporal en vivo para este servidor", + "column.firehose_singular": "Línea temporal en vivo", "column.follow_requests": "Solicitudes de seguimiento", "column.home": "Principal", "column.list_members": "Administrar miembros de la lista", @@ -187,6 +223,7 @@ "community.column_settings.local_only": "Sólo local", "community.column_settings.media_only": "Sólo medios", "community.column_settings.remote_only": "Sólo remoto", + "compose.error.blank_post": "El mensaje no puede estar en blanco.", "compose.language.change": "Cambiar idioma", "compose.language.search": "Buscar idiomas…", "compose.published.body": "Mensaje publicado.", @@ -239,6 +276,11 @@ "confirmations.missing_alt_text.secondary": "Enviar de todos modos", "confirmations.missing_alt_text.title": "¿Agregar texto alternativo?", "confirmations.mute.confirm": "Silenciar", + "confirmations.private_quote_notify.cancel": "Volver a editar", + "confirmations.private_quote_notify.confirm": "Enviar mensaje", + "confirmations.private_quote_notify.do_not_show_again": "No mostrarme este mensaje de nuevo", + "confirmations.private_quote_notify.message": "La cuenta a la que estás citando y otras menciones serán notificadas y podrán ver tu mensaje, incluso si no te están siguiendo.", + "confirmations.private_quote_notify.title": "¿Compartir con seguidores y usuarios mencionados?", "confirmations.quiet_post_quote_info.dismiss": "No recordar de nuevo", "confirmations.quiet_post_quote_info.got_it": "Entendido", "confirmations.quiet_post_quote_info.message": "Al citar un mensaje público pero silencioso, tu mensaje se ocultará de las líneas temporales de tendencias.", @@ -252,9 +294,12 @@ "confirmations.revoke_quote.confirm": "Eliminar mensaje", "confirmations.revoke_quote.message": "Esta acción no se puede deshacer.", "confirmations.revoke_quote.title": "¿Eliminar mensaje?", + "confirmations.unblock.confirm": "Desbloquear", + "confirmations.unblock.title": "¿Desbloquear a {name}?", "confirmations.unfollow.confirm": "Dejar de seguir", - "confirmations.unfollow.message": "¿Estás seguro que querés dejar de seguir a {name}?", - "confirmations.unfollow.title": "¿Dejar de seguir al usuario?", + "confirmations.unfollow.title": "¿Dejar de seguir a {name}?", + "confirmations.withdraw_request.confirm": "Retirar solicitud", + "confirmations.withdraw_request.title": "¿Retirar solicitud de seguimiento a {name}?", "content_warning.hide": "Ocultar mensaje", "content_warning.show": "Mostrar de todos modos", "content_warning.show_more": "Mostrar más", @@ -325,6 +370,7 @@ "empty_column.bookmarked_statuses": "Todavía no tenés mensajes guardados en \"Marcadores\". Cuando guardés uno en \"Marcadores\", se mostrará acá.", "empty_column.community": "La línea temporal local está vacía. ¡Escribí algo en modo público para que se empiece a correr la bola!", "empty_column.direct": "Todavía no tenés ninguna mención privada. Cuando enviés o recibás una, se mostrará acá.", + "empty_column.disabled_feed": "Esta línea temporal fue deshabilitada por los administradores de tu servidor.", "empty_column.domain_blocks": "Todavía no hay dominios bloqueados.", "empty_column.explore_statuses": "No hay nada en tendencia ahora mismo. ¡Volvé a revisar más tarde!", "empty_column.favourited_statuses": "Todavía no tenés mensajes favoritos. Cuando marqués uno como favorito, se mostrará acá.", @@ -338,6 +384,7 @@ "empty_column.notification_requests": "¡Todo limpio! No hay nada acá. Cuando recibás nuevas notificaciones, aparecerán acá, acorde a tu configuración.", "empty_column.notifications": "Todavía no tenés ninguna notificación. Cuando otras cuentas interactúen con vos, vas a ver la notificación acá.", "empty_column.public": "¡Naranja! Escribí algo públicamente, o seguí usuarios manualmente de otros servidores para ir llenando esta línea temporal", + "error.no_hashtag_feed_access": "Create una cuenta o iniciá sesión para seguir esta etiqueta.", "error.unexpected_crash.explanation": "Debido a un error en nuestro código o a un problema de compatibilidad con el navegador web, esta página no se pudo mostrar correctamente.", "error.unexpected_crash.explanation_addons": "No se pudo mostrar correctamente esta página. Este error probablemente es causado por un complemento del navegador web o por herramientas de traducción automática.", "error.unexpected_crash.next_steps": "Intentá recargar la página. Si eso no ayuda, podés usar Mastodon a través de un navegador web diferente o aplicación nativa.", @@ -349,11 +396,9 @@ "explore.trending_links": "Noticias", "explore.trending_statuses": "Mensajes", "explore.trending_tags": "Etiquetas", + "featured_carousel.current": "Mensaje {current, number} / {max, number}", "featured_carousel.header": "{count, plural, one {Mensaje fijado} other {Mensajes fijados}}", - "featured_carousel.next": "Siguiente", - "featured_carousel.post": "Mensaje", - "featured_carousel.previous": "Anterior", - "featured_carousel.slide": "{index} de {total}", + "featured_carousel.slide": "Mensaje {current, number} de {max, number}", "filter_modal.added.context_mismatch_explanation": "Esta categoría de filtro no se aplica al contexto en el que accediste a este mensaje. Si querés que el mensaje sea filtrado también en este contexto, vas a tener que editar el filtro.", "filter_modal.added.context_mismatch_title": "¡El contexto no coincide!", "filter_modal.added.expired_explanation": "Esta categoría de filtro caducó; vas a necesitar cambiar la fecha de caducidad para que se aplique.", @@ -396,6 +441,9 @@ "follow_suggestions.who_to_follow": "A quién seguir", "followed_tags": "Etiquetas seguidas", "footer.about": "Información", + "footer.about_mastodon": "Acerca de Mastodon", + "footer.about_server": "Acerca de {domain}", + "footer.about_this_server": "Información", "footer.directory": "Directorio de perfiles", "footer.get_app": "Conseguí la aplicación", "footer.keyboard_shortcuts": "Atajos de teclado", @@ -497,6 +545,7 @@ "keyboard_shortcuts.toggle_hidden": "Mostrar/ocultar texto detrás de la advertencia de contenido (\"CW\")", "keyboard_shortcuts.toggle_sensitivity": "Mostrar/ocultar medios", "keyboard_shortcuts.toot": "Comenzar un mensaje nuevo", + "keyboard_shortcuts.top": "Mover al principio de la lista", "keyboard_shortcuts.translate": "para traducir un mensaje", "keyboard_shortcuts.unfocus": "Quitar el foco del área de texto de redacción o de búsqueda", "keyboard_shortcuts.up": "Subir en la lista", @@ -724,10 +773,10 @@ "poll.refresh": "Refrescar", "poll.reveal": "Ver resultados", "poll.total_people": "{count, plural, one {# persona} other {# personas}}", - "poll.total_votes": "{count, plural, one {# voto} other {# votos}}", + "poll.total_votes": "{count, plural, one {voto} other {votos}}", "poll.vote": "Votar", "poll.voted": "Votaste esta opción", - "poll.votes": "{votes, plural, one {# voto} other {# votos}}", + "poll.votes": "{votes, plural, one {voto} other {votos}}", "poll_button.add_poll": "Agregar encuesta", "poll_button.remove_poll": "Quitar encuesta", "privacy.change": "Configurar privacidad del mensaje", @@ -742,10 +791,12 @@ "privacy.quote.limited": "{visibility}, citas limitadas", "privacy.unlisted.additional": "Esto se comporta exactamente igual que con la configuración de privacidad de mensaje «Público», excepto que el mensaje no aparecerá en las líneas temporales en vivo, ni en las etiquetas, ni en la línea temporal «Explorá», ni en la búsqueda de Mastodon; incluso si optaste por hacer tu cuenta visible.", "privacy.unlisted.long": "Oculto de los resultados de búsqueda, tendencias y líneas temporales públicas de Mastodon", - "privacy.unlisted.short": "Público silencioso", + "privacy.unlisted.short": "Público pero silencioso", "privacy_policy.last_updated": "Última actualización: {date}", "privacy_policy.title": "Política de privacidad", + "quote_error.edit": "Las citas no se pueden agregar al editar un mensaje.", "quote_error.poll": "No se permite citar encuestas.", + "quote_error.private_mentions": "No se permite citar con menciones directas.", "quote_error.quote": "Solo se permite una cita a la vez.", "quote_error.unauthorized": "No tenés autorización para citar este mensaje.", "quote_error.upload": "No se permite citar con archivos multimedia.", @@ -865,8 +916,12 @@ "status.cannot_quote": "No te es permitido citar este mensaje", "status.cannot_reblog": "No se puede adherir a este mensaje", "status.contains_quote": "Contiene cita", - "status.context.load_new_replies": "Hay nuevas respuestas", - "status.context.loading": "Buscando más respuestas", + "status.context.loading": "Cargando más respuestas", + "status.context.loading_error": "No se pudieron cargar nuevas respuestas", + "status.context.loading_success": "Se cargaron nuevas respuestas", + "status.context.more_replies_found": "Se encontraron más respuestas", + "status.context.retry": "Reintentar", + "status.context.show": "Mostrar", "status.continued_thread": "Continuación de hilo", "status.copy": "Copiar enlace al mensaje", "status.delete": "Eliminar", @@ -879,7 +934,7 @@ "status.edited_x_times": "Editado {count, plural, one {{count} vez} other {{count} veces}}", "status.embed": "Obtener código para insertar", "status.favourite": "Marcar como favorito", - "status.favourites": "{count, plural, one {vez marcado como favorito} other {veces marcado como favorito}}", + "status.favourites_count": "{count, plural,one {{counter} favorito}other {{counter} favoritos}}", "status.filter": "Filtrar este mensaje", "status.history.created": "Creado por {name}, {date}", "status.history.edited": "Editado por {name}, {date}", @@ -895,9 +950,12 @@ "status.pin": "Fijar en el perfil", "status.quote": "Citar", "status.quote.cancel": "Cancelar cita", + "status.quote_error.blocked_account_hint.title": "Este mensaje está oculto porque bloqueaste a @{name}.", + "status.quote_error.blocked_domain_hint.title": "Este mensaje está oculto porque bloqueaste {domain}.", "status.quote_error.filtered": "Oculto debido a uno de tus filtros", "status.quote_error.limited_account_hint.action": "Mostrar de todos modos", "status.quote_error.limited_account_hint.title": "Esta cuenta fue ocultada por los moderadores de {domain}.", + "status.quote_error.muted_account_hint.title": "Este mensaje está oculto porque silenciaste a @{name}.", "status.quote_error.not_available": "Mensaje no disponible", "status.quote_error.pending_approval": "Mensaje pendiente", "status.quote_error.pending_approval_popout.body": "En Mastodon, podés controlar si alguien te puede citar. Este mensaje está pendiente hasta obtener la aprobación del autor original.", @@ -908,15 +966,17 @@ "status.quote_policy_change": "Cambiá quién puede citar", "status.quote_post_author": "Se citó un mensaje de @{name}", "status.quote_private": "No se pueden citar los mensajes privados", - "status.quotes": "{count, plural, one {# voto} other {# votos}}", "status.quotes.empty": "Todavía nadie citó este mensaje. Cuando alguien lo haga, se mostrará acá.", + "status.quotes.local_other_disclaimer": "Las citas rechazadas por el autor no serán mostradas.", + "status.quotes.remote_other_disclaimer": "Solo las citas de {domain} están garantizadas de ser mostradas acá. Las citas rechazadas por el autor no serán mostradas.", + "status.quotes_count": "{count, plural,one {{counter} cita} other {{counter} citas}}", "status.read_more": "Leé más", "status.reblog": "Adherir", "status.reblog_or_quote": "Adherir o citar", "status.reblog_private": "Compartir de nuevo con tus seguidores", "status.reblogged_by": "{name} adhirió", - "status.reblogs": "{count, plural, one {adhesión} other {adhesiones}}", "status.reblogs.empty": "Todavía nadie adhirió a este mensaje. Cuando alguien lo haga, se mostrará acá.", + "status.reblogs_count": "{count, plural,one {{counter} adhesión} other {{counter} adhesiones}}", "status.redraft": "Eliminar mensaje original y editarlo", "status.remove_bookmark": "Quitar marcador", "status.remove_favourite": "Quitar de favoritos", @@ -990,6 +1050,8 @@ "video.volume_down": "Bajar volumen", "video.volume_up": "Subir volumen", "visibility_modal.button_title": "Establecer visibilidad", + "visibility_modal.direct_quote_warning.text": "Si guardás la configuración actual, la cita insertada se convertirá en un enlace.", + "visibility_modal.direct_quote_warning.title": "Las citas no pueden ser insertadas en menciones privadas", "visibility_modal.header": "Visibilidad e interacción", "visibility_modal.helper.direct_quoting": "Las menciones privadas redactadas en Mastodon no pueden ser citadas por otras cuentas.", "visibility_modal.helper.privacy_editing": "La visibilidad no se puede cambiar después de que se haya enviado un mensaje.", @@ -998,7 +1060,7 @@ "visibility_modal.helper.unlisted_quoting": "Cuando otras cuentas te citen, sus publicaciones también se ocultarán de las líneas temporales de tendencias.", "visibility_modal.instructions": "Controlá quién puede interactuar con este mensaje. También podés aplicar los ajustes para todos los mensajes futuros accediendo a «Configuración» > «Configuración predeterminada de mensajes».", "visibility_modal.privacy_label": "Visibilidad", - "visibility_modal.quote_followers": "Solo para seguidores", + "visibility_modal.quote_followers": "Solo seguidores", "visibility_modal.quote_label": "Quién puede citar", "visibility_modal.quote_nobody": "Solo yo", "visibility_modal.quote_public": "Cualquier cuenta", diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index 6b48c21d36127b..3ec8beb6bfa861 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Dejar de notificarme cuando @{name} publique algo", "account.domain_blocking": "Bloqueando dominio", "account.edit_profile": "Editar perfil", + "account.edit_profile_short": "Editar", "account.enable_notifications": "Notificarme cuando @{name} publique algo", "account.endorse": "Destacar en mi perfil", "account.familiar_followers_many": "Seguido por {name1}, {name2} y {othersCount, plural,one {# más que conoces}other {# más que conoces}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Sin publicaciones", "account.follow": "Seguir", "account.follow_back": "Seguir también", + "account.follow_back_short": "Seguir también", + "account.follow_request": "Solicitar seguimiento", + "account.follow_request_cancel": "Cancelar solicitud", + "account.follow_request_cancel_short": "Cancelar", + "account.follow_request_short": "Solicitar", "account.followers": "Seguidores", "account.followers.empty": "Nadie sigue a este usuario todavía.", "account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Publicaciones y respuestas", "account.remove_from_followers": "Eliminar {name} de tus seguidores", "account.report": "Denunciar a @{name}", - "account.requested": "Esperando aprobación. Haz clic para cancelar la solicitud de seguimiento", "account.requested_follow": "{name} ha solicitado seguirte", "account.requests_to_follow_you": "Solicita seguirte", "account.share": "Compartir el perfil de @{name}", @@ -108,25 +113,52 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Describe esto para personas con discapacidad visual…", "alt_text_modal.done": "Hecho", "announcement.announcement": "Anuncio", - "annual_report.summary.archetype.booster": "El cazador de tendencias", - "annual_report.summary.archetype.lurker": "El merodeador", - "annual_report.summary.archetype.oracle": "El oráculo", - "annual_report.summary.archetype.pollster": "El encuestador", - "annual_report.summary.archetype.replier": "El más sociable", - "annual_report.summary.followers.followers": "seguidores", - "annual_report.summary.followers.total": "{count} en total", - "annual_report.summary.here_it_is": "Este es el resumen de tu {year}:", - "annual_report.summary.highlighted_post.by_favourites": "publicación con más favoritos", - "annual_report.summary.highlighted_post.by_reblogs": "publicación más impulsada", - "annual_report.summary.highlighted_post.by_replies": "publicación con más respuestas", - "annual_report.summary.highlighted_post.possessive": "de {name}", + "annual_report.announcement.action_build": "Preparar mi Wrapstodon", + "annual_report.announcement.action_dismiss": "No, gracias", + "annual_report.announcement.action_view": "Ver mi Wrapstodon", + "annual_report.announcement.description": "Descubre más sobre tu participación en Mastodon durante el último año.", + "annual_report.announcement.title": "Wrapstodon {year} ha llegado", + "annual_report.nav_item.badge": "Nueva", + "annual_report.shared_page.donate": "Donar", + "annual_report.shared_page.footer": "Generado con {heart} por el equipo de Mastodon", + "annual_report.shared_page.footer_server_info": "{username} usa {domain}, una de las muchas comunidades que funcionan con Mastodon.", + "annual_report.summary.archetype.booster.desc_public": "{name} se mantuvo a la caza de publicaciones para impulsar, dando visibilidad a otros creadores con una puntería perfecta.", + "annual_report.summary.archetype.booster.desc_self": "Te mantuviste a la caza de publicaciones para impulsar, amplificando a otros creadores con una puntería perfecta.", + "annual_report.summary.archetype.booster.name": "El arquero", + "annual_report.summary.archetype.die_drei_fragezeichen": "???", + "annual_report.summary.archetype.lurker.desc_public": "Sabemos que {name} estaba ahí fuera, en algún lugar, disfrutando de Mastodon a su manera tranquila.", + "annual_report.summary.archetype.lurker.desc_self": "Sabemos que estabas ahí fuera, en algún lugar, disfrutando de Mastodon a tu manera, en silencio.", + "annual_report.summary.archetype.lurker.name": "El estoico", + "annual_report.summary.archetype.oracle.desc_public": "{name} creó más publicaciones nuevas que respuestas, lo que mantuvo a Mastodon fresco y con visión de futuro.", + "annual_report.summary.archetype.oracle.desc_self": "Creaste más publicaciones nuevas que respuestas, lo que mantuvo a Mastodon fresco y con visión de futuro.", + "annual_report.summary.archetype.oracle.name": "El oráculo", + "annual_report.summary.archetype.pollster.desc_public": "{name} creó más encuestas que otros tipos de publicaciones, lo que despertó la curiosidad en Mastodon.", + "annual_report.summary.archetype.pollster.desc_self": "Creaste más encuestas que otros tipos de publicaciones, lo que despertó la curiosidad en Mastodon.", + "annual_report.summary.archetype.pollster.name": "El soñador", + "annual_report.summary.archetype.replier.desc_public": "{name} respondía con frecuencia a las publicaciones de otras personas, lo que generaba nuevos debates en Mastodon.", + "annual_report.summary.archetype.replier.desc_self": "Respondías con frecuencia a las publicaciones de otras personas, lo que generaba nuevos debates en Mastodon.", + "annual_report.summary.archetype.replier.name": "La mariposa", + "annual_report.summary.archetype.reveal": "Revelar mi arquetipo", + "annual_report.summary.archetype.reveal_description": "¡Gracias por formar parte de Mastodon! Es hora de descubrir qué arquetipo encarnaste en {year}.", + "annual_report.summary.archetype.title_public": "El arquetipo de {name}", + "annual_report.summary.archetype.title_self": "Tu arquetipo", + "annual_report.summary.close": "Cerrar", + "annual_report.summary.copy_link": "Copiar enlace", + "annual_report.summary.followers.new_followers": "{count, plural,one {nuevo seguidor} other {nuevos seguidores}}", + "annual_report.summary.highlighted_post.boost_count": "Esta publicación fue impulsada {count, plural,one {una vez} other {# veces}}.", + "annual_report.summary.highlighted_post.favourite_count": "Esta publicación fue marcada como favorita {count, plural,one {una vez} other {# veces}}.", + "annual_report.summary.highlighted_post.reply_count": "Esta publicación consiguió {count, plural,one {una respuesta} other {# respuestas}}.", + "annual_report.summary.highlighted_post.title": "Publicación más popular", "annual_report.summary.most_used_app.most_used_app": "aplicación más utilizada", "annual_report.summary.most_used_hashtag.most_used_hashtag": "etiqueta más utilizada", - "annual_report.summary.most_used_hashtag.none": "Ninguna", + "annual_report.summary.most_used_hashtag.used_count": "Incluiste esta etiqueta en {count, plural,one {una publicación} other {# publicaciones}}.", + "annual_report.summary.most_used_hashtag.used_count_public": "{name} incluyó esta etiqueta en {count, plural,one {una publicación} other {# publicaciones}}.", "annual_report.summary.new_posts.new_posts": "nuevas publicaciones", "annual_report.summary.percentile.text": "Eso te sitúa en el topde usuarios de {domain}.", "annual_report.summary.percentile.we_wont_tell_bernie": "No se lo diremos a Bernie.", - "annual_report.summary.thanks": "¡Gracias por ser parte de Mastodon!", + "annual_report.summary.share_elsewhere": "Compartir en otro lado", + "annual_report.summary.share_message": "¡Obtuve el arquetipo {archetype}!", + "annual_report.summary.share_on_mastodon": "Compartir en Mastodon", "attachments_list.unprocessed": "(sin procesar)", "audio.hide": "Ocultar audio", "block_modal.remote_users_caveat": "Le pediremos al servidor {domain} que respete tu decisión. Sin embargo, el cumplimiento no está garantizado, ya que algunos servidores pueden manejar bloques de forma diferente. Las publicaciones públicas pueden ser todavía visibles para los usuarios no conectados.", @@ -152,6 +184,8 @@ "bundle_modal_error.close": "Cerrar", "bundle_modal_error.message": "Algo ha fallado al cargar esta pantalla.", "bundle_modal_error.retry": "Inténtalo de nuevo", + "carousel.current": "Diapositiva {current, number} / {max, number}", + "carousel.slide": "Diapositiva {current, number} de {max, number}", "closed_registrations.other_server_instructions": "Como Mastodon es descentralizado, puedes crear una cuenta en otro servidor y seguir interactuando con este.", "closed_registrations_modal.description": "La creación de una cuenta en {domain} no es posible actualmente, pero ten en cuenta que no necesitas una cuenta específicamente en {domain} para usar Mastodon.", "closed_registrations_modal.find_another_server": "Buscar otro servidor", @@ -167,7 +201,9 @@ "column.domain_blocks": "Dominios ocultados", "column.edit_list": "Editar lista", "column.favourites": "Favoritos", - "column.firehose": "Cronologías", + "column.firehose": "Feeds en vivo", + "column.firehose_local": "Feed en vivo para este servidor", + "column.firehose_singular": "Feed en vivo", "column.follow_requests": "Solicitudes de seguimiento", "column.home": "Inicio", "column.list_members": "Administrar miembros de la lista", @@ -187,6 +223,7 @@ "community.column_settings.local_only": "Solo local", "community.column_settings.media_only": "Solo media", "community.column_settings.remote_only": "Solo remoto", + "compose.error.blank_post": "La publicación no puede estar vacía.", "compose.language.change": "Cambiar idioma", "compose.language.search": "Buscar idiomas...", "compose.published.body": "Publicado.", @@ -239,6 +276,11 @@ "confirmations.missing_alt_text.secondary": "Publicar de todas maneras", "confirmations.missing_alt_text.title": "¿Añadir texto alternativo?", "confirmations.mute.confirm": "Silenciar", + "confirmations.private_quote_notify.cancel": "Seguir editando", + "confirmations.private_quote_notify.confirm": "Publicar", + "confirmations.private_quote_notify.do_not_show_again": "No mostrar este mensaje de nuevo", + "confirmations.private_quote_notify.message": "Tu publicación será notificada y podrá ser vista por la persona a la que mencionas y otras menciones, aún si no te siguen.", + "confirmations.private_quote_notify.title": "¿Compartir con seguidores y usuarios mencionados?", "confirmations.quiet_post_quote_info.dismiss": "No me lo recuerdes otra vez", "confirmations.quiet_post_quote_info.got_it": "Entendido", "confirmations.quiet_post_quote_info.message": "Al citar una publicación pública discreta, tu publicación se ocultará de las cronologías de tendencias.", @@ -252,9 +294,12 @@ "confirmations.revoke_quote.confirm": "Eliminar publicación", "confirmations.revoke_quote.message": "Esta acción no se puede deshacer.", "confirmations.revoke_quote.title": "¿Deseas eliminar la publicación?", + "confirmations.unblock.confirm": "Desbloquear", + "confirmations.unblock.title": "¿Desbloquear a {name}?", "confirmations.unfollow.confirm": "Dejar de seguir", - "confirmations.unfollow.message": "¿Estás seguro de que quieres dejar de seguir a {name}?", - "confirmations.unfollow.title": "¿Dejar de seguir al usuario?", + "confirmations.unfollow.title": "¿Dejar de seguir a {name}?", + "confirmations.withdraw_request.confirm": "Cancelar solicitud", + "confirmations.withdraw_request.title": "¿Cancelar solicitud para seguir a {name}?", "content_warning.hide": "Ocultar publicación", "content_warning.show": "Mostrar de todos modos", "content_warning.show_more": "Mostrar más", @@ -325,6 +370,7 @@ "empty_column.bookmarked_statuses": "Aún no tienes ninguna publicación guardada como marcador. Cuando guardes una, se mostrará aquí.", "empty_column.community": "La cronología local está vacía. ¡Escribe algo públicamente para ponerla en marcha!", "empty_column.direct": "Aún no tienes menciones privadas. Cuando envíes o recibas una, aparecerán aquí.", + "empty_column.disabled_feed": "Este feed fue desactivado por los administradores de tu servidor.", "empty_column.domain_blocks": "Todavía no hay dominios ocultos.", "empty_column.explore_statuses": "Nada es tendencia en este momento. ¡Revisa más tarde!", "empty_column.favourited_statuses": "Todavía no tienes publicaciones favoritas. Cuando le des favorito a una publicación se mostrarán acá.", @@ -338,6 +384,7 @@ "empty_column.notification_requests": "¡Todo limpio! No hay nada aquí. Cuando recibas nuevas notificaciones, aparecerán aquí conforme a tu configuración.", "empty_column.notifications": "No tienes ninguna notificación aún. Interactúa con otros para empezar una conversación.", "empty_column.public": "¡Aquí no hay nada! Escribe algo públicamente o sigue manualmente a usuarios de otros servidores para llenarlo", + "error.no_hashtag_feed_access": "Únete o inicia sesión para ver y seguir esta etiqueta.", "error.unexpected_crash.explanation": "Debido a un error en nuestro código o a un problema de compatibilidad con el navegador, esta página no se ha podido mostrar correctamente.", "error.unexpected_crash.explanation_addons": "No se pudo mostrar correctamente esta página. Este error probablemente fue causado por un complemento del navegador web o por herramientas de traducción automática.", "error.unexpected_crash.next_steps": "Intenta actualizar la página. Si eso no ayuda, es posible que puedas usar Mastodon a través de otro navegador o aplicación nativa.", @@ -349,11 +396,9 @@ "explore.trending_links": "Noticias", "explore.trending_statuses": "Publicaciones", "explore.trending_tags": "Etiquetas", + "featured_carousel.current": "Publicación {current, number} / {max, number}", "featured_carousel.header": "{count, plural,one {Publicación fijada}other {Publicaciones fijadas}}", - "featured_carousel.next": "Siguiente", - "featured_carousel.post": "Publicar", - "featured_carousel.previous": "Anterior", - "featured_carousel.slide": "{index} de {total}", + "featured_carousel.slide": "Publicación {current, number} de {max, number}", "filter_modal.added.context_mismatch_explanation": "Esta categoría de filtro no se aplica al contexto en el que has accedido a esta publlicación. Si quieres que la publicación sea filtrada también en este contexto, tendrás que editar el filtro.", "filter_modal.added.context_mismatch_title": "¡El contexto no coincide!", "filter_modal.added.expired_explanation": "Esta categoría de filtro ha caducado, necesitaras cambiar la fecha de caducidad para que se aplique.", @@ -396,6 +441,9 @@ "follow_suggestions.who_to_follow": "Recomendamos seguir", "followed_tags": "Etiquetas seguidas", "footer.about": "Acerca de", + "footer.about_mastodon": "Acerca de Mastodon", + "footer.about_server": "Acerca de {domain}", + "footer.about_this_server": "Sobre nosotros", "footer.directory": "Directorio de perfiles", "footer.get_app": "Obtener la aplicación", "footer.keyboard_shortcuts": "Atajos de teclado", @@ -497,6 +545,7 @@ "keyboard_shortcuts.toggle_hidden": "Mostrar/ocultar texto detrás de AC", "keyboard_shortcuts.toggle_sensitivity": "Mostrar/ocultar multimedia", "keyboard_shortcuts.toot": "Comenzar una nueva publicación", + "keyboard_shortcuts.top": "Mover al principio de la lista", "keyboard_shortcuts.translate": "para traducir una publicación", "keyboard_shortcuts.unfocus": "Desenfocar área de redacción/búsqueda", "keyboard_shortcuts.up": "Ascender en la lista", @@ -745,7 +794,9 @@ "privacy.unlisted.short": "Pública, pero discreta", "privacy_policy.last_updated": "Actualizado por última vez {date}", "privacy_policy.title": "Política de Privacidad", + "quote_error.edit": "No se pueden añadir citas mientras un post está siendo editado.", "quote_error.poll": "No se permite citar encuestas.", + "quote_error.private_mentions": "Citar no está disponible sin menciones directas.", "quote_error.quote": "Solo se permite una cita a la vez.", "quote_error.unauthorized": "No estás autorizado a citar esta publicación.", "quote_error.upload": "No se permite citar con archivos multimedia.", @@ -785,7 +836,7 @@ "report.forward": "Reenviar a {target}", "report.forward_hint": "Esta cuenta es de otro servidor. ¿Enviar una copia anonimizada del informe allí también?", "report.mute": "Silenciar", - "report.mute_explanation": "No veras sus publiaciones. Todavía pueden seguirte y ver tus publicaciones y no sabrán que están silenciados.", + "report.mute_explanation": "No verás sus publicaciones. Todavía pueden seguirte y ver tus publicaciones y no sabrán que están silenciados.", "report.next": "Siguiente", "report.placeholder": "Comentarios adicionales", "report.reasons.dislike": "No me gusta", @@ -865,8 +916,12 @@ "status.cannot_quote": "No está permitido citar esta publicación", "status.cannot_reblog": "Esta publicación no puede ser impulsada", "status.contains_quote": "Contiene cita", - "status.context.load_new_replies": "Nuevas respuestas disponibles", - "status.context.loading": "Comprobando si hay más respuestas", + "status.context.loading": "Cargando más respuestas", + "status.context.loading_error": "No se pudieron cargar nuevas respuestas", + "status.context.loading_success": "Nuevas respuestas cargadas", + "status.context.more_replies_found": "Se han encontrado más respuestas", + "status.context.retry": "Reintentar", + "status.context.show": "Mostrar", "status.continued_thread": "Hilo continuado", "status.copy": "Copiar enlace al estado", "status.delete": "Borrar", @@ -879,7 +934,7 @@ "status.edited_x_times": "Editado {count, plural, one {{count} time} other {{count} veces}}", "status.embed": "Obtener código para incrustar", "status.favourite": "Favorito", - "status.favourites": "{count, plural, one {favorito} other {favoritos}}", + "status.favourites_count": "{count, plural,one {{counter} favorito}other {{counter} favoritos}}", "status.filter": "Filtrar esta publicación", "status.history.created": "{name} creó {date}", "status.history.edited": "{name} editado {date}", @@ -895,9 +950,12 @@ "status.pin": "Fijar", "status.quote": "Citar", "status.quote.cancel": "Cancelar cita", + "status.quote_error.blocked_account_hint.title": "Esta publicación se ocultó porque bloqueaste a @{name}.", + "status.quote_error.blocked_domain_hint.title": "Este post está oculto porque bloqueaste {domain}.", "status.quote_error.filtered": "Oculto debido a uno de tus filtros", "status.quote_error.limited_account_hint.action": "Mostrar de todas formas", "status.quote_error.limited_account_hint.title": "Esta cuenta ha sido ocultada por los moderadores de {domain}.", + "status.quote_error.muted_account_hint.title": "Esta publicación está oculta porque silenciaste a @{name}.", "status.quote_error.not_available": "Publicación no disponible", "status.quote_error.pending_approval": "Publicación pendiente", "status.quote_error.pending_approval_popout.body": "En Mastodon, puedes controlar si alguien puede citarte. Esta publicación está pendiente mientras obtenemos la aprobación del autor original.", @@ -908,15 +966,17 @@ "status.quote_policy_change": "Cambia quién puede citarte", "status.quote_post_author": "Ha citado una publicación de @{name}", "status.quote_private": "Las publicaciones privadas no pueden citarse", - "status.quotes": "{count, plural,one {cita} other {citas}}", "status.quotes.empty": "Nadie ha citado esta publicación todavía. Cuando alguien lo haga, aparecerá aquí.", + "status.quotes.local_other_disclaimer": "Las citas rechazadas pro el autor no serán mostradas.", + "status.quotes.remote_other_disclaimer": "Solo las citas hechas por {domain} están garantizadas a ser vistas aquí. Las citas rechazadas por el autor no serán mostradas.", + "status.quotes_count": "{count, plural,one {{counter} cita} other {{counter} citas}}", "status.read_more": "Leer más", "status.reblog": "Impulsar", "status.reblog_or_quote": "Impulsar o citar", "status.reblog_private": "Compartir de nuevo con tus seguidores", "status.reblogged_by": "Impulsado por {name}", - "status.reblogs": "{count, plural, one {impulso} other {impulsos}}", "status.reblogs.empty": "Nadie impulsó esta publicación todavía. Cuando alguien lo haga, aparecerá aquí.", + "status.reblogs_count": "{count, plural,one {{counter} impulso} other {{counter} impulsos}}", "status.redraft": "Borrar y volver a borrador", "status.remove_bookmark": "Eliminar marcador", "status.remove_favourite": "Eliminar de favoritos", @@ -990,6 +1050,8 @@ "video.volume_down": "Bajar el volumen", "video.volume_up": "Subir el volumen", "visibility_modal.button_title": "Establece la visibilidad", + "visibility_modal.direct_quote_warning.text": "Si guardas la siguiente configuración, se mostrará un enlace en vez de la cita incrustada.", + "visibility_modal.direct_quote_warning.title": "No se pueden incrustar citas en menciones privadas", "visibility_modal.header": "Visibilidad e interacción", "visibility_modal.helper.direct_quoting": "Las menciones privadas creadas en Mastodon no pueden ser citadas por otros.", "visibility_modal.helper.privacy_editing": "La visibilidad no se puede cambiar después de que se haya hecho una publicación.", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index f84cb4151145fb..a09919bdade282 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Dejar de notificarme cuando @{name} publique algo", "account.domain_blocking": "Bloqueando dominio", "account.edit_profile": "Editar perfil", + "account.edit_profile_short": "Editar", "account.enable_notifications": "Notificarme cuando @{name} publique algo", "account.endorse": "Destacar en el perfil", "account.familiar_followers_many": "Seguido por {name1}, {name2} y {othersCount, plural,one {# más que conoces}other {# más que conoces}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Sin publicaciones", "account.follow": "Seguir", "account.follow_back": "Seguir también", + "account.follow_back_short": "Seguir también", + "account.follow_request": "Solicitud de seguimiento", + "account.follow_request_cancel": "Cancelar solicitud", + "account.follow_request_cancel_short": "Cancelar", + "account.follow_request_short": "Solicitar", "account.followers": "Seguidores", "account.followers.empty": "Todavía nadie sigue a este usuario.", "account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Publicaciones y respuestas", "account.remove_from_followers": "Eliminar {name} de tus seguidores", "account.report": "Reportar a @{name}", - "account.requested": "Esperando aprobación. Haz clic para cancelar la solicitud de seguimiento", "account.requested_follow": "{name} ha solicitado seguirte", "account.requests_to_follow_you": "Solicita seguirte", "account.share": "Compartir el perfil de @{name}", @@ -108,25 +113,31 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Descríbelo para personas con discapacidad visual…", "alt_text_modal.done": "Hecho", "announcement.announcement": "Comunicación", - "annual_report.summary.archetype.booster": "El cazador de tendencias", - "annual_report.summary.archetype.lurker": "El acechador", - "annual_report.summary.archetype.oracle": "El oráculo", - "annual_report.summary.archetype.pollster": "El encuestador", - "annual_report.summary.archetype.replier": "El más sociable", - "annual_report.summary.followers.followers": "seguidores", - "annual_report.summary.followers.total": "{count} en total", - "annual_report.summary.here_it_is": "Aquí está tu resumen de {year}:", - "annual_report.summary.highlighted_post.by_favourites": "publicación con más favoritos", - "annual_report.summary.highlighted_post.by_reblogs": "publicación más impulsada", - "annual_report.summary.highlighted_post.by_replies": "publicación con más respuestas", - "annual_report.summary.highlighted_post.possessive": "de {name}", + "annual_report.announcement.action_build": "Preparar mi Wrapstodon", + "annual_report.announcement.action_dismiss": "No, gracias", + "annual_report.announcement.action_view": "Ver mi Wrapstodon", + "annual_report.announcement.description": "Descubre más sobre tu participación en Mastodon durante el último año.", + "annual_report.announcement.title": "Wrapstodon {year} ha llegado", + "annual_report.nav_item.badge": "Nuevo", + "annual_report.shared_page.donate": "Donar", + "annual_report.shared_page.footer": "Generado con {heart} por el equipo de Mastodon", + "annual_report.summary.archetype.title_public": "El arquetipo de {name}", + "annual_report.summary.archetype.title_self": "Tu arquetipo", + "annual_report.summary.close": "Cerrar", + "annual_report.summary.copy_link": "Copiar enlace", + "annual_report.summary.highlighted_post.boost_count": "Esta publicación fue impulsada {count, plural,one {una vez} other {# veces}}.", + "annual_report.summary.highlighted_post.reply_count": "Esta publicación recibió {count, plural,one {una respuesta} other {# respuestas}}.", + "annual_report.summary.highlighted_post.title": "Publicación más popular", "annual_report.summary.most_used_app.most_used_app": "aplicación más usada", "annual_report.summary.most_used_hashtag.most_used_hashtag": "etiqueta más usada", - "annual_report.summary.most_used_hashtag.none": "Ninguna", + "annual_report.summary.most_used_hashtag.used_count": "Incluiste esta etiqueta en {count, plural,one {una publicación} other {# publicaciones}}.", + "annual_report.summary.most_used_hashtag.used_count_public": "{name} incluyó esta etiqueta en {count, plural, one {una publicación} other {# publicaciones}}.", "annual_report.summary.new_posts.new_posts": "nuevas publicaciones", "annual_report.summary.percentile.text": "Eso te coloca en el topde usuarios de {domain}.", "annual_report.summary.percentile.we_wont_tell_bernie": "No se lo diremos a Bernie.", - "annual_report.summary.thanks": "¡Gracias por ser parte de Mastodon!", + "annual_report.summary.share_elsewhere": "Compartir en otro lugar", + "annual_report.summary.share_message": "¡Obtuve el arquetipo {archetype}!", + "annual_report.summary.share_on_mastodon": "Compartir en Mastodon", "attachments_list.unprocessed": "(sin procesar)", "audio.hide": "Ocultar audio", "block_modal.remote_users_caveat": "Le pediremos al servidor {domain} que respete tu decisión. Sin embargo, el cumplimiento no está garantizado, ya que algunos servidores pueden manejar bloqueos de forma distinta. Los mensajes públicos pueden ser todavía visibles para los usuarios que no hayan iniciado sesión.", @@ -152,6 +163,8 @@ "bundle_modal_error.close": "Cerrar", "bundle_modal_error.message": "Ha habido algún error mientras cargábamos esta pantalla.", "bundle_modal_error.retry": "Inténtalo de nuevo", + "carousel.current": "Diapositiva {current, number} / {max, number}", + "carousel.slide": "Diapositiva {current, number} de {max, number}", "closed_registrations.other_server_instructions": "Como Mastodon es descentralizado, puedes crear una cuenta en otro servidor y seguir interactuando con este.", "closed_registrations_modal.description": "La creación de una cuenta en {domain} no es posible actualmente, pero ten en cuenta que no necesitas una cuenta específicamente en {domain} para usar Mastodon.", "closed_registrations_modal.find_another_server": "Buscar otro servidor", @@ -168,6 +181,8 @@ "column.edit_list": "Editar lista", "column.favourites": "Favoritos", "column.firehose": "Cronologías", + "column.firehose_local": "Cronología para este servidor", + "column.firehose_singular": "Cronología", "column.follow_requests": "Solicitudes de seguimiento", "column.home": "Inicio", "column.list_members": "Administrar miembros de la lista", @@ -187,6 +202,7 @@ "community.column_settings.local_only": "Solo local", "community.column_settings.media_only": "Solo multimedia", "community.column_settings.remote_only": "Solo remoto", + "compose.error.blank_post": "El mensaje no puede estar en blanco.", "compose.language.change": "Cambiar idioma", "compose.language.search": "Buscar idiomas...", "compose.published.body": "Publicado.", @@ -239,6 +255,11 @@ "confirmations.missing_alt_text.secondary": "Publicar de todos modos", "confirmations.missing_alt_text.title": "¿Deseas añadir texto alternativo?", "confirmations.mute.confirm": "Silenciar", + "confirmations.private_quote_notify.cancel": "Volver a la edición", + "confirmations.private_quote_notify.confirm": "Publicar", + "confirmations.private_quote_notify.do_not_show_again": "No mostrar este mensaje de nuevo", + "confirmations.private_quote_notify.message": "La persona a la que estás citando y otras mencionadas serán notificadas y podrán ver tu publicación, incluso si no te siguen.", + "confirmations.private_quote_notify.title": "¿Compartir con seguidores y usuarios mencionados?", "confirmations.quiet_post_quote_info.dismiss": "No me lo vuelvas a recordar", "confirmations.quiet_post_quote_info.got_it": "Entendido", "confirmations.quiet_post_quote_info.message": "Cuando cites una publicación pública silenciosa, tu publicación se ocultará de las cronologías de tendencias.", @@ -252,9 +273,12 @@ "confirmations.revoke_quote.confirm": "Eliminar publicación", "confirmations.revoke_quote.message": "Esta acción no tiene vuelta atrás.", "confirmations.revoke_quote.title": "¿Eliminar la publicación?", + "confirmations.unblock.confirm": "Desbloquear", + "confirmations.unblock.title": "¿Desbloquear a {name}?", "confirmations.unfollow.confirm": "Dejar de seguir", - "confirmations.unfollow.message": "¿Seguro que quieres dejar de seguir a {name}?", - "confirmations.unfollow.title": "¿Dejar de seguir al usuario?", + "confirmations.unfollow.title": "¿Dejar de seguir a {name}?", + "confirmations.withdraw_request.confirm": "Retirar solicitud", + "confirmations.withdraw_request.title": "¿Retirar solicitud de seguimiento a {name}?", "content_warning.hide": "Ocultar publicación", "content_warning.show": "Mostrar de todos modos", "content_warning.show_more": "Mostrar más", @@ -325,6 +349,7 @@ "empty_column.bookmarked_statuses": "Aún no tienes ninguna publicación guardada como marcador. Cuando guardes una, se mostrará aquí.", "empty_column.community": "La línea de tiempo local está vacía. ¡Escribe algo para empezar la fiesta!", "empty_column.direct": "Aún no tienes menciones privadas. Cuando envíes o recibas una, aparecerán aquí.", + "empty_column.disabled_feed": "Esta cronología ha sido desactivada por los administradores del servidor.", "empty_column.domain_blocks": "Todavía no hay dominios bloqueados.", "empty_column.explore_statuses": "No hay nada en tendencia en este momento. ¡Revisa más tarde!", "empty_column.favourited_statuses": "Todavía no tienes publicaciones favoritas. Cuando marques una publicación como favorita, se mostrarán aquí.", @@ -338,6 +363,7 @@ "empty_column.notification_requests": "¡Todo limpio! No hay nada aquí. Cuando recibas nuevas notificaciones, aparecerán aquí conforme a tu configuración.", "empty_column.notifications": "Aún no tienes ninguna notificación. Cuando otras personas interactúen contigo, aparecerán aquí.", "empty_column.public": "¡No hay nada aquí! Escribe algo públicamente, o sigue usuarios de otras instancias manualmente para llenarlo", + "error.no_hashtag_feed_access": "Únete o inicia sesión para ver y seguir esta etiqueta.", "error.unexpected_crash.explanation": "Debido a un error en nuestro código o a un problema de compatibilidad con el navegador, esta página no se ha podido mostrar correctamente.", "error.unexpected_crash.explanation_addons": "No se pudo mostrar correctamente esta página. Este error probablemente fue causado por un complemento del navegador web o por herramientas de traducción automática.", "error.unexpected_crash.next_steps": "Intenta actualizar la página. Si eso no ayuda, quizás puedas usar Mastodon desde otro navegador o aplicación nativa.", @@ -349,11 +375,9 @@ "explore.trending_links": "Noticias", "explore.trending_statuses": "Publicaciones", "explore.trending_tags": "Etiquetas", + "featured_carousel.current": "Publicación {current, number} / {max, number}", "featured_carousel.header": "{count, plural,one {Publicación fijada} other {Publicaciones fijadas}}", - "featured_carousel.next": "Siguiente", - "featured_carousel.post": "Publicación", - "featured_carousel.previous": "Anterior", - "featured_carousel.slide": "{index} de {total}", + "featured_carousel.slide": "Publicación {current, number} de {max, number}", "filter_modal.added.context_mismatch_explanation": "Esta categoría de filtro no se aplica al contexto en el que ha accedido a esta publlicación. Si quieres que la publicación sea filtrada también en este contexto, tendrás que editar el filtro.", "filter_modal.added.context_mismatch_title": "¡El contexto no coincide!", "filter_modal.added.expired_explanation": "Esta categoría de filtro ha caducado, tendrás que cambiar la fecha de caducidad para que se aplique.", @@ -396,6 +420,7 @@ "follow_suggestions.who_to_follow": "A quién seguir", "followed_tags": "Etiquetas seguidas", "footer.about": "Acerca de", + "footer.about_this_server": "Acerca de", "footer.directory": "Directorio de perfiles", "footer.get_app": "Obtener la aplicación", "footer.keyboard_shortcuts": "Atajos de teclado", @@ -497,6 +522,7 @@ "keyboard_shortcuts.toggle_hidden": "Mostrar/ocultar texto tras aviso de contenido (CW)", "keyboard_shortcuts.toggle_sensitivity": "Mostrar/ocultar multimedia", "keyboard_shortcuts.toot": "Comenzar una nueva publicación", + "keyboard_shortcuts.top": "Mover al principio de la lista", "keyboard_shortcuts.translate": "para traducir una publicación", "keyboard_shortcuts.unfocus": "Quitar el foco de la caja de redacción/búsqueda", "keyboard_shortcuts.up": "Moverse hacia arriba en la lista", @@ -745,7 +771,9 @@ "privacy.unlisted.short": "Pública silenciosa", "privacy_policy.last_updated": "Actualizado por última vez {date}", "privacy_policy.title": "Política de Privacidad", + "quote_error.edit": "No se pueden añadir citas cuando se edita una publicación.", "quote_error.poll": "No es posible citar encuestas.", + "quote_error.private_mentions": "No se permite citar con menciones privadas.", "quote_error.quote": "Solo se permite una cita a la vez.", "quote_error.unauthorized": "No tienes permiso para citar esta publicación.", "quote_error.upload": "No se permite citar con archivos multimedia.", @@ -865,8 +893,12 @@ "status.cannot_quote": "No tienes permiso para citar esta publicación", "status.cannot_reblog": "Esta publicación no se puede impulsar", "status.contains_quote": "Contiene cita", - "status.context.load_new_replies": "Hay nuevas respuestas", - "status.context.loading": "Buscando más respuestas", + "status.context.loading": "Cargando más respuestas", + "status.context.loading_error": "No se pudieron cargar nuevas respuestas", + "status.context.loading_success": "Cargadas nuevas respuestas", + "status.context.more_replies_found": "Se encontraron más respuestas", + "status.context.retry": "Reintentar", + "status.context.show": "Mostrar", "status.continued_thread": "Continuó el hilo", "status.copy": "Copiar enlace a la publicación", "status.delete": "Borrar", @@ -879,7 +911,7 @@ "status.edited_x_times": "Editado {count, plural, one {{count} vez} other {{count} veces}}", "status.embed": "Obtener código para incrustar", "status.favourite": "Favorito", - "status.favourites": "{count, plural, one {favorito} other {favoritos}}", + "status.favourites_count": "{count, plural,one {{counter} favorito}other {{counter} favoritos}}", "status.filter": "Filtrar esta publicación", "status.history.created": "{name} creó {date}", "status.history.edited": "{name} editó {date}", @@ -895,9 +927,12 @@ "status.pin": "Fijar", "status.quote": "Citar", "status.quote.cancel": "Cancelar cita", + "status.quote_error.blocked_account_hint.title": "Esta publicación está oculta porque has bloqueado a @{name}.", + "status.quote_error.blocked_domain_hint.title": "Esta publicación está oculta porque has bloqueado @{domain}.", "status.quote_error.filtered": "Oculto debido a uno de tus filtros", "status.quote_error.limited_account_hint.action": "Mostrar de todos modos", "status.quote_error.limited_account_hint.title": "Esta cuenta ha sido ocultada por los moderadores de {domain}.", + "status.quote_error.muted_account_hint.title": "Esta publicación está oculta porque has silenciado a @{name}.", "status.quote_error.not_available": "Publicación no disponible", "status.quote_error.pending_approval": "Publicación pendiente", "status.quote_error.pending_approval_popout.body": "En Mastodon, puedes controlar si alguien puede citarte. Esta publicación está pendiente mientras obtenemos la aprobación del autor original.", @@ -908,15 +943,17 @@ "status.quote_policy_change": "Cambia quién puede citarte", "status.quote_post_author": "Ha citado una publicación de @{name}", "status.quote_private": "Las publicaciones privadas no pueden ser citadas", - "status.quotes": "{count, plural,one {cita} other {citas}}", "status.quotes.empty": "Nadie ha citado esta publicación todavía. Cuando alguien lo haga, aparecerá aquí.", + "status.quotes.local_other_disclaimer": "Las citas rechazadas por el autor no se mostrarán.", + "status.quotes.remote_other_disclaimer": "Solo se garantiza que se muestren las citas de {domain}. Las citas rechazadas por el autor no se mostrarán.", + "status.quotes_count": "{count, plural,one {{counter} cita} other {{counter} citas}}", "status.read_more": "Leer más", "status.reblog": "Impulsar", "status.reblog_or_quote": "Impulsar o citar", "status.reblog_private": "Compartir de nuevo con tus seguidores", "status.reblogged_by": "Impulsado por {name}", - "status.reblogs": "{count, plural, one {impulso} other {impulsos}}", "status.reblogs.empty": "Nadie ha impulsado esta publicación todavía. Cuando alguien lo haga, aparecerá aquí.", + "status.reblogs_count": "{count, plural,one {{counter} impulso} other {{counter} impulsos}}", "status.redraft": "Borrar y volver a borrador", "status.remove_bookmark": "Eliminar marcador", "status.remove_favourite": "Eliminar de favoritos", @@ -990,6 +1027,8 @@ "video.volume_down": "Bajar volumen", "video.volume_up": "Subir volumen", "visibility_modal.button_title": "Configura la visibilidad", + "visibility_modal.direct_quote_warning.text": "Si guardas la configuración actual, la cita incrustada se convertirá en un enlace.", + "visibility_modal.direct_quote_warning.title": "No se pueden incluir citas en menciones privadas", "visibility_modal.header": "Visibilidad e interacciones", "visibility_modal.helper.direct_quoting": "Las menciones privadas publicadas en Mastodon no pueden ser citadas por otros usuarios.", "visibility_modal.helper.privacy_editing": "La visibilidad no se puede cambiar después de que se haya hecho una publicación.", diff --git a/app/javascript/mastodon/locales/et.json b/app/javascript/mastodon/locales/et.json index 4aaeb514210258..d69b7235d2148c 100644 --- a/app/javascript/mastodon/locales/et.json +++ b/app/javascript/mastodon/locales/et.json @@ -3,7 +3,7 @@ "about.contact": "Kontakt:", "about.default_locale": "Vaikimisi", "about.disclaimer": "Mastodon on tasuta ja vaba tarkvara ning Mastodon gGmbH kaubamärk.", - "about.domain_blocks.no_reason_available": "Põhjus teadmata", + "about.domain_blocks.no_reason_available": "Põhjus on teadmata", "about.domain_blocks.preamble": "Mastodon lubab tavaliselt vaadata sisu ning suhelda kasutajatega ükskõik millisest teisest fediversumi serverist. Need on erandid, mis on paika pandud sellel kindlal serveril.", "about.domain_blocks.silenced.explanation": "Sa ei näe üldiselt profiile ja sisu sellelt serverilt, kui sa just tahtlikult seda ei otsi või jälgimise moel nõusolekut ei anna.", "about.domain_blocks.silenced.title": "Piiratud", @@ -14,7 +14,7 @@ "about.powered_by": "Hajutatud sotsiaalmeedia, mille taga on {mastodon}", "about.rules": "Serveri reeglid", "account.account_note_header": "Isiklik märge", - "account.add_or_remove_from_list": "Lisa või Eemalda loeteludest", + "account.add_or_remove_from_list": "Lisa või eemalda loenditest", "account.badges.bot": "Robot", "account.badges.group": "Grupp", "account.block": "Blokeeri @{name}", @@ -28,6 +28,7 @@ "account.disable_notifications": "Peata teavitused @{name} postitustest", "account.domain_blocking": "Blokeeritud domeen", "account.edit_profile": "Muuda profiili", + "account.edit_profile_short": "Muuda", "account.enable_notifications": "Teavita mind @{name} postitustest", "account.endorse": "Too profiilil esile", "account.familiar_followers_many": "Jälgijateks {name1}, {name2} ja veel {othersCount, plural, one {üks kasutaja, keda tead} other {# kasutajat, keda tead}}", @@ -35,11 +36,16 @@ "account.familiar_followers_two": "Jälgijateks {name1} ja {name2}", "account.featured": "Esiletõstetud", "account.featured.accounts": "Profiilid", - "account.featured.hashtags": "Sildid", - "account.featured_tags.last_status_at": "Viimane postitus {date}", + "account.featured.hashtags": "Teemaviited", + "account.featured_tags.last_status_at": "Viimane postitus: {date}", "account.featured_tags.last_status_never": "Postitusi pole", "account.follow": "Jälgi", "account.follow_back": "Jälgi vastu", + "account.follow_back_short": "Jälgi vastu", + "account.follow_request": "Jälgimispäring", + "account.follow_request_cancel": "Tühista päring", + "account.follow_request_cancel_short": "Katkesta", + "account.follow_request_short": "Koosta päring", "account.followers": "Jälgijad", "account.followers.empty": "Keegi ei jälgi veel seda kasutajat.", "account.followers_counter": "{count, plural, one {{counter} jälgija} other {{counter} jälgijat}}", @@ -58,10 +64,10 @@ "account.media": "Meedia", "account.mention": "Maini @{name}", "account.moved_to": "{name} on teada andnud, et ta uus konto on nüüd:", - "account.mute": "Vaigista @{name}", - "account.mute_notifications_short": "Vaigista teavitused", - "account.mute_short": "Vaigista", - "account.muted": "Vaigistatud", + "account.mute": "Summuta @{name}", + "account.mute_notifications_short": "Summuta teavitused", + "account.mute_short": "Summuta", + "account.muted": "Summutatud", "account.muting": "Summutatud konto", "account.mutual": "Te jälgite teineteist", "account.no_bio": "Kirjeldust pole lisatud.", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Postitused ja vastused", "account.remove_from_followers": "Eemalda {name} jälgijate seast", "account.report": "Raporteeri @{name}", - "account.requested": "Ootab kinnitust. Klõpsa jälgimise soovi tühistamiseks", "account.requested_follow": "{name} on taodelnud sinu jälgimist", "account.requests_to_follow_you": "soovib sind jälgida", "account.share": "Jaga @{name} profiili", @@ -82,9 +87,9 @@ "account.unblock_short": "Eemalda blokeering", "account.unendorse": "Ära kuva profiilil", "account.unfollow": "Jälgid", - "account.unmute": "Ära vaigista @{name}", - "account.unmute_notifications_short": "Tühista teadete vaigistamine", - "account.unmute_short": "Lõpeta vaigistamine", + "account.unmute": "Lõpeta {name} kasutaja summutamine", + "account.unmute_notifications_short": "Lõpeta teavituste summutamine", + "account.unmute_short": "Lõpeta summutamine", "account_note.placeholder": "Klõpsa märke lisamiseks", "admin.dashboard.daily_retention": "Kasutajate päevane allesjäämine peale registreerumist", "admin.dashboard.monthly_retention": "Kasutajate kuine allesjäämine peale registreerumist", @@ -108,25 +113,45 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Kirjelda seda nägemispuudega inimeste jaoks…", "alt_text_modal.done": "Valmis", "announcement.announcement": "Teadaanne", - "annual_report.summary.archetype.booster": "Ägesisu küttija", - "annual_report.summary.archetype.lurker": "Hiilija", - "annual_report.summary.archetype.oracle": "Oraakel", - "annual_report.summary.archetype.pollster": "Küsitleja", - "annual_report.summary.archetype.replier": "Sotsiaalne liblikas", - "annual_report.summary.followers.followers": "jälgijad", - "annual_report.summary.followers.total": "{count} kokku", - "annual_report.summary.here_it_is": "Siin on sinu {year} ülevaatlikult:", - "annual_report.summary.highlighted_post.by_favourites": "enim lemmikuks märgitud postitus", - "annual_report.summary.highlighted_post.by_reblogs": "enim jagatud postitus", - "annual_report.summary.highlighted_post.by_replies": "kõige vastatum postitus", - "annual_report.summary.highlighted_post.possessive": "omanik {name}", + "annual_report.announcement.action_build": "Koosta kokkuvõte minu tegevusest Mastodonis", + "annual_report.announcement.action_dismiss": "Tänan, ei", + "annual_report.announcement.action_view": "Vaata kokkuvõtet minu tegevusest Mastodonis", + "annual_report.announcement.description": "Vaata teavet oma suhestumise kohta Mastodonis eelmisel aastal.", + "annual_report.announcement.title": "{year}. aasta Mastodoni kokkuvõte on valmis", + "annual_report.nav_item.badge": "Uus", + "annual_report.shared_page.donate": "Anneta", + "annual_report.shared_page.footer": "Loodud {heart} Mastodoni meeskonna poolt", + "annual_report.shared_page.footer_server_info": "{username} kasutab {domain}-i, üht paljudest kogukondadest, mis toimivad Mastodonil.", + "annual_report.summary.archetype.booster.desc_public": "{name} jätkas postituste otsimist, et neid edendada, tugevdades teisi loojaid täiusliku täpsusega.", + "annual_report.summary.archetype.booster.desc_self": "Sa jätkasid postituste otsimist, et neid edendada, tugevdades teisi loojaid täiusliku täpsusega.", + "annual_report.summary.archetype.booster.name": "Vibukütt", + "annual_report.summary.archetype.die_drei_fragezeichen": "???", + "annual_report.summary.archetype.lurker.desc_public": "Me teame, et {name} oli kusagil seal väljas ja nautis Mastodoni oma vaiksel moel.", + "annual_report.summary.archetype.lurker.desc_self": "Me teame, et sa olid kusagil seal väljas ja nautisid Mastodoni oma vaiksel moel.", + "annual_report.summary.archetype.lurker.name": "Stoiline", + "annual_report.summary.archetype.oracle.desc_public": "{name} lõi rohkem uusi postitusi kui vastuseid, hoides Mastodoni värskena ja tulevikku suunatuna.", + "annual_report.summary.archetype.oracle.desc_self": "Sa lõid rohkem uusi postitusi kui vastuseid, hoides Mastodoni värskena ja tulevikku suunatuna.", + "annual_report.summary.archetype.oracle.name": "Oraakel", + "annual_report.summary.archetype.pollster.desc_public": "{name} lõi rohkem küsitlusi kui teisi postituse tüüpe, äratades Mastodonis uudishimu.", + "annual_report.summary.archetype.pollster.desc_self": "Sa lõid rohkem küsitlusi kui teisi postituse tüüpe, äratades Mastodonis uudishimu.", + "annual_report.summary.archetype.pollster.name": "Uudishimulik", + "annual_report.summary.archetype.replier.desc_public": "{name} vastas sageli teiste inimeste postitustele, pakkudes Mastodonile uusi aruteluteemasid.", + "annual_report.summary.archetype.replier.desc_self": "Sa vastasid sageli teiste inimeste postitustele, pakkudes Mastodonile uusi aruteluteemasid.", + "annual_report.summary.archetype.replier.name": "Liblikas", + "annual_report.summary.archetype.reveal": "Näita mu põhitüüpi", + "annual_report.summary.archetype.reveal_description": "Täname, et oled Mastodoni liige! Aeg on välja selgitada, millist põhitüüpi sa {year}. kehastasid.", + "annual_report.summary.archetype.title_public": "Kasutaja {name} põhitüüp", + "annual_report.summary.archetype.title_self": "Sinu põhitüüp", + "annual_report.summary.close": "Sule", + "annual_report.summary.copy_link": "Kopeeri link", + "annual_report.summary.highlighted_post.title": "Kõige populaarsemad postitused", "annual_report.summary.most_used_app.most_used_app": "enim kasutatud äpp", - "annual_report.summary.most_used_hashtag.most_used_hashtag": "enim kasutatud silt", - "annual_report.summary.most_used_hashtag.none": "Pole", + "annual_report.summary.most_used_hashtag.most_used_hashtag": "enim kasutatud teemaviide", + "annual_report.summary.most_used_hashtag.used_count": "Sa lisasid selle teemaviite {count, plural, one {ühele postitusele} other {#-le postitusele}}.", "annual_report.summary.new_posts.new_posts": "uus postitus", "annual_report.summary.percentile.text": "See paneb su top {domain} kasutajate hulka.", "annual_report.summary.percentile.we_wont_tell_bernie": "Vägev.", - "annual_report.summary.thanks": "Tänud olemast osa Mastodonist!", + "annual_report.summary.share_message": "Minu arhetüüp on {archetype}!", "attachments_list.unprocessed": "(töötlemata)", "audio.hide": "Peida audio", "block_modal.remote_users_caveat": "Serverile {domain} edastatakse palve otsust järgida. Ometi pole see tagatud, kuna mõned serverid võivad blokeeringuid käsitleda omal moel. Avalikud postitused võivad tuvastamata kasutajatele endiselt näha olla.", @@ -152,6 +177,8 @@ "bundle_modal_error.close": "Sulge", "bundle_modal_error.message": "Selle ekraanitäie laadimisel läks midagi valesti.", "bundle_modal_error.retry": "Proovi uuesti", + "carousel.current": "Slaid {current, number} / {max, number}", + "carousel.slide": "Slaid {current, number} / {max, number}", "closed_registrations.other_server_instructions": "Kuna Mastodon on detsentraliseeritud, võib konto teha teise serverisse ja sellegipoolest siinse kontoga suhelda.", "closed_registrations_modal.description": "Praegu ei ole võimalik teha {domain} peale kontot, aga pea meeles, et sul ei pea olema just {domain} konto, et Mastodoni kasutada.", "closed_registrations_modal.find_another_server": "Leia teine server", @@ -168,11 +195,13 @@ "column.edit_list": "Muuda loendit", "column.favourites": "Lemmikud", "column.firehose": "Postitused reaalajas", + "column.firehose_local": "Selle serveri sisuvoog reaalajas", + "column.firehose_singular": "Postitused reaalajas", "column.follow_requests": "Jälgimistaotlused", "column.home": "Kodu", "column.list_members": "Halda loendi liikmeid", "column.lists": "Loetelud", - "column.mutes": "Vaigistatud kasutajad", + "column.mutes": "Summutatud kasutajad", "column.notifications": "Teated", "column.pins": "Kinnitatud postitused", "column.public": "Föderatiivne ajajoon", @@ -187,6 +216,7 @@ "community.column_settings.local_only": "Ainult kohalik", "community.column_settings.media_only": "Ainult meedia", "community.column_settings.remote_only": "Ainult kaug", + "compose.error.blank_post": "Postitus ei saa jääda tühjaks.", "compose.language.change": "Muuda keelt", "compose.language.search": "Otsi keeli...", "compose.published.body": "Postitus tehtud.", @@ -194,7 +224,7 @@ "compose.saved.body": "Postitus salvestatud.", "compose_form.direct_message_warning_learn_more": "Vaata lisa", "compose_form.encryption_warning": "Postitused Mastodonis ei ole otsast-otsani krüpteeritud. Ära jaga mingeid delikaatseid andmeid Mastodoni kaudu.", - "compose_form.hashtag_warning": "See postitus ei ilmu ühegi märksõna all, kuna pole avalik. Vaid avalikud postitused on märksõnade kaudu leitavad.", + "compose_form.hashtag_warning": "See postitus ei ilmu ühegi teemaviite all, kuna pole avalik. Vaid avalikud postitused on teemaviidete kaudu leitavad.", "compose_form.lock_disclaimer": "Su konto ei ole {locked}. Igaüks saab sind jälgida, et näha su ainult-jälgijatele postitusi.", "compose_form.lock_disclaimer.lock": "lukus", "compose_form.placeholder": "Millest mõtled?", @@ -238,7 +268,12 @@ "confirmations.missing_alt_text.message": "Sinu postituses on ilma alt-tekstita meediat. Kirjelduse lisamine aitab su sisu muuta ligipääsetavaks rohkematele inimestele.", "confirmations.missing_alt_text.secondary": "Postita siiski", "confirmations.missing_alt_text.title": "Lisada alt-tekst?", - "confirmations.mute.confirm": "Vaigista", + "confirmations.mute.confirm": "Summuta", + "confirmations.private_quote_notify.cancel": "Tagasi muutmise juurde", + "confirmations.private_quote_notify.confirm": "Avalda postitus", + "confirmations.private_quote_notify.do_not_show_again": "Ära kuva enam seda sõnumit uuesti", + "confirmations.private_quote_notify.message": "Nii see, keda sa tsiteerid, kui need, keda mainid, saavad asjakohase teavituse ja võivad vaadata sinu postitust ka siis, kui nad pole sinu jälgijad.", + "confirmations.private_quote_notify.title": "Kas jagad jälgijate ja mainitud kasutajatega?", "confirmations.quiet_post_quote_info.dismiss": "Ära tuleta enam meelde", "confirmations.quiet_post_quote_info.got_it": "Sain aru", "confirmations.quiet_post_quote_info.message": "Vaikse, aga avaliku postituse tsiteerimisel sinu postitus on peidetud populaarsust koguvatel ajajoontel.", @@ -252,9 +287,12 @@ "confirmations.revoke_quote.confirm": "Eemalda postitus", "confirmations.revoke_quote.message": "Seda tegevust ei saa tagasi pöörata.", "confirmations.revoke_quote.title": "Kas eemaldame postituse?", + "confirmations.unblock.confirm": "Lõpeta blokeerimine", + "confirmations.unblock.title": "Kas lõpetad {name} kasutaja blokeerimise?", "confirmations.unfollow.confirm": "Ära jälgi", - "confirmations.unfollow.message": "Oled kindel, et ei soovi rohkem jälgida kasutajat {name}?", - "confirmations.unfollow.title": "Ei jälgi enam kasutajat?", + "confirmations.unfollow.title": "Kas lõpetad {name} kasutaja jälgimise?", + "confirmations.withdraw_request.confirm": "Tühista päring", + "confirmations.withdraw_request.title": "Tühistad päringu {name} kasutaja jälgimiseks?", "content_warning.hide": "Peida postitus", "content_warning.show": "Näita ikkagi", "content_warning.show_more": "Näita rohkem", @@ -279,7 +317,7 @@ "domain_block_modal.they_can_interact_with_old_posts": "Inimesed sellest serverist saavad suhestuda sinu vanade postitustega.", "domain_block_modal.they_cant_follow": "Sellest serverist ei saa keegi sind jälgida.", "domain_block_modal.they_wont_know": "Nad ei tea, et nad on blokeeritud.", - "domain_block_modal.title": "Blokeerida domeen?", + "domain_block_modal.title": "Kas blokeerid domeeni?", "domain_block_modal.you_will_lose_num_followers": "Sult kaob {followersCount, plural, one {{followersCountDisplay} jälgija} other {{followersCountDisplay} jälgijat}} ja {followingCount, plural, one {{followingCountDisplay} inimene} other {{followingCountDisplay} inimest}}, keda sa ise jälgid.", "domain_block_modal.you_will_lose_relationships": "Sa kaotad kõik oma jälgijad ja inimesed, kes sind jälgivad sellest serverist.", "domain_block_modal.you_wont_see_posts": "Sa ei näe selle serveri kasutajate postitusi ega teavitusi.", @@ -314,8 +352,8 @@ "emoji_button.search_results": "Otsitulemused", "emoji_button.symbols": "Sümbolid", "emoji_button.travel": "Reisimine & kohad", - "empty_column.account_featured.me": "Sa pole veel midagi esile tõstnud. Kas sa teadsid, et oma profiilis saad esile tõsta enamkasutatavaid silte või või sõbra kasutajakontot?", - "empty_column.account_featured.other": "{acct} pole veel midagi esile tõstnud. Kas sa teadsid, et oma profiilis saad esile tõsta enamkasutatavaid silte või või sõbra kasutajakontot?", + "empty_column.account_featured.me": "Sa pole veel midagi esile tõstnud. Kas sa teadsid, et oma profiilis saad esile tõsta enamkasutatavaid teemaviiteid või sõbra kasutajakontot?", + "empty_column.account_featured.other": "{acct} pole veel midagi esile tõstnud. Kas sa teadsid, et oma profiilis saad esile tõsta enamkasutatavaid teemaviiteid või sõbra kasutajakontot?", "empty_column.account_featured_other.unknown": "See kasutajakonto pole veel midagi esile tõstnud.", "empty_column.account_hides_collections": "See kasutaja otsustas mitte teha seda infot saadavaks", "empty_column.account_suspended": "Konto kustutatud", @@ -325,19 +363,21 @@ "empty_column.bookmarked_statuses": "Järjehoidjatesse pole veel lisatud postitusi. Kui lisad mõne, näed neid siin.", "empty_column.community": "Kohalik ajajoon on tühi. Kirjuta midagi avalikult, et pall veerema ajada!", "empty_column.direct": "Sul pole veel ühtegi privaatset mainimist. Kui saadad või saad mõne, ilmuvad need siin.", + "empty_column.disabled_feed": "See infovoog on serveri peakasutajate poolt välja lülitatud.", "empty_column.domain_blocks": "Siin ei ole veel peidetud domeene.", "empty_column.explore_statuses": "Praegu pole ühtegi trendi. Tule hiljem tagasi!", "empty_column.favourited_statuses": "Pole veel lemmikpostitusi. Kui märgid mõne, näed neid siin.", "empty_column.favourites": "Keegi pole veel seda postitust lemmikuks märkinud. Kui keegi seda teeb, siis on ta nähtav siin.", "empty_column.follow_requests": "Pole hetkel ühtegi jälgimistaotlust. Kui saad mõne, näed neid siin.", - "empty_column.followed_tags": "Sa ei jälgi veel ühtegi märksõna. Kui jälgid, ilmuvad need siia.", - "empty_column.hashtag": "Selle sildi all ei ole ühtegi postitust.", + "empty_column.followed_tags": "Sa ei jälgi veel ühtegi teemaviidet. Kui jälgid, ilmuvad need siia.", + "empty_column.hashtag": "Selle teemaviite all ei ole ühtegi postitust.", "empty_column.home": "Su koduajajoon on tühi. Jälgi rohkemaid inimesi, et seda täita {suggestions}", "empty_column.list": "Siin loetelus pole veel midagi. Kui loetelu liikmed teevad uusi postitusi, näed neid siin.", - "empty_column.mutes": "Sa pole veel ühtegi kasutajat vaigistanud.", + "empty_column.mutes": "Sa pole veel ühtegi kasutajat summutanud.", "empty_column.notification_requests": "Kõik tühi! Siin pole mitte midagi. Kui saad uusi teavitusi, ilmuvad need siin vastavalt sinu seadistustele.", "empty_column.notifications": "Ei ole veel teateid. Kui keegi suhtleb sinuga, näed seda siin.", "empty_column.public": "Siin pole midagi! Kirjuta midagi avalikku või jälgi ise kasutajaid täitmaks seda ruumi", + "error.no_hashtag_feed_access": "Selle teemaviite jälgimiseks liitu teenusega või logi sisse oma kasutajakontoga.", "error.unexpected_crash.explanation": "Meie poolse probleemi või veebilehitseja ühilduvusprobleemi tõttu ei suutnud me seda lehekülge korrektselt näidata.", "error.unexpected_crash.explanation_addons": "Seda lehte ei suudetud õigesti kuvada. Selle vea põhjustas arvatavasti mõni lehitseja lisand või automaattõlke tööriist.", "error.unexpected_crash.next_steps": "Proovi lehekülge uuesti avada. Kui see ei aita, võib proovida kasutada Mastodoni mõne muu veebilehitseja või äpi kaudu.", @@ -348,12 +388,10 @@ "explore.title": "Populaarsust koguv", "explore.trending_links": "Uudised", "explore.trending_statuses": "Postitused", - "explore.trending_tags": "Sildid", + "explore.trending_tags": "Teemaviited", + "featured_carousel.current": "Postitus {current, number} / {max, number}", "featured_carousel.header": "{count, plural, one {Esiletõstetud postitus} other {Esiletõstetud postitust}}", - "featured_carousel.next": "Järgmine", - "featured_carousel.post": "Postita", - "featured_carousel.previous": "Eelmine", - "featured_carousel.slide": "{index} / {total}", + "featured_carousel.slide": "Postitus {current, number} / {max, number}", "filter_modal.added.context_mismatch_explanation": "See filtrikategooria ei rakendu kontekstis, kuidas postituseni jõudsid. Kui tahad postitust ka selles kontekstis filtreerida, pead muutma filtrit.", "filter_modal.added.context_mismatch_title": "Konteksti mittesobivus!", "filter_modal.added.expired_explanation": "Selle filtri kategooria on aegunud. pead muutma aegumiskuupäeva, kui tahad, et filter kehtiks.", @@ -394,8 +432,9 @@ "follow_suggestions.similar_to_recently_followed_longer": "Sarnane profiilile, mida hiljuti jälgima hakkasid", "follow_suggestions.view_all": "Vaata kõiki", "follow_suggestions.who_to_follow": "Keda jälgida", - "followed_tags": "Jälgitavad märksõnad", + "followed_tags": "Jälgitavad teemaviited", "footer.about": "Teave", + "footer.about_this_server": "Serveri teave", "footer.directory": "Profiilikataloog", "footer.get_app": "Laadi rakendus", "footer.keyboard_shortcuts": "Kiirklahvid", @@ -406,23 +445,23 @@ "generic.saved": "Salvestatud", "getting_started.heading": "Alustamine", "hashtag.admin_moderation": "Ava modereerimisliides #{name} jaoks", - "hashtag.browse": "Sirvi #{hashtag} sildiga postitusi", - "hashtag.browse_from_account": "Sirvi @{name} kasutaja #{hashtag} sildiga postitusi", + "hashtag.browse": "Sirvi #{hashtag} teemaviitega postitusi", + "hashtag.browse_from_account": "Sirvi @{name} kasutaja #{hashtag} teemaviitega postitusi", "hashtag.column_header.tag_mode.all": "ja {additional}", - "hashtag.column_header.tag_mode.any": "või {additional}", - "hashtag.column_header.tag_mode.none": "ilma {additional}", + "hashtag.column_header.tag_mode.any": "või teemaviide {additional}", + "hashtag.column_header.tag_mode.none": "ilma teemaviiteta {additional}", "hashtag.column_settings.select.no_options_message": "Soovitusi ei leitud", "hashtag.column_settings.select.placeholder": "Sisesta sildid…", "hashtag.column_settings.tag_mode.all": "Kõik need", "hashtag.column_settings.tag_mode.any": "Mõni neist", "hashtag.column_settings.tag_mode.none": "Mitte ükski neist", - "hashtag.column_settings.tag_toggle": "Kaasa lisamärked selle tulba jaoks", + "hashtag.column_settings.tag_toggle": "Kaasa lisasildid selle veeru jaoks", "hashtag.counter_by_accounts": "{count, plural, one {{counter} osalejaga} other {{counter} osalejaga}}", "hashtag.counter_by_uses": "{count, plural, one {{counter} postitusega} other {{counter} postitusega}}", "hashtag.counter_by_uses_today": "{count, plural, one {{counter} postitust} other {{counter} postitust}} täna", "hashtag.feature": "Tõsta profiilis esile", "hashtag.follow": "Jälgi silti", - "hashtag.mute": "Vaigista @#{hashtag}", + "hashtag.mute": "Summuta teemaviide @#{hashtag}", "hashtag.unfeature": "Ära tõsta profiilis esile", "hashtag.unfollow": "Lõpeta sildi jälgimine", "hashtags.and_other": "…ja {count, plural, one {}other {# veel}}", @@ -482,7 +521,7 @@ "keyboard_shortcuts.load_more": "Fookus „Laadi veel“ nupule", "keyboard_shortcuts.local": "Ava kohalik ajajoon", "keyboard_shortcuts.mention": "Maini autorit", - "keyboard_shortcuts.muted": "Ava vaigistatud kasutajate loetelu", + "keyboard_shortcuts.muted": "Ava summutatud kasutajate loetelu", "keyboard_shortcuts.my_profile": "Ava oma profiil", "keyboard_shortcuts.notifications": "Ava teadete veerg", "keyboard_shortcuts.open_media": "Ava meedia", @@ -497,6 +536,7 @@ "keyboard_shortcuts.toggle_hidden": "Näita/peida teksti hoiatuse taga", "keyboard_shortcuts.toggle_sensitivity": "Näita/peida meediat", "keyboard_shortcuts.toot": "Alusta uut postitust", + "keyboard_shortcuts.top": "Tõsta loendi algusesse", "keyboard_shortcuts.translate": "postituse tõlkimiseks", "keyboard_shortcuts.unfocus": "Fookus tekstialalt/otsingult ära", "keyboard_shortcuts.up": "Liigu loetelus üles", @@ -543,11 +583,11 @@ "moved_to_account_banner.text": "Kontot {disabledAccount} ei ole praegu võimalik kasutada, sest kolisid kontole {movedToAccount}.", "mute_modal.hide_from_notifications": "Peida teavituste hulgast", "mute_modal.hide_options": "Peida valikud", - "mute_modal.indefinite": "Kuni eemaldan neilt vaigistuse", + "mute_modal.indefinite": "Kuni eemaldan neilt summutamise", "mute_modal.show_options": "Kuva valikud", "mute_modal.they_can_mention_and_follow": "Ta saab sind mainida ja sind jälgida, kuid sa ei näe teda.", - "mute_modal.they_wont_know": "Ta ei tea, et ta on vaigistatud.", - "mute_modal.title": "Vaigistada kasutaja?", + "mute_modal.they_wont_know": "Ta ei tea, et ta on summutatud.", + "mute_modal.title": "Kas summutad kasutaja?", "mute_modal.you_wont_see_mentions": "Sa ei näe postitusi, mis teda mainivad.", "mute_modal.you_wont_see_posts": "Ta näeb jätkuvalt sinu postitusi, kuid sa ei näe tema omi.", "navigation_bar.about": "Teave", @@ -560,9 +600,9 @@ "navigation_bar.direct": "Privaatsed mainimised", "navigation_bar.domain_blocks": "Peidetud domeenid", "navigation_bar.favourites": "Lemmikud", - "navigation_bar.filters": "Vaigistatud sõnad", + "navigation_bar.filters": "Summutatud sõnad", "navigation_bar.follow_requests": "Jälgimistaotlused", - "navigation_bar.followed_tags": "Jälgitavad märksõnad", + "navigation_bar.followed_tags": "Jälgitavad teemaviited", "navigation_bar.follows_and_followers": "Jälgitavad ja jälgijad", "navigation_bar.import_export": "Import ja eksport", "navigation_bar.lists": "Loetelud", @@ -571,7 +611,7 @@ "navigation_bar.logout": "Logi välja", "navigation_bar.moderation": "Modereerimine", "navigation_bar.more": "Lisavalikud", - "navigation_bar.mutes": "Vaigistatud kasutajad", + "navigation_bar.mutes": "Summutatud kasutajad", "navigation_bar.opened_in_classic_interface": "Postitused, kontod ja teised spetsiaalsed lehed avatakse vaikimisi klassikalises veebiliideses.", "navigation_bar.preferences": "Eelistused", "navigation_bar.privacy_and_reach": "Privaatsus ja ulatus", @@ -712,7 +752,7 @@ "onboarding.profile.display_name": "Näidatav nimi", "onboarding.profile.display_name_hint": "Su täisnimi või naljanimi…", "onboarding.profile.note": "Elulugu", - "onboarding.profile.note_hint": "Saad @mainida teisi kasutajaid või #sildistada…", + "onboarding.profile.note_hint": "Saad @mainida teisi kasutajaid või lisada #teemaviidet…", "onboarding.profile.save_and_continue": "Salvesta ja jätka", "onboarding.profile.title": "Profiili seadistamine", "onboarding.profile.upload_avatar": "Laadi üles profiilipilt", @@ -740,12 +780,14 @@ "privacy.quote.anyone": "{visibility}, kõik võivad tsiteerida", "privacy.quote.disabled": "{visibility}, tsiteerimine pole lubatud", "privacy.quote.limited": "{visibility}, tsiteerimine on piiratud", - "privacy.unlisted.additional": "See on olemuselt küll avalik, aga postitus ei ilmu voogudes ega märksõnades, lehitsedes ega Mastodoni otsingus, isegi kui konto on seadistustes avalik.", + "privacy.unlisted.additional": "See on olemuselt küll avalik, aga postitus ei ilmu voogudes ega teemaviidetes, lehitsedes ega Mastodoni otsingus, isegi kui konto on seadistustes avalik.", "privacy.unlisted.long": "Peidetud Mastodoni otsingutulemustest, pupulaarsust koguva sisu voost ja avalikult ajajoonelt", "privacy.unlisted.short": "Vaikselt avalik", "privacy_policy.last_updated": "Viimati uuendatud {date}", "privacy_policy.title": "Isikuandmete kaitse", + "quote_error.edit": "Postituse muutmisel ei saa tsitaati lisada.", "quote_error.poll": "Tsiteerimine pole küsitlustes lubatud.", + "quote_error.private_mentions": "Tsiteerimine pole otsemainimiste puhul lubatud.", "quote_error.quote": "Korraga on lubatud vaid üks tsitaat.", "quote_error.unauthorized": "Sul pole õigust seda postitust tsiteerida.", "quote_error.upload": "Tsiteerimine pole manuste puhul lubatud.", @@ -784,8 +826,8 @@ "report.comment.title": "Kas arvad, et on veel midagi, mida me peaks teadma?", "report.forward": "Edasta ka {target} domeeni", "report.forward_hint": "See kasutaja on teisest serverist. Kas saadan anonümiseeritud koopia sellest teatest sinna ka?", - "report.mute": "Vaigista", - "report.mute_explanation": "Sa ei näe tema postitusi. Ta võib ikka sind jälgida ja su postitusi näha. Ta ei saa teada, et ta on vaigistatud.", + "report.mute": "Summuta", + "report.mute_explanation": "Sa ei näe tema postitusi. Ta võib ikka sind jälgida ja su postitusi näha. Ta ei saa teada, et ta on summutatud.", "report.next": "Järgmine", "report.placeholder": "Lisaks kommentaarid", "report.reasons.dislike": "Mulle ei meeldi see", @@ -825,7 +867,7 @@ "search.placeholder": "Otsi", "search.quick_action.account_search": "Sobivaid profiile {x}", "search.quick_action.go_to_account": "Mine profiili {x}", - "search.quick_action.go_to_hashtag": "Ava silt {x}", + "search.quick_action.go_to_hashtag": "Ava teemaviide {x}", "search.quick_action.open_url": "Ava URL Mastodonis", "search.quick_action.status_search": "Sobivad postitused {x}", "search.search_or_paste": "Otsi või kleebi URL", @@ -841,7 +883,7 @@ "search_results.all": "Kõik", "search_results.hashtags": "Sildid", "search_results.no_results": "Tulemusi pole.", - "search_results.no_search_yet": "Proovi otsida postitusi, profiile või silte.", + "search_results.no_search_yet": "Proovi otsida postitusi, profiile või teemaviiteid.", "search_results.see_all": "Vaata kõiki", "search_results.statuses": "Postitused", "search_results.title": "Otsi märksõna: {q}", @@ -865,8 +907,12 @@ "status.cannot_quote": "Sul pole õigust seda postitust tsiteerida", "status.cannot_reblog": "Seda postitust ei saa jagada", "status.contains_quote": "Sisaldab tsitaati", - "status.context.load_new_replies": "Leidub uusi vastuseid", - "status.context.loading": "Kontrollin täiendavate vastuste olemasolu", + "status.context.loading": "Laadin veel vastuseid", + "status.context.loading_error": "Uute vastuste laadimine ei õnnestunud", + "status.context.loading_success": "Uued vastused on laaditud", + "status.context.more_replies_found": "Leidub veel vastuseid", + "status.context.retry": "Proovi uuesti", + "status.context.show": "Näita", "status.continued_thread": "Jätkatud lõim", "status.copy": "Kopeeri postituse link", "status.delete": "Kustuta", @@ -879,7 +925,7 @@ "status.edited_x_times": "Muudetud {count, plural, one{{count} kord} other {{count} korda}}", "status.embed": "Hangi manustamiskood", "status.favourite": "Lemmik", - "status.favourites": "{count, plural, one {lemmik} other {lemmikut}}", + "status.favourites_count": "{count, plural, one {{counter} lemmik} other {{counter} lemmikut}}", "status.filter": "Filtreeri seda postitust", "status.history.created": "{name} lõi {date}", "status.history.edited": "{name} muutis {date}", @@ -889,15 +935,18 @@ "status.media_hidden": "Meedia peidetud", "status.mention": "Maini @{name}'i", "status.more": "Veel", - "status.mute": "Vaigista @{name}", - "status.mute_conversation": "Vaigista vestlus", + "status.mute": "Summuta @{name}", + "status.mute_conversation": "Summuta vestlus", "status.open": "Laienda postitus", "status.pin": "Kinnita profiilile", "status.quote": "Tsiteeri", "status.quote.cancel": "Katkesta tsiteerimine", + "status.quote_error.blocked_account_hint.title": "Kuna sa oled blokeerinud kasutaja @{name}, siis see postitus on peidetud.", + "status.quote_error.blocked_domain_hint.title": "Kuna sa oled blokeerinud domeeni @{domain}, siis see postitus on peidetud.", "status.quote_error.filtered": "Peidetud mõne kasutatud filtri tõttu", "status.quote_error.limited_account_hint.action": "Näita ikkagi", "status.quote_error.limited_account_hint.title": "See profiil on peidetud {domain} serveri moderaatorite poolt.", + "status.quote_error.muted_account_hint.title": "Kuna sa oled summutanud kasutaja @{name}, siis see postitus on peidetud.", "status.quote_error.not_available": "Postitus pole saadaval", "status.quote_error.pending_approval": "Postitus on ootel", "status.quote_error.pending_approval_popout.body": "Mastodonis saad sa kontrollida seda, kes võib sind tsiteerida. See postitus on seni ootel, kuni pole algse autori kinnitust tsiteerimisele.", @@ -908,15 +957,17 @@ "status.quote_policy_change": "Muuda neid, kes võivad tsiteerida", "status.quote_post_author": "Tsiteeris kasutaja @{name} postitust", "status.quote_private": "Otsepostituste tsiteerimine pole võimalik", - "status.quotes": "{count, plural, one {# tsiteerimine} other {# tsiteerimist}}", "status.quotes.empty": "Keegi pole seda postitust veel tsiteerinud. Kui keegi seda teeb, siis on ta nähtav siin.", + "status.quotes.local_other_disclaimer": "Autori poolt tagasilükatud tsitaate ei kuvata.", + "status.quotes.remote_other_disclaimer": "Kui kasutaja on {domain} domeenist, siis siin on tagatud vaid tema tsitaatide näitamine. Autori poolt tagasilükatud tsitaate ei kuvata.", + "status.quotes_count": "{count, plural, one {{counter} tsiteerimine} other {{counter} tsiteerimist}}", "status.read_more": "Loe veel", "status.reblog": "Jaga", "status.reblog_or_quote": "Anna hoogu või tsiteeri", "status.reblog_private": "Jaga uuesti oma jälgijatele", "status.reblogged_by": "{name} jagas", - "status.reblogs": "{count, plural, one {jagamine} other {jagamist}}", "status.reblogs.empty": "Keegi pole seda postitust veel jaganud. Kui keegi seda teeb, siis on ta nähtav siin.", + "status.reblogs_count": "{count, plural, one {{counter} jagamine} other {{counter} jagamist}}", "status.redraft": "Kustuta & alga uuesti", "status.remove_bookmark": "Eemalda järjehoidja", "status.remove_favourite": "Eemalda lemmikute seast", @@ -937,7 +988,7 @@ "status.translate": "Tõlgi", "status.translated_from_with": "Tõlgitud {lang} keelest kasutades teenust {provider}", "status.uncached_media_warning": "Eelvaade pole saadaval", - "status.unmute_conversation": "Ära vaigista vestlust", + "status.unmute_conversation": "Lõpeta vestluse summutamine", "status.unpin": "Eemalda profiilile kinnitus", "subscribed_languages.lead": "Pärast muudatust näed koduvaates ja loetelude ajajoontel postitusi valitud keeltes. Ära vali midagi, kui tahad näha postitusi kõikides keeltes.", "subscribed_languages.save": "Salvesta muudatused", @@ -981,15 +1032,17 @@ "video.expand": "Suurenda video", "video.fullscreen": "Täisekraan", "video.hide": "Peida video", - "video.mute": "Vaigista", + "video.mute": "Summuta", "video.pause": "Paus", "video.play": "Mängi", "video.skip_backward": "Keri tagasi", "video.skip_forward": "Keri edasi", - "video.unmute": "Lõpeta vaigistamine", + "video.unmute": "Lõpeta summutamine", "video.volume_down": "Heli vaiksemaks", "video.volume_up": "Heli valjemaks", "visibility_modal.button_title": "Muuda nähtavust", + "visibility_modal.direct_quote_warning.text": "Kui sa need seadistused salvestad, siis lõimitud tsitaat muutub lingiks.", + "visibility_modal.direct_quote_warning.title": "Tsitaate ei saa privaatse mainimise puhul lõimida", "visibility_modal.header": "Nähtavus ja kasutus", "visibility_modal.helper.direct_quoting": "Ainult mainituile mõeldud Mastodoni postitusi ei saa teiste poolt tsiteerida.", "visibility_modal.helper.privacy_editing": "Nähtavust ei saa peale postituse avaldamist muuta.", diff --git a/app/javascript/mastodon/locales/eu.json b/app/javascript/mastodon/locales/eu.json index 579701a525ec10..8b6699864bf952 100644 --- a/app/javascript/mastodon/locales/eu.json +++ b/app/javascript/mastodon/locales/eu.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Utzi jakinarazteari @{name} erabiltzaileak argitaratzean", "account.domain_blocking": "Eragotzitako domeinua", "account.edit_profile": "Editatu profila", + "account.edit_profile_short": "Editatu", "account.enable_notifications": "Jakinarazi @{name} erabiltzaileak argitaratzean", "account.endorse": "Nabarmendu profilean", "account.familiar_followers_many": "Jarraitzaileak: {name1}, {name2} eta beste {othersCount, plural, one {ezagun bat} other {# ezagun}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Bidalketarik ez", "account.follow": "Jarraitu", "account.follow_back": "Jarraitu bueltan", + "account.follow_back_short": "Jarraitu bueltan", + "account.follow_request": "Eskatu jarraitzeko", + "account.follow_request_cancel": "Ezeztatu eskaera", + "account.follow_request_cancel_short": "Ezeztatu", + "account.follow_request_short": "Eskaera", "account.followers": "Jarraitzaileak", "account.followers.empty": "Ez du inork erabiltzaile hau jarraitzen oraindik.", "account.followers_counter": "{count, plural, one {{counter} jarraitzaile} other {{counter} jarraitzaile}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Bidalketak eta erantzunak", "account.remove_from_followers": "Kendu {name} zure jarraitzaileengandik", "account.report": "Salatu @{name}", - "account.requested": "Onarpenaren zain. Egin klik jarraipen-eskaera ezeztatzeko", "account.requested_follow": "{name}-(e)k zu jarraitzeko eskaera egin du", "account.requests_to_follow_you": "Zu jarraitzeko eskaera egin du", "account.share": "Partekatu @{name} erabiltzailearen profila", @@ -108,20 +113,52 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Deskribatu hau ikusmen arazoak dituzten pertsonentzat…", "alt_text_modal.done": "Egina", "announcement.announcement": "Iragarpena", - "annual_report.summary.followers.followers": "jarraitzaileak", - "annual_report.summary.followers.total": "{count} guztira", - "annual_report.summary.here_it_is": "Hona hemen zure {year}. urtearen bilduma:", - "annual_report.summary.highlighted_post.by_favourites": "egindako bidalketa gogokoena", - "annual_report.summary.highlighted_post.by_reblogs": "egindako bidalketa zabalduena", - "annual_report.summary.highlighted_post.by_replies": "erantzun gehien izan dituen bidalketa", - "annual_report.summary.highlighted_post.possessive": "{name}-(r)ena", + "annual_report.announcement.action_build": "Sortu nire Wrapstodon", + "annual_report.announcement.action_dismiss": "Ez, eskerrik asko", + "annual_report.announcement.action_view": "Ikusi nire Wrapstodon", + "annual_report.announcement.description": "Ezagutu azken urtean Mastodonen izan duzun parte-hartzeari buruzko informazio gehiago.", + "annual_report.announcement.title": "Hemen da {2025}(e)ko Wrapstodon", + "annual_report.nav_item.badge": "Berria", + "annual_report.shared_page.donate": "Egin dohaintza", + "annual_report.shared_page.footer": "Mastodonen taldeak {heart} sortua", + "annual_report.shared_page.footer_server_info": "{username}(e)k {domain} erabiltzen du, Mastodonek bultzatutako komunitate ugarietako bat.", + "annual_report.summary.archetype.booster.desc_public": "{name}(e)k promozionatzeko argitalpenen bila jarraitu zuen, beste sortzaile batzuk punteria perfektuarekin anplifikatuz.", + "annual_report.summary.archetype.booster.desc_self": "Bultzatu beharreko argitalpenen zelatan egon zinen, punteria perfektua zuten beste sortzaile batzuk anplifikatuz.", + "annual_report.summary.archetype.booster.name": "Arkularia", + "annual_report.summary.archetype.die_drei_fragezeichen": "???", + "annual_report.summary.archetype.lurker.desc_public": "Badakigu {name} hor zegoela kanpoan, nonbait, Mastodonez gozatzen bere modu lasaian.", + "annual_report.summary.archetype.lurker.desc_self": "Badakigu hor zinela kanpoan, nonbait, Mastodonez gozatzen zure erara, isilean.", + "annual_report.summary.archetype.lurker.name": "Estoikoa", + "annual_report.summary.archetype.oracle.desc_public": "{name}(e)k erantzun baino argitalpen berri gehiago sortu zituen, eta horrek Mastodon fresko eta etorkizuneko ikuspegiarekin mantendu zuen.", + "annual_report.summary.archetype.oracle.desc_self": "Erantzun baino argitalpen berri gehiago sortu zenituen, eta horrek Mastodon fresko eta etorkizuneko ikuspegiarekin mantendu zuen.", + "annual_report.summary.archetype.oracle.name": "Orakulua", + "annual_report.summary.archetype.pollster.desc_public": "{name}(e)k inkesta gehiago sortu zituen beste argitalpen mota batzuek baino, eta horrek jakin-mina piztu zuen Mastodonen.", + "annual_report.summary.archetype.pollster.desc_self": "Beste argitalpen batzuk baino inkesta gehiago sortu zenituen, eta horrek jakin-mina piztu zuen Mastodonen.", + "annual_report.summary.archetype.pollster.name": "Ameslaria", + "annual_report.summary.archetype.replier.desc_public": "{name}(e)k maiz erantzuten zien beste pertsona batzuen argitalpenei, Mastodon polinizatuz eztabaida berriekin.", + "annual_report.summary.archetype.replier.desc_self": "Maiz erantzuten zenien beste pertsona batzuen argitalpenei, Mastodon polinizatuz eztabaida berriekin.", + "annual_report.summary.archetype.replier.name": "Tximeleta", + "annual_report.summary.archetype.reveal": "Erakutsi nire arketipoa", + "annual_report.summary.archetype.reveal_description": "Eskerrik asko Mastodonen parte izateagatik! Bada garaia jakiteko zer arketipo haragitu duzun {year}(e)an.", + "annual_report.summary.archetype.title_public": "{name}-(r)en arketipoa", + "annual_report.summary.archetype.title_self": "Zure arketipoa", + "annual_report.summary.close": "Itxi", + "annual_report.summary.copy_link": "Kopiatu esteka", + "annual_report.summary.followers.new_followers": "{count, plural, one {jarraitzaile berri} other {jarraitzaile berri}}", + "annual_report.summary.highlighted_post.boost_count": "Bidalketa honek {count, plural, one {bultzada 1 dauka} other {# bultzada dauzka}}.", + "annual_report.summary.highlighted_post.favourite_count": "Bidalketa honek {count, plural, one {gogoko 1 dauka} other {# gogoko dauzka}}.", + "annual_report.summary.highlighted_post.reply_count": "Bidalketa honek {count, plural, one {erantzun 1 dauka} other {# erantzun dauzka}}.", + "annual_report.summary.highlighted_post.title": "Bidalketa ospetsuena", "annual_report.summary.most_used_app.most_used_app": "app erabiliena", "annual_report.summary.most_used_hashtag.most_used_hashtag": "traola erabiliena", - "annual_report.summary.most_used_hashtag.none": "Bat ere ez", + "annual_report.summary.most_used_hashtag.used_count": "Traola hau {count, plural, one {bidalketa baten sartu zenuen} other {# bidalketatan sartu zenuen}}.", + "annual_report.summary.most_used_hashtag.used_count_public": "{name}(e)k traola hau {count, plural, one {bidalketa baten sartu zuen} other {# bidalketatan sartu zituen}}.", "annual_report.summary.new_posts.new_posts": "bidalketa berriak", "annual_report.summary.percentile.text": "Horrek jartzen zaitu top (e)an {domain} erabiltzaileen artean ", "annual_report.summary.percentile.we_wont_tell_bernie": "Bernieri ez diogu ezer esango ;)..", - "annual_report.summary.thanks": "Eskerrik asko Mastodonen parte izateagatik!", + "annual_report.summary.share_elsewhere": "Partekatu beste edonon", + "annual_report.summary.share_message": "{arketipoa} arketipoa daukat!", + "annual_report.summary.share_on_mastodon": "Partekatu Mastodonen", "attachments_list.unprocessed": "(prozesatu gabe)", "audio.hide": "Ezkutatu audioa", "block_modal.remote_users_caveat": "{domain} zerbitzariari zure erabakia errespeta dezan eskatuko diogu. Halere, araua beteko den ezin da bermatu, zerbitzari batzuk modu desberdinean kudeatzen baitituzte blokeoak. Baliteke argitalpen publikoak saioa hasi ez duten erabiltzaileentzat ikusgai egotea.", @@ -147,6 +184,8 @@ "bundle_modal_error.close": "Itxi", "bundle_modal_error.message": "Zerbait okerra gertatu da pantaila hau kargatzean.", "bundle_modal_error.retry": "Saiatu berriro", + "carousel.current": "{current, number}/{max, number} diapositiba", + "carousel.slide": "{current, number}. diapositiba {max, number}tik", "closed_registrations.other_server_instructions": "Mastodon deszentralizatua denez, beste kontu bat sortu dezakezu beste zerbitzari batean eta honekin komunikatu.", "closed_registrations_modal.description": "Une honetan ezin da konturik sortu {domain} zerbitzarian, baina kontuan izan Mastodon erabiltzeko ez duzula zertan konturik izan zehazki {domain} zerbitzarian.", "closed_registrations_modal.find_another_server": "Aurkitu beste zerbitzari bat", @@ -163,6 +202,8 @@ "column.edit_list": "Editatu zerrenda", "column.favourites": "Gogokoak", "column.firehose": "Zuzeneko jarioak", + "column.firehose_local": "Zerbitzari honen zuzeneko jarioa", + "column.firehose_singular": "Zuzeneko jarioa", "column.follow_requests": "Jarraitzeko eskaerak", "column.home": "Hasiera", "column.list_members": "Kudeatu zerrrendako partaideak", @@ -182,6 +223,7 @@ "community.column_settings.local_only": "Lokala soilik", "community.column_settings.media_only": "Edukiak soilik", "community.column_settings.remote_only": "Urrunekoa soilik", + "compose.error.blank_post": "Bidalketa ezin da hutsik egon.", "compose.language.change": "Aldatu hizkuntza", "compose.language.search": "Bilatu hizkuntzak...", "compose.published.body": "Argitalpena argitaratuta.", @@ -234,15 +276,30 @@ "confirmations.missing_alt_text.secondary": "Bidali edonola ere", "confirmations.missing_alt_text.title": "Testu alternatiboa gehitu?", "confirmations.mute.confirm": "Mututu", + "confirmations.private_quote_notify.cancel": "Bueltatu ediziora", + "confirmations.private_quote_notify.confirm": "Argitaratu bidalketa", + "confirmations.private_quote_notify.do_not_show_again": "Ez erakutsi mezu hau berriro", + "confirmations.private_quote_notify.message": "Aipatzen ari zaren pertsonak eta aipatutako besteek jakinarazpena jasoko dute eta zure sarrera ikusi ahalko dute, zure jarraitzaileak ez badira ere.", + "confirmations.private_quote_notify.title": "Partekatu jarraitzaileekin eta aipatutako erabiltzaileekin?", + "confirmations.quiet_post_quote_info.dismiss": "Ez gogorarazi berriro", + "confirmations.quiet_post_quote_info.got_it": "Ulertuta", + "confirmations.quiet_post_quote_info.message": "Deiadar urriko bidalketa bat aipatzen duzunean, zure bidalketa joeretatik ezkutatuko da.", + "confirmations.quiet_post_quote_info.title": "Deiadar urriko bidalketaren aipua", "confirmations.redraft.confirm": "Ezabatu eta berridatzi", "confirmations.redraft.message": "Ziur argitalpen hau ezabatu eta zirriborroa berriro egitea nahi duzula? Gogokoak eta bultzadak galduko dira, eta jatorrizko argitalpenaren erantzunak zurtz geratuko dira.", "confirmations.redraft.title": "Ezabatu eta berridatzi bidalketa?", "confirmations.remove_from_followers.confirm": "Jarraitzailea Kendu", "confirmations.remove_from_followers.message": "{name}-k zu jarraitzeari utziko dio. Seguru zaude jarraitu nahi duzula?", "confirmations.remove_from_followers.title": "Jarraitzailea kendu nahi duzu?", + "confirmations.revoke_quote.confirm": "Ezabatu bidalketa", + "confirmations.revoke_quote.message": "Ekintza hau ezin da desegin.", + "confirmations.revoke_quote.title": "Ezabatu bidalketa?", + "confirmations.unblock.confirm": "Desblokeatu", + "confirmations.unblock.title": "Desblokeatu {name}?", "confirmations.unfollow.confirm": "Utzi jarraitzeari", - "confirmations.unfollow.message": "Ziur {name} jarraitzeari utzi nahi diozula?", - "confirmations.unfollow.title": "Erabiltzailea jarraitzeari utzi?", + "confirmations.unfollow.title": "{name} jarraitzeari utzi?", + "confirmations.withdraw_request.confirm": "Baztertu eskaera", + "confirmations.withdraw_request.title": "Baztertu {name} jarraitzeko eskaera?", "content_warning.hide": "Tuta ezkutatu", "content_warning.show": "Erakutsi hala ere", "content_warning.show_more": "Erakutsi gehiago", @@ -268,6 +325,7 @@ "domain_block_modal.they_cant_follow": "Zerbitzari honetako inork ezin zaitu jarraitu.", "domain_block_modal.they_wont_know": "Ez dute jakingo blokeatuak izan direnik.", "domain_block_modal.title": "Domeinua blokeatu nahi duzu?", + "domain_block_modal.you_will_lose_num_followers": "{followersCount, plural, one {Jarraitzaile {followersCountDisplay}} other {{followersCountDisplay} jarraitzaile}} eta {followingCount, plural, one {jarraitzen duzun pertsona {followingCountDisplay}} other {jarraitzen dituzun beste {followingCountDisplay} pertsona}} galduko dituzu.", "domain_block_modal.you_will_lose_relationships": "Instantzia honetatik jarraitzen dituzun jarraitzaile eta pertsona guztiak galduko dituzu.", "domain_block_modal.you_wont_see_posts": "Ez dituzu zerbitzari honetako erabiltzaileen argitalpenik edota jakinarazpenik ikusiko.", "domain_pill.activitypub_lets_connect": "Mastodon-en ez ezik, beste sare sozialen aplikazioetako jendearekin konektatzea eta harremanetan jartzea uzten dizu.", @@ -283,6 +341,7 @@ "domain_pill.your_handle": "Zure helbidea:", "domain_pill.your_server": "Zure etxe digitala, non zure bidalketak dauden. Ez al zaizu gustatzen? Transferitu zerbitzariak edonoiz eta ekarri zure jarraitzaileak ere.", "domain_pill.your_username": "Zerbitzarian duzun identifikatzaile bakarra. Baliteke erabiltzaile-izen bera duten erabiltzaileak zerbitzari desberdinetan aurkitzea.", + "dropdown.empty": "Aukeratu bat", "embed.instructions": "Txertatu bidalketa hau zure webgunean beheko kodea kopiatuz.", "embed.preview": "Hau da izango duen itxura:", "emoji_button.activity": "Jarduera", @@ -311,6 +370,7 @@ "empty_column.bookmarked_statuses": "Oraindik ez dituzu bidalketa laster-markatutarik. Bat laster-markatzerakoan, hemen agertuko da.", "empty_column.community": "Denbora-lerro lokala hutsik dago. Idatzi zerbait publikoki pilota biraka jartzeko!", "empty_column.direct": "Ez duzu aipamen pribaturik oraindik. Baten bat bidali edo jasotzen duzunean, hemen agertuko da.", + "empty_column.disabled_feed": "Zure zerbitzariko administratzaileek jario hau desgaitu dute.", "empty_column.domain_blocks": "Ez dago ezkutatutako domeinurik oraindik.", "empty_column.explore_statuses": "Ez dago joerarik une honetan. Begiratu beranduago!", "empty_column.favourited_statuses": "Ez duzu gogokorik oraindik. Gogoko bat duzunean, hemen agertuko da.", @@ -324,6 +384,7 @@ "empty_column.notification_requests": "Garbi-garbi! Ezertxo ere ez hemen. Jakinarazpenak jasotzen dituzunean, hemen agertuko dira zure ezarpenen arabera.", "empty_column.notifications": "Ez duzu jakinarazpenik oraindik. Jarri besteekin harremanetan elkarrizketa abiatzeko.", "empty_column.public": "Ez dago ezer hemen! Idatzi zerbait publikoki edo jarraitu eskuz beste zerbitzari batzuetako erabiltzaileei hau betetzen joateko", + "error.no_hashtag_feed_access": "Egin bat edo hasi saioa etiketa hau ikusi eta jarraitzeko.", "error.unexpected_crash.explanation": "Gure kodean arazoren bat dela eta, edo nabigatzailearekin bateragarritasun arazoren bat dela eta, orri hau ezin izan da ongi bistaratu.", "error.unexpected_crash.explanation_addons": "Ezin izan da orria behar bezala bistaratu. Errore honen jatorria nabigatzailearen gehigarri batean edo itzulpen automatikoko tresnetan dago ziur aski.", "error.unexpected_crash.next_steps": "Saiatu orria berritzen. Horrek ez badu laguntzen, agian Mastodon erabiltzeko aukera duzu oraindik ere beste nabigatzaile bat edo aplikazio natibo bat erabilita.", @@ -335,10 +396,9 @@ "explore.trending_links": "Berriak", "explore.trending_statuses": "Tutak", "explore.trending_tags": "Traolak", - "featured_carousel.next": "Hurrengoa", - "featured_carousel.post": "Argitaratu", - "featured_carousel.previous": "Aurrekoa", - "featured_carousel.slide": "{total}-tik {index}", + "featured_carousel.current": "{current, number}/{max, number} sarrera", + "featured_carousel.header": "{count, plural, one {Finkatutako sarrera} other {Finkatutako sarrerak}}", + "featured_carousel.slide": "{current, number}. sarrera {max, number}tik", "filter_modal.added.context_mismatch_explanation": "Iragazki-kategoria hau ez zaio aplikatzen bidalketa honetara sartzeko erabili duzun testuinguruari. Bidalketa testuinguru horretan ere iragaztea nahi baduzu, iragazkia editatu beharko duzu.", "filter_modal.added.context_mismatch_title": "Testuingurua ez dator bat!", "filter_modal.added.expired_explanation": "Iragazki kategoria hau iraungi da, eragina izan dezan bere iraungitze-data aldatu beharko duzu.", @@ -381,6 +441,9 @@ "follow_suggestions.who_to_follow": "Zein jarraitu", "followed_tags": "Jarraitutako traolak", "footer.about": "Honi buruz", + "footer.about_mastodon": "Mastodoni buruz", + "footer.about_server": "{domain}-i buruz", + "footer.about_this_server": "Honi buruz", "footer.directory": "Profil-direktorioa", "footer.get_app": "Eskuratu aplikazioa", "footer.keyboard_shortcuts": "Lasterbideak", @@ -438,10 +501,12 @@ "ignore_notifications_modal.private_mentions_title": "Eskatu gabeko aipamen pribatuen jakinarazpenei ez ikusiarena egin?", "info_button.label": "Laguntza", "info_button.what_is_alt_text": "

Zer da Alt testua?

Alt testuak irudiak deskribatzeko aukera ematen du, ikusmen-urritasunak, banda-zabalera txikiko konexioak edo testuinguru gehigarria nahi duten pertsonentzat.

Alt testu argi, zehatz eta objektiboen bidez, guztion irisgarritasuna eta ulermena hobetu ditzakezu.

  • Hartu elementu garrantzitsuenak
  • Laburbildu irudietako testua
  • Erabili esaldien egitura erregularra
  • Baztertu informazio erredundantea.
  • Enfokatu joeretan eta funtsezko elementuetan irudi konplexuetan (diagrametan edo mapetan, adibidez)
", + "interaction_modal.action": "{name} erabiltzailearen sarrerarekin interaktuatzeko, saioa hasi behar duzu erabiltzen duzun Mastodon zerbitzarian.", "interaction_modal.go": "Joan", "interaction_modal.no_account_yet": "Ez al duzu konturik oraindik?", "interaction_modal.on_another_server": "Beste zerbitzari batean", "interaction_modal.on_this_server": "Zerbitzari honetan", + "interaction_modal.title": "Hasi saioa jarraitzeko", "interaction_modal.username_prompt": "Adib. {example}", "intervals.full.days": "{number, plural, one {egun #} other {# egun}}", "intervals.full.hours": "{number, plural, one {ordu #} other {# ordu}}", @@ -462,6 +527,7 @@ "keyboard_shortcuts.home": "hasierako denbora-lerroa irekitzeko", "keyboard_shortcuts.hotkey": "Laster-tekla", "keyboard_shortcuts.legend": "legenda hau bistaratzea", + "keyboard_shortcuts.load_more": "Fokuratu \"Kargatu gehiago\" botoia", "keyboard_shortcuts.local": "denbora-lerro lokala irekitzeko", "keyboard_shortcuts.mention": "egilea aipatzea", "keyboard_shortcuts.muted": "mutututako erabiltzaileen zerrenda irekitzeko", @@ -470,6 +536,7 @@ "keyboard_shortcuts.open_media": "Ireki edukia", "keyboard_shortcuts.pinned": "Ireki finkatutako bidalketen zerrenda", "keyboard_shortcuts.profile": "egilearen profila irekitzeko", + "keyboard_shortcuts.quote": "Aipatu sarrera", "keyboard_shortcuts.reply": "Erantzun bidalketari", "keyboard_shortcuts.requests": "Jarraitzeko eskaeren zerrenda irekia", "keyboard_shortcuts.search": "bilaketan fokua jartzea", @@ -478,9 +545,12 @@ "keyboard_shortcuts.toggle_hidden": "testua erakustea/ezkutatzea abisu baten atzean", "keyboard_shortcuts.toggle_sensitivity": "multimedia erakutsi/ezkutatzeko", "keyboard_shortcuts.toot": "Hasi bidalketa berri bat", + "keyboard_shortcuts.top": "Mugitu zerrendaren hasierara", "keyboard_shortcuts.translate": "bidalketa itzultzeko", "keyboard_shortcuts.unfocus": "testua konposatzeko area / bilaketatik fokua kentzea", "keyboard_shortcuts.up": "zerrendan gora mugitzea", + "learn_more_link.got_it": "Ulertuta", + "learn_more_link.learn_more": "Ikasi gehiago", "lightbox.close": "Itxi", "lightbox.next": "Hurrengoa", "lightbox.previous": "Aurrekoa", @@ -503,6 +573,7 @@ "lists.exclusive": "Ezkutatu kideak Hasieran", "lists.exclusive_hint": "Norbait zerrenda honetan badago, ezkutatu zure Hasierako jariotik mezuak bi aldiz ez ikusteko.", "lists.find_users_to_add": "Bilatu erabiltzaileak gehitzeko", + "lists.list_members_count": "{count, plural, one {kide #} other {# kide}}", "lists.list_name": "Zerrenda izena", "lists.new_list_name": "Zerrenda izen berria", "lists.no_lists_yet": "Ez duzu zerrendarik oraindik.", @@ -514,6 +585,7 @@ "lists.replies_policy.none": "Bat ere ez", "lists.save": "Gorde", "lists.search": "Bilatu", + "lists.show_replies_to": "Erakutsi zerrendako kideen erantzunak hauei:", "load_pending": "{count, plural, one {elementu berri #} other {# elementu berri}}", "loading_indicator.label": "Kargatzen…", "media_gallery.hide": "Ezkutatu", @@ -554,6 +626,10 @@ "navigation_bar.privacy_and_reach": "Pribatutasuna eta irismena", "navigation_bar.search": "Bilatu", "navigation_bar.search_trends": "Bilatu / Joera", + "navigation_panel.collapse_followed_tags": "Itxi jarraitzen dituzun traolen menua", + "navigation_panel.collapse_lists": "Itxi zerrenden menua", + "navigation_panel.expand_followed_tags": "Zabaldu jarraitzen dituzun traolen menua", + "navigation_panel.expand_lists": "Zabaldu zerrenden menua", "not_signed_in_indicator.not_signed_in": "Baliabide honetara sarbidea izateko saioa hasi behar duzu.", "notification.admin.report": "{name} erabiltzaileak {target} salatu du", "notification.admin.report_account": "{name}-(e)k {target}-ren {count, plural, one {bidalketa bat} other {# bidalketa}} salatu zituen {category} delakoagatik", @@ -562,15 +638,20 @@ "notification.admin.report_statuses_other": "{name} erabiltzaileak {target} salatu du", "notification.admin.sign_up": "{name} erabiltzailea erregistratu da", "notification.admin.sign_up.name_and_others": "{name} eta {count, plural, one {erabiltzaile # gehiago} other {# erabiltzaile gehiago}} erregistratu dira", + "notification.annual_report.message": "Zain duzu {year}(e)ko #Wrapstodon! Ikusi urteko zure unerik gogoangarrienak Mastodonen!", + "notification.annual_report.view": "Ikusi #Wrapstodon", "notification.favourite": "{name}(e)k zure bidalketa gogoko du", "notification.favourite.name_and_others_with_link": "{name} eta {count, plural, one {erabiltzaile # gehiagok} other {# erabiltzaile gehiagok}} zure bidalketa gogoko dute", "notification.favourite_pm": "{name}-ek zure aipamen pribatua gogokoetan jarri du", + "notification.favourite_pm.name_and_others_with_link": "{name} erabiltzaileak eta beste {count, plural, one {#ek} other {#(e)k}} gogoko dute zure aipu pribatua", "notification.follow": "{name}(e)k jarraitzen dizu", + "notification.follow.name_and_others": "{name} erabiltzaileak eta {count, plural, one {# gehiagok} other {# gehiagok}} jarraitu dizute", "notification.follow_request": "{name}(e)k zu jarraitzeko eskaera egin du", "notification.follow_request.name_and_others": "{name} eta {count, plural, one {erabiltzaile # gehiagok} other {# erabiltzaile gehiagok}} zu jarraitzeko eskaera egin dute", "notification.label.mention": "Aipamena", "notification.label.private_mention": "Aipamen pribatua", "notification.label.private_reply": "Erantzun pribatua", + "notification.label.quote": "{name} erabiltzaileak zure bidalketa aipatu du", "notification.label.reply": "Erantzuna", "notification.mention": "Aipamena", "notification.mentioned_you": "{name}(e)k aipatu zaitu", @@ -585,18 +666,23 @@ "notification.moderation_warning.action_suspend": "Kontua itxi da.", "notification.own_poll": "Zure inkesta amaitu da", "notification.poll": "Zuk erantzun duzun inkesta bat bukatu da", + "notification.quoted_update": "{name} erabiltzaileak aipatu duzun post bat editatu du", "notification.reblog": "{name}(e)k bultzada eman dio zure bidalketari", "notification.reblog.name_and_others_with_link": "{name} eta {count, plural, one {erabiltzaile # gehiagok} other {# erabiltzaile gehiagok}} bultzada eman diote zure bidalketari", "notification.relationships_severance_event": "{name} erabiltzailearekin galdutako konexioak", "notification.relationships_severance_event.account_suspension": "{from} zerbitzariko administratzaile batek {target} bertan behera utzi du, hau da, ezin izango dituzu jaso hango eguneratzerik edo hangoekin elkarreragin.", + "notification.relationships_severance_event.domain_block": "{from} zerbitzariko administratzaile batek {target} blokeatu du, tartean zure {followersCount} jarraitzaile eta jarraitzen duzun {followingCount, plural, one {kontu #} other {# kontu}}.", "notification.relationships_severance_event.learn_more": "Informazio gehiago", + "notification.relationships_severance_event.user_domain_block": "{target} blokeatu duzu, tartean zure {followersCount} jarraitzaile eta jarraitzen duzun {followingCount, plural, one {kontu #} other {# kontu}}.", "notification.status": "{name} erabiltzaileak bidalketa egin berri du", "notification.update": "{name} erabiltzaileak bidalketa bat editatu du", "notification_requests.accept": "Onartu", "notification_requests.accept_multiple": "{count, plural, one {Onartu eskaera…} other {Onartu # eskaerak…}}", "notification_requests.confirm_accept_multiple.button": "{count, plural, one {Onartu eskaera} other {Onartu eskaerak}}", + "notification_requests.confirm_accept_multiple.message": "{count, plural, one {Jakinarazpen eskaera bat} other {# jakinarazpen eskaera}} onartzekotan zaude. Jarraitu nahi duzu?", "notification_requests.confirm_accept_multiple.title": "Onartu jakinarazpen-eskaerak?", "notification_requests.confirm_dismiss_multiple.button": "{count, plural, one {Baztertu eskaera} other {Baztertu eskaerak}}", + "notification_requests.confirm_dismiss_multiple.message": "{count, plural, one {Jakinarazpen eskaera bat} other {# jakinarazpen eskaera}} baztertzekotan zaude. Gerora ezingo {count, plural, one {duzu} other {dituzu}} berriz erraz atzitu. Jarraitu nahi duzu?", "notification_requests.confirm_dismiss_multiple.title": "Baztertu jakinarazpen-eskaerak?", "notification_requests.dismiss": "Baztertu", "notification_requests.dismiss_multiple": "{count, plural, one {Baztertu eskaera…} other {Baztertu # eskaerak…}}", @@ -624,6 +710,7 @@ "notifications.column_settings.mention": "Aipamenak:", "notifications.column_settings.poll": "Inkestaren emaitzak:", "notifications.column_settings.push": "Push jakinarazpenak", + "notifications.column_settings.quote": "Aipuak:", "notifications.column_settings.reblog": "Bultzadak:", "notifications.column_settings.show": "Erakutsi zutabean", "notifications.column_settings.sound": "Jo soinua", @@ -694,14 +781,25 @@ "poll_button.remove_poll": "Kendu inkesta", "privacy.change": "Aldatu bidalketaren pribatutasuna", "privacy.direct.long": "Argitalpen honetan aipatutako denak", + "privacy.direct.short": "Aipu pribatua", "privacy.private.long": "Soilik jarraitzaileak", "privacy.private.short": "Jarraitzaileak", "privacy.public.long": "Mastodonen dagoen edo ez dagoen edonor", "privacy.public.short": "Publikoa", + "privacy.quote.anyone": "{visibility}, edonork aipa dezake", + "privacy.quote.disabled": "{visibility}, aipuak desgaituta", + "privacy.quote.limited": "{visibility}, aipuak mugatuta", "privacy.unlisted.additional": "Aukera honek publiko modua bezala funtzionatzen du, baina argitalpena ez da agertuko zuzeneko jarioetan edo traoletan, \"Arakatu\" atalean edo Mastodonen bilaketan, nahiz eta kontua zabaltzeko onartu duzun.", - "privacy.unlisted.short": "Deiadar urrikoa", + "privacy.unlisted.long": "Ezkutatuta Mastodon bilaketen emaitzetatik, joeretatik, eta denbora-lerro publikoetatik", + "privacy.unlisted.short": "Ikusgarritasun mugatua", "privacy_policy.last_updated": "Azkenengo eguneraketa {date}", "privacy_policy.title": "Pribatutasun politika", + "quote_error.edit": "Aipuak ezin dira gehitu bidalketa bat editatzean.", + "quote_error.poll": "Inkestak ezin dira aipatu.", + "quote_error.private_mentions": "Aipuak ez dira onartzen aipamen pribatuetan.", + "quote_error.quote": "Bidalketa bakoitzeko aipu bakarra onartzen da.", + "quote_error.unauthorized": "Ez duzu baimenik bidalketa hau aipatzeko.", + "quote_error.upload": "Aipuak ez dira onartzen multimedia eranskinekin.", "recommended": "Gomendatua", "refresh": "Berritu", "regeneration_indicator.please_stand_by": "Itxaron, mesedez.", @@ -717,6 +815,9 @@ "relative_time.minutes": "{number}m", "relative_time.seconds": "{number}s", "relative_time.today": "gaur", + "remove_quote_hint.button_label": "Ulertuta", + "remove_quote_hint.message": "{icon} aukeren menutik egin dezakezu.", + "remove_quote_hint.title": "Aipatu dizuten bidalketa kendu nahi duzu?", "reply_indicator.attachments": "{count, plural, one {# eranskin} other {# eranskin}}", "reply_indicator.cancel": "Utzi", "reply_indicator.poll": "Inkesta", @@ -791,8 +892,10 @@ "search_results.all": "Guztiak", "search_results.hashtags": "Traolak", "search_results.no_results": "Emaitzarik ez.", + "search_results.no_search_yet": "Saiatu bilatzen bidalketak, profilak edo traolak.", "search_results.see_all": "Ikusi guztiak", "search_results.statuses": "Bidalketak", + "search_results.title": "Bilatu \"{q}\"", "server_banner.about_active_users": "Azken 30 egunetan zerbitzari hau erabili duen jendea (hilabeteko erabiltzaile aktiboak)", "server_banner.active_users": "erabiltzaile aktibo", "server_banner.administered_by": "Administratzailea(k):", @@ -806,13 +909,23 @@ "status.admin_account": "Ireki @{name} erabiltzailearen moderazio interfazea", "status.admin_domain": "{domain}-(r)en moderazio-interfazea ireki", "status.admin_status": "Ireki bidalketa hau moderazio interfazean", + "status.all_disabled": "Bultzadak eta aipuak desgaituta daude", "status.block": "Blokeatu @{name}", "status.bookmark": "Laster-marka", "status.cancel_reblog_private": "Kendu bultzada", + "status.cannot_quote": "Ez duzu bidalketa hau aipatzeko baimenik", "status.cannot_reblog": "Bidalketa honi ezin zaio bultzada eman", + "status.contains_quote": "Aipua darama", + "status.context.loading": "Erantzun gehiago kargatzen", + "status.context.loading_error": "Ezin erantzun berririk kargatu", + "status.context.loading_success": "Erantzun berriak kargatuta", + "status.context.more_replies_found": "Erantzun gehiago aurkitu dira", + "status.context.retry": "Saiatu berriz", + "status.context.show": "Erakutsi", "status.continued_thread": "Harian jarraitu zuen", "status.copy": "Kopiatu bidalketaren esteka", "status.delete": "Ezabatu", + "status.delete.success": "Bidalketa ezabatuta", "status.detailed_status": "Elkarrizketaren ikuspegi xehetsua", "status.direct": "Aipatu pribatuki @{name}", "status.direct_indicator": "Aipamen pribatua", @@ -821,7 +934,7 @@ "status.edited_x_times": "{count, plural, one {behin} other {{count} aldiz}} editatua", "status.embed": "Lortu txertatzeko kodea", "status.favourite": "Gogokoa", - "status.favourites": "{count, plural, one {gogoko} other {gogoko}}", + "status.favourites_count": "{count, plural, one {{counter} gogoko} other {{counter} gogoko}}", "status.filter": "Iragazi bidalketa hau", "status.history.created": "{name} erabiltzaileak sortua {date}", "status.history.edited": "{name} erabiltzaileak editatua {date}", @@ -835,18 +948,46 @@ "status.mute_conversation": "Mututu elkarrizketa", "status.open": "Hedatu bidalketa hau", "status.pin": "Finkatu profilean", + "status.quote": "Aipua", + "status.quote.cancel": "Utzi aipua", + "status.quote_error.blocked_account_hint.title": "Bidalketa hau ezkutatuta dago @{name} blokeatu duzulako.", + "status.quote_error.blocked_domain_hint.title": "Bidalketa hau ezkutatuta dago {domain} blokeatu duzulako.", + "status.quote_error.filtered": "Ezkutatuta zure iragazki baten ondorioz", + "status.quote_error.limited_account_hint.action": "Erakutsi hala ere", + "status.quote_error.limited_account_hint.title": "{domain} zerbitzariaren moderatzaileek kontu hau ezkutatu dute.", + "status.quote_error.muted_account_hint.title": "Bidalketa hau ezkutatuta dago @{name} mututu duzulako.", + "status.quote_error.not_available": "Bidalketa ez dago eskuragarri", + "status.quote_error.pending_approval": "Bidalketa zain dago", + "status.quote_error.pending_approval_popout.body": "Mastodonen, norbaitek aipa zaitzakeen kontrola dezakezu. Bidalketa hau argitaratzeke dago jatorrizko egilearen oniritzia jaso bitartean.", + "status.quote_error.revoked": "Egileak bidalketa kendu du", + "status.quote_followers_only": "Bidalketa hau jarraitzaileek soilik aipatu dezakete", + "status.quote_manual_review": "Egileak eskuz berrikusiko du", + "status.quote_noun": "Aipua", + "status.quote_policy_change": "Aldatu nork aipa zaitzakeen", + "status.quote_post_author": "@{name} erabiltzailearen bidalketaren aipua", + "status.quote_private": "Bidalketa pribatuak ezin dira aipatu", + "status.quotes.empty": "Momentuz inork ez du bidalketa hau aipatu. Norbaitek eginez gero, hemen agertuko da.", + "status.quotes.local_other_disclaimer": "Egileak errefusatutako aipuak ez dira erakutsiko.", + "status.quotes.remote_other_disclaimer": "{domain} zerbitzariko aipuak baino ez daude bermatuta hemen. Egileak errefusatutako aipuak ez dira erakutsiko.", + "status.quotes_count": "{count, plural, one {{counter} aipu} other {{counter} aipu}}", "status.read_more": "Irakurri gehiago", "status.reblog": "Bultzada", + "status.reblog_or_quote": "Bultzatu edo aipatu", + "status.reblog_private": "Partekatu berriz zure jarraitzaileekin", "status.reblogged_by": "{name}(r)en bultzada", - "status.reblogs": "{count, plural, one {bultzada} other {bultzada}}", "status.reblogs.empty": "Inork ez dio bultzada eman bidalketa honi oraindik. Inork egiten badu, hemen agertuko da.", + "status.reblogs_count": "{count, plural, one {{counter} bultzaeda} other {{counter} bultzaeda}}", "status.redraft": "Ezabatu eta berridatzi", "status.remove_bookmark": "Kendu laster-marka", + "status.remove_favourite": "Kendu gogokoetatik", + "status.remove_quote": "Kendu", "status.replied_in_thread": "Harian erantzun zuen", "status.replied_to": "{name} erabiltzaileari erantzuna", "status.reply": "Erantzun", "status.replyAll": "Erantzun harian", "status.report": "Salatu @{name}", + "status.request_quote": "Eskatu aipatzeko", + "status.revoke_quote": "Ezabatu nire bidalketa @{name}-(r)en bidalketatik", "status.sensitive_warning": "Kontuz: Eduki hunkigarria", "status.share": "Partekatu", "status.show_less_all": "Erakutsi denetarik gutxiago", @@ -866,7 +1007,9 @@ "tabs_bar.notifications": "Jakinarazpenak", "tabs_bar.publish": "Bidalketa berria", "tabs_bar.search": "Bilatu", + "terms_of_service.effective_as_of": "Indarrean {date}tik aurrera", "terms_of_service.title": "Zerbitzuaren baldintzak", + "terms_of_service.upcoming_changes_on": "{date} datarako datozen aldaketak", "time_remaining.days": "{number, plural, one {egun #} other {# egun}} amaitzeko", "time_remaining.hours": "{number, plural, one {ordu #} other {# ordu}} amaitzeko", "time_remaining.minutes": "{number, plural, one {minutu #} other {# minutu}} amaitzeko", @@ -882,6 +1025,12 @@ "upload_button.label": "Gehitu multimedia (JPEG, PNG, GIF, WebM, MP4, MOV)", "upload_error.limit": "Fitxategi igoera muga gaindituta.", "upload_error.poll": "Ez da inkestetan fitxategiak igotzea onartzen.", + "upload_error.quote": "Artxiboak igotzea ez da onartzen aipuekin.", + "upload_form.drag_and_drop.instructions": "Multimedia eranskin bat aukeratzeko, sakatu espazioa edo enter. Arrastatu bitartean, erabili gezi-teklak multimedia eranskina edozein norabidetan mugitzeko. Sakatu berriz espazioa edon enter multimedia eranskina bere kokapen berrian jartzeko, edo sakatu ESC uzteko.", + "upload_form.drag_and_drop.on_drag_cancel": "Arrastatzea bertan behera utzi da. {item} multimedia eranskina ez da mugitu.", + "upload_form.drag_and_drop.on_drag_end": "{item} multimedia eranskina ez da mugitu.", + "upload_form.drag_and_drop.on_drag_over": "{item} multimedia eranskina mugitu da.", + "upload_form.drag_and_drop.on_drag_start": "{item} multimedia eranskina hautatu da.", "upload_form.edit": "Editatu", "upload_progress.label": "Igotzen...", "upload_progress.processing": "Prozesatzen…", @@ -892,10 +1041,28 @@ "video.expand": "Hedatu bideoa", "video.fullscreen": "Pantaila osoa", "video.hide": "Ezkutatu bideoa", + "video.mute": "Mututu", "video.pause": "Pausatu", "video.play": "Jo", + "video.skip_backward": "Saltatu atzerantz", "video.skip_forward": "Jauzi aurrerantz", "video.unmute": "Soinua ezarri", "video.volume_down": "Bolumena jaitsi", - "video.volume_up": "Bolumena Igo" + "video.volume_up": "Bolumena Igo", + "visibility_modal.button_title": "Ezarri ikusgarritasuna", + "visibility_modal.direct_quote_warning.text": "Uneko ezarpenak gordez gero, txertatutako aipua lotura bilakatuko da.", + "visibility_modal.direct_quote_warning.title": "Aipuak ezin dira sartu aipamen prbatuetan", + "visibility_modal.header": "Ikusgarritasuna eta elkarreraginak", + "visibility_modal.helper.direct_quoting": "Mastodonen argitaratutako aipamen pribatuak ezin dituzte beste erabiltzaileek aipatu.", + "visibility_modal.helper.privacy_editing": "Ikusgarritasuna ezin da aldatu bidalketa argitaratu ondoren.", + "visibility_modal.helper.privacy_private_self_quote": "Bidalketa pribatuen auto-aipuak ezin dira publiko egin.", + "visibility_modal.helper.private_quoting": "Jarraitzaileentzat soilik sortutako bidalketak Mastodonen ezin dituzte beste batzuek aipatu.", + "visibility_modal.helper.unlisted_quoting": "Jendeak aipatzen zaituenean, bere bidalketa ere joeren denbora-lerro publikoetatik ezkutatuko da.", + "visibility_modal.instructions": "Kontrolatu nork izan dezakeen elkarreragina bidalketa honekin. Ezarpenak etorkizuneko bidalketa guztiei ere aplika diezazkiekezu Hobespenak > Bidalketarako lehentsitakoak atalera joanda.", + "visibility_modal.privacy_label": "Ikusgarritasuna", + "visibility_modal.quote_followers": "Jarraitzaileentzat soilik", + "visibility_modal.quote_label": "Nork aipa dezake", + "visibility_modal.quote_nobody": "Nik bakarrik", + "visibility_modal.quote_public": "Edonork", + "visibility_modal.save": "Gorde" } diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index b2b0a6c2de372c..7684a328fbb5ca 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -1,10 +1,10 @@ { - "about.blocks": "کارسازهای نظارت شده", + "about.blocks": "کارسازهای نظارت شده", "about.contact": "تماس:", - "about.default_locale": "پیش‌فرض", + "about.default_locale": "پیش‌گزیده", "about.disclaimer": "ماستودون نرم‌افزار آزاد و نشان تجاری یک شرکت غیر انتفاعی با مسئولیت محدود آلمانی است.", "about.domain_blocks.no_reason_available": "دلیلی موجود نیست", - "about.domain_blocks.preamble": "ماستودون عموماً می‌گذارد محتوا را از از هر کارساز دیگری در دنیای شبکه‌های اجتماعی غیرمتمرکز دیده و با آنان برهم‌کنش داشته باشید. این‌ها استثناهایی هستند که روی این کارساز خاص وضع شده‌اند.", + "about.domain_blocks.preamble": "ماستودون عموماً می‌گذارد محتوا را از هر کارساز دیگری در دنیای شبکه‌های اجتماعی غیرمتمرکز دیده و با آنان برهم‌کنش داشته باشید. این‌ها استثناهایی هستند که روی این کارساز خاص وضع شده‌اند.", "about.domain_blocks.silenced.explanation": "عموماً نمایه‌ها و محتوا از این کارساز را نمی‌بینید، مگر این که به طور خاص دنبالشان گشته یا با پی گیری، داوطلب دیدنشان شوید.", "about.domain_blocks.silenced.title": "محدود", "about.domain_blocks.suspended.explanation": "هیچ داده‌ای از این کارساز پردازش، ذخیره یا مبادله نخواهد شد، که هرگونه برهم‌کنش یا ارتباط با کاربران این کارساز را غیرممکن خواهد کرد.", @@ -28,18 +28,24 @@ "account.disable_notifications": "آگاه کردن من هنگام فرسته‌های ‎@{name} را متوقّف کن", "account.domain_blocking": "دامنهٔ مسدود کرده", "account.edit_profile": "ویرایش نمایه", + "account.edit_profile_short": "ویرایش", "account.enable_notifications": "هنگام فرسته‌های ‎@{name} مرا آگاه کن", "account.endorse": "معرّفی در نمایه", "account.familiar_followers_many": "پی‌گرفته از سوی {name1}، {name2} و {othersCount, plural,one {یکی دیگر از پی‌گرفته‌هایتان} other {# نفر دیگر از پی‌گرفته‌هایتان}}", "account.familiar_followers_one": "پی‌گرفته از سوی {name1}", "account.familiar_followers_two": "پی‌گرفته از سوی {name1} و {name2}", - "account.featured": " پیشنهادی", + "account.featured": "پیشنهادی", "account.featured.accounts": "نمایه‌ها", "account.featured.hashtags": "برچسب‌ها", "account.featured_tags.last_status_at": "آخرین فرسته در {date}", "account.featured_tags.last_status_never": "بدون فرسته", "account.follow": "پی‌گرفتن", "account.follow_back": "پی‌گیری متقابل", + "account.follow_back_short": "پی‌گیری متقابل", + "account.follow_request": "درخواست پی‌گیری", + "account.follow_request_cancel": "لغو درخواست", + "account.follow_request_cancel_short": "لغو", + "account.follow_request_short": "درخواست", "account.followers": "پی‌گیرندگان", "account.followers.empty": "هنوز کسی پی‌گیر این کاربر نیست.", "account.followers_counter": "{count, plural, one {{counter} پی‌گیرنده} other {{counter} پی‌گیرنده}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "فرسته‌ها و پاسخ‌ها", "account.remove_from_followers": "برداشتن {name} از پی‌گیران", "account.report": "گزارش ‎@{name}", - "account.requested": "منتظر پذیرش است. برای لغو درخواست پی‌گیری کلیک کنید", "account.requested_follow": "{name} درخواست پی‌گیریتان را داد", "account.requests_to_follow_you": "درخواست پی‌گیریتان را دارد", "account.share": "هم‌رسانی نمایهٔ ‎@{name}", @@ -108,25 +113,16 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "شرح برای افرادی با مشکلات بینایی…", "alt_text_modal.done": "انجام شد", "announcement.announcement": "اعلامیه", - "annual_report.summary.archetype.booster": "باحال‌یاب", - "annual_report.summary.archetype.lurker": "کم‌پیدا", - "annual_report.summary.archetype.oracle": "غیب‌گو", - "annual_report.summary.archetype.pollster": "نظرسنج", - "annual_report.summary.archetype.replier": "پاسخگو", - "annual_report.summary.followers.followers": "دنبال کننده", - "annual_report.summary.followers.total": "در مجموع {count}", - "annual_report.summary.here_it_is": "بازبینی {year} تان:", - "annual_report.summary.highlighted_post.by_favourites": "پرپسندترین فرسته", - "annual_report.summary.highlighted_post.by_reblogs": "پرتقویت‌ترین فرسته", - "annual_report.summary.highlighted_post.by_replies": "پرپاسخ‌ترین فرسته", - "annual_report.summary.highlighted_post.possessive": "{name}", + "annual_report.announcement.action_build": "ساخت خلاصهٔ ماستودونم", + "annual_report.announcement.action_view": "دیدن خلاصهٔ ماستودونم", + "annual_report.announcement.description": "کشف بیش‌تر دربارهٔ درگیریتان روی ماستودون در سال گذشته.", + "annual_report.announcement.title": "خلاصهٔ ماستودون {year} این‌جاست", "annual_report.summary.most_used_app.most_used_app": "پراستفاده‌ترین کاره", "annual_report.summary.most_used_hashtag.most_used_hashtag": "پراستفاده‌ترین برچسب", - "annual_report.summary.most_used_hashtag.none": "هیچ‌کدام", "annual_report.summary.new_posts.new_posts": "فرستهٔ جدید", "annual_report.summary.percentile.text": "بین کاربران {domain} جزوبرتر هستید.", "annual_report.summary.percentile.we_wont_tell_bernie": "به برنی خبر نمی‌دهیم.", - "annual_report.summary.thanks": "سپاس که بخشی از ماستودون هستید!", + "annual_report.summary.share_message": "من کهن‌الگوی {archetype} را گرفتم!", "attachments_list.unprocessed": "(پردازش نشده)", "audio.hide": "نهفتن صدا", "block_modal.remote_users_caveat": "از کارساز {domain} خواهیم خواست که به تصمیمتان احترام بگذارد. با این حال تضمینی برای رعایتش وجود ندارد؛ زیرا برخی کارسازها ممکن است مسدودی را متفاوت مدیریت کنند. ممکن است فرسته‌های عمومی همچنان برای کاربران وارد نشده نمایان باشند.", @@ -152,6 +148,8 @@ "bundle_modal_error.close": "بستن", "bundle_modal_error.message": "هنگام بار کردن این صفحه، اشتباهی رخ داد.", "bundle_modal_error.retry": "تلاش دوباره", + "carousel.current": "پردهٔ {current, number} از {max, number}", + "carousel.slide": "پردهٔ {current, number} از {max, number}", "closed_registrations.other_server_instructions": "از آن‌جا که ماستودون نامتمرکز است، می‌توانید حسابی روی کارسازی دیگر ساخته و همچنان با این‌یکی در تعامل باشید.", "closed_registrations_modal.description": "هم‌اکنون امکان ساخت حساب روی {domain} وجود ندارد؛ ولی لطفاً به خاطر داشته باشید که برای استفاده از ماستودون، نیازی به داشتن حساب روی {domain} نیست.", "closed_registrations_modal.find_another_server": "یافتن کارسازی دیگر", @@ -168,6 +166,8 @@ "column.edit_list": "ویرایش سیاهه", "column.favourites": "برگزیده‌ها", "column.firehose": "خوراک‌های زنده", + "column.firehose_local": "خوراک زندهٔ این کارساز", + "column.firehose_singular": "خوراک زنده", "column.follow_requests": "درخواست‌های پی‌گیری", "column.home": "خانه", "column.list_members": "مدیریت اعضای سیاهه", @@ -187,6 +187,7 @@ "community.column_settings.local_only": "فقط محلی", "community.column_settings.media_only": "فقط رسانه", "community.column_settings.remote_only": "تنها دوردست", + "compose.error.blank_post": "فرسته نمی‌تواند خالی باشد.", "compose.language.change": "تغییر زبان", "compose.language.search": "جست‌وجوی زبان‌ها...", "compose.published.body": "فرسته منتشر شد.", @@ -239,6 +240,15 @@ "confirmations.missing_alt_text.secondary": "به هر حال پست کن", "confirmations.missing_alt_text.title": "متن جایگزین اضافه شود؟", "confirmations.mute.confirm": "خموش", + "confirmations.private_quote_notify.cancel": "بازگشت به ویرایش کردن", + "confirmations.private_quote_notify.confirm": "انتشار فرسته", + "confirmations.private_quote_notify.do_not_show_again": "دیگر این پیام نشان داده نشود", + "confirmations.private_quote_notify.message": "فردی که نقلش می‌کنید و کسانی که اشاره شده‌اند آگاه خواهند شد و می‌توانند فرسته‌تان را ببینند؛ حتا اگر پیتان نگیرند.", + "confirmations.private_quote_notify.title": "هم‌رسانی با پی‌گیرندگان و کاربران اشاره شده؟", + "confirmations.quiet_post_quote_info.dismiss": "دیگر یادآوری نشود", + "confirmations.quiet_post_quote_info.got_it": "گرفتم", + "confirmations.quiet_post_quote_info.message": "هنگام نقل کردن فرستهٔ عمومی ساکت، فرسته‌تان از خط‌های زمانی داغ پنهان خواهد بود.", + "confirmations.quiet_post_quote_info.title": "نقل کردن فرسته‌های عمومی ساکت", "confirmations.redraft.confirm": "حذف و بازنویسی", "confirmations.redraft.message": "مطمئنید که می‌خواهید این فرسته را حذف کنید و از نو بنویسید؟ با این کار تقویت‌ها و پسندهایش از دست رفته و پاسخ‌ها به آن بی‌مرجع می‌شود.", "confirmations.redraft.title": "حذف و پیش‌نویسی دوبارهٔ فرسته؟", @@ -248,9 +258,12 @@ "confirmations.revoke_quote.confirm": "حذف فرسته", "confirmations.revoke_quote.message": "این اقدام قابل بازگشت نیست.", "confirmations.revoke_quote.title": "آیا فرسته را حذف کنم؟", + "confirmations.unblock.confirm": "رفع انسداد", + "confirmations.unblock.title": "رفع انسداد {name}؟", "confirmations.unfollow.confirm": "پی‌نگرفتن", - "confirmations.unfollow.message": "مطمئنید که می‌خواهید به پی‌گیری از {name} پایان دهید؟", - "confirmations.unfollow.title": "ناپی‌گیری کاربر؟", + "confirmations.unfollow.title": "ناپی‌گیری {name}؟", + "confirmations.withdraw_request.confirm": "انصراف از درخواست", + "confirmations.withdraw_request.title": "انصراف از درخواست پی‌گیری {name}؟", "content_warning.hide": "نهفتن فرسته", "content_warning.show": "در هر صورت نشان داده شود", "content_warning.show_more": "نمایش بیش‌تر", @@ -321,6 +334,7 @@ "empty_column.bookmarked_statuses": "هنوز هیچ فرستهٔ نشانه‌گذاری شده‌ای ندارید. هنگامی که فرسته‌ای را نشانه‌گذاری کنید، این‌جا نشان داده خواهد شد.", "empty_column.community": "خط زمانی محلی خالیست. چیزی نوشته تا چرخش بچرخد!", "empty_column.direct": "هنوز هیچ اشاره خصوصی‌ای ندارید. هنگامی که چنین پیامی بگیرید یا بفرستید این‌جا نشان داده خواهد شد.", + "empty_column.disabled_feed": "این خوراک به دست مدیران کارسازتان از کار انداخته شده.", "empty_column.domain_blocks": "هنوز هیچ دامنه‌ای مسدود نشده است.", "empty_column.explore_statuses": "الآن چیزی پرطرفدار نیست. بعداً دوباره بررسی کنید!", "empty_column.favourited_statuses": "شما هنوز هیچ فرسته‌ای را نپسندیده‌اید. هنگامی که فرسته‌ای را بپسندید، این‌جا نشان داده خواهد شد.", @@ -334,6 +348,7 @@ "empty_column.notification_requests": "همه چیز تمیز است! هیچ‌چیزی این‌جا نیست. هنگامی که آگاهی‌های جدیدی دریافت کنید، بسته به تنظیماتتان این‌جا ظاهر خواهند شد.", "empty_column.notifications": "هنوز هیچ آگاهی‌ای ندارید. هنگامی که دیگران با شما برهم‌کنش داشته باشند، این‌جا خواهید دیدش.", "empty_column.public": "این‌جا هنوز چیزی نیست! خودتان چیزی بنویسید یا کاربران کارسازهای دیگر را پی‌گیری کنید تا این‌جا پُر شود", + "error.no_hashtag_feed_access": "پیوستن یا ورود برای مشاهده و پی‌گیری این برچسب.", "error.unexpected_crash.explanation": "به خاطر اشکالی در کدهای ما یا ناسازگاری با مرورگر شما، این صفحه به درستی نمایش نیافت.", "error.unexpected_crash.explanation_addons": "این صفحه نمی‌تواند درست نشان داده شود. احتمالاً این خطا ناشی از یک افزونهٔ مرورگر یا ابزار ترجمهٔ خودکار است.", "error.unexpected_crash.next_steps": "لطفاً صفحه را دوباره باز کنید. اگر کمکی نکرد، شاید همچنان بتوانید با ماستودون از راه یک مرورگر دیگر یا با یکی از کاره‌های آن کار کنید.", @@ -345,11 +360,9 @@ "explore.trending_links": "اخبار", "explore.trending_statuses": "فرسته‌ها", "explore.trending_tags": "برچسب‌ها", + "featured_carousel.current": "فرستهٔ {current, number} از {max, number}", "featured_carousel.header": "{count, plural, one {فرسته سنجاق‌شده} other {فرسته‌های سنجاق‌شده}}", - "featured_carousel.next": "بعدی", - "featured_carousel.post": "فرسته", - "featured_carousel.previous": "قبلی", - "featured_carousel.slide": "{index} از {total}", + "featured_carousel.slide": "فرستهٔ {current, number} از {max, number}", "filter_modal.added.context_mismatch_explanation": "این دستهٔ پالایه به بافتاری که در آن به این فرسته دسترسی دارید اعمال نمی‌شود. اگر می‌خواهید فرسته در این بافتار هم پالوده شود، باید پالایه را ویرایش کنید.", "filter_modal.added.context_mismatch_title": "بافتار نامطابق!", "filter_modal.added.expired_explanation": "این دستهٔ پالایه منقضی شده است. برای اعمالش باید تاریخ انقضا را عوض کنید.", @@ -392,6 +405,7 @@ "follow_suggestions.who_to_follow": "افرادی برای پی‌گیری", "followed_tags": "برچسب‌های پی‌گرفته", "footer.about": "درباره", + "footer.about_this_server": "درباره", "footer.directory": "فهرست نمایه‌ها", "footer.get_app": "گرفتن کاره", "footer.keyboard_shortcuts": "میان‌برهای صفحه‌کلید", @@ -449,10 +463,12 @@ "ignore_notifications_modal.private_mentions_title": "چشم‌پوشی از نام‌بری‌های خصوصی ناخواسته؟", "info_button.label": "راهنما", "info_button.what_is_alt_text": "

متن جایگزین چیست؟

متن جایگزین توضیحات تصویری را برای افراد دارای اختلالات بینایی، اتصالات با پهنای باند کم یا کسانی که به دنبال زمینه اضافی هستند ارائه می دهد.

با نوشتن متن جایگزین واضح، مختصر و عینی می توانید دسترسی و درک را برای همه بهبود بخشید.

  • عناصر مهم را ضبط کنید
  • متن را در تصاویر خلاصه کنید
  • از ساختار جمله منظم استفاده کنید
  • از اطلاعات اضافی خودداری کنید
  • روی روندها و یافته های کلیدی در تصاویر پیچیده (مانند نمودارها یا نقشه ها) تمرکز کنید.
", + "interaction_modal.action": "برای تعامل با فرستهٔ {name} باید به حسابتان روی هر کارساز ماستودونی که استفاده می‌کنید وارد شوید.", "interaction_modal.go": "برو", "interaction_modal.no_account_yet": "هنوز حساب کاربری ندارید؟", "interaction_modal.on_another_server": "روی کارسازی دیگر", "interaction_modal.on_this_server": "روی این کارساز", + "interaction_modal.title": "ورود برای ادامه", "interaction_modal.username_prompt": "به عنوان مثال {example}", "intervals.full.days": "{number, plural, one {# روز} other {# روز}}", "intervals.full.hours": "{number, plural, one {# ساعت} other {# ساعت}}", @@ -473,6 +489,7 @@ "keyboard_shortcuts.home": "گشودن خط زمانی خانگی", "keyboard_shortcuts.hotkey": "میان‌بر", "keyboard_shortcuts.legend": "نمایش این نشانه", + "keyboard_shortcuts.load_more": "تمرکز روی دکمهٔ «بار کردن بیش‌تر»", "keyboard_shortcuts.local": "گشودن خط زمانی محلی", "keyboard_shortcuts.mention": "اشاره به نویسنده", "keyboard_shortcuts.muted": "گشودن فهرست کاربران خموش", @@ -481,6 +498,7 @@ "keyboard_shortcuts.open_media": "گشودن رسانه", "keyboard_shortcuts.pinned": "گشودن سیاههٔ فرسته‌های سنجاق شده", "keyboard_shortcuts.profile": "گشودن نمایهٔ نویسنده", + "keyboard_shortcuts.quote": "نقل فرسته", "keyboard_shortcuts.reply": "پاسخ به فرسته", "keyboard_shortcuts.requests": "گشودن سیاههٔ درخواست‌های پی‌گیری", "keyboard_shortcuts.search": "تمرکز روی نوار جست‌وجو", @@ -489,6 +507,7 @@ "keyboard_shortcuts.toggle_hidden": "نمایش/نهفتن نوشتهٔ پشت هشدار محتوا", "keyboard_shortcuts.toggle_sensitivity": "نمایش/نهفتن رسانه", "keyboard_shortcuts.toot": "شروع یک فرستهٔ جدید", + "keyboard_shortcuts.top": "جابه‌جایی به بالای سیاهه", "keyboard_shortcuts.translate": "برای ترجمه یک پست", "keyboard_shortcuts.unfocus": "برداشتن تمرکز از ناحیهٔ نوشتن یا جست‌وجو", "keyboard_shortcuts.up": "بالا بردن در سیاهه", @@ -530,7 +549,7 @@ "lists.search": "جست‌وجو", "lists.show_replies_to": "شامل پاسخ از اعضای لیست به", "load_pending": "{count, plural, one {# مورد جدید} other {# مورد جدید}}", - "loading_indicator.label": "در حال بارگذاری…", + "loading_indicator.label": "بار کردن…", "media_gallery.hide": "نهفتن", "moved_to_account_banner.text": "حسابتان {disabledAccount} اکنون از کار افتاده؛ چرا که به {movedToAccount} منتقل شدید.", "mute_modal.hide_from_notifications": "نهفتن از آگاهی‌ها", @@ -581,12 +600,12 @@ "notification.admin.report_statuses_other": "{name}، {target} را گزارش داد", "notification.admin.sign_up": "{name} ثبت نام کرد", "notification.admin.sign_up.name_and_others": "{name} و {count, plural, one {# نفر دیگر} other {# نفر دیگر}} ثبت‌نام کردند", - "notification.annual_report.message": "آمار ‪#Wrapstodon‬ ‏{year} تان منتظر است! لحظه‌های به یاد ماندنی و نقاط پررنگ سال را روی ماستودون رونمایی کنید!", - "notification.annual_report.view": "دیدن ‪#Wrapstodon‬", + "notification.annual_report.message": "#خلاصه_ماستودون {year} منتظرتان است! رونمایی از لحظه‌های به یاد ماندنی و نقاط پررنگ سال روی ماستودون!", + "notification.annual_report.view": "دیدن #خلاصه_ماستودون", "notification.favourite": "{name} فرسته‌تان را برگزید", "notification.favourite.name_and_others_with_link": "{name} و {count, plural, one {# نفر دیگر} other {# نفر دیگر}} فرسته‌تان را برگزیدند", - "notification.favourite_pm": "{name} ذکر خصوصی شما را مورد علاقه قرار داد", - "notification.favourite_pm.name_and_others_with_link": "{name} و {count, plural, one {دیگری} other {دیگران}} ذکر خصوصی شما را مورد علاقه قرار دادند", + "notification.favourite_pm": "{name} اشارهٔ خصوصیتان را برگزید", + "notification.favourite_pm.name_and_others_with_link": "‏{name} و {count, plural, one {# نفر دیگر} other {# نفر دیگر}} اشارهٔ خصوصیتان را برگزیدند", "notification.follow": "‫{name}‬ پی‌گیرتان شد", "notification.follow.name_and_others": "{name} و {count, plural, other {#}} نفر دیگر پیتان گرفتند", "notification.follow_request": "{name} درخواست پی‌گیریتان را داد", @@ -609,6 +628,7 @@ "notification.moderation_warning.action_suspend": "حسابتان معلّق شده.", "notification.own_poll": "نظرسنجیتان پایان یافت", "notification.poll": "نظرسنجی‌ای که در آن رأی دادید به پایان رسید", + "notification.quoted_update": "‏{name} فرسته‌ای که نقل کردید را ویراست", "notification.reblog": "‫{name}‬ فرسته‌تان را تقویت کرد", "notification.reblog.name_and_others_with_link": "{name} و {count, plural, one {# نفر دیگر} other {# نفر دیگر}} فرسته‌تان را تقویت کردند", "notification.relationships_severance_event": "قطع ارتباط با {name}", @@ -621,10 +641,10 @@ "notification_requests.accept": "پذیرش", "notification_requests.accept_multiple": "{count, plural, one {پذیرش درخواست…} other {پذیرش درخواست‌ها…}}", "notification_requests.confirm_accept_multiple.button": "پذیرش {count, plural,one {درخواست} other {درخواست‌ها}}", - "notification_requests.confirm_accept_multiple.message": "در حال پذیرش {count, plural,one {یک}other {#}} درخواست آگاهی هستید. مطمئنید که می‌خواهید ادامه دهید؟", + "notification_requests.confirm_accept_multiple.message": "دارید {count, plural,one {یک}other {#}} درخواست آگاهی را می‌پذیرید. مطمئنید که می‌خواهید ادامه دهید؟", "notification_requests.confirm_accept_multiple.title": "پذیرش درخواست‌های آگاهی؟", "notification_requests.confirm_dismiss_multiple.button": "رد {count, plural,one {درخواست} other {درخواست‌ها}}", - "notification_requests.confirm_dismiss_multiple.message": "شما در شرف رد کردن {count, plural, one {یک درخواست آگاهی} other {درخواست آگاهی}} هستید. دیگر نمی توانید به راحتی به {count, plural, one {آن} other {آن‌ها}} دسترسی پیدا کنید. آیا مطمئن هستید که می خواهید ادامه دهید؟", + "notification_requests.confirm_dismiss_multiple.message": "دارید {count, plural, one {یک درخواست آگاهی} other {# درخواست آگاهی}} را رد می‌کنید که دیگر نمی توانید به راحتی به {count, plural, one {آن} other {آن‌ها}} دسترسی پیدا کنید. مطمئنید که می‌خواهید ادامه دهید؟", "notification_requests.confirm_dismiss_multiple.title": "رد کردن درخواست‌های آگاهی؟", "notification_requests.dismiss": "دورانداختن", "notification_requests.dismiss_multiple": "{count, plural, one {دورانداختن درخواست…} other {دورانداختن درخواست‌ها…}}", @@ -723,19 +743,29 @@ "poll_button.remove_poll": "برداشتن نظرسنجی", "privacy.change": "تغییر محرمانگی فرسته", "privacy.direct.long": "هرکسی که در فرسته نام برده شده", - "privacy.direct.short": "ذکر خصوصی", + "privacy.direct.short": "اشارهٔ خصوصی", "privacy.private.long": "تنها پی‌گیرندگانتان", "privacy.private.short": "پی‌گیرندگان", "privacy.public.long": "هرکسی در و بیرون از ماستودون", "privacy.public.short": "عمومی", + "privacy.quote.anyone": "‏{visibility}، هرکسی می‌تواند نقل کند", + "privacy.quote.disabled": "‏{visibility}، نقل‌ها از کار افتاده", + "privacy.quote.limited": "‏{visibility}، نقل‌ها محدود شده", "privacy.unlisted.additional": "درست مثل عمومی رفتار می‌کند؛ جز این که فرسته در برچسب‌ها یا خوراک‌های زنده، کشف یا جست‌وجوی ماستودون ظاهر نخواهد شد. حتا اگر کلیّت نمایه‌تان اجازه داده باشد.", + "privacy.unlisted.long": "نهفته از نتیجه‌های جست‌وجوی ماستودون و خط‌های زمانی داغ و عمومی", "privacy.unlisted.short": "عمومی ساکت", "privacy_policy.last_updated": "آخرین به‌روز رسانی در {date}", "privacy_policy.title": "سیاست محرمانگی", + "quote_error.edit": "هنگام ویراستن فرسته نمی‌توان نقلی افزود.", + "quote_error.poll": "نقل نظرسنجی‌ها مجاز نیست.", + "quote_error.private_mentions": "نقل اشاره‌های مستقیم مجاز نیست.", + "quote_error.quote": "در هر زمان تنها یک نقل مجاز است.", + "quote_error.unauthorized": "مجاز به نقل این فرسته نیستید.", + "quote_error.upload": "نقل پیوست‌های رسانه‌ای مجاز نیست.", "recommended": "پیشنهادشده", "refresh": "نوسازی", "regeneration_indicator.please_stand_by": "لطفا منتظر باشید.", - "regeneration_indicator.preparing_your_home_feed": "در حال آماده کردن خوراک خانگی شما…", + "regeneration_indicator.preparing_your_home_feed": "آماده کردن خوراک خانگیتان…", "relative_time.days": "{number} روز", "relative_time.full.days": "{number, plural, one {# روز} other {# روز}} پیش", "relative_time.full.hours": "{number, plural, one {# ساعت} other {# ساعت}} پیش", @@ -747,6 +777,9 @@ "relative_time.minutes": "{number} دقیقه", "relative_time.seconds": "{number} ثانیه", "relative_time.today": "امروز", + "remove_quote_hint.button_label": "گرفتم", + "remove_quote_hint.message": "می‌توانید این کار را از {icon} فهرست گزینه‌ها انجام دهید.", + "remove_quote_hint.title": "می‌خواهید فرستهٔ نقل شده‌تان را بردارید؟", "reply_indicator.attachments": "{count, plural, one {# پیوست} other {# پیوست}}", "reply_indicator.cancel": "لغو", "reply_indicator.poll": "نظرسنجی", @@ -783,9 +816,9 @@ "report.statuses.subtitle": "همهٔ موارد انجام شده را برگزینید", "report.statuses.title": "آیا فرسته‌ای وجود دارد که از این گزارش پشتیبانی کند؟", "report.submit": "فرستادن", - "report.target": "در حال گزارش {target}", + "report.target": "گزارش کردن {target}", "report.thanks.take_action": "در اینجا گزینه‌هایی برای کنترل آنچه در ماستودون میبینید، وجود دارد:", - "report.thanks.take_action_actionable": "در حالی که ما این مورد را بررسی می‌کنیم، می‌توانید علیه ‎@{name} اقدام کنید:", + "report.thanks.take_action_actionable": "تا بررسیش می‌کنیم می‌توانید علیه ‎@{name} اقدام کنید:", "report.thanks.title": "نمی‌خواهید این را ببینید؟", "report.thanks.title_actionable": "ممنون بابت گزارش، ما آن را بررسی خواهیم کرد.", "report.unfollow": "پی‌نگرفتن ‎@{name}", @@ -838,15 +871,23 @@ "status.admin_account": "گشودن واسط مدیریت برای ‎@{name}", "status.admin_domain": "گشودن واسط مدیریت برای ‎{domain}", "status.admin_status": "گشودن این فرسته در واسط مدیریت", + "status.all_disabled": "تقویت‌ها و نقل‌ها از کار افتاده‌اند", "status.block": "انسداد ‎@{name}", "status.bookmark": "نشانک", "status.cancel_reblog_private": "ناتقویت", + "status.cannot_quote": "مجاز به نقل این فرسته نیستید", "status.cannot_reblog": "این فرسته قابل تقویت نیست", - "status.context.load_new_replies": "پاسخ‌های جدیدی موجودند", - "status.context.loading": "بررسی کردن برای پاسخ‌های بیش‌تر", + "status.contains_quote": "دارای نقل", + "status.context.loading": "بار کردن پاسخ‌های بیش‌تر", + "status.context.loading_error": "نتوانست پاسخ‌های بیش‌تری بار کند", + "status.context.loading_success": "پاسخ‌های جدید بار شدند", + "status.context.more_replies_found": "پاسخ‌های بیش‌تری پیدا شد", + "status.context.retry": "تلاش دوباره", + "status.context.show": "نمایش", "status.continued_thread": "رشتهٔ دنباله دار", "status.copy": "رونوشت از پیوند فرسته", "status.delete": "حذف", + "status.delete.success": "فرسته حذف شد", "status.detailed_status": "نمایش کامل گفتگو", "status.direct": "اشارهٔ خصوصی به ‪@{name}‬", "status.direct_indicator": "اشارهٔ خصوصی", @@ -855,7 +896,7 @@ "status.edited_x_times": "{count, plural, one {{count} مرتبه} other {{count} مرتبه}} ویرایش شد", "status.embed": "گرفتن کد تعبیه", "status.favourite": "برگزیده‌", - "status.favourites": "{count, plural, one {برگزیده} other {برگزیده}}", + "status.favourites_count": "{count, plural, one {{counter} برگزیدن} other {{counter} برگزیدن}}", "status.filter": "پالایش این فرسته", "status.history.created": "توسط {name} در {date} ایجاد شد", "status.history.edited": "توسط {name} در {date} ویرایش شد", @@ -871,24 +912,43 @@ "status.pin": "سنجاق به نمایه", "status.quote": "نقل‌قول", "status.quote.cancel": "لغو نقل", + "status.quote_error.blocked_account_hint.title": "این فرسته نهفته است چرا که ‪@{name}‬ را مسدود کرده‌اید.", + "status.quote_error.blocked_domain_hint.title": "این فرسته نهفته است چرا که {domain} را مسدود کرده‌اید.", "status.quote_error.filtered": "نهفته بنا بر یکی از پالایه‌هایتان", + "status.quote_error.limited_account_hint.action": "نمایش به هر روی", + "status.quote_error.limited_account_hint.title": "این حساب به دست ناظم‌های {domain} پنهان شده.", + "status.quote_error.muted_account_hint.title": "این فرسته نهفته است چرا که ‪@{name}‬ را خموش کرده‌اید.", "status.quote_error.not_available": "فرسته در دسترس نیست", "status.quote_error.pending_approval": "فرسته منتظر", + "status.quote_error.pending_approval_popout.body": "روی ماستودون می‌توان افراد مجاز به نقل را واپایید. این فرسته تا زمان گرفتن تأییده از نگارندهٔ اصلی معلّق است.", + "status.quote_error.revoked": "فرسته به دست نگارنده برداشته شد", + "status.quote_followers_only": "تنها پی‌گیران می‌توانند این فرسته را نقل کنند", + "status.quote_manual_review": "نگارنده به صورت دستی بررسی خواهد کرد", + "status.quote_noun": "نقل", "status.quote_policy_change": "تغییر کسانی که می‌توانند نقل کنند", "status.quote_post_author": "فرسته‌ای از @{name} نقل شد", + "status.quote_private": "فرسته‌های خصوصی نمی‌توانند نقل شوند", + "status.quotes.empty": "هنوز کسی این فرسته را نقل نکرده. وقتی کسی چنین کند این‌جا نشان داده خواهد شد.", + "status.quotes.local_other_disclaimer": "نقل‌هایی که به دست نگارنده رد شده باشند نشان داده نخواهند شد.", + "status.quotes.remote_other_disclaimer": "تنها نقل‌ها از {domain} تضمین نمایش در این‌جا را دارند. نقل‌های رد شده به دست نگاره نشان داده نخواهند شد.", + "status.quotes_count": "{count, plural, one {{counter} نقل} other {{counter} نقل}}", "status.read_more": "بیشتر بخوانید", "status.reblog": "تقویت", + "status.reblog_or_quote": "نقل یا تقویت", + "status.reblog_private": "هم‌رسانی دوباره با پی‌گیرانتان", "status.reblogged_by": "‫{name}‬ تقویت کرد", - "status.reblogs": "{count, plural, one {تقویت} other {تقویت}}", "status.reblogs.empty": "هنوز هیچ کسی این فرسته را تقویت نکرده است. وقتی کسی چنین کاری کند، این‌جا نمایش داده خواهد شد.", + "status.reblogs_count": "{count, plural, one {{counter} تقویت} other {{counter} تقویت}}", "status.redraft": "حذف و بازنویسی", "status.remove_bookmark": "برداشتن نشانک", "status.remove_favourite": "حذف از موارد دلخواه", + "status.remove_quote": "برداشتن", "status.replied_in_thread": "در رشته پاسخ داده", "status.replied_to": "به {name} پاسخ داد", "status.reply": "پاسخ", "status.replyAll": "پاسخ به رشته", "status.report": "گزارش ‎@{name}", + "status.request_quote": "درخواست نقل", "status.revoke_quote": "حذف فرسته‌ام از فرسته @{name}", "status.sensitive_warning": "محتوای حساس", "status.share": "هم‌رسانی", @@ -927,14 +987,15 @@ "upload_button.label": "افزودن تصاویر، ویدیو یا یک پروندهٔ صوتی", "upload_error.limit": "از حد مجاز بارگذاری پرونده فراتر رفتید.", "upload_error.poll": "بارگذاری پرونده در نظرسنجی‌ها مجاز نیست.", + "upload_error.quote": "بارگذاری پرونده در نقل‌ها محاز نیست.", "upload_form.drag_and_drop.instructions": "برای دریافت پیوست رسانه، space را فشار دهید یا وارد کنید. در حین کشیدن، از کلیدهای جهت دار برای حرکت دادن پیوست رسانه در هر جهت معین استفاده کنید. برای رها کردن ضمیمه رسانه در موقعیت جدید خود، مجدداً space یا enter را فشار دهید، یا برای لغو، escape را فشار دهید.", "upload_form.drag_and_drop.on_drag_cancel": "کشیدن لغو شد. پیوست رسانه {item} حذف شد.", "upload_form.drag_and_drop.on_drag_end": "پیوست رسانه {item} حذف شد.", "upload_form.drag_and_drop.on_drag_over": "پیوست رسانه {item} منتقل شد.", "upload_form.drag_and_drop.on_drag_start": "پیوست رسانه {item} برداشته شد.", "upload_form.edit": "ویرایش", - "upload_progress.label": "در حال بارگذاری...", - "upload_progress.processing": "در حال پردازش…", + "upload_progress.label": "بار گذاشتن...", + "upload_progress.processing": "پردازش کردن…", "username.taken": "این نام کاربری گرفته شده. نام دیگری امتحان کنید", "video.close": "بستن ویدیو", "video.download": "بارگیری پرونده", @@ -951,9 +1012,19 @@ "video.volume_down": "کاهش حجم صدا", "video.volume_up": "افزایش حجم صدا", "visibility_modal.button_title": "تنظیم نمایانی", + "visibility_modal.direct_quote_warning.text": "اگر تنظبمات کنونی را ذخیره کنید نقل تعبیه شده تبدیل به پیوند خواهد شد.", + "visibility_modal.direct_quote_warning.title": "نقل‌ها نمی‌توانند در اشاره‌های خصوصی تعبیه شوند", "visibility_modal.header": "نمایانی و برهم‌کنش", + "visibility_modal.helper.direct_quoting": "اشاره‌های خصوصی روی ماستودون نیم‌توانند به دست دیگران نقل شوند.", + "visibility_modal.helper.privacy_editing": "پس از انتشار فرسته نمی‌توان نمایانی را تغییر داد.", + "visibility_modal.helper.privacy_private_self_quote": "خودنقلی فرسته‌های خصوصی نمی‌تواند عمومی شود.", + "visibility_modal.helper.private_quoting": "فرسته‌ّای فقط پی‌گیران روی ماستودون نمی‌توانند به دست دیگران نقل شوند.", "visibility_modal.helper.unlisted_quoting": "هنگامی که افراد نقلتان می‌کنند فرسته‌شان هم از خط‌زمانی‌های داغ پنهان خواهد بود.", + "visibility_modal.instructions": "واپایش کسانی که می‌توانند با این فرسته تعامل داشته باشند. می‌تواند با رفتن به ترجیحات > پیش‌گزیده‌ها فرستادن تنظیمات را به همهٔ فرسته‌های آینده نیز اعمال کنید.", + "visibility_modal.privacy_label": "نمایانی", "visibility_modal.quote_followers": "فقط پی‌گیرندگان", + "visibility_modal.quote_label": "چه‌کسی می‌تواند نقل کند", + "visibility_modal.quote_nobody": "فقط من", "visibility_modal.quote_public": "هرکسی", "visibility_modal.save": "ذخیره" } diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index 8bf69ab31d873b..c0cd9a77cd5a9b 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Lopeta ilmoittamasta minulle, kun @{name} julkaisee", "account.domain_blocking": "Verkkotunnus estetty", "account.edit_profile": "Muokkaa profiilia", + "account.edit_profile_short": "Muokkaa", "account.enable_notifications": "Ilmoita minulle, kun @{name} julkaisee", "account.endorse": "Suosittele profiilissa", "account.familiar_followers_many": "Seuraajina {name1}, {name2} ja {othersCount, plural, one {1 muu, jonka tunnet} other {# muuta, jotka tunnet}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Ei julkaisuja", "account.follow": "Seuraa", "account.follow_back": "Seuraa takaisin", + "account.follow_back_short": "Seuraa takaisin", + "account.follow_request": "Pyydä lupaa seurata", + "account.follow_request_cancel": "Peruuta pyyntö", + "account.follow_request_cancel_short": "Peruuta", + "account.follow_request_short": "Pyyntö", "account.followers": "Seuraajat", "account.followers.empty": "Kukaan ei seuraa tätä käyttäjää vielä.", "account.followers_counter": "{count, plural, one {{counter} seuraaja} other {{counter} seuraajaa}}", @@ -70,12 +76,11 @@ "account.posts_with_replies": "Julkaisut ja vastaukset", "account.remove_from_followers": "Poista {name} seuraajista", "account.report": "Raportoi @{name}", - "account.requested": "Odottaa hyväksyntää. Peruuta seurantapyyntö napsauttamalla", "account.requested_follow": "{name} on pyytänyt lupaa seurata sinua", "account.requests_to_follow_you": "Pyynnöt seurata sinua", "account.share": "Jaa käyttäjän @{name} profiili", "account.show_reblogs": "Näytä käyttäjän @{name} tehostukset", - "account.statuses_counter": "{count, plural, one {{counter} julkaisu} other {{counter} julkaisua}}", + "account.statuses_counter": "{count, plural, one {{counter} julkaisu} other {{counter} julkaisua}}", "account.unblock": "Kumoa käyttäjän @{name} esto", "account.unblock_domain": "Kumoa verkkotunnuksen {domain} esto", "account.unblock_domain_short": "Kumoa esto", @@ -95,7 +100,7 @@ "admin.impact_report.instance_followers": "Seuraajat, jotka käyttäjämme menettäisivät", "admin.impact_report.instance_follows": "Seuraajat, jotka heidän käyttäjänsä menettäisivät", "admin.impact_report.title": "Vaikutusten yhteenveto", - "alert.rate_limited.message": "Yritä uudelleen {retry_time, time, medium} jälkeen.", + "alert.rate_limited.message": "Yritä uudelleen kello {retry_time, time, medium} jälkeen.", "alert.rate_limited.title": "Pyyntömäärää rajoitettu", "alert.unexpected.message": "Tapahtui odottamaton virhe.", "alert.unexpected.title": "Hups!", @@ -108,25 +113,52 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Kuvaile tätä näkövammallisille ihmisille…", "alt_text_modal.done": "Valmis", "announcement.announcement": "Tiedote", - "annual_report.summary.archetype.booster": "Tehostaja", - "annual_report.summary.archetype.lurker": "Lymyilijä", - "annual_report.summary.archetype.oracle": "Oraakkeli", - "annual_report.summary.archetype.pollster": "Mielipidetutkija", - "annual_report.summary.archetype.replier": "Sosiaalinen perhonen", - "annual_report.summary.followers.followers": "seuraajaa", - "annual_report.summary.followers.total": "{count} yhteensä", - "annual_report.summary.here_it_is": "Tässä on katsaus vuoteesi {year}:", - "annual_report.summary.highlighted_post.by_favourites": "suosikkeihin lisätyin julkaisu", - "annual_report.summary.highlighted_post.by_reblogs": "tehostetuin julkaisu", - "annual_report.summary.highlighted_post.by_replies": "julkaisu, jolla on eniten vastauksia", - "annual_report.summary.highlighted_post.possessive": "Käyttäjän {name}", + "annual_report.announcement.action_build": "Koosta oma Wrapstodon", + "annual_report.announcement.action_dismiss": "Ei kiitos", + "annual_report.announcement.action_view": "Näytä oma Wrapstodon", + "annual_report.announcement.description": "Tutustu toimintaasi Mastodonissa kuluneen vuoden aikana.", + "annual_report.announcement.title": "Wrapstodon {year} on täällä", + "annual_report.nav_item.badge": "Uusi", + "annual_report.shared_page.donate": "Lahjoita", + "annual_report.shared_page.footer": "Luotu {heart}:lla Mastodon-tiimin toimesta", + "annual_report.shared_page.footer_server_info": "{username} käyttää palvelinta {domain}, yhtä monista Mastodonin tarjoamista yhteisöistä.", + "annual_report.summary.archetype.booster.desc_public": "{name} pysyi valppaana tehostettavien julkaisujen suhteen, vahvistaen muita luojia täydellisellä tähtäyksellä.", + "annual_report.summary.archetype.booster.desc_self": "Pysyit valppaana tehostettavien julkaisujen suhteen, vahvistaen muita luojia täydellisellä tähtäyksellä.", + "annual_report.summary.archetype.booster.name": "Jousimies", + "annual_report.summary.archetype.die_drei_fragezeichen": "???", + "annual_report.summary.archetype.lurker.desc_public": "Tiedämme, että {name} oli siellä jossakin, nauttien Mastodonista omalla hiljaisella tavallaan.", + "annual_report.summary.archetype.lurker.desc_self": "Tiedämme, että olit siellä jossakin, nauttien Mastodonista omalla hiljaisella tavallasi.", + "annual_report.summary.archetype.lurker.name": "Stoalainen", + "annual_report.summary.archetype.oracle.desc_public": "{name} loi enemmän julkaisuja kuin vastauksia, siten pitäen Mastodonin tuoreena ja valmiina tulevaisuuteen.", + "annual_report.summary.archetype.oracle.desc_self": "Loit enemmän julkaisuja kuin vastauksia, siten pitäen Mastodonin tuoreena ja valmiina tulevaisuuteen.", + "annual_report.summary.archetype.oracle.name": "Oraakkeli", + "annual_report.summary.archetype.pollster.desc_public": "{name} loi enemmän äänestyksiä kuin muita julkaisutyyppejä, siten herättäen uteliaisuutta Mastodonissa.", + "annual_report.summary.archetype.pollster.desc_self": "Loit enemmän äänestyksiä kuin muita julkaisutyyppejä, siten herättäen uteliaisuutta Mastodonissa.", + "annual_report.summary.archetype.pollster.name": "Ihmettelijä", + "annual_report.summary.archetype.replier.desc_public": "{name} vastasi säännöllisesti toisten julkaisuihin, siten pölyttäen Mastodonia uusilla keskusteluilla.", + "annual_report.summary.archetype.replier.desc_self": "Vastasit säännöllisesti toisten julkaisuihin, siten pölyttäen Mastodonia uusilla keskusteluilla.", + "annual_report.summary.archetype.replier.name": "Perhonen", + "annual_report.summary.archetype.reveal": "Paljasta arkkityyppini", + "annual_report.summary.archetype.reveal_description": "Kiitos, että olet osa Mastodonia! Aika selvittää, minkä arkkityypin ruumiillistuma olit vuonna {year}.", + "annual_report.summary.archetype.title_public": "Käyttäjän {name} arkkityyppi", + "annual_report.summary.archetype.title_self": "Arkkityyppisi", + "annual_report.summary.close": "Sulje", + "annual_report.summary.copy_link": "Kopioi linkki", + "annual_report.summary.followers.new_followers": "{count, plural, one {uusi seuraaja} other {uutta seuraajaa}}", + "annual_report.summary.highlighted_post.boost_count": "Tätä julkaisua tehostettiin {count, plural, one {kerran} other {# kertaa}}.", + "annual_report.summary.highlighted_post.favourite_count": "Tämä julkaisu lisättiin suosikkeihin {count, plural, one {kerran} other {# kertaa}}.", + "annual_report.summary.highlighted_post.reply_count": "Tämä julkaisu sai {count, plural, one {1 vastauksen} other {# vastausta}}.", + "annual_report.summary.highlighted_post.title": "Suosituin julkaisu", "annual_report.summary.most_used_app.most_used_app": "käytetyin sovellus", "annual_report.summary.most_used_hashtag.most_used_hashtag": "käytetyin aihetunniste", - "annual_report.summary.most_used_hashtag.none": "Ei mitään", + "annual_report.summary.most_used_hashtag.used_count": "Sisällytit tämän aihetunnisteen {count, plural, one {1 julkaisuun} other {# julkaisuun}}.", + "annual_report.summary.most_used_hashtag.used_count_public": "{name} sisällytti tämän aihetunnisteen {count, plural, one {1 julkaisuun} other {# julkaisuun}}.", "annual_report.summary.new_posts.new_posts": "uutta julkaisua", "annual_report.summary.percentile.text": "Olet osa huippujoukkoa, johon kuuluu{domain}-käyttäjistä.", "annual_report.summary.percentile.we_wont_tell_bernie": "Emme kerro Bernie Sandersille.", - "annual_report.summary.thanks": "Kiitos, että olet osa Mastodonia!", + "annual_report.summary.share_elsewhere": "Jaa muualla", + "annual_report.summary.share_message": "Arkkityyppini on {archetype}!", + "annual_report.summary.share_on_mastodon": "Jaa Mastodonissa", "attachments_list.unprocessed": "(käsittelemätön)", "audio.hide": "Piilota ääni", "block_modal.remote_users_caveat": "Pyydämme palvelinta {domain} kunnioittamaan päätöstäsi. Myötämielisyyttä ei kuitenkaan taata, koska jotkin palvelimet voivat käsitellä estoja eri tavalla. Julkiset julkaisut voivat silti näkyä kirjautumattomille käyttäjille.", @@ -152,6 +184,8 @@ "bundle_modal_error.close": "Sulje", "bundle_modal_error.message": "Jotain meni pieleen tätä näyttöä ladattaessa.", "bundle_modal_error.retry": "Yritä uudelleen", + "carousel.current": "Dia {current, number} / {max, number}", + "carousel.slide": "Dia {current, number} / {max, number}", "closed_registrations.other_server_instructions": "Koska Mastodon on hajautettu, voit luoda tilin toiselle palvelimelle ja olla silti vuorovaikutuksessa tämän kanssa.", "closed_registrations_modal.description": "Tilin luonti palvelimelle {domain} ei tällä hetkellä ole mahdollista, mutta ota huomioon, ettei Mastodonin käyttö edellytä juuri kyseisen palvelimen tiliä.", "closed_registrations_modal.find_another_server": "Etsi toinen palvelin", @@ -168,6 +202,8 @@ "column.edit_list": "Muokkaa listaa", "column.favourites": "Suosikit", "column.firehose": "Livesyötteet", + "column.firehose_local": "Tämän palvelimen livesyöte", + "column.firehose_singular": "Livesyöte", "column.follow_requests": "Seurantapyynnöt", "column.home": "Koti", "column.list_members": "Hallitse listan jäseniä", @@ -187,12 +223,13 @@ "community.column_settings.local_only": "Vain paikalliset", "community.column_settings.media_only": "Vain media", "community.column_settings.remote_only": "Vain etätilit", + "compose.error.blank_post": "Julkaisu ei voi olla tyhjä.", "compose.language.change": "Vaihda kieli", "compose.language.search": "Hae kieliä…", "compose.published.body": "Julkaisu lähetetty.", "compose.published.open": "Avaa", "compose.saved.body": "Julkaisu tallennettu.", - "compose_form.direct_message_warning_learn_more": "Lisätietoja", + "compose_form.direct_message_warning_learn_more": "Lue lisää", "compose_form.encryption_warning": "Mastodonin julkaisut eivät ole päästä päähän salattuja. Älä jaa arkaluonteisia tietoja Mastodonissa.", "compose_form.hashtag_warning": "Tätä julkaisua ei voi liittää aihetunnisteisiin, koska se ei ole julkinen. Vain näkyvyydeltään julkisiksi määritettyjä julkaisuja voidaan hakea aihetunnisteiden avulla.", "compose_form.lock_disclaimer": "Tilisi ei ole {locked}. Kuka tahansa voi seurata tiliäsi ja nähdä vain seuraajille rajaamasi julkaisut.", @@ -223,7 +260,7 @@ "confirmations.discard_draft.edit.cancel": "Palaa muokkaamaan", "confirmations.discard_draft.edit.message": "Jatkaminen tuhoaa kaikki muutokset, joita olet tehnyt julkaisuun, jota olet parhaillaan muokkaamassa.", "confirmations.discard_draft.edit.title": "Hylätäänkö luonnosjulkaisusi muutokset?", - "confirmations.discard_draft.post.cancel": "Palaa lunnokseen", + "confirmations.discard_draft.post.cancel": "Palaa luonnokseen", "confirmations.discard_draft.post.message": "Jatkaminen tuhoaa julkaisun, jota olet parhaillaan laatimassa.", "confirmations.discard_draft.post.title": "Hylätäänkö luonnosjulkaisusi?", "confirmations.discard_edit_media.confirm": "Hylkää", @@ -239,6 +276,11 @@ "confirmations.missing_alt_text.secondary": "Julkaise silti", "confirmations.missing_alt_text.title": "Lisätäänkö vaihtoehtoinen teksti?", "confirmations.mute.confirm": "Mykistä", + "confirmations.private_quote_notify.cancel": "Takaisin muokkaukseen", + "confirmations.private_quote_notify.confirm": "Julkaise", + "confirmations.private_quote_notify.do_not_show_again": "Älä näytä tätä viestiä uudelleen", + "confirmations.private_quote_notify.message": "Lainaamasi käyttäjä ja muut mainitut saavat ilmoituksen ja voivat tarkastella julkaisuasi, vaikka he eivät seuraisi sinua.", + "confirmations.private_quote_notify.title": "Jaetaanko seuraajien ja mainittujen käyttäjien kanssa?", "confirmations.quiet_post_quote_info.dismiss": "Älä muistuta minua uudelleen", "confirmations.quiet_post_quote_info.got_it": "Selvä", "confirmations.quiet_post_quote_info.message": "Kun lainaat vaivihkaa julkisia julkaisuja, oma julkaisusi piilotetaan suosittujen julkaisujen aikajanoilta.", @@ -252,9 +294,12 @@ "confirmations.revoke_quote.confirm": "Poista julkaisu", "confirmations.revoke_quote.message": "Tätä toimea ei voi peruuttaa.", "confirmations.revoke_quote.title": "Poistetaanko julkaisu?", + "confirmations.unblock.confirm": "Kumoa esto", + "confirmations.unblock.title": "Kumotaanko käyttäjän {name} esto?", "confirmations.unfollow.confirm": "Lopeta seuraaminen", - "confirmations.unfollow.message": "Haluatko varmasti lopettaa profiilin {name} seuraamisen?", - "confirmations.unfollow.title": "Lopetetaanko käyttäjän seuraaminen?", + "confirmations.unfollow.title": "Lopetetaanko käyttäjän {name} seuraaminen?", + "confirmations.withdraw_request.confirm": "Peruuta pyyntö", + "confirmations.withdraw_request.title": "Peruutetaanko pyyntö seurata käyttäjää {name}?", "content_warning.hide": "Piilota julkaisu", "content_warning.show": "Näytä kuitenkin", "content_warning.show_more": "Näytä lisää", @@ -280,7 +325,7 @@ "domain_block_modal.they_cant_follow": "Kukaan tältä palvelimelta ei voi seurata sinua.", "domain_block_modal.they_wont_know": "Hän ei saa tietää tulleensa estetyksi.", "domain_block_modal.title": "Estetäänkö verkkotunnus?", - "domain_block_modal.you_will_lose_num_followers": "Menetät {followersCount, plural, one {{followersCountDisplay} seuraajasi} other {{followersCountDisplay} seuraajaasi}} ja {followingCount, plural, one {{followingCountDisplay} seurattavasi} other {{followingCountDisplay} seurattavaasi}}.", + "domain_block_modal.you_will_lose_num_followers": "Menetät {followersCount, plural, one {{followersCountDisplay}:n seuraajasi} other {{followersCountDisplay} seuraajaasi}} ja {followingCount, plural, one {{followingCountDisplay}:n seurattavasi} other {{followingCountDisplay} seurattavaasi}}.", "domain_block_modal.you_will_lose_relationships": "Menetät kaikki tämän palvelimen seuraajasi ja seurattavasi.", "domain_block_modal.you_wont_see_posts": "Et enää näe julkaisuja etkä ilmoituksia tämän palvelimen käyttäjiltä.", "domain_pill.activitypub_lets_connect": "Sen avulla voit muodostaa yhteyden ja olla vuorovaikutuksessa ihmisten kanssa, ei vain Mastodonissa vaan myös muissa sosiaalisissa sovelluksissa.", @@ -320,11 +365,12 @@ "empty_column.account_hides_collections": "Käyttäjä on päättänyt pitää nämä tiedot yksityisinä", "empty_column.account_suspended": "Tili jäädytetty", "empty_column.account_timeline": "Ei julkaisuja täällä!", - "empty_column.account_unavailable": "Profiilia ei ole saatavilla", + "empty_column.account_unavailable": "Profiili ei saatavilla", "empty_column.blocks": "Et ole vielä estänyt käyttäjiä.", "empty_column.bookmarked_statuses": "Et ole vielä lisännyt julkaisuja kirjanmerkkeihisi. Kun lisäät yhden, se näkyy tässä.", "empty_column.community": "Paikallinen aikajana on tyhjä. Kirjoita jotain julkista, niin homma lähtee käyntiin!", "empty_column.direct": "Yksityismainintoja ei vielä ole. Jos lähetät tai sinulle lähetetään sellaisia, näet ne täällä.", + "empty_column.disabled_feed": "Palvelimen ylläpito on poistanut käytöstä tämän syötteen.", "empty_column.domain_blocks": "Verkkotunnuksia ei ole vielä estetty.", "empty_column.explore_statuses": "Mikään ei ole nyt suosittua. Tarkista myöhemmin uudelleen!", "empty_column.favourited_statuses": "Sinulla ei ole vielä yhtään suosikkijulkaisua. Kun lisäät sellaisen, näkyy se tässä.", @@ -338,9 +384,10 @@ "empty_column.notification_requests": "Olet ajan tasalla! Täällä ei ole mitään uutta kerrottavaa. Kun saat uusia ilmoituksia, ne näkyvät täällä asetustesi mukaisesti.", "empty_column.notifications": "Sinulla ei ole vielä ilmoituksia. Kun muut ovat vuorovaikutuksessa kanssasi, näet sen täällä.", "empty_column.public": "Täällä ei ole mitään! Kirjoita jotain julkisesti tai seuraa muiden palvelinten käyttäjiä, niin saat sisältöä", + "error.no_hashtag_feed_access": "Liity tai kirjaudu sisään, niin voit tarkastella ja seurata tätä aihetunnistetta.", "error.unexpected_crash.explanation": "Sivua ei voida näyttää oikein ohjelmointivirheen tai selaimen yhteensopivuusvajeen vuoksi.", "error.unexpected_crash.explanation_addons": "Sivua ei voitu näyttää oikein. Tämä virhe johtuu todennäköisesti selaimen lisäosasta tai automaattisista käännöstyökaluista.", - "error.unexpected_crash.next_steps": "Kokeile päivittää sivu. Jos se ei auta, voi Mastodonin käyttö ehkä onnistua eri selaimella tai natiivisovelluksella.", + "error.unexpected_crash.next_steps": "Kokeile päivittää sivu. Jos se ei auta, Mastodonin käyttö voi ehkä onnistua eri selaimella tai natiivisovelluksella.", "error.unexpected_crash.next_steps_addons": "Yritä poistaa ne käytöstä, ja virkistä sitten sivunlataus. Mikäli ongelma jatkuu, voit mahdollisesti käyttää Mastodonia eri selaimella tai natiivilla sovelluksella.", "errors.unexpected_crash.copy_stacktrace": "Kopioi pinon jäljitys leikepöydälle", "errors.unexpected_crash.report_issue": "Ilmoita ongelmasta", @@ -349,11 +396,9 @@ "explore.trending_links": "Uutiset", "explore.trending_statuses": "Julkaisut", "explore.trending_tags": "Aihetunnisteet", + "featured_carousel.current": "Julkaisu {current, number} / {max, number}", "featured_carousel.header": "{count, plural, one {Kiinnitetty julkaisu} other {Kiinnitetyt julkaisut}}", - "featured_carousel.next": "Seuraava", - "featured_carousel.post": "Julkaisu", - "featured_carousel.previous": "Edellinen", - "featured_carousel.slide": "{index} / {total}", + "featured_carousel.slide": "Julkaisu {current, number} / {max, number}", "filter_modal.added.context_mismatch_explanation": "Tämä suodatinluokka ei koske asiayhteyttä, jossa olet tarkastellut tätä julkaisua. Jos haluat julkaisun suodatettavan myös tässä asiayhteydessä, muokkaa suodatinta.", "filter_modal.added.context_mismatch_title": "Asiayhteys ei täsmää!", "filter_modal.added.expired_explanation": "Tämä suodatinluokka on vanhentunut, joten sinun on muutettava viimeistä voimassaolopäivää, jotta suodatusta käytettäisiin.", @@ -396,6 +441,9 @@ "follow_suggestions.who_to_follow": "Ehdotuksia seurattavaksi", "followed_tags": "Seurattavat aihetunnisteet", "footer.about": "Tietoja", + "footer.about_mastodon": "Tietoja Mastodonista", + "footer.about_server": "Tietoja palvelimesta {domain}", + "footer.about_this_server": "Tietoja", "footer.directory": "Profiilihakemisto", "footer.get_app": "Hanki sovellus", "footer.keyboard_shortcuts": "Pikanäppäimet", @@ -417,15 +465,15 @@ "hashtag.column_settings.tag_mode.any": "Mikä tahansa näistä", "hashtag.column_settings.tag_mode.none": "Ei mitään näistä", "hashtag.column_settings.tag_toggle": "Sisällytä lisätunnisteet tähän sarakkeeseen", - "hashtag.counter_by_accounts": "{count, plural, one {{counter} osallistuja} other {{counter} osallistujaa}}", - "hashtag.counter_by_uses": "{count, plural, one{{counter} julkaisu} other {{counter} julkaisua}}", - "hashtag.counter_by_uses_today": "{count, plural, one {{counter} julkaisu} other {{counter} julkaisua}} tänään", + "hashtag.counter_by_accounts": "{count, plural, one {{counter} osallistuja} other {{counter} osallistujaa}}", + "hashtag.counter_by_uses": "{count, plural, one {{counter} julkaisu} other {{counter} julkaisua}}", + "hashtag.counter_by_uses_today": "{count, plural, one {{counter} julkaisu} other {{counter} julkaisua}} tänään", "hashtag.feature": "Suosittele profiilissa", "hashtag.follow": "Seuraa aihetunnistetta", "hashtag.mute": "Mykistä #{hashtag}", "hashtag.unfeature": "Kumoa suosittelu profiilissa", "hashtag.unfollow": "Lopeta aihetunnisteen seuraaminen", - "hashtags.and_other": "…ja {count, plural, other {# lisää}}", + "hashtags.and_other": "…ja {count, plural, other {# lisää}}", "hints.profiles.followers_may_be_missing": "Tämän profiilin seuraajia saattaa puuttua.", "hints.profiles.follows_may_be_missing": "Tämän profiilin seurattavia saattaa puuttua.", "hints.profiles.posts_may_be_missing": "Tämän profiilin julkaisuja saattaa puuttua.", @@ -460,9 +508,9 @@ "interaction_modal.on_this_server": "Tällä palvelimella", "interaction_modal.title": "Jatka kirjautumalla sisään", "interaction_modal.username_prompt": "Esim. {example}", - "intervals.full.days": "{number, plural, one {# päivä} other {# päivää}}", - "intervals.full.hours": "{number, plural, one {# tunti} other {# tuntia}}", - "intervals.full.minutes": "{number, plural, one {# minuutti} other {# minuuttia}}", + "intervals.full.days": "{number, plural, one {# päivä} other {# päivää}}", + "intervals.full.hours": "{number, plural, one {# tunti} other {# tuntia}}", + "intervals.full.minutes": "{number, plural, one {# minuutti} other {# minuuttia}}", "keyboard_shortcuts.back": "Siirry takaisin", "keyboard_shortcuts.blocked": "Avaa estettyjen käyttäjien luettelo", "keyboard_shortcuts.boost": "Tehosta julkaisua", @@ -479,7 +527,7 @@ "keyboard_shortcuts.home": "Avaa kotiaikajana", "keyboard_shortcuts.hotkey": "Pikanäppäin", "keyboard_shortcuts.legend": "Näytä tämä ohje", - "keyboard_shortcuts.load_more": "Kohdista ”Lataa lisää” -painikkeeseen", + "keyboard_shortcuts.load_more": "Kohdista ”Lataa lisää” -⁠painikkeeseen", "keyboard_shortcuts.local": "Avaa paikallinen aikajana", "keyboard_shortcuts.mention": "Mainitse tekijä", "keyboard_shortcuts.muted": "Avaa mykistettyjen käyttäjien luettelo", @@ -493,10 +541,11 @@ "keyboard_shortcuts.requests": "Avaa seurantapyyntöjen luettelo", "keyboard_shortcuts.search": "Kohdista hakukenttään", "keyboard_shortcuts.spoilers": "Näytä tai piilota sisältövaroituskenttä", - "keyboard_shortcuts.start": "Avaa Näin pääset alkuun -sarake", + "keyboard_shortcuts.start": "Avaa ”Näin pääset alkuun” -⁠sarake", "keyboard_shortcuts.toggle_hidden": "Näytä tai piilota sisältövaroituksella merkitty teksti", "keyboard_shortcuts.toggle_sensitivity": "Näytä tai piilota media", "keyboard_shortcuts.toot": "Luo uusi julkaisu", + "keyboard_shortcuts.top": "Siirry luettelon alkuun", "keyboard_shortcuts.translate": "Käännä julkaisu", "keyboard_shortcuts.unfocus": "Poistu kirjoitus- tai hakukentästä", "keyboard_shortcuts.up": "Siirry luettelossa taaksepäin", @@ -511,7 +560,7 @@ "limited_account_hint.title": "Palvelimen {domain} moderaattorit ovat piilottaneet tämän profiilin.", "link_preview.author": "Tehnyt {name}", "link_preview.more_from_author": "Lisää tekijältä {name}", - "link_preview.shares": "{count, plural, one {{counter} julkaisu} other {{counter} julkaisua}}", + "link_preview.shares": "{count, plural, one {{counter} julkaisu} other {{counter} julkaisua}}", "lists.add_member": "Lisää", "lists.add_to_list": "Lisää listaan", "lists.add_to_lists": "Lisää {name} listaan", @@ -524,7 +573,7 @@ "lists.exclusive": "Piilota jäsenet kotisyötteestä", "lists.exclusive_hint": "Jos joku on tässä listassa, piilota hänet kotisyötteestäsi, jotta et näe hänen julkaisujaan kahteen kertaan.", "lists.find_users_to_add": "Etsi lisättäviä käyttäjiä", - "lists.list_members_count": "{count, plural, one {# jäsen} other {# jäsentä}}", + "lists.list_members_count": "{count, plural, one {# jäsen} other {# jäsentä}}", "lists.list_name": "Listan nimi", "lists.new_list_name": "Uuden listan nimi", "lists.no_lists_yet": "Ei vielä listoja.", @@ -537,7 +586,7 @@ "lists.save": "Tallenna", "lists.search": "Haku", "lists.show_replies_to": "Sisällytä listan jäsenten vastaukset kohteeseen", - "load_pending": "{count, plural, one {# uusi kohde} other {# uutta kohdetta}}", + "load_pending": "{count, plural, one {# uusi kohde} other {# uutta kohdetta}}", "loading_indicator.label": "Ladataan…", "media_gallery.hide": "Piilota", "moved_to_account_banner.text": "Tilisi {disabledAccount} on tällä hetkellä poissa käytöstä, koska teit siirron tiliin {movedToAccount}.", @@ -596,7 +645,7 @@ "notification.favourite_pm": "{name} lisäsi yksityismainintasi suosikkeihinsa", "notification.favourite_pm.name_and_others_with_link": "{name} ja {count, plural, one {# muu} other {# muuta}} lisäsivät yksityismainintasi suosikkeihinsa", "notification.follow": "{name} seurasi sinua", - "notification.follow.name_and_others": "{name} ja {count, plural, one {# muu} other {# muuta}} seurasivat sinua", + "notification.follow.name_and_others": "{name} ja {count, plural, one {# muu} other {# muuta}} seurasivat sinua", "notification.follow_request": "{name} on pyytänyt lupaa seurata sinua", "notification.follow_request.name_and_others": "{name} ja {count, plural, one {# muu} other {# muuta}} pyysivät saada seurata sinua", "notification.label.mention": "Maininta", @@ -619,7 +668,7 @@ "notification.poll": "Äänestys, johon osallistuit, on päättynyt", "notification.quoted_update": "{name} muokkasi lainaamaasi julkaisua", "notification.reblog": "{name} tehosti julkaisuasi", - "notification.reblog.name_and_others_with_link": "{name} ja {count, plural, one {# muu} other {# muuta}} tehostivat julkaisuasi", + "notification.reblog.name_and_others_with_link": "{name} ja {count, plural, one {# muu} other {# muuta}} tehostivat julkaisuasi", "notification.relationships_severance_event": "Menetettiin yhteydet palvelimeen {name}", "notification.relationships_severance_event.account_suspension": "Palvelimen {from} ylläpitäjä on jäädyttänyt palvelimen {target} vuorovaikutuksen. Enää et voi siis vastaanottaa päivityksiä heiltä tai olla yhteyksissä heidän kanssaan.", "notification.relationships_severance_event.domain_block": "Palvelimen {from} ylläpitäjä on estänyt palvelimen {target} vuorovaikutuksen – mukaan lukien {followersCount} seuraajistasi ja {followingCount, plural, one {# seurattavistasi} other {# seurattavistasi}}.", @@ -628,7 +677,7 @@ "notification.status": "{name} julkaisi juuri", "notification.update": "{name} muokkasi julkaisua", "notification_requests.accept": "Hyväksy", - "notification_requests.accept_multiple": "{count, plural, one {Hyväksy # pyyntö…} other {Hyväksy # pyyntöä…}}", + "notification_requests.accept_multiple": "{count, plural, one {Hyväksy # pyyntö…} other {Hyväksy # pyyntöä…}}", "notification_requests.confirm_accept_multiple.button": "{count, plural, one {Hyväksy pyyntö} other {Hyväksy pyynnöt}}", "notification_requests.confirm_accept_multiple.message": "Olet aikeissa hyväksyä {count, plural, one {ilmoituspyynnön} other {# ilmoituspyyntöä}}. Haluatko varmasti jatkaa?", "notification_requests.confirm_accept_multiple.title": "Hyväksytäänkö ilmoituspyynnöt?", @@ -692,7 +741,7 @@ "notifications.policy.filter_limited_accounts_title": "Moderoidut tilit", "notifications.policy.filter_new_accounts.hint": "Luotu {days, plural, one {viime päivän} other {viimeisen # päivän}} aikana", "notifications.policy.filter_new_accounts_title": "Uudet tilit", - "notifications.policy.filter_not_followers_hint": "Mukaan lukien alle {days, plural, one {päivän} other {# päivää}} sinua seuranneet", + "notifications.policy.filter_not_followers_hint": "Mukaan lukien alle {days, plural, one {päivän} other {# päivää}} sinua seuranneet", "notifications.policy.filter_not_followers_title": "Käyttäjät, jotka eivät seuraa sinua", "notifications.policy.filter_not_following_hint": "Kunnes hyväksyt heidät manuaalisesti", "notifications.policy.filter_not_following_title": "Käyttäjät, joita et seuraa", @@ -723,11 +772,11 @@ "poll.closed": "Päättynyt", "poll.refresh": "Päivitä", "poll.reveal": "Näytä tulokset", - "poll.total_people": "{count, plural, one {# käyttäjä} other {# käyttäjää}}", - "poll.total_votes": "{count, plural, one {# ääni} other {# ääntä}}", + "poll.total_people": "{count, plural, one {# käyttäjä} other {# käyttäjää}}", + "poll.total_votes": "{count, plural, one {# ääni} other {# ääntä}}", "poll.vote": "Äänestä", "poll.voted": "Äänestit tätä vastausta", - "poll.votes": "{votes, plural, one {# ääni} other {# ääntä}}", + "poll.votes": "{votes, plural, one {# ääni} other {# ääntä}}", "poll_button.add_poll": "Lisää äänestys", "poll_button.remove_poll": "Poista äänestys", "privacy.change": "Muuta julkaisun näkyvyyttä", @@ -737,7 +786,7 @@ "privacy.private.short": "Seuraajat", "privacy.public.long": "Kuka tahansa Mastodonissa ja sen ulkopuolella", "privacy.public.short": "Julkinen", - "privacy.quote.anyone": "{visibility}, kuka vain voi lainata", + "privacy.quote.anyone": "{visibility}, kuka tahansa voi lainata", "privacy.quote.disabled": "{visibility}, lainaukset poissa käytöstä", "privacy.quote.limited": "{visibility}, lainauksia rajoitettu", "privacy.unlisted.additional": "Tämä toimii muuten kuin julkinen, mutta julkaisut eivät näy livesyöte-, aihetunniste- tai selausnäkymissä eivätkä Mastodonin hakutuloksissa, vaikka ne olisivat käyttäjätililläsi yleisesti sallittuina.", @@ -745,7 +794,9 @@ "privacy.unlisted.short": "Vaivihkaa julkinen", "privacy_policy.last_updated": "Päivitetty viimeksi {date}", "privacy_policy.title": "Tietosuojakäytäntö", - "quote_error.poll": "Äänestysten lainaaminen ei ole sallittua.", + "quote_error.edit": "Lainauksia ei voi lisätä julkaisua muokattaessa.", + "quote_error.poll": "Lainaaminen äänestyksen ohessa ei ole sallittua.", + "quote_error.private_mentions": "Lainaaminen ei ole sallittua yksityismaininnoissa.", "quote_error.quote": "Vain yksi lainaus kerrallaan on sallittu.", "quote_error.unauthorized": "Sinulla ei ole valtuuksia lainata tätä julkaisua.", "quote_error.upload": "Medialiitteiden lainaaminen ei ole sallittua.", @@ -753,21 +804,21 @@ "refresh": "Päivitä", "regeneration_indicator.please_stand_by": "Ole valmiina.", "regeneration_indicator.preparing_your_home_feed": "Kotisyötettäsi valmistellaan…", - "relative_time.days": "{number} pv", - "relative_time.full.days": "{number, plural, one {# päivä} other {# päivää}} sitten", - "relative_time.full.hours": "{number, plural, one {# tunti} other {# tuntia}} sitten", + "relative_time.days": "{number} pv", + "relative_time.full.days": "{number, plural, one {# päivä} other {# päivää}} sitten", + "relative_time.full.hours": "{number, plural, one {# tunti} other {# tuntia}} sitten", "relative_time.full.just_now": "juuri nyt", - "relative_time.full.minutes": "{number, plural, one {# minuutti} other {# minuuttia}} sitten", - "relative_time.full.seconds": "{number, plural, one {# sekunti} other {# sekuntia}} sitten", - "relative_time.hours": "{number} t", + "relative_time.full.minutes": "{number, plural, one {# minuutti} other {# minuuttia}} sitten", + "relative_time.full.seconds": "{number, plural, one {# sekunti} other {# sekuntia}} sitten", + "relative_time.hours": "{number} t", "relative_time.just_now": "nyt", - "relative_time.minutes": "{number} min", - "relative_time.seconds": "{number} s", + "relative_time.minutes": "{number} min", + "relative_time.seconds": "{number} s", "relative_time.today": "tänään", "remove_quote_hint.button_label": "Selvä", "remove_quote_hint.message": "Voit tehdä sen {icon}-valikosta.", "remove_quote_hint.title": "Haluatko poistaa lainatun julkaisusi?", - "reply_indicator.attachments": "{count, plural, one {# liite} other {# liitettä}}", + "reply_indicator.attachments": "{count, plural, one {# liite} other {# liitettä}}", "reply_indicator.cancel": "Peruuta", "reply_indicator.poll": "Äänestys", "report.block": "Estä", @@ -810,7 +861,7 @@ "report.thanks.title_actionable": "Kiitos raportista – tutkimme asiaa.", "report.unfollow": "Lopeta käyttäjän @{name} seuraaminen", "report.unfollow_explanation": "Seuraat tätä tiliä. Jotta et enää näkisi sen julkaisuja kotisyötteessäsi, lopeta tilin seuraaminen.", - "report_notification.attached_statuses": "{count, plural, one {{count} julkaisu} other {{count} julkaisua}} liitteenä", + "report_notification.attached_statuses": "{count, plural, one {{count} julkaisu} other {{count} julkaisua}} liitteenä", "report_notification.categories.legal": "Lakiseikat", "report_notification.categories.legal_sentence": "laiton sisältö", "report_notification.categories.other": "Muu", @@ -826,11 +877,11 @@ "search.quick_action.account_search": "Profiilit haulla {x}", "search.quick_action.go_to_account": "Siirry profiiliin {x}", "search.quick_action.go_to_hashtag": "Siirry aihetunnisteeseen {x}", - "search.quick_action.open_url": "Avaa URL-osoite Mastodonissa", + "search.quick_action.open_url": "Avaa URL-⁠osoite Mastodonissa", "search.quick_action.status_search": "Julkaisut haulla {x}", - "search.search_or_paste": "Hae tai liitä URL-osoite", + "search.search_or_paste": "Hae tai liitä URL-⁠osoite", "search_popout.full_text_search_disabled_message": "Ei saatavilla palvelimella {domain}.", - "search_popout.full_text_search_logged_out_message": "Käytettävissä vain sisäänkirjautuneena.", + "search_popout.full_text_search_logged_out_message": "Saatavilla vain sisäänkirjautuneena.", "search_popout.language_code": "ISO-kielikoodi", "search_popout.options": "Hakuvalinnat", "search_popout.quick_actions": "Pikatoiminnot", @@ -865,8 +916,12 @@ "status.cannot_quote": "Sinulla ei ole oikeutta lainata tätä julkaisua", "status.cannot_reblog": "Tätä julkaisua ei voi tehostaa", "status.contains_quote": "Sisältää lainauksen", - "status.context.load_new_replies": "Uusia vastauksia saatavilla", - "status.context.loading": "Tarkistetaan lisävastauksia", + "status.context.loading": "Ladataan lisää vastauksia", + "status.context.loading_error": "Ei voitu ladata lisää vastauksia", + "status.context.loading_success": "Uudet vastaukset ladattu", + "status.context.more_replies_found": "Löytyi lisää vastauksia", + "status.context.retry": "Yritä uudelleen", + "status.context.show": "Näytä", "status.continued_thread": "Jatkoi ketjua", "status.copy": "Kopioi linkki julkaisuun", "status.delete": "Poista", @@ -876,10 +931,10 @@ "status.direct_indicator": "Yksityismaininta", "status.edit": "Muokkaa", "status.edited": "Viimeksi muokattu {date}", - "status.edited_x_times": "Muokattu {count, plural, one {{count} kerran} other {{count} kertaa}}", + "status.edited_x_times": "Muokattu {count, plural, one {{count} kerran} other {{count} kertaa}}", "status.embed": "Hanki upotuskoodi", "status.favourite": "Suosikki", - "status.favourites": "{count, plural, one {suosikki} other {suosikkia}}", + "status.favourites_count": "{count, plural, one {{counter} suosikki} other {{counter} suosikkia}}", "status.filter": "Suodata tämä julkaisu", "status.history.created": "{name} loi {date}", "status.history.edited": "{name} muokkasi {date}", @@ -895,9 +950,12 @@ "status.pin": "Kiinnitä profiiliin", "status.quote": "Lainaa", "status.quote.cancel": "Peruuta lainaus", + "status.quote_error.blocked_account_hint.title": "Tämä julkaisu on piilotettu, koska olet estänyt käyttäjän @{name}.", + "status.quote_error.blocked_domain_hint.title": "Tämä julkaisu on piilotettu, koska olet estänyt verkkotunnuksen {domain}.", "status.quote_error.filtered": "Piilotettu jonkin asettamasi suodattimen takia", "status.quote_error.limited_account_hint.action": "Näytä kuitenkin", "status.quote_error.limited_account_hint.title": "Palvelimen {domain} moderaattorit ovat piilottaneet tämän profiilin.", + "status.quote_error.muted_account_hint.title": "Tämä julkaisu on piilotettu, koska olet mykistänyt käyttäjän @{name}.", "status.quote_error.not_available": "Julkaisu ei saatavilla", "status.quote_error.pending_approval": "Julkaisu odottaa", "status.quote_error.pending_approval_popout.body": "Mastodonissa voit hallita, voiko joku lainata sinua. Tämä julkaisu on vireillä siihen asti, että saamme alkuperäisen tekijän hyväksynnän.", @@ -908,15 +966,17 @@ "status.quote_policy_change": "Vaihda, kuka voi lainata", "status.quote_post_author": "Lainaa käyttäjän @{name} julkaisua", "status.quote_private": "Yksityisiä julkaisuja ei voi lainata", - "status.quotes": "{count, plural, one {lainaus} other {lainausta}}", "status.quotes.empty": "Kukaan ei ole vielä lainannut tätä julkaisua. Kun joku tekee niin, se tulee tähän näkyviin.", + "status.quotes.local_other_disclaimer": "Tekijän hylkäämiä lainauksia ei näytetä.", + "status.quotes.remote_other_disclaimer": "Vain palvelimen {domain} lainaukset näkyvät taatusti tässä. Tekijän hylkäämiä lainauksia ei näytetä.", + "status.quotes_count": "{count, plural, one {{counter} lainaus} other {{counter} lainausta}}", "status.read_more": "Näytä enemmän", "status.reblog": "Tehosta", "status.reblog_or_quote": "Tehosta tai lainaa", "status.reblog_private": "Jaa uudelleen seuraajiesi kanssa", "status.reblogged_by": "{name} tehosti", - "status.reblogs": "{count, plural, one {tehostus} other {tehostusta}}", "status.reblogs.empty": "Kukaan ei ole vielä tehostanut tätä julkaisua. Kun joku tekee niin, tulee hän tähän näkyviin.", + "status.reblogs_count": "{count, plural, one {{counter} tehostus} other {{counter} tehostusta}}", "status.redraft": "Poista ja palauta muokattavaksi", "status.remove_bookmark": "Poista kirjanmerkki", "status.remove_favourite": "Poista suosikeista", @@ -933,10 +993,10 @@ "status.show_less_all": "Näytä kaikista vähemmän", "status.show_more_all": "Näytä kaikista enemmän", "status.show_original": "Näytä alkuperäinen", - "status.title.with_attachments": "{user} liitti {attachmentCount, plural, one {{attachmentCount} tiedoston} other {{attachmentCount} tiedostoa}}", + "status.title.with_attachments": "{user} julkaisi {attachmentCount, plural, one {liitteen} other {{attachmentCount} liitettä}}", "status.translate": "Käännä", - "status.translated_from_with": "Käännetty kielestä {lang} käyttäen palvelua {provider}", - "status.uncached_media_warning": "Esikatselu ei ole käytettävissä", + "status.translated_from_with": "Käännetty kielestä {lang} palvelulla {provider}", + "status.uncached_media_warning": "Esikatselu ei saatavilla", "status.unmute_conversation": "Kumoa keskustelun mykistys", "status.unpin": "Irrota profiilista", "subscribed_languages.lead": "Vain valituilla kielillä kirjoitetut julkaisut näkyvät koti- ja lista-aikajanoillasi muutoksen jälkeen. Älä valitse mitään, jos haluat nähdä julkaisuja kaikilla kielillä.", @@ -950,17 +1010,17 @@ "terms_of_service.effective_as_of": "Tulee voimaan {date}", "terms_of_service.title": "Käyttöehdot", "terms_of_service.upcoming_changes_on": "Tulevia muutoksia {date}", - "time_remaining.days": "{number, plural, one {# päivä} other {# päivää}} jäljellä", - "time_remaining.hours": "{number, plural, one {# tunti} other {# tuntia}} jäljellä", - "time_remaining.minutes": "{number, plural, one {# minuutti} other {# minuuttia}} jäljellä", + "time_remaining.days": "{number, plural, one {# päivä} other {# päivää}} jäljellä", + "time_remaining.hours": "{number, plural, one {# tunti} other {# tuntia}} jäljellä", + "time_remaining.minutes": "{number, plural, one {# minuutti} other {# minuuttia}} jäljellä", "time_remaining.moments": "Hetkiä jäljellä", - "time_remaining.seconds": "{number, plural, one {# sekunti} other {# sekuntia}} jäljellä", + "time_remaining.seconds": "{number, plural, one {# sekunti} other {# sekuntia}} jäljellä", "trends.counter_by_accounts": "{count, plural, one {{counter} käyttäjä} other {{counter} käyttäjää}} {days, plural, one {viime päivänä} other {viimeisenä {days} päivänä}}", "trends.trending_now": "Suosittua nyt", "ui.beforeunload": "Luonnos häviää, jos poistut Mastodonista.", - "units.short.billion": "{count} mrd.", - "units.short.million": "{count} milj.", - "units.short.thousand": "{count} t.", + "units.short.billion": "{count} mrd.", + "units.short.million": "{count} milj.", + "units.short.thousand": "{count} t.", "upload_area.title": "Lähetä raahaamalla ja pudottamalla tähän", "upload_button.label": "Lisää kuvia, video tai äänitiedosto", "upload_error.limit": "Tiedostolähetysten rajoitus ylitetty.", @@ -990,6 +1050,8 @@ "video.volume_down": "Vähennä äänenvoimakkuutta", "video.volume_up": "Lisää äänenvoimakkuutta", "visibility_modal.button_title": "Aseta näkyvyys", + "visibility_modal.direct_quote_warning.text": "Jos tallennat nykyiset asetukset, upotettu lainaus muunnetaan linkiksi.", + "visibility_modal.direct_quote_warning.title": "Lainauksia ei voi upottaa yksityismainintoihin", "visibility_modal.header": "Näkyvyys ja vuorovaikutus", "visibility_modal.helper.direct_quoting": "Muut eivät voi lainata Mastodonissa kirjoitettuja yksityismainintoja.", "visibility_modal.helper.privacy_editing": "Näkyvyyttä ei voi muuttaa julkaisun jälkeen.", diff --git a/app/javascript/mastodon/locales/fil.json b/app/javascript/mastodon/locales/fil.json index f8425dd8efd185..82c1616ddf2f7f 100644 --- a/app/javascript/mastodon/locales/fil.json +++ b/app/javascript/mastodon/locales/fil.json @@ -1,6 +1,7 @@ { "about.blocks": "Mga pinatimping server", "about.contact": "Kontak:", + "about.default_locale": "Default", "about.disclaimer": "Ang Mastodon ay software na malaya at bukas-na-pinagmulan, at isang tatak-pangkalakal ng Mastodon gGmbH.", "about.domain_blocks.no_reason_available": "Hindi makuha ang dahilan", "about.domain_blocks.preamble": "Sa kadalasan, hinahayaan ka ng Mastodon na makita ang mga content sa, at makipag-interact sa users ng, ibang servers sa fediverse. Narito ang exceptions na ginawa sa partikular na server na ito.", @@ -8,6 +9,7 @@ "about.domain_blocks.silenced.title": "Limitado", "about.domain_blocks.suspended.explanation": "Walang data mula sa server na ito ang mapoproseso, maiimbak o maipagpapaplitan. Sa gayon. imposibleng magawa ang interaksiyon o komunikasyon sa ibang users sa server na ito.", "about.domain_blocks.suspended.title": "Suspendido", + "about.language_label": "Wika", "about.not_available": "Hindi available ang impormasyong ito.", "about.powered_by": "Decentralisadong social media na pinapagana ng {mastodon}", "about.rules": "Mga alituntunin ng server", @@ -19,21 +21,31 @@ "account.block_domain": "Hadlangan ang domain na {domain}", "account.block_short": "Hadlangan", "account.blocked": "Hinadlangan", + "account.blocking": "Pagharang", "account.cancel_follow_request": "I-kansela ang pagsunod", "account.copy": "I-sipi ang kawing sa profile", "account.direct": "Palihim banggitin si @{name}", "account.disable_notifications": "I-tigil ang pagpapaalam sa akin tuwing nagpopost si @{name}", + "account.domain_blocking": "Pag-block ng domain", "account.edit_profile": "Baguhin ang profile", + "account.edit_profile_short": "I-edit", "account.enable_notifications": "Ipaalam sa akin kapag nag-post si @{name}", "account.endorse": "I-tampok sa profile", "account.familiar_followers_many": "Sinusundan nina {name1}, {name2}, at {othersCount, plural, one {# iba pa na kilala mo} other {# na iba pa na kilala mo}}", "account.familiar_followers_one": "Sinusindan ni/ng {name1}", "account.familiar_followers_two": "Sinusindan nina {name1} at {name2}", "account.featured": "Itinatampok", + "account.featured.accounts": "Mga Profile", + "account.featured.hashtags": "Mga Hashtag", "account.featured_tags.last_status_at": "Huling post noong {date}", "account.featured_tags.last_status_never": "Walang mga post", "account.follow": "Sundan", "account.follow_back": "Sundan pabalik", + "account.follow_back_short": "I-follow back", + "account.follow_request": "Humiling na mag-follow", + "account.follow_request_cancel": "I-cancel ang request", + "account.follow_request_cancel_short": "Cancel", + "account.follow_request_short": "Request", "account.followers": "Mga tagasunod", "account.followers.empty": "Wala pang sumusunod sa tagagamit na ito.", "account.followers_counter": "{count, plural, one {{counter} tagasunod} other {{counter} tagasunod}}", @@ -56,16 +68,29 @@ "account.mute_notifications_short": "I-mute ang mga abiso", "account.mute_short": "I-mute", "account.muted": "Naka-mute", + "account.muting": "Pag-mute", + "account.mutual": "Pina-follow nyo ang isa't-isa", "account.no_bio": "Walang nakalaan na paglalarawan.", "account.open_original_page": "Buksan ang pinagmulang pahina", "account.posts": "Mga post", + "account.posts_with_replies": "Mga Post at Reply", + "account.remove_from_followers": "Alisin si {name} sa mga follower", "account.report": "I-ulat si/ang @{name}", - "account.requested": "Naghihintay ng pag-apruba. I-click upang ikansela ang hiling sa pagsunod", "account.requested_follow": "Hinihiling ni {name} na sundan ka", + "account.requests_to_follow_you": "Mga Request para i-fillow ka", "account.share": "Ibahagi ang profile ni @{name}", "account.show_reblogs": "Ipakita ang mga pagpapalakas mula sa/kay {name}", + "account.statuses_counter": "{count,plural,one {{counter} i-post} other {{counter} mga post}}", + "account.unblock": "I-unblock si @{name}", + "account.unblock_domain": "I-unblock ang domain {domain}", + "account.unblock_domain_short": "I-unblock", + "account.unblock_short": "I-unblock", "account.unendorse": "Huwag itampok sa profile", "account.unfollow": "Huwag nang sundan", + "account.unmute": "I-unmute si @{name}", + "account.unmute_notifications_short": "I-unmute ang mga notification", + "account.unmute_short": "I-unmute", + "account_note.placeholder": "I-click para magdagdag ng note", "admin.dashboard.retention.cohort_size": "Mga bagong tagagamit", "alert.rate_limited.message": "Mangyaring subukan muli pagkatapos ng {retry_time, time, medium}.", "audio.hide": "Itago ang tunog", diff --git a/app/javascript/mastodon/locales/fo.json b/app/javascript/mastodon/locales/fo.json index a8db9baeca0078..e87ba3cead3e55 100644 --- a/app/javascript/mastodon/locales/fo.json +++ b/app/javascript/mastodon/locales/fo.json @@ -19,7 +19,7 @@ "account.badges.group": "Bólkur", "account.block": "Banna @{name}", "account.block_domain": "Banna økisnavnið {domain}", - "account.block_short": "Blokera", + "account.block_short": "Banna", "account.blocked": "Bannað/ur", "account.blocking": "Banni", "account.cancel_follow_request": "Strika fylgjaraumbøn", @@ -28,6 +28,7 @@ "account.disable_notifications": "Ikki boða mær frá, tá @{name} skrivar", "account.domain_blocking": "Banni økisnavn", "account.edit_profile": "Broyt vanga", + "account.edit_profile_short": "Rætta", "account.enable_notifications": "Boða mær frá, tá @{name} skrivar", "account.endorse": "Víst á vangamyndini", "account.familiar_followers_many": "{name1}, {name2} og {othersCount, plural, one {ein annar/onnur tú kennir} other {# onnur tú kennir}} fylgja", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Einki uppslag", "account.follow": "Fylg", "account.follow_back": "Fylg aftur", + "account.follow_back_short": "Fylg aftur", + "account.follow_request": "Umbønir um at fylgja tær", + "account.follow_request_cancel": "Strika víðaribeining", + "account.follow_request_cancel_short": "Ógilda", + "account.follow_request_short": "Áheitan", "account.followers": "Fylgjarar", "account.followers.empty": "Ongar fylgjarar enn.", "account.followers_counter": "{count, plural, one {{counter} fylgjari} other {{counter} fylgjarar}}", @@ -49,7 +55,7 @@ "account.follows.empty": "Hesin brúkari fylgir ongum enn.", "account.follows_you": "Fylgir tær", "account.go_to_profile": "Far til vanga", - "account.hide_reblogs": "Fjal lyft frá @{name}", + "account.hide_reblogs": "Fjal stimbran frá @{name}", "account.in_memoriam": "In memoriam.", "account.joined_short": "Gjørdist limur", "account.languages": "Broyt fylgd mál", @@ -70,11 +76,10 @@ "account.posts_with_replies": "Uppsløg og svar", "account.remove_from_followers": "Strika {name} av fylgjaralista", "account.report": "Melda @{name}", - "account.requested": "Bíðar eftir góðkenning. Trýst fyri at angra umbønina", "account.requested_follow": "{name} hevur biðið um at fylgja tær", "account.requests_to_follow_you": "Umbønir um at fylgja tær", "account.share": "Deil vanga @{name}'s", - "account.show_reblogs": "Vís lyft frá @{name}", + "account.show_reblogs": "Vís stimbran frá @{name}", "account.statuses_counter": "{count, plural, one {{counter} postur} other {{counter} postar}}", "account.unblock": "Banna ikki @{name}", "account.unblock_domain": "Banna ikki økisnavnið {domain}", @@ -108,25 +113,52 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Lýs hetta fyri fólk við tey, ið hava avmarkaða sjón…", "alt_text_modal.done": "Liðugt", "announcement.announcement": "Kunngerð", - "annual_report.summary.archetype.booster": "Kuli jagarin", - "annual_report.summary.archetype.lurker": "Lúrarin", - "annual_report.summary.archetype.oracle": "Oraklið", - "annual_report.summary.archetype.pollster": "Spyrjarin", - "annual_report.summary.archetype.replier": "Sosiali firvaldurin", - "annual_report.summary.followers.followers": "fylgjarar", - "annual_report.summary.followers.total": "{count} íalt", - "annual_report.summary.here_it_is": "Her er ein samandráttur av {year}:", - "annual_report.summary.highlighted_post.by_favourites": "mest dámdi postur", - "annual_report.summary.highlighted_post.by_reblogs": "oftast lyfti postur", - "annual_report.summary.highlighted_post.by_replies": "postur við flestum svarum", - "annual_report.summary.highlighted_post.possessive": "hjá {name}", + "annual_report.announcement.action_build": "Ger mítt Wrapstodon", + "annual_report.announcement.action_dismiss": "Nei takk", + "annual_report.announcement.action_view": "Vís mítt Wrapstodon", + "annual_report.announcement.description": "Fá meira at vita um títt virksemi á Mastodon seinasta árið.", + "annual_report.announcement.title": "Wrapstodon {year} er komið", + "annual_report.nav_item.badge": "Nýtt", + "annual_report.shared_page.donate": "Stuðla", + "annual_report.shared_page.footer": "Gjørt við {heart} av Mastodon toyminum", + "annual_report.shared_page.footer_server_info": "{username} brúkar {domain}, ein av fleiri felagsskapum, sum er drivin av Mastodon.", + "annual_report.summary.archetype.booster.desc_public": "{name} helt fram at veiða postar at lyfta, og styrkti aðrar skaparar við perfektum sikti.", + "annual_report.summary.archetype.booster.desc_self": "Tú helt fram at veiða postar at lyfta, og styrkti aðrar skaparar við perfektum sikti.", + "annual_report.summary.archetype.booster.name": "Bogaskjúttin", + "annual_report.summary.archetype.die_drei_fragezeichen": "???", + "annual_report.summary.archetype.lurker.desc_public": "Vit vita, at {name} var úti har onkustaðni og neyt Mastodon uppá sín egna stilla máta.", + "annual_report.summary.archetype.lurker.desc_self": "Vit vita, at tú var úti har onkustaðni og neyt Mastodon uppá tín egna stilla máta.", + "annual_report.summary.archetype.lurker.name": "Stóikarin", + "annual_report.summary.archetype.oracle.desc_public": "{name} gjørdi fleiri nýggjar postar enn svar og helt Mastodon feskt og framtíðarrættað.", + "annual_report.summary.archetype.oracle.desc_self": "Tú gjørdi fleiri nýggjar postar enn svar og helt Mastodon feskt og framtíðarrættað.", + "annual_report.summary.archetype.oracle.name": "Oraklið", + "annual_report.summary.archetype.pollster.desc_public": "{name} stovnaði fleiri atkvøðugreiðslur enn onnur sløg av postum og dyrkaði forvitni á Mastodon.", + "annual_report.summary.archetype.pollster.desc_self": "Tú stovnaði fleiri atkvøðugreiðslur enn onnur sløg av postum og dyrkaði forvitni á Mastodon.", + "annual_report.summary.archetype.pollster.name": "Undrandi", + "annual_report.summary.archetype.replier.desc_public": "{name} svaraði ofta til postar hjá øðrum og ríkaði Mastodon við nýggjum kjaki.", + "annual_report.summary.archetype.replier.desc_self": "Tú svaraði ofta til postar hjá øðrum og ríkaði Mastodon við nýggjum kjaki.", + "annual_report.summary.archetype.replier.name": "Firvaldurin", + "annual_report.summary.archetype.reveal": "Avdúka mítt frumsnið", + "annual_report.summary.archetype.reveal_description": "Takk fyri at tú er partur av Mastodon! Nú er tíðin komin at finna útav hvat frumsnið tú fall inn í í {year}.", + "annual_report.summary.archetype.title_public": "Frumsniðið hjá {name}", + "annual_report.summary.archetype.title_self": "Títt frumsnið", + "annual_report.summary.close": "Lat aftur", + "annual_report.summary.copy_link": "Avrita leinki", + "annual_report.summary.followers.new_followers": "{count, plural, one {{counter} nýggjur fylgjari} other {{counter} nýggir fylgjarar}}", + "annual_report.summary.highlighted_post.boost_count": "Hesin posturin var lyftur {count, plural, one {eina ferð} other {# ferðir}}.", + "annual_report.summary.highlighted_post.favourite_count": "Hesin posturin var yndismerktur {count, plural, one {eina ferð} other {# ferðir}}.", + "annual_report.summary.highlighted_post.reply_count": "Hesin posturin fekk {count, plural, one {eitt svar} other {# svar}}.", + "annual_report.summary.highlighted_post.title": "Best umtókti postur", "annual_report.summary.most_used_app.most_used_app": "mest brúkta app", "annual_report.summary.most_used_hashtag.most_used_hashtag": "mest brúkta frámerki", - "annual_report.summary.most_used_hashtag.none": "Einki", + "annual_report.summary.most_used_hashtag.used_count": "Tú brúkti hetta frámerkið í {count, plural, one {einum posti} other {# postum}}.", + "annual_report.summary.most_used_hashtag.used_count_public": "{name} brúkti hetta frámerkið í {count, plural, one {einum posti} other {# postum}}.", "annual_report.summary.new_posts.new_posts": "nýggir postar", "annual_report.summary.percentile.text": "Tað fær teg í toppav {domain} brúkarum.", "annual_report.summary.percentile.we_wont_tell_bernie": "Vit fara ikki at fortelja Bernie tað.", - "annual_report.summary.thanks": "Takk fyri at tú er partur av Mastodon!", + "annual_report.summary.share_elsewhere": "Deil aðrastaðni", + "annual_report.summary.share_message": "Eg fekk {archetype} frumsniðið!", + "annual_report.summary.share_on_mastodon": "Deil á Mastodon", "attachments_list.unprocessed": "(óviðgjørt)", "audio.hide": "Fjal ljóð", "block_modal.remote_users_caveat": "Vit biðja ambætaran {domain} virða tína avgerð. Kortini er eingin vissa um samsvar, av tí at fleiri ambætarar handfara blokkar ymiskt. Almennir postar kunnu framvegis vera sjónligir fyri brúkarar, sum ikki eru innritaðir.", @@ -152,6 +184,8 @@ "bundle_modal_error.close": "Lat aftur", "bundle_modal_error.message": "Okkurt gekk galið, tá hendan síðan bleiv innlisin.", "bundle_modal_error.retry": "Royn umaftur", + "carousel.current": "Glæra {current, number} / {max, number}", + "carousel.slide": "Glæra {current, number} av {max, number}", "closed_registrations.other_server_instructions": "Av tí at Mastodon er desentraliserað, kanst tú stovna eina kontu á einum øðrum ambætara og framvegis virka saman við hesum ambætaranum.", "closed_registrations_modal.description": "Tað er ikki møguligt at stovna sær eina kontu á {domain} í løtuni, men vinarliga hav í huga at tær nýtist ikki eina kontu á júst {domain} fyri at brúka Mastodon.", "closed_registrations_modal.find_another_server": "Finn ein annan ambætara", @@ -168,6 +202,8 @@ "column.edit_list": "Broyt lista", "column.favourites": "Dámdir postar", "column.firehose": "Beinleiðis rásir", + "column.firehose_local": "Beinleiðis rás hjá hesum ambætaranum", + "column.firehose_singular": "Beinleiðis rás", "column.follow_requests": "Umbønir at fylgja", "column.home": "Heim", "column.list_members": "Rætta limalista", @@ -187,6 +223,7 @@ "community.column_settings.local_only": "Einans lokalt", "community.column_settings.media_only": "Einans miðlar", "community.column_settings.remote_only": "Einans útifrá", + "compose.error.blank_post": "Postar kunnu ikki vera blankir.", "compose.language.change": "Skift mál", "compose.language.search": "Leita eftir málum...", "compose.published.body": "Postur útgivin.", @@ -239,6 +276,11 @@ "confirmations.missing_alt_text.secondary": "Posta allíkavæl", "confirmations.missing_alt_text.title": "Legg alternativan tekst afturat?", "confirmations.mute.confirm": "Doyv", + "confirmations.private_quote_notify.cancel": "Aftur til rættingar", + "confirmations.private_quote_notify.confirm": "Útgev post", + "confirmations.private_quote_notify.do_not_show_again": "Ikki vísa hesi boðini aftur", + "confirmations.private_quote_notify.message": "Persónurin, sum tú siterar, og onnur, sum eru nevnd, verða kunnaði og kunnu síggja postin hjá tær, sjálvt um tey ikki fylgja tær.", + "confirmations.private_quote_notify.title": "Deil við fylgjarum og nevndum brúkarum?", "confirmations.quiet_post_quote_info.dismiss": "Ikki minna meg á tað aftur", "confirmations.quiet_post_quote_info.got_it": "Eg skilji", "confirmations.quiet_post_quote_info.message": "Tá tú siterar ein stillan almennan post, verður posturin hjá tær fjaldur frá tíðarlinjum, ið vísa vælumtóktar postar.", @@ -252,9 +294,12 @@ "confirmations.revoke_quote.confirm": "Strika post", "confirmations.revoke_quote.message": "Hendan atgerðin kann ikki angrast.", "confirmations.revoke_quote.title": "Strika post?", + "confirmations.unblock.confirm": "Banna ikki", + "confirmations.unblock.title": "Banna ikki {name}?", "confirmations.unfollow.confirm": "Fylg ikki", - "confirmations.unfollow.message": "Ert tú vís/ur í, at tú vil steðga við at fylgja {name}?", - "confirmations.unfollow.title": "Gevst at fylgja brúkara?", + "confirmations.unfollow.title": "Gevst at fylgja {name}?", + "confirmations.withdraw_request.confirm": "Tak umbønina aftur", + "confirmations.withdraw_request.title": "Tak umbønina um at fylgja {name} aftur?", "content_warning.hide": "Fjal post", "content_warning.show": "Vís kortini", "content_warning.show_more": "Vís meiri", @@ -325,6 +370,7 @@ "empty_column.bookmarked_statuses": "Tú hevur enn einki goymt uppslag. Tú tú goymir eitt uppslag, kemur tað her.", "empty_column.community": "Lokala tíðarlinjan er tóm. Skriva okkurt alment fyri at fáa boltin á rull!", "empty_column.direct": "Tú hevur ongar privatar umrøður enn. Tá tú sendir ella móttekur eina privata umrøðu, so verður hon sjónlig her.", + "empty_column.disabled_feed": "Hendan rásin er gjørd óvirkin av ambætaraumsitarunum hjá tær.", "empty_column.domain_blocks": "Enn eru eingi blokeraði domenir.", "empty_column.explore_statuses": "Einki rák er beint nú. Royn aftur seinni!", "empty_column.favourited_statuses": "Tú hevur ongar yndispostar enn. Tá tú gevur einum posti yndismerki, so sært tú hann her.", @@ -338,6 +384,7 @@ "empty_column.notification_requests": "Alt er klárt! Her er einki. Tá tú fært nýggjar fráboðanir, síggjast tær her sambært tínum stillingum.", "empty_column.notifications": "Tú hevur ongar fráboðanir enn. Tá onnur samskifta við teg, so sær tú fráboðaninar her.", "empty_column.public": "Einki er her! Skriva okkurt alment ella fylg brúkarum frá øðrum ambætarum fyri at fylla tilfar í", + "error.no_hashtag_feed_access": "Melda til ella rita inn fyri at síggja og fylgja hesum frámerkinum.", "error.unexpected_crash.explanation": "Orsakað av einum feili í okkara kotu ella orsakað av at kagin hjá tær ikki er sambæriligur við skipanina, so bar ikki til at vísa hesa síðuna rætt.", "error.unexpected_crash.explanation_addons": "Hendan síðan kundi ikki vísast rætt. Orsøkin til feilin er sannlíkt vegna eina uppíbygging í kaganum hjá tær ella vegna amboð til sjálvvirkandi umseting.", "error.unexpected_crash.next_steps": "Royn at lesa síðuna inn av nýggjum. Hjálpir tað ikki, so kann vera, at tað ber til at brúka Mastodon við einum øðrum kaga ella við eini app.", @@ -349,11 +396,9 @@ "explore.trending_links": "Tíðindi", "explore.trending_statuses": "Postar", "explore.trending_tags": "Frámerki", + "featured_carousel.current": "Postur {current, number} / {max, number}", "featured_carousel.header": "{count, plural, one {festur postur} other {festir postar}}", - "featured_carousel.next": "Næsta", - "featured_carousel.post": "Postur", - "featured_carousel.previous": "Fyrra", - "featured_carousel.slide": "{index} av {total}", + "featured_carousel.slide": "Postur {current, number} av {max, number}", "filter_modal.added.context_mismatch_explanation": "Hesin filturbólkurin viðvíkur ikki kontekstinum, sum tú hevur fingið atgongd til hendan postin. Ynskir tú at posturin verður filtreraður í hesum kontekstinum eisini, so er neyðugt at tú rættar filtrið.", "filter_modal.added.context_mismatch_title": "Ósamsvar við kontekst!", "filter_modal.added.expired_explanation": "Hesin filturbólkurin er útgingin, og tú mást broyta dagfestingina fyri at hann skal virka.", @@ -396,6 +441,9 @@ "follow_suggestions.who_to_follow": "Hvørji tú átti at fylgt", "followed_tags": "Fylgd frámerki", "footer.about": "Um", + "footer.about_mastodon": "Um Mastodon", + "footer.about_server": "Um {domain}", + "footer.about_this_server": "Um", "footer.directory": "Vangaskrá", "footer.get_app": "Heinta appina", "footer.keyboard_shortcuts": "Knappasnarvegir", @@ -433,7 +481,7 @@ "hints.profiles.see_more_follows": "Sí fleiri, ið viðkomandi fylgir, á {domain}", "hints.profiles.see_more_posts": "Sí fleiri postar á {domain}", "home.column_settings.show_quotes": "Vís siteringar", - "home.column_settings.show_reblogs": "Vís lyft", + "home.column_settings.show_reblogs": "Vís stimbranir", "home.column_settings.show_replies": "Vís svar", "home.hide_announcements": "Fjal kunngerðir", "home.pending_critical_update.body": "Vinarliga dagfør Mastodon ambætaran hjá tær so skjótt sum møguligt!", @@ -465,7 +513,7 @@ "intervals.full.minutes": "{number, plural, one {# minuttur} other {# minuttir}}", "keyboard_shortcuts.back": "Bakka", "keyboard_shortcuts.blocked": "Siggj listan við bannaðum brúkarum", - "keyboard_shortcuts.boost": "Lyft post", + "keyboard_shortcuts.boost": "Stimbra post", "keyboard_shortcuts.column": "Fá teig í miðdepilin", "keyboard_shortcuts.compose": "Fá skriviøkið í miðdeplin", "keyboard_shortcuts.description": "Frágreiðing", @@ -497,6 +545,7 @@ "keyboard_shortcuts.toggle_hidden": "Vís/fjal tekst handan CW", "keyboard_shortcuts.toggle_sensitivity": "Vís ella fjal innihald", "keyboard_shortcuts.toot": "Byrja nýggjan post", + "keyboard_shortcuts.top": "Flyt til ovast á listanum", "keyboard_shortcuts.translate": "at umseta ein post", "keyboard_shortcuts.unfocus": "Tak skrivi-/leiti-økið úr miðdeplinum", "keyboard_shortcuts.up": "Flyt upp á listanum", @@ -618,7 +667,7 @@ "notification.own_poll": "Tín atkvøðugreiðsla er endað", "notification.poll": "Ein atkvøðugreiðsla, har tú atkvøddi, er endað", "notification.quoted_update": "{name} rættaði ein post, sum tú hevur siterað", - "notification.reblog": "{name} lyfti tín post", + "notification.reblog": "{name} stimbraði tín post", "notification.reblog.name_and_others_with_link": "{name} og {count, plural, one {# annar/onnur} other {# onnur}} framhevjaðu tín post", "notification.relationships_severance_event": "Mist sambond við {name}", "notification.relationships_severance_event.account_suspension": "Ein umsitari frá {from} hevur gjørt {target} óvirkna, sum merkir, at tú ikki kanst móttaka dagføringar ella virka saman við teimum longur.", @@ -745,7 +794,9 @@ "privacy.unlisted.short": "Stillur almenningur", "privacy_policy.last_updated": "Seinast dagført {date}", "privacy_policy.title": "Privatlívspolitikkur", + "quote_error.edit": "Sitatir kunnu ikki leggjast afturat tá tú rættar ein post.", "quote_error.poll": "Tað er ikki loyvt at sitera spurnarkanningar.", + "quote_error.private_mentions": "Sitering er ikki loyvd við beinleiðis umrøðum.", "quote_error.quote": "Bara ein sitering er loyvd í senn.", "quote_error.unauthorized": "Tú hevur ikki rættindi at sitera hendan postin.", "quote_error.upload": "Sitering er ikki loyvd um miðlar eru viðheftir.", @@ -865,8 +916,12 @@ "status.cannot_quote": "Tú hevur ikki loyvi at sitera hendan postin", "status.cannot_reblog": "Tað ber ikki til at stimbra hendan postin", "status.contains_quote": "Inniheldur sitat", - "status.context.load_new_replies": "Nýggj svar tøk", - "status.context.loading": "Kanni um tað eru fleiri svar", + "status.context.loading": "Tekur fleiri svar niður", + "status.context.loading_error": "Fekk ikki tikið nýggj svar niður", + "status.context.loading_success": "Nýggj svar tikin niður", + "status.context.more_replies_found": "Fleiri svar funnin", + "status.context.retry": "Royn aftur", + "status.context.show": "Vís", "status.continued_thread": "Framhaldandi tráður", "status.copy": "Kopiera leinki til postin", "status.delete": "Strika", @@ -879,7 +934,7 @@ "status.edited_x_times": "Rættað {count, plural, one {{count} ferð} other {{count} ferð}}", "status.embed": "Fá kodu at seta inn", "status.favourite": "Dámdur postur", - "status.favourites": "{count, plural, one {yndispostur} other {yndispostar}}", + "status.favourites_count": "{count, plural, one {{counter} yndispostur} other {{counter} yndispostar}}", "status.filter": "Filtrera hendan postin", "status.history.created": "{name} stovnað {date}", "status.history.edited": "{name} rættað {date}", @@ -895,9 +950,12 @@ "status.pin": "Ger fastan í vangan", "status.quote": "Sitat", "status.quote.cancel": "Ógilda sitat", + "status.quote_error.blocked_account_hint.title": "Hesin posturin er fjaldur, tí tú hevur blokerað @{name}.", + "status.quote_error.blocked_domain_hint.title": "Hesin posturin er fjaldur, tí tú hevur blokerað @{domain}.", "status.quote_error.filtered": "Eitt av tínum filtrum fjalir hetta", "status.quote_error.limited_account_hint.action": "Vís kortini", "status.quote_error.limited_account_hint.title": "Hendan kontan er fjald av kjakleiðarunum á {domain}.", + "status.quote_error.muted_account_hint.title": "Hesin posturin er fjaldur, tí tú hevur doyvt @{name}.", "status.quote_error.not_available": "Postur ikki tøkur", "status.quote_error.pending_approval": "Postur bíðar", "status.quote_error.pending_approval_popout.body": "Á Mastodon kanst tú avgera, um onkur kann sitera teg. Hesin posturin bíðar eftir góðkenning frá upprunahøvundinum.", @@ -908,15 +966,17 @@ "status.quote_policy_change": "Broyt hvør kann sitera", "status.quote_post_author": "Siteraði ein post hjá @{name}", "status.quote_private": "Privatir postar kunnu ikki siterast", - "status.quotes": "{count, plural, one {sitat} other {sitat}}", "status.quotes.empty": "Eingin hevur siterað hendan postin enn. Tá onkur siterar postin, verður hann sjónligur her.", + "status.quotes.local_other_disclaimer": "Sitatir, sum eru avvíst av høvundanum, verða ikki víst.", + "status.quotes.remote_other_disclaimer": "Einans sitatir frá {domain} vera garanterað víst her. Sitatir, sum eru avvíst av høvundanum, verða ikki víst.", + "status.quotes_count": "{count, plural, one {{counter} sitat} other {{counter} sitat}}", "status.read_more": "Les meira", "status.reblog": "Stimbra", "status.reblog_or_quote": "Stimbra ella sitera", "status.reblog_private": "Deil aftur við tínum fylgjarum", "status.reblogged_by": "{name} stimbrað", - "status.reblogs": "{count, plural, one {stimbran} other {stimbranir}}", "status.reblogs.empty": "Eingin hevur stimbrað hendan postin enn. Tá onkur stimbrar postin, verður hann sjónligur her.", + "status.reblogs_count": "{count, plural, one {{counter} stimbran} other {{counter} stimbranir}}", "status.redraft": "Strika & ger nýggja kladdu", "status.remove_bookmark": "Gloym", "status.remove_favourite": "Strika í yndismerkjum", @@ -990,6 +1050,8 @@ "video.volume_down": "Minka ljóðstyrki", "video.volume_up": "Øk um ljóðstyrki", "visibility_modal.button_title": "Set sýni", + "visibility_modal.direct_quote_warning.text": "Um tú goymir verandi stillingar, verður innskotna sitatið umskapað til eitt leinki.", + "visibility_modal.direct_quote_warning.title": "Sitatir kunnu ikki innskjótast í privatar umrøður", "visibility_modal.header": "Sýni og samvirkni", "visibility_modal.helper.direct_quoting": "Privatar umrøður, sum eru skrivaðar á Mastodon, kunnu ikki siterast av øðrum.", "visibility_modal.helper.privacy_editing": "Sýni kann ikki broytast eftir, at ein postur er útgivin.", diff --git a/app/javascript/mastodon/locales/fr-CA.json b/app/javascript/mastodon/locales/fr-CA.json index 2bc6195c55cec2..95b8efe078ba78 100644 --- a/app/javascript/mastodon/locales/fr-CA.json +++ b/app/javascript/mastodon/locales/fr-CA.json @@ -21,29 +21,35 @@ "account.block_domain": "Bloquer le domaine {domain}", "account.block_short": "Bloquer", "account.blocked": "Bloqué·e", - "account.blocking": "Bloquer", + "account.blocking": "Bloqué", "account.cancel_follow_request": "Retirer cette demande d'abonnement", - "account.copy": "Copier le lien vers le profil", + "account.copy": "Copier le lien du profil", "account.direct": "Mention privée @{name}", "account.disable_notifications": "Ne plus me notifier quand @{name} publie", - "account.domain_blocking": "Bloquer domaine", + "account.domain_blocking": "Domaine bloqué", "account.edit_profile": "Modifier le profil", + "account.edit_profile_short": "Modifier", "account.enable_notifications": "Me notifier quand @{name} publie", "account.endorse": "Inclure sur profil", - "account.familiar_followers_many": "Suivi par {name1},{name2}, et {othersCount, plural,one {une personne connue} other {# autres personnel connues}}", + "account.familiar_followers_many": "Suivi par {name1}, {name2}, et {othersCount, plural, one {une autre personne que vous suivez} other {# autres personnes que vous suivez}}", "account.familiar_followers_one": "Suivi par {name1}", "account.familiar_followers_two": "Suivi par {name1} et {name2}", "account.featured": "En vedette", - "account.featured.accounts": "Profiles", + "account.featured.accounts": "Profils", "account.featured.hashtags": "Hashtags", "account.featured_tags.last_status_at": "Dernière publication {date}", "account.featured_tags.last_status_never": "Aucune publication", "account.follow": "Suivre", "account.follow_back": "Suivre en retour", + "account.follow_back_short": "Suivre en retour", + "account.follow_request": "Demande d’abonnement", + "account.follow_request_cancel": "Annuler la demande", + "account.follow_request_cancel_short": "Annuler", + "account.follow_request_short": "Demander à suivre", "account.followers": "abonné·e·s", "account.followers.empty": "Personne ne suit ce compte pour l'instant.", "account.followers_counter": "{count, plural, one {{counter} abonné·e} other {{counter} abonné·e·s}}", - "account.followers_you_know_counter": "Vous connaissez {counter}", + "account.followers_you_know_counter": "{count, plural, one {{counter} suivi}, other {{counter} suivis}}", "account.following": "Abonné·e", "account.following_counter": "{count, plural, one {{counter} abonnement} other {{counter} abonnements}}", "account.follows.empty": "Ce compte ne suit personne présentement.", @@ -68,11 +74,10 @@ "account.open_original_page": "Ouvrir la page d'origine", "account.posts": "Publications", "account.posts_with_replies": "Publications et réponses", - "account.remove_from_followers": "Retirer {name} des suiveurs", + "account.remove_from_followers": "Retirer {name} des abonnés", "account.report": "Signaler @{name}", - "account.requested": "En attente d’approbation. Cliquez pour annuler la demande", "account.requested_follow": "{name} a demandé à vous suivre", - "account.requests_to_follow_you": "Demande a vous suivre", + "account.requests_to_follow_you": "Demande à vous suivre", "account.share": "Partager le profil de @{name}", "account.show_reblogs": "Afficher les boosts de @{name}", "account.statuses_counter": "{count, plural, one {{counter} message} other {{counter} messages}}", @@ -101,32 +106,59 @@ "alert.unexpected.title": "Oups!", "alt_text_badge.title": "Texte alternatif", "alt_text_modal.add_alt_text": "Ajouter un texte alternatif", - "alt_text_modal.add_text_from_image": "Ajouter le texte provenant de l'image", + "alt_text_modal.add_text_from_image": "Extraire le texte de l'image", "alt_text_modal.cancel": "Annuler", "alt_text_modal.change_thumbnail": "Changer la vignette", "alt_text_modal.describe_for_people_with_hearing_impairments": "Décrire pour les personnes ayant des difficultés d’audition…", "alt_text_modal.describe_for_people_with_visual_impairments": "Décrire pour les personnes ayant des problèmes de vue…", "alt_text_modal.done": "Terminé", "announcement.announcement": "Annonce", - "annual_report.summary.archetype.booster": "Le chasseur de sang-froid", - "annual_report.summary.archetype.lurker": "Le faucheur", - "annual_report.summary.archetype.oracle": "L’oracle", - "annual_report.summary.archetype.pollster": "Le sondeur", - "annual_report.summary.archetype.replier": "Le papillon social", - "annual_report.summary.followers.followers": "abonné·e·s", - "annual_report.summary.followers.total": "{count} au total", - "annual_report.summary.here_it_is": "Voici votre récap de {year} :", - "annual_report.summary.highlighted_post.by_favourites": "post le plus aimé", - "annual_report.summary.highlighted_post.by_reblogs": "post le plus boosté", - "annual_report.summary.highlighted_post.by_replies": "post avec le plus de réponses", - "annual_report.summary.highlighted_post.possessive": "{name}'s", + "annual_report.announcement.action_build": "Générer mon Wrapstodon", + "annual_report.announcement.action_dismiss": "Non merci", + "annual_report.announcement.action_view": "Voir mon Wrapstodon", + "annual_report.announcement.description": "En découvrir plus concernant votre activité sur Mastodon pour l'année écoulée.", + "annual_report.announcement.title": "Le Wrapstodon {year} est arrivé", + "annual_report.nav_item.badge": "Nouveau", + "annual_report.shared_page.donate": "Faire un don", + "annual_report.shared_page.footer": "Généré avec {heart} par l'équipe de Mastodon", + "annual_report.shared_page.footer_server_info": "{username} utilise {domain}, l'une des nombreuses communautés propulsées par Mastodon.", + "annual_report.summary.archetype.booster.desc_public": "{name} fut à l’affût des messages à partager, ciblant avec perfection les créatrices et créateurs à amplifier.", + "annual_report.summary.archetype.booster.desc_self": "Vous avez été à l'affût des messages à partager, ciblant avec perfection les créatrices et créateurs à amplifier.", + "annual_report.summary.archetype.booster.name": "Archer, archère", + "annual_report.summary.archetype.die_drei_fragezeichen": "???", + "annual_report.summary.archetype.lurker.desc_public": "Nous savons que {name} a été là, quelque part, profitant calmement de Mastodon à sa manière.", + "annual_report.summary.archetype.lurker.desc_self": "Nous savons que vous avez été là, quelque part, profitant calmement de Mastodon à votre manière.", + "annual_report.summary.archetype.lurker.name": "Stoïque", + "annual_report.summary.archetype.oracle.desc_public": "{name} publia plus de nouveaux messages que de réponses, contribuant à garder Mastodon frais et tourné vers l'avenir.", + "annual_report.summary.archetype.oracle.desc_self": "Vous avez publié plus de nouveaux messages que de réponses, contribuant à garder Mastodon frais et tourné vers l'avenir.", + "annual_report.summary.archetype.oracle.name": "Oracle", + "annual_report.summary.archetype.pollster.desc_public": "{name} publia plus de sondages que n'importe quel autre type de message, cultivant la curiosité sur Mastodon.", + "annual_report.summary.archetype.pollster.desc_self": "Vous avez publié plus de sondages que n'importe quel autre type de message, cultivant la curiosité sur Mastodon.", + "annual_report.summary.archetype.pollster.name": "Curieux, curieuse", + "annual_report.summary.archetype.replier.desc_public": "{name} répliqua fréquemment aux messages des autres, pollinisant Mastodon de nouvelles discussions.", + "annual_report.summary.archetype.replier.desc_self": "Vous avez fréquemment répliqué aux messages des autres, pollinisant Mastodon de nouvelles discussions.", + "annual_report.summary.archetype.replier.name": "Papillon", + "annual_report.summary.archetype.reveal": "Révéler mon archétype", + "annual_report.summary.archetype.reveal_description": "Merci de faire partie de Mastodon ! Il est temps de découvrir quel archétype vous avez incarné en {year}.", + "annual_report.summary.archetype.title_public": "L'archétype de {name}", + "annual_report.summary.archetype.title_self": "Votre archétype", + "annual_report.summary.close": "Fermer", + "annual_report.summary.copy_link": "Copier le lien", + "annual_report.summary.followers.new_followers": "{count, plural, one {nouvel·le abonné·e} other {nouveaux abonné·e·s}}", + "annual_report.summary.highlighted_post.boost_count": "Ce message a été partagé {count, plural, one {une fois} other {# fois}}.", + "annual_report.summary.highlighted_post.favourite_count": "Ce message a été mis en favoris {count, plural, one {une fois} other {# fois}}.", + "annual_report.summary.highlighted_post.reply_count": "Ce message a reçu {count, plural, one {une réponse} other {# réponses}}.", + "annual_report.summary.highlighted_post.title": "Message le plus populaire", "annual_report.summary.most_used_app.most_used_app": "appli la plus utilisée", "annual_report.summary.most_used_hashtag.most_used_hashtag": "hashtag le plus utilisé", - "annual_report.summary.most_used_hashtag.none": "Aucun", + "annual_report.summary.most_used_hashtag.used_count": "Vous avez utilisé ce hashtag dans {count, plural, one {un message} other {# messages}}.", + "annual_report.summary.most_used_hashtag.used_count_public": "{name} a inclus ce hashtag dans {count, plural, one {un message} other {# messages}}.", "annual_report.summary.new_posts.new_posts": "nouveaux messages", "annual_report.summary.percentile.text": "Cela vous place dans le topdes utilisateurs de {domain}.", "annual_report.summary.percentile.we_wont_tell_bernie": "Nous ne le dirons pas à Bernie.", - "annual_report.summary.thanks": "Merci de faire partie de Mastodon!", + "annual_report.summary.share_elsewhere": "Partager ailleurs", + "annual_report.summary.share_message": "J’ai obtenu l’archétype {archetype} !", + "annual_report.summary.share_on_mastodon": "Partager sur Mastodon", "attachments_list.unprocessed": "(non traité)", "audio.hide": "Masquer l'audio", "block_modal.remote_users_caveat": "Nous allons demander au serveur {domain} de respecter votre décision. Cependant, ce respect n'est pas garanti, car certains serveurs peuvent gérer différemment les blocages. Les messages publics peuvent rester visibles par les utilisateur·rice·s non connecté·e·s.", @@ -152,6 +184,8 @@ "bundle_modal_error.close": "Fermer", "bundle_modal_error.message": "Un problème s'est produit lors du chargement de cet écran.", "bundle_modal_error.retry": "Réessayer", + "carousel.current": "Diapositive {current, number} / {max, number}", + "carousel.slide": "Diapositive {current, number} de {max, number}", "closed_registrations.other_server_instructions": "Puisque Mastodon est décentralisé, vous pouvez créer un compte sur un autre serveur et interagir quand même avec celui-ci.", "closed_registrations_modal.description": "Créer un compte sur {domain} est présentement impossible, néanmoins souvenez-vous que vous n'avez pas besoin d'un compte spécifiquement sur {domain} pour utiliser Mastodon.", "closed_registrations_modal.find_another_server": "Trouver un autre serveur", @@ -168,6 +202,8 @@ "column.edit_list": "Modifier la liste", "column.favourites": "Favoris", "column.firehose": "Flux en direct", + "column.firehose_local": "Flux en direct pour ce serveur", + "column.firehose_singular": "Flux en direct", "column.follow_requests": "Demande d'abonnement", "column.home": "Accueil", "column.list_members": "Gérer les membres de la liste", @@ -187,6 +223,7 @@ "community.column_settings.local_only": "Local seulement", "community.column_settings.media_only": "Média seulement", "community.column_settings.remote_only": "À distance seulement", + "compose.error.blank_post": "Le message ne peut être laissé vide.", "compose.language.change": "Changer de langue", "compose.language.search": "Rechercher des langues…", "compose.published.body": "Publiée.", @@ -239,6 +276,11 @@ "confirmations.missing_alt_text.secondary": "Publier quand-même", "confirmations.missing_alt_text.title": "Ajouter un texte alternatif?", "confirmations.mute.confirm": "Masquer", + "confirmations.private_quote_notify.cancel": "Retour à l'édition", + "confirmations.private_quote_notify.confirm": "Publier", + "confirmations.private_quote_notify.do_not_show_again": "Ne plus afficher ce message", + "confirmations.private_quote_notify.message": "La personne citée et celles mentionnées seront notifiées et pourront voir le message, même si elles ne vous suivent pas.", + "confirmations.private_quote_notify.title": "Partager avec les personnes abonnées et mentionnées ?", "confirmations.quiet_post_quote_info.dismiss": "Ne plus me rappeler", "confirmations.quiet_post_quote_info.got_it": "Compris", "confirmations.quiet_post_quote_info.message": "Lorsque vous citez un message public silencieux, votre message sera caché des fils tendances.", @@ -252,9 +294,12 @@ "confirmations.revoke_quote.confirm": "Retirer la publication", "confirmations.revoke_quote.message": "Cette action ne peut pas être annulée.", "confirmations.revoke_quote.title": "Retirer la publication ?", + "confirmations.unblock.confirm": "Débloquer", + "confirmations.unblock.title": "Débloquer {name} ?", "confirmations.unfollow.confirm": "Ne plus suivre", - "confirmations.unfollow.message": "Voulez-vous vraiment arrêter de suivre {name}?", - "confirmations.unfollow.title": "Se désabonner de l'utilisateur·rice ?", + "confirmations.unfollow.title": "Ne plus suivre {name} ?", + "confirmations.withdraw_request.confirm": "Rejeter la demande", + "confirmations.withdraw_request.title": "Rejeter la demande de suivre {name} ?", "content_warning.hide": "Masquer le message", "content_warning.show": "Montrer quand même", "content_warning.show_more": "Montrer plus", @@ -325,6 +370,7 @@ "empty_column.bookmarked_statuses": "Vous n'avez pas de publications parmi vos signets. Lorsque vous en ajouterez une, elle apparaîtra ici.", "empty_column.community": "Le fil local est vide. Écrivez donc quelque chose pour le remplir!", "empty_column.direct": "Vous n'avez pas encore de mentions privées. Quand vous en envoyez ou en recevez, elles apparaîtront ici.", + "empty_column.disabled_feed": "Ce flux a été désactivé par les administrateur·rice·s de votre serveur.", "empty_column.domain_blocks": "Il n’y a aucun domaine bloqué pour le moment.", "empty_column.explore_statuses": "Rien n'est en tendance présentement. Revenez plus tard!", "empty_column.favourited_statuses": "Vous n’avez pas encore de publications favorites. Lorsque vous en ajouterez une, elle apparaîtra ici.", @@ -338,6 +384,7 @@ "empty_column.notification_requests": "C'est fini ! Il n'y a plus rien ici. Lorsque vous recevez de nouvelles notifications, elles apparaitront ici conformément à vos préférences.", "empty_column.notifications": "Vous n'avez pas encore de notifications. Quand d'autres personnes interagissent avec vous, vous en verrez ici.", "empty_column.public": "Il n’y a rien ici! Écrivez quelque chose publiquement, ou bien suivez manuellement des personnes d’autres serveurs pour remplir le fil public", + "error.no_hashtag_feed_access": "Rejoindre ou se connecter pour voir et suivre cet hashtag.", "error.unexpected_crash.explanation": "En raison d’un bogue dans notre code ou d’un problème de compatibilité avec votre navigateur, cette page n’a pas pu être affichée correctement.", "error.unexpected_crash.explanation_addons": "Cette page n’a pas pu être affichée correctement. Cette erreur est probablement causée par une extension de navigateur ou des outils de traduction automatique.", "error.unexpected_crash.next_steps": "Essayez de rafraîchir la page. Si cela n’aide pas, vous pouvez toujours utiliser Mastodon via un autre navigateur ou une application native.", @@ -349,11 +396,9 @@ "explore.trending_links": "Nouvelles", "explore.trending_statuses": "Messages", "explore.trending_tags": "Hashtags", + "featured_carousel.current": "Message {current, number} / {max, number}", "featured_carousel.header": "{count, plural, one {Pinned Post} other {Pinned Posts}}", - "featured_carousel.next": "Suivant", - "featured_carousel.post": "Poste", - "featured_carousel.previous": "Précédent", - "featured_carousel.slide": "{index} de {total}", + "featured_carousel.slide": "Message {current, number} de {max, number}", "filter_modal.added.context_mismatch_explanation": "Cette catégorie de filtre ne s'applique pas au contexte dans lequel vous avez accédé à cette publication. Si vous voulez que la publication soit filtrée dans ce contexte également, vous devrez modifier le filtre.", "filter_modal.added.context_mismatch_title": "Incompatibilité du contexte!", "filter_modal.added.expired_explanation": "Cette catégorie de filtre a expiré, vous devrez modifier la date d'expiration pour qu'elle soit appliquée.", @@ -396,6 +441,9 @@ "follow_suggestions.who_to_follow": "Qui suivre", "followed_tags": "Hashtags suivis", "footer.about": "À propos", + "footer.about_mastodon": "À propos de Mastodon", + "footer.about_server": "À propos de {domain}", + "footer.about_this_server": "À propos", "footer.directory": "Annuaire des profils", "footer.get_app": "Télécharger l’application", "footer.keyboard_shortcuts": "Raccourcis clavier", @@ -497,6 +545,7 @@ "keyboard_shortcuts.toggle_hidden": "Déplier/replier texte derrière avertissement", "keyboard_shortcuts.toggle_sensitivity": "Afficher/cacher médias", "keyboard_shortcuts.toot": "Commencer un nouveau message", + "keyboard_shortcuts.top": "Mettre en tête de liste", "keyboard_shortcuts.translate": "traduire un message", "keyboard_shortcuts.unfocus": "Ne plus se concentrer sur la zone de rédaction/barre de recherche", "keyboard_shortcuts.up": "Monter dans la liste", @@ -510,7 +559,7 @@ "limited_account_hint.action": "Afficher le profil quand même", "limited_account_hint.title": "Ce profil a été masqué par la modération de {domain}.", "link_preview.author": "Par {name}", - "link_preview.more_from_author": "Plus via {name}", + "link_preview.more_from_author": "Voir plus de {name}", "link_preview.shares": "{count, plural, one {{counter} message} other {{counter} messages}}", "lists.add_member": "Ajouter", "lists.add_to_list": "Ajouter à la liste", @@ -548,7 +597,7 @@ "mute_modal.they_can_mention_and_follow": "Ils peuvent vous mentionner et vous suivre, mais vous ne les verrez pas.", "mute_modal.they_wont_know": "Ils ne sauront pas qu'ils ont été rendus silencieux.", "mute_modal.title": "Rendre cet utilisateur silencieux ?", - "mute_modal.you_wont_see_mentions": "Vous ne verrez pas les publications qui le mentionne.", + "mute_modal.you_wont_see_mentions": "Vous ne verrez pas les messages qui le mentionne.", "mute_modal.you_wont_see_posts": "Il peut toujours voir vos publications, mais vous ne verrez pas les siennes.", "navigation_bar.about": "À propos", "navigation_bar.account_settings": "Mot de passe et sécurité", @@ -601,7 +650,7 @@ "notification.follow_request.name_and_others": "{name} et {count, plural, one {# autre} other {# autres}} ont demandé à vous suivre", "notification.label.mention": "Mention", "notification.label.private_mention": "Mention privée", - "notification.label.private_reply": "Répondre en privé", + "notification.label.private_reply": "Réponse privée", "notification.label.quote": "{name} a cité votre publication", "notification.label.reply": "Réponse", "notification.mention": "Mention", @@ -741,11 +790,13 @@ "privacy.quote.disabled": "{visibility}, citations désactivées", "privacy.quote.limited": "{visibility}, citations limitées", "privacy.unlisted.additional": "Se comporte exactement comme « public », sauf que le message n'apparaîtra pas dans les flux en direct, les hashtags, explorer ou la recherche Mastodon, même si vous les avez activé au niveau de votre compte.", - "privacy.unlisted.long": "Caché des résultats de recherche de Mastodon, aux tendances et aux échéanciers publics", + "privacy.unlisted.long": "Caché des résultats de recherche de Mastodon, des tendances et des fils publics", "privacy.unlisted.short": "Public discret", "privacy_policy.last_updated": "Dernière mise à jour {date}", "privacy_policy.title": "Politique de confidentialité", + "quote_error.edit": "Les citations ne peuvent pas être ajoutés lors de l'édition d'un message.", "quote_error.poll": "Les citations ne sont pas autorisées avec les sondages.", + "quote_error.private_mentions": "La citation n'est pas autorisée avec les mentions privées.", "quote_error.quote": "Une seule citation à la fois est autorisée.", "quote_error.unauthorized": "Vous n'êtes pas autorisé⋅e à citer cette publication.", "quote_error.upload": "La citation n'est pas autorisée avec un média joint.", @@ -864,8 +915,13 @@ "status.cancel_reblog_private": "Débooster", "status.cannot_quote": "Vous n'êtes pas autorisé à citer ce message", "status.cannot_reblog": "Cette publication ne peut pas être boostée", - "status.context.load_new_replies": "Nouvelles réponses disponibles", - "status.context.loading": "Vérification de plus de réponses", + "status.contains_quote": "Contient la citation", + "status.context.loading": "Chargement de réponses supplémentaires", + "status.context.loading_error": "Impossible de charger les nouvelles réponses", + "status.context.loading_success": "De nouvelles réponses ont été chargées", + "status.context.more_replies_found": "Plus de réponses trouvées", + "status.context.retry": "Réessayer", + "status.context.show": "Montrer", "status.continued_thread": "Suite du fil", "status.copy": "Copier un lien vers cette publication", "status.delete": "Supprimer", @@ -878,7 +934,7 @@ "status.edited_x_times": "Modifiée {count, plural, one {{count} fois} other {{count} fois}}", "status.embed": "Obtenir le code d'intégration", "status.favourite": "Ajouter aux favoris", - "status.favourites": "{count, plural, one {favori} other {favoris}}", + "status.favourites_count": "{count, plural, one {{counter} favori} other {{counter} favoris}}", "status.filter": "Filtrer cette publication", "status.history.created": "créé par {name} {date}", "status.history.edited": "modifié par {name} {date}", @@ -894,25 +950,33 @@ "status.pin": "Épingler sur profil", "status.quote": "Citer", "status.quote.cancel": "Annuler la citation", + "status.quote_error.blocked_account_hint.title": "Ce message est masqué car vous avez bloqué @{name}.", + "status.quote_error.blocked_domain_hint.title": "Ce message est masqué car vous avez bloqué {domain}.", "status.quote_error.filtered": "Caché en raison de l'un de vos filtres", + "status.quote_error.limited_account_hint.action": "Afficher quand même", + "status.quote_error.limited_account_hint.title": "Ce profil a été masqué par la modération de {domain}.", + "status.quote_error.muted_account_hint.title": "Ce message est masqué car vous avez Mis en sourdine @{name}.", "status.quote_error.not_available": "Publication non disponible", "status.quote_error.pending_approval": "Publication en attente", "status.quote_error.pending_approval_popout.body": "Sur Mastodon, vous pouvez contrôler si quelqu'un peut vous citer. Ce message est en attente pendant que nous recevons l'approbation de l'auteur original.", "status.quote_error.revoked": "Post supprimé par l'auteur", "status.quote_followers_only": "Seul·e·s les abonné·e·s peuvent citer cette publication", "status.quote_manual_review": "L'auteur va vérifier manuellement", + "status.quote_noun": "Citation", "status.quote_policy_change": "Changer qui peut vous citer", "status.quote_post_author": "A cité un message par @{name}", "status.quote_private": "Les publications privées ne peuvent pas être citées", - "status.quotes": " {count, plural, one {quote} other {quotes}}", "status.quotes.empty": "Personne n'a encore cité ce message. Quand quelqu'un le fera, il apparaîtra ici.", + "status.quotes.local_other_disclaimer": "Les citations rejetées par l'auteur ne seront pas affichées.", + "status.quotes.remote_other_disclaimer": "Seules les citations de {domain} sont garanties d'être affichées ici. Les citations rejetées par l'auteur ne seront pas affichées.", + "status.quotes_count": "{count, plural, one {{counter} citation} other {{counter} citations}}", "status.read_more": "En savoir plus", "status.reblog": "Booster", "status.reblog_or_quote": "Boost ou citation", "status.reblog_private": "Partagez à nouveau avec vos abonnés", "status.reblogged_by": "{name} a boosté", - "status.reblogs": "{count, plural, one {boost} other {boosts}}", "status.reblogs.empty": "Personne n’a encore boosté cette publication. Lorsque quelqu’un le fera, elle apparaîtra ici.", + "status.reblogs_count": "{count, plural, one {{counter} partage} other {{counter} partages}}", "status.redraft": "Supprimer et réécrire", "status.remove_bookmark": "Retirer des signets", "status.remove_favourite": "Retirer des favoris", @@ -986,13 +1050,15 @@ "video.volume_down": "Baisser le volume", "video.volume_up": "Augmenter le volume", "visibility_modal.button_title": "Définir la visibilité", + "visibility_modal.direct_quote_warning.text": "Si vous enregistrez les paramètres actuels, la citation sera convertie en lien.", + "visibility_modal.direct_quote_warning.title": "Les citations ne peuvent pas être intégrées dans les mentions privées", "visibility_modal.header": "Visibilité et interactions", "visibility_modal.helper.direct_quoting": "Les mentions privées rédigées sur Mastodon ne peuvent pas être citées par d'autres personnes.", "visibility_modal.helper.privacy_editing": "La visibilité ne peut pas être modifiée après la publication d'un message.", "visibility_modal.helper.privacy_private_self_quote": "Les auto-citations de messages privés ne peuvent pas être rendues publiques.", "visibility_modal.helper.private_quoting": "Les posts accessible uniquement par les followers sur Mastodon ne peuvent être cités par d'autres personnes.", - "visibility_modal.helper.unlisted_quoting": "Lorsque les gens vous citent, leur message sera également caché dans les calendriers tendances.", - "visibility_modal.instructions": "Contrôlez qui peut interagir avec ce post. Vous pouvez également appliquer ces paramètres à tous les futurs messages en allant dans Préférences > Valeurs par d'éfaut de publication.", + "visibility_modal.helper.unlisted_quoting": "Lorsque les gens vous citent, leur message sera également caché des fils tendances.", + "visibility_modal.instructions": "Contrôlez qui peut interagir avec ce post. Vous pouvez également appliquer ces paramètres à tous les futurs messages en allant dans Préférences > Valeurs par défaut de publication.", "visibility_modal.privacy_label": "Visibilité", "visibility_modal.quote_followers": "Abonné·e·s seulement", "visibility_modal.quote_label": "Autoriser les citations pour", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index e579dbe09b904f..2ef5c9acb40b17 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -4,7 +4,7 @@ "about.default_locale": "Défaut", "about.disclaimer": "Mastodon est un logiciel libre, open-source et une marque déposée de Mastodon gGmbH.", "about.domain_blocks.no_reason_available": "Raison non disponible", - "about.domain_blocks.preamble": "Mastodon vous permet généralement de visualiser le contenu et d'interagir avec les utilisateur⋅rices de n'importe quel autre serveur dans le fédivers. Voici les exceptions qui ont été faites sur ce serveur-là.", + "about.domain_blocks.preamble": "Mastodon vous permet généralement de visualiser le contenu et d'interagir avec les utilisateurs et utilisatrices de n'importe quel autre serveur dans le fédivers. Voici les exceptions qui ont été faites sur ce serveur.", "about.domain_blocks.silenced.explanation": "Vous ne verrez généralement pas les profils et le contenu de ce serveur, à moins que vous ne les recherchiez explicitement ou que vous ne choisissiez de les suivre.", "about.domain_blocks.silenced.title": "Limité", "about.domain_blocks.suspended.explanation": "Aucune donnée de ce serveur ne sera traitée, enregistrée ou échangée, rendant impossible toute interaction ou communication avec les comptes de ce serveur.", @@ -21,29 +21,35 @@ "account.block_domain": "Bloquer le domaine {domain}", "account.block_short": "Bloquer", "account.blocked": "Bloqué·e", - "account.blocking": "Bloquer", + "account.blocking": "Bloqué", "account.cancel_follow_request": "Annuler l'abonnement", - "account.copy": "Copier le lien vers le profil", + "account.copy": "Copier le lien du profil", "account.direct": "Mention privée @{name}", "account.disable_notifications": "Ne plus me notifier quand @{name} publie quelque chose", - "account.domain_blocking": "Bloquer domaine", + "account.domain_blocking": "Domaine bloqué", "account.edit_profile": "Modifier le profil", + "account.edit_profile_short": "Modifier", "account.enable_notifications": "Me notifier quand @{name} publie quelque chose", "account.endorse": "Recommander sur votre profil", - "account.familiar_followers_many": "Suivi par {name1},{name2}, et {othersCount, plural,one {une personne connue} other {# autres personnel connues}}", + "account.familiar_followers_many": "Suivi par {name1}, {name2}, et {othersCount, plural, one {une autre personne que vous suivez} other {# autres personnes que vous suivez}}", "account.familiar_followers_one": "Suivi par {name1}", "account.familiar_followers_two": "Suivi par {name1} et {name2}", "account.featured": "En vedette", - "account.featured.accounts": "Profiles", + "account.featured.accounts": "Profils", "account.featured.hashtags": "Hashtags", "account.featured_tags.last_status_at": "Dernier message le {date}", "account.featured_tags.last_status_never": "Aucun message", "account.follow": "Suivre", "account.follow_back": "Suivre en retour", + "account.follow_back_short": "Suivre en retour", + "account.follow_request": "Demande d’abonnement", + "account.follow_request_cancel": "Annuler la demande", + "account.follow_request_cancel_short": "Annuler", + "account.follow_request_short": "Demander à suivre", "account.followers": "Abonné·e·s", "account.followers.empty": "Personne ne suit cet·te utilisateur·rice pour l’instant.", "account.followers_counter": "{count, plural, one {{counter} abonné·e} other {{counter} abonné·e·s}}", - "account.followers_you_know_counter": "Vous connaissez {counter}", + "account.followers_you_know_counter": "{count, plural, one {{counter} suivi}, other {{counter} suivis}}", "account.following": "Abonnements", "account.following_counter": "{count, plural, one {{counter} abonnement} other {{counter} abonnements}}", "account.follows.empty": "Cet·te utilisateur·rice ne suit personne pour l’instant.", @@ -68,11 +74,10 @@ "account.open_original_page": "Ouvrir la page d'origine", "account.posts": "Messages", "account.posts_with_replies": "Messages et réponses", - "account.remove_from_followers": "Retirer {name} des suiveurs", + "account.remove_from_followers": "Retirer {name} des abonnés", "account.report": "Signaler @{name}", - "account.requested": "En attente d’approbation. Cliquez pour annuler la demande", "account.requested_follow": "{name} a demandé à vous suivre", - "account.requests_to_follow_you": "Demande a vous suivre", + "account.requests_to_follow_you": "Demande à vous suivre", "account.share": "Partager le profil de @{name}", "account.show_reblogs": "Afficher les partages de @{name}", "account.statuses_counter": "{count, plural, one {{counter} message} other {{counter} messages}}", @@ -101,32 +106,59 @@ "alert.unexpected.title": "Oups !", "alt_text_badge.title": "Texte alternatif", "alt_text_modal.add_alt_text": "Ajouter un texte alternatif", - "alt_text_modal.add_text_from_image": "Ajouter le texte provenant de l'image", + "alt_text_modal.add_text_from_image": "Extraire le texte de l'image", "alt_text_modal.cancel": "Annuler", "alt_text_modal.change_thumbnail": "Changer la vignette", "alt_text_modal.describe_for_people_with_hearing_impairments": "Décrire pour les personnes ayant des difficultés d’audition…", "alt_text_modal.describe_for_people_with_visual_impairments": "Décrire pour les personnes ayant des problèmes de vue…", "alt_text_modal.done": "Terminé", "announcement.announcement": "Annonce", - "annual_report.summary.archetype.booster": "Le chasseur de sang-froid", - "annual_report.summary.archetype.lurker": "Le faucheur", - "annual_report.summary.archetype.oracle": "L’oracle", - "annual_report.summary.archetype.pollster": "Le sondeur", - "annual_report.summary.archetype.replier": "Le papillon social", - "annual_report.summary.followers.followers": "abonné·e·s", - "annual_report.summary.followers.total": "{count} au total", - "annual_report.summary.here_it_is": "Voici votre récap de {year} :", - "annual_report.summary.highlighted_post.by_favourites": "post le plus aimé", - "annual_report.summary.highlighted_post.by_reblogs": "post le plus boosté", - "annual_report.summary.highlighted_post.by_replies": "post avec le plus de réponses", - "annual_report.summary.highlighted_post.possessive": "{name}'s", + "annual_report.announcement.action_build": "Générer mon Wrapstodon", + "annual_report.announcement.action_dismiss": "Non merci", + "annual_report.announcement.action_view": "Voir mon Wrapstodon", + "annual_report.announcement.description": "En découvrir plus concernant votre activité sur Mastodon pour l'année écoulée.", + "annual_report.announcement.title": "Le Wrapstodon {year} est arrivé", + "annual_report.nav_item.badge": "Nouveau", + "annual_report.shared_page.donate": "Faire un don", + "annual_report.shared_page.footer": "Généré avec {heart} par l'équipe de Mastodon", + "annual_report.shared_page.footer_server_info": "{username} utilise {domain}, l'une des nombreuses communautés propulsées par Mastodon.", + "annual_report.summary.archetype.booster.desc_public": "{name} fut à l’affût des messages à partager, ciblant avec perfection les créatrices et créateurs à amplifier.", + "annual_report.summary.archetype.booster.desc_self": "Vous avez été à l'affût des messages à partager, ciblant avec perfection les créatrices et créateurs à amplifier.", + "annual_report.summary.archetype.booster.name": "Archer, archère", + "annual_report.summary.archetype.die_drei_fragezeichen": "???", + "annual_report.summary.archetype.lurker.desc_public": "Nous savons que {name} a été là, quelque part, profitant calmement de Mastodon à sa manière.", + "annual_report.summary.archetype.lurker.desc_self": "Nous savons que vous avez été là, quelque part, profitant calmement de Mastodon à votre manière.", + "annual_report.summary.archetype.lurker.name": "Stoïque", + "annual_report.summary.archetype.oracle.desc_public": "{name} publia plus de nouveaux messages que de réponses, contribuant à garder Mastodon frais et tourné vers l'avenir.", + "annual_report.summary.archetype.oracle.desc_self": "Vous avez publié plus de nouveaux messages que de réponses, contribuant à garder Mastodon frais et tourné vers l'avenir.", + "annual_report.summary.archetype.oracle.name": "Oracle", + "annual_report.summary.archetype.pollster.desc_public": "{name} publia plus de sondages que n'importe quel autre type de message, cultivant la curiosité sur Mastodon.", + "annual_report.summary.archetype.pollster.desc_self": "Vous avez publié plus de sondages que n'importe quel autre type de message, cultivant la curiosité sur Mastodon.", + "annual_report.summary.archetype.pollster.name": "Curieux, curieuse", + "annual_report.summary.archetype.replier.desc_public": "{name} répliqua fréquemment aux messages des autres, pollinisant Mastodon de nouvelles discussions.", + "annual_report.summary.archetype.replier.desc_self": "Vous avez fréquemment répliqué aux messages des autres, pollinisant Mastodon de nouvelles discussions.", + "annual_report.summary.archetype.replier.name": "Papillon", + "annual_report.summary.archetype.reveal": "Révéler mon archétype", + "annual_report.summary.archetype.reveal_description": "Merci de faire partie de Mastodon ! Il est temps de découvrir quel archétype vous avez incarné en {year}.", + "annual_report.summary.archetype.title_public": "L'archétype de {name}", + "annual_report.summary.archetype.title_self": "Votre archétype", + "annual_report.summary.close": "Fermer", + "annual_report.summary.copy_link": "Copier le lien", + "annual_report.summary.followers.new_followers": "{count, plural, one {nouvel·le abonné·e} other {nouveaux abonné·e·s}}", + "annual_report.summary.highlighted_post.boost_count": "Ce message a été partagé {count, plural, one {une fois} other {# fois}}.", + "annual_report.summary.highlighted_post.favourite_count": "Ce message a été mis en favoris {count, plural, one {une fois} other {# fois}}.", + "annual_report.summary.highlighted_post.reply_count": "Ce message a reçu {count, plural, one {une réponse} other {# réponses}}.", + "annual_report.summary.highlighted_post.title": "Message le plus populaire", "annual_report.summary.most_used_app.most_used_app": "appli la plus utilisée", "annual_report.summary.most_used_hashtag.most_used_hashtag": "hashtag le plus utilisé", - "annual_report.summary.most_used_hashtag.none": "Aucun", + "annual_report.summary.most_used_hashtag.used_count": "Vous avez utilisé ce hashtag dans {count, plural, one {un message} other {# messages}}.", + "annual_report.summary.most_used_hashtag.used_count_public": "{name} a inclus ce hashtag dans {count, plural, one {un message} other {# messages}}.", "annual_report.summary.new_posts.new_posts": "nouveaux messages", "annual_report.summary.percentile.text": "Cela vous place dans le topdes utilisateurs de {domain}.", "annual_report.summary.percentile.we_wont_tell_bernie": "Nous ne le dirons pas à Bernie.", - "annual_report.summary.thanks": "Merci de faire partie de Mastodon!", + "annual_report.summary.share_elsewhere": "Partager ailleurs", + "annual_report.summary.share_message": "J’ai obtenu l’archétype {archetype} !", + "annual_report.summary.share_on_mastodon": "Partager sur Mastodon", "attachments_list.unprocessed": "(non traité)", "audio.hide": "Masquer l'audio", "block_modal.remote_users_caveat": "Nous allons demander au serveur {domain} de respecter votre décision. Cependant, ce respect n'est pas garanti, car certains serveurs peuvent gérer différemment les blocages. Les messages publics peuvent rester visibles par les utilisateur·rice·s non connecté·e·s.", @@ -152,6 +184,8 @@ "bundle_modal_error.close": "Fermer", "bundle_modal_error.message": "Un problème s'est produit lors du chargement de cet écran.", "bundle_modal_error.retry": "Réessayer", + "carousel.current": "Diapositive {current, number} / {max, number}", + "carousel.slide": "Diapositive {current, number} de {max, number}", "closed_registrations.other_server_instructions": "Puisque Mastodon est décentralisé, vous pouvez créer un compte sur un autre serveur et interagir quand même avec celui-ci.", "closed_registrations_modal.description": "Créer un compte sur {domain} est actuellement impossible, néanmoins souvenez-vous que vous n'avez pas besoin d'un compte spécifiquement sur {domain} pour utiliser Mastodon.", "closed_registrations_modal.find_another_server": "Trouver un autre serveur", @@ -168,6 +202,8 @@ "column.edit_list": "Modifier la liste", "column.favourites": "Favoris", "column.firehose": "Flux en direct", + "column.firehose_local": "Flux en direct pour ce serveur", + "column.firehose_singular": "Flux en direct", "column.follow_requests": "Demandes d'abonnement", "column.home": "Accueil", "column.list_members": "Gérer les membres de la liste", @@ -187,6 +223,7 @@ "community.column_settings.local_only": "Local seulement", "community.column_settings.media_only": "Média uniquement", "community.column_settings.remote_only": "Distant seulement", + "compose.error.blank_post": "Le message ne peut être laissé vide.", "compose.language.change": "Changer de langue", "compose.language.search": "Rechercher des langues...", "compose.published.body": "Message Publié.", @@ -239,6 +276,11 @@ "confirmations.missing_alt_text.secondary": "Publier quand-même", "confirmations.missing_alt_text.title": "Ajouter un texte alternatif?", "confirmations.mute.confirm": "Masquer", + "confirmations.private_quote_notify.cancel": "Retour à l'édition", + "confirmations.private_quote_notify.confirm": "Publier", + "confirmations.private_quote_notify.do_not_show_again": "Ne plus afficher ce message", + "confirmations.private_quote_notify.message": "La personne citée et celles mentionnées seront notifiées et pourront voir le message, même si elles ne vous suivent pas.", + "confirmations.private_quote_notify.title": "Partager avec les personnes abonnées et mentionnées ?", "confirmations.quiet_post_quote_info.dismiss": "Ne plus me rappeler", "confirmations.quiet_post_quote_info.got_it": "Compris", "confirmations.quiet_post_quote_info.message": "Lorsque vous citez un message public silencieux, votre message sera caché des fils tendances.", @@ -252,9 +294,12 @@ "confirmations.revoke_quote.confirm": "Retirer la publication", "confirmations.revoke_quote.message": "Cette action ne peut pas être annulée.", "confirmations.revoke_quote.title": "Retirer la publication ?", + "confirmations.unblock.confirm": "Débloquer", + "confirmations.unblock.title": "Débloquer {name} ?", "confirmations.unfollow.confirm": "Ne plus suivre", - "confirmations.unfollow.message": "Voulez-vous vraiment vous désabonner de {name} ?", - "confirmations.unfollow.title": "Se désabonner de l'utilisateur·rice ?", + "confirmations.unfollow.title": "Ne plus suivre {name} ?", + "confirmations.withdraw_request.confirm": "Rejeter la demande", + "confirmations.withdraw_request.title": "Rejeter la demande de suivre {name} ?", "content_warning.hide": "Masquer le message", "content_warning.show": "Montrer quand même", "content_warning.show_more": "Montrer plus", @@ -325,6 +370,7 @@ "empty_column.bookmarked_statuses": "Vous n'avez pas de message en marque-page. Lorsque vous en ajouterez un, il apparaîtra ici.", "empty_column.community": "Le fil public local est vide. Écrivez donc quelque chose pour le remplir !", "empty_column.direct": "Vous n'avez pas encore de mentions privées. Quand vous en enverrez ou recevrez, elles apparaîtront ici.", + "empty_column.disabled_feed": "Ce flux a été désactivé par les administrateur·rice·s de votre serveur.", "empty_column.domain_blocks": "Il n’y a aucun domaine bloqué pour le moment.", "empty_column.explore_statuses": "Rien n'est en tendance pour le moment. Revenez plus tard !", "empty_column.favourited_statuses": "Vous n’avez pas encore de message en favori. Lorsque vous en ajouterez un, il apparaîtra ici.", @@ -338,6 +384,7 @@ "empty_column.notification_requests": "C'est fini ! Il n'y a plus rien ici. Lorsque vous recevez de nouvelles notifications, elles apparaitront ici conformément à vos préférences.", "empty_column.notifications": "Vous n’avez pas encore de notification. Interagissez avec d’autres personnes pour débuter la conversation.", "empty_column.public": "Il n’y a rien ici ! Écrivez quelque chose publiquement, ou bien suivez manuellement des personnes d’autres serveurs pour remplir le fil public", + "error.no_hashtag_feed_access": "Rejoindre ou se connecter pour voir et suivre cet hashtag.", "error.unexpected_crash.explanation": "En raison d’un bug dans notre code ou d’un problème de compatibilité avec votre navigateur, cette page n’a pas pu être affichée correctement.", "error.unexpected_crash.explanation_addons": "Cette page n’a pas pu être affichée correctement. Cette erreur est probablement causée par une extension de navigateur ou des outils de traduction automatique.", "error.unexpected_crash.next_steps": "Essayez de rafraîchir la page. Si cela n’aide pas, vous pouvez toujours utiliser Mastodon via un autre navigateur ou une application native.", @@ -346,14 +393,12 @@ "errors.unexpected_crash.report_issue": "Signaler le problème", "explore.suggested_follows": "Personnes", "explore.title": "Tendances", - "explore.trending_links": "Nouvelles", + "explore.trending_links": "Actualités", "explore.trending_statuses": "Messages", "explore.trending_tags": "Hashtags", + "featured_carousel.current": "Message {current, number} / {max, number}", "featured_carousel.header": "{count, plural, one {Pinned Post} other {Pinned Posts}}", - "featured_carousel.next": "Suivant", - "featured_carousel.post": "Poste", - "featured_carousel.previous": "Précédent", - "featured_carousel.slide": "{index} de {total}", + "featured_carousel.slide": "Message {current, number} de {max, number}", "filter_modal.added.context_mismatch_explanation": "Cette catégorie de filtre ne s'applique pas au contexte dans lequel vous avez accédé à ce message. Si vous voulez que le message soit filtré dans ce contexte également, vous devrez modifier le filtre.", "filter_modal.added.context_mismatch_title": "Incompatibilité du contexte !", "filter_modal.added.expired_explanation": "Cette catégorie de filtre a expiré, vous devrez modifier la date d'expiration pour qu'elle soit appliquée.", @@ -396,6 +441,9 @@ "follow_suggestions.who_to_follow": "Qui suivre", "followed_tags": "Hashtags suivis", "footer.about": "À propos", + "footer.about_mastodon": "À propos de Mastodon", + "footer.about_server": "À propos de {domain}", + "footer.about_this_server": "À propos", "footer.directory": "Annuaire des profils", "footer.get_app": "Télécharger l’application", "footer.keyboard_shortcuts": "Raccourcis clavier", @@ -497,6 +545,7 @@ "keyboard_shortcuts.toggle_hidden": "Déplier/replier le texte derrière un CW", "keyboard_shortcuts.toggle_sensitivity": "Afficher/cacher les médias", "keyboard_shortcuts.toot": "Commencer un nouveau message", + "keyboard_shortcuts.top": "Mettre en tête de liste", "keyboard_shortcuts.translate": "traduire un message", "keyboard_shortcuts.unfocus": "Quitter la zone de rédaction/barre de recherche", "keyboard_shortcuts.up": "Monter dans la liste", @@ -510,7 +559,7 @@ "limited_account_hint.action": "Afficher le profil quand même", "limited_account_hint.title": "Ce profil a été masqué par la modération de {domain}.", "link_preview.author": "Par {name}", - "link_preview.more_from_author": "Plus via {name}", + "link_preview.more_from_author": "Voir plus de {name}", "link_preview.shares": "{count, plural, one {{counter} message} other {{counter} messages}}", "lists.add_member": "Ajouter", "lists.add_to_list": "Ajouter à la liste", @@ -548,7 +597,7 @@ "mute_modal.they_can_mention_and_follow": "Ils peuvent vous mentionner et vous suivre, mais vous ne les verrez pas.", "mute_modal.they_wont_know": "Ils ne sauront pas qu'ils ont été rendus silencieux.", "mute_modal.title": "Rendre cet utilisateur silencieux ?", - "mute_modal.you_wont_see_mentions": "Vous ne verrez pas les publications qui le mentionne.", + "mute_modal.you_wont_see_mentions": "Vous ne verrez pas les messages qui le mentionne.", "mute_modal.you_wont_see_posts": "Il peut toujours voir vos publications, mais vous ne verrez pas les siennes.", "navigation_bar.about": "À propos", "navigation_bar.account_settings": "Mot de passe et sécurité", @@ -601,7 +650,7 @@ "notification.follow_request.name_and_others": "{name} et {count, plural, one {# autre} other {# autres}} ont demandé à vous suivre", "notification.label.mention": "Mention", "notification.label.private_mention": "Mention privée", - "notification.label.private_reply": "Répondre en privé", + "notification.label.private_reply": "Réponse privée", "notification.label.quote": "{name} a cité votre publication", "notification.label.reply": "Réponse", "notification.mention": "Mention", @@ -741,11 +790,13 @@ "privacy.quote.disabled": "{visibility}, citations désactivées", "privacy.quote.limited": "{visibility}, citations limitées", "privacy.unlisted.additional": "Se comporte exactement comme « public », sauf que le message n'apparaîtra pas dans les flux en direct, les hashtags, explorer ou la recherche Mastodon, même si vous les avez activé au niveau de votre compte.", - "privacy.unlisted.long": "Caché des résultats de recherche de Mastodon, aux tendances et aux échéanciers publics", + "privacy.unlisted.long": "Caché des résultats de recherche de Mastodon, des tendances et des fils publics", "privacy.unlisted.short": "Public discret", "privacy_policy.last_updated": "Dernière mise à jour {date}", "privacy_policy.title": "Politique de confidentialité", + "quote_error.edit": "Les citations ne peuvent pas être ajoutés lors de l'édition d'un message.", "quote_error.poll": "Les citations ne sont pas autorisées avec les sondages.", + "quote_error.private_mentions": "La citation n'est pas autorisée avec les mentions privées.", "quote_error.quote": "Une seule citation à la fois est autorisée.", "quote_error.unauthorized": "Vous n'êtes pas autorisé⋅e à citer cette publication.", "quote_error.upload": "La citation n'est pas autorisée avec un média joint.", @@ -791,7 +842,7 @@ "report.reasons.dislike": "Cela ne me plaît pas", "report.reasons.dislike_description": "Ce n'est pas quelque chose que vous voulez voir", "report.reasons.legal": "C'est illégal", - "report.reasons.legal_description": "Vous pensez que cela viole la loi de votre pays ou celui du serveur", + "report.reasons.legal_description": "Vous pensez que cela viole la loi de votre pays ou de celui du serveur", "report.reasons.other": "Pour une autre raison", "report.reasons.other_description": "Le problème ne correspond pas aux autres catégories", "report.reasons.spam": "C'est du spam", @@ -864,8 +915,13 @@ "status.cancel_reblog_private": "Annuler le partage", "status.cannot_quote": "Vous n'êtes pas autorisé à citer ce message", "status.cannot_reblog": "Ce message ne peut pas être partagé", - "status.context.load_new_replies": "Nouvelles réponses disponibles", - "status.context.loading": "Vérification de plus de réponses", + "status.contains_quote": "Contient la citation", + "status.context.loading": "Chargement de réponses supplémentaires", + "status.context.loading_error": "Impossible de charger les nouvelles réponses", + "status.context.loading_success": "De nouvelles réponses ont été chargées", + "status.context.more_replies_found": "Plus de réponses trouvées", + "status.context.retry": "Réessayer", + "status.context.show": "Montrer", "status.continued_thread": "Suite du fil", "status.copy": "Copier le lien vers le message", "status.delete": "Supprimer", @@ -878,7 +934,7 @@ "status.edited_x_times": "Modifié {count, plural, one {{count} fois} other {{count} fois}}", "status.embed": "Obtenir le code d'intégration", "status.favourite": "Ajouter aux favoris", - "status.favourites": "{count, plural, one {favori} other {favoris}}", + "status.favourites_count": "{count, plural, one {{counter} favori} other {{counter} favoris}}", "status.filter": "Filtrer ce message", "status.history.created": "créé par {name} {date}", "status.history.edited": "modifié par {name} {date}", @@ -894,25 +950,33 @@ "status.pin": "Épingler sur le profil", "status.quote": "Citer", "status.quote.cancel": "Annuler la citation", + "status.quote_error.blocked_account_hint.title": "Ce message est masqué car vous avez bloqué @{name}.", + "status.quote_error.blocked_domain_hint.title": "Ce message est masqué car vous avez bloqué {domain}.", "status.quote_error.filtered": "Caché en raison de l'un de vos filtres", + "status.quote_error.limited_account_hint.action": "Afficher quand même", + "status.quote_error.limited_account_hint.title": "Ce profil a été masqué par la modération de {domain}.", + "status.quote_error.muted_account_hint.title": "Ce message est masqué car vous avez Mis en sourdine @{name}.", "status.quote_error.not_available": "Publication non disponible", "status.quote_error.pending_approval": "Publication en attente", "status.quote_error.pending_approval_popout.body": "Sur Mastodon, vous pouvez contrôler si quelqu'un peut vous citer. Ce message est en attente pendant que nous recevons l'approbation de l'auteur original.", "status.quote_error.revoked": "Post supprimé par l'auteur", "status.quote_followers_only": "Seul·e·s les abonné·e·s peuvent citer cette publication", "status.quote_manual_review": "L'auteur va vérifier manuellement", + "status.quote_noun": "Citation", "status.quote_policy_change": "Changer qui peut vous citer", "status.quote_post_author": "A cité un message par @{name}", "status.quote_private": "Les publications privées ne peuvent pas être citées", - "status.quotes": " {count, plural, one {quote} other {quotes}}", "status.quotes.empty": "Personne n'a encore cité ce message. Quand quelqu'un le fera, il apparaîtra ici.", - "status.read_more": "En savoir plus", + "status.quotes.local_other_disclaimer": "Les citations rejetées par l'auteur ne seront pas affichées.", + "status.quotes.remote_other_disclaimer": "Seules les citations de {domain} sont garanties d'être affichées ici. Les citations rejetées par l'auteur ne seront pas affichées.", + "status.quotes_count": "{count, plural, one {{counter} citation} other {{counter} citations}}", + "status.read_more": "Lire la suite", "status.reblog": "Partager", "status.reblog_or_quote": "Boost ou citation", "status.reblog_private": "Partagez à nouveau avec vos abonnés", "status.reblogged_by": "{name} a partagé", - "status.reblogs": "{count, plural, one {boost} other {boosts}}", "status.reblogs.empty": "Personne n’a encore partagé ce message. Lorsque quelqu’un le fera, il apparaîtra ici.", + "status.reblogs_count": "{count, plural, one {{counter} partage} other {{counter} partages}}", "status.redraft": "Supprimer et réécrire", "status.remove_bookmark": "Retirer des marque-pages", "status.remove_favourite": "Retirer des favoris", @@ -986,13 +1050,15 @@ "video.volume_down": "Baisser le volume", "video.volume_up": "Augmenter le volume", "visibility_modal.button_title": "Définir la visibilité", + "visibility_modal.direct_quote_warning.text": "Si vous enregistrez les paramètres actuels, la citation sera convertie en lien.", + "visibility_modal.direct_quote_warning.title": "Les citations ne peuvent pas être intégrées dans les mentions privées", "visibility_modal.header": "Visibilité et interactions", "visibility_modal.helper.direct_quoting": "Les mentions privées rédigées sur Mastodon ne peuvent pas être citées par d'autres personnes.", "visibility_modal.helper.privacy_editing": "La visibilité ne peut pas être modifiée après la publication d'un message.", "visibility_modal.helper.privacy_private_self_quote": "Les auto-citations de messages privés ne peuvent pas être rendues publiques.", "visibility_modal.helper.private_quoting": "Les posts accessible uniquement par les followers sur Mastodon ne peuvent être cités par d'autres personnes.", - "visibility_modal.helper.unlisted_quoting": "Lorsque les gens vous citent, leur message sera également caché dans les calendriers tendances.", - "visibility_modal.instructions": "Contrôlez qui peut interagir avec ce post. Vous pouvez également appliquer ces paramètres à tous les futurs messages en allant dans Préférences > Valeurs par d'éfaut de publication.", + "visibility_modal.helper.unlisted_quoting": "Lorsque les gens vous citent, leur message sera également caché des fils tendances.", + "visibility_modal.instructions": "Contrôlez qui peut interagir avec ce post. Vous pouvez également appliquer ces paramètres à tous les futurs messages en allant dans Préférences > Valeurs par défaut de publication.", "visibility_modal.privacy_label": "Visibilité", "visibility_modal.quote_followers": "Abonné·e·s seulement", "visibility_modal.quote_label": "Autoriser les citations pour", diff --git a/app/javascript/mastodon/locales/fy.json b/app/javascript/mastodon/locales/fy.json index 31b5196f3edc0b..2a98f2978218b2 100644 --- a/app/javascript/mastodon/locales/fy.json +++ b/app/javascript/mastodon/locales/fy.json @@ -70,7 +70,6 @@ "account.posts_with_replies": "Berjochten en reaksjes", "account.remove_from_followers": "{name} as folger fuortsmite", "account.report": "@{name} rapportearje", - "account.requested": "Wacht op goedkarring. Klik om it folchfersyk te annulearjen", "account.requested_follow": "{name} hat dy in folchfersyk stjoerd", "account.requests_to_follow_you": "Fersiken om jo te folgjen", "account.share": "Profyl fan @{name} diele", @@ -108,25 +107,11 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Beskriuw dit foar blinen en fisueel beheinde…", "alt_text_modal.done": "Klear", "announcement.announcement": "Oankundiging", - "annual_report.summary.archetype.booster": "De cool-hunter", - "annual_report.summary.archetype.lurker": "De lurker", - "annual_report.summary.archetype.oracle": "It orakel", - "annual_report.summary.archetype.pollster": "De opinypeiler", - "annual_report.summary.archetype.replier": "De sosjale flinter", - "annual_report.summary.followers.followers": "folgers", - "annual_report.summary.followers.total": "totaal {count}", - "annual_report.summary.here_it_is": "Jo jieroersjoch foar {year}:", - "annual_report.summary.highlighted_post.by_favourites": "berjocht mei de measte favoriten", - "annual_report.summary.highlighted_post.by_reblogs": "berjocht mei de measte boosts", - "annual_report.summary.highlighted_post.by_replies": "berjocht mei de measte reaksjes", - "annual_report.summary.highlighted_post.possessive": "{name}’s", "annual_report.summary.most_used_app.most_used_app": "meast brûkte app", "annual_report.summary.most_used_hashtag.most_used_hashtag": "meast brûkte hashtag", - "annual_report.summary.most_used_hashtag.none": "Gjin", "annual_report.summary.new_posts.new_posts": "nije berjochten", "annual_report.summary.percentile.text": "Hjirmei hearre jo ta de top fan {domain}.", "annual_report.summary.percentile.we_wont_tell_bernie": "Wy sille Bernie neat fertelle.", - "annual_report.summary.thanks": "Tank dat jo part binne fan Mastodon!", "attachments_list.unprocessed": "(net ferwurke)", "audio.hide": "Audio ferstopje", "block_modal.remote_users_caveat": "Wy freegje de server {domain} om jo beslút te respektearjen. It neilibben hjirfan is echter net garandearre, omdat guon servers blokkaden oars ynterpretearje kinne. Iepenbiere berjochten binne mooglik noch hieltyd sichtber foar net-oanmelde brûkers.", @@ -246,8 +231,6 @@ "confirmations.remove_from_followers.message": "{name} sil jo net mear folgje. Binne jo wis dat jo trochgean wolle?", "confirmations.remove_from_followers.title": "Folger fuortsmite?", "confirmations.unfollow.confirm": "Net mear folgje", - "confirmations.unfollow.message": "Binne jo wis dat jo {name} net mear folgje wolle?", - "confirmations.unfollow.title": "Brûker net mear folgje?", "content_warning.hide": "Berjocht ferstopje", "content_warning.show": "Dochs toane", "content_warning.show_more": "Mear toane", @@ -342,10 +325,6 @@ "explore.trending_statuses": "Berjochten", "explore.trending_tags": "Hashtags", "featured_carousel.header": "{count, plural, one {Fêstset berjocht} other {Fêstsette berjochten}}", - "featured_carousel.next": "Folgjende", - "featured_carousel.post": "Berjocht", - "featured_carousel.previous": "Foarige", - "featured_carousel.slide": "{index} fan {total}", "filter_modal.added.context_mismatch_explanation": "Dizze filterkategory is net fan tapassing op de kontekst wêryn jo dit berjocht benadere hawwe. As jo wolle dat it berjocht ek yn dizze kontekst filtere wurdt, moatte jo it filter bewurkje.", "filter_modal.added.context_mismatch_title": "Kontekst komt net oerien!", "filter_modal.added.expired_explanation": "Dizze filterkategory is ferrûn. Jo moatte de ferrindatum wizigje om de kategory tapasse te kinnen.", @@ -834,8 +813,6 @@ "status.bookmark": "Blêdwizer tafoegje", "status.cancel_reblog_private": "Net langer booste", "status.cannot_reblog": "Dit berjocht kin net boost wurde", - "status.context.load_new_replies": "Nije reaksjes beskikber", - "status.context.loading": "Op nije reaksjes oan it kontrolearjen", "status.continued_thread": "Ferfolgje it petear", "status.copy": "Copy link to status", "status.delete": "Fuortsmite", @@ -847,7 +824,6 @@ "status.edited_x_times": "{count, plural, one {{count} kear} other {{count} kearen}} bewurke", "status.embed": "Koade om op te nimmen", "status.favourite": "Favoryt", - "status.favourites": "{count, plural, one {favoryt} other {favoriten}}", "status.filter": "Dit berjocht filterje", "status.history.created": "{name} makke dit {date}", "status.history.edited": "{name} bewurke dit {date}", @@ -865,7 +841,6 @@ "status.read_more": "Mear ynfo", "status.reblog": "Booste", "status.reblogged_by": "{name} hat boost", - "status.reblogs": "{count, plural, one {boost} other {boosts}}", "status.reblogs.empty": "Net ien hat dit berjocht noch boost. Wannear’t ien dit docht, falt dat hjir te sjen.", "status.redraft": "Fuortsmite en opnij opstelle", "status.remove_bookmark": "Blêdwizer fuortsmite", diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json index 4ede71c4870b90..f1c51200ff30b3 100644 --- a/app/javascript/mastodon/locales/ga.json +++ b/app/javascript/mastodon/locales/ga.json @@ -1,10 +1,10 @@ { - "about.blocks": "Freastalaithe faoi stiúir", + "about.blocks": "Freastalaithe modhnaithe", "about.contact": "Teagmháil:", "about.default_locale": "Réamhshocrú", "about.disclaimer": "Bogearra foinse oscailte saor in aisce is ea Mastodon, agus is le Mastodon gGmbH an trádmharc.", - "about.domain_blocks.no_reason_available": "Níl an fáth ar fáil", - "about.domain_blocks.preamble": "Go hiondúil, tugann Mastadán cead duit a bheith ag plé le húsáideoirí as freastalaí ar bith eile sa chomhchruinne agus a gcuid inneachair a fheiceáil. Seo iad na heisceachtaí a rinneadh ar an bhfreastalaí áirithe seo.", + "about.domain_blocks.no_reason_available": "Cúis nach bhfuil ar fáil", + "about.domain_blocks.preamble": "Go ginearálta, tugann Mastodon deis duit ábhar a fheiceáil ó aon fhreastalaí eile sa fediverse agus idirghníomhú leo. Seo iad na heisceachtaí atá déanta ar an bhfreastalaí seo.", "about.domain_blocks.silenced.explanation": "Go hiondúil ní fheicfidh tú próifílí ná inneachar ón bhfreastalaí seo, ach amháin má bhíonn tú á lorg nó má ghlacann tú lena leanúint d'aon ghnó.", "about.domain_blocks.silenced.title": "Teoranta", "about.domain_blocks.suspended.explanation": "Ní dhéanfar aon sonra ón fhreastalaí seo a phróiseáil, a stóráil ná a mhalartú, rud a fhágann nach féidir aon teagmháil ná aon chumarsáid a dhéanamh le húsáideoirí ón fhreastalaí seo.", @@ -15,19 +15,20 @@ "about.rules": "Rialacha an fhreastalaí", "account.account_note_header": "Nóta pearsanta", "account.add_or_remove_from_list": "Cuir Le nó Bain De na liostaí", - "account.badges.bot": "Bota", + "account.badges.bot": "Uathoibrithe", "account.badges.group": "Grúpa", - "account.block": "Déan cosc ar @{name}", + "account.block": "Bac @{name}", "account.block_domain": "Bac ainm fearainn {domain}", - "account.block_short": "Bloc", + "account.block_short": "Bac", "account.blocked": "Bactha", "account.blocking": "Ag Blocáil", - "account.cancel_follow_request": "Éirigh as iarratas leanta", + "account.cancel_follow_request": "Cealaigh leanúint", "account.copy": "Cóipeáil nasc chuig an bpróifíl", "account.direct": "Luaigh @{name} go príobháideach", - "account.disable_notifications": "Éirigh as ag cuir mé in eol nuair bpostálann @{name}", + "account.disable_notifications": "Stop ag cur in iúl dom nuair a dhéanann @{name} postáil", "account.domain_blocking": "Fearann a bhlocáil", "account.edit_profile": "Cuir an phróifíl in eagar", + "account.edit_profile_short": "Cuir in Eagar", "account.enable_notifications": "Cuir mé in eol nuair bpostálann @{name}", "account.endorse": "Cuir ar an phróifíl mar ghné", "account.familiar_followers_many": "Ina dhiaidh sin ag {name1}, {name2}, agus {othersCount, plural, \n one {duine eile atá aithnid duit} \n two {# duine eile atá aithnid duit} \n few {# dhuine eile atá aithnid duit} \n many {# nduine eile atá aithnid duit} \n other {# duine eile atá aithnid duit}}", @@ -39,7 +40,12 @@ "account.featured_tags.last_status_at": "Postáil is déanaí ar {date}", "account.featured_tags.last_status_never": "Gan aon phoist", "account.follow": "Lean", - "account.follow_back": "Leanúint ar ais", + "account.follow_back": "Lean ar ais", + "account.follow_back_short": "Lean ar ais", + "account.follow_request": "Iarratas chun leanúint", + "account.follow_request_cancel": "Cealaigh an t-iarratas", + "account.follow_request_cancel_short": "Cealaigh", + "account.follow_request_short": "Iarratas", "account.followers": "Leantóirí", "account.followers.empty": "Ní leanann éinne an t-úsáideoir seo fós.", "account.followers_counter": "{count, plural, one {{counter} leantóir} other {{counter} leantóirí}}", @@ -50,7 +56,7 @@ "account.follows_you": "Leanann tú", "account.go_to_profile": "Téigh go dtí próifíl", "account.hide_reblogs": "Folaigh moltaí ó @{name}", - "account.in_memoriam": "Cuimhneachán.", + "account.in_memoriam": "Ón tseanaimsir.", "account.joined_short": "Cláraithe", "account.languages": "Athraigh teangacha foscríofa", "account.link_verified_on": "Seiceáladh úinéireacht an naisc seo ar {date}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Postálacha agus freagraí", "account.remove_from_followers": "Bain {name} de na leantóirí", "account.report": "Tuairiscigh @{name}", - "account.requested": "Ag fanacht le ceadú. Cliceáil chun an iarratas leanúnaí a chealú", "account.requested_follow": "D'iarr {name} ort do chuntas a leanúint", "account.requests_to_follow_you": "Iarratais chun tú a leanúint", "account.share": "Roinn próifíl @{name}", @@ -108,25 +113,52 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Déan cur síos air seo do dhaoine a bhfuil lagú amhairc orthu…", "alt_text_modal.done": "Déanta", "announcement.announcement": "Fógra", - "annual_report.summary.archetype.booster": "An sealgair fionnuar", - "annual_report.summary.archetype.lurker": "An lurker", - "annual_report.summary.archetype.oracle": "An oracal", - "annual_report.summary.archetype.pollster": "An pollaire", - "annual_report.summary.archetype.replier": "An féileacán sóisialta", - "annual_report.summary.followers.followers": "leanúna", - "annual_report.summary.followers.total": "{count} san iomlán", - "annual_report.summary.here_it_is": "Seo do {year} faoi athbhreithniú:", - "annual_report.summary.highlighted_post.by_favourites": "post is fearr leat", - "annual_report.summary.highlighted_post.by_reblogs": "post is treisithe", - "annual_report.summary.highlighted_post.by_replies": "post leis an líon is mó freagraí", - "annual_report.summary.highlighted_post.possessive": "{name}'s", + "annual_report.announcement.action_build": "Tóg mo Wrapstodon", + "annual_report.announcement.action_dismiss": "Ní raibh maith agat", + "annual_report.announcement.action_view": "Féach ar mo Wrapstodon", + "annual_report.announcement.description": "Faigh tuilleadh eolais faoi do rannpháirtíocht ar Mastodon le bliain anuas.", + "annual_report.announcement.title": "Tá Wrapstodon {year} tagtha", + "annual_report.nav_item.badge": "Nua", + "annual_report.shared_page.donate": "Tabhair Síntiús", + "annual_report.shared_page.footer": "Gineadh le {heart} ag foireann Mastodon", + "annual_report.shared_page.footer_server_info": "Úsáideann {username} {domain}, ceann de go leor pobail atá faoi thiomáint ag Mastodon.", + "annual_report.summary.archetype.booster.desc_public": "D’fhan {name} ag cuardach postálacha le cur chun cinn, ag cur le cruthaitheoirí eile le cuspóir foirfe.", + "annual_report.summary.archetype.booster.desc_self": "D’fhan tú ag cuardach postálacha le borradh a chur fúthu, ag cur le cruthaitheoirí eile le cuspóir foirfe.", + "annual_report.summary.archetype.booster.name": "An Saighdeoir", + "annual_report.summary.archetype.die_drei_fragezeichen": "???", + "annual_report.summary.archetype.lurker.desc_public": "Tá a fhios againn go raibh {name} amuigh ansin, áit éigin, ag baint taitnimh as Mastodon ar a mbealach ciúin féin.", + "annual_report.summary.archetype.lurker.desc_self": "Tá a fhios againn go raibh tú amuigh ansin, áit éigin, ag baint taitnimh as Mastodon ar do bhealach ciúin féin.", + "annual_report.summary.archetype.lurker.name": "An Stoiceach", + "annual_report.summary.archetype.oracle.desc_public": "Chruthaigh {name} níos mó postálacha nua ná freagraí, rud a choinnigh Mastodon úr agus dírithe ar an todhchaí.", + "annual_report.summary.archetype.oracle.desc_self": "Chruthaigh tú níos mó postálacha nua ná freagraí, rud a choinnigh Mastodon úr agus dírithe ar an todhchaí.", + "annual_report.summary.archetype.oracle.name": "An tOracal", + "annual_report.summary.archetype.pollster.desc_public": "Chruthaigh {name} níos mó pobalbhreitheanna ná cineálacha poist eile, rud a chothaigh fiosracht faoi Mastodon.", + "annual_report.summary.archetype.pollster.desc_self": "Chruthaigh tú níos mó pobalbhreitheanna ná cineálacha poist eile, rud a chothaigh fiosracht ar Mastodon.", + "annual_report.summary.archetype.pollster.name": "An tIontasóir", + "annual_report.summary.archetype.replier.desc_public": "Is minic a d’fhreagair {name} poist daoine eile, rud a spreag plé nua i Mastodon.", + "annual_report.summary.archetype.replier.desc_self": "Is minic a d’fhreagair tú poist daoine eile, ag maolú Mastodon le plé nua.", + "annual_report.summary.archetype.replier.name": "An Féileacán", + "annual_report.summary.archetype.reveal": "Nocht mo sheandálaíocht", + "annual_report.summary.archetype.reveal_description": "Go raibh maith agat as bheith mar chuid de Mastodon! Tá sé in am a fháil amach cén t-archetíopa a léirigh tú i {year}.", + "annual_report.summary.archetype.title_public": "Seanchineál {name}", + "annual_report.summary.archetype.title_self": "Do sheandálaíocht", + "annual_report.summary.close": "Dún", + "annual_report.summary.copy_link": "Cóipeáil nasc", + "annual_report.summary.followers.new_followers": "{count, plural, one {leantóir nua} two {leantóirí nua} few {leantóirí nua} many {leantóirí nua} other {leantóirí nua}}", + "annual_report.summary.highlighted_post.boost_count": "Borradh a tugadh faoin bpost seo {count, plural, one {uair amháin} two {# uaire} few {# uaire} many {# uaire} other {# uaire}}.", + "annual_report.summary.highlighted_post.favourite_count": "Cuireadh an post seo leis na ceanáin {count, plural, one {uair amháin} two {# uaire} few {# uaire} many {# uaire} other {# uaire}}.", + "annual_report.summary.highlighted_post.reply_count": "Fuair ​​an post seo {count, plural, one {freagra amháin} two {# freagraí} few {# freagraí} many {# freagraí} other {# freagraí}}.", + "annual_report.summary.highlighted_post.title": "An post is mó tóir", "annual_report.summary.most_used_app.most_used_app": "aip is mó a úsáidtear", "annual_report.summary.most_used_hashtag.most_used_hashtag": "hashtag is mó a úsáidtear", - "annual_report.summary.most_used_hashtag.none": "Dada", + "annual_report.summary.most_used_hashtag.used_count": "Chuir tú an haischlib seo i {count, plural, one {post amháin} two {# poist} few {# poist} many {# poist} other {# poist}}.", + "annual_report.summary.most_used_hashtag.used_count_public": "Chuir {name} an haischlib seo i {count, plural, one {post amháin} two {# poist} few {# poist} many {# poist} other {# poist}}.", "annual_report.summary.new_posts.new_posts": "postanna nua", "annual_report.summary.percentile.text": "Cuireann sé sin i mbarr úsáideoirí {domain}. thú", "annual_report.summary.percentile.we_wont_tell_bernie": "Ní inseoidh muid do Bernie.", - "annual_report.summary.thanks": "Go raibh maith agat as a bheith mar chuid de Mastodon!", + "annual_report.summary.share_elsewhere": "Roinn in áit eile", + "annual_report.summary.share_message": "Fuair ​​mé an t-archetíopa {archetype}!", + "annual_report.summary.share_on_mastodon": "Comhroinn ar Mastodon", "attachments_list.unprocessed": "(neamhphróiseáilte)", "audio.hide": "Cuir fuaim i bhfolach", "block_modal.remote_users_caveat": "Iarrfaimid ar an bhfreastalaí {domain} meas a bheith agat ar do chinneadh. Mar sin féin, ní ráthaítear comhlíonadh toisc go bhféadfadh roinnt freastalaithe bloic a láimhseáil ar bhealach difriúil. Seans go mbeidh postálacha poiblí fós le feiceáil ag úsáideoirí nach bhfuil logáilte isteach.", @@ -152,6 +184,8 @@ "bundle_modal_error.close": "Dún", "bundle_modal_error.message": "Tharla earráid agus an scáileán seo á lódáil.", "bundle_modal_error.retry": "Bain triail as arís", + "carousel.current": "Sleamhnán {current, number} / {max, number}", + "carousel.slide": "Sleamhnán {current, number} of {max, number}", "closed_registrations.other_server_instructions": "Mar rud díláraithe Mastodon, is féidir leat cuntas a chruthú ar seirbheálaí eile ach fós idirghníomhaigh leis an ceann seo.", "closed_registrations_modal.description": "Ní féidir cuntas a chruthú ar {domain} faoi láthair, ach cuimhnigh nach gá go mbeadh cuntas agat go sonrach ar {domain} chun Mastodon a úsáid.", "closed_registrations_modal.find_another_server": "Faigh freastalaí eile", @@ -168,6 +202,8 @@ "column.edit_list": "Cuir liosta in eagar", "column.favourites": "Ceanáin", "column.firehose": "Fothaí beo", + "column.firehose_local": "Fotha beo don fhreastalaí seo", + "column.firehose_singular": "Beo-bheatha", "column.follow_requests": "Iarratais leanúnaí", "column.home": "Baile", "column.list_members": "Bainistigh baill liosta", @@ -187,6 +223,7 @@ "community.column_settings.local_only": "Áitiúil amháin", "community.column_settings.media_only": "Meáin Amháin", "community.column_settings.remote_only": "Cian amháin", + "compose.error.blank_post": "Ní féidir an post a fhágáil bán.", "compose.language.change": "Athraigh teanga", "compose.language.search": "Cuardaigh teangacha...", "compose.published.body": "Post foilsithe.", @@ -233,12 +270,17 @@ "confirmations.follow_to_list.title": "Lean an t-úsáideoir?", "confirmations.logout.confirm": "Logáil amach", "confirmations.logout.message": "An bhfuil tú cinnte gur mhaith leat logáil amach?", - "confirmations.logout.title": "Logáil Amach?", + "confirmations.logout.title": "Logáil amach?", "confirmations.missing_alt_text.confirm": "Cuir téacs alt leis", "confirmations.missing_alt_text.message": "Tá meáin gan alt téacs i do phostáil. Má chuirtear tuairiscí leis, cabhraíonn sé seo leat d’inneachar a rochtain do níos mó daoine.", "confirmations.missing_alt_text.secondary": "Post ar aon nós", "confirmations.missing_alt_text.title": "Cuir téacs alt leis?", "confirmations.mute.confirm": "Balbhaigh", + "confirmations.private_quote_notify.cancel": "Ar ais chuig an eagarthóireacht", + "confirmations.private_quote_notify.confirm": "Foilsigh an post", + "confirmations.private_quote_notify.do_not_show_again": "Ná taispeáin an teachtaireacht seo dom arís", + "confirmations.private_quote_notify.message": "Cuirfear an duine atá á lua agat agus luanna eile ar an eolas agus beidh siad in ann do phost a fheiceáil, fiú mura bhfuil siad ag leanúint thú.", + "confirmations.private_quote_notify.title": "Roinn le leantóirí agus úsáideoirí a luadh?", "confirmations.quiet_post_quote_info.dismiss": "Ná cuir i gcuimhne dom arís", "confirmations.quiet_post_quote_info.got_it": "Tuigim é", "confirmations.quiet_post_quote_info.message": "Agus post poiblí ciúin á lua, beidh do phost i bhfolach ó amlínte treochta.", @@ -252,9 +294,12 @@ "confirmations.revoke_quote.confirm": "Bain postáil", "confirmations.revoke_quote.message": "Ní féidir an gníomh seo a chealú.", "confirmations.revoke_quote.title": "Bain postáil?", + "confirmations.unblock.confirm": "Díbhlocáil", + "confirmations.unblock.title": "Díbhlocáil {name}?", "confirmations.unfollow.confirm": "Ná lean", - "confirmations.unfollow.message": "An bhfuil tú cinnte gur mhaith leat {name} a dhíleanúint?", - "confirmations.unfollow.title": "Dílean ​​an t-úsáideoir?", + "confirmations.unfollow.title": "Díleanúint {name}?", + "confirmations.withdraw_request.confirm": "Iarratas ar tharraingt siar", + "confirmations.withdraw_request.title": "Iarratas chun {name} a leanúint a tharraingt siar?", "content_warning.hide": "Folaigh postáil", "content_warning.show": "Taispeáin ar aon nós", "content_warning.show_more": "Taispeáin níos mó", @@ -325,6 +370,7 @@ "empty_column.bookmarked_statuses": "Níl aon phostáil leabharmharcaithe agat fós. Nuair a dhéanann tú leabharmharc, beidh sé le feiceáil anseo.", "empty_column.community": "Tá an amlíne áitiúil folamh. Foilsigh rud éigin go poiblí le tús a chur le cúrsaí!", "empty_column.direct": "Níl aon tagairtí príobháideacha agat fós. Nuair a sheolann tú nó a gheobhaidh tú ceann, beidh sé le feiceáil anseo.", + "empty_column.disabled_feed": "Tá an fotha seo díchumasaithe ag riarthóirí do fhreastalaí.", "empty_column.domain_blocks": "Níl aon fearainn bhactha ann go fóill.", "empty_column.explore_statuses": "Níl rud ar bith ag treochtáil faoi láthair. Tar ar ais ar ball!", "empty_column.favourited_statuses": "Níl aon postálacha is fearr leat fós. Nuair is fearr leat ceann, beidh sé le feiceáil anseo.", @@ -338,6 +384,7 @@ "empty_column.notification_requests": "Gach soiléir! Níl aon rud anseo. Nuair a gheobhaidh tú fógraí nua, beidh siad le feiceáil anseo de réir do shocruithe.", "empty_column.notifications": "Níl aon fógraí agat fós. Nuair a dhéanann daoine eile idirghníomhú leat, feicfear anseo é.", "empty_column.public": "Faic anseo! Scríobh rud éigin go poiblí, nó lean úsáideoirí ar fhreastalaithe eile chun é a líonadh", + "error.no_hashtag_feed_access": "Bí linn nó logáil isteach chun an haischlib seo a fheiceáil agus a leanúint.", "error.unexpected_crash.explanation": "De bharr fabht inár gcód, nó fadhb le chomhoiriúnacht brabhsálaí, níorbh fhéadfadh an leathanach seo a léiriú i gceart.", "error.unexpected_crash.explanation_addons": "Ní taispeántar an leathanach seo mar is ceart. Is dócha go gcruthaíonn breiseán brabhsálaí nó uirlisí uathaistriúcháin an fhadhb seo.", "error.unexpected_crash.next_steps": "Bain triail as an leathanach a athnuachan. Mura gcabhraíonn sé sin, seans go mbeidh tú fós in ann Mastodon a úsáid trí bhrabhsálaí nó aip dhúchais eile.", @@ -349,11 +396,9 @@ "explore.trending_links": "Nuacht", "explore.trending_statuses": "Postálacha", "explore.trending_tags": "Haischlibeanna", + "featured_carousel.current": "Post {current, number} / {max, number}", "featured_carousel.header": "{count, plural, one {Postáil phinnáilte} two {Poist Phionáilte} few {Poist Phionáilte} many {Poist Phionáilte} other {Poist Phionáilte}}", - "featured_carousel.next": "Ar Aghaidh", - "featured_carousel.post": "Post", - "featured_carousel.previous": "Roimhe Seo", - "featured_carousel.slide": "{index} de {total}", + "featured_carousel.slide": "Post {current, number} of {max, number}", "filter_modal.added.context_mismatch_explanation": "Ní bhaineann an chatagóir scagaire seo leis an gcomhthéacs ina bhfuair tú rochtain ar an bpostáil seo. Más mian leat an postáil a scagadh sa chomhthéacs seo freisin, beidh ort an scagaire a chur in eagar.", "filter_modal.added.context_mismatch_title": "Neamhréir comhthéacs!", "filter_modal.added.expired_explanation": "Tá an chatagóir scagaire seo imithe in éag, beidh ort an dáta éaga a athrú chun é a chur i bhfeidhm.", @@ -396,6 +441,9 @@ "follow_suggestions.who_to_follow": "Cé le leanúint", "followed_tags": "Hashtags le leanúint", "footer.about": "Maidir le", + "footer.about_mastodon": "Maidir le Mastodon", + "footer.about_server": "Maidir le {domain}", + "footer.about_this_server": "Maidir", "footer.directory": "Eolaire próifílí", "footer.get_app": "Faigh an aip", "footer.keyboard_shortcuts": "Aicearraí méarchláir", @@ -432,7 +480,7 @@ "hints.profiles.see_more_followers": "Féach ar a thuilleadh leantóirí ar {domain}", "hints.profiles.see_more_follows": "Féach tuilleadh seo a leanas ar {domain}", "hints.profiles.see_more_posts": "Féach ar a thuilleadh postálacha ar {domain}", - "home.column_settings.show_quotes": "Taispeáin Sleachta", + "home.column_settings.show_quotes": "Taispeáin sleachta", "home.column_settings.show_reblogs": "Taispeáin moltaí", "home.column_settings.show_replies": "Taispeán freagraí", "home.hide_announcements": "Cuir fógraí i bhfolach", @@ -497,6 +545,7 @@ "keyboard_shortcuts.toggle_hidden": "Taispeáin/folaigh an téacs taobh thiar de CW", "keyboard_shortcuts.toggle_sensitivity": "Taispeáin / cuir i bhfolach meáin", "keyboard_shortcuts.toot": "Cuir tús le postáil nua", + "keyboard_shortcuts.top": "Bog go barr an liosta", "keyboard_shortcuts.translate": "post a aistriú", "keyboard_shortcuts.unfocus": "Unfocus cum textarea/search", "keyboard_shortcuts.up": "Bog suas ar an liosta", @@ -661,7 +710,7 @@ "notifications.column_settings.mention": "Tráchtanna:", "notifications.column_settings.poll": "Torthaí suirbhéanna:", "notifications.column_settings.push": "Brúfhógraí", - "notifications.column_settings.quote": "Luachain:", + "notifications.column_settings.quote": "Sleachta:", "notifications.column_settings.reblog": "Moltaí:", "notifications.column_settings.show": "Taispeáin i gcolún", "notifications.column_settings.sound": "Seinn an fhuaim", @@ -738,14 +787,16 @@ "privacy.public.long": "Duine ar bith ar agus amach Mastodon", "privacy.public.short": "Poiblí", "privacy.quote.anyone": "{visibility}, is féidir le duine ar bith lua", - "privacy.quote.disabled": "{visibility}, comharthaí athfhriotail díchumasaithe", - "privacy.quote.limited": "{visibility}, luachana teoranta", + "privacy.quote.disabled": "{visibility}, sleachta díchumasaithe", + "privacy.quote.limited": "{visibility}, sleachta teoranta", "privacy.unlisted.additional": "Iompraíonn sé seo díreach mar a bheadh ​​poiblí, ach amháin ní bheidh an postáil le feiceáil i bhfothaí beo nó i hashtags, in iniúchadh nó i gcuardach Mastodon, fiú má tá tú liostáilte ar fud an chuntais.", "privacy.unlisted.long": "I bhfolach ó thorthaí cuardaigh Mastodon, treochtaí, agus amlínte poiblí", "privacy.unlisted.short": "Poiblí ciúin", "privacy_policy.last_updated": "Nuashonraithe {date}", "privacy_policy.title": "Polasaí príobháideachais", + "quote_error.edit": "Ní féidir sleachta a chur leis agus post á chur in eagar.", "quote_error.poll": "Ní cheadaítear lua le pobalbhreitheanna.", + "quote_error.private_mentions": "Ní cheadaítear lua le tagairtí díreacha.", "quote_error.quote": "Ní cheadaítear ach luachan amháin ag an am.", "quote_error.unauthorized": "Níl údarás agat an post seo a lua.", "quote_error.upload": "Ní cheadaítear lua a dhéanamh le ceangaltáin sna meáin.", @@ -858,15 +909,19 @@ "status.admin_account": "Oscail comhéadan modhnóireachta do @{name}", "status.admin_domain": "Oscail comhéadan modhnóireachta le haghaidh {domain}", "status.admin_status": "Oscail an postáil seo sa chomhéadan modhnóireachta", - "status.all_disabled": "Tá borradh agus luachana díchumasaithe", + "status.all_disabled": "Tá treisiúcháin agus sleachta díchumasaithe", "status.block": "Bac @{name}", "status.bookmark": "Leabharmharcanna", "status.cancel_reblog_private": "Dímhol", "status.cannot_quote": "Ní cheadaítear duit an post seo a lua", "status.cannot_reblog": "Ní féidir an phostáil seo a mholadh", "status.contains_quote": "Tá luachan ann", - "status.context.load_new_replies": "Freagraí nua ar fáil", - "status.context.loading": "Ag seiceáil le haghaidh tuilleadh freagraí", + "status.context.loading": "Ag lódáil tuilleadh freagraí", + "status.context.loading_error": "Níorbh fhéidir freagraí nua a lódáil", + "status.context.loading_success": "Freagraí nua luchtaithe", + "status.context.more_replies_found": "Tuilleadh freagraí aimsithe", + "status.context.retry": "Déan iarracht arís", + "status.context.show": "Taispeáin", "status.continued_thread": "Snáithe ar lean", "status.copy": "Cóipeáil an nasc chuig an bpostáil", "status.delete": "Scrios", @@ -879,7 +934,7 @@ "status.edited_x_times": "Curtha in eagar {count, plural, one {{count} uair amháin} two {{count} uair} few {{count} uair} many {{count} uair} other {{count} uair}}", "status.embed": "Faigh cód leabú", "status.favourite": "Is fearr leat", - "status.favourites": "{count, plural, one {a bhfuil grá agat do} two {gráite} few {gráite} many {gráite} other {gráite}}", + "status.favourites_count": "{count, plural,\n one {{counter} cheanán}\n two {{counter} cheanáin}\n few {{counter} ceanáin}\n many {{counter} ceanán}\n other {{counter} ceanáin}\n}", "status.filter": "Déan scagadh ar an bpostáil seo", "status.history.created": "Chruthaigh {name} {date}", "status.history.edited": "Curtha in eagar ag {name} in {date}", @@ -895,9 +950,12 @@ "status.pin": "Pionnáil ar do phróifíl", "status.quote": "Luachan", "status.quote.cancel": "Cealaigh an luachan", + "status.quote_error.blocked_account_hint.title": "Tá an post seo i bhfolach mar gur chuir tú bac ar @{name}.", + "status.quote_error.blocked_domain_hint.title": "Tá an post seo i bhfolach mar gur chuir tú bac ar {domain}.", "status.quote_error.filtered": "I bhfolach mar gheall ar cheann de do scagairí", "status.quote_error.limited_account_hint.action": "Taispeáin ar aon nós", "status.quote_error.limited_account_hint.title": "Tá an cuntas seo i bhfolach ag modhnóirí {domain}.", + "status.quote_error.muted_account_hint.title": "Tá an post seo i bhfolach mar gur chuir tú @{name} ar neamhní.", "status.quote_error.not_available": "Níl an postáil ar fáil", "status.quote_error.pending_approval": "Post ar feitheamh", "status.quote_error.pending_approval_popout.body": "Ar Mastodon, is féidir leat a rialú an féidir le duine tú a lua nó nach féidir. Tá an post seo ar feitheamh fad is atá ceadú an údair bhunaidh á fháil againn.", @@ -908,15 +966,17 @@ "status.quote_policy_change": "Athraigh cé a fhéadann luachan a thabhairt", "status.quote_post_author": "Luaigh mé post le @{name}", "status.quote_private": "Ní féidir poist phríobháideacha a lua", - "status.quotes": "{count, plural, one {sliocht} few {sliocht} other {sliocht}}", "status.quotes.empty": "Níl an post seo luaite ag aon duine go fóill. Nuair a dhéanann duine é, taispeánfar anseo é.", + "status.quotes.local_other_disclaimer": "Ní thaispeánfar sleachta ar dhiúltaigh an t-údar dóibh.", + "status.quotes.remote_other_disclaimer": "Níl ráthaíocht ann go dtaispeánfar anseo ach sleachta ó {domain}. Ní thaispeánfar sleachta ar dhiúltaigh an t-údar dóibh.", + "status.quotes_count": "{count, plural, one {{counter} sleacht} two {{counter} sleachta} few {{counter} sleachta} many {{counter} sleachta} other {{counter} sleachta}}", "status.read_more": "Léan a thuilleadh", "status.reblog": "Treisiú", "status.reblog_or_quote": "Borradh nó luachan", "status.reblog_private": "Roinn arís le do leanúna", "status.reblogged_by": "Mhol {name}", - "status.reblogs": "{count, plural, one {buaic} other {buaic}}", "status.reblogs.empty": "Níor mhol éinne an phostáil seo fós. Nuair a mholfaidh duine éigin í, taispeánfar anseo é sin.", + "status.reblogs_count": "{count, plural,\n one {{counter} athfhriotal}\n two {{counter} athfhriotail}\n few {{counter} athfhriotail}\n many {{counter} athfhriotal}\n other {{counter} athfhriotail}\n}", "status.redraft": "Scrios ⁊ athdhréachtaigh", "status.remove_bookmark": "Bain leabharmharc", "status.remove_favourite": "Bain ó cheanáin", @@ -965,7 +1025,7 @@ "upload_button.label": "Cuir íomhánna, físeán nó comhad fuaime leis", "upload_error.limit": "Sáraíodh an teorainn uaslódála comhaid.", "upload_error.poll": "Ní cheadaítear uaslódáil comhad le pobalbhreith.", - "upload_error.quote": "Ní cheadaítear uaslódáil comhaid le comharthaí athfhriotail.", + "upload_error.quote": "Ní cheadaítear uaslódáil comhaid le sleachta.", "upload_form.drag_and_drop.instructions": "Chun ceangaltán meán a phiocadh suas, brúigh spás nó cuir isteach. Agus tú ag tarraingt, bain úsáid as na heochracha saigheada chun an ceangaltán meán a bhogadh i dtreo ar bith. Brúigh spás nó cuir isteach arís chun an ceangaltán meán a scaoileadh ina phost nua, nó brúigh éalú chun cealú.", "upload_form.drag_and_drop.on_drag_cancel": "Cuireadh an tarraingt ar ceal. Scaoileadh ceangaltán meán {item}.", "upload_form.drag_and_drop.on_drag_end": "Scaoileadh ceangaltán meán {item}.", @@ -990,10 +1050,12 @@ "video.volume_down": "Toirt síos", "video.volume_up": "Toirt suas", "visibility_modal.button_title": "Socraigh infheictheacht", + "visibility_modal.direct_quote_warning.text": "Má shábhálann tú na socruithe reatha, déanfar an luachan leabaithe a thiontú ina nasc.", + "visibility_modal.direct_quote_warning.title": "Ní féidir sleachta a leabú i dtráchtanna príobháideacha", "visibility_modal.header": "Infheictheacht agus idirghníomhaíocht", "visibility_modal.helper.direct_quoting": "Ní féidir le daoine eile tráchtanna príobháideacha a scríobhadh ar Mastodon a lua.", "visibility_modal.helper.privacy_editing": "Ní féidir infheictheacht a athrú tar éis post a fhoilsiú.", - "visibility_modal.helper.privacy_private_self_quote": "Ní féidir féin-luachanna ó phoist phríobháideacha a chur ar fáil don phobal.", + "visibility_modal.helper.privacy_private_self_quote": "Ní féidir féin-sleachta ó phoist phríobháideacha a chur ar fáil don phobal.", "visibility_modal.helper.private_quoting": "Ní féidir le daoine eile poist atá scríofa ar Mastodon agus atá dírithe ar leanúna amháin a lua.", "visibility_modal.helper.unlisted_quoting": "Nuair a luann daoine thú, beidh a bpost i bhfolach ó amlínte treochta freisin.", "visibility_modal.instructions": "Rialaigh cé a fhéadfaidh idirghníomhú leis an bpost seo. Is féidir leat socruithe a chur i bhfeidhm ar gach post amach anseo trí nascleanúint a dhéanamh chuig Sainroghanna > Réamhshocruithe Postála.", diff --git a/app/javascript/mastodon/locales/gd.json b/app/javascript/mastodon/locales/gd.json index 2b2efe733c8950..16ce197f3f738c 100644 --- a/app/javascript/mastodon/locales/gd.json +++ b/app/javascript/mastodon/locales/gd.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Na cuir brath thugam tuilleadh nuair a chuireas @{name} post ris", "account.domain_blocking": "Àrainn ’ga bacadh", "account.edit_profile": "Deasaich a’ phròifil", + "account.edit_profile_short": "Deasaich", "account.enable_notifications": "Cuir brath thugam nuair a chuireas @{name} post ris", "account.endorse": "Brosnaich air a’ phròifil", "account.familiar_followers_many": "’Ga leantainn le {name1}, {name2}, and {othersCount, plural, one {# eile air a bheil thu eòlach} other {# eile air a bheil thu eòlach}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Gun phost", "account.follow": "Lean", "account.follow_back": "Lean air ais", + "account.follow_back_short": "Lean air ais", + "account.follow_request": "Iarr leantainn", + "account.follow_request_cancel": "Sguir dhen iarrtas", + "account.follow_request_cancel_short": "Sguir dheth", + "account.follow_request_short": "Iarr", "account.followers": "Luchd-leantainn", "account.followers.empty": "Chan eil neach sam bith a’ leantainn air a’ chleachdaiche seo fhathast.", "account.followers_counter": "{count, plural, one {{counter} neach-leantainn} other {{counter} luchd-leantainn}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Postaichean ’s freagairtean", "account.remove_from_followers": "Thoir {name} air falbh on luchd-leantainn", "account.report": "Dèan gearan mu @{name}", - "account.requested": "A’ feitheamh air aontachadh. Briog airson sgur dhen iarrtas leantainn", "account.requested_follow": "Dh’iarr {name} ’gad leantainn", "account.requests_to_follow_you": "Iarrtasan leantainn", "account.share": "Co-roinn a’ phròifil aig @{name}", @@ -108,25 +113,11 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Mìnich seo dhan fheadhainn air a bheil cion-lèirsinne…", "alt_text_modal.done": "Deiseil", "announcement.announcement": "Brath-fios", - "annual_report.summary.archetype.booster": "Brosnaiche", - "annual_report.summary.archetype.lurker": "Eala-bhalbh", - "annual_report.summary.archetype.oracle": "Coinneach Odhar", - "annual_report.summary.archetype.pollster": "Cunntair nam beachd", - "annual_report.summary.archetype.replier": "Ceatharnach nam freagairt", - "annual_report.summary.followers.followers": "luchd-leantainn", - "annual_report.summary.followers.total": "{count} gu h-iomlan", - "annual_report.summary.here_it_is": "Seo mar a chaidh {year} leat:", - "annual_report.summary.highlighted_post.by_favourites": "am post as annsa", - "annual_report.summary.highlighted_post.by_reblogs": "am post air a bhrosnachadh as trice", - "annual_report.summary.highlighted_post.by_replies": "am post dhan deach fhreagairt as trice", - "annual_report.summary.highlighted_post.possessive": "Aig {name},", "annual_report.summary.most_used_app.most_used_app": "an aplacaid a chaidh a cleachdadh as trice", "annual_report.summary.most_used_hashtag.most_used_hashtag": "an taga hais a chaidh a cleachdadh as trice", - "annual_report.summary.most_used_hashtag.none": "Chan eil gin", "annual_report.summary.new_posts.new_posts": "postaichean ùra", "annual_report.summary.percentile.text": "Tha thu am measgdhen luchd-cleachdaidh as cliùitiche air {domain}.", "annual_report.summary.percentile.we_wont_tell_bernie": "Ainmeil ’nad latha ’s ’nad linn.", - "annual_report.summary.thanks": "Mòran taing airson conaltradh air Mastodon.", "attachments_list.unprocessed": "(gun phròiseasadh)", "audio.hide": "Falaich an fhuaim", "block_modal.remote_users_caveat": "Iarraidh sinn air an fhrithealaiche {domain} gun gèill iad ri do cho-dhùnadh. Gidheadh, chan eil barantas gun gèill iad on a làimhsicheas cuid a fhrithealaichean bacaidhean air dòigh eadar-dhealaichte. Dh’fhaoidte gum faic daoine gun chlàradh a-steach na postaichean poblach agad fhathast.", @@ -152,6 +143,8 @@ "bundle_modal_error.close": "Dùin", "bundle_modal_error.message": "Chaidh rudeigin ceàrr le luchdadh na sgrìn seo.", "bundle_modal_error.retry": "Feuch ris a-rithist", + "carousel.current": "Sleamhnag {current, number} / {max, number}", + "carousel.slide": "Sleamhnag {current, number} à {max, number}", "closed_registrations.other_server_instructions": "Air sgàth ’s gu bheil Mastodon sgaoilte, ’s urrainn dhut cunntas a chruthachadh air frithealaiche eile agus conaltradh ris an fhrithealaiche seo co-dhiù.", "closed_registrations_modal.description": "Cha ghabh cunntas a chruthachadh air {domain} aig an àm seo ach thoir an aire nach fheum thu cunntas air {domain} gu sònraichte airson Mastodon a chleachdadh.", "closed_registrations_modal.find_another_server": "Lorg frithealaiche eile", @@ -168,6 +161,8 @@ "column.edit_list": "Deasaich an liosta", "column.favourites": "Annsachdan", "column.firehose": "An saoghal beò", + "column.firehose_local": "Loidhne-ama bheò an fhrithealaiche seo", + "column.firehose_singular": "Loidhne-ama bheò beò", "column.follow_requests": "Iarrtasan leantainn", "column.home": "Dachaigh", "column.list_members": "Stiùir buill na liosta", @@ -187,6 +182,7 @@ "community.column_settings.local_only": "Feadhainn ionadail a-mhàin", "community.column_settings.media_only": "Meadhanan a-mhàin", "community.column_settings.remote_only": "Feadhainn chèin a-mhàin", + "compose.error.blank_post": "Chan urrrainn dho phost a bhith bàn.", "compose.language.change": "Atharraich an cànan", "compose.language.search": "Lorg cànan…", "compose.published.body": "Chaidh am post fhoillseachadh.", @@ -239,10 +235,15 @@ "confirmations.missing_alt_text.secondary": "Postaich e co-dhiù", "confirmations.missing_alt_text.title": "A bheil thu airson roghainn teacsa a chur ris?", "confirmations.mute.confirm": "Mùch", + "confirmations.private_quote_notify.cancel": "Till dhan deasachadh", + "confirmations.private_quote_notify.confirm": "Foillsich am post", + "confirmations.private_quote_notify.do_not_show_again": "Na seall an teachdaireachd seo a-rithist dhomh", + "confirmations.private_quote_notify.message": "Cuiridh sinn brath dhan neach a tha thu a’ luaidh ’s do na daoine eile le iomradh orra agus chì iad am post agad fiù mur eil iad ’gad leantainn.", + "confirmations.private_quote_notify.title": "A bheil thu airson a cho-roinneadh leis an luchd-leantainn ’s na cleachdaichean le iomradh orra?", "confirmations.quiet_post_quote_info.dismiss": "Na cuiribh seo ’nam chuimhne a-rithist", "confirmations.quiet_post_quote_info.got_it": "Tha mi agaibh", "confirmations.quiet_post_quote_info.message": "Nuair a luaidheas tu post a tha poblach ach sàmhach, thèid am post agad fhalach o loidhnichean-ama nan treandaichean.", - "confirmations.quiet_post_quote_info.title": "Luaidh air postaichean poblach ach sàmhach", + "confirmations.quiet_post_quote_info.title": "Luaidh air postaichean sàmhach", "confirmations.redraft.confirm": "Sguab às ⁊ dèan dreachd ùr", "confirmations.redraft.message": "A bheil thu cinnteach gu bheil thu airson am post seo a sguabadh às agus dreachd ùr a thòiseachadh? Caillidh tu gach annsachd is brosnachadh air agus thèid freagairtean dhan phost thùsail ’nan dìlleachdanan.", "confirmations.redraft.title": "A bheil thu airson am post a sguabadh às ⁊ dreachd ùr a dhèanamh dheth?", @@ -252,9 +253,12 @@ "confirmations.revoke_quote.confirm": "Thoir am post air falbh", "confirmations.revoke_quote.message": "Cha ghabh seo a neo-dhèanamh.", "confirmations.revoke_quote.title": "A bheil thu airson am post a thoirt air falbh?", + "confirmations.unblock.confirm": "Dì-bhac", + "confirmations.unblock.title": "A bheil thu airson {name} a dhì-bhacadh?", "confirmations.unfollow.confirm": "Na lean tuilleadh", - "confirmations.unfollow.message": "A bheil thu cinnteach nach eil thu airson {name} a leantainn tuilleadh?", - "confirmations.unfollow.title": "A bheil thu airson sgur de leantainn a chleachdaiche?", + "confirmations.unfollow.title": "A bheil thu airson sgur de {name} a leantainn?", + "confirmations.withdraw_request.confirm": "Cuir d’ iarrtas dhan dàrna taobh", + "confirmations.withdraw_request.title": "A bheil thu airson d’ iarrtas gus {name} a leantainn a chur dhan dàrna taobh?", "content_warning.hide": "Falaich am post", "content_warning.show": "Seall e co-dhiù", "content_warning.show_more": "Seall barrachd dheth", @@ -325,6 +329,7 @@ "empty_column.bookmarked_statuses": "Chan eil comharra-lìn ri post agad fhathast. Nuair a nì thu comharra-lìn de dh’fhear, nochdaidh e an-seo.", "empty_column.community": "Tha an loidhne-ama ionadail falamh. Sgrìobh rudeigin gu poblach airson toiseach-tòiseachaidh a dhèanamh!", "empty_column.direct": "Chan eil iomradh prìobhaideach agad fhathast. Nuair a chuireas no a gheibh thu tè, nochdaidh i an-seo.", + "empty_column.disabled_feed": "Chaidh an loidhne-ama seo a chur à comas le rianairean an fhrithealaiche agad.", "empty_column.domain_blocks": "Cha deach àrainn sam bith a bhacadh fhathast.", "empty_column.explore_statuses": "Chan eil dad a’ treandadh an-dràsta fhèin. Thoir sùil a-rithist an ceann greis!", "empty_column.favourited_statuses": "Chan eil annsachd air post agad fhathast. Nuair a nì thu annsachd de dh’fhear, nochdaidh e an-seo.", @@ -349,11 +354,9 @@ "explore.trending_links": "Naidheachdan", "explore.trending_statuses": "Postaichean", "explore.trending_tags": "Tagaichean hais", + "featured_carousel.current": "Post {current, number} / {max, number}", "featured_carousel.header": "{count, plural, one {Post prìnichte} two {Postaichean prìnichte} few {Postaichean prìnichte} other {Postaichean prìnichte}}", - "featured_carousel.next": "Air adhart", - "featured_carousel.post": "Post", - "featured_carousel.previous": "Air ais", - "featured_carousel.slide": "{index} à {total}", + "featured_carousel.slide": "Post {current, number} à {max, number}", "filter_modal.added.context_mismatch_explanation": "Chan eil an roinn-seòrsa criathraidh iom seo chaidh dhan cho-theacs san do dh’inntrig thu am post seo. Ma tha thu airson am post a chriathradh sa cho-theacs seo cuideachd, feumaidh tu a’ chriathrag a dheasachadh.", "filter_modal.added.context_mismatch_title": "Co-theacsa neo-iomchaidh!", "filter_modal.added.expired_explanation": "Dh’fhalbh an ùine air an roinn-seòrsa criathraidh seo agus feumaidh tu an ceann-là crìochnachaidh atharrachadh mus cuir thu an sàs i.", @@ -396,6 +399,7 @@ "follow_suggestions.who_to_follow": "Molaidhean leantainn", "followed_tags": "Tagaichean hais ’gan leantainn", "footer.about": "Mu dhèidhinn", + "footer.about_this_server": "Mu dhèidhinn", "footer.directory": "Eòlaire nam pròifil", "footer.get_app": "Faigh an aplacaid", "footer.keyboard_shortcuts": "Ath-ghoiridean a’ mheur-chlàir", @@ -737,15 +741,17 @@ "privacy.private.short": "Luchd-leantainn", "privacy.public.long": "Duine sam bith taobh a-staigh no a-muigh Mhastodon", "privacy.public.short": "Poblach", - "privacy.quote.anyone": "{visibility}, faodaidh neach sam bith a luaidh", - "privacy.quote.disabled": "{visibility}, luaidhean à comas", - "privacy.quote.limited": "{visibility}, luaidhean cuingichte", + "privacy.quote.anyone": "{visibility}, luaidh fosgailte", + "privacy.quote.disabled": "{visibility}, luaidh à comas", + "privacy.quote.limited": "{visibility}, luaidh cuingichte", "privacy.unlisted.additional": "Tha seo coltach ris an fhaicsinneachd phoblach ach cha nochd am post air loidhnichean-ama an t-saoghail phoblaich, nan tagaichean hais no an rùrachaidh no ann an toraidhean luirg Mhastodon fiù ’s ma thug thu ro-aonta airson sin seachad.", - "privacy.unlisted.long": "Falaichte o na toraidhean-luirg, na treandaichean ’s na loichnichean-ama poblach", - "privacy.unlisted.short": "Poblach ach sàmhach", + "privacy.unlisted.long": "Poblach ach falaichte o na toraidhean-luirg, na treandaichean ’s na loichnichean-ama poblach", + "privacy.unlisted.short": "Sàmhach", "privacy_policy.last_updated": "An t-ùrachadh mu dheireadh {date}", "privacy_policy.title": "Poileasaidh prìobhaideachd", + "quote_error.edit": "Chan urrainn dhut luaidh a chur ris nuair a bhios tu ri deasachadh puist.", "quote_error.poll": "Chan fhaod thu luaidh a chur an cois cunntais-bheachd.", + "quote_error.private_mentions": "Chan fhaod thu luaidh a chur an cois iomraidh phrìobhaidich.", "quote_error.quote": "Chan eil taic ach ri aon luaidh aig an aon àm.", "quote_error.unauthorized": "Chan fhaod thu am post seo a luaidh.", "quote_error.upload": "Chan fhaod thu meadhan a chur ri luaidh.", @@ -864,8 +870,13 @@ "status.cancel_reblog_private": "Na brosnaich tuilleadh", "status.cannot_quote": "Chan fhaod thu am post seo a luaidh", "status.cannot_reblog": "Cha ghabh am post seo brosnachadh", - "status.context.load_new_replies": "Tha freagairt no dhà ùr ri fhaighinn", - "status.context.loading": "A’ toirt sùil airson barrachd fhreagairtean", + "status.contains_quote": "Tha luaidh na bhroinn", + "status.context.loading": "A’ luchdadh barrachd fhreagairtean", + "status.context.loading_error": "Cha b’ urrainn dhuinn nam freagairtean ùra a luchdadh", + "status.context.loading_success": "Chaidh na freagairtean ùra a luchdadh", + "status.context.more_replies_found": "Fhuair sinn lorg air barrachd fhreagairtean", + "status.context.retry": "Feuch ris a-rithist", + "status.context.show": "Seall", "status.continued_thread": "Pàirt de shnàithlean", "status.copy": "Dèan lethbhreac dhen cheangal dhan phost", "status.delete": "Sguab às", @@ -878,7 +889,6 @@ "status.edited_x_times": "Chaidh a dheasachadh {count, plural, one {{count} turas} two {{count} thuras} few {{count} tursan} other {{count} turas}}", "status.embed": "Faigh còd leabachaidh", "status.favourite": "Cuir ris na h-annsachdan", - "status.favourites": "{count, plural, one {annsachd} two {annsachd} few {annsachdan} other {annsachd}}", "status.filter": "Criathraich am post seo", "status.history.created": "Chruthaich {name} {date} e", "status.history.edited": "Dheasaich {name} {date} e", @@ -894,24 +904,30 @@ "status.pin": "Prìnich ris a’ phròifil", "status.quote": "Luaidh", "status.quote.cancel": "Sguir dhen luaidh", + "status.quote_error.blocked_account_hint.title": "Tha am post seo falaichte on a bhac thu @{name}.", + "status.quote_error.blocked_domain_hint.title": "Tha am post seo falaichte on a bhac thu {domain}.", "status.quote_error.filtered": "Falaichte le criathrag a th’ agad", + "status.quote_error.limited_account_hint.action": "Seall e co-dhiù", + "status.quote_error.limited_account_hint.title": "Chaidh an cunntas seo fhalach le maoir {domain}.", + "status.quote_error.muted_account_hint.title": "Tha am post seo falaichte on a mhùch thu @{name}.", "status.quote_error.not_available": "Chan eil am post ri fhaighinn", "status.quote_error.pending_approval": "Cha deach dèiligeadh ris a’ phost fhathast", "status.quote_error.pending_approval_popout.body": "Air Mastodon, ’s urrainn dhut stiùireadh am faod cuideigin do luaidh gus nach fhaod. Tha am post seo a’ feitheamh air aonta an ùghdair thùsail.", "status.quote_error.revoked": "Chaidh am post a thoirt air falbh leis an ùghdar", "status.quote_followers_only": "Chan fhaod ach luchd-leantainn am post seo a luaidh", "status.quote_manual_review": "Nì an t-ùghdar lèirmheas air a làimh", + "status.quote_noun": "Luaidh", "status.quote_policy_change": "Atharraich cò dh’fhaodas luaidh", "status.quote_post_author": "Luaidh air post le @{name}", "status.quote_private": "Chan fhaodar postaichean prìobhaideach a luaidh", - "status.quotes": "{count, plural, one {luaidh} two {luaidh} few {luaidhean} other {luaidh}}", "status.quotes.empty": "Chan deach am post seo a luaidh le duine sam bith fhathast. Nuair a luaidheas cuideigin e, nochdaidh iad an-seo.", + "status.quotes.local_other_disclaimer": "Cha tèid luaidhean a dhiùilt an ùghdar a shealltainn.", + "status.quotes.remote_other_disclaimer": "Cha dèid ach luaidhean o {domain} a shealltainn an-seo le cinnt. Cha dèid luaidhean a dhiùilt an ùghdar a shealltainn.", "status.read_more": "Leugh an còrr", "status.reblog": "Brosnaich", "status.reblog_or_quote": "Brosnaich no luaidh", "status.reblog_private": "Co-roinn leis an luchd-leantainn agad a-rithist", "status.reblogged_by": "’Ga bhrosnachadh le {name}", - "status.reblogs": "{count, plural, one {bhrosnachadh} two {bhrosnachadh} few {brosnachaidhean} other {brosnachadh}}", "status.reblogs.empty": "Chan deach am post seo a bhrosnachadh le duine sam bith fhathast. Nuair a bhrosnaicheas cuideigin e, nochdaidh iad an-seo.", "status.redraft": "Sguab às ⁊ dèan dreachd ùr", "status.remove_bookmark": "Thoir an comharra-lìn air falbh", @@ -986,17 +1002,19 @@ "video.volume_down": "Lùghdaich an fhuaim", "video.volume_up": "Cuir an fhuaim an àirde", "visibility_modal.button_title": "Suidhich an fhaicsinneachd", + "visibility_modal.direct_quote_warning.text": "Ma shàbhaileas tu na roghainnean làithreach, thèid ceangal a dhèanamh dhen luaidh leabaichte.", + "visibility_modal.direct_quote_warning.title": "Chan urrainn dhut luaidh a leabachadh ann an iomradh prìobhaideach", "visibility_modal.header": "Faicsinneachd ⁊ eadar-ghabhail", "visibility_modal.helper.direct_quoting": "Chan urrainn do chàch iomraidhean prìobhaideach a chaidh a sgrìobhadh le Mastodon a luaidh.", "visibility_modal.helper.privacy_editing": "Chan urrainn dhut faicsinneachd puist atharrachadh às dhèidh fhoillseachadh.", "visibility_modal.helper.privacy_private_self_quote": "Chan fhaodar fèin-luaidhean air postaichean prìobhaideach a dhèanamh poblach.", "visibility_modal.helper.private_quoting": "Chan urrainn do chàch postaichean dhan luchd-leantainn a-mhàin a chaidh a sgrìobhadh le Mastodon a luaidh.", "visibility_modal.helper.unlisted_quoting": "Nuair a luaidheas daoine thu, thèid am post aca-san fhalach o loidhnichean-ama nan treandaichean.", - "visibility_modal.instructions": "Stiùirich cò dh’fhaodas eadar-ghabhahil leis a’ phost seo. ’S urrainn dhut do roghainnean airson nam postaichean ri teachd a thaghadh aig Roghainnean > Bun-roghainnean a’ phostaidh", + "visibility_modal.instructions": "Stiùirich cò dh’fhaodas eadar-ghabhahil leis a’ phost seo. ’S urrainn dhut do roghainnean airson nam postaichean ri teachd a thaghadh aig Roghainnean > Bun-roghainnean a’ phostaidh.", "visibility_modal.privacy_label": "Faicsinneachd", "visibility_modal.quote_followers": "Luchd-leantainn a-mhàin", "visibility_modal.quote_label": "Cò dh’fhaodas luaidh", "visibility_modal.quote_nobody": "Mi fhìn a-mhàin", - "visibility_modal.quote_public": "Duine sam bith", + "visibility_modal.quote_public": "Neach sam bith", "visibility_modal.save": "Sàbhail" } diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index 2d37337a0c10f8..d707ba57100b2b 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Deixar de notificarme cando @{name} publica", "account.domain_blocking": "Bloqueo do dominio", "account.edit_profile": "Editar perfil", + "account.edit_profile_short": "Editar", "account.enable_notifications": "Noficarme cando @{name} publique", "account.endorse": "Amosar no perfil", "account.familiar_followers_many": "Seguida por {name1}, {name2}, e {othersCount, plural, one {outra conta que coñeces} other {outras # contas que coñeces}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Sen publicacións", "account.follow": "Seguir", "account.follow_back": "Seguir tamén", + "account.follow_back_short": "Seguir tamén", + "account.follow_request": "Solicitar seguir", + "account.follow_request_cancel": "Desbotar a petición", + "account.follow_request_cancel_short": "Desbotar", + "account.follow_request_short": "Solicitar", "account.followers": "Seguidoras", "account.followers.empty": "Aínda ninguén segue esta usuaria.", "account.followers_counter": "{count, plural, one {{counter} seguidora} other {{counter} seguidoras}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Publicacións e respostas", "account.remove_from_followers": "Retirar a {name} das seguidoras", "account.report": "Informar sobre @{name}", - "account.requested": "Agardando aprobación. Preme para desbotar a solicitude", "account.requested_follow": "{name} solicitou seguirte", "account.requests_to_follow_you": "Solicita seguirte", "account.share": "Compartir o perfil de @{name}", @@ -108,25 +113,52 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Describe isto para as persoas con dificultades visuais…", "alt_text_modal.done": "Feito", "announcement.announcement": "Anuncio", - "annual_report.summary.archetype.booster": "O Telexornal", - "annual_report.summary.archetype.lurker": "Volleur", - "annual_report.summary.archetype.oracle": "Sabichón", - "annual_report.summary.archetype.pollster": "I.G.E.", - "annual_report.summary.archetype.replier": "Lareteire", - "annual_report.summary.followers.followers": "seguidoras", - "annual_report.summary.followers.total": "{count} en total", - "annual_report.summary.here_it_is": "Este é o resumo do teu {year}:", - "annual_report.summary.highlighted_post.by_favourites": "a publicación mais favorecida", - "annual_report.summary.highlighted_post.by_reblogs": "a publicación con mais promocións", - "annual_report.summary.highlighted_post.by_replies": "a publicación con mais respostas", - "annual_report.summary.highlighted_post.possessive": "de {name}", + "annual_report.announcement.action_build": "Crear o meu Wrapstodon", + "annual_report.announcement.action_dismiss": "Non, grazas", + "annual_report.announcement.action_view": "Ver o meu Wrapstodon", + "annual_report.announcement.description": "Olla o que andiveches facendo en Mastodon durante o último ano.", + "annual_report.announcement.title": "Chegou o Wrapstodon de {year}", + "annual_report.nav_item.badge": "Novidade", + "annual_report.shared_page.donate": "Doar", + "annual_report.shared_page.footer": "Creado con {heart} polo equipo de Mastodon", + "annual_report.shared_page.footer_server_info": "{username} utiliza {domain}, unha das moitas comunidades de Mastodon.", + "annual_report.summary.archetype.booster.desc_public": "{name} á caza de publicacións para promover, dando relevancia a outras creadoras.", + "annual_report.summary.archetype.booster.desc_self": "Á caza de publicacións para promover, dando relevancia a outras creadoras.", + "annual_report.summary.archetype.booster.name": "Arqueire", + "annual_report.summary.archetype.die_drei_fragezeichen": "???", + "annual_report.summary.archetype.lurker.desc_public": "Sabemos que {name} andivo por aí, nalgures, desfrutando de Mastodon paseniño e ao seu xeito.", + "annual_report.summary.archetype.lurker.desc_self": "Sabemos que andiveches por aí, nalgures, desfrutando de Mastodon tranquilamente e ao teu xeito.", + "annual_report.summary.archetype.lurker.name": "Xubilade", + "annual_report.summary.archetype.oracle.desc_public": "{name} creou máis publicacións que respostas, mantendo Mastodon ao día e ollando ao futuro.", + "annual_report.summary.archetype.oracle.desc_self": "Creaches máis publicacións que respostas, mantendo Mastodon ao día e ollando o futuro.", + "annual_report.summary.archetype.oracle.name": "O Oráculo", + "annual_report.summary.archetype.pollster.desc_public": "{name} creou máis enquisas que publicacións normais, cultivando a curiosidade en Mastodon.", + "annual_report.summary.archetype.pollster.desc_self": "Creaches máis enquisas que publicacións normais, cultivando a curiosidade en Mastodon.", + "annual_report.summary.archetype.pollster.name": "O mar de dúbidas", + "annual_report.summary.archetype.replier.desc_public": "{name} respondeu con frecuencia a outras persoas, polinizando Mastodon con novas conversas.", + "annual_report.summary.archetype.replier.desc_self": "Respondeches con frecuencia a outras persoas, polinizando Mastodon con novas conversas.", + "annual_report.summary.archetype.replier.name": "A Bolboreta", + "annual_report.summary.archetype.reveal": "Mostrar o meu arquetipo", + "annual_report.summary.archetype.reveal_description": "Grazas por ser parte de Mastodon! É hora de coñecer o teu arquetipo de usuaria no {year}.", + "annual_report.summary.archetype.title_public": "Arquetipo de {name}", + "annual_report.summary.archetype.title_self": "O teu arquetipo", + "annual_report.summary.close": "Fechar", + "annual_report.summary.copy_link": "Copiar ligazón", + "annual_report.summary.followers.new_followers": "{count, plural, one {nova seguidora} other {nevas seguidoras}}", + "annual_report.summary.highlighted_post.boost_count": "Esta publicación promoveuse {count, plural, one {unha vez} other {# veces}}.", + "annual_report.summary.highlighted_post.favourite_count": "Esta publicación favoreceuse {count, plural, one {unha vez} other {# veces}}.", + "annual_report.summary.highlighted_post.reply_count": "Esta publicación tivo {count, plural, one {unha resposta} other {# respostas}}.", + "annual_report.summary.highlighted_post.title": "Publicación máis popular", "annual_report.summary.most_used_app.most_used_app": "app que mais usaches", "annual_report.summary.most_used_hashtag.most_used_hashtag": "o cancelo mais utilizado", - "annual_report.summary.most_used_hashtag.none": "Nada", + "annual_report.summary.most_used_hashtag.used_count": "Incluíches este cancelo en {count, plural, one {1 publicación} other {# publicacións}}.", + "annual_report.summary.most_used_hashtag.used_count_public": "{name} incluíu este cancelo en {count, plural, one {1 publicación} other {# publicacións}}.", "annual_report.summary.new_posts.new_posts": "novas publicacións", "annual_report.summary.percentile.text": "Sitúante no top das usuarias de {domain}.", "annual_report.summary.percentile.we_wont_tell_bernie": "Moito tes que contarnos!", - "annual_report.summary.thanks": "Grazas por ser parte de Mastodon!", + "annual_report.summary.share_elsewhere": "Compárteo onde queiras", + "annual_report.summary.share_message": "Resulta que son… {archetype}!", + "annual_report.summary.share_on_mastodon": "Compartir en Mastodon", "attachments_list.unprocessed": "(sen procesar)", "audio.hide": "Agochar audio", "block_modal.remote_users_caveat": "Ímoslle pedir ao servidor {domain} que respecte a túa decisión. Emporiso, non hai garantía de que atenda a petición xa que os servidores xestionan os bloqueos de formas diferentes. As publicacións públicas poderían aínda ser visibles para usuarias que non iniciaron sesión.", @@ -152,6 +184,8 @@ "bundle_modal_error.close": "Pechar", "bundle_modal_error.message": "Algo fallou mentras cargaba esta páxina.", "bundle_modal_error.retry": "Téntao de novo", + "carousel.current": "Filmina {current, number} / {max, number}", + "carousel.slide": "Filmina {current, number} de {max, number}", "closed_registrations.other_server_instructions": "Cómo Mastodon é descentralizado, podes crear unha conta noutro servidor e interactuar igualmente con este.", "closed_registrations_modal.description": "Actualmente non é posible crear unha conta en {domain}, pero ten en conta que non precisas unha conta específicamente en {domain} para usar Mastodon.", "closed_registrations_modal.find_another_server": "Atopa outro servidor", @@ -168,6 +202,8 @@ "column.edit_list": "Editar lista", "column.favourites": "Favoritas", "column.firehose": "O que acontece", + "column.firehose_local": "Acontece agora neste servidor", + "column.firehose_singular": "O que acontece", "column.follow_requests": "Peticións de seguimento", "column.home": "Inicio", "column.list_members": "Xestionar membros da lista", @@ -187,6 +223,7 @@ "community.column_settings.local_only": "Só local", "community.column_settings.media_only": "Só multimedia", "community.column_settings.remote_only": "Só remoto", + "compose.error.blank_post": "A publicación non pode estar baleira.", "compose.language.change": "Elixe o idioma", "compose.language.search": "Buscar idiomas...", "compose.published.body": "Mensaxe publicada.", @@ -239,6 +276,11 @@ "confirmations.missing_alt_text.secondary": "Publicar igualmente", "confirmations.missing_alt_text.title": "Engadir texto descritivo?", "confirmations.mute.confirm": "Acalar", + "confirmations.private_quote_notify.cancel": "Volver á edición", + "confirmations.private_quote_notify.confirm": "Publicar publicación", + "confirmations.private_quote_notify.do_not_show_again": "Non me volvas a mostrar esta mensaxe", + "confirmations.private_quote_notify.message": "A persoa que estás a citar e outras contas mencionadas serán notificadas e poderán ver a túa publicación, incluso se non te seguen.", + "confirmations.private_quote_notify.title": "Compartir coas seguidoras e coas usuarias mencionadas?", "confirmations.quiet_post_quote_info.dismiss": "Non lembrarmo máis", "confirmations.quiet_post_quote_info.got_it": "Entendido", "confirmations.quiet_post_quote_info.message": "Ao citar unha publicación pública limitada a túa publicación non aparecerá nas cronoloxías de tendencias.", @@ -252,9 +294,12 @@ "confirmations.revoke_quote.confirm": "Eliminar publicación", "confirmations.revoke_quote.message": "Esta acción non se pode desfacer.", "confirmations.revoke_quote.title": "Eliminar publicación?", + "confirmations.unblock.confirm": "Desbloquear", + "confirmations.unblock.title": "Desbloquear a {name}?", "confirmations.unfollow.confirm": "Deixar de seguir", - "confirmations.unfollow.message": "Tes certeza de querer deixar de seguir a {name}?", - "confirmations.unfollow.title": "Deixar de seguir á usuaria?", + "confirmations.unfollow.title": "Deixa de seguir a {name}?", + "confirmations.withdraw_request.confirm": "Retirar solicitude", + "confirmations.withdraw_request.title": "Retirar a petición de seguimento para {name}?", "content_warning.hide": "Agochar publicación", "content_warning.show": "Mostrar igualmente", "content_warning.show_more": "Mostrar máis", @@ -325,6 +370,7 @@ "empty_column.bookmarked_statuses": "Aínda non marcaches ningunha publicación. Cando o fagas, aparecerán aquí.", "empty_column.community": "A cronoloxía local está baleira. Escribe algo de xeito público para espallalo!", "empty_column.direct": "Aínda non tes mencións privadas. Cando envíes ou recibas unha, aparecerá aquí.", + "empty_column.disabled_feed": "A administración do teu servidor desactivou esta canle.", "empty_column.domain_blocks": "Aínda non hai dominios agochados.", "empty_column.explore_statuses": "Non hai temas en voga. Volve máis tarde!", "empty_column.favourited_statuses": "Aínda non tes publicacións favoritas. Cando favorezas unha, aparecerá aquí.", @@ -338,6 +384,7 @@ "empty_column.notification_requests": "Todo ben! Nada por aquí. Cando recibas novas notificacións aparecerán aquí seguindo o criterio dos teus axustes.", "empty_column.notifications": "Aínda non tes notificacións. Aparecerán cando outras persoas interactúen contigo.", "empty_column.public": "Nada por aquí! Escribe algo de xeito público, ou segue de xeito manual usuarias doutros servidores para ir enchéndoo", + "error.no_hashtag_feed_access": "Crea unha conta ou accede para ver e seguir este cancelo.", "error.unexpected_crash.explanation": "Debido a un erro no noso código ou a unha compatilidade co teu navegador, esta páxina non pode ser amosada correctamente.", "error.unexpected_crash.explanation_addons": "Non se puido mostrar correctamente a páxina. Habitualmente este erro está causado por algún engadido do navegador ou ferramentas de tradución automática.", "error.unexpected_crash.next_steps": "Tenta actualizar a páxina. Se isto non axuda podes tamén empregar Mastodon noutro navegador ou aplicación nativa.", @@ -349,11 +396,9 @@ "explore.trending_links": "Novas", "explore.trending_statuses": "Publicacións", "explore.trending_tags": "Cancelos", + "featured_carousel.current": "Publicación {current, number} / {max, number}", "featured_carousel.header": "{count, plural, one {Publicación fixada} other {Publicacións fixadas}}", - "featured_carousel.next": "Seguinte", - "featured_carousel.post": "Publicación", - "featured_carousel.previous": "Anterior", - "featured_carousel.slide": "{index} de {total}", + "featured_carousel.slide": "Publicación {current, number} de {max, number}", "filter_modal.added.context_mismatch_explanation": "Esta categoría de filtro non se aplica ao contexto no que accedeches a esta publicación. Se queres que a publicación se filtre nese contexto tamén, terás que editar o filtro.", "filter_modal.added.context_mismatch_title": "Non concorda o contexto!", "filter_modal.added.expired_explanation": "Esta categoría de filtro caducou, terás que cambiar a data de caducidade para que se aplique.", @@ -396,6 +441,9 @@ "follow_suggestions.who_to_follow": "A quen seguir", "followed_tags": "Cancelos seguidos", "footer.about": "Sobre", + "footer.about_mastodon": "Sobre Mastodon", + "footer.about_server": "Sobre {domain}", + "footer.about_this_server": "Sobre", "footer.directory": "Directorio de perfís", "footer.get_app": "Descarga a app", "footer.keyboard_shortcuts": "Atallos do teclado", @@ -497,6 +545,7 @@ "keyboard_shortcuts.toggle_hidden": "Para mostrar o texto tras Aviso de Contido (CW)", "keyboard_shortcuts.toggle_sensitivity": "Para amosar/agochar contido multimedia", "keyboard_shortcuts.toot": "Para escribir unha nova publicación", + "keyboard_shortcuts.top": "Mover arriba de todo", "keyboard_shortcuts.translate": "para traducir unha publicación", "keyboard_shortcuts.unfocus": "Para deixar de destacar a área de escritura/procura", "keyboard_shortcuts.up": "Para mover cara arriba na listaxe", @@ -733,7 +782,7 @@ "privacy.change": "Axustar privacidade", "privacy.direct.long": "Todas as mencionadas na publicación", "privacy.direct.short": "Mención privada", - "privacy.private.long": "Só para seguidoras", + "privacy.private.long": "Só quen te segue", "privacy.private.short": "Seguidoras", "privacy.public.long": "Para todas dentro e fóra de Mastodon", "privacy.public.short": "Público", @@ -745,7 +794,9 @@ "privacy.unlisted.short": "Pública limitada", "privacy_policy.last_updated": "Actualizado por última vez no {date}", "privacy_policy.title": "Política de Privacidade", + "quote_error.edit": "Non se poden engadir citas ao editar unha publicación.", "quote_error.poll": "Non se permite citar as enquisas.", + "quote_error.private_mentions": "As citas non están permitidas nas mencións directas.", "quote_error.quote": "Só se permite citar unha vez.", "quote_error.unauthorized": "Non tes permiso para citar esta publicación.", "quote_error.upload": "As citas non están permitidas para anexos multimedia.", @@ -851,7 +902,7 @@ "server_banner.is_one_of_many": "{domain} é un dos moitos servidores Mastodon independentes que podes usar para participar do Fediverso.", "server_banner.server_stats": "Estatísticas do servidor:", "sign_in_banner.create_account": "Crear conta", - "sign_in_banner.follow_anyone": "Sigue a quen queiras no Fediverso e le as publicacións en orde cronolóxica. Sen algoritmos, publicidade nin titulares engañosos.", + "sign_in_banner.follow_anyone": "Sigue a quen queiras no Fediverso e le as publicacións en orde cronolóxica. Sen algoritmos, publicidade nin titulares enganosos.", "sign_in_banner.mastodon_is": "Mastodon é o mellor xeito de estar ao día do que acontece.", "sign_in_banner.sign_in": "Iniciar sesión", "sign_in_banner.sso_redirect": "Acceder ou Crear conta", @@ -865,8 +916,12 @@ "status.cannot_quote": "Non tes permiso para citar esta publicación", "status.cannot_reblog": "Esta publicación non pode ser promovida", "status.contains_quote": "Contén unha cita", - "status.context.load_new_replies": "Non hai respostas dispoñibles", - "status.context.loading": "Mirando se hai máis respostas", + "status.context.loading": "Cargando máis respostas", + "status.context.loading_error": "Non se puideron mostrar novas respostas", + "status.context.loading_success": "Móstranse novas respostas", + "status.context.more_replies_found": "Existen máis respostas", + "status.context.retry": "Volver tentar", + "status.context.show": "Mostrar", "status.continued_thread": "Continua co fío", "status.copy": "Copiar ligazón á publicación", "status.delete": "Eliminar", @@ -879,7 +934,7 @@ "status.edited_x_times": "Editado {count, plural, one {{count} vez} other {{count} veces}}", "status.embed": "O código a incluír", "status.favourite": "Favorecer", - "status.favourites": "{count, plural, one {favorecemento} other {favorecementos}}", + "status.favourites_count": "{count, plural, one {{counter} favorecemento} other {{counter} favorecementos}}", "status.filter": "Filtrar esta publicación", "status.history.created": "{name} creouno o {date}", "status.history.edited": "{name} editouno o {date}", @@ -895,9 +950,12 @@ "status.pin": "Fixar no perfil", "status.quote": "Citar", "status.quote.cancel": "Cancelar a cita", + "status.quote_error.blocked_account_hint.title": "A publicación está oculta porque bloqueaches a @{name}.", + "status.quote_error.blocked_domain_hint.title": "A publicación está oculta porque bloqueaches {domain}.", "status.quote_error.filtered": "Oculto debido a un dos teus filtros", "status.quote_error.limited_account_hint.action": "Mostrar igualmente", "status.quote_error.limited_account_hint.title": "A moderación de {domain} ocultou esta conta.", + "status.quote_error.muted_account_hint.title": "A publicación está oculta porque silenciaches a @{name}.", "status.quote_error.not_available": "Publicación non dispoñible", "status.quote_error.pending_approval": "Publicación pendente", "status.quote_error.pending_approval_popout.body": "En Mastodon podes establecer se permites que te citen. Esta publicación queda pendente á espera de que a persoa autora orixinal o autorice.", @@ -908,15 +966,17 @@ "status.quote_policy_change": "Cambia quen pode citarte", "status.quote_post_author": "Citou unha publicación de @{name}", "status.quote_private": "As publicacións privadas non se poden citar", - "status.quotes": "{count, plural, one {cita} other {citas}}", "status.quotes.empty": "Aínda ninguén citou esta publicación. Cando alguén o faga aparecerá aquí.", + "status.quotes.local_other_disclaimer": "Non se mostrarán as citas rexeitadas pola autora.", + "status.quotes.remote_other_disclaimer": "Só se garante que se mostren as citas do dominio {domain}. Non se mostrarán as citas rexeitadas pola persoa autora.", + "status.quotes_count": "{count, plural, one {{counter} cita} other {{counter} citas}}", "status.read_more": "Ler máis", "status.reblog": "Promover", "status.reblog_or_quote": "Promover ou citar", "status.reblog_private": "Volver a compartir coas túas seguidoras", "status.reblogged_by": "{name} promoveu", - "status.reblogs": "{count, plural, one {promoción} other {promocións}}", "status.reblogs.empty": "Aínda ninguén promoveu esta publicación. Cando alguén o faga, amosarase aquí.", + "status.reblogs_count": "{count, plural, one {{counter} promoción} other {{counter} promocións}}", "status.redraft": "Eliminar e reescribir", "status.remove_bookmark": "Eliminar marcador", "status.remove_favourite": "Retirar das favoritas", @@ -990,6 +1050,8 @@ "video.volume_down": "Baixar volume", "video.volume_up": "Subir volume", "visibility_modal.button_title": "Establece a visibilidade", + "visibility_modal.direct_quote_warning.text": "Se gardas os axustes actuais a cita incluída vaise converter nunha ligazón.", + "visibility_modal.direct_quote_warning.title": "As citas non se poden incluír nas mencións privadas", "visibility_modal.header": "Visibilidade e interaccións", "visibility_modal.helper.direct_quoting": "As mencións privadas creadas con Mastodon non poden ser citadas.", "visibility_modal.helper.privacy_editing": "Non se pode cambiar a visibilidade unha vez foi publicada.", @@ -1000,7 +1062,7 @@ "visibility_modal.privacy_label": "Visibilidade", "visibility_modal.quote_followers": "Só para seguidoras", "visibility_modal.quote_label": "Quen pode citar", - "visibility_modal.quote_nobody": "Só para min", + "visibility_modal.quote_nobody": "Só eu", "visibility_modal.quote_public": "Calquera", "visibility_modal.save": "Gardar" } diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index bcba5e0b7ce103..3cee7adc5e12e7 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -28,6 +28,7 @@ "account.disable_notifications": "הפסק לשלוח לי התראות כש@{name} מפרסמים", "account.domain_blocking": "רשימת השרתים החסומים", "account.edit_profile": "עריכת פרופיל", + "account.edit_profile_short": "עריכה", "account.enable_notifications": "שלח לי התראות כש@{name} מפרסם", "account.endorse": "קדם את החשבון בפרופיל", "account.familiar_followers_many": "החשבון נעקב על ידי {name1}, {name2} ועוד {othersCount, plural,one {אחד נוסף שמוכר לך}other {# נוספים שמוכרים לך}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "אין חצרוצים", "account.follow": "לעקוב", "account.follow_back": "לעקוב בחזרה", + "account.follow_back_short": "לעקוב בחזרה", + "account.follow_request": "בקשה לעקוב אחרי", + "account.follow_request_cancel": "ביטול בקשה", + "account.follow_request_cancel_short": "ביטול", + "account.follow_request_short": "בקשה", "account.followers": "עוקבים", "account.followers.empty": "אף אחד לא עוקב אחר המשתמש הזה עדיין.", "account.followers_counter": "{count, plural,one {עוקב אחד} other {{counter} עוקבים}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "הודעות ותגובות", "account.remove_from_followers": "הסרת {name} מעוקבי", "account.report": "דווח על @{name}", - "account.requested": "בהמתנה לאישור. לחצי כדי לבטל בקשת מעקב", "account.requested_follow": "{name} ביקשו לעקוב אחריך", "account.requests_to_follow_you": "ביקשו לעקוב אחריך", "account.share": "שתף את הפרופיל של @{name}", @@ -108,25 +113,52 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "תיאור התוכן לפגועי ראיה…", "alt_text_modal.done": "סיום", "announcement.announcement": "הכרזה", - "annual_report.summary.archetype.booster": "ההד-וניסט(ית)", - "annual_report.summary.archetype.lurker": "השורץ.ת השקט.ה", - "annual_report.summary.archetype.oracle": "כבוד הרב.ה", - "annual_report.summary.archetype.pollster": "הסקרן.ית", - "annual_report.summary.archetype.replier": "הפרפר.ית החברתי.ת", - "annual_report.summary.followers.followers": "עוקבים", - "annual_report.summary.followers.total": "{count} בסך הכל", - "annual_report.summary.here_it_is": "והנה סיכום {year} שלך:", - "annual_report.summary.highlighted_post.by_favourites": "התות הכי מחובב", - "annual_report.summary.highlighted_post.by_reblogs": "התות הכי מהודהד", - "annual_report.summary.highlighted_post.by_replies": "התות עם מספר התשובות הגבוה ביותר", - "annual_report.summary.highlighted_post.possessive": "של {name}", + "annual_report.announcement.action_build": "בנה לי את הסיכומודון שלי", + "annual_report.announcement.action_dismiss": "לא תודה", + "annual_report.announcement.action_view": "לצפייה בסיכומודון שלי", + "annual_report.announcement.description": "ללמוד עוד על דבפוסי השימוש שלך במסטודון לאורך השנה החולפת.", + "annual_report.announcement.title": "סיכומודון {year} הגיע", + "annual_report.nav_item.badge": "חדש", + "annual_report.shared_page.donate": "לתרומה", + "annual_report.shared_page.footer": "נוצר עם כל ה-{heart} על ידי צוות מסטודון", + "annual_report.shared_page.footer_server_info": "{username} משתמש.ת ב־{domain}, שרת אחד מבין קהילות רבות שבנויות על מסטודון.", + "annual_report.summary.archetype.booster.desc_public": "{name} צדו הודעות מעניינות להדהד, והגבירו קולותיהם של יוצרים אחרים בדיוק של חתול המזנק על הטרף.", + "annual_report.summary.archetype.booster.desc_self": "צדת הודעות מעניינות להדהד, והגברת קולותיהם של יוצרים אחרים בדיוק של חתול המזנק על הטרף.", + "annual_report.summary.archetype.booster.name": "החתול הצייד", + "annual_report.summary.archetype.die_drei_fragezeichen": "???", + "annual_report.summary.archetype.lurker.desc_public": "למיטב ידיעתנו {name} היו שם, איפשהוא, נהנים ממסטודון בדרכם השקטה.", + "annual_report.summary.archetype.lurker.desc_self": "למיטב ידיעתנו היית שם, איפשהוא, נהנית ממסטודון בדרכך השקטה.", + "annual_report.summary.archetype.lurker.name": "הסטואי.ת", + "annual_report.summary.archetype.oracle.desc_public": "{name} יצרו יותר הודעות מאשר תגובות, ושמרו על מסטודון רעננה ועם פנים לעתיד.", + "annual_report.summary.archetype.oracle.desc_self": "יצרת יותר הודעות מאשר תגובות, ושמרת על מסטודון רעננה ועם פנים לעתיד.", + "annual_report.summary.archetype.oracle.name": "כבוד הרב.ה", + "annual_report.summary.archetype.pollster.desc_public": "{name} יצרו יותר סקרים מאשר כל סוג הודעה אחר, וכך הרבו סקרנות במסטודון.", + "annual_report.summary.archetype.pollster.desc_self": "יצרת יותר סקרים מאשר כל סוג הודעה אחר, וכך הרבת סקרנות במסטודון.", + "annual_report.summary.archetype.pollster.name": "התוהה", + "annual_report.summary.archetype.replier.desc_public": "{name} ענו תכופות לאחרים, והפרו את מסטודון בדיונים חדשים.", + "annual_report.summary.archetype.replier.desc_self": "ענית תכופות לאחרים, והפרית את מסטודון בדיונים חדשים.", + "annual_report.summary.archetype.replier.name": "הפרפר", + "annual_report.summary.archetype.reveal": "גלו את הארכיטיפ שלכם", + "annual_report.summary.archetype.reveal_description": "תודה שאתם חלק ממסטודון! הגיע הזמן לגלות מה הארכיטיפ שגילמתן בשנת {year}.", + "annual_report.summary.archetype.title_public": "הארכיטיפ של {name}", + "annual_report.summary.archetype.title_self": "הארכיטיפ שלך", + "annual_report.summary.close": "סגירה", + "annual_report.summary.copy_link": "העתק קישור", + "annual_report.summary.followers.new_followers": "{count, plural,one {עוקב חדש} other {עוקבים חדשים}}", + "annual_report.summary.highlighted_post.boost_count": "הודעה זו הודהדה {count, plural,one {פעם אחת}other {# פעמים}}.", + "annual_report.summary.highlighted_post.favourite_count": "הודעה זו חובבה {count, plural,one {פעם אחת}other {# פעמים}}.", + "annual_report.summary.highlighted_post.reply_count": "הודעה זו נענתה על ידי {count, plural,one {תגובה אחת}other {# תגובות}}.", + "annual_report.summary.highlighted_post.title": "ההודעה הפופולרית ביותר", "annual_report.summary.most_used_app.most_used_app": "היישומון שהכי בשימוש", "annual_report.summary.most_used_hashtag.most_used_hashtag": "התג בשימוש הרב ביותר", - "annual_report.summary.most_used_hashtag.none": "אף אחד", + "annual_report.summary.most_used_hashtag.used_count": "כללת את התגית הזו {count, plural,one {בהודעה אחת}other {ב-# הודעות}}.", + "annual_report.summary.most_used_hashtag.used_count_public": "{name} כללו את התגית {count, plural,one {בהודעה אחת}other {ב־# הודעות}}.", "annual_report.summary.new_posts.new_posts": "הודעות חדשות", "annual_report.summary.percentile.text": "ממקם אותך באחוזון של משמשי {domain}.", "annual_report.summary.percentile.we_wont_tell_bernie": "לא נגלה לברני.", - "annual_report.summary.thanks": "תודה על היותך חלק ממסטודון!", + "annual_report.summary.share_elsewhere": "שיתוף במקום אחר", + "annual_report.summary.share_message": "זוהיתי כדוגמא לטיפוס {archetype}!", + "annual_report.summary.share_on_mastodon": "לשתף במסטודון", "attachments_list.unprocessed": "(לא מעובד)", "audio.hide": "השתק", "block_modal.remote_users_caveat": "אנו נבקש מהשרת {domain} לכבד את החלטתך. עם זאת, ציות למוסכמות איננו מובטח כיוון ששרתים מסויימים עשויים לטפל בחסימות בצורה אחרת. הודעות פומביות עדיין יהיו גלויות לעיני משתמשים שאינם מחוברים.", @@ -152,6 +184,8 @@ "bundle_modal_error.close": "לסגור", "bundle_modal_error.message": "משהו השתבש בעת טעינת המסך הזה.", "bundle_modal_error.retry": "לנסות שוב", + "carousel.current": "שקופית{current, number} מתוך {max, number}", + "carousel.slide": "שקופית {current, number} מתוך {max, number}", "closed_registrations.other_server_instructions": "מכיוון שמסטודון היא רשת מבוזרת, ניתן ליצור חשבון על שרת נוסף ועדיין לקיים קשר עם משתמשים בשרת זה.", "closed_registrations_modal.description": "יצירת חשבון על שרת {domain} איננה אפשרית כרגע, אבל זכרו שאינכן זקוקות לחשבון על {domain} כדי להשתמש במסטודון.", "closed_registrations_modal.find_another_server": "חיפוש שרת אחר", @@ -168,6 +202,8 @@ "column.edit_list": "עריכת רשימה", "column.favourites": "חיבובים", "column.firehose": "פידים עדכניים", + "column.firehose_local": "פיד זמן האמת עבור שרת זה", + "column.firehose_singular": "פיד זמן אמת", "column.follow_requests": "בקשות מעקב", "column.home": "פיד הבית", "column.list_members": "ניהול חברי הרשימה", @@ -187,6 +223,7 @@ "community.column_settings.local_only": "מקומי בלבד", "community.column_settings.media_only": "מדיה בלבד", "community.column_settings.remote_only": "מרוחק בלבד", + "compose.error.blank_post": "הודעה לא יכולה להיות ריקה.", "compose.language.change": "שינוי שפת ההודעה", "compose.language.search": "חיפוש שפות...", "compose.published.body": "הודעה פורסמה.", @@ -239,6 +276,11 @@ "confirmations.missing_alt_text.secondary": "לפרסם בכל זאת", "confirmations.missing_alt_text.title": "להוסיף מלל חלופי?", "confirmations.mute.confirm": "להשתיק", + "confirmations.private_quote_notify.cancel": "חזרה לעריכה", + "confirmations.private_quote_notify.confirm": "פרסום ההודעה", + "confirmations.private_quote_notify.do_not_show_again": "אל תראה לי הודעה זו שוב", + "confirmations.private_quote_notify.message": "המצוטט ומאוזכרים אחרים יקבלו התראה ויוכלו לקרוא את ההודעה שלך, אפילו אם אינם עוקבים אחריך.", + "confirmations.private_quote_notify.title": "לשתף עם עוקבים ומאוזכרים?", "confirmations.quiet_post_quote_info.dismiss": "אל תזכיר לי שוב", "confirmations.quiet_post_quote_info.got_it": "הבנתי", "confirmations.quiet_post_quote_info.message": "כשמצטטים הודעה ציבורית שקטה, גם ההודעה המצטטת תוסתר מחלון הנושאים החמים.", @@ -252,9 +294,12 @@ "confirmations.revoke_quote.confirm": "הסרת הודעה", "confirmations.revoke_quote.message": "פעולה זו אינה הפיכה.", "confirmations.revoke_quote.title": "הסרת הודעה?", + "confirmations.unblock.confirm": "הסרת חסימה", + "confirmations.unblock.title": "הסרת חסימה מ־{name}?", "confirmations.unfollow.confirm": "הפסקת מעקב", - "confirmations.unfollow.message": "להפסיק מעקב אחרי {name}?", - "confirmations.unfollow.title": "לבטל מעקב אחר המשתמש.ת?", + "confirmations.unfollow.title": "בטול מעקב אחרי {name}?", + "confirmations.withdraw_request.confirm": "משיכת בקשה", + "confirmations.withdraw_request.title": "משיכת בקשת מעקב אחרי {name}?", "content_warning.hide": "הסתרת חיצרוץ", "content_warning.show": "להציג בכל זאת", "content_warning.show_more": "הצג עוד", @@ -325,6 +370,7 @@ "empty_column.bookmarked_statuses": "אין עדיין הודעות שחיבבת. כשתחבב את הראשונה, היא תופיע כאן.", "empty_column.community": "פיד השרת המקומי ריק. יש לפרסם משהו כדי שדברים יתרחילו להתגלגל!", "empty_column.direct": "אין לך שום הודעות פרטיות עדיין. כשתשלחו או תקבלו אחת, היא תופיע כאן.", + "empty_column.disabled_feed": "פיד זה נחסם לשימוש על ידי מנהלי השרת שלך.", "empty_column.domain_blocks": "אין עדיין קהילות מוסתרות.", "empty_column.explore_statuses": "אין נושאים חמים כרגע. אולי אחר כך!", "empty_column.favourited_statuses": "אין עדיין הודעות שחיבבת. כשתחבב/י את הראשונה, היא תופיע כאן.", @@ -338,6 +384,7 @@ "empty_column.notification_requests": "בום! אין פה כלום. כשיווצרו עוד התראות, הן יופיעו כאן על בסיס ההעדפות שלך.", "empty_column.notifications": "אין התראות עדיין. יאללה, הגיע הזמן להתחיל להתערבב.", "empty_column.public": "אין פה כלום! כדי למלא את הטור הזה אפשר לכתוב משהו, או להתחיל לעקוב אחרי אנשים מקהילות אחרות", + "error.no_hashtag_feed_access": "הצטרפו או התחברו כדי לעקוב אחרי תגית זו.", "error.unexpected_crash.explanation": "עקב תקלה בקוד שלנו או בעיית תאימות דפדפן, לא ניתן להציג דף זה כראוי.", "error.unexpected_crash.explanation_addons": "לא ניתן להציג דף זה כראוי. הבעיה נגרמת כנראה עקב תוסף דפדפן או כלי תרגום אוטומטי.", "error.unexpected_crash.next_steps": "נסה/י לרענן את הדף. אם זה לא עוזר, אולי אפשר עדיין להשתמש במסטודון דרך דפדפן אחר או באמצעות אפליקציה ילידית.", @@ -349,11 +396,9 @@ "explore.trending_links": "חדשות", "explore.trending_statuses": "הודעות", "explore.trending_tags": "תגיות", + "featured_carousel.current": "הודעה{current, number} מתוך {max, number}", "featured_carousel.header": "{count, plural, one {הודעה אחת נעוצה} two {הודעותיים נעוצות} many {הודעות נעוצות} other {הודעות נעוצות}}", - "featured_carousel.next": "הבא", - "featured_carousel.post": "הודעה", - "featured_carousel.previous": "הקודם", - "featured_carousel.slide": "{index} מתוך {total}", + "featured_carousel.slide": "הודעה {current, number} מתוך {max, number}", "filter_modal.added.context_mismatch_explanation": "קטגוריית הסנן הזאת לא חלה על ההקשר שממנו הגעת אל ההודעה הזו. אם תרצה/י שההודעה תסונן גם בהקשר זה, תצטרך/י לערוך את הסנן.", "filter_modal.added.context_mismatch_title": "אין התאמה להקשר!", "filter_modal.added.expired_explanation": "פג תוקפה של קטגוריית הסינון הזו, יש צורך לשנות את תאריך התפוגה כדי שהסינון יוחל.", @@ -396,6 +441,9 @@ "follow_suggestions.who_to_follow": "אחרי מי לעקוב", "followed_tags": "התגיות שהחשבון שלך עוקב אחריהן", "footer.about": "אודות", + "footer.about_mastodon": "אודות מסטודון", + "footer.about_server": "‮אודות ‭{domain}", + "footer.about_this_server": "אודות", "footer.directory": "ספריית פרופילים", "footer.get_app": "להתקנת היישומון", "footer.keyboard_shortcuts": "קיצורי מקלדת", @@ -497,6 +545,7 @@ "keyboard_shortcuts.toggle_hidden": "הצגת/הסתרת טקסט מוסתר מאחורי אזהרת תוכן", "keyboard_shortcuts.toggle_sensitivity": "הצגת/הסתרת מדיה", "keyboard_shortcuts.toot": "להתחיל חיצרוץ חדש", + "keyboard_shortcuts.top": "העברה לראש הרשימה", "keyboard_shortcuts.translate": "לתרגם הודעה", "keyboard_shortcuts.unfocus": "לצאת מתיבת חיבור/חיפוש", "keyboard_shortcuts.up": "לנוע במעלה הרשימה", @@ -745,7 +794,9 @@ "privacy.unlisted.short": "ציבורי שקט", "privacy_policy.last_updated": "עודכן לאחרונה {date}", "privacy_policy.title": "מדיניות פרטיות", + "quote_error.edit": "לא ניתן להוסיף ציטוטים בשלב עריכת ההודעה.", "quote_error.poll": "לא ניתן לכלול משאל כאשר מחברים הודעת ציטוט.", + "quote_error.private_mentions": "ציטוט אינו אפשרי בהודעות פרטיות.", "quote_error.quote": "רק ציטוט אחד מותר בכל הודעה.", "quote_error.unauthorized": "אין לך הרשאה לצטט את ההודעה הזו.", "quote_error.upload": "ציטוט הכולל מדיה אינו אפשרי.", @@ -865,8 +916,12 @@ "status.cannot_quote": "אין לך הרשאה לצטט את ההודעה הזו", "status.cannot_reblog": "לא ניתן להדהד חצרוץ זה", "status.contains_quote": "הודעה מכילה ציטוט", - "status.context.load_new_replies": "הגיעו תגובות חדשות", - "status.context.loading": "מחפש תגובות חדשות", + "status.context.loading": "נטענות תשובות נוספות", + "status.context.loading_error": "טעינת תשובות נוספות נכשלה", + "status.context.loading_success": "תשובות חדשות נטענו", + "status.context.more_replies_found": "תשובות נוספות נמצאו", + "status.context.retry": "נסה שוב", + "status.context.show": "הצג", "status.continued_thread": "שרשור מתמשך", "status.copy": "העתק/י קישור להודעה זו", "status.delete": "מחיקה", @@ -879,7 +934,7 @@ "status.edited_x_times": "נערך {count, plural, one {פעם {count}} other {{count} פעמים}}", "status.embed": "העתקת קוד להטמעה", "status.favourite": "חיבוב", - "status.favourites": "{count, plural, one {חיבוב אחד} two {זוג חיבובים} other {# חיבובים}}", + "status.favourites_count": "{count, plural, one {חיבוב אחד} two {חיבוביים} many {{counter} חיבובים} other {{counter} חיבובים}}", "status.filter": "סנן הודעה זו", "status.history.created": "{name} יצר/ה {date}", "status.history.edited": "{name} ערך/ה {date}", @@ -895,9 +950,12 @@ "status.pin": "הצמדה לפרופיל שלי", "status.quote": "ציטוט", "status.quote.cancel": "ביטול הודעת ציטוט", + "status.quote_error.blocked_account_hint.title": "ההודעה הזו מוסתרת כי חסמת את @{name}.", + "status.quote_error.blocked_domain_hint.title": "ההודעה הזו מוסתרת כי חסמת את כל {domain}.", "status.quote_error.filtered": "מוסתר בהתאם לסננים שלך", "status.quote_error.limited_account_hint.action": "להציג בכל זאת", "status.quote_error.limited_account_hint.title": "חשבון הזה הוסתר על ידי מנחי הדיון של {domain}.", + "status.quote_error.muted_account_hint.title": "ההודעה הזו מוסתרת כי השתקת את @{name}.", "status.quote_error.not_available": "ההודעה לא זמינה", "status.quote_error.pending_approval": "ההודעה בהמתנה לאישור", "status.quote_error.pending_approval_popout.body": "ברשת מסטודון, ניתן להגביל את האפשרות לצטט הודעות. ההודעה הזו ממתינה עד שהמחבר.ת של ההודעה המקורית יאשרו לך את הציטוט.", @@ -906,17 +964,19 @@ "status.quote_manual_review": "מחבר.ת ההודעה יחזרו אליך אחרי בדיקה", "status.quote_noun": "ציטוט", "status.quote_policy_change": "הגדרת הרשאה לציטוט הודעותיך", - "status.quote_post_author": "ההודעה צוטטה על ידי @{name}", + "status.quote_post_author": "ההודעה היא ציטוט של @{name}", "status.quote_private": "הודעות פרטיות לא ניתנות לציטוט", - "status.quotes": "{count, plural,one {ציטוט}other {ציטוטים}}", "status.quotes.empty": "עוד לא ציטטו את ההודעה הזו. כאשר זה יקרה, הציטוטים יופיעו כאן.", + "status.quotes.local_other_disclaimer": "ציטוטים שיידחו על ידי המחברים המקוריים לא יוצגו.", + "status.quotes.remote_other_disclaimer": "רק ציטוטים מהשרת {domain} מובטחים שיופיעו פה. ציטוטים שנדחו על ידי המצוטטים לא יופיעו.", + "status.quotes_count": "{count, plural, one {ציטוט אחד} two {שני ציטוטים} many {{counter} ציטוטים} other {{counter} ציטוטים}}", "status.read_more": "לקרוא עוד", "status.reblog": "הדהוד", "status.reblog_or_quote": "להדהד או לצטט", "status.reblog_private": "שיתוף מחדש עם העוקבות והעוקבים שלך", "status.reblogged_by": "{name} הידהד/ה:", - "status.reblogs": "{count, plural, one {הדהוד אחד} two {שני הדהודים} other {# הדהודים}}", "status.reblogs.empty": "עוד לא הידהדו את ההודעה הזו. כאשר זה יקרה, ההדהודים יופיעו כאן.", + "status.reblogs_count": "{count, plural, one {הידהוד אחד} two {שני הדהודים} many {{counter} הדהודים} other {{counter} הדהודים}}", "status.redraft": "מחיקה ועריכה מחדש", "status.remove_bookmark": "הסרת סימניה", "status.remove_favourite": "להסיר מרשימת המועדפים", @@ -990,6 +1050,8 @@ "video.volume_down": "הנמכת עוצמת השמע", "video.volume_up": "הגברת עוצמת שמע", "visibility_modal.button_title": "בחירת רמת חשיפה", + "visibility_modal.direct_quote_warning.text": "אם תשמרו את ההעדפות הנוכחיות, הציטוט המוטמע יהפוך לקישור.", + "visibility_modal.direct_quote_warning.title": "לא ניתן להטמיע ציטוטים בהודעות פרטיות", "visibility_modal.header": "חשיפה והידוּד (אינטראקציה)", "visibility_modal.helper.direct_quoting": "איזכורים פרטיים שנוצרו במסטודון חסומים מציטוט על ידי אחרים.", "visibility_modal.helper.privacy_editing": "רמת החשיפה של ההודעה לא ניתנת לשינוי אחרי הפרסום.", diff --git a/app/javascript/mastodon/locales/hi.json b/app/javascript/mastodon/locales/hi.json index ca56ad484e2057..c8fa5f86f5c7c4 100644 --- a/app/javascript/mastodon/locales/hi.json +++ b/app/javascript/mastodon/locales/hi.json @@ -1,6 +1,7 @@ { "about.blocks": "मॉडरेट सर्वर", "about.contact": "कांटेक्ट:", + "about.default_locale": "Default", "about.disclaimer": "मास्टोडन एक ओपन सोर्स सॉफ्टवेयर है, और मास्टोडन gGmbH का ट्रेडमार्क है।", "about.domain_blocks.no_reason_available": "कारण उपलब्ध नहीं है!", "about.domain_blocks.preamble": "मास्टोडन आम तौर पर आपको कंटेंट को देखने और फेडिवेर्से में किसी अन्य सर्वर से उपयोगकर्ताओं के साथ बातचीत करने की अनुमति देता है। ये अपवाद हैं जो इस विशेष सर्वर पर बनाए गए हैं।", @@ -8,9 +9,11 @@ "about.domain_blocks.silenced.title": "सीमित", "about.domain_blocks.suspended.explanation": "इस सर्वर से कोई डेटा संसाधित, संग्रहीत या आदान-प्रदान नहीं किया जाएगा, जिससे इस सर्वर से उपयोगकर्ताओं के साथ कोई भी बातचीत या संचार असंभव हो जाएगा", "about.domain_blocks.suspended.title": "सस्पेंड किआ गया है!", + "about.language_label": "भाषा", "about.not_available": "यह जानकारी इस सर्वर पर उपलब्ध नहीं कराई गई है।", "about.powered_by": "{mastodon} द्वारा संचालित डेसेंट्रलीसेड सोशल मीडिया प्लैटफ़ॉर्म!", "about.rules": "सर्वर के नियम", + "account.account_note_header": "व्यक्तिगत नोंध", "account.add_or_remove_from_list": "सूची में जोड़ें या हटाए", "account.badges.bot": "बॉट", "account.badges.group": "समूह", @@ -18,17 +21,31 @@ "account.block_domain": "{domain} के सारी चीज़े छुपाएं", "account.block_short": "ब्लॉक किया गया", "account.blocked": "ब्लॉक", + "account.blocking": "प्रतिबंधित करना", "account.cancel_follow_request": "फॉलो रिक्वेस्ट वापस लें", "account.copy": "प्रोफाइल पर लिंक कॉपी करें", "account.direct": "निजि तरीके से उल्लेख करे @{name}", "account.disable_notifications": "@{name} पोस्ट के लिए मुझे सूचित मत करो", + "account.domain_blocking": "डोमेन ब्लॉक करें", "account.edit_profile": "प्रोफ़ाइल संपादित करें", + "account.edit_profile_short": "संपादित करें", "account.enable_notifications": "जब @{name} पोस्ट मौजूद हो सूचित करें", "account.endorse": "प्रोफ़ाइल पर दिखाए", + "account.familiar_followers_many": "{name1}{name2} और {othersCount, plural, one {एक और जिन्हे आप जानते है} other {# और जिन्हे आप जानते है}}", + "account.familiar_followers_one": "{name1} ने अनुसरण किया है", + "account.familiar_followers_two": "{name1} और {name2} ने अनुसरण किया है", + "account.featured": "प्रचलित", + "account.featured.accounts": "प्रोफ़ाइल", + "account.featured.hashtags": "हैशटैग्स", "account.featured_tags.last_status_at": "{date} का अंतिम पोस्ट", "account.featured_tags.last_status_never": "कोई पोस्ट नहीं है", "account.follow": "फॉलो करें", "account.follow_back": "फॉलो करें", + "account.follow_back_short": "वापस अनुसरण करें", + "account.follow_request": "अनुसरण करने की बिनती करें", + "account.follow_request_cancel": "अनुरोध रद्द करें", + "account.follow_request_cancel_short": "रद्द करें", + "account.follow_request_short": "अनुरोध करें", "account.followers": "फॉलोवर", "account.followers.empty": "कोई भी इस यूज़र् को फ़ॉलो नहीं करता है", "account.following": "फॉलोइंग", @@ -52,7 +69,6 @@ "account.posts": "टूट्स", "account.posts_with_replies": "टूट्स एवं जवाब", "account.report": "रिपोर्ट @{name}", - "account.requested": "मंजूरी का इंतजार। फॉलो रिक्वेस्ट को रद्द करने के लिए क्लिक करें", "account.requested_follow": "{name} ने आपको फॉलो करने के लिए अनुरोध किया है", "account.share": "@{name} की प्रोफाइल शेयर करे", "account.show_reblogs": "@{name} के बूस्ट दिखाए", @@ -169,7 +185,6 @@ "confirmations.redraft.confirm": "मिटायें और पुनःप्रारूपण करें", "confirmations.redraft.message": "क्या आप वाकई इस स्टेटस को हटाना चाहते हैं और इसे फिर से ड्राफ्ट करना चाहते हैं? पसंदीदा और बूस्ट खो जाएंगे, और मूल पोस्ट के उत्तर अनाथ हो जाएंगे।", "confirmations.unfollow.confirm": "अनफॉलो करें", - "confirmations.unfollow.message": "क्या आप वाकई {name} को अनफॉलो करना चाहते हैं?", "conversation.delete": "वार्तालाप हटाएँ", "conversation.mark_as_read": "पढ़ा गया के रूप में चिह्नित करें", "conversation.open": "वार्तालाप देखें", diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json index cb3f25de0699fe..3f8842d91ffadd 100644 --- a/app/javascript/mastodon/locales/hr.json +++ b/app/javascript/mastodon/locales/hr.json @@ -51,7 +51,6 @@ "account.posts": "Objave", "account.posts_with_replies": "Objave i odgovori", "account.report": "Prijavi @{name}", - "account.requested": "Čekanje na potvrdu. Kliknite za poništavanje zahtjeva za praćenje", "account.requested_follow": "{name} zatražio/la je praćenje", "account.share": "Podijeli profil @{name}", "account.show_reblogs": "Prikaži boostove od @{name}", @@ -147,7 +146,6 @@ "confirmations.mute.confirm": "Utišaj", "confirmations.redraft.confirm": "Izbriši i ponovno uredi", "confirmations.unfollow.confirm": "Prestani pratiti", - "confirmations.unfollow.message": "Jeste li sigurni da želite prestati pratiti {name}?", "conversation.delete": "Izbriši razgovor", "conversation.mark_as_read": "Označi kao pročitano", "conversation.open": "Prikaži razgovor", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index 83fb3e60fe4c6e..c21b6016f08e46 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Ne figyelmeztessen, ha @{name} bejegyzést tesz közzé", "account.domain_blocking": "Domain tiltás", "account.edit_profile": "Profil szerkesztése", + "account.edit_profile_short": "Szerkesztés", "account.enable_notifications": "Figyelmeztessen, ha @{name} bejegyzést tesz közzé", "account.endorse": "Kiemelés a profilodon", "account.familiar_followers_many": "{name1}, {name2} és még {othersCount, plural, one {egy valaki} other {# valaki}}, akit ismersz", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Nincs bejegyzés", "account.follow": "Követés", "account.follow_back": "Viszontkövetés", + "account.follow_back_short": "Visszakövetés", + "account.follow_request": "Követési kérés", + "account.follow_request_cancel": "Kérés törlése", + "account.follow_request_cancel_short": "Mégse", + "account.follow_request_short": "Kérés", "account.followers": "Követő", "account.followers.empty": "Ezt a felhasználót még senki sem követi.", "account.followers_counter": "{count, plural, one {{counter} követő} other {{counter} követő}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Bejegyzések és válaszok", "account.remove_from_followers": "{name} eltávolítása a követők közül", "account.report": "@{name} jelentése", - "account.requested": "Jóváhagyásra vár. Kattints a követési kérés visszavonásához", "account.requested_follow": "{name} kérte, hogy követhessen", "account.requests_to_follow_you": "Kéri, hogy követhessen", "account.share": "@{name} profiljának megosztása", @@ -108,25 +113,52 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Írd le a látássérültek számára…", "alt_text_modal.done": "Kész", "announcement.announcement": "Közlemény", - "annual_report.summary.archetype.booster": "A cool-vadász", - "annual_report.summary.archetype.lurker": "A settenkedő", - "annual_report.summary.archetype.oracle": "Az orákulum", - "annual_report.summary.archetype.pollster": "A közvélemény-kutató", - "annual_report.summary.archetype.replier": "A társasági pillangó", - "annual_report.summary.followers.followers": "követő", - "annual_report.summary.followers.total": "{count} összesen", - "annual_report.summary.here_it_is": "Itt a {year}. év értékelése:", - "annual_report.summary.highlighted_post.by_favourites": "legkedvencebb bejegyzés", - "annual_report.summary.highlighted_post.by_reblogs": "legtöbbet megtolt bejegyzés", - "annual_report.summary.highlighted_post.by_replies": "bejegyzés a legtöbb válasszal", - "annual_report.summary.highlighted_post.possessive": "{name} fióktól", + "annual_report.announcement.action_build": "Saját Wrapstodon összeállítása", + "annual_report.announcement.action_dismiss": "Nem, köszönöm", + "annual_report.announcement.action_view": "Saját Wrapstodon megtekintése", + "annual_report.announcement.description": "Fedezz fel többet a Mastodonon az elmúlt évben végzett tevékenységeidről.", + "annual_report.announcement.title": "A Wrapstodon {year} megérkezett", + "annual_report.nav_item.badge": "Új", + "annual_report.shared_page.donate": "Adományozás", + "annual_report.shared_page.footer": "A Mastodon által {heart}-tel előállítva", + "annual_report.shared_page.footer_server_info": "{username} a {domain} domaint használja, amely a Mastodon által működtetett számos közösség egyike.", + "annual_report.summary.archetype.booster.desc_public": "{name} megtolandó bejegyzésekre vadászott, tökéletes célzással felerősítve mások üzeneteit.", + "annual_report.summary.archetype.booster.desc_self": "Megtolandó bejegyzésekre vadásztál, tökéletes célzással felerősítve mások üzeneteit.", + "annual_report.summary.archetype.booster.name": "Az íjász", + "annual_report.summary.archetype.die_drei_fragezeichen": "???", + "annual_report.summary.archetype.lurker.desc_public": "Tudjuk, hogy {name} ott volt, valahol, a saját csendes módján élvezve a Mastodont.", + "annual_report.summary.archetype.lurker.desc_self": "Tudjuk, hogy ott voltál, valahol, a saját csendes módodon élvezve a Mastodont.", + "annual_report.summary.archetype.lurker.name": "A sztoikus", + "annual_report.summary.archetype.oracle.desc_public": "{name} több bejegyzést hozott létre, mint választ, így a Mastodon friss és jövőbe tekintő marad.", + "annual_report.summary.archetype.oracle.desc_self": "Több bejegyzést hoztál létre, mint választ, így a Mastodon friss és jövőbe tekintő marad.", + "annual_report.summary.archetype.oracle.name": "Az orákulum", + "annual_report.summary.archetype.pollster.desc_public": "{name} több szavazást hozott létre, mint bármilyen más bejegyzést, ezzel támogatva a kíváncsiságot a Mastodonon.", + "annual_report.summary.archetype.pollster.desc_self": "Több szavazást hoztál létre, mint bármilyen más bejegyzést, ezzel támogatva a kíváncsiságot a Mastodonon.", + "annual_report.summary.archetype.pollster.name": "A csodálkozó", + "annual_report.summary.archetype.replier.desc_public": "{name} gyakran válaszolt más emberek bejegyzéseire, új témákat porozva be a Mastodonon.", + "annual_report.summary.archetype.replier.desc_self": "Gyakran válaszoltál más emberek bejegyzéseire, új témákat porozva be a Mastodonon.", + "annual_report.summary.archetype.replier.name": "A pillangó", + "annual_report.summary.archetype.reveal": "Saját archetípus felfedése", + "annual_report.summary.archetype.reveal_description": "Köszönjük, hogy a Mastodon része vagy! Tudj meg többet az archetípusodról {year} évében.", + "annual_report.summary.archetype.title_public": "{name} archetípusa", + "annual_report.summary.archetype.title_self": "Saját archetípus", + "annual_report.summary.close": "Bezárás", + "annual_report.summary.copy_link": "Hivatkozás másolása", + "annual_report.summary.followers.new_followers": "{count, plural, one {új követő} other {új követők}}", + "annual_report.summary.highlighted_post.boost_count": "Ez a bejegyzés {count, plural, one {egyszer} other {# alkalommal}} volt megtolva.", + "annual_report.summary.highlighted_post.favourite_count": "Ez a bejegyzés {count, plural, one {egyszer} other {# alkalommal}} volt kedvencnek jelölve.", + "annual_report.summary.highlighted_post.reply_count": "Ez a bejegyzés {count, plural, one {egy választ} other {# választ}} kapott.", + "annual_report.summary.highlighted_post.title": "Legnépszerűbb bejegyzés", "annual_report.summary.most_used_app.most_used_app": "legtöbbet használt app", "annual_report.summary.most_used_hashtag.most_used_hashtag": "legtöbbet használt hashtag", - "annual_report.summary.most_used_hashtag.none": "Nincs", + "annual_report.summary.most_used_hashtag.used_count": "Ez a hashtag {count, plural, one {egy bejegyzésedben} other {# bejegyzésedben}} szerepel.", + "annual_report.summary.most_used_hashtag.used_count_public": "{name} {count, plural, one {egy bejegyzésében} other {# bejegyzésében}} használta ezt a hashtaget.", "annual_report.summary.new_posts.new_posts": "új bejegyzés", "annual_report.summary.percentile.text": "Ezzel a csúcs{domain} felhasználó között vagy.", "annual_report.summary.percentile.we_wont_tell_bernie": "Nem mondjuk el Bernie-nek.", - "annual_report.summary.thanks": "Kösz, hogy a Mastodon része vagy!", + "annual_report.summary.share_elsewhere": "Megosztás máshol", + "annual_report.summary.share_message": "{archetype} lett az archetípusom!", + "annual_report.summary.share_on_mastodon": "Megosztás a Mastodonon", "attachments_list.unprocessed": "(feldolgozatlan)", "audio.hide": "Hang elrejtése", "block_modal.remote_users_caveat": "Arra kérjük a {domain} kiszolgálót, hogy tartsa tiszteletben a döntésedet. Ugyanakkor az együttműködés nem garantált, mivel néhány kiszolgáló másképp kezelheti a letiltásokat. A nyilvános bejegyzések a be nem jelentkezett felhasználók számára továbbra is látszódhatnak.", @@ -152,6 +184,8 @@ "bundle_modal_error.close": "Bezárás", "bundle_modal_error.message": "Hiba történt a képernyő betöltésekor.", "bundle_modal_error.retry": "Próbáld újra", + "carousel.current": "{current, number}. dia / {max, number}", + "carousel.slide": "{current, number}. dia / {max, number}", "closed_registrations.other_server_instructions": "Mivel a Mastdon decentralizált, létrehozhatsz egy fiókot egy másik kiszolgálón és mégis kapcsolódhatsz ehhez.", "closed_registrations_modal.description": "Fiók létrehozása a {domain} kiszolgálón jelenleg nem lehetséges, de jó, ha tudod, hogy nem szükséges fiókkal rendelkezni pont a {domain} kiszolgálón, hogy használhasd a Mastodont.", "closed_registrations_modal.find_another_server": "Másik kiszolgáló keresése", @@ -168,6 +202,8 @@ "column.edit_list": "Lista módosítása", "column.favourites": "Kedvencek", "column.firehose": "Hírfolyamok", + "column.firehose_local": "Élő hírfolyam a kiszolgálóhoz", + "column.firehose_singular": "Élő hírfolyam", "column.follow_requests": "Követési kérések", "column.home": "Kezdőlap", "column.list_members": "Listatagok kezelése", @@ -187,6 +223,7 @@ "community.column_settings.local_only": "Csak helyi", "community.column_settings.media_only": "Csak média", "community.column_settings.remote_only": "Csak távoli", + "compose.error.blank_post": "A bejegyzés nem lehet üres.", "compose.language.change": "Nyelv megváltoztatása", "compose.language.search": "Nyelvek keresése…", "compose.published.body": "A bejegyzés publikálásra került.", @@ -239,6 +276,11 @@ "confirmations.missing_alt_text.secondary": "Közzététel mindenképpen", "confirmations.missing_alt_text.title": "Helyettesítő szöveg hozzáadása?", "confirmations.mute.confirm": "Némítás", + "confirmations.private_quote_notify.cancel": "Vissza a szerkesztéshez", + "confirmations.private_quote_notify.confirm": "Bejegyzés közzététele", + "confirmations.private_quote_notify.do_not_show_again": "Ne jelenítse meg újra ezt az üzenetet", + "confirmations.private_quote_notify.message": "Akit idézel, és a többi megemlített értesítve lesz, és megtekinthetik a bejegyzésed, még akkor is, ha nem követnek.", + "confirmations.private_quote_notify.title": "Megosztás a követőkkel és a megemlített felhasználókkal?", "confirmations.quiet_post_quote_info.dismiss": "Ne emlékeztessen újra", "confirmations.quiet_post_quote_info.got_it": "Rendben", "confirmations.quiet_post_quote_info.message": "Ha csendes nyilvános bejegyzést idézel, akkor a bejegyzés el lesz rejtve a felkapottak idővonalairól.", @@ -252,9 +294,12 @@ "confirmations.revoke_quote.confirm": "Bejegyzés eltávolítása", "confirmations.revoke_quote.message": "Ez a művelet nem vonható vissza.", "confirmations.revoke_quote.title": "Bejegyzés eltávolítása?", + "confirmations.unblock.confirm": "Tiltás feloldása", + "confirmations.unblock.title": "{name} tiltásának feloldása?", "confirmations.unfollow.confirm": "Követés visszavonása", - "confirmations.unfollow.message": "Biztos, hogy vissza szeretnéd vonni {name} követését?", - "confirmations.unfollow.title": "Megszünteted a felhasználó követését?", + "confirmations.unfollow.title": "{name} követésének megszüntetése?", + "confirmations.withdraw_request.confirm": "Kérés visszavonása", + "confirmations.withdraw_request.title": "{name} követése kérésének visszavonása?", "content_warning.hide": "Bejegyzés elrejtése", "content_warning.show": "Megjelenítés mindenképp", "content_warning.show_more": "Több megjelenítése", @@ -325,6 +370,7 @@ "empty_column.bookmarked_statuses": "Még nincs egyetlen könyvjelzőzött bejegyzésed sem. Ha könyvjelzőzöl egyet, itt fog megjelenni.", "empty_column.community": "A helyi idővonal üres. Tégy közzé valamit nyilvánosan, hogy elindítsd az eseményeket!", "empty_column.direct": "Még nincs egy személyes említésed sem. Küldéskor vagy fogadáskor itt fognak megjelenni.", + "empty_column.disabled_feed": "A kiszolgálód rendszergazdái letiltották ezt a hírfolyamot.", "empty_column.domain_blocks": "Még nem lett letiltva egyetlen domain sem.", "empty_column.explore_statuses": "Jelenleg semmi sem felkapott. Nézz vissza később!", "empty_column.favourited_statuses": "Még nincs egyetlen kedvenc bejegyzésed sem. Ha kedvencnek jelölsz egyet, itt fog megjelenni.", @@ -338,6 +384,7 @@ "empty_column.notification_requests": "Minden tiszta! Itt nincs semmi. Ha új értesítéseket kapsz, azok itt jelennek meg a beállításoknak megfelelően.", "empty_column.notifications": "Jelenleg még nincsenek értesítéseid. Ha mások kapcsolatba lépnek veled, ezek itt lesznek láthatóak.", "empty_column.public": "Jelenleg itt nincs semmi! Írj valamit nyilvánosan vagy kövess más kiszolgálón levő felhasználókat, hogy megtöltsd.", + "error.no_hashtag_feed_access": "Csatlakozz vagy jelentkezz be, hogy megtekintsd és kövesd ezt a hashtaget.", "error.unexpected_crash.explanation": "Egy kód- vagy böngészőkompatibilitási hiba miatt ez az oldal nem jeleníthető meg helyesen.", "error.unexpected_crash.explanation_addons": "Ezt az oldalt nem lehet helyesen megjeleníteni. Ezt a hibát valószínűleg egy böngésző kiegészítő vagy egy automatikus fordító okozza.", "error.unexpected_crash.next_steps": "Próbáld frissíteni az oldalt. Ha ez nem segít, egy másik böngészőn vagy appon keresztül még mindig használhatod a Mastodont.", @@ -349,11 +396,9 @@ "explore.trending_links": "Hírek", "explore.trending_statuses": "Bejegyzések", "explore.trending_tags": "Hashtagek", + "featured_carousel.current": "{current, number}. bejegyzés / {max, number}", "featured_carousel.header": "{count, plural, one {Kiemelt bejegyzés} other {Kiemelt bejegyzések}}", - "featured_carousel.next": "Következő", - "featured_carousel.post": "Bejegyzés", - "featured_carousel.previous": "Előző", - "featured_carousel.slide": "{index} / {total}", + "featured_carousel.slide": "{current, number}. bejegyzés / {max, number}", "filter_modal.added.context_mismatch_explanation": "Ez a szűrőkategória nem érvényes abban a környezetben, amelyből elérted ezt a bejegyzést. Ha ebben a környezetben is szűrni szeretnéd a bejegyzést, akkor szerkesztened kell a szűrőt.", "filter_modal.added.context_mismatch_title": "Környezeti eltérés.", "filter_modal.added.expired_explanation": "Ez a szűrőkategória elévült, a használatához módosítanod kell az elévülési dátumot.", @@ -396,6 +441,9 @@ "follow_suggestions.who_to_follow": "Kit érdemes követni", "followed_tags": "Követett hashtagek", "footer.about": "Névjegy", + "footer.about_mastodon": "A Mastodonról", + "footer.about_server": "A {domain} domainről", + "footer.about_this_server": "Névjegy", "footer.directory": "Profiltár", "footer.get_app": "Alkalmazás beszerzése", "footer.keyboard_shortcuts": "Gyorsbillentyűk", @@ -497,6 +545,7 @@ "keyboard_shortcuts.toggle_hidden": "Tartalmi figyelmeztetéssel ellátott szöveg megjelenítése/elrejtése", "keyboard_shortcuts.toggle_sensitivity": "Média megjelenítése/elrejtése", "keyboard_shortcuts.toot": "Új bejegyzés írása", + "keyboard_shortcuts.top": "Ugrás a lista elejére", "keyboard_shortcuts.translate": "Bejegyzés lefordítása", "keyboard_shortcuts.unfocus": "Szerkesztés/keresés fókuszból való kivétele", "keyboard_shortcuts.up": "Mozgás felfelé a listában", @@ -590,7 +639,7 @@ "notification.admin.sign_up": "{name} regisztrált", "notification.admin.sign_up.name_and_others": "{name} és {count, plural, one {# másik} other {# másik}} regisztrált", "notification.annual_report.message": "Vár a {year}. év #Wrapstodon jelentése! Fedd fel az éved jelentős eseményeit és emlékezetes pillanatait a Mastodonon!", - "notification.annual_report.view": "#Wrapstodon Megtekintése", + "notification.annual_report.view": "#Wrapstodon megtekintése", "notification.favourite": "{name} kedvencnek jelölte a bejegyzésedet", "notification.favourite.name_and_others_with_link": "{name} és {count, plural, one {# másik} other {# másik}} kedvencnek jelölte a bejegyzésedet", "notification.favourite_pm": "{name} kedvelte a privát említésedet", @@ -745,7 +794,9 @@ "privacy.unlisted.short": "Csendes nyilvános", "privacy_policy.last_updated": "Utoljára frissítve: {date}", "privacy_policy.title": "Adatvédelmi szabályzat", + "quote_error.edit": "Idézés nem adható hozzá bejegyzés szerkesztésekor.", "quote_error.poll": "Az idézés szavazások esetén nincs engedélyezve.", + "quote_error.private_mentions": "Az idézés közvetlen említések esetén nem engedélyezett.", "quote_error.quote": "Egyszerre csak egy idézet van engedélyezve.", "quote_error.unauthorized": "Nem idézheted ezt a bejegyzést.", "quote_error.upload": "Az idézés médiamellékletek esetén nem engedélyezett.", @@ -865,8 +916,12 @@ "status.cannot_quote": "Nem idézheted ezt a bejegyzést", "status.cannot_reblog": "Ezt a bejegyzést nem lehet megtolni", "status.contains_quote": "Idézést tartalmaz", - "status.context.load_new_replies": "Új válaszok érhetőek el", - "status.context.loading": "További válaszok keresése", + "status.context.loading": "Több válasz betöltése", + "status.context.loading_error": "Az új válaszok nem tölthetőek be", + "status.context.loading_success": "Új válaszok betöltve", + "status.context.more_replies_found": "Több válasz található", + "status.context.retry": "Újra", + "status.context.show": "Megjelenítés", "status.continued_thread": "Folytatott szál", "status.copy": "Link másolása bejegyzésbe", "status.delete": "Törlés", @@ -879,7 +934,7 @@ "status.edited_x_times": "{count, plural, one {{count} alkalommal} other {{count} alkalommal}} szerkesztve", "status.embed": "Beágyazási kód lekérése", "status.favourite": "Kedvenc", - "status.favourites": "{count, plural, one {kedvenc} other {kedvenc}}", + "status.favourites_count": "{count, plural, one {{counter} kedvenc} other {{counter} kedvenc}}", "status.filter": "E bejegyzés szűrése", "status.history.created": "{name} létrehozta: {date}", "status.history.edited": "{name} szerkesztette: {date}", @@ -895,28 +950,33 @@ "status.pin": "Kitűzés a profilodra", "status.quote": "Idézés", "status.quote.cancel": "Idézés elvetése", + "status.quote_error.blocked_account_hint.title": "Ez a bejegyzés rejtett, mert blokkoltad @{name} felhasználót.", + "status.quote_error.blocked_domain_hint.title": "Ez a bejegyzés rejtett, mert blokkoltad a(z) {domain} domaint.", "status.quote_error.filtered": "A szűrőid miatt rejtett", "status.quote_error.limited_account_hint.action": "Megjelenítés mindenképp", "status.quote_error.limited_account_hint.title": "Ezt a fiókot elrejtették a(z) {domain} moderátorai.", + "status.quote_error.muted_account_hint.title": "Ez a bejegyzés rejtett, mert némítottad @{name} felhasználót.", "status.quote_error.not_available": "A bejegyzés nem érhető el", "status.quote_error.pending_approval": "A bejegyzés függőben van", "status.quote_error.pending_approval_popout.body": "A Mastodonon te mondod meg, hogy valaki idézhet-e. Ez a bejegyzés addig függőben marad, amíg az eredeti szerző nem engedélyezi azt.", - "status.quote_error.revoked": "A szerző eltávolítta a bejegyzést", + "status.quote_error.revoked": "A szerző eltávolította a bejegyzést", "status.quote_followers_only": "Csak a követők idézhetik ezt a bejegyzést", "status.quote_manual_review": "A szerző kézileg fogja jóváhagyni", "status.quote_noun": "Idézés", "status.quote_policy_change": "Módosítás, hogy kik idézhetnek", "status.quote_post_author": "Idézte @{name} bejegyzését", "status.quote_private": "A privát bejegyzések nem idézhetőek", - "status.quotes": "{count, plural, one {idézés} other {idézés}}", "status.quotes.empty": "Senki sem idézte még ezt a bejegyzést. Ha valaki megteszi, itt fog megjelenni.", + "status.quotes.local_other_disclaimer": "A szerző által elutasított idézések nem fognak megjelenni.", + "status.quotes.remote_other_disclaimer": "Csak a(z) {domain} idézései jelennek meg itt garantáltan. A szerző által elutasított idézések nem fognak megjelenni.", + "status.quotes_count": "{count, plural, one {{counter} idézet} other {{counter} idézet}}", "status.read_more": "Bővebben", "status.reblog": "Megtolás", "status.reblog_or_quote": "Megtolás vagy idézés", "status.reblog_private": "Megosztás a követőiddel", "status.reblogged_by": "{name} megtolta", - "status.reblogs": "{count, plural, one {megtolás} other {megtolás}}", "status.reblogs.empty": "Senki sem tolta még meg ezt a bejegyzést. Ha valaki megteszi, itt fog megjelenni.", + "status.reblogs_count": "{count, plural, one {{counter} megtolás} other {{counter} megtolás}}", "status.redraft": "Törlés és újraírás", "status.remove_bookmark": "Könyvjelző eltávolítása", "status.remove_favourite": "Eltávolítás a kedvencek közül", @@ -990,6 +1050,8 @@ "video.volume_down": "Hangerő le", "video.volume_up": "Hangerő fel", "visibility_modal.button_title": "Láthatóság beállítása", + "visibility_modal.direct_quote_warning.text": "Ha mented a jelenlegi beállításokat, akkor a beágyazott idézet hivatkozássá lesz alakítva.", + "visibility_modal.direct_quote_warning.title": "Idézetek nem ágyazhatóak be privát említésekbe", "visibility_modal.header": "Láthatóság és interakció", "visibility_modal.helper.direct_quoting": "A Mastodonon készült privát említéseket mások nem idézhetik.", "visibility_modal.helper.privacy_editing": "A láthatóság nem módosítható a bejegyzés közzététele után.", diff --git a/app/javascript/mastodon/locales/hy.json b/app/javascript/mastodon/locales/hy.json index 62669eda9b5d20..0c77efd6cdeb12 100644 --- a/app/javascript/mastodon/locales/hy.json +++ b/app/javascript/mastodon/locales/hy.json @@ -43,7 +43,6 @@ "account.posts": "Գրառումներ", "account.posts_with_replies": "Գրառումներ եւ պատասխաններ", "account.report": "Բողոքել @{name}֊ի մասին", - "account.requested": "Հաստատման կարիք ունի։ Սեղմիր՝ հետեւելու հայցը չեղարկելու համար։", "account.requested_follow": "{name}-ը ցանկանում է հետեւել քեզ", "account.share": "Կիսուել @{name}֊ի էջով", "account.show_reblogs": "Ցուցադրել @{name}֊ի տարածածները", @@ -125,7 +124,6 @@ "confirmations.mute.confirm": "Լռեցնել", "confirmations.redraft.confirm": "Ջնջել եւ խմբագրել նորից", "confirmations.unfollow.confirm": "Ապահետեւել", - "confirmations.unfollow.message": "Վստա՞հ ես, որ ուզում ես այլեւս չհետեւել {name}֊ին։", "conversation.delete": "Ջնջել խօսակցութիւնը", "conversation.mark_as_read": "Նշել որպէս ընթերցուած", "conversation.open": "Դիտել խօսակցութիւնը", diff --git a/app/javascript/mastodon/locales/ia.json b/app/javascript/mastodon/locales/ia.json index 8291e8ad5f9040..a32a39617bd6f5 100644 --- a/app/javascript/mastodon/locales/ia.json +++ b/app/javascript/mastodon/locales/ia.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Non plus notificar me quando @{name} publica", "account.domain_blocking": "Dominio blocate", "account.edit_profile": "Modificar profilo", + "account.edit_profile_short": "Modificar", "account.enable_notifications": "Notificar me quando @{name} publica", "account.endorse": "Evidentiar sur le profilo", "account.familiar_followers_many": "Sequite per {name1}, {name2}, e {othersCount, plural, one {un altere que tu cognosce} other {# alteres que tu cognosce}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Necun message", "account.follow": "Sequer", "account.follow_back": "Sequer in retorno", + "account.follow_back_short": "Sequer in retorno", + "account.follow_request": "Requestar de sequer", + "account.follow_request_cancel": "Cancellar requesta", + "account.follow_request_cancel_short": "Cancellar", + "account.follow_request_short": "Requesta", "account.followers": "Sequitores", "account.followers.empty": "Necuno seque ancora iste usator.", "account.followers_counter": "{count, plural, one {{counter} sequitor} other {{counter} sequitores}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Messages e responsas", "account.remove_from_followers": "Remover {name} del sequitores", "account.report": "Reportar @{name}", - "account.requested": "Attendente le approbation. Clicca pro cancellar le requesta de sequer", "account.requested_follow": "{name} ha requestate de sequer te", "account.requests_to_follow_you": "Requestas de sequer te", "account.share": "Compartir profilo de @{name}", @@ -108,25 +113,11 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Describe isto pro personas con impedimentos visual…", "alt_text_modal.done": "Preste", "announcement.announcement": "Annuncio", - "annual_report.summary.archetype.booster": "Le impulsator", - "annual_report.summary.archetype.lurker": "Le lector", - "annual_report.summary.archetype.oracle": "Le oraculo", - "annual_report.summary.archetype.pollster": "Le sondagista", - "annual_report.summary.archetype.replier": "Le responditor", - "annual_report.summary.followers.followers": "sequitores", - "annual_report.summary.followers.total": "{count} in total", - "annual_report.summary.here_it_is": "Ecce tu summario de {year}:", - "annual_report.summary.highlighted_post.by_favourites": "message le plus favorite", - "annual_report.summary.highlighted_post.by_reblogs": "message le plus impulsate", - "annual_report.summary.highlighted_post.by_replies": "message le plus respondite", - "annual_report.summary.highlighted_post.possessive": "{name}, ecce tu…", "annual_report.summary.most_used_app.most_used_app": "application le plus usate", "annual_report.summary.most_used_hashtag.most_used_hashtag": "hashtag le plus usate", - "annual_report.summary.most_used_hashtag.none": "Necun", "annual_report.summary.new_posts.new_posts": "nove messages", "annual_report.summary.percentile.text": "Isto te pone in le primeusatores de {domain}.", "annual_report.summary.percentile.we_wont_tell_bernie": "Tu es un primo inter pares.", - "annual_report.summary.thanks": "Gratias pro facer parte de Mastodon!", "attachments_list.unprocessed": "(non processate)", "audio.hide": "Celar audio", "block_modal.remote_users_caveat": "Nos demandera al servitor {domain} de respectar tu decision. Nonobstante, le conformitate non es garantite perque alcun servitores pote tractar le blocadas de maniera differente. Le messages public pote esser totevia visibile pro le usatores non authenticate.", @@ -152,6 +143,8 @@ "bundle_modal_error.close": "Clauder", "bundle_modal_error.message": "Un error ha occurrite durante le cargamento de iste schermo.", "bundle_modal_error.retry": "Tentar novemente", + "carousel.current": "Diapositiva {current, number} / {max, number}", + "carousel.slide": "Diapositiva {current, number} de {max, number}", "closed_registrations.other_server_instructions": "Perque Mastodon es decentralisate, tu pote crear un conto sur un altere servitor e totevia interager con iste servitor.", "closed_registrations_modal.description": "Crear un conto sur {domain} non es actualmente possibile, ma considera que non es necessari haber un conto specificamente sur {domain} pro usar Mastodon.", "closed_registrations_modal.find_another_server": "Cercar un altere servitor", @@ -167,7 +160,9 @@ "column.domain_blocks": "Dominios blocate", "column.edit_list": "Modificar lista", "column.favourites": "Favorites", - "column.firehose": "Fluxos in directo", + "column.firehose": "Fluxos in vivo", + "column.firehose_local": "Fluxo in vivo de iste servitor", + "column.firehose_singular": "Fluxo in vivo", "column.follow_requests": "Requestas de sequimento", "column.home": "Initio", "column.list_members": "Gerer le membros del lista", @@ -187,6 +182,7 @@ "community.column_settings.local_only": "Solmente local", "community.column_settings.media_only": "Solmente multimedia", "community.column_settings.remote_only": "A distantia solmente", + "compose.error.blank_post": "Le message non pote esser vacue.", "compose.language.change": "Cambiar le lingua", "compose.language.search": "Cercar linguas...", "compose.published.body": "Message publicate.", @@ -239,6 +235,11 @@ "confirmations.missing_alt_text.secondary": "Publicar totevia", "confirmations.missing_alt_text.title": "Adder texto alternative?", "confirmations.mute.confirm": "Silentiar", + "confirmations.private_quote_notify.cancel": "Retornar al modification", + "confirmations.private_quote_notify.confirm": "Publicar message", + "confirmations.private_quote_notify.do_not_show_again": "Non monstrar me iste message de novo", + "confirmations.private_quote_notify.message": "Le persona que tu cita e altere personas mentionate recipera un notification e potera vider tu message, etiam si illes non te seque.", + "confirmations.private_quote_notify.title": "Compartir on sequitores e usatores mentionate?", "confirmations.quiet_post_quote_info.dismiss": "Non rememorar me de novo", "confirmations.quiet_post_quote_info.got_it": "Comprendite", "confirmations.quiet_post_quote_info.message": "Quando tu cita un message public quiete, tu message non apparera in le chronologias de tendentias.", @@ -252,9 +253,12 @@ "confirmations.revoke_quote.confirm": "Remover message", "confirmations.revoke_quote.message": "Iste action non pote esser disfacite.", "confirmations.revoke_quote.title": "Remover message?", + "confirmations.unblock.confirm": "Disblocar", + "confirmations.unblock.title": "Disblocar {name}?", "confirmations.unfollow.confirm": "Non plus sequer", - "confirmations.unfollow.message": "Es tu secur que tu vole cessar de sequer {name}?", - "confirmations.unfollow.title": "Cessar de sequer le usator?", + "confirmations.unfollow.title": "Cessar de sequer {name}?", + "confirmations.withdraw_request.confirm": "Retirar requesta", + "confirmations.withdraw_request.title": "Retirar le requesta de sequer {name}?", "content_warning.hide": "Celar le message", "content_warning.show": "Monstrar in omne caso", "content_warning.show_more": "Monstrar plus", @@ -325,6 +329,7 @@ "empty_column.bookmarked_statuses": "Tu non ha ancora messages in marcapaginas. Quando tu adde un message al marcapaginas, illo apparera hic.", "empty_column.community": "Le chronologia local es vacue. Scribe qualcosa public pro poner le cosas in marcha!", "empty_column.direct": "Tu non ha ancora mentiones private. Quando tu invia o recipe un mention, illo apparera hic.", + "empty_column.disabled_feed": "Iste fluxo ha essite disactivate per le administratores de tu servitor.", "empty_column.domain_blocks": "Il non ha dominios blocate ancora.", "empty_column.explore_statuses": "Il non ha tendentias in iste momento. Reveni plus tarde!", "empty_column.favourited_statuses": "Tu non ha alcun message favorite ancora. Quando tu marca un message como favorite, illo apparera hic.", @@ -338,6 +343,7 @@ "empty_column.notification_requests": "Iste lista es toto vacue! Quando tu recipe notificationes, illos apparera hic como configurate in tu parametros.", "empty_column.notifications": "Tu non ha ancora notificationes. Quando altere personas interage con te, tu lo videra hic.", "empty_column.public": "Il ha nihil hic! Scribe qualcosa public, o manualmente seque usatores de altere servitores, pro plenar lo", + "error.no_hashtag_feed_access": "Inscribe te o aperi session pro vider e sequer iste hashtag.", "error.unexpected_crash.explanation": "A causa de un defecto in nostre codice o de un problema de compatibilitate del navigator, iste pagina non pote esser visualisate correctemente.", "error.unexpected_crash.explanation_addons": "Iste pagina non pote esser visualisate correctemente. Iste error es probabilemente causate per un additivo al navigator o per un utensile de traduction automatic.", "error.unexpected_crash.next_steps": "Tenta refrescar le pagina. Si isto non remedia le problema, es possibile que tu pote totevia usar Mastodon per medio de un altere navigator o application native.", @@ -349,11 +355,9 @@ "explore.trending_links": "Novas", "explore.trending_statuses": "Messages", "explore.trending_tags": "Hashtags", + "featured_carousel.current": "Message {current, number} / {max, number}", "featured_carousel.header": "{count, plural, one {Message fixate} other {Messages fixate}}", - "featured_carousel.next": "Sequente", - "featured_carousel.post": "Message", - "featured_carousel.previous": "Precedente", - "featured_carousel.slide": "{index} de {total}", + "featured_carousel.slide": "Message {current, number} de {max, number}", "filter_modal.added.context_mismatch_explanation": "Iste categoria de filtros non se applica al contexto in le qual tu ha accedite a iste message. Pro filtrar le message in iste contexto tamben, modifica le filtro.", "filter_modal.added.context_mismatch_title": "Contexto incoherente!", "filter_modal.added.expired_explanation": "Iste categoria de filtros ha expirate. Tu debe modificar le data de expiration pro applicar lo.", @@ -396,6 +400,7 @@ "follow_suggestions.who_to_follow": "Qui sequer", "followed_tags": "Hashtags sequite", "footer.about": "A proposito", + "footer.about_this_server": "A proposito", "footer.directory": "Directorio de profilos", "footer.get_app": "Obtener le application", "footer.keyboard_shortcuts": "Accessos directe de claviero", @@ -452,7 +457,7 @@ "ignore_notifications_modal.not_following_title": "Ignorar notificationes de personas que tu non seque?", "ignore_notifications_modal.private_mentions_title": "Ignorar notificationes de mentiones private non requestate?", "info_button.label": "Adjuta", - "info_button.what_is_alt_text": "

Que es texto alternative?

Le texto alternative forni descriptiones de imagines a personas con impedimentos visual, con connexiones lente, o qui cerca contexto additional.

Tu pote meliorar le accessibilitate e le comprension pro totes scribente un texto alternative clar, concise e objective.

  • Captura le elementos importante
  • Summarisa texto in imagines
  • Usa le structura de phrase normal
  • Evita information redundante
  • In figuras complexe (como diagrammas o mappas), concentra te sur le tendentias e punctos clave
", + "info_button.what_is_alt_text": "

Que es texto alternative?

Le texto alternative forni descriptiones de imagines a personas con impedimentos visual, con connexiones lente a internet, o qui cerca contexto supplementari.

Tu pote meliorar le accessibilitate e le comprension pro totes si tu scribe un texto alternative clar, concise e objective.

  • Captura le elementos importante
  • Summarisa texto in imagines
  • Usa un structura conventional de phrases
  • Evita information redundante
  • In figuras complexe (como diagrammas o mappas), concentra te sur le tendentias e punctos clave
", "interaction_modal.action": "Pro interager con le message de {name}, tu debe acceder a tu conto sur le servitor Mastodon que tu usa.", "interaction_modal.go": "Revenir", "interaction_modal.no_account_yet": "Tu non ha ancora un conto?", @@ -566,8 +571,8 @@ "navigation_bar.follows_and_followers": "Sequites e sequitores", "navigation_bar.import_export": "Importar e exportar", "navigation_bar.lists": "Listas", - "navigation_bar.live_feed_local": "Canal in directo (local)", - "navigation_bar.live_feed_public": "Canal in directo (public)", + "navigation_bar.live_feed_local": "Fluxo in vivo (local)", + "navigation_bar.live_feed_public": "Fluxo in vivo (public)", "navigation_bar.logout": "Clauder session", "navigation_bar.moderation": "Moderation", "navigation_bar.more": "Plus", @@ -740,12 +745,14 @@ "privacy.quote.anyone": "{visibility}, omnes pote citar", "privacy.quote.disabled": "{visibility}, citation disactivate", "privacy.quote.limited": "{visibility}, citation limitate", - "privacy.unlisted.additional": "Isto es exactemente como public, excepte que le message non apparera in fluxos in directo, in hashtags, in Explorar, o in le recerca de Mastodon, mesmo si tu ha optate pro render tote le conto discoperibile.", + "privacy.unlisted.additional": "Isto es exactemente como public, excepte que le message non apparera in fluxos in vivo, in hashtags, in Explorar, o in le recerca de Mastodon, mesmo si tu ha optate pro render tote le conto discoperibile.", "privacy.unlisted.long": "Non apparera in le resultatos de recerca, tendentias e chronologias public de Mastodon", "privacy.unlisted.short": "Public, non listate", "privacy_policy.last_updated": "Ultime actualisation {date}", "privacy_policy.title": "Politica de confidentialitate", + "quote_error.edit": "Non es possibile adder citationes quando se modifica un message.", "quote_error.poll": "Non es permittite citar sondages.", + "quote_error.private_mentions": "Non es permittite citar con mentiones directe.", "quote_error.quote": "Solmente un citation al vice es permittite.", "quote_error.unauthorized": "Tu non es autorisate a citar iste message.", "quote_error.upload": "Non es permittite citar con annexos multimedial.", @@ -864,8 +871,13 @@ "status.cancel_reblog_private": "Disfacer impulso", "status.cannot_quote": "Tu non es autorisate a citar iste message", "status.cannot_reblog": "Iste message non pote esser impulsate", - "status.context.load_new_replies": "Nove responsas disponibile", - "status.context.loading": "Cercante plus responsas", + "status.contains_quote": "Contine un citation", + "status.context.loading": "Cargante plus responsas", + "status.context.loading_error": "Non poteva cargar nove responsas", + "status.context.loading_success": "Nove responsas cargate", + "status.context.more_replies_found": "Plus responsas trovate", + "status.context.retry": "Tentar de novo", + "status.context.show": "Monstrar", "status.continued_thread": "Continuation del discussion", "status.copy": "Copiar ligamine a message", "status.delete": "Deler", @@ -878,7 +890,7 @@ "status.edited_x_times": "Modificate {count, plural, one {{count} vice} other {{count} vices}}", "status.embed": "Obtener codice de incorporation", "status.favourite": "Adder al favorites", - "status.favourites": "{count, plural, one {favorite} other {favorites}}", + "status.favourites_count": "{count, plural, one {{counter} favorite} other {{counter} favorites}}", "status.filter": "Filtrar iste message", "status.history.created": "create per {name} le {date}", "status.history.edited": "modificate per {name} le {date}", @@ -894,25 +906,33 @@ "status.pin": "Fixar sur profilo", "status.quote": "Citar", "status.quote.cancel": "Cancellar le citation", + "status.quote_error.blocked_account_hint.title": "Iste message es celate perque tu ha blocate @{name}.", + "status.quote_error.blocked_domain_hint.title": "Iste message es celate perque tu ha blocate {domain}.", "status.quote_error.filtered": "Celate a causa de un de tu filtros", + "status.quote_error.limited_account_hint.action": "Monstrar in omne caso", + "status.quote_error.limited_account_hint.title": "Iste conto ha essite celate per le moderatores de {domain}.", + "status.quote_error.muted_account_hint.title": "Iste message es celate perque tu ha silentiate @{name}.", "status.quote_error.not_available": "Message indisponibile", "status.quote_error.pending_approval": "Message pendente", "status.quote_error.pending_approval_popout.body": "Sur Mastodon, tu pote controlar si on pote citar te. Iste message attende ora le approbation del autor original.", "status.quote_error.revoked": "Message removite per le autor", "status.quote_followers_only": "Solmente sequitores pote citar iste message", "status.quote_manual_review": "Le autor lo examinara manualmente", + "status.quote_noun": "Citation", "status.quote_policy_change": "Cambiar qui pote citar", "status.quote_post_author": "Ha citate un message de @{name}", "status.quote_private": "Le messages private non pote esser citate", - "status.quotes": "{count, plural, one {citation} other {citationes}}", "status.quotes.empty": "Necuno ha ancora citate iste message. Quando alcuno lo face, illo apparera hic.", + "status.quotes.local_other_disclaimer": "Le citationes rejectate per le autor non essera monstrate.", + "status.quotes.remote_other_disclaimer": "Solmente le citationes de {domain} se garanti de esser monstrate hic. Citationes rejectate per le autor non essera monstrate.", + "status.quotes_count": "{count, plural, one {{counter} citation} other {{counter} citationes}}", "status.read_more": "Leger plus", "status.reblog": "Impulsar", "status.reblog_or_quote": "Impulsar o citar", "status.reblog_private": "Re-compartir con tu sequitores", "status.reblogged_by": "Impulsate per {name}", - "status.reblogs": "{count, plural, one {impulso} other {impulsos}}", "status.reblogs.empty": "Necuno ha ancora impulsate iste message. Quando alcuno lo face, le impulsos apparera hic.", + "status.reblogs_count": "{count, plural, one {{counter} impulso} other {{counter} impulsos}}", "status.redraft": "Deler e reconciper", "status.remove_bookmark": "Remover marcapagina", "status.remove_favourite": "Remover del favoritos", @@ -986,6 +1006,7 @@ "video.volume_down": "Abassar le volumine", "video.volume_up": "Augmentar le volumine", "visibility_modal.button_title": "Definir visibilitate", + "visibility_modal.direct_quote_warning.text": "Si tu salva le parametros actual, le citation incorporate se convertera in un ligamine.", "visibility_modal.header": "Visibilitate e interaction", "visibility_modal.helper.direct_quoting": "Le mentiones private scribite sur Mastodon non pote esser citate per alteres.", "visibility_modal.helper.privacy_editing": "Le visibilitate de un message non pote esser cambiate post su publication.", diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json index 7c5c816c67dde4..c8d7d8ef0011f8 100644 --- a/app/javascript/mastodon/locales/id.json +++ b/app/javascript/mastodon/locales/id.json @@ -64,7 +64,6 @@ "account.posts": "Kiriman", "account.posts_with_replies": "Kiriman dan balasan", "account.report": "Laporkan @{name}", - "account.requested": "Menunggu persetujuan. Klik untuk membatalkan permintaan", "account.requested_follow": "{name} ingin mengikuti Anda", "account.share": "Bagikan profil @{name}", "account.show_reblogs": "Tampilkan boost dari @{name}", @@ -190,8 +189,6 @@ "confirmations.redraft.message": "Apakah anda yakin ingin menghapus postingan ini dan menyusun ulang postingan ini? Favorit dan peningkatan akan hilang, dan balasan ke postingan asli tidak akan terhubung ke postingan manapun.", "confirmations.redraft.title": "Delete & redraft post?", "confirmations.unfollow.confirm": "Berhenti mengikuti", - "confirmations.unfollow.message": "Apakah Anda ingin berhenti mengikuti {name}?", - "confirmations.unfollow.title": "Unfollow user?", "content_warning.hide": "Hide post", "content_warning.show": "Show anyway", "conversation.delete": "Hapus percakapan", diff --git a/app/javascript/mastodon/locales/ie.json b/app/javascript/mastodon/locales/ie.json index 4a464ef16e2e0c..1ee6784a9058c6 100644 --- a/app/javascript/mastodon/locales/ie.json +++ b/app/javascript/mastodon/locales/ie.json @@ -52,7 +52,6 @@ "account.posts": "Postas", "account.posts_with_replies": "Postas e replicas", "account.report": "Raportar @{name}", - "account.requested": "Atendent aprobation. Cliccar por anullar li petition de sequer", "account.requested_follow": "{name} ha petit sequer te", "account.share": "Distribuer li profil de @{name}", "account.show_reblogs": "Monstrar boosts de @{name}", @@ -169,7 +168,6 @@ "confirmations.redraft.confirm": "Deleter & redacter", "confirmations.redraft.message": "Esque tu vermen vole deleter ti-ci posta e redacter it? Favorites e boosts va esser perdit, e responses al posta original va esser orfanat.", "confirmations.unfollow.confirm": "Dessequer", - "confirmations.unfollow.message": "Esque tu vermen vole dessequer {name}?", "conversation.delete": "Deleter conversation", "conversation.mark_as_read": "Marcar quam leet", "conversation.open": "Vider conversation", @@ -614,7 +612,6 @@ "status.edited": "Ultimmen actualisat ye {date}", "status.edited_x_times": "Modificat {count, plural, one {{count} vez} other {{count} vezes}}", "status.favourite": "Favoritisar", - "status.favourites": "{count, plural, one {favorit} other {favorites}}", "status.filter": "Filtrar ti-ci posta", "status.history.created": "creat de {name} ye {date}", "status.history.edited": "modificat de {name} ye {date}", @@ -631,7 +628,6 @@ "status.read_more": "Leer plu", "status.reblog": "Boostar", "status.reblogged_by": "{name} boostat", - "status.reblogs": "{count, plural, one {boost} other {boosts}}", "status.reblogs.empty": "Ancor nequi ha boostat ti-ci posta. Quande alqui fa it, ilu va aparir ci.", "status.redraft": "Deleter & redacter", "status.remove_bookmark": "Remover marcator", diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json index b51d32d05a772d..adf604a5d5d124 100644 --- a/app/javascript/mastodon/locales/io.json +++ b/app/javascript/mastodon/locales/io.json @@ -55,7 +55,6 @@ "account.posts": "Mesaji", "account.posts_with_replies": "Afishi e respondi", "account.report": "Denuncar @{name}", - "account.requested": "Vartante aprobo", "account.requested_follow": "{name} demandis sequar tu", "account.share": "Partigez profilo di @{name}", "account.show_reblogs": "Montrez repeti de @{name}", @@ -91,25 +90,11 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Priskribar co por personi kun viddeskapableso…", "alt_text_modal.done": "Finis", "announcement.announcement": "Anunco", - "annual_report.summary.archetype.booster": "La plurrepetanto", - "annual_report.summary.archetype.lurker": "La plurcelanto", - "annual_report.summary.archetype.oracle": "La pluraktivo", - "annual_report.summary.archetype.pollster": "La votinquestoiganto", - "annual_report.summary.archetype.replier": "La plurrespondanto", - "annual_report.summary.followers.followers": "sequanti", - "annual_report.summary.followers.total": "{count} sumo", - "annual_report.summary.here_it_is": "Caibe es vua rivido ye {year}:", - "annual_report.summary.highlighted_post.by_favourites": "maxim prizita mesajo", - "annual_report.summary.highlighted_post.by_reblogs": "maxim repetita mesajo", - "annual_report.summary.highlighted_post.by_replies": "mesajo kun la maxim multa respondi", - "annual_report.summary.highlighted_post.possessive": "di {name}", "annual_report.summary.most_used_app.most_used_app": "maxim uzita aplikajo", "annual_report.summary.most_used_hashtag.most_used_hashtag": "maxim uzita gretvorto", - "annual_report.summary.most_used_hashtag.none": "Nulo", "annual_report.summary.new_posts.new_posts": "nova afishi", "annual_report.summary.percentile.text": "To pozas vu sur la supro di uzanti di {domain}.", "annual_report.summary.percentile.we_wont_tell_bernie": "Ni ne dicas ad Bernio.", - "annual_report.summary.thanks": "Danki por partoprenar sur Mastodon!", "attachments_list.unprocessed": "(neprocedita)", "audio.hide": "Celez audio", "block_modal.remote_users_caveat": "Ni questionos {domain} di la servilo por respektar vua decido. Publika posti forsan ankore estas videbla a neenirinta uzanti.", @@ -219,8 +204,6 @@ "confirmations.redraft.message": "Ka vu certe volas efacar ca posto e riskisigar ol? Favoriziti e repeti esos perdita, e respondi al posto originala esos orfanigita.", "confirmations.redraft.title": "Ka efacar & riskisar posto?", "confirmations.unfollow.confirm": "Desequez", - "confirmations.unfollow.message": "Ka vu certe volas desequar {name}?", - "confirmations.unfollow.title": "Ka dessequar uzanto?", "content_warning.hide": "Celez posto", "content_warning.show": "Montrez nur", "content_warning.show_more": "Montrar plu", @@ -790,7 +773,6 @@ "status.edited_x_times": "Redaktesis ye {count, plural, one {{count} foyo} other {{count} foyi}}", "status.embed": "Ganez adherkodexo", "status.favourite": "Favorizar", - "status.favourites": "{count, plural, one {stelumo} other {stelumi}}", "status.filter": "Filtragez ca posto", "status.history.created": "{name} kreis ye {date}", "status.history.edited": "{name} redaktis ye {date}", @@ -807,7 +789,6 @@ "status.read_more": "Lektez plu", "status.reblog": "Repetez", "status.reblogged_by": "{name} repetis", - "status.reblogs": "{count, plural, one {repeto} other {repeti}}", "status.reblogs.empty": "Nulu ja repetis ca posto. Kande ulu facas lo, lu montresos hike.", "status.redraft": "Efacez e riskisigez", "status.remove_bookmark": "Forigar lektosigno", diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json index f380a4b4a50c80..a9b8011b4aee01 100644 --- a/app/javascript/mastodon/locales/is.json +++ b/app/javascript/mastodon/locales/is.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Hætta að láta mig vita þegar @{name} sendir inn", "account.domain_blocking": "Útiloka lén", "account.edit_profile": "Breyta notandasniði", + "account.edit_profile_short": "Breyta", "account.enable_notifications": "Láta mig vita þegar @{name} sendir inn", "account.endorse": "Birta á notandasniði", "account.familiar_followers_many": "Fylgt af {name1}, {name2} og {othersCount, plural, one {einum öðrum sem þú þekkir} other {# öðrum sem þú þekkir}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Engar færslur", "account.follow": "Fylgjast með", "account.follow_back": "Fylgjast með til baka", + "account.follow_back_short": "Fylgjast með til baka", + "account.follow_request": "Beiðni um að fylgjast með", + "account.follow_request_cancel": "Hætta við beiðni", + "account.follow_request_cancel_short": "Hætta við", + "account.follow_request_short": "Beiðni", "account.followers": "Fylgjendur", "account.followers.empty": "Ennþá fylgist enginn með þessum notanda.", "account.followers_counter": "{count, plural, one {Fylgjandi: {counter}} other {Fylgjendur: {counter}}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Færslur og svör", "account.remove_from_followers": "Fjarlægja {name} úr fylgjendum", "account.report": "Kæra @{name}", - "account.requested": "Bíður eftir samþykki. Smelltu til að hætta við beiðni um að fylgjast með", "account.requested_follow": "{name} hefur beðið um að fylgjast með þér", "account.requests_to_follow_you": "Bað um að fylgjast með þér", "account.share": "Deila notandasniði fyrir @{name}", @@ -108,25 +113,52 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Lýstu þessu fyrir fólk með skerta sjón…", "alt_text_modal.done": "Lokið", "announcement.announcement": "Auglýsing", - "annual_report.summary.archetype.booster": "Svali gaurinn", - "annual_report.summary.archetype.lurker": "Lurkurinn", - "annual_report.summary.archetype.oracle": "Völvan", - "annual_report.summary.archetype.pollster": "Kannanafíkillinn", - "annual_report.summary.archetype.replier": "Félagsveran", - "annual_report.summary.followers.followers": "fylgjendur", - "annual_report.summary.followers.total": "{count} alls", - "annual_report.summary.here_it_is": "Hér er yfirlitið þitt fyrir {year}:", - "annual_report.summary.highlighted_post.by_favourites": "færsla sett oftast í eftirlæti", - "annual_report.summary.highlighted_post.by_reblogs": "færsla oftast endurbirt", - "annual_report.summary.highlighted_post.by_replies": "færsla með flestum svörum", - "annual_report.summary.highlighted_post.possessive": "{name}", + "annual_report.announcement.action_build": "Búa til ársuppgjör fyrir mig", + "annual_report.announcement.action_dismiss": "Nei takk", + "annual_report.announcement.action_view": "Skoða ársuppgjörið mitt", + "annual_report.announcement.description": "Skoðaðu meira um virkni þína á Mastodon á síðastliðnu ári.", + "annual_report.announcement.title": "Ársuppgjörið {year} er komið", + "annual_report.nav_item.badge": "Nýtt", + "annual_report.shared_page.donate": "Styrkja", + "annual_report.shared_page.footer": "{heart}-kveðjur frá Mastodon-teyminu", + "annual_report.shared_page.footer_server_info": "{username} notar {domain}, einu af samfélögunum sem Mastodon drífur.", + "annual_report.summary.archetype.booster.desc_public": "{name} var á höttunum eftir færslum til að endurbirta og hitti þannig í mark með að magna upp það sem aðrir voru að gera.", + "annual_report.summary.archetype.booster.desc_self": "Þú varst á höttunum eftir færslum til að endurbirta og hittir þannig í mark með að magna upp það sem aðrir voru að gera.", + "annual_report.summary.archetype.booster.name": "Veiðmaðurinn", + "annual_report.summary.archetype.die_drei_fragezeichen": "???", + "annual_report.summary.archetype.lurker.desc_public": "Við vitum að {name} var þarna úti, einhversstaðar, að njóta Mastodon á sínum eigin hljóðlátu forsendum.", + "annual_report.summary.archetype.lurker.desc_self": "Við vitum að þú varst þarna úti, einhversstaðar, að njóta Mastodon á þínum eigin hljóðlátu forsendum.", + "annual_report.summary.archetype.lurker.name": "Heimspekingurinn", + "annual_report.summary.archetype.oracle.desc_public": "{name} útbjó fleiri færslur en svör og hélt þannig Mastodon fersku og vísandi til framtíðar.", + "annual_report.summary.archetype.oracle.desc_self": "Þú útbjóst fleiri færslur en svör og hélst þannig Mastodon fersku og vísandi til framtíðar.", + "annual_report.summary.archetype.oracle.name": "Völvan", + "annual_report.summary.archetype.pollster.desc_public": "{name} útbjó fleiri kannanir en aðrar gerðir af færslum og ræktaði þannig forvitni á ökrum Mastodon.", + "annual_report.summary.archetype.pollster.desc_self": "Þú útbjóst fleiri kannanir en aðrar gerðir af færslum og ræktaðir þannig forvitni á ökrum Mastodon.", + "annual_report.summary.archetype.pollster.name": "Könnuðurinn", + "annual_report.summary.archetype.replier.desc_public": "{name} svaraði oft færslum annara og frjóvgaði Mastodon með nýjum samræðum.", + "annual_report.summary.archetype.replier.desc_self": "Þú svaraðir oft færslum annara og frjóvgaðir Mastodon með nýjum samræðum.", + "annual_report.summary.archetype.replier.name": "Fiðrildið", + "annual_report.summary.archetype.reveal": "Uppljóstra um erkitýpuna mína", + "annual_report.summary.archetype.reveal_description": "Takk fyrir að vera hluti af Mastodon! Nú er tíminn til að sjá hvaða erkitýpu þú líktist árið {year}.", + "annual_report.summary.archetype.title_public": "Erkitýpan fyrir {name}", + "annual_report.summary.archetype.title_self": "Erkitýpan þín", + "annual_report.summary.close": "Loka", + "annual_report.summary.copy_link": "Afrita tengil", + "annual_report.summary.followers.new_followers": "{count, plural, one {nýr fylgjandi} other {nýir fylgjendur}}", + "annual_report.summary.highlighted_post.boost_count": "Þessi færsla var endurbirt {count, plural, one {einu sinni} other {# sinnum}}.", + "annual_report.summary.highlighted_post.favourite_count": "Þessi færsla var sett í eftirlæti {count, plural, one {einu sinni} other {# sinnum}}.", + "annual_report.summary.highlighted_post.reply_count": "Þessi færsla fékk {count, plural, one {eitt svar} other {# svör}}.", + "annual_report.summary.highlighted_post.title": "Vinsælasta færslan", "annual_report.summary.most_used_app.most_used_app": "mest notaða forrit", "annual_report.summary.most_used_hashtag.most_used_hashtag": "mest notaða myllumerki", - "annual_report.summary.most_used_hashtag.none": "Ekkert", + "annual_report.summary.most_used_hashtag.used_count": "Þú hafðir þetta myllumerki með í {count, plural, one {einni færslu} other {# færslum}}.", + "annual_report.summary.most_used_hashtag.used_count_public": "{name} hafði þetta myllumerki með í {count, plural, one {einni færslu} other {# færslum}}.", "annual_report.summary.new_posts.new_posts": "nýjar færslur", "annual_report.summary.percentile.text": "Þetta setur þig á meðalof {domain} virkustu notendanna.", "annual_report.summary.percentile.we_wont_tell_bernie": "Við förum ekkert að raupa um þetta.", - "annual_report.summary.thanks": "Takk fyrir að vera hluti af Mastodon-samfélaginu!", + "annual_report.summary.share_elsewhere": "Deila annarsstaðar", + "annual_report.summary.share_message": "Ég er víst {archetype}-týpan!", + "annual_report.summary.share_on_mastodon": "Deila á Mastodon", "attachments_list.unprocessed": "(óunnið)", "audio.hide": "Fela hljóð", "block_modal.remote_users_caveat": "Við munum biðja {domain} netþjóninn um að virða ákvörðun þína. Hitt er svo annað mál hvort hann fari eftir þessu, ekki er hægt að tryggja eftirfylgni því sumir netþjónar meðhöndla útilokanir á sinn hátt. Opinberar færslur gætu verið sýnilegar notendum sem ekki eru skráðir inn.", @@ -152,6 +184,8 @@ "bundle_modal_error.close": "Loka", "bundle_modal_error.message": "Eitthvað fór úrskeiðis við að hlaða inn þessum skjá.", "bundle_modal_error.retry": "Reyndu aftur", + "carousel.current": "Skyggna {current, number} / {max, number}", + "carousel.slide": "Skyggna {current, number} af {max, number}", "closed_registrations.other_server_instructions": "Þar sem Mastodon er ekki miðstýrt, þá getur þú búið til aðgang á öðrum þjóni, en samt haft samskipti við þennan.", "closed_registrations_modal.description": "Að búa til aðgang á {domain} er ekki mögulegt eins og er, en vinsamlegast hafðu í huga að þú þarft ekki aðgang sérstaklega á {domain} til að nota Mastodon.", "closed_registrations_modal.find_another_server": "Finna annan netþjón", @@ -168,6 +202,8 @@ "column.edit_list": "Breyta lista", "column.favourites": "Eftirlæti", "column.firehose": "Bein streymi", + "column.firehose_local": "Beint streymi á þessum netþjóni", + "column.firehose_singular": "Beint streymi", "column.follow_requests": "Beiðnir um að fylgjast með", "column.home": "Heim", "column.list_members": "Sýsla með meðlimi listans", @@ -187,6 +223,7 @@ "community.column_settings.local_only": "Einungis staðvært", "community.column_settings.media_only": "Einungis myndskrár", "community.column_settings.remote_only": "Einungis fjartengt", + "compose.error.blank_post": "Færsla má ekki vera auð.", "compose.language.change": "Skipta um tungumál", "compose.language.search": "Leita að tungumálum...", "compose.published.body": "Færsla birt.", @@ -239,6 +276,11 @@ "confirmations.missing_alt_text.secondary": "Birta samt", "confirmations.missing_alt_text.title": "Bæta við hjálpartexta?", "confirmations.mute.confirm": "Þagga", + "confirmations.private_quote_notify.cancel": "Til baka í breytingar", + "confirmations.private_quote_notify.confirm": "Birta færslu", + "confirmations.private_quote_notify.do_not_show_again": "Ekki birta þessi skilaboð aftur", + "confirmations.private_quote_notify.message": "Sá sem vitnað er í og aðrir sem minnst er á verða látin vita og munu geta skoðað færsluna þína, jafnvel þótt viðkomandi fylgist ekki með þér.", + "confirmations.private_quote_notify.title": "Deila með fylgjendum og þeim sem minnst er á?", "confirmations.quiet_post_quote_info.dismiss": "Ekki minna mig aftur á þetta", "confirmations.quiet_post_quote_info.got_it": "Náði því", "confirmations.quiet_post_quote_info.message": "Þegar þú vitnar í hljóðláta opinbera færslu, verður færslan þín ekki birt á vinsældatímalínum.", @@ -252,9 +294,12 @@ "confirmations.revoke_quote.confirm": "Fjarlægja færslu", "confirmations.revoke_quote.message": "Þessa aðgerð er ekki hægt að afturkalla.", "confirmations.revoke_quote.title": "Fjarlægja færslu?", + "confirmations.unblock.confirm": "Aflétta útilokun", + "confirmations.unblock.title": "Aflétta útilokun á {name}?", "confirmations.unfollow.confirm": "Hætta að fylgja", - "confirmations.unfollow.message": "Ertu viss um að þú viljir hætta að fylgjast með {name}?", - "confirmations.unfollow.title": "Hætta að fylgjast með viðkomandi?", + "confirmations.unfollow.title": "Hætta að fylgjast með {name}?", + "confirmations.withdraw_request.confirm": "Taka beiðni til baka", + "confirmations.withdraw_request.title": "Taka aftur beiðni um að fylgjast með {name}?", "content_warning.hide": "Fela færslu", "content_warning.show": "Birta samt", "content_warning.show_more": "Sýna meira", @@ -304,9 +349,9 @@ "emoji_button.custom": "Sérsniðin", "emoji_button.flags": "Flögg", "emoji_button.food": "Matur og drykkur", - "emoji_button.label": "Setja inn tjáningartákn", + "emoji_button.label": "Setja inn lyndistákn", "emoji_button.nature": "Náttúra", - "emoji_button.not_found": "Engin samsvarandi tjáningartákn fundust", + "emoji_button.not_found": "Engin samsvarandi lyndistákn fundust", "emoji_button.objects": "Hlutir", "emoji_button.people": "Fólk", "emoji_button.recent": "Oft notuð", @@ -325,6 +370,7 @@ "empty_column.bookmarked_statuses": "Þú ert ekki ennþá með neinar bókamerktar færslur. Þegar þú bókamerkir færslu, mun það birtast hér.", "empty_column.community": "Staðværa tímalínan er tóm. Skrifaðu eitthvað opinberlega til að láta boltann fara að rúlla!", "empty_column.direct": "Þú ert ekki ennþá með neitt einkaspjall við neinn. Þegar þú sendir eða tekur við slíku, mun það birtast hér.", + "empty_column.disabled_feed": "Þetta streymi hefur verið gert óvirkt af stjórnendum netþjónis þíns.", "empty_column.domain_blocks": "Það eru ennþá engin útilokuð lén.", "empty_column.explore_statuses": "Ekkert er á uppleið í augnablikinu. Athugaðu aftur síðar!", "empty_column.favourited_statuses": "Þú ert ekki ennþá með neinar eftirlætisfærslur. Þegar þú setur færslu í eftirlæti, munu þau birtast hér.", @@ -338,6 +384,7 @@ "empty_column.notification_requests": "Allt hreint! Það er ekkert hér. Þegar þú færð nýjar tilkynningar, munu þær birtast hér í samræmi við stillingarnar þínar.", "empty_column.notifications": "Þú ert ekki ennþá með neinar tilkynningar. Vertu í samskiptum við aðra til að umræður fari af stað.", "empty_column.public": "Það er ekkert hér! Skrifaðu eitthvað opinberlega, eða fylgstu með notendum á öðrum netþjónum til að fylla upp í þetta", + "error.no_hashtag_feed_access": "Skráðu þig inn eða stofnaðu aðgang til að skoða og fylgjast með þessu myllumerki.", "error.unexpected_crash.explanation": "Vegna villu í kóðanum okkar eða samhæfnivandamála í vafra er ekki hægt að birta þessa síðu svo vel sé.", "error.unexpected_crash.explanation_addons": "Ekki er hægt að birta þessa síðu rétt. Þetta er líklega af völdum forritsviðbótar í vafranum eða sjálfvirkra þýðainaverkfæra.", "error.unexpected_crash.next_steps": "Prófaðu að endurlesa síðuna. Ef það hjálpar ekki til, má samt vera að þú getir notað Mastodon í gegnum annan vafra eða forrit.", @@ -349,11 +396,9 @@ "explore.trending_links": "Fréttir", "explore.trending_statuses": "Færslur", "explore.trending_tags": "Myllumerki", + "featured_carousel.current": "Færsla {current, number} / {max, number}", "featured_carousel.header": "{count, plural, one {Fest færsla} other {Festar færslur}}", - "featured_carousel.next": "Næsta", - "featured_carousel.post": "Færsla", - "featured_carousel.previous": "Fyrra", - "featured_carousel.slide": "{index} af {total}", + "featured_carousel.slide": "Færsla {current, number} af {max, number}", "filter_modal.added.context_mismatch_explanation": "Þessi síuflokkur á ekki við í því samhengi sem aðgangur þinn að þessari færslu felur í sér. Ef þú vilt að færslan sé einnig síuð í þessu samhengi, þá þarftu að breyta síunni.", "filter_modal.added.context_mismatch_title": "Misræmi í samhengi!", "filter_modal.added.expired_explanation": "Þessi síuflokkur er útrunninn, þú þarft að breyta gidistímanum svo hann geti átt við.", @@ -396,6 +441,9 @@ "follow_suggestions.who_to_follow": "Hverjum á að fylgjast með", "followed_tags": "Vöktuð myllumerki", "footer.about": "Nánari upplýsingar", + "footer.about_mastodon": "Um Mastodon", + "footer.about_server": "Um {domain}", + "footer.about_this_server": "Nánari upplýsingar", "footer.directory": "Notandasniðamappa", "footer.get_app": "Ná í forritið", "footer.keyboard_shortcuts": "Flýtileiðir á lyklaborði", @@ -497,6 +545,7 @@ "keyboard_shortcuts.toggle_hidden": "Birta/fela texta á bak við aðvörun vegna efnis", "keyboard_shortcuts.toggle_sensitivity": "Birta/fela myndir", "keyboard_shortcuts.toot": "Byrja nýja færslu", + "keyboard_shortcuts.top": "Færa efst á listann", "keyboard_shortcuts.translate": "að þýða færslu", "keyboard_shortcuts.unfocus": "Taka virkni úr textainnsetningarreit eða leit", "keyboard_shortcuts.up": "Fara ofar í listanum", @@ -589,8 +638,8 @@ "notification.admin.report_statuses_other": "{name} kærði {target}", "notification.admin.sign_up": "{name} skráði sig", "notification.admin.sign_up.name_and_others": "{name} og {count, plural, one {# í viðbót hefur} other {# í viðbót hafa}} skráð sig", - "notification.annual_report.message": "{year} á #Wrapstodon bíður! Afhjúpaðu hvað bar hæst á árinu og minnistæðustu augnablikin á Mastodon!", - "notification.annual_report.view": "Skoða #Wrapstodon", + "notification.annual_report.message": "Ársuppgjörið {year} bíður á #Wrapstodon! Afhjúpaðu hvað bar hæst á árinu og minnistæðustu augnablikin á Mastodon!", + "notification.annual_report.view": "Skoða ársuppgjör á #Wrapstodon", "notification.favourite": "{name} setti færsluna þína í eftirlæti", "notification.favourite.name_and_others_with_link": "{name} og {count, plural, one {# í viðbót hefur} other {# í viðbót hafa}} sett færsluna þína í eftirlæti", "notification.favourite_pm": "{name} setti í eftirlæti færslu í einkaspjalli þar sem þú minntist á viðkomandi", @@ -745,7 +794,9 @@ "privacy.unlisted.short": "Hljóðlátt opinbert", "privacy_policy.last_updated": "Síðast uppfært {date}", "privacy_policy.title": "Persónuverndarstefna", + "quote_error.edit": "Ekki er hægt að bæta við tilvitnunum þegar færslum er breytt.", "quote_error.poll": "Ekki er leyft að vitna í kannanir.", + "quote_error.private_mentions": "Tilvitnanir eru ekki leyfðar í beinu einkaspjalli.", "quote_error.quote": "Einungis ein tilvitnun er leyfð í einu.", "quote_error.unauthorized": "Þú hefur ekki heimild til að vitna í þessa færslu.", "quote_error.upload": "Ekki er leyft að vitna í myndviðhengi.", @@ -864,8 +915,13 @@ "status.cancel_reblog_private": "Taka úr endurbirtingu", "status.cannot_quote": "Þú hefur ekki heimild til að vitna í þessa færslu", "status.cannot_reblog": "Þessa færslu er ekki hægt að endurbirta", - "status.context.load_new_replies": "Ný svör hafa borist", - "status.context.loading": "Athuga með fleiri svör", + "status.contains_quote": "Inniheldur tilvitnun", + "status.context.loading": "Hleð inn fleiri svörum", + "status.context.loading_error": "Gat ekki hlaðið inn nýjum svörum", + "status.context.loading_success": "Nýjum svörum hlaðið inn", + "status.context.more_replies_found": "Fleiri svör fundust", + "status.context.retry": "Reyna aftur", + "status.context.show": "Sýna", "status.continued_thread": "Hélt samtali áfram", "status.copy": "Afrita tengil í færslu", "status.delete": "Eyða", @@ -878,7 +934,7 @@ "status.edited_x_times": "Breytt {count, plural, one {{count} sinni} other {{count} sinnum}}", "status.embed": "Ná í innfellanlegan kóða", "status.favourite": "Eftirlæti", - "status.favourites": "{count, plural, one {eftirlæti} other {eftirlæti}}", + "status.favourites_count": "{count, plural, one {{counter} eftirlæti} other {{counter} eftirlæti}}", "status.filter": "Sía þessa færslu", "status.history.created": "{name} útbjó {date}", "status.history.edited": "{name} breytti {date}", @@ -894,25 +950,33 @@ "status.pin": "Festa á notandasnið", "status.quote": "Tilvitnun", "status.quote.cancel": "Hætta við tilvitnun", + "status.quote_error.blocked_account_hint.title": "Þessi færsla er falin vegna þess að ú hefur lokað á @{name}.", + "status.quote_error.blocked_domain_hint.title": "Þessi færsla er falin vegna þess að ú hefur lokað á {domain}.", "status.quote_error.filtered": "Falið vegna einnar síu sem er virk", + "status.quote_error.limited_account_hint.action": "Birta samt", + "status.quote_error.limited_account_hint.title": "Þessi notandaaðgangur hefur verið falinn af stjórnendum á {domain}.", + "status.quote_error.muted_account_hint.title": "Þessi færsla er falin vegna þess að ú hefur þaggað niður í @{name}.", "status.quote_error.not_available": "Færsla ekki tiltæk", "status.quote_error.pending_approval": "Færsla í bið", "status.quote_error.pending_approval_popout.body": "Á Mastodon geturðu stjórnað því hvort aðrir geti vitnað í þig. Þessi færsla bíður eftir samþykki upprunalegs höfundar.", "status.quote_error.revoked": "Færsla fjarlægð af höfundi", "status.quote_followers_only": "Einungis fylgjendur geta vitnað í þessa færslu", "status.quote_manual_review": "Höfundur mun yfirfara handvirkt", + "status.quote_noun": "Tilvitnun", "status.quote_policy_change": "Breyttu því hver getur tilvitnað", "status.quote_post_author": "Vitnaði í færslu frá @{name}", "status.quote_private": "Ekki er hægt að vitna í einkafærslur", - "status.quotes": "{count, plural, one {tilvitnun} other {tilvitnanir}}", "status.quotes.empty": "Enginn hefur ennþá vitnað í þessa færslu. Þegar einhver gerir það, mun það birtast hér.", + "status.quotes.local_other_disclaimer": "Tilvitnanir sem höfundur hafnar verða ekki birtar.", + "status.quotes.remote_other_disclaimer": "Aðeins tilvitnanir frá {domain} munu birtast hér. Tilvitnanir sem höfundur hafnar verða ekki birtar.", + "status.quotes_count": "{count, plural, one {{counter} tilvitnun} other {{counter} tilvitnanir}}", "status.read_more": "Lesa meira", "status.reblog": "Endurbirting", "status.reblog_or_quote": "Endurbirta eða vitna í færslu", "status.reblog_private": "Deildu aftur með þeim sem fylgjast með þér", "status.reblogged_by": "{name} endurbirti", - "status.reblogs": "{count, plural, one {endurbirting} other {endurbirtingar}}", "status.reblogs.empty": "Enginn hefur ennþá endurbirt þessa færslu. Þegar einhver gerir það, mun það birtast hér.", + "status.reblogs_count": "{count, plural, one {{counter} endurbirting} other {{counter} endurbirtingar}}", "status.redraft": "Eyða og endurvinna drög", "status.remove_bookmark": "Fjarlægja bókamerki", "status.remove_favourite": "Fjarlægja úr eftirlætum", @@ -986,13 +1050,15 @@ "video.volume_down": "Lækka hljóðstyrk", "video.volume_up": "Hækka hljóðstyrk", "visibility_modal.button_title": "Stilla sýnileika", + "visibility_modal.direct_quote_warning.text": "Ef þú vistar þessa stillingu, þá verður ívöfnu tilvitnuninni breytt í tengil.", + "visibility_modal.direct_quote_warning.title": "Ekki er hægt að ívefja tilvitnanir í beinu einkaspjalli", "visibility_modal.header": "Sýnileiki og gagnvirkni", "visibility_modal.helper.direct_quoting": "Ekki er hægt að vitna í einkaspjall sem skrifað er á Mastodon.", "visibility_modal.helper.privacy_editing": "Ekki er hægt að breyta sýnileika færslu eftir að hún hefur verið birt.", "visibility_modal.helper.privacy_private_self_quote": "Tilvitnanir í sjálfan sig úr einkaspjallfærslum er ekki hægt að gera opinberar.", "visibility_modal.helper.private_quoting": "Ekki er hægt að vitna í færslur einungis til fylgjenda sem skrifaðar eru á Mastodon.", "visibility_modal.helper.unlisted_quoting": "Þegar fólk vitnar í þig verða færslurnar þeirr einnig faldar á vinsældatímalínum.", - "visibility_modal.instructions": ". Stýrðu því hverjir geta átt við þessa færslu. Þú getur líka ákvarðað stillingar fyrir allar færslur í framtíðinni með því að fara í Kjörstillingar > Sjálfgefin gildi við gerð færslna.", + "visibility_modal.instructions": "Stýrðu því hverjir geta átt við þessa færslu. Þú getur líka ákvarðað stillingar fyrir allar færslur í framtíðinni með því að fara í Kjörstillingar > Sjálfgefin gildi við gerð færslna.", "visibility_modal.privacy_label": "Sýnileiki", "visibility_modal.quote_followers": "Einungis fylgjendur", "visibility_modal.quote_label": "Hverjir geta gert tilvitnanir", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 448b51944bd349..3f509078594ffd 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -28,9 +28,10 @@ "account.disable_notifications": "Smetti di avvisarmi quando @{name} pubblica un post", "account.domain_blocking": "Account di un dominio bloccato", "account.edit_profile": "Modifica profilo", + "account.edit_profile_short": "Modifica", "account.enable_notifications": "Avvisami quando @{name} pubblica un post", "account.endorse": "In evidenza sul profilo", - "account.familiar_followers_many": "Seguito da {name1}, {name2}, e {othersCount, plural, one {un altro che conosci} other {# altri che conosci}}", + "account.familiar_followers_many": "Seguito da {name1}, {name2}, e {othersCount, plural, one {un altro che conosci} other {altri # che conosci}}", "account.familiar_followers_one": "Seguito da {name1}", "account.familiar_followers_two": "Seguito da {name1} e {name2}", "account.featured": "In primo piano", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Nessun post", "account.follow": "Segui", "account.follow_back": "Segui a tua volta", + "account.follow_back_short": "Segui a tua volta", + "account.follow_request": "Richiesta di seguire", + "account.follow_request_cancel": "Annulla la richiesta", + "account.follow_request_cancel_short": "Annulla", + "account.follow_request_short": "Richiesta", "account.followers": "Follower", "account.followers.empty": "Ancora nessuno segue questo utente.", "account.followers_counter": "{count, plural, one {{counter} seguace} other {{counter} seguaci}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Post e risposte", "account.remove_from_followers": "Rimuovi {name} dai seguaci", "account.report": "Segnala @{name}", - "account.requested": "In attesa d'approvazione. Clicca per annullare la richiesta di seguire", "account.requested_follow": "{name} ha richiesto di seguirti", "account.requests_to_follow_you": "Richieste di seguirti", "account.share": "Condividi il profilo di @{name}", @@ -108,25 +113,52 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Descrivi questo per le persone con disabilità visive…", "alt_text_modal.done": "Fatto", "announcement.announcement": "Annuncio", - "annual_report.summary.archetype.booster": "Cacciatore/trice di tendenze", - "annual_report.summary.archetype.lurker": "L'osservatore/trice", - "annual_report.summary.archetype.oracle": "L'oracolo", - "annual_report.summary.archetype.pollster": "Sondaggista", - "annual_report.summary.archetype.replier": "Utente socievole", - "annual_report.summary.followers.followers": "seguaci", - "annual_report.summary.followers.total": "{count} in totale", - "annual_report.summary.here_it_is": "Ecco il tuo {year} in sintesi:", - "annual_report.summary.highlighted_post.by_favourites": "il post più apprezzato", - "annual_report.summary.highlighted_post.by_reblogs": "il post più condiviso", - "annual_report.summary.highlighted_post.by_replies": "il post con più risposte", - "annual_report.summary.highlighted_post.possessive": "di {name}", + "annual_report.announcement.action_build": "Costruisci il mio Wrapstodon", + "annual_report.announcement.action_dismiss": "No, grazie", + "annual_report.announcement.action_view": "Visualizza il mio Wrapstodon", + "annual_report.announcement.description": "Scopri di più sul tuo coinvolgimento su Mastodon nell'ultimo anno.", + "annual_report.announcement.title": "Wrapstodon {year} è arrivato", + "annual_report.nav_item.badge": "Nuovo", + "annual_report.shared_page.donate": "Dona", + "annual_report.shared_page.footer": "Generato con {heart} dal team di Mastodon", + "annual_report.shared_page.footer_server_info": "{username} usa {domain}, una delle tante comunità basate su Mastodon.", + "annual_report.summary.archetype.booster.desc_public": "{name} è rimasto/a alla ricerca di post da condividere, amplificando il lavoro di altri creatori con precisione mirata.", + "annual_report.summary.archetype.booster.desc_self": "Sei rimasto/a alla ricerca di post da condividere, amplificando il lavoro di altri creatori con precisione mirata.", + "annual_report.summary.archetype.booster.name": "L'Arciere", + "annual_report.summary.archetype.die_drei_fragezeichen": "???", + "annual_report.summary.archetype.lurker.desc_public": "Sappiamo che {name} era là fuori, da qualche parte, a godersi Mastodon in tutta tranquillità.", + "annual_report.summary.archetype.lurker.desc_self": "Sappiamo che eri là fuori, da qualche parte, a goderti Mastodon in tutta tranquillità.", + "annual_report.summary.archetype.lurker.name": "Lo Stoico", + "annual_report.summary.archetype.oracle.desc_public": "{name} ha creato più nuovi post che risposte, mantenendo Mastodon fresco e orientato al futuro.", + "annual_report.summary.archetype.oracle.desc_self": "Hai creato più nuovi post che risposte, mantenendo Mastodon fresco e orientato al futuro.", + "annual_report.summary.archetype.oracle.name": "L'Oracolo", + "annual_report.summary.archetype.pollster.desc_public": "{name} ha creato più sondaggi rispetto ad altri tipi di post, alimentando la curiosità su Mastodon.", + "annual_report.summary.archetype.pollster.desc_self": "Hai creato più sondaggi rispetto ad altri tipi di post, alimentando la curiosità su Mastodon.", + "annual_report.summary.archetype.pollster.name": "L'Esploratore", + "annual_report.summary.archetype.replier.desc_public": "{name} ha frequentemente risposto ai post di altre persone, alimentando Mastodon con nuove discussioni.", + "annual_report.summary.archetype.replier.desc_self": "Hai frequentemente risposto ai post di altre persone, alimentando Mastodon con nuove discussioni.", + "annual_report.summary.archetype.replier.name": "La Farfalla", + "annual_report.summary.archetype.reveal": "Rivela il mio archetipo", + "annual_report.summary.archetype.reveal_description": "Grazie per far parte di Mastodon! È il momento di scoprire quale archetipo hai incarnato nel {year}.", + "annual_report.summary.archetype.title_public": "Archetipo di {name}", + "annual_report.summary.archetype.title_self": "Il tuo archetipo", + "annual_report.summary.close": "Chiudi", + "annual_report.summary.copy_link": "Copia il сollegamento", + "annual_report.summary.followers.new_followers": "{count, plural, one {nuovo seguace} other {nuovi seguaci}}", + "annual_report.summary.highlighted_post.boost_count": "Questo post è stato condiviso {count, plural, one {1 volta} other {# volte}}.", + "annual_report.summary.highlighted_post.favourite_count": "Questo post è stato aggiunto ai preferiti {count, plural, one {1 volta} other {# volte}}.", + "annual_report.summary.highlighted_post.reply_count": "Questo post ha ricevuto {count, plural, one {1 risposta} other {# risposte}}.", + "annual_report.summary.highlighted_post.title": "Il post più popolare", "annual_report.summary.most_used_app.most_used_app": "l'app più utilizzata", "annual_report.summary.most_used_hashtag.most_used_hashtag": "l'hashtag più usato", - "annual_report.summary.most_used_hashtag.none": "Nessuno", + "annual_report.summary.most_used_hashtag.used_count": "Hai incluso questo hashtag in {count, plural, one {1 post} other {# post}}.", + "annual_report.summary.most_used_hashtag.used_count_public": "{name} ha incluso questo hashtag in {count, plural, one {1 post} other {# post}}.", "annual_report.summary.new_posts.new_posts": "nuovi post", "annual_report.summary.percentile.text": "Ciò ti colloca in cimaagli utenti di {domain}.", "annual_report.summary.percentile.we_wont_tell_bernie": "Non lo diremo a Bernie.", - "annual_report.summary.thanks": "Grazie per far parte di Mastodon!", + "annual_report.summary.share_elsewhere": "Condividi altrove", + "annual_report.summary.share_message": "Ho ottenuto l'archetipo: {archetype}!", + "annual_report.summary.share_on_mastodon": "Condividi su Mastodon", "attachments_list.unprocessed": "(non elaborato)", "audio.hide": "Nascondi audio", "block_modal.remote_users_caveat": "Chiederemo al server {domain} di rispettare la tua decisione. Tuttavia, la conformità non è garantita poiché alcuni server potrebbero gestire i blocchi in modo diverso. I post pubblici potrebbero essere ancora visibili agli utenti che non hanno effettuato l'accesso.", @@ -152,6 +184,8 @@ "bundle_modal_error.close": "Chiudi", "bundle_modal_error.message": "Si è verificato un errore durante il caricamento di questa schermata.", "bundle_modal_error.retry": "Riprova", + "carousel.current": "Diapositiva {current, number} / {max, number}", + "carousel.slide": "Diapositiva {current, number} di {max, number}", "closed_registrations.other_server_instructions": "Poiché Mastodon è decentralizzato, puoi creare un profilo su un altro server, pur continuando a interagire con questo.", "closed_registrations_modal.description": "Correntemente, è impossibile creare un profilo su {domain}, ma sei pregato di tenere presente che non necessiti di un profilo specificamente su {domain} per utilizzare Mastodon.", "closed_registrations_modal.find_another_server": "Trova un altro server", @@ -167,7 +201,9 @@ "column.domain_blocks": "Domini bloccati", "column.edit_list": "Modifica lista", "column.favourites": "Preferiti", - "column.firehose": "Feed dal vivo", + "column.firehose": "Feed in diretta", + "column.firehose_local": "Feed in diretta per questo server", + "column.firehose_singular": "Feed in diretta", "column.follow_requests": "Richieste di seguirti", "column.home": "Home", "column.list_members": "Gestisci i membri della lista", @@ -187,6 +223,7 @@ "community.column_settings.local_only": "Solo Locale", "community.column_settings.media_only": "Solo Media", "community.column_settings.remote_only": "Solo Remoto", + "compose.error.blank_post": "Il post non può essere vuoto.", "compose.language.change": "Cambia la lingua", "compose.language.search": "Cerca lingue...", "compose.published.body": "Post pubblicato.", @@ -239,6 +276,11 @@ "confirmations.missing_alt_text.secondary": "Pubblica comunque", "confirmations.missing_alt_text.title": "Aggiungere testo alternativo?", "confirmations.mute.confirm": "Silenzia", + "confirmations.private_quote_notify.cancel": "Torna a modificare", + "confirmations.private_quote_notify.confirm": "Pubblica il post", + "confirmations.private_quote_notify.do_not_show_again": "Non mostrarmi più questo messaggio", + "confirmations.private_quote_notify.message": "La persona che stai citando e le altre persone menzionate riceveranno una notifica e potranno visualizzare il tuo post, anche se non ti stanno seguendo.", + "confirmations.private_quote_notify.title": "Condividere con i seguaci e gli utenti menzionati?", "confirmations.quiet_post_quote_info.dismiss": "Non ricordarmelo più", "confirmations.quiet_post_quote_info.got_it": "Ho capito", "confirmations.quiet_post_quote_info.message": "Quando citi un post pubblico silenzioso, il tuo post verrà nascosto dalle timeline di tendenza.", @@ -252,9 +294,12 @@ "confirmations.revoke_quote.confirm": "Elimina il post", "confirmations.revoke_quote.message": "Questa azione non può essere annullata.", "confirmations.revoke_quote.title": "Rimuovere il post?", + "confirmations.unblock.confirm": "Sblocca", + "confirmations.unblock.title": "Sbloccare {name}?", "confirmations.unfollow.confirm": "Smetti di seguire", - "confirmations.unfollow.message": "Sei sicuro di voler smettere di seguire {name}?", - "confirmations.unfollow.title": "Smettere di seguire l'utente?", + "confirmations.unfollow.title": "Smettere di seguire {name}?", + "confirmations.withdraw_request.confirm": "Annulla la richiesta", + "confirmations.withdraw_request.title": "Annullare la richiesta di seguire {name}?", "content_warning.hide": "Nascondi post", "content_warning.show": "Mostra comunque", "content_warning.show_more": "Mostra di più", @@ -325,6 +370,7 @@ "empty_column.bookmarked_statuses": "Non hai ancora salvato nei segnalibri alcun post. Quando lo farai, apparirà qui.", "empty_column.community": "La cronologia locale è vuota. Scrivi qualcosa pubblicamente per dare inizio alla festa!", "empty_column.direct": "Non hai ancora alcuna menzione privata. Quando ne invierai o riceverai una, apparirà qui.", + "empty_column.disabled_feed": "Questo feed è stato disabilitato dagli amministratori del tuo server.", "empty_column.domain_blocks": "Ancora nessun dominio bloccato.", "empty_column.explore_statuses": "Nulla è in tendenza al momento. Ricontrolla più tardi!", "empty_column.favourited_statuses": "Non hai ancora alcun post preferito. Quando ne salverai uno tra i preferiti, apparirà qui.", @@ -338,6 +384,7 @@ "empty_column.notification_requests": "Tutto chiaro! Non c'è niente qui. Quando ricevi nuove notifiche, verranno visualizzate qui in base alle tue impostazioni.", "empty_column.notifications": "Non hai ancora nessuna notifica. Quando altre persone interagiranno con te, le vedrai qui.", "empty_column.public": "Non c'è nulla qui! Scrivi qualcosa pubblicamente o segui manualmente gli utenti dagli altri server per riempire questo spazio", + "error.no_hashtag_feed_access": "Iscriviti o accedi per visualizzare e seguire questo hashtag.", "error.unexpected_crash.explanation": "A causa di un bug nel nostro codice o di un problema di compatibilità del browser, non è stato possibile visualizzare correttamente questa pagina.", "error.unexpected_crash.explanation_addons": "Impossibile mostrare correttamente questa pagina. Questo errore è probabilmente causato da un addon del browser o da strumenti di traduzione automatica.", "error.unexpected_crash.next_steps": "Prova a ricaricare la pagina. Se non aiuta, potresti comunque utilizzare Mastodon tramite un browser differente o un'app nativa.", @@ -349,11 +396,9 @@ "explore.trending_links": "Notizie", "explore.trending_statuses": "Post", "explore.trending_tags": "Hashtag", + "featured_carousel.current": "Post {current, number} / {max, number}", "featured_carousel.header": "{count, plural, one {Post appuntato} other {Post appuntati}}", - "featured_carousel.next": "Successivo", - "featured_carousel.post": "Post", - "featured_carousel.previous": "Precedente", - "featured_carousel.slide": "{index} di {total}", + "featured_carousel.slide": "Post {current, number} di {max, number}", "filter_modal.added.context_mismatch_explanation": "La categoria di questo filtro non si applica al contesto in cui hai acceduto a questo post. Se desideri che il post sia filtrato anche in questo contesto, dovrai modificare il filtro.", "filter_modal.added.context_mismatch_title": "Contesto non corrispondente!", "filter_modal.added.expired_explanation": "La categoria di questo filtro è scaduta, dovrvai modificarne la data di scadenza per applicarlo.", @@ -396,6 +441,9 @@ "follow_suggestions.who_to_follow": "Chi seguire", "followed_tags": "Hashtag seguiti", "footer.about": "Info", + "footer.about_mastodon": "Riguardo Mastodon", + "footer.about_server": "Riguardo {domain}", + "footer.about_this_server": "Info", "footer.directory": "Cartella dei profili", "footer.get_app": "Scarica l'app", "footer.keyboard_shortcuts": "Scorciatoie da tastiera", @@ -497,6 +545,7 @@ "keyboard_shortcuts.toggle_hidden": "Mostra/Nasconde il testo dietro CW", "keyboard_shortcuts.toggle_sensitivity": "Mostra/Nasconde media", "keyboard_shortcuts.toot": "Crea un nuovo post", + "keyboard_shortcuts.top": "Sposta all'inizio della lista", "keyboard_shortcuts.translate": "Traduce un post", "keyboard_shortcuts.unfocus": "Rimuove il focus sull'area di composizione testuale/ricerca", "keyboard_shortcuts.up": "Scorre in su nell'elenco", @@ -566,8 +615,8 @@ "navigation_bar.follows_and_followers": "Seguiti e seguaci", "navigation_bar.import_export": "Importa ed esporta", "navigation_bar.lists": "Liste", - "navigation_bar.live_feed_local": "Feed live (locale)", - "navigation_bar.live_feed_public": "Feed live (pubblico)", + "navigation_bar.live_feed_local": "Feed in diretta (locale)", + "navigation_bar.live_feed_public": "Feed in diretta (pubblico)", "navigation_bar.logout": "Disconnettiti", "navigation_bar.moderation": "Moderazione", "navigation_bar.more": "Altro", @@ -745,7 +794,9 @@ "privacy.unlisted.short": "Pubblico silenzioso", "privacy_policy.last_updated": "Ultimo aggiornamento {date}", "privacy_policy.title": "Politica sulla Privacy", + "quote_error.edit": "Le citazioni non possono essere aggiunte quando si modifica un post.", "quote_error.poll": "Nei sondaggi non sono consentite le citazioni.", + "quote_error.private_mentions": "Le citazioni non sono consentite con le menzioni dirette.", "quote_error.quote": "È consentita una sola citazione alla volta.", "quote_error.unauthorized": "Non sei autorizzato a citare questo post.", "quote_error.upload": "Le citazioni non sono consentite con gli allegati multimediali.", @@ -864,8 +915,13 @@ "status.cancel_reblog_private": "Annulla reblog", "status.cannot_quote": "Non ti è consentito citare questo post", "status.cannot_reblog": "Questo post non può essere condiviso", - "status.context.load_new_replies": "Nuove risposte disponibili", - "status.context.loading": "Controllo per altre risposte", + "status.contains_quote": "Contiene una citazione", + "status.context.loading": "Caricamento di altre risposte", + "status.context.loading_error": "Impossibile caricare nuove risposte", + "status.context.loading_success": "Nuove risposte caricate", + "status.context.more_replies_found": "Sono state trovate altre risposte", + "status.context.retry": "Riprova", + "status.context.show": "Mostra", "status.continued_thread": "Discussione continua", "status.copy": "Copia link al post", "status.delete": "Elimina", @@ -878,7 +934,7 @@ "status.edited_x_times": "Modificato {count, plural, one {{count} volta} other {{count} volte}}", "status.embed": "Ottieni codice incorporato", "status.favourite": "Preferito", - "status.favourites": "{count, plural, one {preferito} other {preferiti}}", + "status.favourites_count": "{count, plural, one {{counter} apprezzamento} other {{counter} apprezzamenti}}", "status.filter": "Filtra questo post", "status.history.created": "Creato da {name} il {date}", "status.history.edited": "Modificato da {name} il {date}", @@ -894,27 +950,33 @@ "status.pin": "Fissa in cima sul profilo", "status.quote": "Cita", "status.quote.cancel": "Annulla la citazione", + "status.quote_error.blocked_account_hint.title": "Questo post è nascosto perché hai bloccato @{name}.", + "status.quote_error.blocked_domain_hint.title": "Questo post è nascosto perché hai bloccato @{domain}.", "status.quote_error.filtered": "Nascosto a causa di uno dei tuoi filtri", "status.quote_error.limited_account_hint.action": "Mostra comunque", "status.quote_error.limited_account_hint.title": "Questo profilo è stato nascosto dai moderatori di {domain}.", + "status.quote_error.muted_account_hint.title": "Questo post è nascosto perché hai silenziato @{name}.", "status.quote_error.not_available": "Post non disponibile", "status.quote_error.pending_approval": "Post in attesa", "status.quote_error.pending_approval_popout.body": "Su Mastodon, puoi controllare se qualcuno può citarti. Questo post è in attesa dell'approvazione dell'autore originale.", "status.quote_error.revoked": "Post rimosso dall'autore", "status.quote_followers_only": "Solo i seguaci possono citare questo post", "status.quote_manual_review": "L'autore esaminerà manualmente", + "status.quote_noun": "Citazione", "status.quote_policy_change": "Cambia chi può citare", "status.quote_post_author": "Citato un post di @{name}", "status.quote_private": "I post privati non possono essere citati", - "status.quotes": "{count, plural, one {citazione} other {citazioni}}", "status.quotes.empty": "Nessuno ha ancora citato questo post. Quando qualcuno lo farà, verrà visualizzato qui.", + "status.quotes.local_other_disclaimer": "Le citazioni rifiutate dall'autore non verranno mostrate.", + "status.quotes.remote_other_disclaimer": "Solo le citazioni provenienti da {domain} saranno mostrate qui. Le citazioni rifiutate dall'autore non saranno mostrate.", + "status.quotes_count": "{count, plural, one {{counter} citazione} other {{counter} citazioni}}", "status.read_more": "Leggi di più", "status.reblog": "Reblog", "status.reblog_or_quote": "Condividi o cita", "status.reblog_private": "Condividi di nuovo con i tuoi seguaci", "status.reblogged_by": "Rebloggato da {name}", - "status.reblogs": "{count, plural, one {boost} other {boost}}", "status.reblogs.empty": "Ancora nessuno ha rebloggato questo post. Quando qualcuno lo farà, apparirà qui.", + "status.reblogs_count": "{count, plural, one {{counter} condivisione} other {{counter} condivisioni}}", "status.redraft": "Elimina e riscrivi", "status.remove_bookmark": "Rimuovi segnalibro", "status.remove_favourite": "Rimuovi dai preferiti", @@ -988,6 +1050,8 @@ "video.volume_down": "Abbassa volume", "video.volume_up": "Alza volume", "visibility_modal.button_title": "Imposta la visibilità", + "visibility_modal.direct_quote_warning.text": "Se si salvano le impostazioni correnti, la citazione incorporata verrà convertita in un collegamento.", + "visibility_modal.direct_quote_warning.title": "Le citazioni non possono essere incorporate in menzioni private", "visibility_modal.header": "Visibilità e interazione", "visibility_modal.helper.direct_quoting": "Le menzioni private scritte su Mastodon non possono essere citate da altri.", "visibility_modal.helper.privacy_editing": "La visibilità non può essere modificata dopo la pubblicazione di un post.", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index ba42000ec7c350..342cc4fc323727 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -28,6 +28,7 @@ "account.disable_notifications": "@{name}さんの投稿時の通知を停止", "account.domain_blocking": "ブロックしているドメイン", "account.edit_profile": "プロフィール編集", + "account.edit_profile_short": "編集", "account.enable_notifications": "@{name}さんの投稿時に通知", "account.endorse": "プロフィールで紹介する", "account.familiar_followers_many": "{name1}、{name2}、他{othersCount, plural, one {one other you know} other {# others you know}}人のユーザーにフォローされています", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "投稿がありません", "account.follow": "フォロー", "account.follow_back": "フォローバック", + "account.follow_back_short": "フォローバック", + "account.follow_request": "フォローリクエスト", + "account.follow_request_cancel": "リクエストをキャンセル", + "account.follow_request_cancel_short": "キャンセル", + "account.follow_request_short": "リクエスト", "account.followers": "フォロワー", "account.followers.empty": "まだ誰もフォローしていません。", "account.followers_counter": "{count, plural, other {{counter} フォロワー}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "投稿と返信", "account.remove_from_followers": "{name}さんをフォロワーから削除", "account.report": "@{name}さんを通報", - "account.requested": "フォロー承認待ちです。クリックしてキャンセル", "account.requested_follow": "{name}さんがあなたにフォローリクエストしました", "account.requests_to_follow_you": "フォローリクエスト", "account.share": "@{name}さんのプロフィールを共有する", @@ -108,25 +113,16 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "目が不自由な方のために説明してください…", "alt_text_modal.done": "完了", "announcement.announcement": "お知らせ", - "annual_report.summary.archetype.booster": "トレンドハンター", - "annual_report.summary.archetype.lurker": "ROM専", - "annual_report.summary.archetype.oracle": "予言者", - "annual_report.summary.archetype.pollster": "調査員", - "annual_report.summary.archetype.replier": "社交家", - "annual_report.summary.followers.followers": "フォロワー", - "annual_report.summary.followers.total": "合計{count}", - "annual_report.summary.here_it_is": "こちらがあなたの{year}年の振り返りです", - "annual_report.summary.highlighted_post.by_favourites": "最もお気に入りされた投稿", - "annual_report.summary.highlighted_post.by_reblogs": "最もブーストされた投稿", - "annual_report.summary.highlighted_post.by_replies": "最も返信が多かった投稿", - "annual_report.summary.highlighted_post.possessive": "{name}の", + "annual_report.summary.followers.new_followers": "{count, plural, other {新しいフォロワー}}", + "annual_report.summary.highlighted_post.boost_count": "この投稿は {count, plural, other {# 回}}ブーストされました。", + "annual_report.summary.highlighted_post.favourite_count": "この投稿は {count, plural, other {# 回}}お気に入りされました。", + "annual_report.summary.highlighted_post.title": "最も人気のある投稿", "annual_report.summary.most_used_app.most_used_app": "最も使用されているアプリ", "annual_report.summary.most_used_hashtag.most_used_hashtag": "最も使用されたハッシュタグ", - "annual_report.summary.most_used_hashtag.none": "なし", + "annual_report.summary.most_used_hashtag.used_count": "あなたはこのハッシュタグを {count, plural, other {# 件の投稿}}に含めました。", "annual_report.summary.new_posts.new_posts": "新しい投稿", "annual_report.summary.percentile.text": "{domain}で 上位に入ります!", "annual_report.summary.percentile.we_wont_tell_bernie": "バー二ーには秘密にしておくよ。", - "annual_report.summary.thanks": "Mastodonの一員になってくれてありがとう!", "attachments_list.unprocessed": "(未処理)", "audio.hide": "音声を閉じる", "block_modal.remote_users_caveat": "このサーバーはあなたのブロックの意思を尊重するように {domain} へ通知します。しかし、サーバーによってはブロック機能の扱いが異なる場合もありえるため、相手のサーバー側で求める通りの処理が行われる確証はありません。また、公開投稿はユーザーがログアウト状態であれば閲覧できる可能性があります。", @@ -168,6 +164,8 @@ "column.edit_list": "リストを編集", "column.favourites": "お気に入り", "column.firehose": "リアルタイムフィード", + "column.firehose_local": "このサーバーのリアルタイムフィード", + "column.firehose_singular": "リアルタイムフィード", "column.follow_requests": "フォローリクエスト", "column.home": "ホーム", "column.list_members": "リストのメンバーを管理", @@ -247,10 +245,14 @@ "confirmations.remove_from_followers.message": "{name}さんはあなたをフォローしなくなります。本当によろしいですか?", "confirmations.remove_from_followers.title": "フォロワーを削除しますか?", "confirmations.revoke_quote.confirm": "投稿を削除", + "confirmations.revoke_quote.message": "この操作は元に戻せません。", "confirmations.revoke_quote.title": "投稿を削除しますか?", + "confirmations.unblock.confirm": "ブロック解除", + "confirmations.unblock.title": "@{name}さんのブロックを解除しますか?", "confirmations.unfollow.confirm": "フォロー解除", - "confirmations.unfollow.message": "本当に{name}さんのフォローを解除しますか?", - "confirmations.unfollow.title": "フォローを解除しようとしています", + "confirmations.unfollow.title": "{name} さんのフォローを解除しますか?", + "confirmations.withdraw_request.confirm": "リクエスト取り消し", + "confirmations.withdraw_request.title": "{name} さんのフォローリクエストを取り消しますか?", "content_warning.hide": "内容を隠す", "content_warning.show": "承知して表示", "content_warning.show_more": "続きを表示", @@ -320,6 +322,7 @@ "empty_column.bookmarked_statuses": "まだ何もブックマーク登録していません。ブックマーク登録するとここに表示されます。", "empty_column.community": "ローカルタイムラインはまだ使われていません。何か書いてみましょう!", "empty_column.direct": "非公開の返信はまだありません。非公開でやりとりをするとここに表示されます。", + "empty_column.disabled_feed": "このフィードはサーバー管理者によって無効にされています。", "empty_column.domain_blocks": "ブロックしているドメインはありません。", "empty_column.explore_statuses": "まだ何もありません。後で確認してください。", "empty_column.favourited_statuses": "お気に入りの投稿はまだありません。お気に入りに登録すると、ここに表示されます。", @@ -345,10 +348,6 @@ "explore.trending_statuses": "投稿", "explore.trending_tags": "ハッシュタグ", "featured_carousel.header": "{count, plural, other {固定された投稿}}", - "featured_carousel.next": "次へ", - "featured_carousel.post": "投稿", - "featured_carousel.previous": "前へ", - "featured_carousel.slide": "{index} / {total}", "filter_modal.added.context_mismatch_explanation": "このフィルターカテゴリーはあなたがアクセスした投稿のコンテキストには適用されません。この投稿のコンテキストでもフィルターを適用するにはフィルターを編集する必要があります。", "filter_modal.added.context_mismatch_title": "コンテキストが一致しません!", "filter_modal.added.expired_explanation": "このフィルターカテゴリーは有効期限が切れています。適用するには有効期限を更新してください。", @@ -391,6 +390,7 @@ "follow_suggestions.who_to_follow": "フォローを増やしてみませんか?", "followed_tags": "フォロー中のハッシュタグ", "footer.about": "概要", + "footer.about_this_server": "概要", "footer.directory": "ディレクトリ", "footer.get_app": "アプリを入手", "footer.keyboard_shortcuts": "キーボードショートカット", @@ -472,6 +472,7 @@ "keyboard_shortcuts.home": "ホームタイムラインを開く", "keyboard_shortcuts.hotkey": "ホットキー", "keyboard_shortcuts.legend": "この一覧を表示", + "keyboard_shortcuts.load_more": "「もっと見る」ボタンに移動", "keyboard_shortcuts.local": "ローカルタイムラインを開く", "keyboard_shortcuts.mention": "メンション", "keyboard_shortcuts.muted": "ミュートしたユーザーのリストを開く", @@ -492,6 +493,7 @@ "keyboard_shortcuts.translate": "投稿を翻訳する", "keyboard_shortcuts.unfocus": "投稿の入力欄・検索欄から離れる", "keyboard_shortcuts.up": "カラム内一つ上に移動", + "learn_more_link.got_it": "了解", "learn_more_link.learn_more": "もっと見る", "lightbox.close": "閉じる", "lightbox.next": "次", @@ -606,6 +608,7 @@ "notification.moderation_warning.action_suspend": "あなたのアカウントは停止されました。", "notification.own_poll": "アンケートが終了しました", "notification.poll": "投票したアンケートが終了しました", + "notification.quoted_update": "あなたが引用した投稿を {name} が編集しました", "notification.reblog": "{name}さんがあなたの投稿をブーストしました", "notification.reblog.name_and_others_with_link": "{name}さんとほか{count, plural, other {#人}}がブーストしました", "notification.relationships_severance_event": "{name} との関係が失われました", @@ -748,6 +751,7 @@ "relative_time.minutes": "{number}分前", "relative_time.seconds": "{number}秒前", "relative_time.today": "今日", + "remove_quote_hint.button_label": "了解", "reply_indicator.attachments": "{count, plural, other {#件のメディア}}", "reply_indicator.cancel": "キャンセル", "reply_indicator.poll": "アンケート", @@ -844,7 +848,7 @@ "status.cancel_reblog_private": "ブースト解除", "status.cannot_quote": "この投稿は引用できません", "status.cannot_reblog": "この投稿はブーストできません", - "status.context.load_new_replies": "新しい返信があります", + "status.context.retry": "リトライ", "status.continued_thread": "続きのスレッド", "status.copy": "投稿へのリンクをコピー", "status.delete": "削除", @@ -857,7 +861,6 @@ "status.edited_x_times": "{count}回編集", "status.embed": "埋め込みコードを取得", "status.favourite": "お気に入り", - "status.favourites": "{count, plural, one {お気に入り} other {お気に入り}}", "status.filter": "この投稿をフィルターする", "status.history.created": "{name}さんが{date}に作成", "status.history.edited": "{name}さんが{date}に編集", @@ -876,12 +879,14 @@ "status.quote_error.filtered": "あなたのフィルター設定によって非表示になっています", "status.quote_error.pending_approval": "承認待ちの投稿", "status.quote_noun": "引用", - "status.quotes": "{count, plural, other {引用}}", + "status.quote_policy_change": "引用できるユーザーの変更", + "status.quote_post_author": "{name} の投稿を引用", + "status.quote_private": "非公開の投稿は引用できません", + "status.quotes.local_other_disclaimer": "投稿者が拒否した引用は表示されません。", "status.read_more": "もっと見る", "status.reblog": "ブースト", "status.reblog_or_quote": "ブーストか引用", "status.reblogged_by": "{name}さんがブースト", - "status.reblogs": "{count, plural, one {ブースト} other {ブースト}}", "status.reblogs.empty": "まだ誰もブーストしていません。ブーストされるとここに表示されます。", "status.redraft": "削除して下書きに戻す", "status.remove_bookmark": "ブックマークを削除", @@ -892,6 +897,7 @@ "status.reply": "返信", "status.replyAll": "全員に返信", "status.report": "@{name}さんを通報", + "status.revoke_quote": "{name} さんの投稿から自分の投稿を削除", "status.sensitive_warning": "閲覧注意", "status.share": "共有", "status.show_less_all": "全て隠す", @@ -929,6 +935,7 @@ "upload_button.label": "メディアを追加 (複数の画像または1つの動画か音声ファイル)", "upload_error.limit": "アップロードできる上限を超えています。", "upload_error.poll": "アンケートではファイルをアップロードできません。", + "upload_error.quote": "引用ではファイルをアップロードできません。", "upload_form.drag_and_drop.instructions": "メディア添付ファイルを選択するには、スペースキーまたはエンターキーを押してください。ドラッグ中は、矢印キーを使ってメディア添付ファイルを任意の方向に移動できます。再度スペースキーまたはエンターキーを押すと新しい位置にメディア添付ファイルをドロップできます。キャンセルするにはエスケープキーを押してください。", "upload_form.drag_and_drop.on_drag_cancel": "ドラッグがキャンセルされました。メディア添付ファイル {item} がドロップされました。", "upload_form.drag_and_drop.on_drag_end": "メディア添付ファイル {item} がドロップされました。", @@ -953,6 +960,11 @@ "video.volume_down": "音量を下げる", "video.volume_up": "音量を上げる", "visibility_modal.button_title": "公開範囲の設定", + "visibility_modal.header": "公開範囲と引用", + "visibility_modal.helper.direct_quoting": "Mastodon で作成された非公開の返信は他人から引用できません。", + "visibility_modal.helper.private_quoting": "Mastodon で作成されたフォロワーのみの投稿は他人から引用できません。", + "visibility_modal.helper.unlisted_quoting": "誰かがあなたを引用すると、その投稿もトレンドから非表示になります。", + "visibility_modal.instructions": "この投稿の公開・引用範囲を設定します。また、「デフォルトの投稿設定」から今後の投稿の範囲を設定可能です。", "visibility_modal.privacy_label": "公開範囲", "visibility_modal.quote_followers": "フォロワーのみ", "visibility_modal.quote_label": "引用できるユーザー", diff --git a/app/javascript/mastodon/locales/ka.json b/app/javascript/mastodon/locales/ka.json index ea7375fd00be70..2b495ab0b140e9 100644 --- a/app/javascript/mastodon/locales/ka.json +++ b/app/javascript/mastodon/locales/ka.json @@ -21,7 +21,6 @@ "account.posts": "პოსტები", "account.posts_with_replies": "ტუტები და პასუხები", "account.report": "დაარეპორტე @{name}", - "account.requested": "დამტკიცების მოლოდინში. დააწკაპუნეთ რომ უარყოთ დადევნების მოთხონვა", "account.share": "გააზიარე @{name}-ის პროფილი", "account.show_reblogs": "აჩვენე ბუსტები @{name}-სგან", "account.unblock": "განბლოკე @{name}", @@ -72,7 +71,6 @@ "confirmations.mute.confirm": "დადუმება", "confirmations.redraft.confirm": "გაუქმება და გადანაწილება", "confirmations.unfollow.confirm": "ნუღარ მიჰყვები", - "confirmations.unfollow.message": "დარწმუნებული ხართ, აღარ გსურთ მიჰყვებოდეთ {name}-ს?", "embed.instructions": "ეს სტატუსი ჩასვით თქვენს ვებ-საიტზე შემდეგი კოდის კოპირებით.", "embed.preview": "ესაა თუ როგორც გამოჩნდება:", "emoji_button.activity": "აქტივობა", diff --git a/app/javascript/mastodon/locales/kab.json b/app/javascript/mastodon/locales/kab.json index c48480995753b7..05ea5f312ef27e 100644 --- a/app/javascript/mastodon/locales/kab.json +++ b/app/javascript/mastodon/locales/kab.json @@ -24,6 +24,7 @@ "account.direct": "Bder-d @{name} weḥd-s", "account.disable_notifications": "Ḥbes ur iyi-d-ttazen ara ilɣa mi ara d-isuffeɣ @{name}", "account.edit_profile": "Ẓreg amaɣnu", + "account.edit_profile_short": "Ẓreg", "account.enable_notifications": "Azen-iyi-d ilɣa mi ara d-isuffeɣ @{name}", "account.endorse": "Welleh fell-as deg umaɣnu-inek", "account.familiar_followers_many": "Yeṭṭafaṛ-it {name1} d {name2}, akked {othersCount, plural, one {yiwen nniḍen i tessneḍ} other {# nniḍen i tessneḍ}}", @@ -35,6 +36,9 @@ "account.featured_tags.last_status_never": "Ulac tisuffaɣ", "account.follow": "Ḍfer", "account.follow_back": "Ḍfer-it ula d kečč·mm", + "account.follow_request_cancel": "Semmet asuter", + "account.follow_request_cancel_short": "Semmet", + "account.follow_request_short": "Asuter", "account.followers": "Imeḍfaren", "account.followers.empty": "Ar tura, ulac yiwen i yeṭṭafaṛen amseqdac-agi.", "account.followers_counter": "{count, plural, one {{counter} n umḍfar} other {{counter} n yimeḍfaren}}", @@ -63,7 +67,6 @@ "account.posts_with_replies": "Tisuffaɣ d tririyin", "account.remove_from_followers": "Kkes {name} seg ineḍfaren", "account.report": "Cetki ɣef @{name}", - "account.requested": "Di laɛḍil ad yettwaqbel. Ssit i wakken ad yefsex usuter n uḍfar", "account.requested_follow": "{name} yessuter ad k·m-yeḍfer", "account.share": "Bḍu amaɣnu n @{name}", "account.show_reblogs": "Ssken-d inebḍa n @{name}", @@ -89,13 +92,9 @@ "alt_text_modal.cancel": "Semmet", "alt_text_modal.done": "Immed", "announcement.announcement": "Ulɣu", - "annual_report.summary.followers.followers": "imeḍfaṛen", - "annual_report.summary.followers.total": "{count} deg aɣrud", "annual_report.summary.most_used_app.most_used_app": "asnas yettwasqedcen s waṭas", - "annual_report.summary.most_used_hashtag.none": "Ula yiwen", "annual_report.summary.new_posts.new_posts": "tisuffaɣ timaynutin", "annual_report.summary.percentile.we_wont_tell_bernie": "Ur as-neqqar i yiwen.", - "annual_report.summary.thanks": "Tanemmirt imi i tettekkiḍ deg Mastodon!", "audio.hide": "Ffer amesli", "block_modal.show_less": "Ssken-d drus", "block_modal.show_more": "Ssken-d ugar", @@ -194,8 +193,11 @@ "confirmations.remove_from_followers.confirm": "Kkes aneḍfar", "confirmations.revoke_quote.confirm": "Kkes tasuffeɣt", "confirmations.revoke_quote.title": "Kkes tasuffeɣt?", + "confirmations.unblock.confirm": "Serreḥ", + "confirmations.unblock.title": "Serreḥ i {name}?", "confirmations.unfollow.confirm": "Ur ḍḍafaṛ ara", - "confirmations.unfollow.message": "Tetḥeqqeḍ belli tebɣiḍ ur teṭafaṛeḍ ara {name}?", + "confirmations.unfollow.title": "Ḥbes aḍfar n {name}?", + "confirmations.withdraw_request.confirm": "Agwi asuter", "content_warning.hide": "Ffer tasuffeɣt", "content_warning.show": "Ssken-d akken tebɣu tili", "content_warning.show_more": "Sken-d ugar", @@ -267,10 +269,7 @@ "explore.trending_links": "Isallen", "explore.trending_statuses": "Tisuffaɣ", "explore.trending_tags": "Ihacṭagen", - "featured_carousel.next": "Uḍfiṛ", - "featured_carousel.post": "Tasuffeɣt", - "featured_carousel.previous": "Uzwir", - "featured_carousel.slide": "{index} ɣef {total}", + "featured_carousel.header": "{count, plural, one {n tsuffeɣt tunṭiḍt} other {n tsuffaɣ tunṭiḍin}}", "filter_modal.added.review_and_configure_title": "Iɣewwaṛen n imzizdig", "filter_modal.added.settings_link": "asebter n yiɣewwaṛen", "filter_modal.added.short_explanation": "Tasuffeɣt-a tettwarna ɣer taggayt-a n yimsizdegen: {title}.", @@ -298,6 +297,7 @@ "follow_suggestions.who_to_follow": "Ad tḍefreḍ?", "followed_tags": "Ihacṭagen yettwaḍfaren", "footer.about": "Ɣef", + "footer.about_this_server": "Ɣef", "footer.directory": "Akaram n imeɣna", "footer.get_app": "Awi-d asnas", "footer.keyboard_shortcuts": "Inegzumen n unasiw", @@ -352,6 +352,7 @@ "keyboard_shortcuts.direct": "to open direct messages column", "keyboard_shortcuts.down": "i kennu ɣer wadda n tebdart", "keyboard_shortcuts.enter": "i tildin n tsuffeɣt", + "keyboard_shortcuts.favourite": "Smenyef tassuɣeft", "keyboard_shortcuts.favourites": "Ldi tabdert n yismenyifen", "keyboard_shortcuts.federated": "i tildin n tsuddemt tamatut n yisallen", "keyboard_shortcuts.heading": "Inegzumen n unasiw", @@ -364,8 +365,9 @@ "keyboard_shortcuts.my_profile": "akken ad d-teldiḍ amaɣnu-ik", "keyboard_shortcuts.notifications": "Ad d-yeldi ajgu n yilɣa", "keyboard_shortcuts.open_media": "i tiɣwalin yeldin", - "keyboard_shortcuts.pinned": "akken ad teldiḍ tabdart n tjewwiqin yettwasentḍen", + "keyboard_shortcuts.pinned": "akken ad teldiḍ tabdart n tsuffaɣ tunṭiḍin", "keyboard_shortcuts.profile": "akken ad d-teldiḍ amaɣnu n umeskar", + "keyboard_shortcuts.quote": "Tanebdurt n tsuffeɣt", "keyboard_shortcuts.reply": "i tririt", "keyboard_shortcuts.requests": "akken ad d-teldiḍ tabdert n yisuturen n teḍfeṛt", "keyboard_shortcuts.search": "to focus search", @@ -444,6 +446,7 @@ "navigation_bar.privacy_and_reach": "Tabḍnit akked wagwaḍ", "navigation_bar.search": "Nadi", "navigation_bar.search_trends": "Anadi / Anezzuɣ", + "navigation_panel.collapse_lists": "Sneḍfes umuɣ n tebdart", "not_signed_in_indicator.not_signed_in": "You need to sign in to access this resource.", "notification.admin.report": "Yemla-t-id {name} {target}", "notification.admin.sign_up": "Ijerred {name}", @@ -454,6 +457,7 @@ "notification.follow": "iṭṭafar-ik·em-id {name}", "notification.follow.name_and_others": "{name} akked {count, plural, one {# nniḍen} other {# nniḍen}} iḍfeṛ-k·m-id", "notification.follow_request": "{name} yessuter-d ad k·m-yeḍfeṛ", + "notification.follow_request.name_and_others": "{name} d {count, plural, one {# nnayeḍ} other {# nniḍen}} yessuter-d ad k·kem-yeḍfer", "notification.label.mention": "Abdar", "notification.label.private_mention": "Abdar uslig", "notification.label.private_reply": "Tiririt tusligt", @@ -464,6 +468,7 @@ "notification.moderation_warning.action_suspend": "Yettwaseḥbes umiḍan-ik.", "notification.own_poll": "Tafrant-ik·im tfuk", "notification.reblog": "{name} yebḍa tajewwiqt-ik i tikelt-nniḍen", + "notification.reblog.name_and_others_with_link": "{name} akked {count, plural, one {# nnayeḍ} other {# nniḍen}} zzuzren tasuffeɣt-ik·im", "notification.relationships_severance_event.learn_more": "Issin ugar", "notification.status": "{name} akken i d-yessufeɣ", "notification_requests.accept": "Qbel", @@ -555,6 +560,8 @@ "regeneration_indicator.please_stand_by": "Ttxil rǧu.", "regeneration_indicator.preparing_your_home_feed": "Ha-tt-an tsuddemt-ik·im tagejdant tettwaheggay…", "relative_time.days": "{number}u", + "relative_time.full.days": "{number, plural, one {# n wass} other {# n wussan}} aya", + "relative_time.full.hours": "{number, plural, one {# n usrag} other {# n yesragen}} aya", "relative_time.full.just_now": "tura kan", "relative_time.hours": "{number}isr", "relative_time.just_now": "tura", @@ -562,6 +569,7 @@ "relative_time.seconds": "{number}tas", "relative_time.today": "ass-a", "remove_quote_hint.button_label": "Gziɣ-t", + "remove_quote_hint.title": "D tidet, tebɣiḍ ad tekkseḍ tasuffeɣt-inek·inem i d-yettwabedren?", "reply_indicator.attachments": "{count, plural, one {# n umedday} other {# n imeddayen}}", "reply_indicator.cancel": "Sefsex", "reply_indicator.poll": "Afmiḍi", @@ -630,6 +638,7 @@ "search_results.title": "Igemmaḍ n unadi ɣef \"{q}\"", "server_banner.active_users": "iseqdacen urmiden", "server_banner.administered_by": "Yettwadbel sɣur :", + "server_banner.is_one_of_many": "{domain} d yiwen seg seg waṭṭas n iqeddacen imzurag n Mastodon i tzemreḍ ad tsqesdceḍ i wakken ad tettekkiḍ deg fediverse.", "server_banner.server_stats": "Tidaddanin n uqeddac:", "sign_in_banner.create_account": "Snulfu-d amiḍan", "sign_in_banner.sign_in": "Qqen", @@ -639,7 +648,11 @@ "status.bookmark": "Creḍ", "status.cancel_reblog_private": "Sefsex beṭṭu", "status.cannot_reblog": "Tasuffeɣt-a ur tezmir ara ad tettwabḍu tikelt-nniḍen", - "status.context.load_new_replies": "Llant tririyin timaynutin", + "status.contains_quote": "Yegber tanebdurt", + "status.context.loading": "Aɛebbi n tririyin nniḍen", + "status.context.more_replies_found": "Ugar n tririyin ttwafent", + "status.context.retry": "Ɛreḍ tikkelt nniḍen", + "status.context.show": "Sken-d", "status.continued_thread": "Asqerdec yettkemmil", "status.copy": "Nɣel assaɣ ɣer tasuffeɣt", "status.delete": "Kkes", @@ -651,7 +664,6 @@ "status.edited_x_times": "Tettwaẓreg {count, plural, one {{count} n tikkelt} other {{count} n tikkal}}", "status.embed": "Awi-d tangalt n weslaɣ", "status.favourite": "Amenyaf", - "status.favourites": "{count, plural, one {n usmenyaf} other {n ismenyafen}}", "status.filter": "Sizdeg tassufeɣt-a", "status.history.created": "Yerna-t {name} {date}", "status.history.edited": "Ibeddel-it {name} {date}", @@ -668,13 +680,15 @@ "status.quote": "Tanebdurt", "status.quote.cancel": "Semmet tanebdurt", "status.quote_error.limited_account_hint.action": "Sken-d akken ibɣu yili", + "status.quote_error.not_available": "Tasuffeɣt-a ulac-itt", "status.quote_error.revoked": "Tasuffeɣt-a yekkes-itt umeskar-is", + "status.quote_noun": "Tanebdurt", + "status.quote_policy_change": "Snifel anwa i izemren ad d-yebder", "status.quote_post_author": "Yebder-d tasuffeɣt sɣur @{name}", "status.read_more": "Issin ugar", "status.reblog": "Bḍu", "status.reblog_or_quote": "Zuzer neɣ ader-d", "status.reblogged_by": "Yebḍa-tt {name}", - "status.reblogs": "{count, plural, one {n usnerni} other {n yisnernuyen}}", "status.reblogs.empty": "Ula yiwen ur yebḍi tajewwiqt-agi ar tura. Ticki yebḍa-tt yiwen, ad d-iban da.", "status.redraft": "Kkes tɛiwdeḍ tira", "status.remove_bookmark": "Kkes tacreḍt", diff --git a/app/javascript/mastodon/locales/kk.json b/app/javascript/mastodon/locales/kk.json index b04790d9a1200f..7784f3d9e6871e 100644 --- a/app/javascript/mastodon/locales/kk.json +++ b/app/javascript/mastodon/locales/kk.json @@ -9,6 +9,7 @@ "about.domain_blocks.silenced.title": "Шектеулі", "about.domain_blocks.suspended.explanation": "Бұл сервердің деректері өңделмейді, сақталмайды және айырбасталмайды, сондықтан бұл сервердің қолданушыларымен кез келген әрекеттесу немесе байланыс мүмкін емес.", "about.domain_blocks.suspended.title": "Тоқтатылған", + "about.language_label": "Тіл", "about.not_available": "Бұл ақпарат бұл серверде қолжетімді емес.", "about.powered_by": "{mastodon} негізіндегі орталықсыз әлеуметтік желі", "about.rules": "Сервер ережелері", @@ -19,11 +20,13 @@ "account.block_domain": "{domain} доменін бұғаттау", "account.block_short": "Бұғаттау", "account.blocked": "Бұғатталған", + "account.blocking": "Бұғаттау", "account.cancel_follow_request": "Withdraw follow request", "account.direct": "@{name} жеке айту", "account.disable_notifications": "@{name} постары туралы ескертпеу", "account.domain_blocking": "Доменді бұғаттау", "account.edit_profile": "Профильді өңдеу", + "account.edit_profile_short": "Түзеу", "account.enable_notifications": "@{name} постары туралы ескерту", "account.endorse": "Профильде ұсыну", "account.familiar_followers_many": "{name1}, {name2} және {othersCount, plural, one {сіз білетін тағы бір адам} other {сіз білетін тағы # адам}} жазылған", @@ -65,7 +68,6 @@ "account.posts_with_replies": "Постар мен жауаптар", "account.remove_from_followers": "{name} жазылушылардан жою", "account.report": "Шағымдану @{name}", - "account.requested": "Растауын күтіңіз. Жазылудан бас тарту үшін басыңыз", "account.requested_follow": "{name} сізге жазылуға сұраныс жіберді", "account.requests_to_follow_you": "Сізге жазылу сұраныстары", "account.share": "@{name} профилін бөлісу\"", @@ -92,7 +94,11 @@ "alert.rate_limited.title": "Бағалау шектеулі", "alert.unexpected.message": "Бір нәрсе дұрыс болмады.", "alert.unexpected.title": "Өй!", + "alt_text_badge.title": "Балама мазмұны", + "alt_text_modal.add_alt_text": "Балама мазмұнды қосу", + "alt_text_modal.done": "Дайын", "announcement.announcement": "Хабарландыру", + "annual_report.summary.close": "Жабу", "boost_modal.combo": "Келесіде өткізіп жіберу үшін басыңыз {combo}", "bundle_column_error.retry": "Қайтадан көріңіз", "bundle_modal_error.close": "Жабу", @@ -141,7 +147,6 @@ "confirmations.mute.confirm": "Үнсіз қылу", "confirmations.redraft.confirm": "Өшіруді құптау", "confirmations.unfollow.confirm": "Оқымау", - "confirmations.unfollow.message": "\"{name} атты қолданушыға енді жазылғыңыз келмей ме?", "conversation.delete": "Пікірталасты өшіру", "conversation.mark_as_read": "Оқылды деп белгіле", "conversation.open": "Пікірталасты қарау", diff --git a/app/javascript/mastodon/locales/kn.json b/app/javascript/mastodon/locales/kn.json index ae4e4dd7b33326..1d383c6b276510 100644 --- a/app/javascript/mastodon/locales/kn.json +++ b/app/javascript/mastodon/locales/kn.json @@ -13,7 +13,6 @@ "account.followers": "ಹಿಂಬಾಲಕರು", "account.posts": "ಟೂಟ್‌ಗಳು", "account.posts_with_replies": "Toots and replies", - "account.requested": "Awaiting approval", "account.unblock_domain": "Unhide {domain}", "account_note.placeholder": "Click to add a note", "alert.unexpected.title": "ಅಯ್ಯೋ!", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index 40c9ae00044bc5..6dfb4948863d6a 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -24,10 +24,11 @@ "account.blocking": "차단함", "account.cancel_follow_request": "팔로우 취소", "account.copy": "프로필 링크 복사", - "account.direct": "@{name} 님에게 개인적으로 멘션", + "account.direct": "@{name} 님에게 개인 멘션", "account.disable_notifications": "@{name} 의 게시물 알림 끄기", "account.domain_blocking": "도메인 차단함", "account.edit_profile": "프로필 편집", + "account.edit_profile_short": "수정", "account.enable_notifications": "@{name} 의 게시물 알림 켜기", "account.endorse": "프로필에 추천하기", "account.familiar_followers_many": "{name1}, {name2} 님 외 내가 아는 {othersCount, plural, other {#}} 명이 팔로우함", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "게시물 없음", "account.follow": "팔로우", "account.follow_back": "맞팔로우", + "account.follow_back_short": "맞팔로우", + "account.follow_request": "팔로우 요청", + "account.follow_request_cancel": "요청 취소", + "account.follow_request_cancel_short": "취소", + "account.follow_request_short": "요청", "account.followers": "팔로워", "account.followers.empty": "아직 아무도 이 사용자를 팔로우하고 있지 않습니다.", "account.followers_counter": "{count, plural, other {팔로워 {counter}명}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "게시물과 답장", "account.remove_from_followers": "팔로워에서 {name} 제거", "account.report": "@{name} 신고", - "account.requested": "승인 대기 중. 클릭해서 취소하기", "account.requested_follow": "{name} 님이 팔로우 요청을 보냈습니다", "account.requests_to_follow_you": "팔로우 요청", "account.share": "@{name}의 프로필 공유", @@ -108,25 +113,38 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "시각 장애가 있는 사람들을 위한 설명을 작성하세요…", "alt_text_modal.done": "완료", "announcement.announcement": "공지사항", - "annual_report.summary.archetype.booster": "연쇄부스트마", - "annual_report.summary.archetype.lurker": "은둔자", - "annual_report.summary.archetype.oracle": "예언자", - "annual_report.summary.archetype.pollster": "여론조사원", - "annual_report.summary.archetype.replier": "답글나비", - "annual_report.summary.followers.followers": "팔로워", - "annual_report.summary.followers.total": "총 {count}", - "annual_report.summary.here_it_is": "{year}년 결산입니다:", - "annual_report.summary.highlighted_post.by_favourites": "가장 많은 좋아요를 받은 게시물", - "annual_report.summary.highlighted_post.by_reblogs": "가장 많이 부스트된 게시물", - "annual_report.summary.highlighted_post.by_replies": "가장 많은 답글을 받은 게시물", - "annual_report.summary.highlighted_post.possessive": "{name} 님의", + "annual_report.announcement.action_build": "랩스토돈 만들기", + "annual_report.announcement.action_view": "랩스토돈 보기", + "annual_report.announcement.title": "{year} 랩스토돈이 도착했습니다", + "annual_report.summary.archetype.booster.desc_public": "{name} 님은 부스트할 게시물을 기다리고 정확한 조준으로 다른 제작자들을 밀어주었습니다.", + "annual_report.summary.archetype.booster.desc_self": "당신은 부스트할 게시물을 기다리고 정확한 조준으로 다른 제작자들을 밀어주었습니다.", + "annual_report.summary.archetype.booster.name": "궁수", + "annual_report.summary.archetype.die_drei_fragezeichen": "???", + "annual_report.summary.archetype.lurker.name": "금욕주의자", + "annual_report.summary.archetype.oracle.desc_public": "{name} 님은 답글보다 새로운 글을 많이 작성해 마스토돈을 신선하고 미래지향적으로 만들었습니다.", + "annual_report.summary.archetype.oracle.desc_self": "당신은 답글보다 새로운 글을 많이 작성해 마스토돈을 신선하고 미래지향적으로 만들었습니다.", + "annual_report.summary.archetype.oracle.name": "예언자", + "annual_report.summary.archetype.pollster.desc_public": "{name} 님은 다른 게시물보다 투표를 많이 만들었고 마스토돈에서 호기심을 일궈냈습니다.", + "annual_report.summary.archetype.pollster.desc_self": "당신은 다른 게시물보다 투표를 많이 만들었고 마스토돈에서 호기심을 일궈냈습니다.", + "annual_report.summary.archetype.pollster.name": "호기심쟁이", + "annual_report.summary.archetype.replier.desc_public": "{name} 님은 다른 사람의 게시물에 자주 답글을 남겨 마스토돈에 새로운 논의거리를 만들어냈습니다.", + "annual_report.summary.archetype.replier.desc_self": "당신은 다른 사람의 게시물에 자주 답글을 남겨 마스토돈에 새로운 논의거리를 만들어냈습니다.", + "annual_report.summary.archetype.replier.name": "나비", + "annual_report.summary.archetype.reveal": "내 특성을 확인합니다", + "annual_report.summary.archetype.reveal_description": "마스토돈의 일원이 되어주셔서 감사합니다! {year} 년엔 어떤 특성을 부여받는지 알아봅시다.", + "annual_report.summary.archetype.title_public": "{name} 님의 특성", + "annual_report.summary.archetype.title_self": "당신의 특성", + "annual_report.summary.close": "닫기", + "annual_report.summary.highlighted_post.boost_count": "이 게시물은 {count, plural,other {# 번}} 부스트되었습니다.", + "annual_report.summary.highlighted_post.favourite_count": "이 게시물은 {count, plural,other {# 번}} 마음에 들었습니다.", + "annual_report.summary.highlighted_post.reply_count": "이 게시물은 {count, plural, other {# 개}}의 답글을 받았습니다.", + "annual_report.summary.highlighted_post.title": "가장 인기있는 게시물", "annual_report.summary.most_used_app.most_used_app": "가장 많이 사용한 앱", "annual_report.summary.most_used_hashtag.most_used_hashtag": "가장 많이 사용한 해시태그", - "annual_report.summary.most_used_hashtag.none": "없음", "annual_report.summary.new_posts.new_posts": "새 게시물", "annual_report.summary.percentile.text": "{domain} 사용자의 상위입니다.", "annual_report.summary.percentile.we_wont_tell_bernie": "종부세는 안 걷을게요", - "annual_report.summary.thanks": "마스토돈과 함께 해주셔서 감사합니다!", + "annual_report.summary.share_message": "나는 {archetype} 특성을 부여받았습니다!", "attachments_list.unprocessed": "(처리 안 됨)", "audio.hide": "소리 숨기기", "block_modal.remote_users_caveat": "우리는 {domain} 서버가 당신의 결정을 존중해 주길 부탁할 것입니다. 하지만 몇몇 서버는 차단을 다르게 취급할 수 있기 때문에 규정이 준수되는 것을 보장할 수는 없습니다. 공개 게시물은 로그인 하지 않은 사용자들에게 여전히 보여질 수 있습니다.", @@ -152,6 +170,8 @@ "bundle_modal_error.close": "닫기", "bundle_modal_error.message": "이 화면을 불러오는 중 뭔가 잘못되었습니다.", "bundle_modal_error.retry": "다시 시도", + "carousel.current": "페이지 {current, number} / {max, number}", + "carousel.slide": "{max, number} 중 {current, number} 페이지", "closed_registrations.other_server_instructions": "마스토돈은 분산화 되어 있기 때문에, 다른 서버에서 계정을 만들더라도 이 서버와 상호작용 할 수 있습니다.", "closed_registrations_modal.description": "{domain}은 현재 가입이 불가능합니다. 하지만 마스토돈을 이용하기 위해 꼭 {domain}을 사용할 필요는 없다는 사실을 인지해 두세요.", "closed_registrations_modal.find_another_server": "다른 서버 찾기", @@ -162,12 +182,14 @@ "column.bookmarks": "북마크", "column.community": "로컬 타임라인", "column.create_list": "리스트 만들기", - "column.direct": "개인적인 멘션", + "column.direct": "개인 멘션", "column.directory": "프로필 둘러보기", "column.domain_blocks": "차단한 도메인", "column.edit_list": "리스트 편집", "column.favourites": "좋아요", "column.firehose": "실시간 피드", + "column.firehose_local": "이 서버에 대한 실시간 피드", + "column.firehose_singular": "실시간 피드", "column.follow_requests": "팔로우 요청", "column.home": "홈", "column.list_members": "리스트 구성원 관리", @@ -187,6 +209,7 @@ "community.column_settings.local_only": "로컬만", "community.column_settings.media_only": "미디어만", "community.column_settings.remote_only": "원격지만", + "compose.error.blank_post": "빈 게시물은 게시할 수 없습니다.", "compose.language.change": "언어 변경", "compose.language.search": "언어 검색...", "compose.published.body": "게시하였습니다.", @@ -239,6 +262,10 @@ "confirmations.missing_alt_text.secondary": "그냥 게시하기", "confirmations.missing_alt_text.title": "대체 텍스트를 추가할까요? ", "confirmations.mute.confirm": "뮤트", + "confirmations.private_quote_notify.cancel": "편집으로 돌아가기", + "confirmations.private_quote_notify.confirm": "게시", + "confirmations.private_quote_notify.do_not_show_again": "이 메시지를 다시 표시하지 않음", + "confirmations.private_quote_notify.message": "인용하려는 사람과 멘션된 사람들은 나를 팔로우하지 않더라도 게시물에 대한 알림을 받으며 내용을 볼 수 있습니다.", "confirmations.quiet_post_quote_info.dismiss": "다시 보지 않기", "confirmations.quiet_post_quote_info.got_it": "알겠습니다", "confirmations.quiet_post_quote_info.message": "조용한 공개 게시물을 인용하면 그 게시물은 유행 타임라인에서 나타나지 않을 것입니다.", @@ -252,9 +279,12 @@ "confirmations.revoke_quote.confirm": "게시물 삭제", "confirmations.revoke_quote.message": "이 작업은 되돌릴 수 없습니다.", "confirmations.revoke_quote.title": "게시물을 지울까요?", + "confirmations.unblock.confirm": "차단 해제", + "confirmations.unblock.title": "{name} 님을 차단 해제할까요?", "confirmations.unfollow.confirm": "팔로우 해제", - "confirmations.unfollow.message": "정말로 {name} 님을 팔로우 해제하시겠습니까?", - "confirmations.unfollow.title": "사용자를 언팔로우 할까요?", + "confirmations.unfollow.title": "{name} 님을 언팔로우 할까요?", + "confirmations.withdraw_request.confirm": "요청 삭제", + "confirmations.withdraw_request.title": "{name} 님에 대한 팔로우 요청을 취소할까요?", "content_warning.hide": "게시물 숨기기", "content_warning.show": "무시하고 보기", "content_warning.show_more": "더 보기", @@ -324,7 +354,8 @@ "empty_column.blocks": "아직 아무도 차단하지 않았습니다.", "empty_column.bookmarked_statuses": "아직 북마크에 저장한 게시물이 없습니다. 게시물을 북마크 지정하면 여기에 나타납니다.", "empty_column.community": "로컬 타임라인에 아무것도 없습니다. 아무거나 적어 보세요!", - "empty_column.direct": "개인적인 멘션이 없습니다. 보내거나 받으면 여기에 표시됩니다.", + "empty_column.direct": "개인 멘션이 없습니다. 보내거나 받으면 여기에 표시됩니다.", + "empty_column.disabled_feed": "이 피드는 서버 관리자에 의해 비활성화되었습니다.", "empty_column.domain_blocks": "아직 차단한 도메인이 없습니다.", "empty_column.explore_statuses": "아직 유행하는 것이 없습니다. 나중에 다시 확인하세요!", "empty_column.favourited_statuses": "아직 좋아요한 게시물이 없습니다. 게시물을 좋아요 하면 여기에 나타납니다.", @@ -349,11 +380,9 @@ "explore.trending_links": "소식", "explore.trending_statuses": "게시물", "explore.trending_tags": "해시태그", + "featured_carousel.current": "게시물 {current, number} / {max, number}", "featured_carousel.header": "{count, plural, other {고정된 게시물}}", - "featured_carousel.next": "다음", - "featured_carousel.post": "게시물", - "featured_carousel.previous": "이전", - "featured_carousel.slide": "{total} 중 {index}", + "featured_carousel.slide": "{max, number} 중 {current, number} 번째 게시물", "filter_modal.added.context_mismatch_explanation": "이 필터 카테고리는 당신이 이 게시물에 접근한 문맥에 적용되지 않습니다. 만약 이 문맥에서도 필터되길 원한다면, 필터를 수정해야 합니다.", "filter_modal.added.context_mismatch_title": "문맥 불일치!", "filter_modal.added.expired_explanation": "이 필터 카테고리는 만료되었습니다, 적용하려면 만료 일자를 변경할 필요가 있습니다.", @@ -396,6 +425,7 @@ "follow_suggestions.who_to_follow": "팔로우할 만한 사람", "followed_tags": "팔로우 중인 해시태그", "footer.about": "정보", + "footer.about_this_server": "정보", "footer.directory": "프로필 책자", "footer.get_app": "앱 다운로드하기", "footer.keyboard_shortcuts": "키보드 단축키", @@ -416,7 +446,7 @@ "hashtag.column_settings.tag_mode.all": "모두", "hashtag.column_settings.tag_mode.any": "어느것이든", "hashtag.column_settings.tag_mode.none": "이것들을 제외하고", - "hashtag.column_settings.tag_toggle": "추가 해시태그를 이 컬럼에 추가합니다", + "hashtag.column_settings.tag_toggle": "추가 해시태그를 이 칼럼에 포함하기", "hashtag.counter_by_accounts": "{count, plural, other {참여자 {counter}명}}", "hashtag.counter_by_uses": "{count, plural, other {게시물 {counter}개}}", "hashtag.counter_by_uses_today": "오늘 {count, plural, other {{counter} 개의 게시물}}", @@ -457,6 +487,7 @@ "interaction_modal.no_account_yet": "아직 계정이 없나요?", "interaction_modal.on_another_server": "다른 서버에", "interaction_modal.on_this_server": "이 서버에서", + "interaction_modal.title": "로그인해서 계속하기", "interaction_modal.username_prompt": "예시: {example}", "intervals.full.days": "{number} 일", "intervals.full.hours": "{number} 시간", @@ -467,7 +498,7 @@ "keyboard_shortcuts.column": "해당 컬럼에 포커스", "keyboard_shortcuts.compose": "작성창에 포커스", "keyboard_shortcuts.description": "설명", - "keyboard_shortcuts.direct": "개인적인 멘션 컬럼 열기", + "keyboard_shortcuts.direct": "개인 멘션 칼럼 열기", "keyboard_shortcuts.down": "리스트에서 아래로 이동", "keyboard_shortcuts.enter": "게시물 열기", "keyboard_shortcuts.favourite": "게시물 좋아요", @@ -495,6 +526,7 @@ "keyboard_shortcuts.toggle_hidden": "CW로 가려진 텍스트를 표시/비표시", "keyboard_shortcuts.toggle_sensitivity": "미디어 보이기/숨기기", "keyboard_shortcuts.toot": "새 게시물 작성", + "keyboard_shortcuts.top": "목록의 최상단으로 이동", "keyboard_shortcuts.translate": "게시물 번역", "keyboard_shortcuts.unfocus": "작성창에서 포커스 해제", "keyboard_shortcuts.up": "리스트에서 위로 이동", @@ -555,7 +587,7 @@ "navigation_bar.automated_deletion": "게시물 자동 삭제", "navigation_bar.blocks": "차단한 사용자", "navigation_bar.bookmarks": "북마크", - "navigation_bar.direct": "개인적인 멘션", + "navigation_bar.direct": "개인 멘션", "navigation_bar.domain_blocks": "차단한 도메인", "navigation_bar.favourites": "좋아요", "navigation_bar.filters": "뮤트한 단어", @@ -661,7 +693,7 @@ "notifications.column_settings.push": "푸시 알림", "notifications.column_settings.quote": "인용:", "notifications.column_settings.reblog": "부스트:", - "notifications.column_settings.show": "컬럼에 표시", + "notifications.column_settings.show": "칼럼에 표시", "notifications.column_settings.sound": "효과음 재생", "notifications.column_settings.status": "새 게시물:", "notifications.column_settings.unread_notifications.category": "읽지 않은 알림", @@ -695,7 +727,7 @@ "notifications.policy.filter_not_following_hint": "내가 수동으로 승인하지 않는 한", "notifications.policy.filter_not_following_title": "내가 팔로우하지 않는 사람들", "notifications.policy.filter_private_mentions_hint": "내가 한 멘션에 단 답글이거나 내가 발신자를 팔로우 한 것이 아닌 이상 걸러집니다", - "notifications.policy.filter_private_mentions_title": "청하지 않은 개인적인 멘션", + "notifications.policy.filter_private_mentions_title": "청하지 않은 개인 멘션", "notifications.policy.title": "알림 조건 설정", "notifications_permission_banner.enable": "데스크탑 알림 활성화", "notifications_permission_banner.how_to_control": "마스토돈이 열려 있지 않을 때에도 알림을 받으려면, 데스크탑 알림을 활성화 하세요. 당신은 어떤 종류의 반응이 데스크탑 알림을 발생할 지를 {icon} 버튼을 통해 세세하게 설정할 수 있습니다.", @@ -743,9 +775,11 @@ "privacy.unlisted.short": "조용한 공개", "privacy_policy.last_updated": "{date}에 마지막으로 업데이트됨", "privacy_policy.title": "개인정보처리방침", + "quote_error.edit": "게시물을 수정하면서 인용을 추가할 수 없습니다.", "quote_error.poll": "인용과 투표를 함께 사용할 수 없습니다.", + "quote_error.private_mentions": "인용과 개인 멘션을 함께 사용할 수 없습니다.", "quote_error.quote": "한 번의 인용만 허용됩니다.", - "quote_error.unauthorized": "이 글을 인용할 권한이 없습니다.", + "quote_error.unauthorized": "이 게시물을 인용할 권한이 없습니다.", "quote_error.upload": "인용과 미디어 첨부를 함께 사용할 수 없습니다.", "recommended": "추천함", "refresh": "새로고침", @@ -860,22 +894,28 @@ "status.block": "@{name} 차단", "status.bookmark": "북마크", "status.cancel_reblog_private": "부스트 취소", + "status.cannot_quote": "인용을 비허용한 게시물", "status.cannot_reblog": "이 게시물은 부스트 할 수 없습니다", - "status.context.load_new_replies": "새 답글 보기", - "status.context.loading": "추가 답글 확인중", + "status.contains_quote": "인용 포함", + "status.context.loading": "더 많은 답글 불러오는 중", + "status.context.loading_error": "새 답글을 불러올 수 없습니다", + "status.context.loading_success": "새 답글을 불러왔습니다", + "status.context.more_replies_found": "답글을 더 찾았습니다", + "status.context.retry": "재시도", + "status.context.show": "보기", "status.continued_thread": "이어지는 글타래", "status.copy": "게시물 링크 복사", "status.delete": "삭제", "status.delete.success": "게시물 삭제됨", "status.detailed_status": "대화 자세히 보기", - "status.direct": "@{name} 님에게 개인적으로 멘션", - "status.direct_indicator": "개인적인 멘션", + "status.direct": "@{name} 님에게 개인 멘션", + "status.direct_indicator": "개인 멘션", "status.edit": "수정", "status.edited": "{date}에 마지막으로 편집됨", "status.edited_x_times": "{count, plural, other {{count}}} 번 수정됨", "status.embed": "임베드 코드 받기", "status.favourite": "좋아요", - "status.favourites": "{count, plural, other {좋아요}}", + "status.favourites_count": "{count, plural, other {{counter}}} 마음에 들어함", "status.filter": "이 게시물을 필터", "status.history.created": "{name} 님이 {date}에 게시함", "status.history.edited": "{name} 님이 {date}에 수정함", @@ -891,26 +931,37 @@ "status.pin": "고정", "status.quote": "인용", "status.quote.cancel": "인용 취소", + "status.quote_error.blocked_account_hint.title": "@{name} 님을 차단했기 때문에 이 게시물은 숨겨졌습니다.", + "status.quote_error.blocked_domain_hint.title": "{domain}을 차단했기 때문에 이 게시물은 숨겨졌습니다.", "status.quote_error.filtered": "필터에 의해 가려짐", + "status.quote_error.limited_account_hint.action": "그냥 보기", + "status.quote_error.limited_account_hint.title": "이 계정은 {domain}의 중재자에 의해 숨겨진 상태입니다.", + "status.quote_error.muted_account_hint.title": "@{name}을 뮤트했기 때문에 이 게시물은 숨겨졌습니다.", "status.quote_error.not_available": "게시물 사용 불가", "status.quote_error.pending_approval": "게시물 대기중", - "status.quote_followers_only": "팔로워만 이 게시물을 인용할 수 있습니다", + "status.quote_error.pending_approval_popout.body": "Mastodon에서는 타인이 인용할 수 있는지 여부를 제어할 수 있습니다. 이 게시물은 원저자의 승인을 얻을 때까지 계류됩니다.", + "status.quote_error.revoked": "원작성자에 의해 게시물 삭제됨", + "status.quote_followers_only": "팔로워만 인용할 수 있는 게시물", "status.quote_manual_review": "작성자가 직접 검토합니다", + "status.quote_noun": "인용", "status.quote_policy_change": "누가 인용할 수 있는지", "status.quote_post_author": "인용된 @{name} 님의 게시물", "status.quote_private": "비공개 게시물은 인용할 수 없습니다", - "status.quotes": "{count, plural, other {#}} 인용", "status.quotes.empty": "아직 아무도 이 게시물을 인용하지 않았습니다. 누군가 인용한다면 여기에 표시됩니다.", + "status.quotes.local_other_disclaimer": "원작자가 거부한 인용은 표시되지 않습니다.", + "status.quotes.remote_other_disclaimer": "{domain}의 인용만 여기에 확정적으로 보여집니다. 원작자가 거부한 인용은 보여지지 않습니다.", + "status.quotes_count": "{count, plural, other {{counter}}} 인용", "status.read_more": "더 보기", "status.reblog": "부스트", "status.reblog_or_quote": "부스트 또는 인용", "status.reblog_private": "팔로워들에게 다시 공유", "status.reblogged_by": "{name} 님이 부스트했습니다", - "status.reblogs": "{count, plural, other {부스트}}", "status.reblogs.empty": "아직 아무도 이 게시물을 부스트하지 않았습니다. 부스트 한 사람들이 여기에 표시 됩니다.", + "status.reblogs_count": "{count, plural, other {{counter}}} 부스트", "status.redraft": "지우고 다시 쓰기", "status.remove_bookmark": "북마크 삭제", "status.remove_favourite": "즐겨찾기에서 제거", + "status.remove_quote": "삭제", "status.replied_in_thread": "글타래에 답장", "status.replied_to": "{name} 님에게", "status.reply": "답장", @@ -981,7 +1032,12 @@ "video.volume_up": "음량 증가", "visibility_modal.button_title": "공개범위 설정", "visibility_modal.header": "공개범위와 반응", - "visibility_modal.helper.unlisted_quoting": "사람들이 나를 인용한 경우 그 게시물 또한 유행에서 제외됩니다.", + "visibility_modal.helper.direct_quoting": "마스토돈에서 작성된 개인적인 멘션은 남들이 인용할 수 없습니다.", + "visibility_modal.helper.privacy_editing": "공개범위는 게시한 다음 수정할 수 없습니다.", + "visibility_modal.helper.privacy_private_self_quote": "자신의 비공개 게시물을 공개 게시물로 인용할 수 없습니다.", + "visibility_modal.helper.private_quoting": "마스토돈에서 작성된 팔로워 전용 게시물은 다른 사용자가 인용할 수 없습니다.", + "visibility_modal.helper.unlisted_quoting": "사람들에게 인용된 경우, 인용한 게시물도 유행 타임라인에서 감추게 됩니다.", + "visibility_modal.instructions": "누가 이 게시물과 상호작용할 수 있는 지 제어합니다. 또한 환경설정 > 게시물 기본설정으로 이동해 향후 모든 게시물의 설정을 적용할 수 있습니다.", "visibility_modal.privacy_label": "공개 범위", "visibility_modal.quote_followers": "팔로워만", "visibility_modal.quote_label": "인용할 수 있는 사람", diff --git a/app/javascript/mastodon/locales/ku.json b/app/javascript/mastodon/locales/ku.json index 3518959e2b4c0f..778ff2684a5635 100644 --- a/app/javascript/mastodon/locales/ku.json +++ b/app/javascript/mastodon/locales/ku.json @@ -1,6 +1,7 @@ { "about.blocks": "Rajekarên çavdêrkirî", "about.contact": "Têkilî:", + "about.default_locale": "Berdest", "about.disclaimer": "Mastodon belaş e, nermalaveke çavkaniya vekirî ye û markeyeke Mastodon gGmbHê ye.", "about.domain_blocks.no_reason_available": "Sedem ne berdest e", "about.domain_blocks.preamble": "Mastodon bi gelemperî dihêle ku tu naverokê bibînî û bi bikarhênerên ji rajekareke din a li fendiverse re têkilî dayne. Ev awaretyên ku li ser vê rajekara taybetî hatine çêkirin ev in.", @@ -8,6 +9,7 @@ "about.domain_blocks.silenced.title": "Sînorkirî", "about.domain_blocks.suspended.explanation": "Dê tu daneya ji van rajekaran neyê berhev kirin, tomarkirin an jî guhertin, ku têkilî an danûstendinek bi bikarhênerên van rajekaran re tune dike.", "about.domain_blocks.suspended.title": "Hatiye rawestandin", + "about.language_label": "Ziman", "about.not_available": "Ev zanyarî li ser vê rajekarê nehatine peydakirin.", "about.powered_by": "Medyaya civakî ya nenavendî bi hêzdariya {mastodon}", "about.rules": "Rêbazên rajekar", @@ -19,17 +21,25 @@ "account.block_domain": "Navpera {domain} asteng bike", "account.block_short": "Asteng bike", "account.blocked": "Astengkirî", + "account.blocking": "Astengkirin", "account.cancel_follow_request": "Daxwaza şopandinê vekişîne", "account.copy": "Girêdanê bo profîlê jê bigire", "account.direct": "Bi taybetî qale @{name} bike", "account.disable_notifications": "Êdî min agahdar neke gava @{name} diweşîne", "account.edit_profile": "Profîlê serrast bike", + "account.edit_profile_short": "Serrast bike", "account.enable_notifications": "Min agahdar bike gava @{name} diweşîne", "account.endorse": "Taybetiyên li ser profîl", + "account.featured.accounts": "Profîl", + "account.featured.hashtags": "Hashtag", "account.featured_tags.last_status_at": "Şandiya dawî di {date} de", "account.featured_tags.last_status_never": "Şandî tune ne", "account.follow": "Bişopîne", "account.follow_back": "Bişopîne", + "account.follow_request": "Bo şopandinê daxwaz bike", + "account.follow_request_cancel": "Daxwazê têk bibe", + "account.follow_request_cancel_short": "Têk bibe", + "account.follow_request_short": "Daxwaz bike", "account.followers": "Şopîner", "account.followers.empty": "Kesekî hin ev bikarhêner neşopandiye.", "account.followers_counter": "{count, plural, one {{counter} şopîner} other {{counter} şopîner}}", @@ -58,7 +68,6 @@ "account.posts": "Şandî", "account.posts_with_replies": "Şandî û bersiv", "account.report": "@{name} ragihîne", - "account.requested": "Li benda erêkirinê ye. Ji bo betal kirina daxwazê pêl bikin", "account.requested_follow": "{name} dixwaze te bişopîne", "account.share": "Profîla @{name} parve bike", "account.show_reblogs": "Bilindkirinên ji @{name} nîşan bike", @@ -86,9 +95,6 @@ "alt_text_modal.change_thumbnail": "Wêneyê biçûk biguherîne", "alt_text_modal.done": "Qediya", "announcement.announcement": "Daxuyanî", - "annual_report.summary.followers.followers": "şopîner", - "annual_report.summary.followers.total": "{count} tevahî", - "annual_report.summary.most_used_hashtag.none": "Ne yek", "annual_report.summary.new_posts.new_posts": "şandiyên nû", "attachments_list.unprocessed": "(bêpêvajo)", "audio.hide": "Dengê veşêre", @@ -170,7 +176,6 @@ "confirmations.redraft.confirm": "Jê bibe & ji nû ve serrast bike", "confirmations.redraft.message": "Bi rastî tu dixwazî şandî ye jê bibî û ji nû ve reşnivîsek çê bikî? Bijarte û şandî wê wenda bibin û bersivên ji bo şandiyê resen wê sêwî bimînin.", "confirmations.unfollow.confirm": "Neşopîne", - "confirmations.unfollow.message": "Ma tu dixwazî ku dev ji şopa {name} berdî?", "content_warning.show_more": "Bêtir nîşan bide", "conversation.delete": "Axaftinê jê bibe", "conversation.mark_as_read": "Wekî xwendî nîşan bide", @@ -215,7 +220,7 @@ "empty_column.explore_statuses": "Tiştek niha di rojevê de tune. Paşê vegere!", "empty_column.follow_requests": "Hê jî daxwaza şopandinê tunne ye. Dema daxwazek hat, yê li vir were nîşan kirin.", "empty_column.hashtag": "Di vê hashtagê de hêj tiştekî tune.", - "empty_column.home": "Rojeva demnameya te vala ye! Ji bona tijîkirinê bêtir mirovan bişopîne. {suggestions}", + "empty_column.home": "Rojeva demnameya te vala ye! Bo tijîkirina wê bêtir mirovan bişopîne.", "empty_column.list": "Di vê rêzokê de hîn tiştek tune ye. Gava ku endamên vê rêzokê peyamên nû biweşînin, ew ê li vir xuya bibin.", "empty_column.mutes": "Te tu bikarhêner bêdeng nekiriye.", "empty_column.notifications": "Hêj hişyariyên te tunene. Dema ku mirovên din bi we re têkilî danîn, hûn ê wê li vir bibînin.", @@ -253,6 +258,7 @@ "follow_requests.unlocked_explanation": "Tevlî ku ajimêra te ne kilît kiriye, karmendên {domain} digotin qey tu dixwazî ku pêşdîtina daxwazên şopandinê bi destan bike.", "follow_suggestions.view_all": "Tevahiyan nîşan bide", "footer.about": "Derbar", + "footer.about_this_server": "Derbar", "footer.directory": "Pelrêça profîlan", "footer.get_app": "Bernameyê bistîne", "footer.keyboard_shortcuts": "Kurteriyên klavyeyê", diff --git a/app/javascript/mastodon/locales/kw.json b/app/javascript/mastodon/locales/kw.json index b1116a7a317c81..d21d75c182b1f2 100644 --- a/app/javascript/mastodon/locales/kw.json +++ b/app/javascript/mastodon/locales/kw.json @@ -25,7 +25,6 @@ "account.posts": "Postow", "account.posts_with_replies": "Postow ha gorthebow", "account.report": "Reportya @{name}", - "account.requested": "Ow kortos komendyans. Klyckyewgh dhe hedhi govyn holya", "account.share": "Kevrenna profil @{name}", "account.show_reblogs": "Diskwedhes kenerthow a @{name}", "account.unblock": "Anlettya @{name}", @@ -87,7 +86,6 @@ "confirmations.mute.confirm": "Tawhe", "confirmations.redraft.confirm": "Dilea & daskynskrifa", "confirmations.unfollow.confirm": "Anholya", - "confirmations.unfollow.message": "Owgh hwi sur a vynnes anholya {name}?", "conversation.delete": "Dilea kesklapp", "conversation.mark_as_read": "Merkya vel redys", "conversation.open": "Gweles kesklapp", diff --git a/app/javascript/mastodon/locales/la.json b/app/javascript/mastodon/locales/la.json index 720e940996f9cf..a3b416a1cede41 100644 --- a/app/javascript/mastodon/locales/la.json +++ b/app/javascript/mastodon/locales/la.json @@ -189,7 +189,7 @@ "notification.update": "{name} nuntium correxit", "notification_requests.accept": "Accipe", "notification_requests.confirm_accept_multiple.message": "Tu es accepturus {count, plural, one {una notitia petitionem} other {# notitia petitiones}}. Certus esne procedere vis?", - "notification_requests.confirm_dismiss_multiple.message": "Tu {count, plural, one {unam petitionem notificationis} other {# petitiones notificationum}} abrogāre prōximum es. {count, plural, one {Illa} other {Eae}} facile accessū nōn erit. Certus es tē procedere velle?", + "notification_requests.confirm_dismiss_multiple.message": "Tu {count, plural, one {unam petitionem notificationis} other {# petitiones notificationum}} abrogāre prōximum es. {count, plural, one {it} other {Eae}} facile accessū nōn erit. Certus es tē procedere velle?", "notifications.filter.all": "Omnia", "notifications.filter.polls": "Eventus electionis", "notifications.group": "{count} Notificātiōnēs", @@ -243,24 +243,105 @@ "status.delete": "Oblitterare", "status.edit": "Recolere", "status.edited_x_times": "Emendatum est {count, plural, one {{count} tempus} other {{count} tempora}}", - "status.favourites": "{count, plural, one {favoritum} other {favorita}}", "status.history.created": "{name} creatum {date}", "status.history.edited": "{name} correxit {date}", "status.open": "Expand this status", + "status.quotes.empty": "Nemo hanc commentationem adhuc citavit. Cum quis citaverit, hic apparebit.", + "status.quotes.local_other_disclaimer": "Citationes ab auctore reiec­tæ non monstrabuntur.", + "status.quotes.remote_other_disclaimer": "Tantum citae ex {domain} hic exhiberi praestantur. Citae ab auctore reiectae non exhibebuntur.", + "status.quotes_count": "{count, plural, one {{counter} citatio} other {{counter} citationes}}", + "status.read_more": "Plura lege", + "status.reblog": "Promovere", + "status.reblog_or_quote": "Promovere aut cita", + "status.reblog_private": "Iterum cum sectatoribus tuis communica", "status.reblogged_by": "{name} adiuvavit", - "status.reblogs": "{count, plural, one {auctus} other {auctūs}}", + "status.reblogs.empty": "Nemo hanc publicationem adhuc promovit. Cum quis eam promoveat, hic apparebunt.", + "status.reblogs_count": "{count, plural, one {{counter} incrementum} other {{counter} incrementa}}", + "status.redraft": "Dele et redig", + "status.remove_bookmark": "Tolle signum", + "status.remove_favourite": "Tolle ad delectis", + "status.remove_quote": "Tolle", + "status.replied_in_thread": "In filo responsum", + "status.replied_to": "{name} respondit", + "status.reply": "Respondere", + "status.replyAll": "Responde ad filum", + "status.report": "Referre @{name}", + "status.request_quote": "Pretium petere", + "status.revoke_quote": "Tolle nuntium meum ex nuntio @{name}", + "status.sensitive_warning": "Materia delicata", + "status.share": "Communica", + "status.show_less_all": "Omnibus minus monstra", + "status.show_more_all": "Omnibus plura monstra", + "status.show_original": "Monstra originalem", "status.title.with_attachments": "{user} publicavit {attachmentCount, plural, one {unum annexum} other {{attachmentCount} annexa}}", + "status.translate": "Converte", + "status.translated_from_with": "Translatum ex {lang} per {provider}", + "status.uncached_media_warning": "Praevisum non praesto est", + "status.unmute_conversation": "Conversationem reserare", + "subscribed_languages.lead": "Tantum epistolae in linguis selectis in domo tua apparebunt et indices temporum post mutationem. Neminem eligatis qui epistolas in omnibus linguis recipiat.", + "subscribed_languages.save": "Servare mutationes", + "subscribed_languages.target": "Muta linguas subscriptas pro {target}", "tabs_bar.home": "Domi", + "tabs_bar.menu": "Elenchus", + "tabs_bar.notifications": "Acta Vicimediorum", + "tabs_bar.publish": "Nova publicatio", + "tabs_bar.search": "Quaere", + "terms_of_service.effective_as_of": "Valet ex {date}", + "terms_of_service.title": "Termini servitii", + "terms_of_service.upcoming_changes_on": "Mutationes venturae die {date}", "time_remaining.days": "{number, plural, one {# die} other {# dies}} restant", "time_remaining.hours": "{number, plural, one {# hora} other {# horae}} restant", "time_remaining.minutes": "{number, plural, one {# minutum} other {# minuta}} restant", + "time_remaining.moments": "Momenta reliqua", "time_remaining.seconds": "{number, plural, one {# secundum} other {# secunda}} restant", - "trends.counter_by_accounts": "{count, plural, one {{counter} persōna} other {{counter} persōnae}} in {days, plural, one {diē prīdiē} other {diēbus praeteritīs {days}}}", + "trends.counter_by_accounts": "{count, plural, one {{counter} persōna} other {{counter} persōnae}} in {days, plural, one {days} other {diēbus praeteritīs {days}}}", + "trends.trending_now": "Nunc in usu", "ui.beforeunload": "Si Mastodon discesseris, tua epitome peribit.", "units.short.billion": "{count} millia milionum", "units.short.million": "{count} milionum", "units.short.thousand": "{count} millia", - "upload_button.label": "Imaginēs, vīdeō aut fīle audītūs adde", + "upload_area.title": "Trahe et depone ad imponendum", + "upload_button.label": "Adde imagines, pelliculam, aut fasciculum sonorum.", + "upload_error.limit": "Limes onerationis superatus est.", + "upload_error.poll": "Nullis suffragiis licet fascicula imponere.", + "upload_error.quote": "Nullum oneramentum fasciculi cum citationibus permittitur.", + "upload_form.drag_and_drop.instructions": "Ad annexum mediorum tollendum, preme clavem \"Space\" aut \"Enter\". Dum traheis, utere clavibus sagittariis ad annexum mediorum in quamlibet partem movendum. Preme iterum \"Space\" aut \"Enter\" ad annexum mediorum in novo loco deponendum, aut preme \"Escape\" ad desinendum.", + "upload_form.drag_and_drop.on_drag_cancel": "Tractatio revocata est. Adiunctum medium {item} demissum est.", + "upload_form.drag_and_drop.on_drag_end": "Adhaesum medium {item} demissum est.", + "upload_form.drag_and_drop.on_drag_over": "Adhaesum medium {item} motum est.", + "upload_form.drag_and_drop.on_drag_start": "Adhaesum medium {item} sublatum est.", "upload_form.edit": "Recolere", - "upload_progress.label": "Uploading…" + "upload_progress.label": "Oneratur...", + "upload_progress.processing": "Processus…", + "username.taken": "Illud nomen usoris occupatum est. Aliud tenta.", + "video.close": "Pelliculam claude", + "video.download": "Prehendere fasciculus", + "video.exit_fullscreen": "Exitus ex plenum monitorium", + "video.expand": "Expande pelliculam", + "video.fullscreen": "Plenum monitorium", + "video.hide": "celare pellicula", + "video.mute": "Mutus", + "video.pause": "intermittere", + "video.play": "gignere", + "video.skip_backward": "Redire", + "video.skip_forward": "Progredi", + "video.unmute": "Sordes tollere", + "video.volume_down": "Volumen deminui", + "video.volume_up": "Volumen augete", + "visibility_modal.button_title": "Visibilitatem statuere", + "visibility_modal.direct_quote_warning.text": "Si praesentia configuramenta servaveris, sententia inserta in nexum convertetur.", + "visibility_modal.direct_quote_warning.title": "Citatio in mentitionibus privatis inseriri non possunt.", + "visibility_modal.header": "Visibilitas et interactio", + "visibility_modal.helper.direct_quoting": "Mentiones privatae in Mastodon scriptae ab aliis citari non possunt.", + "visibility_modal.helper.privacy_editing": "Visibilitas mutari non potest postquam nuntius publicatus est.", + "visibility_modal.helper.privacy_private_self_quote": "Citationes propriae nuntiorum privatorum publicari non possunt.", + "visibility_modal.helper.private_quoting": "Nuntii in Mastodon a sequacibus tantum scriptī ab aliis citari non possunt.", + "visibility_modal.helper.unlisted_quoting": "Cum te citant, eorum scriptum etiam ex indicibus popularibus celabitur.", + "visibility_modal.instructions": "Régula quis cum hoc scripto agere possit. Potes etiam ordinationes omnibus scriptis futuris adhibere, navigando ad Praeferentias > Regulas scriptionis praedefinitas.", + "visibility_modal.privacy_label": "Visibilitas", + "visibility_modal.quote_followers": "Sectatores tantum", + "visibility_modal.quote_label": "Quis citare potest", + "visibility_modal.quote_nobody": "Sicut me", + "visibility_modal.quote_public": "quisquis", + "visibility_modal.save": "Servare" } diff --git a/app/javascript/mastodon/locales/lad.json b/app/javascript/mastodon/locales/lad.json index 754f6eb3c6b190..4ba1450029c0df 100644 --- a/app/javascript/mastodon/locales/lad.json +++ b/app/javascript/mastodon/locales/lad.json @@ -28,17 +28,27 @@ "account.disable_notifications": "Desha de avizarme sovre publikasyones de @{name}", "account.domain_blocking": "Blokando el domeno", "account.edit_profile": "Edita profil", + "account.edit_profile_short": "Edita", "account.enable_notifications": "Avizame kuando @{name} publike", "account.endorse": "Avalia en profil", + "account.familiar_followers_one": "Segido por {name1}", + "account.familiar_followers_two": "Segido por {name1} i {name2}", + "account.featured": "Avaliado", "account.featured.accounts": "Profiles", "account.featured.hashtags": "Etiketas", "account.featured_tags.last_status_at": "Ultima publikasyon de {date}", "account.featured_tags.last_status_never": "No ay publikasyones", "account.follow": "Sige", "account.follow_back": "Sige tamyen", + "account.follow_back_short": "Sige tambyen", + "account.follow_request": "Solisita segirle", + "account.follow_request_cancel": "Anula solisitud", + "account.follow_request_cancel_short": "Anula", + "account.follow_request_short": "Solisitud", "account.followers": "Suivantes", "account.followers.empty": "Por agora dingun no sige a este utilizador.", "account.followers_counter": "{count, plural, one {{counter} suivante} other {{counter} suivantes}}", + "account.followers_you_know_counter": "{counter} ke koneses", "account.following": "Sigiendo", "account.following_counter": "{count, plural, other {Sigiendo a {counter}}}", "account.follows.empty": "Este utilizador ainda no sige a dingun.", @@ -58,12 +68,12 @@ "account.mute_short": "Silensia", "account.muted": "Silensiado", "account.muting": "Silensyando", + "account.mutual": "Vos sigesh mutualmente", "account.no_bio": "No ay deskripsion.", "account.open_original_page": "Avre pajina orijnala", "account.posts": "Publikasyones", "account.posts_with_replies": "Kon repuestas", "account.report": "Raporta @{name}", - "account.requested": "Asperando achetasion. Klika para anular la solisitud de segimiento", "account.requested_follow": "{name} tiene solisitado segirte", "account.requests_to_follow_you": "Solisita segirte", "account.share": "Partaja el profil de @{name}", @@ -94,22 +104,14 @@ "alert.unexpected.title": "Atyo!", "alt_text_badge.title": "Teksto alternativo", "alt_text_modal.add_alt_text": "Adjusta teksto alternativo", + "alt_text_modal.add_text_from_image": "Adjusta teksto de imaje", "alt_text_modal.cancel": "Anula", "alt_text_modal.change_thumbnail": "Troka minyatura", "alt_text_modal.done": "Fecho", "announcement.announcement": "Pregon", - "annual_report.summary.archetype.pollster": "El anketero", - "annual_report.summary.followers.followers": "suivantes", - "annual_report.summary.followers.total": "{count} en total", - "annual_report.summary.highlighted_post.by_favourites": "la puvlikasyon mas favoritada", - "annual_report.summary.highlighted_post.by_reblogs": "la puvlikasyon mas repartajada", - "annual_report.summary.highlighted_post.by_replies": "la puvlikasyon kon mas repuestas", - "annual_report.summary.highlighted_post.possessive": "de {name}", "annual_report.summary.most_used_app.most_used_app": "la aplikasyon mas uzada", "annual_report.summary.most_used_hashtag.most_used_hashtag": "la etiketa mas uzada", - "annual_report.summary.most_used_hashtag.none": "Dinguno", "annual_report.summary.new_posts.new_posts": "puvlikasyones muevas", - "annual_report.summary.thanks": "Mersi por ser parte de Mastodon!", "attachments_list.unprocessed": "(no prosesado)", "audio.hide": "Eskonde audio", "block_modal.show_less": "Amostra manko", @@ -149,6 +151,7 @@ "column.edit_list": "Edita lista", "column.favourites": "Te plazen", "column.firehose": "Linyas en bivo", + "column.firehose_singular": "Linya en bivo", "column.follow_requests": "Solisitudes de segimiento", "column.home": "Linya prinsipala", "column.lists": "Listas", @@ -207,16 +210,22 @@ "confirmations.logout.message": "Estas siguro ke keres salir de tu kuento?", "confirmations.logout.title": "Salir?", "confirmations.missing_alt_text.confirm": "Adjusta teksto alternativo", + "confirmations.missing_alt_text.secondary": "Puvlika de todos modos", "confirmations.missing_alt_text.title": "Adjustar teksto alternativo?", "confirmations.mute.confirm": "Silensia", + "confirmations.quiet_post_quote_info.got_it": "Entyendo", "confirmations.redraft.confirm": "Efasa i reeskrive", "confirmations.redraft.message": "Estas siguro ke keres efasar esta publikasyon i reeskrivirla? Pedreras todos los favoritos i repartajasyones asosiados kon esta publikasyon i repuestas a eya seran guerfanadas.", "confirmations.redraft.title": "Efasar i reeskrivir?", + "confirmations.remove_from_followers.confirm": "Kita suivante", + "confirmations.remove_from_followers.title": "Kitar suivante?", "confirmations.revoke_quote.confirm": "Kita puvlikasyon", "confirmations.revoke_quote.title": "Kitar puvlikasyon?", + "confirmations.unblock.confirm": "Dezbloka", + "confirmations.unblock.title": "Dezblokar a @{name}?", "confirmations.unfollow.confirm": "Desige", - "confirmations.unfollow.message": "Estas siguro ke keres deshar de segir a {name}?", - "confirmations.unfollow.title": "Desige utilizador?", + "confirmations.unfollow.title": "Desegir a @{name}?", + "confirmations.withdraw_request.confirm": "Anula solisitud", "content_warning.hide": "Eskonde puvlikasyon", "content_warning.show": "Amostra entanto", "content_warning.show_more": "Amostra mas", @@ -248,6 +257,7 @@ "domain_pill.username": "Nombre de utilizador", "domain_pill.whats_in_a_handle": "En ke konsiste el alias?", "domain_pill.your_handle": "Tu alias:", + "dropdown.empty": "Eskoje una opsyon", "embed.instructions": "Enkrusta esta publikasyon en tu sitio internetiko kopiando este kodiche.", "embed.preview": "Paresera ansina:", "emoji_button.activity": "Aktivita", @@ -296,9 +306,6 @@ "explore.trending_links": "Haberes", "explore.trending_statuses": "Publikasyones", "explore.trending_tags": "Etiketas", - "featured_carousel.next": "Sigiente", - "featured_carousel.post": "Puvlikasyon", - "featured_carousel.previous": "Anterior", "filter_modal.added.context_mismatch_explanation": "Esta kategoria del filtro no se aplika al konteksto en ke tienes aksesido esta publikasyon. Si keres ke la publikasyon sea filtrada en este konteksto tamyen, kale editar el filtro.", "filter_modal.added.context_mismatch_title": "El konteksto no koensida!", "filter_modal.added.expired_explanation": "Esta kategoria de filtros tiene kadukado. Kale ke trokar la data de kadukasion para aplikarla.", @@ -339,6 +346,7 @@ "follow_suggestions.who_to_follow": "A ken segir", "followed_tags": "Etiketas segidas", "footer.about": "Sovre mozotros", + "footer.about_this_server": "Sovre mozotros", "footer.directory": "Katalogo de profiles", "footer.get_app": "Abasha aplikasyon", "footer.keyboard_shortcuts": "Akortamientos de klaviatura", @@ -373,6 +381,7 @@ "hints.profiles.see_more_followers": "Ve mas suivantes en {domain}", "hints.profiles.see_more_follows": "Ve mas segidos en {domain}", "hints.profiles.see_more_posts": "Ve mas puvlikasyones en {domain}", + "home.column_settings.show_quotes": "Muestra sitas", "home.column_settings.show_reblogs": "Amostra repartajasyones", "home.column_settings.show_replies": "Amostra repuestas", "home.hide_announcements": "Eskonde pregones", @@ -387,8 +396,11 @@ "ignore_notifications_modal.not_following_title": "Inyorar avizos de personas a las kualas no siges?", "ignore_notifications_modal.private_mentions_title": "Ignorar avizos de mensyones privadas no solisitadas?", "info_button.label": "Ayuda", + "interaction_modal.go": "Va", + "interaction_modal.no_account_yet": "Ainda no tienes kuento?", "interaction_modal.on_another_server": "En otro sirvidor", "interaction_modal.on_this_server": "En este sirvidor", + "interaction_modal.title": "Konektate para kontinuar", "interaction_modal.username_prompt": "Por enshemplo {example}", "intervals.full.days": "{number, plural, one {# diya} other {# diyas}}", "intervals.full.hours": "{number, plural, one {# ora} other {# oras}}", @@ -417,6 +429,7 @@ "keyboard_shortcuts.open_media": "Avre multimedia", "keyboard_shortcuts.pinned": "Avre lista de publikasyones fiksadas", "keyboard_shortcuts.profile": "Avre profil del autor", + "keyboard_shortcuts.quote": "Sita puvlikasyon", "keyboard_shortcuts.reply": "Arisponde a publikasyon", "keyboard_shortcuts.requests": "Avre lista de solisitudes de suivantes", "keyboard_shortcuts.search": "Enfoka en la vara de bushkeda", @@ -425,8 +438,10 @@ "keyboard_shortcuts.toggle_hidden": "Amostra/eskonde teksto detras de avertensya de kontenido (CW)", "keyboard_shortcuts.toggle_sensitivity": "Amostra/eskonde multimedia", "keyboard_shortcuts.toot": "Eskrive mueva publikasyon", + "keyboard_shortcuts.translate": "para trezladar una puvlikasyon", "keyboard_shortcuts.unfocus": "No enfoka en el area de eskrivir/bushkeda", "keyboard_shortcuts.up": "Move verso arriva en la lista", + "learn_more_link.got_it": "Entyendo", "learn_more_link.learn_more": "Ambezate mas", "lightbox.close": "Serra", "lightbox.next": "Sigiente", @@ -443,8 +458,13 @@ "lists.delete": "Efasa lista", "lists.done": "Fecho", "lists.edit": "Edita lista", + "lists.find_users_to_add": "Bushka utilizadores para adjustar", "lists.list_name": "Nombre de lista", "lists.new_list_name": "Nombre de mueva lista", + "lists.no_lists_yet": "Ainda no ay listas.", + "lists.no_members_yet": "Ainda no ay myembros.", + "lists.no_results_found": "No se toparon rezultados.", + "lists.remove_member": "Kita", "lists.replies_policy.followed": "Kualseker utilizador segido", "lists.replies_policy.list": "Miembros de la lista", "lists.replies_policy.none": "Dinguno", @@ -464,6 +484,7 @@ "navigation_bar.about": "Sovre mozotros", "navigation_bar.administration": "Administrasyon", "navigation_bar.advanced_interface": "Avre en la enterfaz avanzada", + "navigation_bar.automated_deletion": "Efasasyon otomatika de publikasyones", "navigation_bar.blocks": "Utilizadores blokados", "navigation_bar.bookmarks": "Markadores", "navigation_bar.direct": "Enmentaduras privadas", @@ -483,6 +504,8 @@ "navigation_bar.preferences": "Preferensyas", "navigation_bar.privacy_and_reach": "Privasita i alkanse", "navigation_bar.search": "Bushka", + "navigation_bar.search_trends": "Bushka / Trendes", + "navigation_panel.expand_lists": "Espande menu de lista", "not_signed_in_indicator.not_signed_in": "Nesesitas konektarse kon tu kuento para akseder este rekurso.", "notification.admin.report": "{name} raporto {target}", "notification.admin.report_statuses": "{name} raporto {target} por {category}", @@ -495,6 +518,7 @@ "notification.label.mention": "Enmenta", "notification.label.private_mention": "Enmentadura privada", "notification.label.private_reply": "Repuesta privada", + "notification.label.quote": "{name} sito tu publikasyon", "notification.label.reply": "Arisponde", "notification.mention": "Enmenta", "notification.mentioned_you": "{name} te enmento", @@ -534,6 +558,7 @@ "notifications.column_settings.mention": "Enmentaduras:", "notifications.column_settings.poll": "Rizultados de anketas:", "notifications.column_settings.push": "Avizos arrepushados", + "notifications.column_settings.quote": "Sitas:", "notifications.column_settings.reblog": "Repartajasyones:", "notifications.column_settings.show": "Amostra en kolumna", "notifications.column_settings.sound": "Reproduse son", @@ -573,6 +598,7 @@ "onboarding.follows.done": "Fecho", "onboarding.follows.empty": "Malorozamente, no se pueden amostrar rezultados en este momento. Puedes aprovar uzar la bushkeda o navigar por la pajina de eksplorasyon para topar personas a las que segir, o aprovarlo de muevo mas tadre.", "onboarding.follows.search": "Bushka", + "onboarding.follows.title": "Sige personas para ampezar", "onboarding.profile.discoverable": "Faz ke mi profil apareska en bushkedas", "onboarding.profile.discoverable_hint": "Kuando permites ke tu profil sea diskuvriravle en Mastodon, tus publikasyones podran apareser en rezultados de bushkedas i trendes i tu profil podra ser sujerido a personas kon intereses similares a los tuyos.", "onboarding.profile.display_name": "Nombre amostrado", @@ -609,6 +635,7 @@ "privacy_policy.title": "Politika de privasita", "recommended": "Rekomendado", "refresh": "Arefreska", + "regeneration_indicator.please_stand_by": "Aspera por favor.", "relative_time.days": "{number} d", "relative_time.full.days": "antes {number, plural, one {# diya} other {# diyas}}", "relative_time.full.hours": "antes {number, plural, one {# ora} other {# oras}}", @@ -620,6 +647,7 @@ "relative_time.minutes": "{number} m", "relative_time.seconds": "{number} s", "relative_time.today": "oy", + "remove_quote_hint.button_label": "Entyendo", "reply_indicator.attachments": "{count, plural, one {# anekso} other {# aneksos}}", "reply_indicator.cancel": "Anula", "reply_indicator.poll": "Anketa", @@ -710,14 +738,24 @@ "status.bookmark": "Marka", "status.cancel_reblog_private": "No repartaja", "status.cannot_reblog": "Esta publikasyon no se puede repartajar", + "status.contains_quote": "Kontriene sita", + "status.context.loading": "Kargando mas repuestas", + "status.context.loading_error": "No se pudieron kargar repuestas muevas", + "status.context.loading_success": "Muevas repuestas kargadas", + "status.context.more_replies_found": "Se toparon mas repuestas", + "status.context.retry": "Reprova", + "status.context.show": "Amostra", + "status.continued_thread": "Kontinuasion del filo", "status.copy": "Kopia atadijo de publikasyon", "status.delete": "Efasa", + "status.delete.success": "Puvlikasyon kitada", "status.detailed_status": "Vista de konversasyon detalyada", "status.direct": "Enmenta a @{name} en privado", "status.direct_indicator": "Enmentadura privada", "status.edit": "Edita", "status.edited": "Ultima edisyon: {date}", "status.edited_x_times": "Editado {count, plural, one {{count} vez} other {{count} vezes}}", + "status.embed": "Obtiene kodiche para enkrustar", "status.favourite": "Te plaze", "status.filter": "Filtra esta publikasyon", "status.history.created": "{name} kriyo {date}", @@ -732,18 +770,33 @@ "status.mute_conversation": "Silensia konversasyon", "status.open": "Espande publikasyon", "status.pin": "Fiksa en profil", + "status.quote": "Sita", + "status.quote.cancel": "Anula la sita", + "status.quote_error.limited_account_hint.action": "Amostra entanto", + "status.quote_error.limited_account_hint.title": "Este kuento fue eskondido por los moderadores de {domain}.", + "status.quote_error.not_available": "Puvlikasyon no desponivle", + "status.quote_error.pending_approval": "Puvlikasyon esta asperando", + "status.quote_error.revoked": "Puvlikasyon kitada por el otor", + "status.quote_followers_only": "Solo los suivantes pueden sitar esta puvlikasyon", + "status.quote_noun": "Sita", + "status.quote_policy_change": "Troka ken puede sitar", + "status.quote_post_author": "Sito una puvlikasyon de @{name}", + "status.quote_private": "No se puede sitar puvlikasyones privadas", "status.read_more": "Melda mas", "status.reblog": "Repartaja", + "status.reblog_or_quote": "Repartaja o partaja", "status.reblogged_by": "{name} repartajo", "status.reblogs.empty": "Ainda nadie tiene repartajado esta publikasyon. Kuando algien lo aga, se amostrara aki.", "status.redraft": "Efasa i eskrive de muevo", "status.remove_bookmark": "Kita markador", "status.remove_favourite": "Kita de los favoritos", + "status.remove_quote": "Kita", "status.replied_in_thread": "Arispondo en filo", "status.replied_to": "Arispondio a {name}", "status.reply": "Arisponde", "status.replyAll": "Arisponde al filo", "status.report": "Raporta @{name}", + "status.request_quote": "Solisita sitasyon", "status.sensitive_warning": "Kontenido sensivle", "status.share": "Partaja", "status.show_less_all": "Amostra manko para todo", @@ -793,7 +846,12 @@ "video.pause": "Pauza", "video.play": "Reproduze", "video.unmute": "Desilensia", + "visibility_modal.button_title": "Konfigura la vizibilita", + "visibility_modal.header": "Vizibilita i enteraksyon", "visibility_modal.privacy_label": "Vizivilita", "visibility_modal.quote_followers": "Solo suivantes", + "visibility_modal.quote_label": "Ken puede sitar", + "visibility_modal.quote_nobody": "Solo yo", + "visibility_modal.quote_public": "Todos", "visibility_modal.save": "Guadra" } diff --git a/app/javascript/mastodon/locales/lt.json b/app/javascript/mastodon/locales/lt.json index 6276bb80383152..f6da5eb6326b16 100644 --- a/app/javascript/mastodon/locales/lt.json +++ b/app/javascript/mastodon/locales/lt.json @@ -1,6 +1,7 @@ { "about.blocks": "Prižiūrimi serveriai", "about.contact": "Kontaktai:", + "about.default_locale": "Numatyta", "about.disclaimer": "„Mastodon“ – tai nemokama atvirojo kodo programinė įranga ir „Mastodon gGmbH“ prekės ženklas.", "about.domain_blocks.no_reason_available": "Priežastis nepateikta", "about.domain_blocks.preamble": "„Mastodon“ paprastai leidžia peržiūrėti turinį ir bendrauti su naudotojais iš bet kurio kito fediverse esančio serverio. Šios yra išimtys, kurios buvo padarytos šiame konkrečiame serveryje.", @@ -8,11 +9,12 @@ "about.domain_blocks.silenced.title": "Apribota", "about.domain_blocks.suspended.explanation": "Jokie duomenys iš šio serverio nebus apdorojami, saugomi ar keičiami, todėl bet kokia sąveika ar bendravimas su šio serverio naudotojais bus neįmanomas.", "about.domain_blocks.suspended.title": "Pristabdyta", + "about.language_label": "Kalba", "about.not_available": "Ši informacija nebuvo pateikta šiame serveryje.", "about.powered_by": "Decentralizuota socialinė medija, veikianti pagal „{mastodon}“", "about.rules": "Serverio taisyklės", "account.account_note_header": "Asmeninė pastaba", - "account.add_or_remove_from_list": "Pridėti arba pašalinti iš sąrašų", + "account.add_or_remove_from_list": "Įtraukti arba šalinti iš sąrašų", "account.badges.bot": "Automatizuotas", "account.badges.group": "Grupė", "account.block": "Blokuoti @{name}", @@ -26,20 +28,30 @@ "account.disable_notifications": "Nustoti man pranešti, kai @{name} paskelbia", "account.domain_blocking": "Blokuoti domeną", "account.edit_profile": "Redaguoti profilį", + "account.edit_profile_short": "Redaguoti", "account.enable_notifications": "Pranešti man, kai @{name} paskelbia", "account.endorse": "Rodyti profilyje", + "account.familiar_followers_many": "Sekama {name1}, {name2}, ir {othersCount, plural, one {dar vieno} few {# dar keleto} many {# dar kelių} other {# kitų pažystąmų}}", + "account.familiar_followers_one": "Seka {name1}", + "account.familiar_followers_two": "{name1} ir {name2} seka", "account.featured": "Rodomi", "account.featured.accounts": "Profiliai", - "account.featured.hashtags": "Saitažodžiai", + "account.featured.hashtags": "Grotažymės", "account.featured_tags.last_status_at": "Paskutinis įrašas {date}", "account.featured_tags.last_status_never": "Nėra įrašų", "account.follow": "Sekti", "account.follow_back": "Sekti atgal", + "account.follow_back_short": "Sekti atgal", + "account.follow_request": "Prašyti sekti", + "account.follow_request_cancel": "Atšaukti prašymą", + "account.follow_request_cancel_short": "Atšaukti", + "account.follow_request_short": "Prašymas", "account.followers": "Sekėjai", "account.followers.empty": "Šio naudotojo dar niekas neseka.", "account.followers_counter": "{count, plural, one {{counter} sekėjas} few {{counter} sekėjai} many {{counter} sekėjo} other {{counter} sekėjų}}", + "account.followers_you_know_counter": "{counter} žinomas", "account.following": "Sekama", - "account.following_counter": "{count, plural, one {{counter} sekimas} few {{counter} sekimai} many {{counter} sekimo} other {{counter} sekimų}}", + "account.following_counter": "{count, plural, one {{counter} seka} few {{counter} seka} many {{counter} seka} other {{counter} seka}}", "account.follows.empty": "Šis naudotojas dar nieko neseka.", "account.follows_you": "Seka tave", "account.go_to_profile": "Eiti į profilį", @@ -64,7 +76,6 @@ "account.posts_with_replies": "Įrašai ir atsakymai", "account.remove_from_followers": "Šalinti {name} iš sekėjų", "account.report": "Pranešti apie @{name}", - "account.requested": "Laukiama patvirtinimo. Spustelėk, kad atšauktum sekimo prašymą", "account.requested_follow": "{name} paprašė tave sekti", "account.requests_to_follow_you": "Prašymai sekti jus", "account.share": "Bendrinti @{name} profilį", @@ -102,25 +113,16 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Aprašykite tai regos sutrikimų turintiems asmenims…", "alt_text_modal.done": "Atlikta", "announcement.announcement": "Skelbimas", - "annual_report.summary.archetype.booster": "Šaunus medžiotojas", - "annual_report.summary.archetype.lurker": "Stebėtojas", - "annual_report.summary.archetype.oracle": "Vydūnas", - "annual_report.summary.archetype.pollster": "Apklausos rengėjas", - "annual_report.summary.archetype.replier": "Socialinis drugelis", - "annual_report.summary.followers.followers": "sekėjai (-ų)", - "annual_report.summary.followers.total": "iš viso {count}", - "annual_report.summary.here_it_is": "Štai jūsų {year} apžvalga:", - "annual_report.summary.highlighted_post.by_favourites": "labiausiai pamėgtas įrašas", - "annual_report.summary.highlighted_post.by_reblogs": "labiausiai pasidalintas įrašas", - "annual_report.summary.highlighted_post.by_replies": "įrašas su daugiausiai atsakymų", - "annual_report.summary.highlighted_post.possessive": "{name}", + "annual_report.announcement.action_build": "Sukurti mano Wrapstodon", + "annual_report.announcement.action_view": "Peržiūrėti mano Wrapstodon", + "annual_report.announcement.description": "Sužinokite daugiau apie savo aktyvumą Mastodon\"e per pastaruosius metus.", + "annual_report.announcement.title": "Wrapstodon {year} jau čia", "annual_report.summary.most_used_app.most_used_app": "labiausiai naudota programa", - "annual_report.summary.most_used_hashtag.most_used_hashtag": "labiausiai naudotas saitažodis", - "annual_report.summary.most_used_hashtag.none": "Nieko", + "annual_report.summary.most_used_hashtag.most_used_hashtag": "labiausiai naudota grotažymė", "annual_report.summary.new_posts.new_posts": "nauji įrašai", "annual_report.summary.percentile.text": "Tai reiškia, kad esate tarppopuliariausių {domain} naudotojų.", "annual_report.summary.percentile.we_wont_tell_bernie": "Mes nesakysime Bernie.", - "annual_report.summary.thanks": "Dėkojame, kad esate „Mastodon“ dalis!", + "annual_report.summary.share_message": "Aš gavau „{archetype}“!", "attachments_list.unprocessed": "(neapdorotas)", "audio.hide": "Slėpti garsą", "block_modal.remote_users_caveat": "Paprašysime serverio {domain} gerbti tavo sprendimą. Tačiau atitiktis negarantuojama, nes kai kurie serveriai gali skirtingai tvarkyti blokavimus. Vieši įrašai vis tiek gali būti matomi neprisijungusiems naudotojams.", @@ -146,6 +148,8 @@ "bundle_modal_error.close": "Uždaryti", "bundle_modal_error.message": "Įkeliant šį ekraną kažkas nutiko ne taip.", "bundle_modal_error.retry": "Bandyti dar kartą", + "carousel.current": "Skirtukas {current, number} / {max, number}", + "carousel.slide": "Rodoma {current, number} iš {max, number}", "closed_registrations.other_server_instructions": "Kadangi „Mastodon“ yra decentralizuotas, gali susikurti paskyrą kitame serveryje ir vis tiek bendrauti su šiuo serveriu.", "closed_registrations_modal.description": "Sukurti paskyrą serveryje {domain} šiuo metu neįmanoma, bet nepamiršk, kad norint naudotis „Mastodon“ nebūtina turėti paskyrą serveryje {domain}.", "closed_registrations_modal.find_another_server": "Rasti kitą serverį", @@ -162,6 +166,8 @@ "column.edit_list": "Redaguoti sąrašą", "column.favourites": "Mėgstami", "column.firehose": "Tiesioginiai srautai", + "column.firehose_local": "Tiesioginis srautas iš šio serverio", + "column.firehose_singular": "Tiesioginis srautas", "column.follow_requests": "Sekimo prašymai", "column.home": "Pagrindinis", "column.list_members": "Tvarkyti sąrašo narius", @@ -181,6 +187,7 @@ "community.column_settings.local_only": "Tik vietinis", "community.column_settings.media_only": "Tik medija", "community.column_settings.remote_only": "Tik nuotolinis", + "compose.error.blank_post": "Įrašas negali būti tuščias.", "compose.language.change": "Keisti kalbą", "compose.language.search": "Ieškoti kalbų...", "compose.published.body": "Įrašas paskelbtas.", @@ -188,7 +195,7 @@ "compose.saved.body": "Įrašas išsaugotas.", "compose_form.direct_message_warning_learn_more": "Sužinoti daugiau", "compose_form.encryption_warning": "„Mastodon“ įrašai nėra visapusiškai šifruojami. Per „Mastodon“ nesidalyk jokia slapta informacija.", - "compose_form.hashtag_warning": "Šis įrašas nebus įtrauktas į jokį saitažodį, nes ji nėra vieša. Tik viešų įrašų galima ieškoti pagal saitažodį.", + "compose_form.hashtag_warning": "Šis įrašas nebus įtrauktas į jokį grotažodį, nes jis nėra viešas. Tik vieši įrašai gali būti ieškomi pagal grotažymę.", "compose_form.lock_disclaimer": "Tavo paskyra nėra {locked}. Bet kas gali sekti tave ir peržiūrėti tik sekėjams skirtus įrašus.", "compose_form.lock_disclaimer.lock": "užrakinta", "compose_form.placeholder": "Kas tavo mintyse?", @@ -213,6 +220,13 @@ "confirmations.delete_list.confirm": "Ištrinti", "confirmations.delete_list.message": "Ar tikrai nori negrįžtamai ištrinti šį sąrašą?", "confirmations.delete_list.title": "Ištrinti sąrašą?", + "confirmations.discard_draft.confirm": "Išsaugoti ir tęsti", + "confirmations.discard_draft.edit.cancel": "Tęsti redagavimą", + "confirmations.discard_draft.edit.message": "Tęsdami, jūs prarasite visus pakeitimus, kuriuos padarėte šiuo metu redaguojamame įraše.", + "confirmations.discard_draft.edit.title": "Atmesti pakeitimus savo įraše?", + "confirmations.discard_draft.post.cancel": "Tęsti juodraščio redagavimą", + "confirmations.discard_draft.post.message": "Tęsdami ištrinsite šiuo metu kuriamą įrašą.", + "confirmations.discard_draft.post.title": "Atmesti savo įrašo juodraštį?", "confirmations.discard_edit_media.confirm": "Atmesti", "confirmations.discard_edit_media.message": "Turi neišsaugotų medijos aprašymo ar peržiūros pakeitimų. Vis tiek juos atmesti?", "confirmations.follow_to_list.confirm": "Sekti ir pridėti prie sąrašo", @@ -226,15 +240,30 @@ "confirmations.missing_alt_text.secondary": "Siųsti vis tiek", "confirmations.missing_alt_text.title": "Pridėti alternatyvųjį tekstą?", "confirmations.mute.confirm": "Nutildyti", + "confirmations.private_quote_notify.cancel": "Grįžti prie redagavimo", + "confirmations.private_quote_notify.confirm": "Paskelbti įrašą", + "confirmations.private_quote_notify.do_not_show_again": "Neberodyti šio pranešimo dar kartą", + "confirmations.private_quote_notify.message": "Asmuo, kurį paminite, ir kiti paminėti asmenys bus informuoti ir galės peržiūrėti jūsų įrašą, net jei jie neseka jūsų.", + "confirmations.private_quote_notify.title": "Dalytis su sekėjais ir paminėtais vartotojais?", + "confirmations.quiet_post_quote_info.dismiss": "Daugiau man nepriminti", + "confirmations.quiet_post_quote_info.got_it": "Supratau", + "confirmations.quiet_post_quote_info.message": "Kai norite paminėti tylų viešą įrašą, jūsų įrašas bus paslėptas Tendencijų sąrašuose.", + "confirmations.quiet_post_quote_info.title": "Kai paminite tylius viešus įrašus", "confirmations.redraft.confirm": "Ištrinti ir iš naujo parengti", "confirmations.redraft.message": "Ar tikrai nori ištrinti šį įrašą ir parengti jį iš naujo? Bus prarasti mėgstami ir pasidalinimai, o atsakymai į originalų įrašą bus panaikinti.", "confirmations.redraft.title": "Ištrinti ir iš naujo parengti įrašą?", "confirmations.remove_from_followers.confirm": "Šalinti sekėją", "confirmations.remove_from_followers.message": "{name} nustos jus sekti. Ar tikrai norite tęsti?", "confirmations.remove_from_followers.title": "Šalinti sekėją?", + "confirmations.revoke_quote.confirm": "Pašalinti įrašą", + "confirmations.revoke_quote.message": "Šio veiksmo negalima anuliuoti.", + "confirmations.revoke_quote.title": "Pašalinti įrašą?", + "confirmations.unblock.confirm": "Atblokuoti", + "confirmations.unblock.title": "Atblokuoti {name}?", "confirmations.unfollow.confirm": "Nebesekti", - "confirmations.unfollow.message": "Ar tikrai nori nebesekti {name}?", - "confirmations.unfollow.title": "Nebesekti naudotoją?", + "confirmations.unfollow.title": "Nebesekti {name}?", + "confirmations.withdraw_request.confirm": "Atšaukti prašymą", + "confirmations.withdraw_request.title": "Atšaukti prašymą sekti {name}?", "content_warning.hide": "Slėpti įrašą", "content_warning.show": "Rodyti vis tiek", "content_warning.show_more": "Rodyti daugiau", @@ -253,6 +282,7 @@ "disabled_account_banner.text": "Tavo paskyra {disabledAccount} šiuo metu išjungta.", "dismissable_banner.community_timeline": "Tai – naujausi vieši įrašai iš žmonių, kurių paskyros talpinamos {domain}.", "dismissable_banner.dismiss": "Atmesti", + "dismissable_banner.public_timeline": "Tai yra naujausi vieši įrašai iš fediverse, kuriuos seka {domain} vartotojai.", "domain_block_modal.block": "Blokuoti serverį", "domain_block_modal.block_account_instead": "Blokuoti @{name} vietoj to", "domain_block_modal.they_can_interact_with_old_posts": "Žmonės iš šio serverio gali bendrauti su tavo senomis įrašomis.", @@ -275,6 +305,7 @@ "domain_pill.your_handle": "Tavo socialinis medijos vardas:", "domain_pill.your_server": "Tavo skaitmeniniai namai, kuriuose saugomi visi tavo įrašai. Nepatinka šis? Bet kada perkelk serverius ir atsivesk ir savo sekėjus.", "domain_pill.your_username": "Tavo unikalus identifikatorius šiame serveryje. Skirtinguose serveriuose galima rasti naudotojų su tuo pačiu naudotojo vardu.", + "dropdown.empty": "Pasirinkite variantą", "embed.instructions": "Įterpk šį įrašą į savo svetainę nukopijuojant toliau pateiktą kodą.", "embed.preview": "Štai kaip tai atrodys:", "emoji_button.activity": "Veikla", @@ -292,28 +323,32 @@ "emoji_button.search_results": "Paieškos rezultatai", "emoji_button.symbols": "Simboliai", "emoji_button.travel": "Kelionės ir vietos", + "empty_column.account_featured.me": "Jūs dar nieko neparyškinote. Ar žinojote, kad savo profilyje galite parodyti dažniausiai naudojamas grotažymes ir netgi savo draugų paskyras?", + "empty_column.account_featured.other": "{acct} dar nieko neparyškino. Ar žinojote, kad savo profilyje galite pateikti dažniausiai naudojamus grotžymes ir netgi savo draugų paskyras?", "empty_column.account_featured_other.unknown": "Ši paskyra dar nieko neparodė.", "empty_column.account_hides_collections": "Šis (-i) naudotojas (-a) pasirinko nepadaryti šią informaciją prieinamą.", "empty_column.account_suspended": "Paskyra pristabdyta.", "empty_column.account_timeline": "Nėra čia įrašų.", "empty_column.account_unavailable": "Profilis neprieinamas.", "empty_column.blocks": "Dar neužblokavai nė vieno naudotojo.", - "empty_column.bookmarked_statuses": "Dar neturi nė vienos įrašo pridėtos žymės. Kai vieną iš jų pridėsi į žymes, jis bus rodomas čia.", + "empty_column.bookmarked_statuses": "Dar neturi nė vienos įrašo su žyma. Kai vieną žymų pridėsi prie įrašo, jis bus rodomas čia.", "empty_column.community": "Vietinė laiko skalė yra tuščia. Parašyk ką nors viešai, kad pradėtum sąveikauti.", "empty_column.direct": "Dar neturi jokių privačių paminėjimų. Kai išsiųsi arba gausi vieną iš jų, jis bus rodomas čia.", + "empty_column.disabled_feed": "Šis srautas buvo išjungtas jūsų serverio administratorių.", "empty_column.domain_blocks": "Kol kas nėra užblokuotų serverių.", "empty_column.explore_statuses": "Šiuo metu niekas nėra tendencinga. Patikrinkite vėliau!", "empty_column.favourited_statuses": "Dar neturi mėgstamų įrašų. Kai vieną iš jų pamėgsi, jis bus rodomas čia.", "empty_column.favourites": "Šio įrašo dar niekas nepamėgo. Kai kas nors tai padarys, jie bus rodomi čia.", "empty_column.follow_requests": "Dar neturi jokių sekimo prašymų. Kai gausi tokį prašymą, jis bus rodomas čia.", - "empty_column.followed_tags": "Dar neseki jokių saitažodžių. Kai tai padarysi, jie bus rodomi čia.", - "empty_column.hashtag": "Nėra nieko šiame saitažodyje kol kas.", + "empty_column.followed_tags": "Dar neseki jokių grotažymių. Kai tai padarysi, jos bus rodomos čia.", + "empty_column.hashtag": "Šioje gratažymėje kol kas nieko nėra.", "empty_column.home": "Tavo pagrindinio laiko skalė tuščia. Sek daugiau žmonių, kad ją užpildytum.", - "empty_column.list": "Nėra nieko šiame sąraše kol kas. Kai šio sąrašo nariai paskelbs naujų įrašų, jie bus rodomi čia.", + "empty_column.list": "Šiame sąraše dar nieko nėra. Kai šio sąrašo nariai paskelbs naujus įrašus, jie bus rodomi čia.", "empty_column.mutes": "Dar nesi nutildęs (-usi) nė vieno naudotojo.", "empty_column.notification_requests": "Viskas švaru! Čia nieko nėra. Kai gausi naujų pranešimų, jie bus rodomi čia pagal tavo nustatymus.", "empty_column.notifications": "Dar neturi jokių pranešimų. Kai kiti žmonės su tavimi sąveikaus, matysi tai čia.", "empty_column.public": "Čia nieko nėra. Parašyk ką nors viešai arba rankiniu būdu sek naudotojus iš kitų serverių, kad jį užpildytum.", + "error.no_hashtag_feed_access": "Registruokitės arba prisijunkite, kad galėtumėte peržiūrėti ir sekti šį žymę.", "error.unexpected_crash.explanation": "Dėl mūsų kodo riktos arba naršyklės suderinamumo problemos šis puslapis negalėjo būti rodomas teisingai.", "error.unexpected_crash.explanation_addons": "Šį puslapį nepavyko parodyti teisingai. Šią klaidą greičiausiai sukėlė naršyklės priedas arba automatinio vertimo įrankiai.", "error.unexpected_crash.next_steps": "Pabandyk atnaujinti puslapį. Jei tai nepadeda, galbūt vis dar galėsi naudotis Mastodon per kitą naršyklę arba savąją programėlę.", @@ -321,9 +356,13 @@ "errors.unexpected_crash.copy_stacktrace": "Kopijuoti dėklo eigą į iškarpinę", "errors.unexpected_crash.report_issue": "Pranešti apie problemą", "explore.suggested_follows": "Žmonės", + "explore.title": "Populiaru", "explore.trending_links": "Naujienos", "explore.trending_statuses": "Įrašai", - "explore.trending_tags": "Saitažodžiai", + "explore.trending_tags": "Grotažymės", + "featured_carousel.current": "Įrašas {current, number} / {max, number}", + "featured_carousel.header": "{count, plural, one {Iškeltas įrašas} few {Iškelti įrašai} many {Iškeltų įrašų} other {Iškelti įrašai}}", + "featured_carousel.slide": "Įrašas {current, number} iš {max, number}", "filter_modal.added.context_mismatch_explanation": "Ši filtro kategorija netaikoma kontekstui, kuriame peržiūrėjai šį įrašą. Jei nori, kad įrašas būtų filtruojamas ir šiame kontekste, turėsi redaguoti filtrą.", "filter_modal.added.context_mismatch_title": "Konteksto neatitikimas.", "filter_modal.added.expired_explanation": "Ši filtro kategorija nustojo galioti. Kad ji būtų taikoma, turėsi pakeisti galiojimo datą.", @@ -364,8 +403,9 @@ "follow_suggestions.similar_to_recently_followed_longer": "Panašūs į profilius, kuriuos neseniai seki", "follow_suggestions.view_all": "Peržiūrėti viską", "follow_suggestions.who_to_follow": "Ką sekti", - "followed_tags": "Sekami saitažodžiai", + "followed_tags": "Sekamos grotažymės", "footer.about": "Apie", + "footer.about_this_server": "Apie", "footer.directory": "Profilių katalogas", "footer.get_app": "Gauti programėlę", "footer.keyboard_shortcuts": "Spartieji klavišai", @@ -375,21 +415,26 @@ "footer.terms_of_service": "Paslaugų sąlygos", "generic.saved": "Išsaugota", "getting_started.heading": "Kaip pradėti", - "hashtag.admin_moderation": "Atverti prižiūrėjimo sąsają saitažodžiui #{name}", + "hashtag.admin_moderation": "Atverti moderavimo langą #{name}", + "hashtag.browse": "Naršyti įrašus su #{hashtag}", + "hashtag.browse_from_account": "Naršyti @{name} įrašus su grotažyme #{hashtag}", "hashtag.column_header.tag_mode.all": "ir {additional}", "hashtag.column_header.tag_mode.any": "ar {additional}", "hashtag.column_header.tag_mode.none": "be {additional}", "hashtag.column_settings.select.no_options_message": "Pasiūlymų nerasta.", - "hashtag.column_settings.select.placeholder": "Įvesti saitažodžius…", + "hashtag.column_settings.select.placeholder": "Įvesk grotažymes…", "hashtag.column_settings.tag_mode.all": "Visi šie", - "hashtag.column_settings.tag_mode.any": "Bet kuris iš šių", - "hashtag.column_settings.tag_mode.none": "Nė vienas iš šių", - "hashtag.column_settings.tag_toggle": "Įtraukti papildomas šio stulpelio žymes", - "hashtag.counter_by_accounts": "{count, plural, one {{counter} dalyvis} few {{counter} dalyviai} many {{counter} dalyvio} other {{counter} dalyvių}}", + "hashtag.column_settings.tag_mode.any": "Bet kuris šių", + "hashtag.column_settings.tag_mode.none": "Nei vienas šių", + "hashtag.column_settings.tag_toggle": "Įtraukti papildomas žymas į šį stulpelį", + "hashtag.counter_by_accounts": "{count, plural, one {{counter} dalyvis} few {{counter} dalyviai} many {{counter} dalyvių} other {{counter} dalyviai}}", "hashtag.counter_by_uses": "{count, plural, one {{counter} įrašas} few {{counter} įrašai} many {{counter} įrašo} other {{counter} įrašų}}", "hashtag.counter_by_uses_today": "{count, plural, one {{counter} įrašas} few {{counter} įrašai} many {{counter} įrašo} other {{counter} įrašų}} šiandien", - "hashtag.follow": "Sekti saitažodį", - "hashtag.unfollow": "Nebesekti saitažodį", + "hashtag.feature": "Rodyti profilyje", + "hashtag.follow": "Sekti grotažymę", + "hashtag.mute": "Nutildyti žymą #{hashtag}", + "hashtag.unfeature": "Neberodyti profilyje", + "hashtag.unfollow": "Nebesekti grotažymės", "hashtags.and_other": "…ir {count, plural, one {# daugiau} few {# daugiau} many {# daugiau}other {# daugiau}}", "hints.profiles.followers_may_be_missing": "Sekėjai šiai profiliui gali būti nepateikti.", "hints.profiles.follows_may_be_missing": "Sekimai šiai profiliui gali būti nepateikti.", @@ -397,6 +442,7 @@ "hints.profiles.see_more_followers": "Žiūrėti daugiau sekėjų serveryje {domain}", "hints.profiles.see_more_follows": "Žiūrėti daugiau sekimų serveryje {domain}", "hints.profiles.see_more_posts": "Žiūrėti daugiau įrašų serveryje {domain}", + "home.column_settings.show_quotes": "Rodyti paminėjimus", "home.column_settings.show_reblogs": "Rodyti pakėlimus", "home.column_settings.show_replies": "Rodyti atsakymus", "home.hide_announcements": "Slėpti skelbimus", @@ -417,17 +463,19 @@ "ignore_notifications_modal.private_mentions_title": "Ignoruoti pranešimus iš neprašytų privačių paminėjimų?", "info_button.label": "Žinynas", "info_button.what_is_alt_text": "

Kas yra alternatyvusis tekstas?

Alternatyvusis tekstas pateikia vaizdų aprašymus asmenims su regos sutrikimais, turintiems mažo pralaidumo ryšį arba ieškantiems papildomo konteksto.

Galite pagerinti prieinamumą ir suprantamumą visiems, jei parašysite aiškų, glaustą ir objektyvų alternatyvųjį tekstą.

  • Užfiksuokite svarbiausius elementus.
  • Apibendrinkite tekstą vaizduose.
  • Naudokite įprasta sakinio struktūrą.
  • Venkite nereikalingos informacijos.
  • Sutelkite dėmesį į tendencijas ir pagrindines išvadas sudėtinguose vaizdiniuose (tokiuose kaip diagramos ar žemėlapiai).
", + "interaction_modal.action": "Norėdami bendrauti su {name} įrašu, turite prisijungti prie savo paskyros bet kuriame Mastodon serveryje, kurį naudojate.", "interaction_modal.go": "Eiti", "interaction_modal.no_account_yet": "Dar neturite paskyros?", "interaction_modal.on_another_server": "Kitame serveryje", "interaction_modal.on_this_server": "Šiame serveryje", + "interaction_modal.title": "Jei norite tęsti, prisijunkite", "interaction_modal.username_prompt": "Pvz., {example}", "intervals.full.days": "{number, plural, one {# diena} few {# dienos} many {# dienos} other {# dienų}}", "intervals.full.hours": "{number, plural, one {# valanda} few {# valandos} many {# valandos} other {# valandų}}", "intervals.full.minutes": "{number, plural, one {# minutė} few {# minutes} many {# minutės} other {# minučių}}", "keyboard_shortcuts.back": "Naršyti atgal", "keyboard_shortcuts.blocked": "Atidaryti užblokuotų naudotojų sąrašą", - "keyboard_shortcuts.boost": "Pakelti įrašą", + "keyboard_shortcuts.boost": "Dalintis įrašu", "keyboard_shortcuts.column": "Fokusuoti stulpelį", "keyboard_shortcuts.compose": "Fokusuoti rengykles teksto sritį", "keyboard_shortcuts.description": "Aprašymas", @@ -441,6 +489,7 @@ "keyboard_shortcuts.home": "Atidaryti pagrindinį laiko skalę", "keyboard_shortcuts.hotkey": "Spartusis klavišas", "keyboard_shortcuts.legend": "Rodyti šią legendą", + "keyboard_shortcuts.load_more": "Fokusuoti „Įkelti daugiau“ mygtuką", "keyboard_shortcuts.local": "Atidaryti vietinę laiko skalę", "keyboard_shortcuts.mention": "Paminėti autorių (-ę)", "keyboard_shortcuts.muted": "Atidaryti nutildytų naudotojų sąrašą", @@ -449,6 +498,7 @@ "keyboard_shortcuts.open_media": "Atidaryti mediją", "keyboard_shortcuts.pinned": "Atverti prisegtų įrašų sąrašą", "keyboard_shortcuts.profile": "Atidaryti autoriaus (-ės) profilį", + "keyboard_shortcuts.quote": "Paminėti įrašą", "keyboard_shortcuts.reply": "Atsakyti į įrašą", "keyboard_shortcuts.requests": "Atidaryti sekimo prašymų sąrašą", "keyboard_shortcuts.search": "Fokusuoti paieškos juostą", @@ -457,9 +507,12 @@ "keyboard_shortcuts.toggle_hidden": "Rodyti / slėpti tekstą po TĮ", "keyboard_shortcuts.toggle_sensitivity": "Rodyti / slėpti mediją", "keyboard_shortcuts.toot": "Pradėti naują įrašą", + "keyboard_shortcuts.top": "Perkelti į sąrašo viršų", "keyboard_shortcuts.translate": "išversti įrašą", "keyboard_shortcuts.unfocus": "Nebefokusuoti rengykles teksto sritį / paiešką", "keyboard_shortcuts.up": "Perkelti į viršų sąraše", + "learn_more_link.got_it": "Supratau", + "learn_more_link.learn_more": "Sužinoti daugiau", "lightbox.close": "Uždaryti", "lightbox.next": "Kitas", "lightbox.previous": "Ankstesnis", @@ -509,8 +562,10 @@ "mute_modal.you_wont_see_mentions": "Nematysi įrašus, kuriuose jie paminimi.", "mute_modal.you_wont_see_posts": "Jie vis tiek gali matyti tavo įrašus, bet tu nematysi jų.", "navigation_bar.about": "Apie", + "navigation_bar.account_settings": "Slaptažodis ir saugumas", "navigation_bar.administration": "Administravimas", "navigation_bar.advanced_interface": "Atidaryti išplėstinę žiniatinklio sąsają", + "navigation_bar.automated_deletion": "Automatinis įrašų ištrynimas", "navigation_bar.blocks": "Užblokuoti naudotojai", "navigation_bar.bookmarks": "Žymės", "navigation_bar.direct": "Privatūs paminėjimai", @@ -518,15 +573,25 @@ "navigation_bar.favourites": "Mėgstami", "navigation_bar.filters": "Nutildyti žodžiai", "navigation_bar.follow_requests": "Sekimo prašymai", - "navigation_bar.followed_tags": "Sekami saitažodžiai", + "navigation_bar.followed_tags": "Sekamos grotažymės", "navigation_bar.follows_and_followers": "Sekimai ir sekėjai", + "navigation_bar.import_export": "Importas ir eksportas", "navigation_bar.lists": "Sąrašai", + "navigation_bar.live_feed_local": "Tiesioginis srautas (vietinis)", + "navigation_bar.live_feed_public": "Tiesioginis srautas (viešas)", "navigation_bar.logout": "Atsijungti", "navigation_bar.moderation": "Prižiūrėjimas", + "navigation_bar.more": "Daugiau", "navigation_bar.mutes": "Nutildyti naudotojai", "navigation_bar.opened_in_classic_interface": "Įrašai, paskyros ir kiti konkretūs puslapiai pagal numatytuosius nustatymus atidaromi klasikinėje žiniatinklio sąsajoje.", "navigation_bar.preferences": "Nuostatos", + "navigation_bar.privacy_and_reach": "Privatumas ir pasiekiamumas", "navigation_bar.search": "Ieškoti", + "navigation_bar.search_trends": "Paieška / Populiaru", + "navigation_panel.collapse_followed_tags": "Sutraukti sekamų žymių meniu", + "navigation_panel.collapse_lists": "Sutraukti sąrašo meniu", + "navigation_panel.expand_followed_tags": "Išskleisti sekamų grotažymių meniu", + "navigation_panel.expand_lists": "Išskleisti sąrašo meniu", "not_signed_in_indicator.not_signed_in": "Norint pasiekti šį išteklį, reikia prisijungti.", "notification.admin.report": "{name} pranešė {target}", "notification.admin.report_account": "{name} pranešė {count, plural, one {# įrašą} few {# įrašus} many {# įrašo} other {# įrašų}} iš {target} kategorijai {category}", @@ -538,13 +603,17 @@ "notification.annual_report.message": "Jūsų laukia {year} #Wrapstodon! Atskleiskite savo metų svarbiausius įvykius ir įsimintinas akimirkas platformoje „Mastodon“.", "notification.annual_report.view": "Peržiūrėti #Wrapstodon", "notification.favourite": "{name} pamėgo tavo įrašą", + "notification.favourite.name_and_others_with_link": "{name} ir {count, plural,one {dar kažkas} few {# kiti} other {# kitų}} pamėgo tavo įrašą", "notification.favourite_pm": "{name} pamėgo jūsų privatų paminėjimą", + "notification.favourite_pm.name_and_others_with_link": "{name} ir {count, plural,one {dar kažkas} few {# kiti} other {# kitų}} pamėgo tavo privatų paminėjimą", "notification.follow": "{name} seka tave", "notification.follow.name_and_others": "{name} ir {count, plural, one {# kitas} few {# kiti} many {# kito} other {# kitų}} seka tave", "notification.follow_request": "{name} paprašė tave sekti", + "notification.follow_request.name_and_others": "{name} ir {count, plural,one {dar kažkas} few {# kiti} other {# kitų}} pradėjo tave sekti", "notification.label.mention": "Paminėjimas", "notification.label.private_mention": "Privatus paminėjimas", "notification.label.private_reply": "Privatus atsakymas", + "notification.label.quote": "{name} paminėjo jūsų įrašą", "notification.label.reply": "Atsakymas", "notification.mention": "Paminėjimas", "notification.mentioned_you": "{name} paminėjo jus", @@ -552,15 +621,19 @@ "notification.moderation_warning": "Gavai prižiūrėjimo įspėjimą", "notification.moderation_warning.action_delete_statuses": "Kai kurie tavo įrašai buvo pašalintos.", "notification.moderation_warning.action_disable": "Tavo paskyra buvo išjungta.", - "notification.moderation_warning.action_mark_statuses_as_sensitive": "Kai kurie tavo įrašai buvo pažymėtos kaip jautrios.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Kai kurie tavo įrašai buvo pažymėti kaip turintys jautrią informaciją.", "notification.moderation_warning.action_none": "Tavo paskyra gavo prižiūrėjimo įspėjimą.", - "notification.moderation_warning.action_sensitive": "Nuo šiol tavo įrašai bus pažymėti kaip jautrūs.", + "notification.moderation_warning.action_sensitive": "Nuo šiol tavo įrašai bus pažymėti kaip su jautria informacija.", "notification.moderation_warning.action_silence": "Tavo paskyra buvo apribota.", "notification.moderation_warning.action_suspend": "Tavo paskyra buvo sustabdyta.", "notification.own_poll": "Tavo apklausa baigėsi", "notification.poll": "Baigėsi apklausa, kurioje balsavai", - "notification.reblog": "{name} pakėlė tavo įrašą", + "notification.quoted_update": "{name} redagavo jūsų cituotą įrašą", + "notification.reblog": "{name} dalinosi tavo įrašu", + "notification.reblog.name_and_others_with_link": "{name} ir {count, plural,one {dar kažkas} few {# kiti} other {# kitų}} pasidalino tavo įrašu", "notification.relationships_severance_event": "Prarasti sąryšiai su {name}", + "notification.relationships_severance_event.account_suspension": "Administratorius iš {from} sustabdė {target}, todėl jūs nebegalėsite gauti jų naujienų ar bendrauti su jais.", + "notification.relationships_severance_event.domain_block": "Administratorius iš {from} užblokavo {target}, įskaitant {followersCount} iš tavo sekėjų ir {followingCount, plural,one {# žmogų, kurį} few {# žmones, kuriuos} other {# žmonių, kuriuos}} seki.", "notification.relationships_severance_event.learn_more": "Sužinoti daugiau", "notification.relationships_severance_event.user_domain_block": "Tu užblokavai {target}. Pašalinama {followersCount} savo sekėjų ir {followingCount, plural, one {# paskyrą} few {# paskyrai} many {# paskyros} other {# paskyrų}}, kurios seki.", "notification.status": "{name} ką tik paskelbė", @@ -599,7 +672,8 @@ "notifications.column_settings.mention": "Paminėjimai:", "notifications.column_settings.poll": "Balsavimo rezultatai:", "notifications.column_settings.push": "Tiesioginiai pranešimai", - "notifications.column_settings.reblog": "Pakėlimai:", + "notifications.column_settings.quote": "Paminėjimai:", + "notifications.column_settings.reblog": "Pasidalinimai:", "notifications.column_settings.show": "Rodyti stulpelyje", "notifications.column_settings.sound": "Paleisti garsą", "notifications.column_settings.status": "Nauji įrašai:", @@ -607,7 +681,7 @@ "notifications.column_settings.unread_notifications.highlight": "Paryškinti neperskaitytus pranešimus", "notifications.column_settings.update": "Redagavimai:", "notifications.filter.all": "Visi", - "notifications.filter.boosts": "Pakėlimai", + "notifications.filter.boosts": "Pasidalinimai", "notifications.filter.favourites": "Mėgstami", "notifications.filter.follows": "Sekimai", "notifications.filter.mentions": "Paminėjimai", @@ -626,11 +700,14 @@ "notifications.policy.filter": "Filtruoti", "notifications.policy.filter_hint": "Siųsti į filtruotų pranešimų gautiejus", "notifications.policy.filter_limited_accounts_hint": "Apribota serverio prižiūrėtojų", - "notifications.policy.filter_limited_accounts_title": "Prižiūrėmi paskyrai", + "notifications.policy.filter_limited_accounts_title": "Moderuojamos paskyros", "notifications.policy.filter_new_accounts.hint": "Sukurta per {days, plural, one {vieną dieną} few {# dienas} many {# dienos} other {# dienų}}", "notifications.policy.filter_new_accounts_title": "Naujos paskyros", + "notifications.policy.filter_not_followers_hint": "Įskaitant žmones, kurie seka jus mažiau nei {days, plural, one {vieną dieną} few {# dienas} many {# dienų} other {# dienų}}", + "notifications.policy.filter_not_followers_title": "Žmonės, kurie neseka tavęs", "notifications.policy.filter_not_following_hint": "Kol jų nepatvirtinsi rankiniu būdu", "notifications.policy.filter_not_following_title": "Žmonių, kuriuos neseki", + "notifications.policy.filter_private_mentions_hint": "Filtruojama, išskyrus atsakymus į tavo paties paminėjimą arba jei seki siuntėją", "notifications.policy.filter_private_mentions_title": "Nepageidaujami privatūs paminėjimai", "notifications.policy.title": "Tvarkyti pranešimus iš…", "notifications_permission_banner.enable": "Įjungti darbalaukio pranešimus", @@ -671,10 +748,20 @@ "privacy.private.short": "Sekėjai", "privacy.public.long": "Bet kas iš Mastodon ir ne Mastodon", "privacy.public.short": "Vieša", - "privacy.unlisted.additional": "Tai veikia lygiai taip pat, kaip ir vieša, tik įrašas nebus rodomas tiesioginiuose srautuose, saitažodžiose, naršyme ar Mastodon paieškoje, net jei esi įtraukęs (-usi) visą paskyrą.", + "privacy.quote.anyone": "{visibility}, kiekvienas gali cituoti", + "privacy.quote.disabled": "{visibility}, paminėjimai išjungti", + "privacy.quote.limited": "{visibility}, paminėjimai apriboti", + "privacy.unlisted.additional": "Tai veikia lygiai taip pat, kaip ir vieša, tik įrašas nebus rodomas tiesioginiuose srautuose, grotažymėse, naršyme ar Mastodon paieškoje, net jei esi įtraukęs (-usi) visą paskyrą.", + "privacy.unlisted.long": "Paslėptas nuo „Mastodon“ paieškos rezultatų, tendencijų ir viešų įrašų sienų", "privacy.unlisted.short": "Tyliai vieša", "privacy_policy.last_updated": "Paskutinį kartą atnaujinta {date}", "privacy_policy.title": "Privatumo politika", + "quote_error.edit": "Paminėjimai negali būti pridedami, kai keičiamas įrašas.", + "quote_error.poll": "Cituoti apklausose negalima.", + "quote_error.private_mentions": "Cituoti privačius paminėjus nėra leidžiama.", + "quote_error.quote": "Leidžiama pateikti tik vieną citatą vienu metu.", + "quote_error.unauthorized": "Jums neleidžiama cituoti šio įrašo.", + "quote_error.upload": "Cituoti ir pridėti papildomas bylas negalima.", "recommended": "Rekomenduojama", "refresh": "Atnaujinti", "regeneration_indicator.please_stand_by": "Laukite.", @@ -690,6 +777,9 @@ "relative_time.minutes": "{number} min.", "relative_time.seconds": "{number} sek.", "relative_time.today": "šiandien", + "remove_quote_hint.button_label": "Supratau", + "remove_quote_hint.message": "Tai galite padaryti iš {icon} parinkčių meniu.", + "remove_quote_hint.title": "Norite pašalinti savo citatą?", "reply_indicator.attachments": "{count, plural, one {# priedas} few {# priedai} many {# priedo} other {# priedų}}", "reply_indicator.cancel": "Atšaukti", "reply_indicator.poll": "Apklausa", @@ -743,6 +833,7 @@ "report_notification.categories.violation": "Taisyklės pažeidimas", "report_notification.categories.violation_sentence": "taisyklės pažeidimas", "report_notification.open": "Atidaryti ataskaitą", + "search.clear": "Išvalyti paiešką", "search.no_recent_searches": "Nėra naujausių paieškų.", "search.placeholder": "Paieška", "search.quick_action.account_search": "Profiliai, atitinkantys {x}", @@ -751,7 +842,7 @@ "search.quick_action.open_url": "Atidaryti URL adresą Mastodon", "search.quick_action.status_search": "Pranešimai, atitinkantys {x}", "search.search_or_paste": "Ieškoti arba įklijuoti URL", - "search_popout.full_text_search_disabled_message": "Nepasiekima {domain}.", + "search_popout.full_text_search_disabled_message": "Paieška {domain} įrašuose išjungta.", "search_popout.full_text_search_logged_out_message": "Pasiekiama tik prisijungus.", "search_popout.language_code": "ISO kalbos kodas", "search_popout.options": "Paieškos nustatymai", @@ -761,9 +852,9 @@ "search_popout.user": "naudotojas", "search_results.accounts": "Profiliai", "search_results.all": "Visi", - "search_results.hashtags": "Saitažodžiai", + "search_results.hashtags": "Grotažymės", "search_results.no_results": "Nėra rezultatų.", - "search_results.no_search_yet": "Pabandykite ieškoti įrašų, profilių arba saitažodžių.", + "search_results.no_search_yet": "Pabandykite ieškoti įrašų, profilių arba grotažymių.", "search_results.see_all": "Žiūrėti viską", "search_results.statuses": "Įrašai", "search_results.title": "Paieška užklausai „{q}“", @@ -780,23 +871,32 @@ "status.admin_account": "Atidaryti prižiūrėjimo sąsają @{name}", "status.admin_domain": "Atidaryti prižiūrėjimo sąsają {domain}", "status.admin_status": "Atidaryti šį įrašą prižiūrėjimo sąsajoje", + "status.all_disabled": "Įrašo dalinimaisi ir paminėjimai išjungti", "status.block": "Blokuoti @{name}", - "status.bookmark": "Pridėti į žymės", - "status.cancel_reblog_private": "Nebepasidalinti", + "status.bookmark": "Žymė", + "status.cancel_reblog_private": "Nesidalinti", + "status.cannot_quote": "Jums neleidžiama paminėti šio įrašo", "status.cannot_reblog": "Šis įrašas negali būti pakeltas.", - "status.context.load_new_replies": "Yra naujų atsakymų", - "status.context.loading": "Tikrinama dėl daugiau atsakymų", + "status.contains_quote": "Turi citatą", + "status.context.loading": "Įkeliama daugiau atsakymų", + "status.context.loading_error": "Nepavyko įkelti naujų atsakymų", + "status.context.loading_success": "Įkelti nauji atsakymai", + "status.context.more_replies_found": "Rasta daugiau atsakymų", + "status.context.retry": "Kartoti", + "status.context.show": "Rodyti", "status.continued_thread": "Tęsiama gijoje", "status.copy": "Kopijuoti nuorodą į įrašą", "status.delete": "Ištrinti", + "status.delete.success": "Įrašas ištrintas", "status.detailed_status": "Išsami pokalbio peržiūra", "status.direct": "Privačiai paminėti @{name}", "status.direct_indicator": "Privatus paminėjimas", "status.edit": "Redaguoti", "status.edited": "Paskutinį kartą redaguota {date}", "status.edited_x_times": "Redaguota {count, plural, one {{count} kartą} few {{count} kartus} many {{count} karto} other {{count} kartų}}", + "status.embed": "Gaukite įterpimo kodą", "status.favourite": "Pamėgti", - "status.favourites": "{count, plural, one {mėgstamas} few {mėgstamai} many {mėgstamų} other {mėgstamų}}", + "status.favourites_count": "{count, plural, one {{counter} patiko} few {{counter} patiko} many {{counter} patiko} other {{counter} patiko}}", "status.filter": "Filtruoti šį įrašą", "status.history.created": "{name} sukurta {date}", "status.history.edited": "{name} redaguota {date}", @@ -808,20 +908,48 @@ "status.more": "Daugiau", "status.mute": "Nutildyti @{name}", "status.mute_conversation": "Nutildyti pokalbį", - "status.open": "Išplėsti šį įrašą", + "status.open": "Išskleisti šį įrašą", "status.pin": "Prisegti prie profilio", + "status.quote": "Paminėjimai", + "status.quote.cancel": "Atšaukti paminėjimą", + "status.quote_error.blocked_account_hint.title": "Šis įrašas yra paslėptas, nes jūs esate užblokavę @{name}.", + "status.quote_error.blocked_domain_hint.title": "Šis įrašas yra paslėptas, nes jūs užblokavote {domain}.", + "status.quote_error.filtered": "Paslėpta dėl vieno iš jūsų filtrų", + "status.quote_error.limited_account_hint.action": "Vis tiek rodyti", + "status.quote_error.limited_account_hint.title": "Šis paskyra buvo paslėpta {domain} moderatorių.", + "status.quote_error.muted_account_hint.title": "Šis įrašas yra paslėptas, nes jūs esate užtildę @{name}.", + "status.quote_error.not_available": "Įrašas nepasiekiamas", + "status.quote_error.pending_approval": "Įrašas peržiūrimas", + "status.quote_error.pending_approval_popout.body": "„Mastodon“ galite kontroliuoti, ar kas nors gali jus cituoti (paminėti). Šis įrašas bus laukimo būsenoje, kol gausite originalaus įrašo autoriaus sutikimą.", + "status.quote_error.revoked": "Autorius pašalino įrašą", + "status.quote_followers_only": "Tik sekėjai gali cituoti šį įrašą", + "status.quote_manual_review": "Autorius atskirai įvertins paskelbimą", + "status.quote_noun": "Paminėjimas", + "status.quote_policy_change": "Keisti, kas gali cituoti", + "status.quote_post_author": "Paminėjo įrašą iš @{name}", + "status.quote_private": "Privačių įrašų negalima cituoti", + "status.quotes.empty": "Šio įrašo dar niekas nepaminėjo. Kai kas nors tai padarys, jie bus rodomi čia.", + "status.quotes.local_other_disclaimer": "Autoriaus atmesti įrašo paminėjimai nebus rodomi.", + "status.quotes.remote_other_disclaimer": "Čia bus rodoma tik paminėjimai iš {domain}. Autoriaus atmesti įrašo paminėjimai nebus rodomi.", + "status.quotes_count": "{count, plural, one {{counter} paminėjimas} few {{counter} paminėjimai} many {{counter} paminėjimai} other {{counter} paminėjimai}}", "status.read_more": "Skaityti daugiau", - "status.reblog": "Pakelti", - "status.reblogged_by": "{name} pakėlė", - "status.reblogs.empty": "Šio įrašo dar niekas nepakėlė. Kai kas nors tai padarys, jie bus rodomi čia.", + "status.reblog": "Dalintis", + "status.reblog_or_quote": "Dalintis arba cituoti", + "status.reblog_private": "Vėl pasidalinkite su savo sekėjais", + "status.reblogged_by": "{name} pasidalino", + "status.reblogs.empty": "Šiuo įrašu dar niekas nesidalino. Kai kas nors tai padarys, jie bus rodomi čia.", + "status.reblogs_count": "{count, plural, one {{counter} pasidalinimas} few {{counter} pasidalinimai} many {{counter} pasidalinimų} other {{counter} pasidalinimai}}", "status.redraft": "Ištrinti ir parengti iš naujo", "status.remove_bookmark": "Pašalinti žymę", "status.remove_favourite": "Šalinti iš mėgstamų", + "status.remove_quote": "Pašalinti", "status.replied_in_thread": "Atsakyta gijoje", "status.replied_to": "Atsakyta į {name}", "status.reply": "Atsakyti", "status.replyAll": "Atsakyti į giją", "status.report": "Pranešti apie @{name}", + "status.request_quote": "Citavimo sutikimas", + "status.revoke_quote": "Pašalinti mano įrašo citavimą iš @{name}’s įrašo", "status.sensitive_warning": "Jautrus turinys", "status.share": "Bendrinti", "status.show_less_all": "Rodyti mažiau visiems", @@ -837,7 +965,10 @@ "subscribed_languages.save": "Išsaugoti pakeitimus", "subscribed_languages.target": "Keisti prenumeruojamas kalbas {target}", "tabs_bar.home": "Pagrindinis", + "tabs_bar.menu": "Meniu", "tabs_bar.notifications": "Pranešimai", + "tabs_bar.publish": "Naujas įrašas", + "tabs_bar.search": "Paieška", "terms_of_service.effective_as_of": "Įsigaliojo nuo {date}.", "terms_of_service.title": "Paslaugų sąlygos", "terms_of_service.upcoming_changes_on": "Numatomi pakeitimai {date}.", @@ -856,6 +987,7 @@ "upload_button.label": "Pridėti vaizdų, vaizdo įrašą arba garso failą", "upload_error.limit": "Viršyta failo įkėlimo riba.", "upload_error.poll": "Failų įkėlimas neleidžiamas su apklausomis.", + "upload_error.quote": "Paminint įrašą bylos įkėlimas negalimas.", "upload_form.drag_and_drop.instructions": "Kad paimtum medijos priedą, paspausk tarpo arba įvedimo klavišą. Tempant naudok rodyklių klavišus, kad perkeltum medijos priedą bet kuria kryptimi. Dar kartą paspausk tarpo arba įvedimo klavišą, kad nuleistum medijos priedą naujoje vietoje, arba paspausk grįžimo klavišą, kad atšauktum.", "upload_form.drag_and_drop.on_drag_cancel": "Nutempimas buvo atšauktas. Medijos priedas {item} buvo nuleistas.", "upload_form.drag_and_drop.on_drag_end": "Medijos priedas {item} buvo nuleistas.", @@ -874,5 +1006,25 @@ "video.mute": "Išjungti garsą", "video.pause": "Pristabdyti", "video.play": "Leisti", - "video.skip_backward": "Praleisti atgal" + "video.skip_backward": "Praleisti atgal", + "video.skip_forward": "Praleisti į priekį", + "video.unmute": "Atšaukti nutildymą", + "video.volume_down": "Patildyti", + "video.volume_up": "Pagarsinti", + "visibility_modal.button_title": "Nustatyti matomumą", + "visibility_modal.direct_quote_warning.text": "Jei išsaugosite dabartinius nustatymus, įterpta citata bus konvertuota į nuorodą.", + "visibility_modal.direct_quote_warning.title": "Cituojami įrašai negali būti įterpiami į privačius paminėjimus", + "visibility_modal.header": "Matomumas ir sąveika", + "visibility_modal.helper.direct_quoting": "Privatūs paminėjimai, parašyti platformoje „Mastodon“, negali būti cituojami kitų.", + "visibility_modal.helper.privacy_editing": "Matomumo nustatymai negali būti keičiami po to, kai įrašas yra paskelbtas.", + "visibility_modal.helper.privacy_private_self_quote": "Privačių įrašų paminėjimai negali būti skelbiami viešai.", + "visibility_modal.helper.private_quoting": "Tik sekėjams skirti įrašai, parašyti platformoje „Mastodon“, negali būti cituojami kitų.", + "visibility_modal.helper.unlisted_quoting": "Kai žmonės jus cituos, jų įrašai taip pat bus paslėpti iš populiariausių naujienų srauto.", + "visibility_modal.instructions": "Kontroliuokite, kas gali bendrauti su šiuo įrašu. Taip pat galite taikyti nustatymus visiems būsimiems įrašams, pereidami į Preferences > Posting defaults.", + "visibility_modal.privacy_label": "Matomumas", + "visibility_modal.quote_followers": "Tik sekėjai", + "visibility_modal.quote_label": "Kas gali cituoti", + "visibility_modal.quote_nobody": "Tik aš", + "visibility_modal.quote_public": "Visi", + "visibility_modal.save": "Išsaugoti" } diff --git a/app/javascript/mastodon/locales/lv.json b/app/javascript/mastodon/locales/lv.json index d80b8daf200e9f..260b4486b85cab 100644 --- a/app/javascript/mastodon/locales/lv.json +++ b/app/javascript/mastodon/locales/lv.json @@ -26,8 +26,9 @@ "account.copy": "Ievietot saiti uz profilu starpliktuvē", "account.direct": "Pieminēt @{name} privāti", "account.disable_notifications": "Pārtraukt man paziņot, kad @{name} izveido ierakstu", - "account.domain_blocking": "Bloķēts domēns", + "account.domain_blocking": "Liegts domēns", "account.edit_profile": "Labot profilu", + "account.edit_profile_short": "Labot", "account.enable_notifications": "Paziņot man, kad @{name} izveido ierakstu", "account.endorse": "Izcelts profilā", "account.familiar_followers_many": "Kam seko {name1}, {name2}, un {othersCount, plural, zero {pārējie # jums pazīstami} one {vēl viens jums pazīstams} other {pārējie # jums pazīstami}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Nav ierakstu", "account.follow": "Sekot", "account.follow_back": "Sekot atpakaļ", + "account.follow_back_short": "Sekot atpakaļ", + "account.follow_request": "Pieprasīt sekot", + "account.follow_request_cancel": "Atcelt pieprasījumu", + "account.follow_request_cancel_short": "Atcelt", + "account.follow_request_short": "Pieprasīt", "account.followers": "Sekotāji", "account.followers.empty": "Šim lietotājam vēl nav sekotāju.", "account.followers_counter": "{count, plural, zero {{count} sekotāju} one {{count} sekotājs} other {{count} sekotāji}}", @@ -70,15 +76,14 @@ "account.posts_with_replies": "Ieraksti un atbildes", "account.remove_from_followers": "Dzēst sekotāju {name}", "account.report": "Ziņot par @{name}", - "account.requested": "Gaida apstiprinājumu. Nospied, lai atceltu sekošanas pieparasījumu", "account.requested_follow": "{name} nosūtīja Tev sekošanas pieprasījumu", "account.requests_to_follow_you": "Sekošanas pieprasījumi", "account.share": "Dalīties ar @{name} profilu", "account.show_reblogs": "Parādīt @{name} pastiprinātos ierakstus", "account.statuses_counter": "{count, plural, zero {{counter} ierakstu} one {{counter} ieraksts} other {{counter} ieraksti}}", - "account.unblock": "Atbloķēt @{name}", - "account.unblock_domain": "Atbloķēt domēnu {domain}", - "account.unblock_domain_short": "Atbloķēt", + "account.unblock": "Atcelt liegšanu @{name}", + "account.unblock_domain": "Atcelt domēna {domain} liegšanu", + "account.unblock_domain_short": "Atcelt liegšanu", "account.unblock_short": "Atbloķēt", "account.unendorse": "Neizcelt profilā", "account.unfollow": "Pārstāt sekot", @@ -108,30 +113,19 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Aprakstīt šo cilvēkiem ar redzes traucējumiem…", "alt_text_modal.done": "Gatavs", "announcement.announcement": "Paziņojums", - "annual_report.summary.archetype.lurker": "Glūņa", - "annual_report.summary.archetype.oracle": "Orākuls", - "annual_report.summary.archetype.replier": "Sabiedriskais tauriņš", - "annual_report.summary.followers.followers": "sekotāji", - "annual_report.summary.followers.total": "pavisam {count}", - "annual_report.summary.here_it_is": "Šeit ir {year}. gada pārskats:", - "annual_report.summary.highlighted_post.by_favourites": "izlasēm visvairāk pievienotais ieraksts", - "annual_report.summary.highlighted_post.by_reblogs": "vispastiprinātākais ieraksts", - "annual_report.summary.highlighted_post.by_replies": "ieraksts ar vislielāko atbilžu skaitu", - "annual_report.summary.highlighted_post.possessive": "{name}", "annual_report.summary.most_used_app.most_used_app": "visizmantotākā lietotne", "annual_report.summary.most_used_hashtag.most_used_hashtag": "visizmantotākais tēmturis", - "annual_report.summary.most_used_hashtag.none": "Nav", "annual_report.summary.new_posts.new_posts": "jauni ieraksti", "annual_report.summary.percentile.text": "Tas ievieto Tevi virsējosno {domain} lietotājiem.", "annual_report.summary.percentile.we_wont_tell_bernie": "Mēs neteiksim Bērnijam.", - "annual_report.summary.thanks": "Paldies, ka esi daļa no Mastodon!", "attachments_list.unprocessed": "(neapstrādāti)", "audio.hide": "Slēpt audio", - "block_modal.remote_users_caveat": "Mēs vaicāsim serverim {domain} ņemt vērā Tavu lēmumu. Tomēr atbilstība nav nodrošināta, jo atsevišķi serveri liegšanu var apstrādāt citādi. Publiski ieraksti joprojām var būt redzami lietotājiem, kuri nav pieteikušies.", + "block_modal.remote_users_caveat": "Mēs lūgsim serverim {domain} ņemt vērā Tavu lēmumu. Tomēr sadarbība nav garantēta, jo atsevišķi serveri bloķēšanu var apstrādāt citādi. Publiski ieraksti, iespējams, joprojām būs redzami lietotājiem, kuri nav pieteikušies.", "block_modal.show_less": "Rādīt mazāk", - "block_modal.show_more": "Parādīt mazāk", - "block_modal.they_cant_mention": "Nevar Tevi pieminēt vai sekot Tev.", - "block_modal.they_cant_see_posts": "Lietotajs nevarēs redzēt Tavus ierakstus, un Tu neredzēsi lietotāja.", + "block_modal.show_more": "Rādīt vairāk", + "block_modal.they_cant_mention": "Lietotājs nevarēs Tevi pieminēt vai Tev sekot.", + "block_modal.they_cant_see_posts": "Lietotājs nevarēs redzēt Tavus, un Tu neredzēsi lietotāja ierakstus.", + "block_modal.they_will_know": "Lietotājs varēs redzēt, ka ir bloķēts.", "block_modal.title": "Bloķēt lietotāju?", "block_modal.you_wont_see_mentions": "Tu neredzēsi ierakstus, kuros ir minēts šis lietotājs.", "boost_modal.combo": "Nospied {combo}, lai nākamreiz šo izlaistu", @@ -155,13 +149,13 @@ "closed_registrations_modal.preamble": "Mastodon ir decentralizēts, tāpēc neatkarīgi no tā, kur Tu izveido savu kontu, varēsi sekot un mijiedarboties ar ikvienu šajā serverī. Tu pat vari to pašizvietot!", "closed_registrations_modal.title": "Reģistrēšanās Mastodon", "column.about": "Par", - "column.blocks": "Bloķētie lietotāji", + "column.blocks": "Liegtie lietotāji", "column.bookmarks": "Grāmatzīmes", "column.community": "Vietējā laika līnija", "column.create_list": "Izveidot sarakstu", "column.direct": "Privātas pieminēšanas", "column.directory": "Pārlūkot profilus", - "column.domain_blocks": "Bloķētie domēni", + "column.domain_blocks": "Liegtie domēni", "column.edit_list": "Labot sarakstu", "column.favourites": "Izlase", "column.firehose": "Tiešraides plūsmas", @@ -184,6 +178,7 @@ "community.column_settings.local_only": "Tikai vietējie", "community.column_settings.media_only": "Tikai multivide", "community.column_settings.remote_only": "Tikai attālinātie", + "compose.error.blank_post": "Ieraksts nedrīkst būt tukšs.", "compose.language.change": "Mainīt valodu", "compose.language.search": "Meklēt valodas...", "compose.published.body": "Ieraksts pievienots.", @@ -209,7 +204,7 @@ "compose_form.spoiler.unmarked": "Pievienot satura brīdinājumu", "compose_form.spoiler_placeholder": "Satura brīdinājums (pēc izvēles)", "confirmation_modal.cancel": "Atcelt", - "confirmations.block.confirm": "Bloķēt", + "confirmations.block.confirm": "Liegt", "confirmations.delete.confirm": "Dzēst", "confirmations.delete.message": "Vai tiešām izdzēst šo ierakstu?", "confirmations.delete.title": "Izdzēst ierakstu?", @@ -217,7 +212,9 @@ "confirmations.delete_list.message": "Vai tiešām neatgriezeniski izdzēst šo sarakstu?", "confirmations.delete_list.title": "Izdzēst sarakstu?", "confirmations.discard_draft.confirm": "Atmest un turpināt", - "confirmations.discard_draft.edit.cancel": "Atsākt labošanu", + "confirmations.discard_draft.edit.cancel": "Atgriezties pie labošanas", + "confirmations.discard_draft.edit.message": "Ja turpināsi, tiks atmestas izmaiņas ierakstā, kuru Tu šobrīd labo.", + "confirmations.discard_draft.edit.title": "Atmest ieraksta labojumus?", "confirmations.discard_draft.post.cancel": "Atsākt melnrakstu", "confirmations.discard_draft.post.message": "Turpinot tiks atmests pašreiz sastādītais ieraksts.", "confirmations.discard_draft.post.title": "Atmest melnraksta ierakstu?", @@ -234,6 +231,11 @@ "confirmations.missing_alt_text.secondary": "Vienalga iesūtīt", "confirmations.missing_alt_text.title": "Pievienot aprakstošo tekstu?", "confirmations.mute.confirm": "Apklusināt", + "confirmations.private_quote_notify.cancel": "Atgriezties pie labošanas", + "confirmations.private_quote_notify.confirm": "Publicēt ierakstu", + "confirmations.private_quote_notify.do_not_show_again": "Nerādīt vairāk šo paziņojumu", + "confirmations.private_quote_notify.title": "Dalīties ar sekotājiem un pieminētajiem lietotājiem?", + "confirmations.quiet_post_quote_info.got_it": "Sapratu", "confirmations.redraft.confirm": "Dzēst un pārrakstīt", "confirmations.redraft.message": "Vai tiešām vēlies izdzēst šo ierakstu un veidot jaunu tā uzmetumu? Pievienošana izlasēs un pastiprinājumi tiks zaudēti, un sākotnējā ieraksta atbildes paliks bez saiknes ar to.", "confirmations.redraft.title": "Izdzēst un rakstīt ierakstu no jauna?", @@ -243,9 +245,11 @@ "confirmations.revoke_quote.confirm": "Noņemt ierakstu", "confirmations.revoke_quote.message": "Šo darbību nevar atsaukt.", "confirmations.revoke_quote.title": "Noņemt ierakstu?", + "confirmations.unblock.confirm": "Atbloķēt", + "confirmations.unblock.title": "Atbloķēt {name}?", "confirmations.unfollow.confirm": "Pārstāt sekot", - "confirmations.unfollow.message": "Vai tiešam vairs nevēlies sekot lietotājam {name}?", - "confirmations.unfollow.title": "Pārtraukt sekošanu lietotājam?", + "confirmations.unfollow.title": "Pārtraukt sekot {name}?", + "confirmations.withdraw_request.confirm": "Atsaukt pieprasījumu", "content_warning.hide": "Paslēpt ierakstu", "content_warning.show": "Tomēr rādīt", "content_warning.show_more": "Rādīt vairāk", @@ -265,11 +269,13 @@ "dismissable_banner.community_timeline": "Šie ir jaunākie publiskie ieraksti no cilvēkiem, kuru konti ir mitināti {domain}.", "dismissable_banner.dismiss": "Atcelt", "dismissable_banner.public_timeline": "Šie ir jaunākie Fediverse lietotāju publiskie ieraksti, kuriem {domain} seko cilvēki.", - "domain_block_modal.block": "Bloķēt serveri", - "domain_block_modal.block_account_instead": "Tā vietā liegt @{name}", - "domain_block_modal.they_cant_follow": "Neviens šajā serverī nevar Tev sekot.", - "domain_block_modal.they_wont_know": "Viņi nezinās, ka tikuši bloķēti.", - "domain_block_modal.title": "Bloķēt domēnu?", + "domain_block_modal.block": "Liegt serveri", + "domain_block_modal.block_account_instead": "Tā vietā bloķēt @{name}", + "domain_block_modal.they_cant_follow": "Neviens no šī servera nevarēs Tev sekot.", + "domain_block_modal.they_wont_know": "Viņi nezinās, ka tikuši liegti.", + "domain_block_modal.title": "Liegt domēnu?", + "domain_block_modal.you_will_lose_num_followers": "Tu zaudēsi {followersCount, plural, one {{followersCountDisplay} sekotāju} other {{followersCountDisplay} sekotājus}} un {followingCount, plural, one {{followingCountDisplay} sekojamo} other {{followingCountDisplay} sekojamos}}.", + "domain_block_modal.you_will_lose_relationships": "Tu zaudēsi visus sekotājus un sekojamos no šī servera.", "domain_pill.activitypub_lets_connect": "Tas ļauj savienoties un mijiedarboties ar cilvēkiem ne tikai no Mastodon, bet arī starp dažādām sabiedriskajām lietotnēm.", "domain_pill.activitypub_like_language": "ActivityPub ir kā valoda, kurā Mastodon sazināš ar citiem sabiedriskajiem tīkliem.", "domain_pill.server": "Serveris", @@ -302,11 +308,11 @@ "empty_column.account_suspended": "Konta darbība ir apturēta", "empty_column.account_timeline": "Šeit nav ierakstu.", "empty_column.account_unavailable": "Profils nav pieejams", - "empty_column.blocks": "Pašreiz tu neesi nevienu bloķējis.", + "empty_column.blocks": "Pagaidām Tu neesi liedzis nevienu lietotāju.", "empty_column.bookmarked_statuses": "Pašlaik Tev nav neviena grāmatzīmēs pievienota ieraksta. Kad tādu pievienosi, tas parādīsies šeit.", "empty_column.community": "Vietējā laika līnija ir tukša. Uzraksti kaut ko publiski, lai iekustinātu visu!", "empty_column.direct": "Tev vēl nav privātu pieminēšanu. Kad Tu nosūtīsi vai saņemsi kādu, tā pārādīsies šeit.", - "empty_column.domain_blocks": "Vēl nav neviena bloķēta domēna.", + "empty_column.domain_blocks": "Vēl nav neviena liegta domēna.", "empty_column.explore_statuses": "Pašlaik nav nekā aktuāla. Ieskaties šeit vēlāk!", "empty_column.favourited_statuses": "Tev vēl nav izlasei pievienotu ierakstu. Kad pievienosi kādu, tas tiks parādīts šeit.", "empty_column.favourites": "Šo ierakstu vēl neviens nav pievienojis izlasei. Kad kāds to izdarīs, tas parādīsies šeit.", @@ -325,13 +331,10 @@ "errors.unexpected_crash.copy_stacktrace": "Kopēt stacktrace uz starpliktuvi", "errors.unexpected_crash.report_issue": "Ziņot par problēmu", "explore.suggested_follows": "Cilvēki", + "explore.title": "Populārākie", "explore.trending_links": "Jaunumi", "explore.trending_statuses": "Ieraksti", "explore.trending_tags": "Tēmturi", - "featured_carousel.next": "Tālāk", - "featured_carousel.post": "Ieraksts", - "featured_carousel.previous": "Atpakaļ", - "featured_carousel.slide": "{index} / {total}", "filter_modal.added.context_mismatch_explanation": "Šī atlases kategorija neattiecas uz kontekstu, kurā esi piekļuvis šim ierakstam. Ja vēlies, lai ieraksts tiktu atlasīts arī šajā kontekstā, Tev būs jālabo atlase.", "filter_modal.added.context_mismatch_title": "Konteksta neatbilstība!", "filter_modal.added.expired_explanation": "Šai atlases kategorijai ir beidzies derīguma termiņš. Lai to lietotu, Tev būs jāmaina derīguma termiņš.", @@ -367,6 +370,7 @@ "follow_suggestions.who_to_follow": "Kam sekot", "followed_tags": "Sekojamie tēmturi", "footer.about": "Par", + "footer.about_this_server": "Par", "footer.directory": "Profilu direktorija", "footer.get_app": "Iegūt lietotni", "footer.keyboard_shortcuts": "Īsinājumtaustiņi", @@ -409,6 +413,9 @@ "home.pending_critical_update.title": "Ir pieejams būtisks drošības atjauninājums.", "home.show_announcements": "Rādīt paziņojumus", "ignore_notifications_modal.ignore": "Neņemt vērā paziņojumus", + "ignore_notifications_modal.limited_accounts_title": "Neņemt vērā paziņojumus no moderētiem kontiem?", + "ignore_notifications_modal.new_accounts_title": "Neņemt vērā paziņojumus no jauniem kontiem?", + "ignore_notifications_modal.not_followers_title": "Neņemt vērā paziņojumus no cilvēkiem, kas tev neseko?", "ignore_notifications_modal.not_following_title": "Neņemt vērā paziņojumus no cilvēkiem, kuriem neseko?", "info_button.label": "Palīdzība", "interaction_modal.go": "Aiziet", @@ -420,7 +427,7 @@ "intervals.full.hours": "{number, plural, one {# stunda} other {# stundas}}", "intervals.full.minutes": "{number, plural, one {# minūte} other {# minūtes}}", "keyboard_shortcuts.back": "Pāriet atpakaļ", - "keyboard_shortcuts.blocked": "Atvērt bloķēto lietotāju sarakstu", + "keyboard_shortcuts.blocked": "Atvērt liegto lietotāju sarakstu", "keyboard_shortcuts.boost": "Pastiprināt ierakstu", "keyboard_shortcuts.column": "Fokusēt kolonnu", "keyboard_shortcuts.compose": "Fokusēt veidojamā teksta lauku", @@ -443,6 +450,7 @@ "keyboard_shortcuts.open_media": "Atvērt multividi", "keyboard_shortcuts.pinned": "Atvērt piesprausto ierakstu sarakstu", "keyboard_shortcuts.profile": "Atvērt autora profilu", + "keyboard_shortcuts.quote": "Citēt ierakstu", "keyboard_shortcuts.reply": "Atbildēt", "keyboard_shortcuts.requests": "Atvērt sekošanas pieprasījumu sarakstu", "keyboard_shortcuts.search": "Fokusēt meklēšanas joslu", @@ -490,17 +498,20 @@ "mute_modal.show_options": "Parādīt iespējas", "mute_modal.title": "Apklusināt lietotāju?", "navigation_bar.about": "Par", + "navigation_bar.account_settings": "Parole un drošība", "navigation_bar.administration": "Pārvaldība", "navigation_bar.advanced_interface": "Atvērt paplašinātā tīmekļa saskarnē", - "navigation_bar.blocks": "Bloķētie lietotāji", + "navigation_bar.automated_deletion": "Automātiska ziņu dzēšana", + "navigation_bar.blocks": "Liegtie lietotāji", "navigation_bar.bookmarks": "Grāmatzīmes", "navigation_bar.direct": "Privātas pieminēšanas", - "navigation_bar.domain_blocks": "Bloķētie domēni", + "navigation_bar.domain_blocks": "Liegtie domēni", "navigation_bar.favourites": "Izlase", "navigation_bar.filters": "Apklusinātie vārdi", "navigation_bar.follow_requests": "Sekošanas pieprasījumi", "navigation_bar.followed_tags": "Sekojamie tēmturi", "navigation_bar.follows_and_followers": "Seko un sekotāji", + "navigation_bar.import_export": "Imports un eksports", "navigation_bar.lists": "Saraksti", "navigation_bar.logout": "Iziet", "navigation_bar.moderation": "Satura pārraudzība", @@ -508,6 +519,7 @@ "navigation_bar.mutes": "Apklusinātie lietotāji", "navigation_bar.opened_in_classic_interface": "Ieraksti, konti un citas noteiktas lapas pēc noklusējuma tiek atvērtas klasiskajā tīmekļa saskarnē.", "navigation_bar.preferences": "Iestatījumi", + "navigation_bar.privacy_and_reach": "Privātums un sasniedzamība", "navigation_bar.search": "Meklēt", "not_signed_in_indicator.not_signed_in": "Ir jāpiesakās, lai piekļūtu šim resursam.", "notification.admin.report": "{name} ziņoja par {target}", @@ -520,7 +532,8 @@ "notification.favourite_pm.name_and_others_with_link": "{name} un {count, plural, one {# cits} other {# citi}} pievienoja Tavu privāto pieminējumu izlasē", "notification.follow": "{name} uzsāka Tev sekot", "notification.follow_request": "{name} nosūtīja Tev sekošanas pieprasījumu", - "notification.mentioned_you": "{name} pieminēja jūs", + "notification.label.quote": "{name} citēja tavu ierakstu", + "notification.mentioned_you": "{name} pieminēja tevi", "notification.moderation-warning.learn_more": "Uzzināt vairāk", "notification.moderation_warning": "Ir saņemts satura pārraudzības brīdinājums", "notification.moderation_warning.action_delete_statuses": "Daži no Taviem ierakstiem tika noņemti.", @@ -531,6 +544,7 @@ "notification.moderation_warning.action_silence": "Tavs konts tika ierobežots.", "notification.moderation_warning.action_suspend": "Tava konta darbība tika apturēta.", "notification.own_poll": "Tava aptauja ir noslēgusies", + "notification.quoted_update": "{name} laboja tevis citētu ierakstu", "notification.reblog": "{name} pastiprināja Tavu ierakstu", "notification.relationships_severance_event": "Zaudēti savienojumi ar {name}", "notification.relationships_severance_event.learn_more": "Uzzināt vairāk", @@ -618,9 +632,14 @@ "privacy.direct.long": "Visi ierakstā pieminētie", "privacy.private.long": "Tikai Tavi sekotāji", "privacy.private.short": "Sekotāji", - "privacy.public.long": "Jebkurš Mastodon un ārpus tā", - "privacy.public.short": "Redzams visiem", + "privacy.public.long": "Jebkurš Mastodon platformā un ārpus tās", + "privacy.public.short": "Publisks", + "privacy.quote.anyone": "{visibility}, jebkurš var citēt", + "privacy.quote.disabled": "{visibility}, aizliegta citēšana", + "privacy.quote.limited": "{visibility}, ierobežota citēšana", "privacy.unlisted.additional": "Šis uzvedas tieši kā publisks, izņemot to, ka ieraksts neparādīsies tiešraides barotnēs vai tēmturos, izpētē vai Mastodon meklēšanā, pat ja esi to norādījis visa konta ietvaros.", + "privacy.unlisted.long": "Netiks rādīts Mastodon meklēšanas rezultātos, populārākajos, un publiskajās laikjoslās", + "privacy.unlisted.short": "Klusi publisks", "privacy_policy.last_updated": "Pēdējo reizi atjaunināta {date}", "privacy_policy.title": "Privātuma politika", "recommended": "Ieteicams", @@ -636,11 +655,12 @@ "relative_time.minutes": "{number}m", "relative_time.seconds": "{number}s", "relative_time.today": "šodien", + "remove_quote_hint.button_label": "Sapratu", "reply_indicator.attachments": "{count, plural, zero{# pielikumu} one {# pielikums} other {# pielikumi}}", "reply_indicator.cancel": "Atcelt", "reply_indicator.poll": "Aptauja", - "report.block": "Bloķēt", - "report.block_explanation": "Tu neredzēsi viņu ierakstus. Viņi nevarēs redzēt Tavus ierakstus vai sekot tev. Viņi varēs saprast, ka ir liegti.", + "report.block": "Liegt", + "report.block_explanation": "Tu neredzēsi viņu ierakstus. Viņi nevarēs redzēt Tavus ierakstus vai sekot Tev. Viņi varēs saprast, ka ir liegti.", "report.categories.legal": "Tiesisks", "report.categories.other": "Citi", "report.categories.spam": "Mēstule", @@ -721,11 +741,11 @@ "status.admin_account": "Atvērt @{name} satura pārraudzības saskarni", "status.admin_domain": "Atvērt {domain} satura pārraudzības saskarni", "status.admin_status": "Atvērt šo ziņu satura pārraudzības saskarnē", - "status.block": "Bloķēt @{name}", + "status.all_disabled": "Pastiprināšana un citēšana ir atspējota", + "status.block": "Liegt @{name}", "status.bookmark": "Grāmatzīme", "status.cancel_reblog_private": "Nepastiprināt", "status.cannot_reblog": "Šo ierakstu nevar pastiprināt", - "status.context.loading": "Pārbauda, vai ir vairāk atbilžu", "status.continued_thread": "Turpināts pavediens", "status.copy": "Ievietot ieraksta saiti starpliktuvē", "status.delete": "Dzēst", @@ -736,7 +756,6 @@ "status.edited": "Pēdējoreiz labots {date}", "status.edited_x_times": "Labots {count, plural, zero {{count} reižu} one {{count} reizi} other {{count} reizes}}", "status.favourite": "Izlasē", - "status.favourites": "{count, plural, one {izlasē} other {izlasēs}}", "status.filter": "Atlasīt šo ierakstu", "status.history.created": "{name} izveidoja {date}", "status.history.edited": "{name} laboja {date}", @@ -750,10 +769,10 @@ "status.mute_conversation": "Apklusināt sarunu", "status.open": "Izvērst šo ierakstu", "status.pin": "Piespraust profilam", + "status.quote_policy_change": "Kurš var citēt", "status.read_more": "Lasīt vairāk", "status.reblog": "Pastiprināt", "status.reblogged_by": "{name} pastiprināja", - "status.reblogs": "{count, plural, zero {pastiprinājumu} one {pastiprinājums} other {pastiprinājumi}}", "status.reblogs.empty": "Neviens vēl nav pastiprinājis šo ierakstu. Kad kāds to izdarīs, šeit tiks parādīti lietotāji.", "status.redraft": "Dzēst un pārrakstīt", "status.remove_bookmark": "Noņemt grāmatzīmi", @@ -814,8 +833,9 @@ "video.volume_down": "Pagriezt klusāk", "video.volume_up": "Pagriezt skaļāk", "visibility_modal.button_title": "Iestatīt redzamību", - "visibility_modal.header": "Redzamība un mijjiedarbība", + "visibility_modal.header": "Redzamība un mijiedarbība", "visibility_modal.quote_followers": "Tikai sekotāji", + "visibility_modal.quote_label": "Kurš var citēt", "visibility_modal.quote_nobody": "Tikai es", "visibility_modal.quote_public": "Ikviens", "visibility_modal.save": "Saglabāt" diff --git a/app/javascript/mastodon/locales/mk.json b/app/javascript/mastodon/locales/mk.json index 67e561dceefd20..21f7d2d5d02e74 100644 --- a/app/javascript/mastodon/locales/mk.json +++ b/app/javascript/mastodon/locales/mk.json @@ -33,7 +33,6 @@ "account.posts": "Тутови", "account.posts_with_replies": "Тутови и реплики", "account.report": "Пријави @{name}", - "account.requested": "Се чека одобрување. Кликни за да одкажиш барање за следење", "account.share": "Сподели @{name} профил", "account.show_reblogs": "Прикажи бустови од @{name}", "account.unblock": "Одблокирај @{name}", @@ -83,7 +82,6 @@ "confirmations.logout.message": "Дали сте сигурни дека сакате да се одјавите?", "confirmations.mute.confirm": "Заќути", "confirmations.unfollow.confirm": "Одследи", - "confirmations.unfollow.message": "Сигурни сте дека ќе го отследите {name}?", "conversation.delete": "Избриши разговор", "conversation.mark_as_read": "Означете како прочитано", "conversation.open": "Прегледај разговор", diff --git a/app/javascript/mastodon/locales/ml.json b/app/javascript/mastodon/locales/ml.json index 63fc97c8abb85e..017ef1ce3f2b35 100644 --- a/app/javascript/mastodon/locales/ml.json +++ b/app/javascript/mastodon/locales/ml.json @@ -44,7 +44,6 @@ "account.posts": "പോസ്റ്റുകൾ", "account.posts_with_replies": "പോസ്റ്റുകളും മറുപടികളും", "account.report": "റിപ്പോർട്ട് ചെയ്യുക @{name}", - "account.requested": "അനുവാദത്തിനായി കാത്തിരിക്കുന്നു. പിന്തുടരാനുള്ള അപേക്ഷ റദ്ദാക്കുവാൻ ഞെക്കുക", "account.share": "@{name} ന്റെ പ്രൊഫൈൽ പങ്കിടുക", "account.show_reblogs": "@{name} ൽ നിന്നുള്ള ബൂസ്റ്റുകൾ കാണിക്കുക", "account.unblock": "@{name} തടഞ്ഞത് മാറ്റുക", @@ -140,7 +139,6 @@ "confirmations.mute.confirm": "നിശ്ശബ്ദമാക്കുക", "confirmations.redraft.confirm": "മായിച്ച് മാറ്റങ്ങൾ വരുത്തി വീണ്ടും എഴുതുക", "confirmations.unfollow.confirm": "പിന്തുടരുന്നത് നിര്‍ത്തുക", - "confirmations.unfollow.message": "നിങ്ങൾ {name} യെ പിന്തുടരുന്നത് നിർത്തുവാൻ തീർച്ചയായും തീരുമാനിച്ചുവോ?", "conversation.delete": "സംഭാഷണം മായിക്കുക", "conversation.mark_as_read": "വായിച്ചതായി അടയാളപ്പെടുത്തുക", "conversation.open": "സംഭാഷണം കാണുക", diff --git a/app/javascript/mastodon/locales/mr.json b/app/javascript/mastodon/locales/mr.json index 28d3ae9ab37a64..6a762c15273a56 100644 --- a/app/javascript/mastodon/locales/mr.json +++ b/app/javascript/mastodon/locales/mr.json @@ -49,7 +49,6 @@ "account.posts": "Toots", "account.posts_with_replies": "Toots and replies", "account.report": "@{name} ची तक्रार करा", - "account.requested": "Awaiting approval", "account.requested_follow": "{name} ने आपल्याला फॉलो करण्याची रिक्वेस्ट केली आहे", "account.share": "@{name} चे प्रोफाइल शेअर करा", "account.show_reblogs": "{name}चे सर्व बुस्ट्स दाखवा", diff --git a/app/javascript/mastodon/locales/ms.json b/app/javascript/mastodon/locales/ms.json index 2ffdf6cfd0b4bf..71013cf3e6db15 100644 --- a/app/javascript/mastodon/locales/ms.json +++ b/app/javascript/mastodon/locales/ms.json @@ -58,7 +58,6 @@ "account.posts": "Hantaran", "account.posts_with_replies": "Hantaran dan balasan", "account.report": "Laporkan @{name}", - "account.requested": "Menunggu kelulusan. Klik untuk batalkan permintaan ikut", "account.requested_follow": "{name} has requested to follow you", "account.share": "Kongsi profil @{name}", "account.show_reblogs": "Tunjukkan galakan daripada @{name}", @@ -95,25 +94,11 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Terangkan untuk OKU penglihatan…", "alt_text_modal.done": "Selesai", "announcement.announcement": "Pengumuman", - "annual_report.summary.archetype.booster": "Si pencapap", - "annual_report.summary.archetype.lurker": "Si penghendap", - "annual_report.summary.archetype.oracle": "Si penilik", - "annual_report.summary.archetype.pollster": "Si peninjau", - "annual_report.summary.archetype.replier": "Si peramah", - "annual_report.summary.followers.followers": "pengikut", - "annual_report.summary.followers.total": "sebanyak {count}", - "annual_report.summary.here_it_is": "Ini ulasan {year} anda:", - "annual_report.summary.highlighted_post.by_favourites": "hantaran paling disukai ramai", - "annual_report.summary.highlighted_post.by_reblogs": "hantaran paling digalak ramai", - "annual_report.summary.highlighted_post.by_replies": "hantaran paling dibalas ramai", - "annual_report.summary.highlighted_post.possessive": "oleh", "annual_report.summary.most_used_app.most_used_app": "aplikasi paling banyak digunakan", "annual_report.summary.most_used_hashtag.most_used_hashtag": "tanda pagar paling banyak digunakan", - "annual_report.summary.most_used_hashtag.none": "Tiada", "annual_report.summary.new_posts.new_posts": "hantaran baharu", "annual_report.summary.percentile.text": "Anda berkedudukan pengguna {domain}.", "annual_report.summary.percentile.we_wont_tell_bernie": "Rahsia anda selamat bersama kami. ;)", - "annual_report.summary.thanks": "Terima kasih kerana setia bersama Mastodon!", "attachments_list.unprocessed": "(belum diproses)", "audio.hide": "Sembunyikan audio", "block_modal.remote_users_caveat": "Kami akan meminta pelayan {domain} untuk menghormati keputusan anda. Bagaimanapun, pematuhan tidak dijamin kerana ada pelayan yang mungkin menangani sekatan dengan cara berbeza. Hantaran awam mungkin masih tampak kepada pengguna yang tidak log masuk.", @@ -223,8 +208,6 @@ "confirmations.redraft.message": "Adakah anda pasti anda ingin memadam hantaran ini dan gubal semula? Sukaan dan galakan akan hilang, dan balasan ke hantaran asal akan menjadi yatim.", "confirmations.redraft.title": "Padam & gubah semula hantaran?", "confirmations.unfollow.confirm": "Nyahikut", - "confirmations.unfollow.message": "Adakah anda pasti anda ingin nyahikuti {name}?", - "confirmations.unfollow.title": "Berhenti mengikut pengguna?", "content_warning.hide": "Sorok hantaran", "content_warning.show": "Tunjuk saja", "content_warning.show_more": "Tunjuk lebih", @@ -622,7 +605,6 @@ "status.edit": "Sunting", "status.edited_x_times": "Disunting {count, plural, other {{count} kali}}", "status.favourite": "Suka", - "status.favourites": "{count, plural, other {sukaan}}", "status.filter": "Tapiskan hantaran ini", "status.history.created": "{name} mencipta pada {date}", "status.history.edited": "{name} menyunting pada {date}", @@ -639,7 +621,6 @@ "status.read_more": "Baca lagi", "status.reblog": "Galakkan", "status.reblogged_by": "{name} galakkan", - "status.reblogs": "{count, plural, other {galakan}}", "status.reblogs.empty": "Tiada sesiapa yang galakkan hantaran ini. Apabila ada yang galakkan, hantaran akan muncul di sini.", "status.redraft": "Padam & rangka semula", "status.remove_bookmark": "Buang tanda buku", diff --git a/app/javascript/mastodon/locales/my.json b/app/javascript/mastodon/locales/my.json index a65eab5aa708cd..56da51abe93580 100644 --- a/app/javascript/mastodon/locales/my.json +++ b/app/javascript/mastodon/locales/my.json @@ -51,7 +51,6 @@ "account.posts": "ပို့စ်များ", "account.posts_with_replies": "ပို့စ်နှင့် ရီပလိုင်းများ", "account.report": "တိုင်ကြားမည်{name}", - "account.requested": "ခွင့်ပြုချက်စောင့်နေသည်။ ဖော်လိုးပယ်ဖျက်ရန်နှိပ်ပါ", "account.requested_follow": "{name} က သင့်ကို စောင့်ကြည့်ရန် တောင်းဆိုထားသည်", "account.share": "{name}၏ပရိုဖိုင်ကိုမျှဝေပါ", "account.show_reblogs": "@{name} မှ မျှ၀ေမှုများကို ပြပါ\n", @@ -153,7 +152,6 @@ "confirmations.redraft.confirm": "ဖျက်ပြီး ပြန်လည်ရေးမည်။", "confirmations.redraft.message": "သင် ဒီပိုစ့်ကိုဖျက်ပြီး ပြန်တည်းဖြတ်မှာ သေချာပြီလား။ ကြယ်ပွင့်​တွေ နဲ့ ပြန်မျှ​ဝေမှု​တွေကိုဆုံးရှုံးမည်။မူရင်းပို့စ်ဆီကို ပြန်စာ​တွေမှာလည်း​ \nပိုစ့်ကို​တွေ့ရမည်မဟုတ်​တော့ပါ။.", "confirmations.unfollow.confirm": "စောင့်ကြည့်ခြင်းအား ပယ်ဖျက်မည်", - "confirmations.unfollow.message": "{name} ကို စောင်ကြည့်ခြင်းအား ပယ်ဖျက်မည်မှာသေချာပါသလား။", "conversation.delete": "ဤစကားပြောဆိုမှုကို ဖျက်ပစ်မည်", "conversation.mark_as_read": "ဖတ်ပြီးသားအဖြစ်မှတ်ထားပါ", "conversation.open": "Conversation ကိုကြည့်မည်", diff --git a/app/javascript/mastodon/locales/nan.json b/app/javascript/mastodon/locales/nan.json index 9bce13a8dadef2..366f568f0a44b3 100644 --- a/app/javascript/mastodon/locales/nan.json +++ b/app/javascript/mastodon/locales/nan.json @@ -18,7 +18,7 @@ "account.badges.bot": "機器lâng", "account.badges.group": "群組", "account.block": "封鎖 @{name}", - "account.block_domain": "封鎖網域 {domain}", + "account.block_domain": "封鎖域名 {domain}", "account.block_short": "封鎖", "account.blocked": "Hőng封鎖", "account.blocking": "Teh封鎖", @@ -26,8 +26,9 @@ "account.copy": "Khóo-pih個人資料ê連結", "account.direct": "私人提起 @{name}", "account.disable_notifications": "停止佇 {name} PO文ê時通知我", - "account.domain_blocking": "Teh封鎖ê網域", + "account.domain_blocking": "Teh封鎖ê域名", "account.edit_profile": "編輯個人資料", + "account.edit_profile_short": "編輯", "account.enable_notifications": "佇 {name} PO文ê時通知我", "account.endorse": "用個人資料推薦對方", "account.familiar_followers_many": "Hōo {name1}、{name2},kap {othersCount, plural, other {其他 lí熟似ê # ê lâng}} 跟tuè", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "無PO文", "account.follow": "跟tuè", "account.follow_back": "Tuè tńg去", + "account.follow_back_short": "Tuè tńg去", + "account.follow_request": "請求跟tuè lí", + "account.follow_request_cancel": "取消跟tuè", + "account.follow_request_cancel_short": "取消", + "account.follow_request_short": "請求", "account.followers": "跟tuè lí ê", "account.followers.empty": "Tsit ê用者iáu bô lâng跟tuè。", "account.followers_counter": "Hōo {count, plural, other {{count} ê lâng}}跟tuè", @@ -70,7 +76,6 @@ "account.posts_with_replies": "PO文kap回應", "account.remove_from_followers": "Kā {name} tuì跟tuè lí ê ê內底suá掉", "account.report": "檢舉 @{name}", - "account.requested": "Teh等待審查。Tshi̍h tsi̍t-ē 通取消跟tuè請求", "account.requested_follow": "{name} 請求跟tuè lí", "account.requests_to_follow_you": "請求跟tuè lí", "account.share": "分享 @{name} ê個人資料", @@ -108,25 +113,51 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "請替看有困難ê敘述tsit ê內容…", "alt_text_modal.done": "做好ah", "announcement.announcement": "公告", - "annual_report.summary.archetype.booster": "追求趣味ê", - "annual_report.summary.archetype.lurker": "有讀無PO ê", - "annual_report.summary.archetype.oracle": "先知", - "annual_report.summary.archetype.pollster": "愛發動投票ê", - "annual_report.summary.archetype.replier": "社交ê蝴蝶", - "annual_report.summary.followers.followers": "跟tuè lí ê", - "annual_report.summary.followers.total": "Lóng總有 {count} ê", - "annual_report.summary.here_it_is": "下kha是lí {year} 年ê回顧:", - "annual_report.summary.highlighted_post.by_favourites": "Hōo足tsē lâng收藏ê PO文", - "annual_report.summary.highlighted_post.by_reblogs": "Hōo足tsē lâng轉ê PO文", - "annual_report.summary.highlighted_post.by_replies": "有上tsē回應ê PO文", - "annual_report.summary.highlighted_post.possessive": "{name} ê", + "annual_report.announcement.action_build": "建立我ê Wrapstodon", + "annual_report.announcement.action_dismiss": "毋免,多謝。", + "annual_report.announcement.action_view": "看我ê Wrapstodon", + "annual_report.announcement.description": "發現其他關係lí佇最近tsi̍t年參與Mastodon ê狀況。", + "annual_report.announcement.title": "Wrapstodon {year} 已經kàu ah", + "annual_report.nav_item.badge": "新ê", + "annual_report.shared_page.donate": "寄付", + "annual_report.shared_page.footer": "由 Mastodon 團隊用 {heart} 產生", + "annual_report.summary.archetype.booster.desc_public": "{name} 不斷走tshuē通轉送 ê PO文,靠完美ê對千,放大其他ê創作者。", + "annual_report.summary.archetype.booster.desc_self": " Lí不斷走tshuē通轉送 ê PO文,靠完美ê對千,放大其他ê創作者。", + "annual_report.summary.archetype.booster.name": "射手", + "annual_report.summary.archetype.die_drei_fragezeichen": "???", + "annual_report.summary.archetype.lurker.desc_public": "Guán知 {name} 捌佇某物所在,用伊恬靜ê方法享受Mastodon。", + "annual_report.summary.archetype.lurker.desc_self": "Guán知lí捌佇某物所在,用li恬靜ê方法享受Mastodon。", + "annual_report.summary.archetype.lurker.name": "Stoic主義者", + "annual_report.summary.archetype.oracle.desc_public": "{name} 發表ê新ê PO文較tsē回應,保持 Mastodon tshinn-tshioh kap面對未來。", + "annual_report.summary.archetype.oracle.desc_self": "Lí發表ê新ê PO文較tsē回應,保持 Mastodon tshinn-tshioh kap面對未來。", + "annual_report.summary.archetype.oracle.name": "先知", + "annual_report.summary.archetype.pollster.desc_public": "{name} 建立投票較tsē其他PO文類型,佇Mastodon培養好奇。", + "annual_report.summary.archetype.pollster.desc_self": "Lí建立投票較tsē其他PO文類型,佇Mastodon培養好奇。", + "annual_report.summary.archetype.pollster.name": "思考者", + "annual_report.summary.archetype.replier.desc_public": "{name} 定定應別lâng ê PO文,用新ê討論kā Mastodon授粉。", + "annual_report.summary.archetype.replier.desc_self": "You 定定應別lâng ê PO文,用新ê討論kā Mastodon授粉。", + "annual_report.summary.archetype.replier.name": "Ia̍h仔", + "annual_report.summary.archetype.reveal": "顯露我ê原形", + "annual_report.summary.archetype.reveal_description": "感謝lí成做Mastodon ê一部份!Tsit-má tshuē出lí佇 {year} 年表現ê原形。", + "annual_report.summary.archetype.title_public": "{name} ê原形", + "annual_report.summary.archetype.title_self": "Lí ê原形", + "annual_report.summary.close": "關", + "annual_report.summary.copy_link": "Khóo-pih連結", + "annual_report.summary.followers.new_followers": "{count, plural, other {新ê跟tuè ê}}", + "annual_report.summary.highlighted_post.boost_count": "Tsit篇PO文予lâng轉送 {count, plural, other {# kái}}。", + "annual_report.summary.highlighted_post.favourite_count": "Tsit篇PO文予lâng收藏 {count, plural, other {# kái}}。", + "annual_report.summary.highlighted_post.reply_count": "Tsit篇PO文得著 {count, plural, other {# 篇回應}}。", + "annual_report.summary.highlighted_post.title": "上hang ê PO文", "annual_report.summary.most_used_app.most_used_app": "上tsē lâng用ê app", "annual_report.summary.most_used_hashtag.most_used_hashtag": "上tsia̍p用ê hashtag", - "annual_report.summary.most_used_hashtag.none": "無", + "annual_report.summary.most_used_hashtag.used_count": "Lí佇{count, plural, other { # 篇PO文}}內包含tsit ê hashtag。", + "annual_report.summary.most_used_hashtag.used_count_public": "{name} 佇{count, plural, other { # 篇PO文}}內包含tsit ê hashtag。", "annual_report.summary.new_posts.new_posts": "新ê PO文", "annual_report.summary.percentile.text": "Tse 予lí變做 {domain} ê用戶ê ", "annual_report.summary.percentile.we_wont_tell_bernie": "Gún bē kā Bernie講。", - "annual_report.summary.thanks": "多謝成做Mastodon ê成員!", + "annual_report.summary.share_elsewhere": "分享kàu別位", + "annual_report.summary.share_message": "我得著 {archetype} ê典型!", + "annual_report.summary.share_on_mastodon": "佇Mastodon分享", "attachments_list.unprocessed": "(Iáu bē處理)", "audio.hide": "Tshàng聲音", "block_modal.remote_users_caveat": "Guán ē要求服侍器 {domain} 尊重lí ê決定。但是bô法度保證ta̍k ê服侍器lóng遵守,因為tsi̍t-kuá服侍器huân-sè用別款方法處理封鎖。公開ê PO文可能iáu是ē hōo bô登入ê用者看著。", @@ -152,6 +183,8 @@ "bundle_modal_error.close": "關", "bundle_modal_error.message": "Tī載入tsit ê畫面ê時起錯誤。", "bundle_modal_error.retry": "Koh試", + "carousel.current": "頁面 {current, number} / {max, number}", + "carousel.slide": "{max, number} 頁內底ê第 {current, number} 頁", "closed_registrations.other_server_instructions": "因為Mastodon非中心化,所以lí ē當tī別ê服侍器建立口座,iáu ē當kap tsit ê服侍器來往。", "closed_registrations_modal.description": "Tann bē當tī {domain} 建立新ê口座,m̄-koh著記得,lí bô需要 {domain} 服侍器ê帳號,mā ē當用 Mastodon。", "closed_registrations_modal.find_another_server": "Tshuē別ê服侍器", @@ -168,6 +201,8 @@ "column.edit_list": "編輯列單", "column.favourites": "Siōng kah意", "column.firehose": "Tsit-má ê動態", + "column.firehose_local": "Tsit ê服侍器tsit-má ê動態", + "column.firehose_singular": "Tsit-má ê動態", "column.follow_requests": "跟tuè請求", "column.home": "頭頁", "column.list_members": "管理列單ê成員", @@ -187,6 +222,7 @@ "community.column_settings.local_only": "Kan-ta展示本地ê", "community.column_settings.media_only": "Kan-ta展示媒體", "community.column_settings.remote_only": "Kan-ta展示遠距離ê", + "compose.error.blank_post": "PO文bē當空白。", "compose.language.change": "換語言", "compose.language.search": "Tshiau-tshuē語言……", "compose.published.body": "成功PO文。", @@ -239,6 +275,11 @@ "confirmations.missing_alt_text.secondary": "就按呢PO出去", "confirmations.missing_alt_text.title": "Kám beh加添說明文字?", "confirmations.mute.confirm": "消音", + "confirmations.private_quote_notify.cancel": "轉去編輯", + "confirmations.private_quote_notify.confirm": "發布PO文", + "confirmations.private_quote_notify.do_not_show_again": "Mài koh展示tsit ê訊息", + "confirmations.private_quote_notify.message": "Lí所引用kap提起ê ē受通知,而且ē當看lí êPO文,就算in無跟tuè汝。", + "confirmations.private_quote_notify.title": "Kám beh kap跟tuè lí ê hām所提起ê用者分享?", "confirmations.quiet_post_quote_info.dismiss": "請mài koh提醒我", "confirmations.quiet_post_quote_info.got_it": "知ah", "confirmations.quiet_post_quote_info.message": "Nā是引用無tī公共時間線內底êPO文,lí êPO文bē當tī趨勢ê時間線顯示。", @@ -252,9 +293,12 @@ "confirmations.revoke_quote.confirm": "Thâi掉PO文", "confirmations.revoke_quote.message": "Tsit ê動作bē當復原。", "confirmations.revoke_quote.title": "Kám beh thâi掉PO文?", + "confirmations.unblock.confirm": "取消封鎖", + "confirmations.unblock.title": "取消封鎖 {name}?", "confirmations.unfollow.confirm": "取消跟tuè", - "confirmations.unfollow.message": "Lí kám確定無愛跟tuè {name}?", - "confirmations.unfollow.title": "Kám beh取消跟tuè tsit ê用者?", + "confirmations.unfollow.title": "取消跟tuè {name}?", + "confirmations.withdraw_request.confirm": "Kā跟tuè請求收tńg去", + "confirmations.withdraw_request.title": "Kám beh收tńg去跟tuè {name} ê請求?", "content_warning.hide": "Am-khàm PO文", "content_warning.show": "Mā tio̍h顯示", "content_warning.show_more": "其他內容", @@ -325,7 +369,8 @@ "empty_column.bookmarked_statuses": "Lí iáu無加添任何冊籤。Nā是lí加添冊籤,伊ē佇tsia顯示。", "empty_column.community": "本站時間線是空ê。緊來公開PO文oh!", "empty_column.direct": "Lí iáu無任何ê私人訊息。Nā是lí送á是收著私人訊息,ē佇tsia顯示。.", - "empty_column.domain_blocks": "Iáu無封鎖任何網域。", + "empty_column.disabled_feed": "Tsit ê feed已經hōo lí ê服侍器ê管理員停用。", + "empty_column.domain_blocks": "Iáu無封鎖任何域名。", "empty_column.explore_statuses": "目前iáu無有流行ê趨勢,請sió等tsi̍t-ē,koh確認。", "empty_column.favourited_statuses": "Lí iáu無加添任何收藏 ê PO文。Nā是lí加收藏,伊ē佇tsia顯示。", "empty_column.favourites": "Iáu無lâng收藏tsit篇PO文。Nā是有lâng收藏,ē佇tsia顯示。", @@ -338,6 +383,7 @@ "empty_column.notification_requests": "清hōo空ah!內底無物件。若是lí收著新ê通知,ē根據lí ê設定,佇tsia出現。", "empty_column.notifications": "Lí iáu無收著任何通知。Nā別lâng kap lí互動,lí ē佇tsia看著。", "empty_column.public": "內底無物件!寫beh公開ê PO文,á是主動跟tuè別ê服侍器ê用者,來加添內容。", + "error.no_hashtag_feed_access": "加入á是登入,來看kap跟tuè tsit ê hashtag。", "error.unexpected_crash.explanation": "因為原始碼內底有錯誤,á是瀏覽器相容出tshê,tsit頁bē當正確顯示。", "error.unexpected_crash.explanation_addons": "Tsit頁bē當正確顯示,可能是瀏覽器附ê功能,á是自動翻譯工具所致。", "error.unexpected_crash.next_steps": "請試更新tsit頁。若是bē當改善,lí iáu是ē當改使用無kâng ê瀏覽器,á是app,來用Mastodon。", @@ -349,11 +395,9 @@ "explore.trending_links": "新聞", "explore.trending_statuses": "PO文", "explore.trending_tags": "Hashtag", + "featured_carousel.current": "PO文 {current, number} / {max, number}", "featured_carousel.header": "{count, plural, one {{counter} 篇} other {{counter} 篇}} 釘起來ê PO文", - "featured_carousel.next": "下tsi̍t ê", - "featured_carousel.post": "PO文", - "featured_carousel.previous": "頂tsi̍t ê", - "featured_carousel.slide": "{total} 內底ê {index}", + "featured_carousel.slide": "{max, number} 篇PO文內底ê第 {current, number} 篇", "filter_modal.added.context_mismatch_explanation": "Tsit ê過濾器類別bē當適用佇lí所接近使用ê PO文ê情境。若是lí mā beh佇tsit ê情境過濾tsit ê PO文,lí著編輯過濾器。.", "filter_modal.added.context_mismatch_title": "本文無sio合!", "filter_modal.added.expired_explanation": "Tsit ê過濾器類別過期ah,lí需要改到期ê日期來繼續用。", @@ -396,6 +440,7 @@ "follow_suggestions.who_to_follow": "Thang tuè ê", "followed_tags": "跟tuè ê hashtag", "footer.about": "概要", + "footer.about_this_server": "關係本站", "footer.directory": "個人資料ê目錄", "footer.get_app": "The̍h著app", "footer.keyboard_shortcuts": "鍵盤kiu-té khí (shortcut)", @@ -497,6 +542,7 @@ "keyboard_shortcuts.toggle_hidden": "顯示/隱藏內容警告後壁ê PO文", "keyboard_shortcuts.toggle_sensitivity": "顯示/tshàng媒體", "keyboard_shortcuts.toot": "PO新PO文", + "keyboard_shortcuts.top": "Súa kàu列單ê頭", "keyboard_shortcuts.translate": "kā PO文翻譯", "keyboard_shortcuts.unfocus": "離開輸入框仔/tshiau-tshuē格仔", "keyboard_shortcuts.up": "佇列單內kā suá khah面頂", @@ -745,7 +791,9 @@ "privacy.unlisted.short": "恬靜公開", "privacy_policy.last_updated": "上尾更新tī:{date}", "privacy_policy.title": "隱私權政策", + "quote_error.edit": "佇編輯PO文ê時陣bē當加引文。", "quote_error.poll": "有投票ê PO文bē當引用。", + "quote_error.private_mentions": "私訊bē當允准引用。", "quote_error.quote": "Tsi̍t改kan-ta ē當引用tsi̍t篇PO文。", "quote_error.unauthorized": "Lí bô權利引用tsit篇PO文。", "quote_error.upload": "有媒體附件ê PO文無允准引用。", @@ -864,8 +912,13 @@ "status.cancel_reblog_private": "取消轉送", "status.cannot_quote": "Lí bô允准引用tsit篇PO文。", "status.cannot_reblog": "Tsit篇PO文bē當轉送", - "status.context.load_new_replies": "有新ê回應", - "status.context.loading": "Leh檢查其他ê回應", + "status.contains_quote": "包含引用", + "status.context.loading": "載入其他回應", + "status.context.loading_error": "Bē當載入新回應", + "status.context.loading_success": "新ê回應載入ah", + "status.context.more_replies_found": "Tshuē-tio̍h其他回應", + "status.context.retry": "Koh試", + "status.context.show": "顯示", "status.continued_thread": "接續ê討論線", "status.copy": "Khóo-pih PO文ê連結", "status.delete": "Thâi掉", @@ -878,7 +931,7 @@ "status.edited_x_times": "有編輯 {count, plural, one {{count} kái} other {{count} kái}}", "status.embed": "The̍h相tàu ê (embed)程式碼", "status.favourite": "收藏", - "status.favourites": "{count, plural, other {# 篇收藏}}", + "status.favourites_count": "{count, plural, one {{counter} 篇} other {{counter} 篇}} 收藏", "status.filter": "過濾tsit 篇 PO文", "status.history.created": "{name} 佇 {date} 建立", "status.history.edited": "{name} 佇 {date} 編輯", @@ -893,27 +946,33 @@ "status.open": "Kā PO文展開", "status.quote": "引用", "status.quote.cancel": "取消引用", + "status.quote_error.blocked_account_hint.title": "因為lí有封鎖 @{name},tsit篇PO文受khàm掉。", + "status.quote_error.blocked_domain_hint.title": "因為lí有封鎖 {domain},tsit篇PO文受khàm掉。", "status.quote_error.filtered": "Lí所設定ê過濾器kā tse khàm起來", "status.quote_error.limited_account_hint.action": "Iáu是顯示", "status.quote_error.limited_account_hint.title": "Tsit ê口座予 {domain} ê管理員tshàng起來ah。", + "status.quote_error.muted_account_hint.title": "因為lí有消音 @{name},tsit篇PO文受khàm掉。", "status.quote_error.not_available": "PO文bē當看", "status.quote_error.pending_approval": "PO文當咧送", "status.quote_error.pending_approval_popout.body": "佇Mastodon,lí ē當控制PO文kám beh hōo lâng引用。Tsit篇PO文teh等原文作者允准。", "status.quote_error.revoked": "PO文已經hōo作者thâi掉", "status.quote_followers_only": "Kan-ta tuè我ê ē當引用PO文", "status.quote_manual_review": "作者ē hōo lâng人工審核", + "status.quote_noun": "引用", "status.quote_policy_change": "改通引用ê lâng", "status.quote_post_author": "引用 @{name} ê PO文ah", "status.quote_private": "私人PO文bē當引用", - "status.quotes": "{count, plural, other {# 篇引用ê PO文}}", "status.quotes.empty": "Iáu無lâng引用tsit篇PO文。Nā是有lâng引用,ē佇tsia顯示。.", + "status.quotes.local_other_disclaimer": "Hōo作者拒絕引用ê引文bē當顯示。", + "status.quotes.remote_other_disclaimer": "Kan-ta tuì {domain} 來ê引文tsiah保證佇tsia顯示。Hōo作者拒絕ê引文buē顯示。", + "status.quotes_count": "{count, plural, one {{counter} 篇} other {{counter} 篇}} 引用", "status.read_more": "讀詳細", "status.reblog": "轉送", "status.reblog_or_quote": "轉送á是引用", "status.reblog_private": "Koh再hām跟tuè ê分享", "status.reblogged_by": "{name} kā轉送ah", - "status.reblogs": "{count, plural, other {# ê 轉送}}", "status.reblogs.empty": "Iáu無lâng轉送tsit篇PO文。Nā是有lâng轉送,ē佇tsia顯示。", + "status.reblogs_count": "{count, plural, one {{counter} 篇} other {{counter} 篇}} 轉送", "status.redraft": "Thâi掉了後重寫", "status.remove_bookmark": "Thâi掉冊籤", "status.remove_favourite": "Tuì收藏內suá掉", @@ -986,6 +1045,8 @@ "video.volume_down": "變khah細聲", "video.volume_up": "變khah大聲", "visibility_modal.button_title": "設定通看ê程度", + "visibility_modal.direct_quote_warning.text": "Nā是lí儲存目前ê設定,引用êPO文ē轉做連結。", + "visibility_modal.direct_quote_warning.title": "引用bē當tàu入去私人ê提起", "visibility_modal.header": "通看ê程度kap互動", "visibility_modal.helper.direct_quoting": "Mastodon頂發布ê私人提起bē當hōo別lâng引用。", "visibility_modal.helper.privacy_editing": "Po文發布了後,bē當改通看ê程度。", diff --git a/app/javascript/mastodon/locales/ne.json b/app/javascript/mastodon/locales/ne.json index a0b99c597385e0..2d861e470be3a1 100644 --- a/app/javascript/mastodon/locales/ne.json +++ b/app/javascript/mastodon/locales/ne.json @@ -57,7 +57,6 @@ "account.posts_with_replies": "पोस्ट र जवाफहरू", "account.remove_from_followers": "{name}लाई फलोअरहरूबाट हटाउनुहोस्", "account.report": "@{name}लाई रिपोर्ट गर्नुहोस्", - "account.requested": "स्वीकृतिको पर्खाइमा। फलो अनुरोध रद्द गर्न क्लिक गर्नुहोस्", "account.requested_follow": "{name} ले तपाईंलाई फलो गर्न अनुरोध गर्नुभएको छ", "account.share": "@{name} को प्रोफाइल सेयर गर्नुहोस्", "account.show_reblogs": "@{name} को बूस्टहरू देखाउनुहोस्", @@ -78,8 +77,6 @@ "alert.unexpected.message": "एउटा अनपेक्षित त्रुटि भयो।", "alt_text_modal.cancel": "रद्द गर्नुहोस्", "announcement.announcement": "घोषणा", - "annual_report.summary.followers.followers": "फलोअरहरु", - "annual_report.summary.highlighted_post.by_reblogs": "सबैभन्दा बढि बूस्ट गरिएको पोस्ट", "annual_report.summary.new_posts.new_posts": "नयाँ पोस्टहरू", "block_modal.remote_users_caveat": "हामी सर्भर {domain} लाई तपाईंको निर्णयको सम्मान गर्न सोध्नेछौं। तर, हामी अनुपालनको ग्यारेन्टी दिन सक्दैनौं किनभने केही सर्भरहरूले ब्लकहरू फरक रूपमा ह्यान्डल गर्न सक्छन्। सार्वजनिक पोस्टहरू लग इन नभएका प्रयोगकर्ताहरूले देख्न सक्छन्।", "block_modal.show_less": "कम देखाउनुहोस्", @@ -144,8 +141,6 @@ "confirmations.redraft.confirm": "मेटाएर पुन: ड्राफ्ट गर्नुहोस्", "confirmations.redraft.title": "पोस्ट मेटाएर पुन: ड्राफ्ट गर्ने?", "confirmations.unfollow.confirm": "अनफलो गर्नुहोस्", - "confirmations.unfollow.message": "के तपाइँ पक्का हुनुहुन्छ कि तपाइँ {name}लाई अनफलो गर्न चाहनुहुन्छ?", - "confirmations.unfollow.title": "प्रयोगकर्तालाई अनफलो गर्ने?", "disabled_account_banner.account_settings": "खाता सेटिङहरू", "empty_column.direct": "तपाईंले अहिलेसम्म कुनै पनि प्राइवेट उल्लेखहरू प्राप्त गर्नुभएको छैन। तपाईंले कुनै प्राप्त गरेपछि त्यो यहाँ देखिनेछ।", "empty_column.follow_requests": "तपाईंले अहिलेसम्म कुनै पनि फलो अनुरोधहरू प्राप्त गर्नुभएको छैन। तपाईंले कुनै प्राप्त गरेपछि त्यो यहाँ देखिनेछ।", @@ -213,7 +208,6 @@ "status.mute_conversation": "कुराकानी म्यूट गर्नुहोस्", "status.reblog": "बूस्ट गर्नुहोस्", "status.reblogged_by": "{name} ले बूस्ट गर्नुभएको", - "status.reblogs": "{count, plural, one {बूस्ट} other {बूस्टहरू}}", "status.reblogs.empty": "यो पोस्टलाई अहिलेसम्म कसैले पनि बूस्ट गरेको छैन। कसैले बूस्ट गरेमा तिनीहरू यहाँ देखिनेछन्।", "status.unmute_conversation": "कुराकानी अनम्यूट गर्नुहोस्", "visibility_modal.quote_followers": "फलोअरहरु मात्र" diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index f61f7469d52684..792f6afd1dd016 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Geen melding meer geven wanneer @{name} een bericht plaatst", "account.domain_blocking": "Server geblokkeerd", "account.edit_profile": "Profiel bewerken", + "account.edit_profile_short": "Bewerken", "account.enable_notifications": "Geef een melding wanneer @{name} een bericht plaatst", "account.endorse": "Op profiel weergeven", "account.familiar_followers_many": "Gevolgd door {name1}, {name2} en {othersCount, plural, one {één ander bekend account} other {# andere bekende accounts}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Geen berichten", "account.follow": "Volgen", "account.follow_back": "Terugvolgen", + "account.follow_back_short": "Terugvolgen", + "account.follow_request": "Volgverzoek", + "account.follow_request_cancel": "Verzoek annuleren", + "account.follow_request_cancel_short": "Annuleren", + "account.follow_request_short": "Verzoek", "account.followers": "Volgers", "account.followers.empty": "Deze gebruiker heeft nog geen volgers of heeft deze verborgen.", "account.followers_counter": "{count, plural, one {{counter} volger} other {{counter} volgers}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Reacties", "account.remove_from_followers": "{name} als volger verwijderen", "account.report": "@{name} rapporteren", - "account.requested": "Wachten op goedkeuring. Klik om het volgverzoek te annuleren", "account.requested_follow": "{name} wil jou graag volgen", "account.requests_to_follow_you": "Wil jou graag volgen", "account.share": "Account van @{name} delen", @@ -108,25 +113,52 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Beschrijf dit voor blinden en slechtzienden…", "alt_text_modal.done": "Klaar", "announcement.announcement": "Mededeling", - "annual_report.summary.archetype.booster": "De cool-hunter", - "annual_report.summary.archetype.lurker": "De lurker", - "annual_report.summary.archetype.oracle": "Het orakel", - "annual_report.summary.archetype.pollster": "De opiniepeiler", - "annual_report.summary.archetype.replier": "De sociale vlinder", - "annual_report.summary.followers.followers": "volgers", - "annual_report.summary.followers.total": "totaal {count}", - "annual_report.summary.here_it_is": "Hier is jouw terugblik op {year}:", - "annual_report.summary.highlighted_post.by_favourites": "bericht met de meeste favorieten", - "annual_report.summary.highlighted_post.by_reblogs": "bericht met de meeste boosts", - "annual_report.summary.highlighted_post.by_replies": "bericht met de meeste reacties", - "annual_report.summary.highlighted_post.possessive": "{name}'s", + "annual_report.announcement.action_build": "Bouw mijn Wrapstodon", + "annual_report.announcement.action_dismiss": "Nee, bedankt", + "annual_report.announcement.action_view": "Bekijk mijn Wrapstodon", + "annual_report.announcement.description": "Ontdek meer over jouw engagement op Mastodon over het afgelopen jaar.", + "annual_report.announcement.title": "Wrapstodon {year} is gearriveerd", + "annual_report.nav_item.badge": "Nieuw", + "annual_report.shared_page.donate": "Doneren", + "annual_report.shared_page.footer": "Met {heart} gegenereerd door het Mastodonteam", + "annual_report.shared_page.footer_server_info": "{username} gebruikt {domain}, een van de vele door Mastodon mogelijk gemaakte gemeenschappen.", + "annual_report.summary.archetype.booster.desc_public": "{name} bleef op jacht naar berichten om te kunnen boosten, waardoor het geluid van andere gebruikers werd versterkt.", + "annual_report.summary.archetype.booster.desc_self": "Jij bleef op jacht naar berichten om te kunnen boosten, waardoor je het geluid van andere gebruikers versterkte.", + "annual_report.summary.archetype.booster.name": "De Boogschutter", + "annual_report.summary.archetype.die_drei_fragezeichen": "???", + "annual_report.summary.archetype.lurker.desc_public": "We weten dat {name} ergens in stilte van Mastodon aan het genieten was.", + "annual_report.summary.archetype.lurker.desc_self": "We weten dat je ergens in stilte van Mastodon aan het genieten was.", + "annual_report.summary.archetype.lurker.name": "De Stoïcijn", + "annual_report.summary.archetype.oracle.desc_public": "{name} heeft meer nieuwe berichten dan reacties geplaatst, waardoor Mastodon fris en toekomstgericht blijft.", + "annual_report.summary.archetype.oracle.desc_self": "Je hebt meer nieuwe berichten dan reacties geplaatst, waardoor Mastodon fris en toekomstgericht blijft.", + "annual_report.summary.archetype.oracle.name": "Het Orakel", + "annual_report.summary.archetype.pollster.desc_public": "{name} heeft meer polls aangemaakt dan andere soorten berichten, en daarmee de nieuwsgierigheid op Mastodon gecultiveerd.", + "annual_report.summary.archetype.pollster.desc_self": "Je hebt meer polls aangemaakt dan andere soorten berichten, en daarmee de nieuwsgierigheid op Mastodon gecultiveerd.", + "annual_report.summary.archetype.pollster.name": "De Dromer", + "annual_report.summary.archetype.replier.desc_public": "{name} reageerde regelmatig op berichten van andere mensen, waardoor nieuwe discussies op Mastodon tot bloei kwamen.", + "annual_report.summary.archetype.replier.desc_self": "Je reageerde regelmatig op berichten van andere mensen, waardoor nieuwe discussies op Mastodon tot bloei kwamen.", + "annual_report.summary.archetype.replier.name": "De Vlinder", + "annual_report.summary.archetype.reveal": "Onthul mijn archetype", + "annual_report.summary.archetype.reveal_description": "Bedankt dat je deel uitmaakt van Mastodon! Tijd om te ontdekken welk archetype je in {year} belichaamde.", + "annual_report.summary.archetype.title_public": "Het archetype van {name}", + "annual_report.summary.archetype.title_self": "Jouw archetype", + "annual_report.summary.close": "Sluiten", + "annual_report.summary.copy_link": "Link kopiëren", + "annual_report.summary.followers.new_followers": "{count, plural, one {nieuwe volger} other {nieuwe volgers}}", + "annual_report.summary.highlighted_post.boost_count": "Dit bericht is {count, plural, one {één keer} other {# keer}} geboost.", + "annual_report.summary.highlighted_post.favourite_count": "Dit bericht is {count, plural, one {één keer} other {# keer}} als favoriet gemarkeerd.", + "annual_report.summary.highlighted_post.reply_count": "Dit bericht heeft {count, plural, one {één reactie} other {# reacties}}.", + "annual_report.summary.highlighted_post.title": "Meest populaire berichten", "annual_report.summary.most_used_app.most_used_app": "meest gebruikte app", "annual_report.summary.most_used_hashtag.most_used_hashtag": "meest gebruikte hashtag", - "annual_report.summary.most_used_hashtag.none": "Geen", + "annual_report.summary.most_used_hashtag.used_count": "Je hebt deze hashtag in {count, plural, one{één bericht} other {# berichten}} gebruikt.", + "annual_report.summary.most_used_hashtag.used_count_public": "{name} heeft deze hashtag in {count, plural, one {één bericht} other {# berichten}} gebruikt.", "annual_report.summary.new_posts.new_posts": "nieuwe berichten", "annual_report.summary.percentile.text": "Hiermee behoor je tot de top van {domain}.", "annual_report.summary.percentile.we_wont_tell_bernie": "We zullen Bernie niets vertellen.", - "annual_report.summary.thanks": "Bedankt dat je deel uitmaakt van Mastodon!", + "annual_report.summary.share_elsewhere": "Ergens anders delen", + "annual_report.summary.share_message": "Ik heb het archetype {archetype}!", + "annual_report.summary.share_on_mastodon": "Op Mastodon delen", "attachments_list.unprocessed": "(niet verwerkt)", "audio.hide": "Audio verbergen", "block_modal.remote_users_caveat": "We vragen de server {domain} om je besluit te respecteren. Het naleven hiervan is echter niet gegarandeerd, omdat sommige servers blokkades anders kunnen interpreteren. Openbare berichten zijn mogelijk nog steeds zichtbaar voor niet-ingelogde gebruikers.", @@ -152,6 +184,8 @@ "bundle_modal_error.close": "Sluiten", "bundle_modal_error.message": "Er ging iets mis tijdens het laden van dit scherm.", "bundle_modal_error.retry": "Opnieuw proberen", + "carousel.current": "Bericht {current, number} / {max, number}", + "carousel.slide": "Bericht {current, number} van {max, number}", "closed_registrations.other_server_instructions": "Omdat Mastodon gedecentraliseerd is, kun je op een andere server een account registreren en vanaf daar nog steeds met deze server communiceren.", "closed_registrations_modal.description": "Momenteel is het niet mogelijk om op {domain} een account aan te maken. Hou echter in gedachte dat om Mastodon te kunnen gebruiken het niet een vereiste is om op {domain} een account te hebben.", "closed_registrations_modal.find_another_server": "Een andere server zoeken", @@ -168,6 +202,8 @@ "column.edit_list": "Lijst bewerken", "column.favourites": "Favorieten", "column.firehose": "Openbare tijdlijnen", + "column.firehose_local": "Lokale tijdlijn", + "column.firehose_singular": "Openbare tijdlijn", "column.follow_requests": "Volgverzoeken", "column.home": "Start", "column.list_members": "Lijstleden beheren", @@ -187,6 +223,7 @@ "community.column_settings.local_only": "Alleen lokaal", "community.column_settings.media_only": "Alleen media", "community.column_settings.remote_only": "Alleen andere servers", + "compose.error.blank_post": "Bericht mag niet leeg zijn.", "compose.language.change": "Taal veranderen", "compose.language.search": "Talen zoeken...", "compose.published.body": "Bericht gepubliceerd.", @@ -239,10 +276,15 @@ "confirmations.missing_alt_text.secondary": "Toch plaatsen", "confirmations.missing_alt_text.title": "Alt-tekst toevoegen?", "confirmations.mute.confirm": "Negeren", + "confirmations.private_quote_notify.cancel": "Terug naar bewerken", + "confirmations.private_quote_notify.confirm": "Bericht plaatsen", + "confirmations.private_quote_notify.do_not_show_again": "Dit bericht niet meer aan mij tonen", + "confirmations.private_quote_notify.message": "De persoon die je citeert en andere vermelde personen krijgen een melding en kunnen jouw bericht bekijken, zelfs als ze je niet volgen.", + "confirmations.private_quote_notify.title": "Met volgers en vermelde gebruikers delen?", "confirmations.quiet_post_quote_info.dismiss": "Herinner me er niet nogmaals aan", "confirmations.quiet_post_quote_info.got_it": "Begrepen", "confirmations.quiet_post_quote_info.message": "Wanneer je een minder openbaar bericht citeert, verschijnt jouw bericht niet onder trends.", - "confirmations.quiet_post_quote_info.title": "Minder openbare berichten citeren", + "confirmations.quiet_post_quote_info.title": "Berichten die minder openbaar zijn citeren", "confirmations.redraft.confirm": "Verwijderen en herschrijven", "confirmations.redraft.message": "Weet je zeker dat je dit bericht wilt verwijderen en herschrijven? Je verliest wel de boosts en favorieten, en de reacties op het originele bericht raak je kwijt.", "confirmations.redraft.title": "Bericht verwijderen en herschrijven?", @@ -252,9 +294,12 @@ "confirmations.revoke_quote.confirm": "Bericht verwijderen", "confirmations.revoke_quote.message": "Deze actie kan niet ongedaan worden gemaakt.", "confirmations.revoke_quote.title": "Bericht verwijderen?", + "confirmations.unblock.confirm": "Deblokkeren", + "confirmations.unblock.title": "{name} deblokkeren?", "confirmations.unfollow.confirm": "Ontvolgen", - "confirmations.unfollow.message": "Weet je het zeker dat je {name} wilt ontvolgen?", - "confirmations.unfollow.title": "Gebruiker ontvolgen?", + "confirmations.unfollow.title": "{name} ontvolgen?", + "confirmations.withdraw_request.confirm": "Verzoek intrekken", + "confirmations.withdraw_request.title": "Verzoek intrekken om {name} te volgen?", "content_warning.hide": "Bericht verbergen", "content_warning.show": "Alsnog tonen", "content_warning.show_more": "Meer tonen", @@ -325,6 +370,7 @@ "empty_column.bookmarked_statuses": "Jij hebt nog geen berichten aan je bladwijzers toegevoegd. Wanneer je er een aan jouw bladwijzers toevoegt, valt deze hier te zien.", "empty_column.community": "De lokale tijdlijn is nog leeg. Plaats een openbaar bericht om de spits af te bijten!", "empty_column.direct": "Je hebt nog geen privéberichten. Wanneer je er een verstuurt of ontvangt, komen deze hier te staan.", + "empty_column.disabled_feed": "Deze tijdlijn is uitgeschakeld door je serverbeheerders.", "empty_column.domain_blocks": "Er zijn nog geen geblokkeerde servers.", "empty_column.explore_statuses": "Momenteel zijn er geen trends. Kom later terug!", "empty_column.favourited_statuses": "Jij hebt nog geen favoriete berichten. Wanneer je een bericht als favoriet markeert, valt deze hier te zien.", @@ -338,6 +384,7 @@ "empty_column.notification_requests": "Helemaal leeg! Er is hier niets. Wanneer je nieuwe meldingen ontvangt, verschijnen deze hier volgens jouw instellingen.", "empty_column.notifications": "Je hebt nog geen meldingen. Begin met iemand een gesprek.", "empty_column.public": "Er is hier helemaal niks! Plaatst een openbaar bericht of volg mensen van andere servers om het te vullen", + "error.no_hashtag_feed_access": "Registreren of inloggen om de hashtag te bekijken of te volgen.", "error.unexpected_crash.explanation": "Als gevolg van een bug in onze broncode of als gevolg van een compatibiliteitsprobleem met jouw webbrowser, kan deze pagina niet goed worden weergegeven.", "error.unexpected_crash.explanation_addons": "Deze pagina kon niet correct geladen worden. Deze fout wordt waarschijnlijk door een browser-add-on of een automatische vertalingshulpmiddel veroorzaakt.", "error.unexpected_crash.next_steps": "Probeer deze pagina te vernieuwen. Wanneer dit niet helpt is het nog steeds mogelijk om Mastodon in een andere webbrowser of mobiele app te gebruiken.", @@ -349,11 +396,9 @@ "explore.trending_links": "Nieuws", "explore.trending_statuses": "Berichten", "explore.trending_tags": "Hashtags", + "featured_carousel.current": "Bericht {current, number} / {max, number}", "featured_carousel.header": "{count, plural, one {Vastgezet bericht} other {Vastgezette berichten}}", - "featured_carousel.next": "Volgende", - "featured_carousel.post": "Bericht", - "featured_carousel.previous": "Vorige", - "featured_carousel.slide": "{index} van {total}", + "featured_carousel.slide": "Bericht {current, number} van {max, number}", "filter_modal.added.context_mismatch_explanation": "Deze filtercategorie is niet van toepassing op de context waarin je dit bericht hebt benaderd. Als je wilt dat het bericht ook in deze context wordt gefilterd, moet je het filter bewerken.", "filter_modal.added.context_mismatch_title": "Context komt niet overeen!", "filter_modal.added.expired_explanation": "Deze filtercategorie is verlopen. Je moet de vervaldatum wijzigen om de categorie toe te kunnen passen.", @@ -396,6 +441,9 @@ "follow_suggestions.who_to_follow": "Wie te volgen", "followed_tags": "Gevolgde hashtags", "footer.about": "Over", + "footer.about_mastodon": "Over Mastodon", + "footer.about_server": "Over {domain}", + "footer.about_this_server": "Over", "footer.directory": "Gebruikersgids", "footer.get_app": "App downloaden", "footer.keyboard_shortcuts": "Sneltoetsen", @@ -497,6 +545,7 @@ "keyboard_shortcuts.toggle_hidden": "Inhoudswaarschuwing tonen/verbergen", "keyboard_shortcuts.toggle_sensitivity": "Media tonen/verbergen", "keyboard_shortcuts.toot": "Nieuw bericht schrijven", + "keyboard_shortcuts.top": "Naar het begin van de lijst verplaatsen", "keyboard_shortcuts.translate": "om een bericht te vertalen", "keyboard_shortcuts.unfocus": "Tekst- en zoekveld ontfocussen", "keyboard_shortcuts.up": "Naar boven in de lijst bewegen", @@ -730,7 +779,7 @@ "poll.votes": "{votes, plural, one {# stem} other {# stemmen}}", "poll_button.add_poll": "Peiling toevoegen", "poll_button.remove_poll": "Peiling verwijderen", - "privacy.change": "Privacy voor een bericht aanpassen", + "privacy.change": "Privacy van dit bericht aanpassen", "privacy.direct.long": "Alleen voor mensen die specifiek in het bericht worden vermeld", "privacy.direct.short": "Privébericht", "privacy.private.long": "Alleen jouw volgers", @@ -745,7 +794,9 @@ "privacy.unlisted.short": "Minder openbaar", "privacy_policy.last_updated": "Laatst bijgewerkt op {date}", "privacy_policy.title": "Privacybeleid", + "quote_error.edit": "Citaten kunnen niet tijdens het bewerken van een bericht worden toegevoegd.", "quote_error.poll": "Het is niet mogelijk om polls te citeren.", + "quote_error.private_mentions": "Citaten in privéberichten zijn niet toegestaan.", "quote_error.quote": "Je kunt maar één bericht per keer citeren.", "quote_error.unauthorized": "Je bent niet gemachtigd om dit bericht te citeren.", "quote_error.upload": "Je kunt geen mediabijlage aan een bericht met een citaat toevoegen.", @@ -865,8 +916,12 @@ "status.cannot_quote": "Je bent niet gemachtigd om dit bericht te citeren", "status.cannot_reblog": "Dit bericht kan niet geboost worden", "status.contains_quote": "Bevat citaat", - "status.context.load_new_replies": "Nieuwe reacties beschikbaar", - "status.context.loading": "Op nieuwe reacties aan het controleren", + "status.context.loading": "Meer reacties laden", + "status.context.loading_error": "Kon geen nieuwe reacties laden", + "status.context.loading_success": "Nieuwe reacties geladen", + "status.context.more_replies_found": "Meer reacties gevonden", + "status.context.retry": "Opnieuw proberen", + "status.context.show": "Tonen", "status.continued_thread": "Vervolg van gesprek", "status.copy": "Link naar bericht kopiëren", "status.delete": "Verwijderen", @@ -879,7 +934,7 @@ "status.edited_x_times": "{count, plural, one {{count} keer} other {{count} keer}} bewerkt", "status.embed": "Embed-code verkrijgen", "status.favourite": "Favoriet", - "status.favourites": "{count, plural, one {favoriet} other {favorieten}}", + "status.favourites_count": "{count, plural, one {{counter} favoriet} other {{counter} favorieten}}", "status.filter": "Dit bericht filteren", "status.history.created": "{name} plaatste dit {date}", "status.history.edited": "{name} bewerkte dit {date}", @@ -895,11 +950,14 @@ "status.pin": "Aan profielpagina vastmaken", "status.quote": "Citeren", "status.quote.cancel": "Citeren annuleren", + "status.quote_error.blocked_account_hint.title": "Dit bericht is verborgen, omdat jij @{name} hebt geblokkeerd.", + "status.quote_error.blocked_domain_hint.title": "Dit bericht is verborgen, omdat jij alles van {domain} hebt geblokkeerd.", "status.quote_error.filtered": "Verborgen door een van je filters", "status.quote_error.limited_account_hint.action": "Alsnog tonen", "status.quote_error.limited_account_hint.title": "Dit account is door de moderatoren van {domain} verborgen.", + "status.quote_error.muted_account_hint.title": "Dit bericht is verborgen, omdat jij @{name} hebt genegeerd.", "status.quote_error.not_available": "Bericht niet beschikbaar", - "status.quote_error.pending_approval": "Bericht in afwachting", + "status.quote_error.pending_approval": "Bericht in afwachting van goedkeuring", "status.quote_error.pending_approval_popout.body": "Op Mastodon kun je bepalen of iemand je mag citeren. Dit bericht is in afwachting van de goedkeuring van de oorspronkelijke auteur.", "status.quote_error.revoked": "Bericht verwijderd door auteur", "status.quote_followers_only": "Alleen volgers mogen dit bericht citeren", @@ -908,15 +966,17 @@ "status.quote_policy_change": "Wijzig wie jou mag citeren", "status.quote_post_author": "Citeerde een bericht van @{name}", "status.quote_private": "Citeren van berichten aan alleen volgers is niet mogelijk", - "status.quotes": "{count, plural, one {citaat} other {citaten}}", "status.quotes.empty": "Niemand heeft dit bericht nog geciteerd. Wanneer iemand dat doet, wordt dat hier getoond.", + "status.quotes.local_other_disclaimer": "Citaten afgewezen door de auteur worden niet getoond.", + "status.quotes.remote_other_disclaimer": "Alleen citaten van {domain} worden hier gegarandeerd weergegeven. Citaten afgewezen door de auteur worden niet getoond.", + "status.quotes_count": "{count, plural, one {{counter} citaat} other {{counter} citaten}}", "status.read_more": "Meer lezen", "status.reblog": "Boosten", "status.reblog_or_quote": "Boosten of citeren", "status.reblog_private": "Opnieuw met je volgers delen", "status.reblogged_by": "{name} boostte", - "status.reblogs": "{count, plural, one {boost} other {boosts}}", "status.reblogs.empty": "Niemand heeft dit bericht nog geboost. Wanneer iemand dit doet, valt dat hier te zien.", + "status.reblogs_count": "{count, plural, one {{counter} boost} other {{counter} boosts}}", "status.redraft": "Verwijderen en herschrijven", "status.remove_bookmark": "Bladwijzer verwijderen", "status.remove_favourite": "Verwijderen uit favorieten", @@ -990,6 +1050,8 @@ "video.volume_down": "Volume omlaag", "video.volume_up": "Volume omhoog", "visibility_modal.button_title": "Privacy instellen", + "visibility_modal.direct_quote_warning.text": "Wanneer je de huidige instellingen opslaat wordt het citaat in een link veranderd.", + "visibility_modal.direct_quote_warning.title": "Privéberichten kunnen geen citaten bevatten", "visibility_modal.header": "Zichtbaarheid en interactie", "visibility_modal.helper.direct_quoting": "Privéberichten afkomstig van Mastodon kunnen niet door anderen worden geciteerd.", "visibility_modal.helper.privacy_editing": "De zichtbaarheid kan niet meer worden gewijzigd nadat een bericht is gepubliceerd.", diff --git a/app/javascript/mastodon/locales/nn.json b/app/javascript/mastodon/locales/nn.json index caab03e7cd4f71..7ab269682bf529 100644 --- a/app/javascript/mastodon/locales/nn.json +++ b/app/javascript/mastodon/locales/nn.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Slutt å varsle meg når @{name} skriv innlegg", "account.domain_blocking": "Blokkerer domenet", "account.edit_profile": "Rediger profil", + "account.edit_profile_short": "Rediger", "account.enable_notifications": "Varsle meg når @{name} skriv innlegg", "account.endorse": "Vis på profilen", "account.familiar_followers_many": "Fylgt av {name1}, {name2}, og {othersCount, plural, one {ein annan du kjenner} other {# andre du kjenner}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Ingen innlegg", "account.follow": "Fylg", "account.follow_back": "Fylg tilbake", + "account.follow_back_short": "Fylg tilbake", + "account.follow_request": "Folk som vil fylgja deg", + "account.follow_request_cancel": "Avbrit førespurnaden", + "account.follow_request_cancel_short": "Avbryt", + "account.follow_request_short": "Førespurnad", "account.followers": "Fylgjarar", "account.followers.empty": "Ingen fylgjer denne brukaren enno.", "account.followers_counter": "{count, plural, one {{counter} fylgjar} other {{counter} fylgjarar}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Tut og svar", "account.remove_from_followers": "Fjern {name} frå fylgjarane dine", "account.report": "Rapporter @{name}", - "account.requested": "Ventar på aksept. Klikk for å avbryta fylgjeførespurnaden", "account.requested_follow": "{name} har bedt om å få fylgja deg", "account.requests_to_follow_you": "Folk som vil fylgja deg", "account.share": "Del @{name} sin profil", @@ -108,25 +113,15 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Skriv ei skildring for menneske med synsnedsetjingar…", "alt_text_modal.done": "Ferdig", "announcement.announcement": "Kunngjering", - "annual_report.summary.archetype.booster": "Den som jaktar på noko kult", - "annual_report.summary.archetype.lurker": "Den som heng på hjørnet", - "annual_report.summary.archetype.oracle": "Orakelet", - "annual_report.summary.archetype.pollster": "Meiningsmålaren", - "annual_report.summary.archetype.replier": "Den sosiale sumarfuglen", - "annual_report.summary.followers.followers": "fylgjarar", - "annual_report.summary.followers.total": "{count} i alt", - "annual_report.summary.here_it_is": "Her er eit gjensyn med {year}:", - "annual_report.summary.highlighted_post.by_favourites": "det mest omtykte innlegget", - "annual_report.summary.highlighted_post.by_reblogs": "det mest framheva innlegget", - "annual_report.summary.highlighted_post.by_replies": "innlegget med flest svar", - "annual_report.summary.highlighted_post.possessive": "som {name} laga", + "annual_report.announcement.action_build": "Set saman min Årstodon", + "annual_report.announcement.action_view": "Sjå min Årstodon", + "annual_report.announcement.description": "Sjå meir om kva du har gjort på Mastodon siste året.", + "annual_report.announcement.title": "Årstodon {year} er her", "annual_report.summary.most_used_app.most_used_app": "mest brukte app", "annual_report.summary.most_used_hashtag.most_used_hashtag": "mest brukte emneknagg", - "annual_report.summary.most_used_hashtag.none": "Ingen", "annual_report.summary.new_posts.new_posts": "nye innlegg", "annual_report.summary.percentile.text": "Du er av deiivrigaste brukarane på {domain}.", "annual_report.summary.percentile.we_wont_tell_bernie": "Ikkje eit ord til pressa.", - "annual_report.summary.thanks": "Takk for at du er med i Mastodon!", "attachments_list.unprocessed": "(ubehandla)", "audio.hide": "Gøym lyd", "block_modal.remote_users_caveat": "Me vil be tenaren {domain} om å respektera di avgjerd. Me kan ikkje garantera at det vert gjort, sidan nokre tenarar kan handtera blokkering ulikt. Offentlege innlegg kan framleis vera synlege for ikkje-innlogga brukarar.", @@ -152,6 +147,8 @@ "bundle_modal_error.close": "Lat att", "bundle_modal_error.message": "Noko gjekk gale då denne sida vart lasta.", "bundle_modal_error.retry": "Prøv igjen", + "carousel.current": "Side {current, number} / {max, number}", + "carousel.slide": "Side {current, number} av {max, number}", "closed_registrations.other_server_instructions": "Sidan Mastodon er desentralisert kan du lage ein brukar på ein anna tenar og framleis interagere med denne.", "closed_registrations_modal.description": "Det er ikkje mogleg å opprette ein konto på {domain} nett no, men hugs at du ikkje treng ein konto på akkurat {domain} for å nytte Mastodon.", "closed_registrations_modal.find_another_server": "Finn ein annan tenar", @@ -168,6 +165,8 @@ "column.edit_list": "Rediger liste", "column.favourites": "Favorittar", "column.firehose": "Tidslinjer", + "column.firehose_local": "Direktestraum for denne tenaren", + "column.firehose_singular": "Direktestraum", "column.follow_requests": "Fylgjeførespurnadar", "column.home": "Heim", "column.list_members": "Administrer medlemer på lista", @@ -187,6 +186,7 @@ "community.column_settings.local_only": "Berre lokalt", "community.column_settings.media_only": "Berre media", "community.column_settings.remote_only": "Berre eksternt", + "compose.error.blank_post": "Innlegg kan ikkje vera tomme.", "compose.language.change": "Byt språk", "compose.language.search": "Søk språk...", "compose.published.body": "Innlegg publisert.", @@ -239,6 +239,11 @@ "confirmations.missing_alt_text.secondary": "Publiser likevel", "confirmations.missing_alt_text.title": "Legg til alternativ tekst?", "confirmations.mute.confirm": "Demp", + "confirmations.private_quote_notify.cancel": "Tilbake til redigeringa", + "confirmations.private_quote_notify.confirm": "Legg ut innlegget", + "confirmations.private_quote_notify.do_not_show_again": "Ikkje vis dette fleire gonger", + "confirmations.private_quote_notify.message": "Personen du siterer, og andre som er nemnde i innlegget, vil få varsel og kan sjå innlegget ditt, sjølv om dei ikkje fylgjer deg.", + "confirmations.private_quote_notify.title": "Del med fylgjarar og folk som er nemnde?", "confirmations.quiet_post_quote_info.dismiss": "Ikkje minn meg på det att", "confirmations.quiet_post_quote_info.got_it": "Greitt", "confirmations.quiet_post_quote_info.message": "Når du siterer eit stille offentleg innlegg, blir innlegget ditt gøymt frå offentlege populære tidsliner.", @@ -252,9 +257,12 @@ "confirmations.revoke_quote.confirm": "Fjern innlegget", "confirmations.revoke_quote.message": "Du kan ikkje angra denne handlinga.", "confirmations.revoke_quote.title": "Fjern innlegget?", + "confirmations.unblock.confirm": "Opphev blokkeringa", + "confirmations.unblock.title": "Opphev blokkeringa av {name}?", "confirmations.unfollow.confirm": "Slutt å fylgja", - "confirmations.unfollow.message": "Er du sikker på at du vil slutta å fylgja {name}?", - "confirmations.unfollow.title": "Slutt å fylgja brukaren?", + "confirmations.unfollow.title": "Slutt å fylgja {name}?", + "confirmations.withdraw_request.confirm": "Trekk attende førespurnad", + "confirmations.withdraw_request.title": "Trekkja tilbake førespurnaden om å fylgja {name}?", "content_warning.hide": "Gøym innlegg", "content_warning.show": "Vis likevel", "content_warning.show_more": "Vis meir", @@ -314,8 +322,8 @@ "emoji_button.search_results": "Søkeresultat", "emoji_button.symbols": "Symbol", "emoji_button.travel": "Reise & stader", - "empty_column.account_featured.me": "Du har ikkje valt ut noko enno. Visste du at du kan velja ut merkelappar du bruker mykje, og til og med venekontoar på profilen din?", - "empty_column.account_featured.other": "{acct} har ikkje valt ut noko enno. Visste du at du kan velja ut merkelappar du bruker mykje, og til og med venekontoar på profilen din?", + "empty_column.account_featured.me": "Du har ikkje valt ut noko enno. Visste du at du kan velja ut emneknaggar du bruker mykje, og til og med venekontoar på profilen din?", + "empty_column.account_featured.other": "{acct} har ikkje valt ut noko enno. Visste du at du kan velja ut emneknaggar du bruker mykje, og til og med venekontoar på profilen din?", "empty_column.account_featured_other.unknown": "Denne kontoen har ikkje valt ut noko enno.", "empty_column.account_hides_collections": "Denne brukaren har valt å ikkje gjere denne informasjonen tilgjengeleg", "empty_column.account_suspended": "Kontoen er utestengd", @@ -325,6 +333,7 @@ "empty_column.bookmarked_statuses": "Du har ikkje lagra noko bokmerke enno. Når du set bokmerke på eit innlegg, dukkar det opp her.", "empty_column.community": "Den lokale tidslina er tom. Skriv noko offentleg å få ballen til å rulle!", "empty_column.direct": "Du har ingen private omtaler enda. Etter du har sendt eller mottatt en, så vil den dukke opp her.", + "empty_column.disabled_feed": "Administratorane på tenaren din har skrudd av denne straumen.", "empty_column.domain_blocks": "Det er ingen blokkerte domene enno.", "empty_column.explore_statuses": "Ingenting er populært nett no. Prøv att seinare!", "empty_column.favourited_statuses": "Du har ingen favoritt-statusar ennå. Når du merkjer ein som favoritt, dukkar han opp her.", @@ -338,6 +347,7 @@ "empty_column.notification_requests": "Ferdig! Her er det ingenting. Når du får nye varsel, kjem dei opp her slik du har valt.", "empty_column.notifications": "Du har ingen varsel enno. Kommuniser med andre for å starte samtalen.", "empty_column.public": "Det er ingenting her! Skriv noko offentleg, eller fylg brukarar frå andre tenarar manuelt for å få meir her", + "error.no_hashtag_feed_access": "Bli medlem eller logg inn for å sjå og fylgja denne emneknaggen.", "error.unexpected_crash.explanation": "På grunn av eit nettlesarkompatibilitetsproblem eller ein feil i koden vår, kunne ikkje denne sida bli vist slik den skal.", "error.unexpected_crash.explanation_addons": "Denne sida kunne ikkje visast som den skulle. Feilen kjem truleg frå ei nettleserutviding eller frå automatiske omsetjingsverktøy.", "error.unexpected_crash.next_steps": "Prøv å lasta inn sida på nytt. Hjelper ikkje dette kan du framleis nytta Mastodon i ein annan nettlesar eller app.", @@ -349,11 +359,9 @@ "explore.trending_links": "Nytt", "explore.trending_statuses": "Innlegg", "explore.trending_tags": "Emneknaggar", + "featured_carousel.current": "Innlegg {current, number} / {max, number}", "featured_carousel.header": "{count, plural, one {Festa innlegg} other {Festa innlegg}}", - "featured_carousel.next": "Neste", - "featured_carousel.post": "Innlegg", - "featured_carousel.previous": "Førre", - "featured_carousel.slide": "{index} av {total}", + "featured_carousel.slide": "Innlegg {current, number} av {max, number}", "filter_modal.added.context_mismatch_explanation": "Denne filterkategorien gjeld ikkje i den samanhengen du har lese dette innlegget. Viss du vil at innlegget skal filtrerast i denne samanhengen òg, må du endra filteret.", "filter_modal.added.context_mismatch_title": "Konteksten passar ikkje!", "filter_modal.added.expired_explanation": "Denne filterkategorien har gått ut på dato. Du må endre best før datoen for at den skal gjelde.", @@ -396,6 +404,7 @@ "follow_suggestions.who_to_follow": "Kven du kan fylgja", "followed_tags": "Fylgde emneknaggar", "footer.about": "Om", + "footer.about_this_server": "Om", "footer.directory": "Profilmappe", "footer.get_app": "Få appen", "footer.keyboard_shortcuts": "Snøggtastar", @@ -497,6 +506,7 @@ "keyboard_shortcuts.toggle_hidden": "Vis/gøym tekst bak innhaldsvarsel", "keyboard_shortcuts.toggle_sensitivity": "Vis/gøym media", "keyboard_shortcuts.toot": "Lag nytt tut", + "keyboard_shortcuts.top": "Flytt til toppen av lista", "keyboard_shortcuts.translate": "å omsetje eit innlegg", "keyboard_shortcuts.unfocus": "for å fokusere vekk skrive-/søkefeltet", "keyboard_shortcuts.up": "Flytt opp på lista", @@ -577,9 +587,9 @@ "navigation_bar.privacy_and_reach": "Personvern og rekkjevidd", "navigation_bar.search": "Søk", "navigation_bar.search_trends": "Søk / Populært", - "navigation_panel.collapse_followed_tags": "Gøym menyen over merkelappar du fylgjer", + "navigation_panel.collapse_followed_tags": "Gøym menyen over emneknaggar du fylgjer", "navigation_panel.collapse_lists": "Gøym listemenyen", - "navigation_panel.expand_followed_tags": "Utvid menyen over merkelappar du fylgjer", + "navigation_panel.expand_followed_tags": "Utvid menyen over emneknaggar du fylgjer", "navigation_panel.expand_lists": "Utvid listemenyen", "not_signed_in_indicator.not_signed_in": "Du må logga inn for å få tilgang til denne ressursen.", "notification.admin.report": "{name} rapporterte {target}", @@ -740,12 +750,14 @@ "privacy.quote.anyone": "{visibility}, alle kan sitera", "privacy.quote.disabled": "{visibility}, ingen kan sitera", "privacy.quote.limited": "{visibility}, avgrensa sitat", - "privacy.unlisted.additional": "Dette er akkurat som offentleg, bortsett frå at innlegga ikkje dukkar opp i direktestraumar eller merkelappar, i oppdagingar eller Mastodon-søk, sjølv om du har sagt ja til at kontoen skal vera synleg.", + "privacy.unlisted.additional": "Dette er akkurat som offentleg, bortsett frå at innlegga ikkje dukkar opp i direktestraumar eller emneknaggar, i oppdagingar eller Mastodon-søk, sjølv om du har sagt ja til at kontoen skal vera synleg.", "privacy.unlisted.long": "Gøymt frå søkjeresultata på Mastodon, og frå populære og offentlege tidsliner", "privacy.unlisted.short": "Stille offentleg", "privacy_policy.last_updated": "Sist oppdatert {date}", "privacy_policy.title": "Personvernsreglar", + "quote_error.edit": "Du kan ikkje leggja til sitat når du redigerer eit innlegg.", "quote_error.poll": "Du kan ikkje sitera meiningsmålingar.", + "quote_error.private_mentions": "Du kan ikkje sitera direkteomtaler.", "quote_error.quote": "Det er berre lov med eitt sitat om gongen.", "quote_error.unauthorized": "Du har ikkje løyve til å sitera dette innlegget.", "quote_error.upload": "Du kan ikkje sitera medievedlegg.", @@ -841,7 +853,7 @@ "search_results.all": "Alt", "search_results.hashtags": "Emneknaggar", "search_results.no_results": "Ingen resultat.", - "search_results.no_search_yet": "Prøv å søkja etter innlegg, profilar eller merkelappar.", + "search_results.no_search_yet": "Prøv å søkja etter innlegg, profilar eller emneknaggar.", "search_results.see_all": "Sjå alle", "search_results.statuses": "Tut", "search_results.title": "Søk etter \"{q}\"", @@ -864,8 +876,13 @@ "status.cancel_reblog_private": "Opphev framheving", "status.cannot_quote": "Du har ikkje løyve til å sitera dette innlegget", "status.cannot_reblog": "Du kan ikkje framheva dette innlegget", - "status.context.load_new_replies": "Nye svar finst", - "status.context.loading": "Ser etter fleire svar", + "status.contains_quote": "Inneheld eit sitat", + "status.context.loading": "Lastar fleire svar", + "status.context.loading_error": "Kunne ikkje lasta nye svar", + "status.context.loading_success": "Dei nye svara er lasta", + "status.context.more_replies_found": "Fann fleire svar", + "status.context.retry": "Prøv om att", + "status.context.show": "Vis", "status.continued_thread": "Framhald til tråden", "status.copy": "Kopier lenke til status", "status.delete": "Slett", @@ -878,7 +895,7 @@ "status.edited_x_times": "Redigert {count, plural, one {{count} gong} other {{count} gonger}}", "status.embed": "Få innbyggingskode", "status.favourite": "Favoritt", - "status.favourites": "{count, plural, one {favoritt} other {favorittar}}", + "status.favourites_count": "{count, plural, one {{counter} favoritt} other {{counter} favorittar}}", "status.filter": "Filtrer dette innlegget", "status.history.created": "{name} oppretta {date}", "status.history.edited": "{name} redigerte {date}", @@ -894,25 +911,33 @@ "status.pin": "Fest på profil", "status.quote": "Siter", "status.quote.cancel": "Avbryt siteringa", + "status.quote_error.blocked_account_hint.title": "Dette innlegget er gøymt fordi du har blokkert @{name}.", + "status.quote_error.blocked_domain_hint.title": "Dette innlegget er gøymt fordi du har blokkert {domain}.", "status.quote_error.filtered": "Gøymt på grunn av eitt av filtra dine", + "status.quote_error.limited_account_hint.action": "Vis likevel", + "status.quote_error.limited_account_hint.title": "Denne kontoen har vorte skjult av moderatorane på {domain}.", + "status.quote_error.muted_account_hint.title": "Dette innlegget er gøymt fordi du har dempa @{name}.", "status.quote_error.not_available": "Innlegget er ikkje tilgjengeleg", "status.quote_error.pending_approval": "Innlegget ventar", "status.quote_error.pending_approval_popout.body": "På Mastodon kan du kontrollera om folk får sitera deg. Innlegget ditt ventar medan me ventar på at opphavspersonen godkjenner det.", "status.quote_error.revoked": "Innlegget er sletta av skribenten", "status.quote_followers_only": "Berre fylgjarar kan sitera dette innlegget", "status.quote_manual_review": "Skribenten ser gjennom manuelt", + "status.quote_noun": "Sitat", "status.quote_policy_change": "Byt kven som kan sitera", "status.quote_post_author": "Siterte eit innlegg av @{name}", "status.quote_private": "Du kan ikkje sitera private innlegg", - "status.quotes": "{count, plural, one {sitat} other {sitat}}", "status.quotes.empty": "Ingen har sitert dette innlegget enno. Når nokon gje det, vil det dukka opp her.", + "status.quotes.local_other_disclaimer": "Sitat som skribenten avviser, vil ikkje syna.", + "status.quotes.remote_other_disclaimer": "Berre sitat frå {domain} er garanterte å syna her. Sitat som skribenten har avvist, vil ikkje syna.", + "status.quotes_count": "{count, plural, one {{counter} sitat} other {{counter} sitat}}", "status.read_more": "Les meir", "status.reblog": "Framhev", "status.reblog_or_quote": "Framhev eller siter", "status.reblog_private": "Del på nytt med fylgjarane dine", "status.reblogged_by": "{name} framheva", - "status.reblogs": "{count, plural, one {framheving} other {framhevingar}}", "status.reblogs.empty": "Ingen har framheva dette tutet enno. Om nokon gjer, så dukkar det opp her.", + "status.reblogs_count": "{count, plural, one {{counter} framheving} other {{counter} framhevingar}}", "status.redraft": "Slett & skriv på nytt", "status.remove_bookmark": "Fjern bokmerke", "status.remove_favourite": "Fjern frå favorittar", @@ -986,6 +1011,8 @@ "video.volume_down": "Volum ned", "video.volume_up": "Volum opp", "visibility_modal.button_title": "Vel vising", + "visibility_modal.direct_quote_warning.text": "Viss du lagrar innstillingane, vil det innebygde sitatet bli omgjort til lenke.", + "visibility_modal.direct_quote_warning.title": "Du kan ikkje byggja inn sitat i direkteomtaler", "visibility_modal.header": "Vising og samhandling", "visibility_modal.helper.direct_quoting": "Private omtalar som er skrivne på Mastodon kan ikkje siterast av andre.", "visibility_modal.helper.privacy_editing": "Du kan ikkje endra korleis eit innlegg viser når du har lagt det ut.", diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json index fa03560194ca4d..8a2ce940eadaa4 100644 --- a/app/javascript/mastodon/locales/no.json +++ b/app/javascript/mastodon/locales/no.json @@ -70,7 +70,6 @@ "account.posts_with_replies": "Innlegg med svar", "account.remove_from_followers": "Fjern {name} fra følgere", "account.report": "Rapporter @{name}", - "account.requested": "Venter på godkjennelse. Klikk for å avbryte forespørselen", "account.requested_follow": "{name} har bedt om å få følge deg", "account.requests_to_follow_you": "Forespørsler om å følge deg", "account.share": "Del @{name} sin profil", @@ -108,25 +107,11 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Beskriv dette for folk med synsproblemer…", "alt_text_modal.done": "Ferdig", "announcement.announcement": "Kunngjøring", - "annual_report.summary.archetype.booster": "Den iskalde jeger", - "annual_report.summary.archetype.lurker": "Det snikende ullteppe", - "annual_report.summary.archetype.oracle": "Allviteren", - "annual_report.summary.archetype.pollster": "Meningsmåleren", - "annual_report.summary.archetype.replier": "Festens midtpunkt", - "annual_report.summary.followers.followers": "følgere", - "annual_report.summary.followers.total": "{count} tilsammen", - "annual_report.summary.here_it_is": "Her er ditt {year} ditt sett sammlet:", - "annual_report.summary.highlighted_post.by_favourites": "mest likte innlegg", - "annual_report.summary.highlighted_post.by_reblogs": "mest opphøyde innlegg", - "annual_report.summary.highlighted_post.by_replies": "innlegg med de fleste tilbakemeldinger", - "annual_report.summary.highlighted_post.possessive": "{name}", "annual_report.summary.most_used_app.most_used_app": "mest brukte applikasjoner", "annual_report.summary.most_used_hashtag.most_used_hashtag": "mest brukte evne knagg", - "annual_report.summary.most_used_hashtag.none": "Ingen", "annual_report.summary.new_posts.new_posts": "nye innlegg", "annual_report.summary.percentile.text": "Det gjør at du er i toppav brukere på {domain}.", "annual_report.summary.percentile.we_wont_tell_bernie": "Vi skal ikke si noe til Bernie.", - "annual_report.summary.thanks": "Takk for at du er med på Mastodon!", "attachments_list.unprocessed": "(ubehandlet)", "audio.hide": "Skjul lyd", "block_modal.remote_users_caveat": "Vi vil be serveren {domain} om å respektere din beslutning. Det er imidlertid ingen garanti at det blir overholdt, siden noen servere kan håndtere blokkeringer på forskjellig vis. Offentlige innlegg kan fortsatt være synlige for ikke-innloggede brukere.", @@ -239,8 +224,6 @@ "confirmations.remove_from_followers.message": "{name} vil ikke lenger følge deg. Er du sikker på at du vil fortsette?", "confirmations.remove_from_followers.title": "Fjern følger?", "confirmations.unfollow.confirm": "Slutt å følge", - "confirmations.unfollow.message": "Er du sikker på at du vil slutte å følge {name}?", - "confirmations.unfollow.title": "Slutt å følge bruker?", "content_warning.hide": "Skjul innlegg", "content_warning.show": "Vis likevel", "content_warning.show_more": "Vis mer", @@ -335,10 +318,6 @@ "explore.trending_statuses": "Innlegg", "explore.trending_tags": "Emneknagger", "featured_carousel.header": "{count, plural, one {{counter} festet innlegg} other {{counter} festede innlegg}}", - "featured_carousel.next": "Neste", - "featured_carousel.post": "Innlegg", - "featured_carousel.previous": "Forrige", - "featured_carousel.slide": "{index} av {total}", "filter_modal.added.context_mismatch_explanation": "Denne filterkategorien gjelder ikke for den konteksten du har åpnet dette innlegget i. Hvis du vil at innlegget skal filtreres i denne konteksten også, må du redigere filteret.", "filter_modal.added.context_mismatch_title": "Feil sammenheng!", "filter_modal.added.expired_explanation": "Denne filterkategorien er utløpt, du må endre utløpsdato for at den skal gjelde.", diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json index 0d87228ea1a412..d1d8daa2115423 100644 --- a/app/javascript/mastodon/locales/oc.json +++ b/app/javascript/mastodon/locales/oc.json @@ -1,10 +1,12 @@ { "about.blocks": "Servidors moderats", "about.contact": "Contacte :", + "about.default_locale": "Per defaut", "about.disclaimer": "Mastodon es gratuit, un logicial libre e una marca de Mastodon gGmbH.", "about.domain_blocks.no_reason_available": "Rason pas disponibla", "about.domain_blocks.silenced.title": "Limitats", "about.domain_blocks.suspended.title": "Suspenduts", + "about.language_label": "Lenga", "about.not_available": "Aquesta informacion foguèt pas renduda disponibla sus aqueste servidor.", "about.powered_by": "Malhum social descentralizat propulsat per {mastodon}", "about.rules": "Règlas del servidor", @@ -16,17 +18,26 @@ "account.block_domain": "Tot amagar del domeni {domain}", "account.block_short": "Blocar", "account.blocked": "Blocat", + "account.blocking": "Blocatge", "account.cancel_follow_request": "Retirar la demanda d’abonament", "account.copy": "Copiar lo ligam del perfil", "account.direct": "Mencionar @{name} en privat", "account.disable_notifications": "Quitar de m’avisar quand @{name} publica quicòm", "account.edit_profile": "Modificar lo perfil", + "account.edit_profile_short": "Modificar", "account.enable_notifications": "M’avisar quand @{name} publica quicòm", "account.endorse": "Mostrar pel perfil", + "account.familiar_followers_many": "Seguit per {name1}, {name2} e {othersCount, plural, one {qualqu’un mai que coneissètz} other {# autras personas que coneissètz}}", + "account.familiar_followers_one": "Seguit per {name1}", + "account.familiar_followers_two": "Seguit per {name1} e {name2}", + "account.featured.accounts": "Perfils", + "account.featured.hashtags": "Etiquetas", "account.featured_tags.last_status_at": "Darrièra publicacion lo {date}", "account.featured_tags.last_status_never": "Cap de publicacion", "account.follow": "Sègre", "account.follow_back": "Sègre en retorn", + "account.follow_request_cancel": "Anullar la demanda", + "account.follow_request_cancel_short": "Anullar", "account.followers": "Seguidors", "account.followers.empty": "Degun sèc pas aqueste utilizaire pel moment.", "account.following": "Abonat", @@ -45,17 +56,20 @@ "account.mute_notifications_short": "Amudir las notificacions", "account.mute_short": "Amudir", "account.muted": "Mes en silenci", + "account.mutual": "Vos seguissètz", "account.no_bio": "Cap de descripcion pas fornida.", "account.open_original_page": "Dobrir la pagina d’origina", "account.posts": "Tuts", "account.posts_with_replies": "Tuts e responsas", + "account.remove_from_followers": "Tirar {name} dels seguidors", "account.report": "Senhalar @{name}", - "account.requested": "Invitacion mandada. Clicatz per anullar", "account.requested_follow": "{name} a demandat a vos sègre", + "account.requests_to_follow_you": "Demanda a vos sègre", "account.share": "Partejar lo perfil a @{name}", "account.show_reblogs": "Mostrar los partatges de @{name}", "account.unblock": "Desblocar @{name}", "account.unblock_domain": "Desblocar {domain}", + "account.unblock_domain_short": "Desblocar", "account.unblock_short": "Desblocat", "account.unendorse": "Mostrar pas pel perfil", "account.unfollow": "Quitar de sègre", @@ -77,8 +91,6 @@ "alt_text_modal.change_thumbnail": "Cambiar de miniatura", "alt_text_modal.done": "Acabat", "announcement.announcement": "Anóncia", - "annual_report.summary.followers.followers": "seguidors", - "annual_report.summary.followers.total": "{count} en total", "attachments_list.unprocessed": "(pas tractat)", "audio.hide": "Amagar àudio", "block_modal.show_less": "Ne veire mens", @@ -98,13 +110,18 @@ "column.blocks": "Personas blocadas", "column.bookmarks": "Marcadors", "column.community": "Flux public local", + "column.create_list": "Crear una lista", "column.direct": "Mencions privadas", "column.directory": "Percórrer los perfils", "column.domain_blocks": "Domenis resconduts", + "column.edit_list": "Modificar la lista", "column.favourites": "Favorits", "column.firehose": "Tuts en dirèct", + "column.firehose_local": "Fial en dirècte d’aqueste servidor", + "column.firehose_singular": "Fial en dirècte", "column.follow_requests": "Demandas d’abonament", "column.home": "Acuèlh", + "column.list_members": "Gestion dels membres de la lista", "column.lists": "Listas", "column.mutes": "Personas rescondudas", "column.notifications": "Notificacions", @@ -117,6 +134,7 @@ "column_header.pin": "Penjar", "column_header.show_settings": "Mostrar los paramètres", "column_header.unpin": "Despenjar", + "column_search.cancel": "Anullar", "community.column_settings.local_only": "Sonque local", "community.column_settings.media_only": "Solament los mèdias", "community.column_settings.remote_only": "Sonque alonhat", @@ -145,15 +163,28 @@ "confirmations.block.confirm": "Blocar", "confirmations.delete.confirm": "Escafar", "confirmations.delete.message": "Volètz vertadièrament escafar l’estatut ?", + "confirmations.delete.title": "Suprimir la publicacion ?", "confirmations.delete_list.confirm": "Suprimir", "confirmations.delete_list.message": "Volètz vertadièrament suprimir aquesta lista per totjorn ?", + "confirmations.delete_list.title": "Suprimir la lista ?", "confirmations.discard_edit_media.confirm": "Ignorar", "confirmations.logout.confirm": "Desconnexion", "confirmations.logout.message": "Volètz vertadièrament vos desconnectar ?", + "confirmations.missing_alt_text.confirm": "Apondre un tèxte alternatiu", + "confirmations.missing_alt_text.message": "Vòstra publicacion conten un mèdia sens tèxt alternatiu. L'apondon de descripcions ajuda a rendre vòstre contengut accessible a mai de monde.", + "confirmations.missing_alt_text.secondary": "Publicar malgrat tot", + "confirmations.missing_alt_text.title": "Apondre un tèxte alternatiu ?", "confirmations.mute.confirm": "Rescondre", + "confirmations.quiet_post_quote_info.dismiss": "Me remembrar pas mai", + "confirmations.quiet_post_quote_info.got_it": "Plan comprés", "confirmations.redraft.confirm": "Escafar & tornar formular", + "confirmations.revoke_quote.title": "Suprimir la publicacion ?", + "confirmations.unblock.confirm": "Desblocar", + "confirmations.unblock.title": "Desblocar {name} ?", "confirmations.unfollow.confirm": "Quitar de sègre", - "confirmations.unfollow.message": "Volètz vertadièrament quitar de sègre {name} ?", + "confirmations.unfollow.title": "Quitar de sègre {name} ?", + "content_warning.hide": "Amagar la publicacion", + "content_warning.show_more": "Ne veire mai", "conversation.delete": "Suprimir la conversacion", "conversation.mark_as_read": "Marcar coma legida", "conversation.open": "Veire la conversacion", @@ -208,6 +239,7 @@ "errors.unexpected_crash.copy_stacktrace": "Copiar las traças al quichapapièrs", "errors.unexpected_crash.report_issue": "Senhalar un problèma", "explore.suggested_follows": "Personas", + "explore.title": "Tendéncia", "explore.trending_links": "Novèlas", "explore.trending_statuses": "Publicacions", "explore.trending_tags": "Etiquetas", @@ -233,6 +265,7 @@ "footer.privacy_policy": "Politica de confidencialitat", "footer.source_code": "Veire lo còdi font", "footer.status": "Estat", + "footer.terms_of_service": "Condicions d’utilizacion", "generic.saved": "Enregistrat", "getting_started.heading": "Per començar", "hashtag.column_header.tag_mode.all": "e {additional}", @@ -248,6 +281,7 @@ "hashtag.counter_by_uses": "{count, plural, one {{counter} tut} other {{counter} tuts}}", "hashtag.counter_by_uses_today": "{count, plural, one {{counter} tut} other {{counter} tuts}} uèi", "hashtag.follow": "Sègre l’etiqueta", + "hashtag.mute": "Amudar #{hashtag}", "hashtag.unfollow": "Quitar de sègre l’etiqueta", "hashtags.and_other": "…e {count, plural, one {}other {# de mai}}", "home.column_settings.show_reblogs": "Mostrar los partatges", @@ -255,6 +289,7 @@ "home.hide_announcements": "Rescondre las anóncias", "home.pending_critical_update.link": "Veire las mesas a jorn", "home.show_announcements": "Mostrar las anóncias", + "info_button.label": "Ajuda", "interaction_modal.on_another_server": "Sus un autre servidor", "interaction_modal.on_this_server": "Sus aqueste servidor", "intervals.full.days": "{number, plural, one {# jorn} other {# jorns}}", @@ -294,21 +329,29 @@ "keyboard_shortcuts.toot": "començar un estatut tot novèl", "keyboard_shortcuts.unfocus": "quitar lo camp tèxte/de recèrca", "keyboard_shortcuts.up": "far montar dins la lista", + "learn_more_link.got_it": "Plan comprés", + "learn_more_link.learn_more": "Ne saber mai", "lightbox.close": "Tampar", "lightbox.next": "Seguent", "lightbox.previous": "Precedent", "limited_account_hint.action": "Afichar lo perfil de tota manièra", "limited_account_hint.title": "Aqueste perfil foguèt rescondut per la moderacion de {domain}.", "link_preview.author": "Per {name}", + "link_preview.more_from_author": "Mai de {name}", "lists.delete": "Suprimir la lista", "lists.edit": "Modificar la lista", "lists.replies_policy.followed": "Quin seguidor que siá", "lists.replies_policy.list": "Membres de la lista", "lists.replies_policy.none": "Degun", + "lists.save": "Enregistrar", + "lists.search": "Recercar", "load_pending": "{count, plural, one {# nòu element} other {# nòu elements}}", "loading_indicator.label": "Cargament…", + "media_gallery.hide": "Rescondre", "navigation_bar.about": "A prepaus", + "navigation_bar.administration": "Administracion", "navigation_bar.advanced_interface": "Dobrir l’interfàcia web avançada", + "navigation_bar.automated_deletion": "Supression auto de las publicacions", "navigation_bar.blocks": "Personas blocadas", "navigation_bar.bookmarks": "Marcadors", "navigation_bar.direct": "Mencions privadas", @@ -318,19 +361,32 @@ "navigation_bar.follow_requests": "Demandas d’abonament", "navigation_bar.followed_tags": "Etiquetas seguidas", "navigation_bar.follows_and_followers": "Abonament e seguidors", + "navigation_bar.import_export": "Import e export", "navigation_bar.lists": "Listas", "navigation_bar.logout": "Desconnexion", + "navigation_bar.moderation": "Moderacion", + "navigation_bar.more": "Mai", "navigation_bar.mutes": "Personas rescondudas", "navigation_bar.preferences": "Preferéncias", "navigation_bar.search": "Recercar", + "navigation_bar.search_trends": "Recèrca / Tendéncia", "not_signed_in_indicator.not_signed_in": "Devètz vos connectar per accedir a aquesta ressorsa.", "notification.admin.report": "{name} senhalèt {target}", "notification.admin.sign_up": "{name} se marquèt", "notification.favourite": "{name} a mes vòstre estatut en favorit", "notification.follow": "{name} vos sèc", "notification.follow_request": "{name} a demandat a vos sègre", + "notification.label.mention": "Mencion", + "notification.label.private_mention": "Mencion privada", + "notification.label.private_reply": "Responsa privada", + "notification.label.quote": "{name} a citat vòstra publicacion", + "notification.label.reply": "Respondre", + "notification.mention": "Mencionar", + "notification.mentioned_you": "{name} vos a mencionat", + "notification.moderation-warning.learn_more": "Ne saber mai", "notification.own_poll": "Vòstre sondatge es acabat", "notification.reblog": "{name} a partejat vòstre estatut", + "notification.relationships_severance_event.learn_more": "Ne saber mai", "notification.status": "{name} ven de publicar", "notification.update": "{name} modiquè sa publicacion", "notifications.clear": "Escafar", @@ -381,7 +437,12 @@ "poll_button.add_poll": "Ajustar un sondatge", "poll_button.remove_poll": "Levar lo sondatge", "privacy.change": "Ajustar la confidencialitat del messatge", + "privacy.direct.long": "Sonque los qu’avètz mencionats dins la publicacion", + "privacy.direct.short": "Mencion privada", + "privacy.private.long": "Mostrar pas qu’als seguidors", + "privacy.private.short": "Seguidors", "privacy.public.short": "Public", + "privacy.unlisted.short": "Public silenciós", "privacy_policy.last_updated": "Darrièra actualizacion {date}", "privacy_policy.title": "Politica de confidencialitat", "refresh": "Actualizar", @@ -425,6 +486,7 @@ "report.target": "Senhalar {target}", "report.thanks.title": "Volètz pas veire aquò ?", "report.unfollow": "Quitar de sègre {name}", + "report.unfollow_explanation": "Seguissètz aqueste compte. Per veire pas pus lors publicacions dins vòstra cronologia, quitatz de lo sègre.", "report_notification.attached_statuses": "{count, plural, one {{count} publicacion junta} other {{count} publicacions juntas}}", "report_notification.categories.other": "Autre", "report_notification.categories.spam": "Messatge indesirable", @@ -456,6 +518,9 @@ "status.bookmark": "Marcador", "status.cancel_reblog_private": "Quitar de partejar", "status.cannot_reblog": "Aqueste estatut pòt pas èsser partejat", + "status.context.loading": "Cargament de mai de responsas", + "status.context.more_replies_found": "Mai de responsas trobadas", + "status.continued_thread": "Seguida del fial", "status.copy": "Copiar lo ligam de l’estatut", "status.delete": "Escafar", "status.detailed_status": "Vista detalhada de la convèrsa", diff --git a/app/javascript/mastodon/locales/pa.json b/app/javascript/mastodon/locales/pa.json index cd20e4bce04656..3f519d8c0959e2 100644 --- a/app/javascript/mastodon/locales/pa.json +++ b/app/javascript/mastodon/locales/pa.json @@ -23,6 +23,7 @@ "account.disable_notifications": "ਜਦੋਂ {name} ਕੋਈ ਪੋਸਟ ਕਰੇ ਤਾਂ ਮੈਨੂੰ ਸੂਚਨਾ ਨਾ ਦਿਓ", "account.domain_blocking": "ਡੋਮੇਨ ਉੱਤੇ ਪਾਬੰਦੀ", "account.edit_profile": "ਪਰੋਫਾਈਲ ਨੂੰ ਸੋਧੋ", + "account.edit_profile_short": "ਸੋਧੋ", "account.enable_notifications": "ਜਦੋਂ {name} ਪੋਸਟ ਕਰੇ ਤਾਂ ਮੈਨੂੰ ਸੂਚਨਾ ਦਿਓ", "account.endorse": "ਪਰੋਫਾਇਲ ਉੱਤੇ ਫ਼ੀਚਰ", "account.familiar_followers_one": "{name1} ਵਲੋਂ ਫ਼ਾਲੋ ਕੀਤਾ", @@ -34,6 +35,11 @@ "account.featured_tags.last_status_never": "ਕੋਈ ਪੋਸਟ ਨਹੀਂ", "account.follow": "ਫ਼ਾਲੋ", "account.follow_back": "ਵਾਪਸ ਫਾਲ਼ੋ ਕਰੋ", + "account.follow_back_short": "ਵਾਪਸ ਫਾਲ਼ੋ ਕਰੋ", + "account.follow_request": "ਫ਼ਾਲੋ ਕਰਨ ਦੀ ਬੇਨਤੀ", + "account.follow_request_cancel": "ਮੰਗ ਰੱਦ ਕਰੋ", + "account.follow_request_cancel_short": "ਰੱਦ ਕਰੋ", + "account.follow_request_short": "ਬੇਨਤੀ", "account.followers": "ਫ਼ਾਲੋਅਰ", "account.followers.empty": "ਇਸ ਵਰਤੋਂਕਾਰ ਨੂੰ ਹਾਲੇ ਕੋਈ ਫ਼ਾਲੋ ਨਹੀਂ ਕਰਦਾ ਹੈ।", "account.followers_counter": "{count, plural, one {{counter} ਫ਼ਾਲੋਅਰ} other {{counter} ਫ਼ਾਲੋਅਰ}}", @@ -55,9 +61,10 @@ "account.open_original_page": "ਅਸਲ ਸਫ਼ੇ ਨੂੰ ਖੋਲ੍ਹੋ", "account.posts": "ਪੋਸਟਾਂ", "account.posts_with_replies": "ਪੋਸਟਾਂ ਅਤੇ ਜਵਾਬ", + "account.remove_from_followers": "{name} ਨੂੰ ਫ਼ਾਲੋਅਰ ਵਿੱਚੋਂ ਹਟਾਓ", "account.report": "{name} ਬਾਰੇ ਰਿਪੋਰਟ ਕਰੋ", - "account.requested": "ਮਨਜ਼ੂਰੀ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ। ਫ਼ਾਲੋ ਬੇਨਤੀਆਂ ਨੂੰ ਰੱਦ ਕਰਨ ਲਈ ਕਲਿੱਕ ਕਰੋ", "account.requested_follow": "{name} ਨੇ ਤੁਹਾਨੂੰ ਫ਼ਾਲੋ ਕਰਨ ਦੀ ਬੇਨਤੀ ਕੀਤੀ ਹੈ", + "account.requests_to_follow_you": "ਤੁਹਾਨੂੰ ਫ਼ਾਲੋ ਕਰਨ ਦੀਆਂ ਬੇਨਤੀਆਂ", "account.share": "{name} ਦਾ ਪਰੋਫ਼ਾਇਲ ਸਾਂਝਾ ਕਰੋ", "account.statuses_counter": "{count, plural, one {{counter} ਪੋਸਟ} other {{counter} ਪੋਸਟਾਂ}}", "account.unblock": "@{name} ਤੋਂ ਪਾਬੰਦੀ ਹਟਾਓ", @@ -74,19 +81,12 @@ "admin.dashboard.retention.cohort_size": "ਨਵੇਂ ਵਰਤੋਂਕਾਰ", "alert.unexpected.title": "ਓਹੋ!", "alt_text_badge.title": "ਬਦਲੀ ਲਿਖਤ", + "alt_text_modal.add_alt_text": "ਬਦਲਵੀ ਲਿਖਤ ਨੂੰ ਜੋੜੋ", "alt_text_modal.cancel": "ਰੱਦ ਕਰੋ", "alt_text_modal.done": "ਮੁਕੰਮਲ", "announcement.announcement": "ਹੋਕਾ", - "annual_report.summary.followers.followers": "ਫ਼ਾਲੋਅਰ", - "annual_report.summary.followers.total": "{count} ਕੁੱਲ", - "annual_report.summary.highlighted_post.by_favourites": "ਸਭ ਤੋਂ ਵੱਧ ਪਸੰਦ ਕੀਤੀ ਪੋਸਟ", - "annual_report.summary.highlighted_post.by_reblogs": "ਸਭ ਤੋਂ ਵੱਧ ਬੂਸਟ ਕੀਤੀ ਪੋਸਟ", - "annual_report.summary.highlighted_post.by_replies": "ਸਭ ਤੋਂ ਵੱਧ ਜਵਾਬ ਦਿੱਤੀ ਗਈ ਪੋਸਟ", - "annual_report.summary.highlighted_post.possessive": "{name}", "annual_report.summary.most_used_app.most_used_app": "ਸਭ ਤੋਂ ਵੱਧ ਵਰਤੀ ਐਪ", - "annual_report.summary.most_used_hashtag.none": "ਕੋਈ ਨਹੀਂ", "annual_report.summary.new_posts.new_posts": "ਨਵੀਆਂ ਪੋਸਟਾਂ", - "annual_report.summary.thanks": "Mastodon ਦਾ ਹਿੱਸਾ ਬਣਨ ਵਾਸਤੇ ਧੰਨਵਾਦ ਹੈ!", "audio.hide": "ਆਡੀਓ ਨੂੰ ਲੁਕਾਓ", "block_modal.show_less": "ਘੱਟ ਦਿਖਾਓ", "block_modal.show_more": "ਵੱਧ ਦਿਖਾਓ", @@ -100,6 +100,9 @@ "bundle_column_error.routing.title": "404", "bundle_modal_error.close": "ਬੰਦ ਕਰੋ", "bundle_modal_error.retry": "ਮੁੜ-ਕੋਸ਼ਿਸ਼ ਕਰੋ", + "carousel.current": "ਸਲਾਈਡ {current, number} / {max, number}", + "carousel.slide": "{max, number} ਵਿੱਚੋਂ {current, number} ਸਲਾਈਡ", + "closed_registrations_modal.find_another_server": "ਹੋਰ ਸਰਵਰ ਲੱਭੋ", "closed_registrations_modal.title": "Mastodon ਲਈ ਸਾਈਨ ਅੱਪ ਕਰੋ", "column.about": "ਸਾਡੇ ਬਾਰੇ", "column.blocks": "ਪਾਬੰਦੀ ਲਾਏ ਵਰਤੋਂਕਾਰ", @@ -112,6 +115,7 @@ "column.edit_list": "ਸੂਚੀ ਨੂੰ ਸੋਧੋ", "column.favourites": "ਮਨਪਸੰਦ", "column.firehose": "ਲਾਈਵ ਫੀਡ", + "column.firehose_singular": "ਲਾਈਵ ਫੀਡ", "column.follow_requests": "ਫ਼ਾਲੋ ਦੀਆਂ ਬੇਨਤੀਆਂ", "column.home": "ਮੁੱਖ ਸਫ਼ਾ", "column.list_members": "ਸੂਚੀ ਦੇ ਮੈਂਬਰ ਦਾ ਇੰਤਜ਼ਾਮ ਕਰੋ", @@ -130,6 +134,7 @@ "community.column_settings.local_only": "ਸਿਰਫ ਲੋਕਲ ਹੀ", "community.column_settings.media_only": "ਸਿਰਫ ਮੀਡੀਆ ਹੀ", "community.column_settings.remote_only": "ਸਿਰਫ਼ ਰਿਮੋਟ ਹੀ", + "compose.error.blank_post": "ਪੋਸਟ ਖਾਲੀ ਨਹੀਂ ਹੋ ਸਕਦੀ ਹੈ।", "compose.language.change": "ਭਾਸ਼ਾ ਬਦਲੋ", "compose.language.search": "ਭਾਸ਼ਾਵਾਂ ਦੀ ਖੋਜ...", "compose.published.body": "ਪੋਸਟ ਪ੍ਰਕਾਸ਼ਿਤ ਕੀਤੀ।", @@ -141,6 +146,8 @@ "compose_form.lock_disclaimer": "ਤੁਹਾਡਾ ਖਾਤਾ {locked} ਨਹੀਂ ਹੈ। ਕੋਈ ਵੀ ਤੁਹਾਡੀਆਂ ਸਿਰਫ਼-ਫ਼ਾਲੋਅਰ ਪੋਸਟਾਂ ਵੇਖਣ ਵਾਸਤੇ ਤੁਹਾਨੂੰ ਫ਼ਾਲੋ ਕਰ ਸਕਦਾ ਹੈ।", "compose_form.lock_disclaimer.lock": "ਲਾਕ ਹੈ", "compose_form.placeholder": "ਤੁਹਾਡੇ ਮਨ ਵਿੱਚ ਕੀ ਹੈ?", + "compose_form.poll.duration": "ਵੋਟ ਲਈ ਸਮਾਂ", + "compose_form.poll.multiple": "ਕਈ ਚੋਣਾਂ", "compose_form.poll.option_placeholder": "{number} ਚੋਣ", "compose_form.poll.single": "ਇਕੱਲੀ ਚੋਣ", "compose_form.poll.type": "ਸਟਾਈਲ", @@ -158,20 +165,35 @@ "confirmations.delete_list.confirm": "ਹਟਾਓ", "confirmations.delete_list.message": "ਕੀ ਤੁਸੀਂ ਇਸ ਸੂਚੀ ਨੂੰ ਪੱਕੇ ਤੌਰ ਉੱਤੇ ਹਟਾਉਣਾ ਚਾਹੁੰਦੇ ਹੋ?", "confirmations.delete_list.title": "ਸੂਚੀ ਨੂੰ ਹਟਾਉਣਾ ਹੈ?", + "confirmations.discard_draft.confirm": "ਖ਼ਾਰਜ ਕਰਕੇ ਜਾਰੀ ਰੱਖੋ", + "confirmations.discard_draft.edit.cancel": "ਸੋਧਣਾ ਜਾਰੀ ਰੱਖੋ", + "confirmations.discard_draft.post.cancel": "ਖਰੜੇ ਉੱਤੇ ਕੰਮ ਜਾਰੀ ਰੱਖੋ", "confirmations.discard_edit_media.confirm": "ਰੱਦ ਕਰੋ", "confirmations.follow_to_list.confirm": "ਫ਼ਾਲੋ ਕਰੋ ਅਤੇ ਲਿਸਟ 'ਚ ਜੋੜੋ", "confirmations.follow_to_list.title": "ਵਰਤੋਂਕਾਰ ਨੂੰ ਫ਼ਾਲੋ ਕਰਨਾ ਹੈ?", "confirmations.logout.confirm": "ਬਾਹਰ ਹੋਵੋ", "confirmations.logout.message": "ਕੀ ਤੁਸੀਂ ਲਾਗ ਆਉਟ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ?", "confirmations.logout.title": "ਲਾਗ ਆਉਟ ਕਰਨਾ ਹੈ?", + "confirmations.missing_alt_text.confirm": "ਬਦਲਵੀ ਲਿਖਤ ਨੂੰ ਜੋੜੋ", "confirmations.missing_alt_text.secondary": "ਕਿਵੇਂ ਵੀ ਪੋਸਟ ਕਰੋ", + "confirmations.missing_alt_text.title": "ਬਦਲਵੀ ਲਿਖਤ ਜੋੜਨੀ ਹੈ?", "confirmations.mute.confirm": "ਮੌਨ ਕਰੋ", + "confirmations.private_quote_notify.cancel": "ਸੋਧ ਕਰਨ ਉੱਤੇ ਵਾਪਸ ਜਾਓ", + "confirmations.private_quote_notify.do_not_show_again": "ਮੈਨੂੰ ਇਹ ਸੁਨੇਹਾ ਫੇਰ ਨਾ ਦਿਖਾਓ", + "confirmations.quiet_post_quote_info.dismiss": "ਮੈਨੂੰ ਮੁੜ ਕੇ ਯਾਦ ਨਾ ਕਰਵਾਓ", + "confirmations.quiet_post_quote_info.got_it": "ਸਮਝ ਗਏ", "confirmations.redraft.confirm": "ਹਟਾਓ ਤੇ ਮੁੜ-ਡਰਾਫਟ", + "confirmations.redraft.title": "ਪੋਸਟ ਨੂੰ ਹਟਾ ਕੇ ਮੁੜ-ਡਰਾਫਟ ਕਰਨਾ ਹੈ?", "confirmations.remove_from_followers.confirm": "ਫ਼ਾਲੋਅਰ ਨੂੰ ਹਟਾਓ", "confirmations.remove_from_followers.title": "ਫ਼ਾਲੋਅਰ ਨੂੰ ਹਟਾਉਣਾ ਹੈ?", + "confirmations.revoke_quote.confirm": "ਪੋਸਟ ਨੂੰ ਹਟਾਓ", + "confirmations.revoke_quote.message": "ਇਸ ਕਾਰਵਾਈ ਨੂੰ ਵਾਪਸ ਨਹੀਂ ਪਰਤਾਇਆ ਜਾ ਸਕਦਾ ਹੈ।", + "confirmations.revoke_quote.title": "ਪੋਸਟ ਨੂੰ ਹਟਾਉਣਾ ਹੈ?", + "confirmations.unblock.confirm": "ਪਾਬੰਦੀ ਨੂੰ ਹਟਾਓ", + "confirmations.unblock.title": "{name} ਤੋਂ ਪਾਬੰਦੀ ਹਟਾਉਣੀ ਹੈ?", "confirmations.unfollow.confirm": "ਅਣ-ਫ਼ਾਲੋ", - "confirmations.unfollow.message": "ਕੀ ਤੁਸੀਂ {name} ਨੂੰ ਅਣ-ਫ਼ਾਲੋ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ?", - "confirmations.unfollow.title": "ਵਰਤੋਂਕਾਰ ਨੂੰ ਅਣ-ਫ਼ਾਲੋ ਕਰਨਾ ਹੈ?", + "confirmations.unfollow.title": "{name} ਨੂੰ ਅਣ-ਫ਼ਾਲੋ ਕਰਨਾ ਹੈ?", + "confirmations.withdraw_request.confirm": "ਬੇਨਤੀ ਵਾਪਸ ਲਵੋ", "content_warning.hide": "ਪੋਸਟ ਨੂੰ ਲੁਕਾਓ", "content_warning.show": "ਕਿਵੇਂ ਵੀ ਵੇਖਾਓ", "content_warning.show_more": "ਹੋਰ ਵੇਖਾਓ", @@ -197,6 +219,7 @@ "domain_pill.username": "ਵਰਤੋਂਕਾਰ-ਨਾਂ", "domain_pill.whats_in_a_handle": "ਹੈਂਡਲ ਕੀ ਹੁੰਦਾ ਹੈ?", "domain_pill.your_handle": "ਤੁਹਾਡਾ ਹੈਂਡਲ:", + "dropdown.empty": "ਕੋਈ ਚੋਣ ਕਰੋ", "embed.instructions": "ਹੇਠਲੇ ਕੋਡ ਨੂੰ ਕਾਪੀ ਕਰਕੇ ਆਪਣੀ ਵੈੱਬਸਾਈਟ ਉੱਤੇ ਇਸ ਪੋਸਟ ਨੂੰ ਇੰਬੈੱਡ ਕਰੋ।", "emoji_button.activity": "ਗਤੀਵਿਧੀ", "emoji_button.clear": "ਮਿਟਾਓ", @@ -227,10 +250,6 @@ "explore.trending_statuses": "ਪੋਸਟਾਂ", "explore.trending_tags": "ਹੈਸ਼ਟੈਗ", "featured_carousel.header": "{count, plural, one {ਟੰਗੀ ਹੋਈ ਪੋਸਟ} other {ਟੰਗੀਆਂ ਹੋਈਆਂ ਪੋਸਟਾਂ}}", - "featured_carousel.next": "ਅੱਗੇ", - "featured_carousel.post": "ਪੋਸਟ", - "featured_carousel.previous": "ਪਿੱਛੇ", - "featured_carousel.slide": "{total} ਵਿੱਚੋਂ {index}", "filter_modal.added.expired_title": "ਫਿਲਟਰ ਦੀ ਮਿਆਦ ਪੁੱਗੀ!", "filter_modal.added.review_and_configure_title": "ਫਿਲਟਰ ਸੈਟਿੰਗਾਂ", "filter_modal.added.settings_link": "ਸੈਟਿੰਗਾਂ ਸਫ਼ਾ", @@ -240,6 +259,7 @@ "filter_modal.select_filter.search": "ਖੋਜੋ ਜਾਂ ਬਣਾਓ", "filter_modal.select_filter.title": "ਇਸ ਪੋਸਟ ਨੂੰ ਫਿਲਟਰ ਕਰੋ", "filter_modal.title.status": "ਇੱਕ ਪੋਸਟ ਨੂੰ ਫਿਲਟਰ ਕਰੋ", + "filtered_notifications_banner.title": "ਫਿਲਟਰ ਕੀਤੇ ਨੋਟੀਫਿਕੇਸ਼ਨ", "firehose.all": "ਸਭ", "firehose.local": "ਇਹ ਸਰਵਰ", "firehose.remote": "ਹੋਰ ਸਰਵਰ", @@ -253,6 +273,7 @@ "follow_suggestions.who_to_follow": "ਕਿਸ ਨੂੰ ਫ਼ਾਲੋ ਕਰੀਏ", "followed_tags": "ਫ਼ਾਲੋ ਕੀਤੇ ਹੈਸ਼ਟੈਗ", "footer.about": "ਸਾਡੇ ਬਾਰੇ", + "footer.about_this_server": "ਇਸ ਬਾਰੇ", "footer.directory": "ਪਰੋਫਾਇਲ ਡਾਇਰੈਕਟਰੀ", "footer.get_app": "ਐਪ ਲਵੋ", "footer.keyboard_shortcuts": "ਕੀਬੋਰਡ ਸ਼ਾਰਟਕੱਟ", @@ -262,6 +283,7 @@ "footer.terms_of_service": "ਸੇਵਾ ਦੀਆਂ ਸ਼ਰਤਾਂ", "generic.saved": "ਸਾਂਭੀ ਗਈ", "getting_started.heading": "ਸ਼ੁਰੂ ਕਰੀਏ", + "hashtag.browse": "#{hashtag} ਵਿੱਚ ਪੋਸਟਾਂ ਨੂੰ ਵੇਖੋ", "hashtag.column_header.tag_mode.all": "ਅਤੇ {additional}", "hashtag.column_header.tag_mode.any": "ਜਾਂ {additional}", "hashtag.column_header.tag_mode.none": "{additional} ਬਿਨਾਂ", @@ -272,6 +294,7 @@ "hashtag.column_settings.tag_mode.none": "ਇਹਨਾਂ ਵਿੱਚੋਂ ਕੋਈ ਨਹੀਂ", "hashtag.column_settings.tag_toggle": "Include additional tags in this column", "hashtag.follow": "ਹੈਸ਼ਟੈਗ ਨੂੰ ਫ਼ਾਲੋ ਕਰੋ", + "hashtag.mute": "#{hashtag} ਨੂੰ ਮੌਨ ਕਰੋ", "hashtag.unfollow": "ਹੈਸ਼ਟੈਗ ਨੂੰ ਅਣ-ਫ਼ਾਲੋ ਕਰੋ", "hints.profiles.see_more_followers": "{domain} ਉੱਤੇ ਹੋਰ ਫ਼ਾਲੋਅਰ ਵੇਖੋ", "hints.profiles.see_more_follows": "{domain} ਉੱਤੇ ਹੋਰ ਫ਼ਾਲੋ ਨੂੰ ਵੇਖੋ", @@ -288,6 +311,7 @@ "interaction_modal.no_account_yet": "ਹਾਲੇ ਖਾਤਾ ਨਹੀਂ ਹੈ?", "interaction_modal.on_another_server": "ਵੱਖਰੇ ਸਰਵਰ ਉੱਤੇ", "interaction_modal.on_this_server": "ਇਸ ਸਰਵਰ ਉੱਤੇ", + "interaction_modal.title": "ਜਾਰੀ ਰੱਖਣ ਲਈ ਸਾਈਨ ਇਨ ਕਰੋ", "interaction_modal.username_prompt": "ਜਿਵੇਂ {example}", "intervals.full.days": "{number, plural, one {# ਦਿਨ} other {# ਦਿਨ}}", "intervals.full.hours": "{number, plural, one {# ਘੰਟਾ} other {# ਘੰਟੇ}}", @@ -326,6 +350,8 @@ "keyboard_shortcuts.translate": "ਪੋਸਟ ਨੂੰ ਅਨੁਵਾਦ ਕਰਨ ਲਈ", "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", "keyboard_shortcuts.up": "ਸੂਚੀ ਵਿੱਚ ਉੱਤੇ ਭੇਜੋ", + "learn_more_link.got_it": "ਸਮਝ ਗਏ", + "learn_more_link.learn_more": "ਹੋਰ ਜਾਣੋ", "lightbox.close": "ਬੰਦ ਕਰੋ", "lightbox.next": "ਅਗਲੀ", "lightbox.previous": "ਪਿਛਲੀ", @@ -356,6 +382,7 @@ "loading_indicator.label": "ਲੋਡ ਹੋ ਰਿਹਾ ਹੈ…", "media_gallery.hide": "ਲੁਕਾਓ", "mute_modal.hide_from_notifications": "ਨੋਟੀਫਿਕੇਸ਼ਨਾਂ ਵਿੱਚੋਂ ਲੁਕਾਓ", + "mute_modal.hide_options": "ਚੋਣਾਂ ਨੂੰ ਲੁਕਾਓ", "mute_modal.show_options": "ਚੋਣਾਂ ਨੂੰ ਵੇਖਾਓ", "mute_modal.title": "ਵਰਤੋਂਕਾਰ ਨੂੰ ਮੌਨ ਕਰਨਾ ਹੈ?", "navigation_bar.about": "ਇਸ ਬਾਰੇ", @@ -373,6 +400,8 @@ "navigation_bar.followed_tags": "ਫ਼ਾਲੋ ਕੀਤੇ ਹੈਸ਼ਟੈਗ", "navigation_bar.follows_and_followers": "ਫ਼ਾਲੋ ਅਤੇ ਫ਼ਾਲੋ ਕਰਨ ਵਾਲੇ", "navigation_bar.lists": "ਸੂਚੀਆਂ", + "navigation_bar.live_feed_local": "ਲਾਈਵ ਫੀਡ (ਲੋਕਲ)", + "navigation_bar.live_feed_public": "ਲਾਈਵ ਫੀਡ (ਪਬਲਿਕ)", "navigation_bar.logout": "ਲਾਗ ਆਉਟ", "navigation_bar.more": "ਹੋਰ", "navigation_bar.mutes": "ਮੌਨ ਕੀਤੇ ਵਰਤੋਂਕਾਰ", @@ -409,16 +438,22 @@ "notification_requests.edit_selection": "ਸੋਧੋ", "notification_requests.exit_selection": "ਮੁਕੰਮਲ", "notification_requests.notifications_from": "{name} ਵਲੋਂ ਨੋਟੀਫਿਕੇਸ਼ਨ", + "notification_requests.title": "ਫਿਲਟਰ ਕੀਤੇ ਨੋਟੀਫਿਕੇਸ਼ਨ", + "notifications.clear": "ਸੂਚਨਾਵਾਂ ਨੂੰ ਮਿਟਾਓ", + "notifications.clear_confirmation": "ਕੀ ਤੁਸੀਂ ਆਪਣੇ ਸਾਰੇ ਨੋਟੀਫਿਕੇਸ਼ਨਾਂ ਨੂੰ ਪੱਕੇ ਤੌਰ ਉੱਤੇ ਹਟਾਉਣਾ ਚਾਹੁੰਦੇ ਹੋ?", "notifications.clear_title": "ਨੋਟਫਿਕੇਸ਼ਨਾਂ ਨੂੰ ਮਿਟਾਉਣਾ ਹੈ?", "notifications.column_settings.admin.report": "ਨਵੀਆਂ ਰਿਪੋਰਟਾਂ:", "notifications.column_settings.alert": "ਡੈਸਕਟਾਪ ਸੂਚਨਾਵਾਂ", "notifications.column_settings.favourite": "ਮਨਪਸੰਦ:", + "notifications.column_settings.filter_bar.advanced": "ਸਭ ਵਰਗਾਂ ਨੂੰ ਵੇਖਾਓ", "notifications.column_settings.filter_bar.category": "ਫੌਰੀ ਫਿਲਟਰ ਪੱਟੀ", "notifications.column_settings.follow": "ਨਵੇਂ ਫ਼ਾਲੋਅਰ:", "notifications.column_settings.follow_request": "ਨਵੀਆਂ ਫ਼ਾਲੋ ਬੇਨਤੀਆਂ:", "notifications.column_settings.group": "ਗਰੁੱਪ", "notifications.column_settings.mention": "ਜ਼ਿਕਰ:", "notifications.column_settings.poll": "ਪੋਲ ਦੇ ਨਤੀਜੇ:", + "notifications.column_settings.push": "ਪੁਸ਼ ਨੋਟੀਫਿਕੇਸ਼ਨ", + "notifications.column_settings.quote": "ਹਵਾਲੇ:", "notifications.column_settings.reblog": "ਬੂਸਟ:", "notifications.column_settings.show": "ਕਾਲਮ ਵਿੱਚ ਵੇਖਾਓ", "notifications.column_settings.sound": "ਆਵਾਜ਼ ਚਲਾਓ", @@ -432,8 +467,10 @@ "notifications.filter.follows": "ਫ਼ਾਲੋ", "notifications.filter.mentions": "ਜ਼ਿਕਰ", "notifications.filter.polls": "ਪੋਲ ਦੇ ਨਤੀਜੇ", + "notifications.filter.statuses": "ਤੁਹਾਡੇ ਵਲੋਂ ਫ਼ਾਲੋ ਕੀਤੇ ਲੋਕਾਂ ਤੋਂ ਅੱਪਡੇਟ", "notifications.grant_permission": "ਇਜਾਜ਼ਤ ਦਿਓ।", "notifications.group": "{count} ਨੋਟੀਫਿਕੇਸ਼ਨ", + "notifications.mark_as_read": "ਹਰ ਨੋਟੀਫਿਕੇਸ਼ਨ ਨੂੰ ਪੜ੍ਹੇ ਵਜੋਂ ਨਿਸ਼ਾਨੀ ਲਾਓ", "notifications.policy.accept": "ਮਨਜ਼ੂਰ", "notifications.policy.accept_hint": "ਨੋਟੀਫਿਕੇਸ਼ਨਾਂ ਵਿੱਚ ਵੇਖਾਓ", "notifications.policy.drop": "ਅਣਡਿੱਠਾ", @@ -456,6 +493,7 @@ "poll.voted": "ਤੁਸੀਂ ਇਸ ਜਵਾਬ ਲਈ ਵੋਟ ਕੀਤਾ", "privacy.change": "ਪੋਸਟ ਦੀ ਪਰਦੇਦਾਰੀ ਨੂੰ ਬਦਲੋ", "privacy.direct.long": "ਪੋਸਟ ਵਿੱਚ ਜ਼ਿਕਰ ਕੀਤੇ ਹਰ ਕੋਈ", + "privacy.direct.short": "ਨਿੱਜੀ ਜ਼ਿਕਰ", "privacy.private.long": "ਸਿਰਫ਼ ਤੁਹਾਡੇ ਫ਼ਾਲੋਅਰ ਹੀ", "privacy.private.short": "ਫ਼ਾਲੋਅਰ", "privacy.public.short": "ਜਨਤਕ", @@ -475,7 +513,9 @@ "relative_time.minutes": "{number}ਮਿੰ", "relative_time.seconds": "{number}ਸ", "relative_time.today": "ਅੱਜ", + "remove_quote_hint.button_label": "ਸਮਝ ਗਏ", "reply_indicator.cancel": "ਰੱਦ ਕਰੋ", + "reply_indicator.poll": "ਵੋਟਾਂ", "report.block": "ਬਲਾਕ", "report.categories.legal": "ਕਨੂੰਨੀ", "report.categories.other": "ਬਾਕੀ", @@ -483,6 +523,7 @@ "report.category.title_account": "ਪਰੋਫਾਈਲ", "report.category.title_status": "ਪੋਸਟ", "report.close": "ਮੁਕੰਮਲ", + "report.forward": "{target} ਨੂੰ ਅੱਗੇ ਭੇਜੋ", "report.mute": "ਮੌਨ ਕਰੋ", "report.next": "ਅਗਲੀ", "report.placeholder": "ਵਧੀਕ ਟਿੱਪਣੀਆਂ", @@ -500,6 +541,7 @@ "report.unfollow": "@{name} ਨੂੰ ਅਣ-ਫ਼ਾਲੋ ਕਰੋ", "report_notification.attached_statuses": "{count, plural, one {# post} other {# posts}} attached", "report_notification.categories.legal": "ਕਨੂੰਨੀ", + "report_notification.categories.legal_sentence": "ਗ਼ੈਰ-ਕਨੂੰਨੀ ਸਮੱਗਰੀ", "report_notification.categories.other": "ਬਾਕੀ", "report_notification.categories.other_sentence": "ਹੋਰ", "report_notification.categories.spam": "ਸਪੈਮ", @@ -507,6 +549,7 @@ "report_notification.categories.violation": "ਨਿਯਮ ਦੀ ਉਲੰਘਣਾ", "report_notification.categories.violation_sentence": "ਨਿਯਮ ਦੀ ਉਲੰਘਣਾ", "report_notification.open": "ਰਿਪੋਰਟ ਨੂੰ ਖੋਲ੍ਹੋ", + "search.clear": "ਖੋਜ ਨੂੰ ਸਾਫ਼ ਕਰੋ", "search.no_recent_searches": "ਕੋਈ ਸੱਜਰੀ ਖੋਜ ਨਹੀਂ ਹੈ", "search.placeholder": "ਖੋਜੋ", "search.quick_action.go_to_account": "ਪਰੋਫਾਈਲ {x} ਉੱਤੇ ਜਾਓ", @@ -523,6 +566,7 @@ "search_results.no_results": "ਕੋਈ ਨਤੀਜੇ ਨਹੀਂ ਹਨ।", "search_results.see_all": "ਸਭ ਵੇਖੋ", "search_results.statuses": "ਪੋਸਟਾਂ", + "search_results.title": "\"{q}\" ਨੂੰ ਖੋਜੋ", "server_banner.active_users": "ਸਰਗਰਮ ਵਰਤੋਂਕਾਰ", "sign_in_banner.create_account": "ਖਾਤਾ ਬਣਾਓ", "sign_in_banner.sign_in": "ਲਾਗਇਨ", @@ -530,8 +574,11 @@ "status.admin_status": "", "status.block": "@{name} ਉੱਤੇ ਪਾਬੰਦੀ ਲਾਓ", "status.bookmark": "ਬੁੱਕਮਾਰਕ", + "status.context.retry": "ਮੁੜ-ਕੋਸ਼ਿਸ਼", + "status.context.show": "ਵੇਖਾਓ", "status.copy": "ਪੋਸਟ ਲਈ ਲਿੰਕ ਕਾਪੀ ਕਰੋ", "status.delete": "ਹਟਾਓ", + "status.delete.success": "ਪੋਸਟ ਨੂੰ ਹਟਾਇਆ", "status.direct": "{name} ਪ੍ਰਾਈਵੇਟ ਜ਼ਿਕਰ", "status.direct_indicator": "ਪ੍ਰਾਈਵੇਟ ਜ਼ਿਕਰ", "status.edit": "ਸੋਧ", @@ -550,13 +597,18 @@ "status.mute_conversation": "ਗੱਲਬਾਤ ਨੂੰ ਮੌਨ ਕਰੋ", "status.open": "ਇਹ ਪੋਸਟ ਨੂੰ ਫੈਲਾਓ", "status.pin": "ਪਰੋਫਾਈਲ ਉੱਤੇ ਟੰਗੋ", + "status.quote": "ਹਵਾਲਾ", + "status.quote.cancel": "ਹਵਾਲੇ ਨੂੰ ਰੱਦ ਕਰੋ", + "status.quotes_count": "{count, plural, one {{counter} ਹਵਾਲਾ} other {{counter} ਹਵਾਲੇ}}", "status.read_more": "ਹੋਰ ਪੜ੍ਹੋ", - "status.reblog": "ਵਧਾਓ", + "status.reblog": "ਬੂਸਟ", + "status.reblog_or_quote": "ਬੂਸਟ ਜਾਂ ਹਵਾਲਾ", "status.reblogged_by": "{name} ਨੇ ਬੂਸਟ ਕੀਤਾ", "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.", "status.redraft": "ਹਟਾਓ ਤੇ ਮੁੜ-ਡਰਾਫਟ", "status.remove_bookmark": "ਬੁੱਕਮਾਰਕ ਨੂੰ ਹਟਾਓ", "status.remove_favourite": "ਮਨਪਸੰਦ ਵਿੱਚੋਂ ਹਟਾਓ", + "status.remove_quote": "ਹਟਾਓ", "status.replied_in_thread": "ਮਾਮਲੇ ਵਿੱਚ ਜਵਾਬ ਦਿਓ", "status.replied_to": "{name} ਨੂੰ ਜਵਾਬ ਦਿੱਤਾ", "status.reply": "ਜਵਾਬ ਦੇਵੋ", @@ -601,6 +653,13 @@ "video.mute": "ਮੌਨ", "video.pause": "ਠਹਿਰੋ", "video.play": "ਚਲਾਓ", + "video.skip_backward": "ਛੱਡ ਕੇ ਪਿੱਛੇ ਜਾਓ", + "video.skip_forward": "ਛੱਡ ਕੇ ਅੱਗੇ ਜਾਓ", "video.volume_down": "ਅਵਾਜ਼ ਘਟਾਓ", - "video.volume_up": "ਅਵਾਜ਼ ਵਧਾਓ" + "video.volume_up": "ਅਵਾਜ਼ ਵਧਾਓ", + "visibility_modal.quote_followers": "ਸਿਰਫ਼ ਫ਼ਾਲੋਅਰ", + "visibility_modal.quote_label": "ਕੌਣ ਹਵਾਲਾ ਦੇ ਸਕਦਾ ਹੈ", + "visibility_modal.quote_nobody": "ਸਿਰਫ਼ ਮੈਂ", + "visibility_modal.quote_public": "ਕੋਈ ਵੀ", + "visibility_modal.save": "ਸੰਭਾਲੋ" } diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index ab1062ed8b0dd2..a6fd027f7ac3d9 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -20,7 +20,7 @@ "account.block": "Blokuj @{name}", "account.block_domain": "Blokuj wszystko z {domain}", "account.block_short": "Zablokuj", - "account.blocked": "Zablokowany(-a)", + "account.blocked": "Zablokowano", "account.blocking": "Blokowanie", "account.cancel_follow_request": "Nie obserwuj", "account.copy": "Skopiuj link do profilu", @@ -28,11 +28,12 @@ "account.disable_notifications": "Przestań powiadamiać mnie o wpisach @{name}", "account.domain_blocking": "Blokowanie domeny", "account.edit_profile": "Edytuj profil", + "account.edit_profile_short": "Edytuj", "account.enable_notifications": "Powiadamiaj mnie o wpisach @{name}", "account.endorse": "Wyróżnij na profilu", - "account.familiar_followers_many": "Obserwowane przez: {name1}, {name2} i {othersCount, plural, one {jeszcze jedną osobę, którą znasz} few {# inne osoby, które znasz} many {# innych osób, które znasz} other {# innych osób, które znasz}}", - "account.familiar_followers_one": "Obserwowane przez {name1}", - "account.familiar_followers_two": "Obserwowane przez {name1} i {name2}", + "account.familiar_followers_many": "To konto jest obserwowane przez {name1}, {name2} i {othersCount, plural, one {jedną inną znaną ci osobę} few {# inne znane ci osoby} many {# innych znanych ci osób} other {# innych znanych ci osób}}", + "account.familiar_followers_one": "To konto jest obserwowane przez {name1}", + "account.familiar_followers_two": "To konto jest obserwowane przez {name1} i {name2}", "account.featured": "Wyróżnione", "account.featured.accounts": "Profile", "account.featured.hashtags": "Tagi", @@ -40,10 +41,15 @@ "account.featured_tags.last_status_never": "Brak postów", "account.follow": "Obserwuj", "account.follow_back": "Również obserwuj", + "account.follow_back_short": "Również obserwuj", + "account.follow_request": "Poproś o zgodę na obserwowanie", + "account.follow_request_cancel": "Anuluj", + "account.follow_request_cancel_short": "Anuluj", + "account.follow_request_short": "Poproś", "account.followers": "Obserwujący", "account.followers.empty": "Nikt jeszcze nie obserwuje tego użytkownika.", "account.followers_counter": "{count, plural, one {{counter} obserwujący} few {{counter} obserwujących} many {{counter} obserwujących} other {{counter} obserwujących}}", - "account.followers_you_know_counter": "{counter} które znasz", + "account.followers_you_know_counter": "znasz {counter}", "account.following": "Obserwowani", "account.following_counter": "{count, plural, one {{counter} obserwowany} few {{counter} obserwowanych} many {{counter} obserwowanych} other {{counter} obserwowanych}}", "account.follows.empty": "Ten użytkownik nie obserwuje jeszcze nikogo.", @@ -62,16 +68,15 @@ "account.mute_notifications_short": "Wycisz powiadomienia", "account.mute_short": "Wycisz", "account.muted": "Wyciszony", - "account.muting": "Wyciszenie", - "account.mutual": "Obserwujecie siebie nazwajem", + "account.muting": "Wyciszanie", + "account.mutual": "Obserwujecie się wzajemnie", "account.no_bio": "Brak opisu.", "account.open_original_page": "Otwórz stronę oryginalną", "account.posts": "Wpisy", "account.posts_with_replies": "Wpisy i odpowiedzi", "account.remove_from_followers": "Usuń {name} z obserwujących", "account.report": "Zgłoś @{name}", - "account.requested": "Oczekująca prośba, kliknij aby anulować", - "account.requested_follow": "{name} chce cię zaobserwować", + "account.requested_follow": "{name} chce cię obserwować", "account.requests_to_follow_you": "Prośby o obserwowanie", "account.share": "Udostępnij profil @{name}", "account.show_reblogs": "Pokazuj podbicia od @{name}", @@ -86,8 +91,8 @@ "account.unmute_notifications_short": "Nie wyciszaj powiadomień", "account.unmute_short": "Nie wyciszaj", "account_note.placeholder": "Kliknij, aby dodać notatkę", - "admin.dashboard.daily_retention": "Wskaźnik utrzymania użytkowników po dniach od rejestracji", - "admin.dashboard.monthly_retention": "Wskaźnik utrzymania użytkowników po miesiącach od rejestracji", + "admin.dashboard.daily_retention": "Wskaźnik utrzymania użytkowników według dni od rejestracji", + "admin.dashboard.monthly_retention": "Wskaźnik utrzymania użytkowników według miesięcy od rejestracji", "admin.dashboard.retention.average": "Średnia", "admin.dashboard.retention.cohort": "Miesiąc rejestracji", "admin.dashboard.retention.cohort_size": "Nowi użytkownicy", @@ -108,25 +113,11 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Opisz to dla osób niedowidzących…", "alt_text_modal.done": "Gotowe", "announcement.announcement": "Ogłoszenie", - "annual_report.summary.archetype.booster": "Łowca treści", - "annual_report.summary.archetype.lurker": "Czyhający", - "annual_report.summary.archetype.oracle": "Wyrocznia", - "annual_report.summary.archetype.pollster": "Ankieter", - "annual_report.summary.archetype.replier": "Motyl społeczny", - "annual_report.summary.followers.followers": "obserwujących", - "annual_report.summary.followers.total": "łącznie {count}", - "annual_report.summary.here_it_is": "Oto przegląd Twojego {year} roku:", - "annual_report.summary.highlighted_post.by_favourites": "najbardziej lubiany wpis", - "annual_report.summary.highlighted_post.by_reblogs": "najczęściej podbijany wpis", - "annual_report.summary.highlighted_post.by_replies": "wpis z największą liczbą komentarzy", - "annual_report.summary.highlighted_post.possessive": "{name}", "annual_report.summary.most_used_app.most_used_app": "najczęściej używana aplikacja", "annual_report.summary.most_used_hashtag.most_used_hashtag": "najczęściej używany hashtag", - "annual_report.summary.most_used_hashtag.none": "Brak", "annual_report.summary.new_posts.new_posts": "nowe wpisy", "annual_report.summary.percentile.text": "To plasuje cię w czołówce użytkowników {domain}.", "annual_report.summary.percentile.we_wont_tell_bernie": "Nie powiemy Berniemu.", - "annual_report.summary.thanks": "Dziękujemy, że jesteś częścią Mastodona!", "attachments_list.unprocessed": "(nieprzetworzone)", "audio.hide": "Ukryj dźwięk", "block_modal.remote_users_caveat": "Poprosimy serwer {domain} o uszanowanie twojej decyzji. Nie jest to jednak gwarantowane, bo niektóre serwery mogą obsługiwać blokady w inny sposób. Publiczne wpisy mogą być nadal widoczne dla niezalogowanych użytkowników.", @@ -168,6 +159,7 @@ "column.edit_list": "Edytuj listę", "column.favourites": "Ulubione", "column.firehose": "Aktualności", + "column.firehose_singular": "Na żywo", "column.follow_requests": "Prośby o obserwację", "column.home": "Strona główna", "column.list_members": "Zarządzaj osobami na liście", @@ -188,7 +180,7 @@ "community.column_settings.media_only": "Tylko multimedia", "community.column_settings.remote_only": "Tylko zdalne", "compose.language.change": "Zmień język", - "compose.language.search": "Szukaj języków...", + "compose.language.search": "Wyszukaj języki...", "compose.published.body": "Wpis został opublikowany.", "compose.published.open": "Otwórz", "compose.saved.body": "Wpis został zapisany.", @@ -221,14 +213,14 @@ "confirmations.delete_list.title": "Usunąć listę?", "confirmations.discard_draft.confirm": "Odrzuć i kontynuuj", "confirmations.discard_draft.edit.cancel": "Wznów edytowanie", - "confirmations.discard_draft.edit.message": "Kontynuowanie spowoduje utratę wszystkich zmian wprowadzonych przez Ciebie w aktualnie edytowanym poście.", - "confirmations.discard_draft.edit.title": "Odrzucić zmiany w poście?", - "confirmations.discard_draft.post.cancel": "Wznów wersję roboczą", - "confirmations.discard_draft.post.message": "Kontynuacja odrzuci aktualnie tworzony post.", - "confirmations.discard_draft.post.title": "Anulować wersję roboczą?", + "confirmations.discard_draft.edit.message": "Kontynuowanie spowoduje utratę wszelkich zmian wprowadzonych w aktualnie edytowanym wpisie.", + "confirmations.discard_draft.edit.title": "Czy chcesz odrzucić zmiany w swoim wpisie?", + "confirmations.discard_draft.post.cancel": "Wznów szkic", + "confirmations.discard_draft.post.message": "Kontynuowanie spowoduje usunięcie aktualnie tworzonego wpisu.", + "confirmations.discard_draft.post.title": "Odrzucić szkic wpisu?", "confirmations.discard_edit_media.confirm": "Odrzuć", - "confirmations.discard_edit_media.message": "Masz niezapisane zmiany w opisie lub podglądzie, odrzucić je mimo to?", - "confirmations.follow_to_list.confirm": "Zaobserwuj i dodaj do listy", + "confirmations.discard_edit_media.message": "Masz niezapisane zmiany w opisie lub podglądzie multimediów. Czy chcesz je mimo to odrzucić?", + "confirmations.follow_to_list.confirm": "Obserwuj i dodaj do listy", "confirmations.follow_to_list.message": "Musisz obserwować {name}, aby dodać do listy.", "confirmations.follow_to_list.title": "Zaobserwować?", "confirmations.logout.confirm": "Wyloguj", @@ -236,21 +228,28 @@ "confirmations.logout.title": "Wylogować?", "confirmations.missing_alt_text.confirm": "Dodaj opis pomocniczy", "confirmations.missing_alt_text.message": "Twój wpis zawiera multimedia bez tekstu alternatywnego. Dodanie opisów pomaga zwiększyć dostępność tych treści dla większej liczby osób.", - "confirmations.missing_alt_text.secondary": "Opublikuj mimo to ", + "confirmations.missing_alt_text.secondary": "Opublikuj mimo wszystko", "confirmations.missing_alt_text.title": "Dodać tekst pomocniczy?", "confirmations.mute.confirm": "Wycisz", + "confirmations.quiet_post_quote_info.dismiss": "Nie przypominaj mi ponownie", + "confirmations.quiet_post_quote_info.got_it": "Rozumiem", + "confirmations.quiet_post_quote_info.message": "Kiedy cytujesz niewidoczny wpis publiczny, twój wpis zostanie ukryty z popularnych osi czasu.", + "confirmations.quiet_post_quote_info.title": "Cytowanie niewidocznych wpisów publicznych", "confirmations.redraft.confirm": "Usuń i popraw", "confirmations.redraft.message": "Czy na pewno chcesz usunąć i poprawić ten wpis? Polubienia, podbicia i komentarze pierwotnego wpisu zostaną utracone.", "confirmations.redraft.title": "Usunąć i poprawić wpis?", - "confirmations.remove_from_followers.confirm": "Usuń obserwującego", - "confirmations.remove_from_followers.message": "{name} przestanie Cię obserwować. Czy na pewno chcesz kontynuować?", - "confirmations.remove_from_followers.title": "Usunąć obserwującego?", - "confirmations.revoke_quote.confirm": "Usuń post", - "confirmations.revoke_quote.message": "Tej akcji nie można cofnąć.", - "confirmations.revoke_quote.title": "Usuń post?", + "confirmations.remove_from_followers.confirm": "Usuń z obserwujących", + "confirmations.remove_from_followers.message": "{name} przestanie cię obserwować. Czy na pewno chcesz kontynuować?", + "confirmations.remove_from_followers.title": "Usunąć z obserwujących?", + "confirmations.revoke_quote.confirm": "Usuń wpis", + "confirmations.revoke_quote.message": "Tej czynności nie można cofnąć.", + "confirmations.revoke_quote.title": "Usunąć wpis?", + "confirmations.unblock.confirm": "Odblokuj", + "confirmations.unblock.title": "Odblokować {name}?", "confirmations.unfollow.confirm": "Nie obserwuj", - "confirmations.unfollow.message": "Czy na pewno nie chcesz obserwować {name}?", - "confirmations.unfollow.title": "Cofnąć obserwację?", + "confirmations.unfollow.title": "Przestać obserwować {name}?", + "confirmations.withdraw_request.confirm": "Wycofaj prośbę", + "confirmations.withdraw_request.title": "Wycofać prośbę o zgodę na obserwowanie {name}?", "content_warning.hide": "Ukryj wpis", "content_warning.show": "Pokaż mimo to", "content_warning.show_more": "Pokaż więcej", @@ -284,7 +283,7 @@ "domain_pill.server": "Serwer", "domain_pill.their_handle": "Nazwa:", "domain_pill.their_server": "Cyfrowy dom wszystkich wpisów tej osoby.", - "domain_pill.their_username": "Unikalny identyfikator na serwerze. Możliwe jest znalezienie użytkowników o tej samej nazwie użytkownika na różnych serwerach.", + "domain_pill.their_username": "Unikalny identyfikator na serwerze. Możliwe jest znalezienie użytkowników o tej samej nazwie na różnych serwerach.", "domain_pill.username": "Nazwa użytkownika", "domain_pill.whats_in_a_handle": "Z czego składa się nazwa?", "domain_pill.who_they_are": "Dzięki temu, że nazwy wskazują, kim ktoś jest i gdzie się znajduje, możesz wchodzić w interakcje z innymi z różnych .", @@ -306,12 +305,12 @@ "emoji_button.objects": "Obiekty", "emoji_button.people": "Ludzie", "emoji_button.recent": "Najczęściej używane", - "emoji_button.search": "Szukaj…", + "emoji_button.search": "Wyszukaj...", "emoji_button.search_results": "Wyniki wyszukiwania", "emoji_button.symbols": "Symbole", "emoji_button.travel": "Podróże i miejsca", - "empty_column.account_featured.me": "Niczego jeszcze nie poleciłeś. Czy wiesz, że możesz wyświetlać swoje hashtagi, z których korzystasz najbardziej, a nawet konta znajomego na swoim profilu?", - "empty_column.account_featured.other": "{acct} nie wyróżnił jeszcze nic. Czy wiesz, że możesz wyświetlać swoje hashtagi, z których korzystasz najbardziej, a nawet konta znajomego na swoim profilu?", + "empty_column.account_featured.me": "Nie dodano jeszcze żadnych polecanych treści. Czy wiesz, że możesz wyróżnić najczęściej używane hashtagi, a nawet konta znajomych na swoim profilu?", + "empty_column.account_featured.other": "Konto {acct} nie wyróżniło jeszcze żadnych treści. Czy wiesz, że możesz wyróżnić najczęściej używane hashtagi, a nawet konta znajomych w swoim profilu?", "empty_column.account_featured_other.unknown": "To konto nie zostało jeszcze wyróżnione.", "empty_column.account_hides_collections": "Ta osoba postanowiła nie udostępniać tych informacji", "empty_column.account_suspended": "Konto zawieszone", @@ -321,6 +320,7 @@ "empty_column.bookmarked_statuses": "Nie dodano jeszcze żadnego wpisu do zakładek. Gdy to zrobisz, pojawi się tutaj.", "empty_column.community": "Lokalna oś czasu jest pusta. Opublikuj coś, by ruszyć z kopyta!", "empty_column.direct": "Nie ma tu jeszcze żadnych wzmianek bezpośrednich. Gdy je wyślesz lub otrzymasz, pojawią się tutaj.", + "empty_column.disabled_feed": "Ten kanał został wyłączony przez administratorów serwera.", "empty_column.domain_blocks": "Brak zablokowanych domen.", "empty_column.explore_statuses": "Nic nie cieszy się teraz popularnością. Sprawdź później!", "empty_column.favourited_statuses": "Nie polubiono jeszcze żadnego wpisu. Gdy to zrobisz, pojawi się tutaj.", @@ -345,11 +345,7 @@ "explore.trending_links": "Aktualności", "explore.trending_statuses": "Wpisy", "explore.trending_tags": "Hasztagi", - "featured_carousel.header": "{count, plural, one {Przypięty post} other {Przypięte posty}}", - "featured_carousel.next": "Następny", - "featured_carousel.post": "Opublikuj", - "featured_carousel.previous": "Poprzedni", - "featured_carousel.slide": "{index} z {total}", + "featured_carousel.header": "{count, plural, one {przypięty wpis} few {przypięte wpisy} many {przypięte wpisy} other {przypięte wpisy}}", "filter_modal.added.context_mismatch_explanation": "To filtrowanie nie dotyczy kategorii, w której pojawił się ten wpis. Jeśli chcesz, aby wpis był filtrowany również w tym kontekście, musisz edytować ustawienia filtrowania.", "filter_modal.added.context_mismatch_title": "Niewłaściwy kontekst!", "filter_modal.added.expired_explanation": "Ta kategoria filtrowania wygasła, aby ją zastosować, należy zmienić datę wygaśnięcia.", @@ -449,10 +445,12 @@ "ignore_notifications_modal.private_mentions_title": "Ignorować powiadomienia od niechcianych wzmianek bezpośrednich?", "info_button.label": "Pomoc", "info_button.what_is_alt_text": "

Czym jest tekst alternatywny?

Tekst alternatywny zawiera opisy zdjęć dla osób niedowidzących, korzystających z połączeń o niskiej przepustowości lub szukających dodatkowego kontekstu.

\n

Możesz poprawić dostępność i czytelność dla wszystkich, pisząc jasny, zwięzły i precyzyjny tekst alternatywny.

\n
    \n
  • Podkreśl ważne elementy
  • \n
  • Streść tekst widoczny na zdjęciach
  • \n
  • Używaj poprawnej struktury zdań
  • \n
  • Unikaj zbędnych informacji
  • \n
  • Skoncentruj się na kluczowych informacjach zawartych w złożonych wizualizacjach (takich jak diagramy lub mapy)
  • \n
", + "interaction_modal.action": "Aby wejść w interakcję z wpisem od {name}, musisz zalogować się na swoje konto na dowolnym serwerze Mastodon, na którym masz już konto.", "interaction_modal.go": "Dalej", "interaction_modal.no_account_yet": "Nie masz jeszcze konta?", "interaction_modal.on_another_server": "Na innym serwerze", "interaction_modal.on_this_server": "Na tym serwerze", + "interaction_modal.title": "Zaloguj się, aby kontynuować", "interaction_modal.username_prompt": "Np. {example}", "intervals.full.days": "{number, plural, one {# dzień} few {# dni} many {# dni} other {# dni}}", "intervals.full.hours": "{number, plural, one {# godzina} few {# godziny} many {# godzin} other {# godzin}}", @@ -473,6 +471,7 @@ "keyboard_shortcuts.home": "Otwórz stronę główną", "keyboard_shortcuts.hotkey": "Skrót klawiszowy", "keyboard_shortcuts.legend": "Wyświetl skróty klawiszowe", + "keyboard_shortcuts.load_more": "Aktywuj przycisk \"Załaduj więcej\"", "keyboard_shortcuts.local": "Otwórz lokalną oś czasu", "keyboard_shortcuts.mention": "Dodaj wzmiankę", "keyboard_shortcuts.muted": "Otwórz listę wyciszonych", @@ -734,9 +733,15 @@ "privacy.quote.disabled": "{visibility}, cytaty wyłączone", "privacy.quote.limited": "{visibility}, cytaty ograniczone", "privacy.unlisted.additional": "Dostępny podobnie jak wpis publiczny, ale nie będzie widoczny w aktualnościach, hashtagach ani wyszukiwarce Mastodon, nawet jeśli twoje konto jest widoczne.", + "privacy.unlisted.long": "Ukryte w wynikach wyszukiwania Mastodona, trendach i publicznych osiach czasu", "privacy.unlisted.short": "Niewidoczny", "privacy_policy.last_updated": "Data ostatniej aktualizacji: {date}", "privacy_policy.title": "Polityka prywatności", + "quote_error.edit": "Podczas edycji wpisu nie można dodawać cytatów.", + "quote_error.poll": "W ankietach nie można cytować.", + "quote_error.quote": "Dozwolone jest tylko jedno cytowanie na raz.", + "quote_error.unauthorized": "Nie masz uprawnień do cytowania tego wpisu.", + "quote_error.upload": "Cytowanie nie jest dozwolone w przypadku załączników multimedialnych.", "recommended": "Zalecane", "refresh": "Odśwież", "regeneration_indicator.please_stand_by": "Proszę czekać.", @@ -753,6 +758,7 @@ "relative_time.seconds": "{number} s.", "relative_time.today": "dzisiaj", "remove_quote_hint.button_label": "Rozumiem", + "remove_quote_hint.message": "Można to zrobić z poziomu menu opcji {icon}.", "remove_quote_hint.title": "Czy chcesz usunąć swój cytowany post?", "reply_indicator.attachments": "{count, plural, one {# załącznik} few {# załączniki} many {# załączników} other {# załączników}}", "reply_indicator.cancel": "Anuluj", @@ -845,12 +851,19 @@ "status.admin_account": "Otwórz interfejs moderacyjny dla @{name}", "status.admin_domain": "Otwórz interfejs moderacyjny dla {domain}", "status.admin_status": "Otwórz ten wpis w interfejsie moderacyjnym", + "status.all_disabled": "Podbicia i cytaty są wyłączone", "status.block": "Zablokuj @{name}", "status.bookmark": "Dodaj zakładkę", "status.cancel_reblog_private": "Cofnij podbicie", + "status.cannot_quote": "Nie można cytować tego wpisu", "status.cannot_reblog": "Ten wpis nie może zostać podbity", - "status.context.load_new_replies": "Dostępne są nowe odpowiedzi", - "status.context.loading": "Sprawdzanie kolejnych odpowiedzi", + "status.contains_quote": "Zawiera cytat", + "status.context.loading": "Wczytywanie kolejnych komentarzy", + "status.context.loading_error": "Nie można wczytać nowych komentarzy", + "status.context.loading_success": "Wczytano nowe komentarze", + "status.context.more_replies_found": "Znaleziono więcej komentarzy", + "status.context.retry": "Spróbuj ponownie", + "status.context.show": "Pokaż", "status.continued_thread": "Ciąg dalszy wątku", "status.copy": "Skopiuj odnośnik do wpisu", "status.delete": "Usuń", @@ -863,7 +876,6 @@ "status.edited_x_times": "Edytowano {count, plural, one {{count} raz} other {{count} razy}}", "status.embed": "Kod osadzenia", "status.favourite": "Dodaj do ulubionych", - "status.favourites": "{count, plural, one {polubienie} few {polubienia} other {polubień}}", "status.filter": "Filtruj ten wpis", "status.history.created": "{name} utworzone {date}", "status.history.edited": "{name} edytowane {date}", @@ -880,26 +892,37 @@ "status.quote": "Cytuj", "status.quote.cancel": "Anuluj cytat", "status.quote_error.filtered": "Ukryte z powodu jednego z Twoich filtrów", + "status.quote_error.limited_account_hint.action": "Pokaż mimo wszystko", + "status.quote_error.limited_account_hint.title": "To konto zostało ukryte przez moderatorów {domain}.", "status.quote_error.not_available": "Post niedostępny", "status.quote_error.pending_approval": "Post oczekujący", + "status.quote_error.pending_approval_popout.body": "Na Mastodon możesz kontrolować, czy ktoś może cytować twoje wpisy. Ten wpis oczekuje na zatwierdzenie przez autora.", + "status.quote_error.revoked": "Wpis został usunięty przez autora", "status.quote_followers_only": "Tylko obserwatorzy mogą cytować ten post", + "status.quote_manual_review": "Autor zatwierdzi ręcznie", + "status.quote_noun": "Cytuj", "status.quote_policy_change": "Zmień kto może cytować", "status.quote_post_author": "Zacytowano post @{name}", "status.quote_private": "Prywatne posty nie mogą być cytowane", + "status.quotes.empty": "Nikt jeszcze nie zacytował tego wpisu. Gdy ktoś to zrobi, pojawi się on tutaj.", + "status.quotes.local_other_disclaimer": "Cytaty odrzucone przez autora nie będą wyświetlane.", + "status.quotes.remote_other_disclaimer": "Będą tutaj wyświetlane tylko cytaty z {domain}. Cytaty odrzucone przez autora nie będą wyświetlane.", "status.read_more": "Czytaj dalej", "status.reblog": "Podbij", "status.reblog_or_quote": "Podbij lub cytuj", + "status.reblog_private": "Udostępnij ponownie swoim obserwującym", "status.reblogged_by": "Podbite przez {name}", - "status.reblogs": "{count, plural, one {podbicie} few {podbicia} other {podbić}}", "status.reblogs.empty": "Nikt nie podbił jeszcze tego wpisu. Gdy ktoś to zrobi, pojawi się tutaj.", "status.redraft": "Usuń i przeredaguj", "status.remove_bookmark": "Usuń zakładkę", "status.remove_favourite": "Usuń z ulubionych", + "status.remove_quote": "Usuń", "status.replied_in_thread": "Odpowiedź w wątku", "status.replied_to": "Odpowiedź do wpisu użytkownika {name}", "status.reply": "Odpowiedz", "status.replyAll": "Odpowiedz na wątek", "status.report": "Zgłoś @{name}", + "status.request_quote": "Poproś o możliwość cytowania", "status.revoke_quote": "Usuń mój wpis z postu @{name}", "status.sensitive_warning": "Wrażliwa zawartość", "status.share": "Udostępnij", @@ -964,6 +987,12 @@ "video.volume_up": "Zwiększ głośność", "visibility_modal.button_title": "Ustaw widoczność", "visibility_modal.header": "Widoczność i interakcja", + "visibility_modal.helper.direct_quoting": "Prywatne wzmianki opublikowane na Mastodonie nie mogą być cytowane przez inne osoby.", + "visibility_modal.helper.privacy_editing": "Widoczność nie może być zmieniona po opublikowaniu wpisu.", + "visibility_modal.helper.privacy_private_self_quote": "Cytaty z prywatnych wpisów nie mogą być publiczne.", + "visibility_modal.helper.private_quoting": "Wpisy publikowane na Mastodonie wyłącznie dla obserwujących nie mogą być cytowane przez inne osoby.", + "visibility_modal.helper.unlisted_quoting": "Kiedy ktoś cytuje twoje wpisy, będą one również ukryte na popularnych osiach czasu.", + "visibility_modal.instructions": "Kontroluj, kto może wchodzić w interakcję z tym wpisem. Możesz również zastosować ustawienia do wszystkich przyszłych wpisów, przechodząc do Preferencje > Domyślne ustawienia publikowania.", "visibility_modal.privacy_label": "Widoczność", "visibility_modal.quote_followers": "Tylko dla obserwujących", "visibility_modal.quote_label": "Kto może cytować", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index cc46fb4304d29b..8fdd502accb36a 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -4,14 +4,14 @@ "about.default_locale": "Padrão", "about.disclaimer": "Mastodon é um software de código aberto e livre, e uma marca registrada de Mastodon gGmbH.", "about.domain_blocks.no_reason_available": "Razão não disponível", - "about.domain_blocks.preamble": "O Mastodon geralmente permite que você veja o conteúdo e interaja com usuários de qualquer outro servidor no fediverso. Estas são as exceções deste servidor em específico.", + "about.domain_blocks.preamble": "O \"Mastodon\" geralmente permite que você veja o conteúdo e interaja com usuários de qualquer outro servidor no \"fediverso\". Estas são as exceções deste servidor em específico.", "about.domain_blocks.silenced.explanation": "Você geralmente não verá perfis e conteúdo deste servidor, a menos que você o procure explicitamente ou opte por seguir.", "about.domain_blocks.silenced.title": "Limitado", "about.domain_blocks.suspended.explanation": "Nenhum dado desse servidor será processado, armazenado ou trocado, impossibilitando qualquer interação ou comunicação com os usuários deste servidor.", "about.domain_blocks.suspended.title": "Suspenso", "about.language_label": "Idioma", "about.not_available": "Esta informação não foi disponibilizada neste servidor.", - "about.powered_by": "Redes sociais descentralizadas alimentadas por {mastodon}", + "about.powered_by": "Rede social descentralizada baseada no {mastodon}", "about.rules": "Regras do servidor", "account.account_note_header": "Nota pessoal", "account.add_or_remove_from_list": "Adicionar ou remover de listas", @@ -28,6 +28,7 @@ "account.disable_notifications": "Cancelar notificações de @{name}", "account.domain_blocking": "Bloqueando domínio", "account.edit_profile": "Editar perfil", + "account.edit_profile_short": "Editar", "account.enable_notifications": "Notificar novos toots de @{name}", "account.endorse": "Recomendar", "account.familiar_followers_many": "Seguido por {name1}, {name2}, e {othersCount, plural, one {um outro que você conhece} other {# outros que você conhece}}", @@ -40,20 +41,25 @@ "account.featured_tags.last_status_never": "Sem publicações", "account.follow": "Seguir", "account.follow_back": "Seguir de volta", + "account.follow_back_short": "Seguir de volta", + "account.follow_request": "Pedir para seguir", + "account.follow_request_cancel": "Cancelar solicitação", + "account.follow_request_cancel_short": "Cancelar", + "account.follow_request_short": "Solicitar", "account.followers": "Seguidores", "account.followers.empty": "Nada aqui.", "account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}", - "account.followers_you_know_counter": "{counter} que você sabe", + "account.followers_you_know_counter": "{counter} que você conhece", "account.following": "Seguindo", "account.following_counter": "{count, plural, one {{counter} seguindo} other {{counter} seguindo}}", "account.follows.empty": "Nada aqui.", "account.follows_you": "Segue você", "account.go_to_profile": "Ir ao perfil", - "account.hide_reblogs": "Ocultar boosts de @{name}", - "account.in_memoriam": "Em memória.", + "account.hide_reblogs": "Ocultar impulsos de @{name}", + "account.in_memoriam": "In Memoriam.", "account.joined_short": "Entrou", "account.languages": "Mudar idiomas inscritos", - "account.link_verified_on": "link verificado em {date}", + "account.link_verified_on": "A propriedade deste link foi verificada em {date}", "account.locked_info": "Trancado. Seguir requer aprovação manual do perfil.", "account.media": "Mídia", "account.mention": "Mencionar @{name}", @@ -70,11 +76,10 @@ "account.posts_with_replies": "Com respostas", "account.remove_from_followers": "Remover {name} dos seguidores", "account.report": "Denunciar @{name}", - "account.requested": "Aguardando aprovação. Clique para cancelar a solicitação", "account.requested_follow": "{name} quer te seguir", "account.requests_to_follow_you": "Pediu para seguir você", "account.share": "Compartilhar perfil de @{name}", - "account.show_reblogs": "Mostrar boosts de @{name}", + "account.show_reblogs": "Mostrar impulsos de @{name}", "account.statuses_counter": "{count, plural, one {{counter} publicação} other {{counter} publicações}}", "account.unblock": "Desbloquear @{name}", "account.unblock_domain": "Desbloquear domínio {domain}", @@ -93,7 +98,7 @@ "admin.dashboard.retention.cohort_size": "Novos usuários", "admin.impact_report.instance_accounts": "Perfis de contas que isso apagaria", "admin.impact_report.instance_followers": "Seguidores que os nossos usuários perderiam", - "admin.impact_report.instance_follows": "Seguidores que os seus usuários perderiam", + "admin.impact_report.instance_follows": "Seguidores que os usuários deles perderiam", "admin.impact_report.title": "Resumo do impacto", "alert.rate_limited.message": "Tente novamente após {retry_time, time, medium}.", "alert.rate_limited.title": "Tentativas limitadas", @@ -101,45 +106,72 @@ "alert.unexpected.title": "Eita!", "alt_text_badge.title": "Texto alternativo", "alt_text_modal.add_alt_text": "Adicione texto alternativo", - "alt_text_modal.add_text_from_image": "Adicione texto da imagem", + "alt_text_modal.add_text_from_image": "Adicione texto a partir da imagem", "alt_text_modal.cancel": "Cancelar", "alt_text_modal.change_thumbnail": "Alterar miniatura", - "alt_text_modal.describe_for_people_with_hearing_impairments": "Descreva isso para pessoas com deficiências auditivas...", - "alt_text_modal.describe_for_people_with_visual_impairments": "Descreva isso para pessoas com deficiências visuais…", + "alt_text_modal.describe_for_people_with_hearing_impairments": "Descreva isto para pessoas com deficiências auditivas…", + "alt_text_modal.describe_for_people_with_visual_impairments": "Descreva isto para pessoas com deficiências visuais…", "alt_text_modal.done": "Feito", "announcement.announcement": "Comunicados", - "annual_report.summary.archetype.booster": "Caçador legal", - "annual_report.summary.archetype.lurker": "O espreitador", - "annual_report.summary.archetype.oracle": "O oráculo", - "annual_report.summary.archetype.pollster": "O pesquisador", - "annual_report.summary.archetype.replier": "A borboleta social", - "annual_report.summary.followers.followers": "seguidores", - "annual_report.summary.followers.total": "{count} total", - "annual_report.summary.here_it_is": "Aqui está seu {year} em revisão:", - "annual_report.summary.highlighted_post.by_favourites": "publicação mais favoritada", - "annual_report.summary.highlighted_post.by_reblogs": "publicação mais impulsionada", - "annual_report.summary.highlighted_post.by_replies": "publicação com mais respostas", - "annual_report.summary.highlighted_post.possessive": "{name}", + "annual_report.announcement.action_build": "Gerar meu Wrapstodon", + "annual_report.announcement.action_dismiss": "Não, obrigado/a", + "annual_report.announcement.action_view": "Ver meu Wrapstodon", + "annual_report.announcement.description": "Descubra mais sobre seu engajamento no Mastodon ao longo do último ano.", + "annual_report.announcement.title": "Chegou o Wrapstodon de {year}", + "annual_report.nav_item.badge": "Novo", + "annual_report.shared_page.donate": "Doe", + "annual_report.shared_page.footer": "Criado com {heart} pela equipe do Mastodon", + "annual_report.shared_page.footer_server_info": "{username} utiliza {domain}, uma das várias comunidades baseadas no Mastodon.", + "annual_report.summary.archetype.booster.desc_public": "{name} se manteve na caça por publicações para impulsionar, amplificando outros criadores com uma mira perfeita.", + "annual_report.summary.archetype.booster.desc_self": "Você se manteve na caça por publicações para impulsionar, amplificando outros criadores com uma mira perfeita.", + "annual_report.summary.archetype.booster.name": "O Arqueiro", + "annual_report.summary.archetype.die_drei_fragezeichen": "???", + "annual_report.summary.archetype.lurker.desc_public": "Sabemos que {name} esteve por aí, em algum lugar, aproveitando o Mastodon do seu próprio jeito silencioso.", + "annual_report.summary.archetype.lurker.desc_self": "Sabemos que você esteve por aí, em algum lugar, aproveitando o Mastodon do seu próprio jeito silencioso.", + "annual_report.summary.archetype.lurker.name": "O Estoico", + "annual_report.summary.archetype.oracle.desc_public": "{name} criou mais publicações novas que respostas, mantendo o Mastodon fresco e em direção ao futuro.", + "annual_report.summary.archetype.oracle.desc_self": "Você criou mais publicações novas que respostas, mantendo o Mastodon fresco e em direção ao futuro.", + "annual_report.summary.archetype.oracle.name": "O Oráculo", + "annual_report.summary.archetype.pollster.desc_public": "{name} criou mais enquetes que quaisquer outros tipos de publicações, cultivando curiosidade no Mastodon.", + "annual_report.summary.archetype.pollster.desc_self": "Você criou mais enquetes que quaisquer outros tipos de publicações, cultivando curiosidade no Mastodon.", + "annual_report.summary.archetype.pollster.name": "O Questionador", + "annual_report.summary.archetype.replier.desc_public": "{name} frequentemente respondeu às publicações de outras pessoas, polinizando o Mastodon com novas discussões.", + "annual_report.summary.archetype.replier.desc_self": "Você frequentemente respondeu às publicações de outras pessoas, polinizando o Mastodon com novas discussões.", + "annual_report.summary.archetype.replier.name": "A Borboleta", + "annual_report.summary.archetype.reveal": "Revele meu arquétipo", + "annual_report.summary.archetype.reveal_description": "Obrigado por ser parte do Mastodon! Está na hora de descobrir qual arquétipo você incorporou em {year}.", + "annual_report.summary.archetype.title_public": "Arquétipo de {name}", + "annual_report.summary.archetype.title_self": "Seu arquétipo", + "annual_report.summary.close": "Fechar", + "annual_report.summary.copy_link": "Copiar link", + "annual_report.summary.followers.new_followers": "{count, plural, one {novo(a) seguidor(a)} other {novos seguidores}}", + "annual_report.summary.highlighted_post.boost_count": "Esta publicação foi impulsionada {count, plural, one {uma vez} other {# vezes}}.", + "annual_report.summary.highlighted_post.favourite_count": "Esta publicação foi favoritada {count, plural, one {uma vez} other {# vezes}}.", + "annual_report.summary.highlighted_post.reply_count": "Esta postagem recebeu {count, plural, one {uma resposta} other {# respostas}}.", + "annual_report.summary.highlighted_post.title": "Publicação mais popular", "annual_report.summary.most_used_app.most_used_app": "aplicativo mais usado", "annual_report.summary.most_used_hashtag.most_used_hashtag": "hashtag mais usada", - "annual_report.summary.most_used_hashtag.none": "Nenhuma", + "annual_report.summary.most_used_hashtag.used_count": "Você incluiu esta hashtag em {count, plural, one {uma publicação} other {# publicações}}.", + "annual_report.summary.most_used_hashtag.used_count_public": "{name} incluiu esta hashtag em {count, plural, one {uma publicação} other {# publicações}}.", "annual_report.summary.new_posts.new_posts": "novas publicações", "annual_report.summary.percentile.text": "Isso lhe coloca no topode usuários de {domain}.", - "annual_report.summary.percentile.we_wont_tell_bernie": "Não contaremos à Bernie.", - "annual_report.summary.thanks": "Obrigada por fazer parte do Mastodon!", + "annual_report.summary.percentile.we_wont_tell_bernie": "Não contaremos ao Bernie.", + "annual_report.summary.share_elsewhere": "Compartilhar em outro lugar", + "annual_report.summary.share_message": "Eu obtive o arquétipo {archetype}!", + "annual_report.summary.share_on_mastodon": "Compartilhar no Mastodon", "attachments_list.unprocessed": "(não processado)", "audio.hide": "Ocultar áudio", - "block_modal.remote_users_caveat": "Pediremos ao servidor {domain} que respeite sua decisão. No entanto, a conformidade não é garantida, já que alguns servidores podem lidar com bloqueios de maneira diferente. As postagens públicas ainda podem estar visíveis para usuários não logados.", + "block_modal.remote_users_caveat": "Pediremos ao servidor {domain} que respeite sua decisão. No entanto, a conformidade não é garantida, já que alguns servidores podem lidar com bloqueios de maneira diferente. As publicações públicas ainda podem estar visíveis para usuários não logados.", "block_modal.show_less": "Mostrar menos", "block_modal.show_more": "Mostrar mais", - "block_modal.they_cant_mention": "Eles não podem mencionar ou seguir você.", - "block_modal.they_cant_see_posts": "Eles não podem ver suas postagens e você não verá as deles.", - "block_modal.they_will_know": "Eles podem ver que estão bloqueados.", + "block_modal.they_cant_mention": "Não poderá mencionar ou seguir você.", + "block_modal.they_cant_see_posts": "Não poderá ver suas publicações e você não verá as dele/a.", + "block_modal.they_will_know": "Poderá ver que você bloqueou.", "block_modal.title": "Bloquear usuário?", - "block_modal.you_wont_see_mentions": "Você não verá publicações que os mencionem.", - "boost_modal.combo": "Pressione {combo} para pular isso na próxima vez", + "block_modal.you_wont_see_mentions": "Você não verá publicações que mencionem este usuário.", + "boost_modal.combo": "Pressione {combo} para pular isto na próxima vez", "boost_modal.reblog": "Impulsionar a publicação?", - "boost_modal.undo_reblog": "Retirar o impulsionamento do post?", + "boost_modal.undo_reblog": "Retirar o impulso da publicação?", "bundle_column_error.copy_stacktrace": "Copiar relatório do erro", "bundle_column_error.error.body": "A página solicitada não pôde ser renderizada. Pode ser devido a um erro no nosso código, ou um problema de compatibilidade do seu navegador.", "bundle_column_error.error.title": "Ah, não!", @@ -152,6 +184,8 @@ "bundle_modal_error.close": "Fechar", "bundle_modal_error.message": "Algo deu errado ao carregar esta tela.", "bundle_modal_error.retry": "Tente novamente", + "carousel.current": "Slide {current, number} / {max, number}", + "carousel.slide": "Slide {current, number} de {max, number}", "closed_registrations.other_server_instructions": "Como o Mastodon é descentralizado, você pode criar uma conta em outro servidor e ainda pode interagir com este.", "closed_registrations_modal.description": "Não é possível criar uma conta em {domain} no momento, mas atente que você não precisa de uma conta especificamente em {domain} para usar o Mastodon.", "closed_registrations_modal.find_another_server": "Encontrar outro servidor", @@ -168,6 +202,8 @@ "column.edit_list": "Editar lista", "column.favourites": "Favoritos", "column.firehose": "Feeds ao vivo", + "column.firehose_local": "Feed ao vivo deste servidor", + "column.firehose_singular": "Feed ao vivo", "column.follow_requests": "Seguidores pendentes", "column.home": "Página inicial", "column.list_members": "Gerenciar membros da lista", @@ -187,11 +223,12 @@ "community.column_settings.local_only": "Somente local", "community.column_settings.media_only": "Somente mídia", "community.column_settings.remote_only": "Somente global", + "compose.error.blank_post": "A publicação não pode estar em branco.", "compose.language.change": "Alterar idioma", "compose.language.search": "Pesquisar idiomas...", "compose.published.body": "Publicado.", "compose.published.open": "Abrir", - "compose.saved.body": "Postagem salva.", + "compose.saved.body": "Publicação salva.", "compose_form.direct_message_warning_learn_more": "Saiba mais", "compose_form.encryption_warning": "As publicações no Mastodon não são criptografadas de ponta-a-ponta. Não compartilhe nenhuma informação sensível no Mastodon.", "compose_form.hashtag_warning": "Esta publicação não será exibida sob nenhuma hashtag, já que não é pública. Apenas postagens públicas podem ser pesquisadas por meio de hashtags.", @@ -207,7 +244,7 @@ "compose_form.poll.type": "Estilo", "compose_form.publish": "Publicar", "compose_form.reply": "Responder", - "compose_form.save_changes": "Atualização", + "compose_form.save_changes": "Atualizar", "compose_form.spoiler.marked": "Com Aviso de Conteúdo", "compose_form.spoiler.unmarked": "Sem Aviso de Conteúdo", "compose_form.spoiler_placeholder": "Aviso de conteúdo (opcional)", @@ -221,11 +258,11 @@ "confirmations.delete_list.title": "Excluir lista?", "confirmations.discard_draft.confirm": "Descartar e continuar", "confirmations.discard_draft.edit.cancel": "Continuar editando", - "confirmations.discard_draft.edit.message": "Continuar vai descartar quaisquer mudanças feitas ao post sendo editado.", - "confirmations.discard_draft.edit.title": "Descartar mudanças no seu post?", + "confirmations.discard_draft.edit.message": "Continuar descartará quaisquer mudanças feitas à publicação sendo editada.", + "confirmations.discard_draft.edit.title": "Descartar mudanças na sua publicação?", "confirmations.discard_draft.post.cancel": "Continuar rascunho", "confirmations.discard_draft.post.message": "Continuar eliminará a publicação que está sendo elaborada no momento.", - "confirmations.discard_draft.post.title": "Eliminar seu esboço de publicação?", + "confirmations.discard_draft.post.title": "Eliminar seu rascunho de publicação?", "confirmations.discard_edit_media.confirm": "Descartar", "confirmations.discard_edit_media.message": "Há mudanças não salvas na descrição ou pré-visualização da mídia. Descartar assim mesmo?", "confirmations.follow_to_list.confirm": "Seguir e adicionar à lista", @@ -236,24 +273,34 @@ "confirmations.logout.title": "Sair da sessão?", "confirmations.missing_alt_text.confirm": "Adicione texto alternativo", "confirmations.missing_alt_text.message": "Seu post contém mídia sem texto alternativo. Adicionar descrições ajuda a tornar seu conteúdo acessível para mais pessoas.", - "confirmations.missing_alt_text.secondary": "Postar mesmo assim", + "confirmations.missing_alt_text.secondary": "Publicar mesmo assim", "confirmations.missing_alt_text.title": "Adicionar texto alternativo?", "confirmations.mute.confirm": "Silenciar", + "confirmations.private_quote_notify.cancel": "Voltar à edição", + "confirmations.private_quote_notify.confirm": "Publicar citação", + "confirmations.private_quote_notify.do_not_show_again": "Não me mostre esta mensagem novamente", + "confirmations.private_quote_notify.message": "A pessoa que está sendo citada e outras mencionadas serão notificadas e poderão ver sua publicação, mesmo que não sigam você.", + "confirmations.private_quote_notify.title": "Compartilhar com seguidores e usuários mencionados?", "confirmations.quiet_post_quote_info.dismiss": "Não me lembrar novamente", "confirmations.quiet_post_quote_info.got_it": "Entendi", + "confirmations.quiet_post_quote_info.message": "Ao citar uma publicação pública silenciosa, sua postagem será oculta das linhas de tempo em tendência.", + "confirmations.quiet_post_quote_info.title": "Citando publicações públicas silenciadas", "confirmations.redraft.confirm": "Excluir e rascunhar", - "confirmations.redraft.message": "Você tem certeza de que quer apagar essa postagem e rascunhá-la? Favoritos e impulsos serão perdidos, e respostas à postagem original ficarão órfãs.", + "confirmations.redraft.message": "Você tem certeza de que quer apagar esta publicação e rascunhá-la? Favoritos e impulsos serão perdidos, e respostas à publicação original ficarão órfãs.", "confirmations.redraft.title": "Excluir e rascunhar publicação?", "confirmations.remove_from_followers.confirm": "Remover seguidor", "confirmations.remove_from_followers.message": "{name} vai parar de te seguir. Tem certeza de que deseja continuar?", "confirmations.remove_from_followers.title": "Remover seguidor?", "confirmations.revoke_quote.confirm": "Remover publicação", - "confirmations.revoke_quote.message": "Essa ação não pode ser desfeita.", + "confirmations.revoke_quote.message": "Esta ação não pode ser desfeita.", "confirmations.revoke_quote.title": "Remover publicação?", + "confirmations.unblock.confirm": "Desbloquear", + "confirmations.unblock.title": "Desbloquear {name}?", "confirmations.unfollow.confirm": "Deixar de seguir", - "confirmations.unfollow.message": "Você tem certeza de que deseja deixar de seguir {name}?", - "confirmations.unfollow.title": "Deixar de seguir o usuário?", - "content_warning.hide": "Ocultar post", + "confirmations.unfollow.title": "Deixar de seguir {name}?", + "confirmations.withdraw_request.confirm": "Retirar solicitação", + "confirmations.withdraw_request.title": "Retirar solicitação para seguir {name}?", + "content_warning.hide": "Ocultar publicação", "content_warning.show": "Mostrar mesmo assim", "content_warning.show_more": "Mostrar mais", "conversation.delete": "Excluir conversa", @@ -271,21 +318,21 @@ "disabled_account_banner.text": "Sua conta {disabledAccount} está desativada no momento.", "dismissable_banner.community_timeline": "Estas são as publicações públicas mais recentes das pessoas cujas contas são hospedadas por {domain}.", "dismissable_banner.dismiss": "Dispensar", - "dismissable_banner.public_timeline": "Estas são as publicações mais recentes das pessoas no fediverse que as pessoas do {domain} seguem.", + "dismissable_banner.public_timeline": "Estas são as publicações mais recentes das pessoas no fediverso que as pessoas do {domain} seguem.", "domain_block_modal.block": "Bloquear servidor", - "domain_block_modal.block_account_instead": "Bloquear @{name}", + "domain_block_modal.block_account_instead": "Bloquear @{name} em vez disso", "domain_block_modal.they_can_interact_with_old_posts": "Pessoas deste servidor podem interagir com suas publicações antigas.", "domain_block_modal.they_cant_follow": "Ninguém deste servidor pode lhe seguir.", "domain_block_modal.they_wont_know": "Eles não saberão que foram bloqueados.", "domain_block_modal.title": "Bloquear domínio?", "domain_block_modal.you_will_lose_num_followers": "Você perderá {followersCount, plural, one {{followersCountDisplay} seguidor} other {{followersCountDisplay} seguidores}} e {followingCount, plural, one {{followingCountDisplay} pessoa que você segue} other {{followingCountDisplay} pessoas que você segue}}.", "domain_block_modal.you_will_lose_relationships": "Você irá perder todos os seguidores e pessoas que você segue neste servidor.", - "domain_block_modal.you_wont_see_posts": "Você não verá postagens ou notificações de usuários neste servidor.", - "domain_pill.activitypub_lets_connect": "Ele permite que você se conecte e interaja com pessoas não apenas no Mastodon, mas também em diferentes aplicativos sociais.", + "domain_block_modal.you_wont_see_posts": "Você não verá publicações ou notificações de usuários neste servidor.", + "domain_pill.activitypub_lets_connect": "Permite que você se conecte e interaja com pessoas não apenas no Mastodon, mas também em diferentes aplicativos sociais.", "domain_pill.activitypub_like_language": "ActivityPub é como a linguagem que o Mastodon fala com outras redes sociais.", "domain_pill.server": "Servidor", - "domain_pill.their_handle": "Seu identificador:", - "domain_pill.their_server": "Sua casa digital, onde ficam todas as suas postagens.", + "domain_pill.their_handle": "Identificador dele/a:", + "domain_pill.their_server": "Casa digital dele/a, onde ficam todas as suas postagens.", "domain_pill.their_username": "Seu identificador exclusivo em seu servidor. É possível encontrar usuários com o mesmo nome de usuário em servidores diferentes.", "domain_pill.username": "Nome de usuário", "domain_pill.whats_in_a_handle": "O que há em um identificador?", @@ -323,12 +370,13 @@ "empty_column.bookmarked_statuses": "Nada aqui. Quando você salvar um toot, ele aparecerá aqui.", "empty_column.community": "A linha local está vazia. Publique algo para começar!", "empty_column.direct": "Você ainda não tem mensagens privadas. Quando você enviar ou receber uma, será exibida aqui.", + "empty_column.disabled_feed": "Este feed foi desativado pelos administradores de seu servidor.", "empty_column.domain_blocks": "Nada aqui.", "empty_column.explore_statuses": "Nada está em alta no momento. Volte mais tarde!", "empty_column.favourited_statuses": "Você ainda não tem publicações favoritas. Quanto você marcar uma como favorita, ela aparecerá aqui.", "empty_column.favourites": "Ninguém marcou esta publicação como favorita até agora. Quando alguém o fizer, será listado aqui.", "empty_column.follow_requests": "Nada aqui. Quando você tiver seguidores pendentes, eles aparecerão aqui.", - "empty_column.followed_tags": "Você ainda não seguiu nenhuma hashtag. Quando seguir uma, elas serão exibidas aqui.", + "empty_column.followed_tags": "Você ainda não seguiu nenhuma hashtag. Quando seguir, elas serão exibidas aqui.", "empty_column.hashtag": "Nada aqui.", "empty_column.home": "Sua página inicial está vazia! Siga mais pessoas para começar: {suggestions}", "empty_column.list": "Nada aqui. Quando membros da lista tootarem, eles aparecerão aqui.", @@ -336,6 +384,7 @@ "empty_column.notification_requests": "Tudo limpo! Não há nada aqui. Quando você receber novas notificações, elas aparecerão aqui de acordo com suas configurações.", "empty_column.notifications": "Interaja com outros usuários para começar a conversar.", "empty_column.public": "Publique algo ou siga manualmente usuários de outros servidores", + "error.no_hashtag_feed_access": "Se cadastre ou faça login para ver e seguir esta hashtag.", "error.unexpected_crash.explanation": "Esta página não pôde ser mostrada corretamente. Este erro provavelmente é devido a um bug em nosso código ou um problema de compatibilidade de navegador.", "error.unexpected_crash.explanation_addons": "Esta página não pôde ser mostrada corretamente. Este erro provavelmente é causado por um complemento do navegador ou ferramentas de tradução automática.", "error.unexpected_crash.next_steps": "Tente atualizar a página. Se isso não ajudar, você ainda poderá usar o Mastodon por meio de um navegador diferente ou de um aplicativo nativo.", @@ -347,16 +396,14 @@ "explore.trending_links": "Notícias", "explore.trending_statuses": "Publicações", "explore.trending_tags": "Hashtags", - "featured_carousel.header": "{count, plural, one {Postagem fixada} other {Postagens fixadas}}", - "featured_carousel.next": "Próximo", - "featured_carousel.post": "Publicação", - "featured_carousel.previous": "Anterior", - "featured_carousel.slide": "{index} de {total}", + "featured_carousel.current": "Publicação {current, number} / {max, number}", + "featured_carousel.header": "{count, plural, one {Publicação fixada} other {Publicações fixadas}}", + "featured_carousel.slide": "Publicação {current, number} de {max, number}", "filter_modal.added.context_mismatch_explanation": "Esta categoria de filtro não se aplica ao contexto no qual você acessou esta publicação. Se quiser que a publicação seja filtrada nesse contexto também, você terá que editar o filtro.", "filter_modal.added.context_mismatch_title": "Incompatibilidade de contexto!", "filter_modal.added.expired_explanation": "Esta categoria de filtro expirou, você precisará alterar a data de expiração para aplicar.", "filter_modal.added.expired_title": "Filtro expirado!", - "filter_modal.added.review_and_configure": "Para revisar e configurar ainda mais esta categoria de filtro, vá até {settings_link}.", + "filter_modal.added.review_and_configure": "Para revisar e configurar ainda mais esta categoria de filtro, vá para {settings_link}.", "filter_modal.added.review_and_configure_title": "Configurações de filtro", "filter_modal.added.settings_link": "página de configurações", "filter_modal.added.short_explanation": "Esta publicação foi adicionada à seguinte categoria de filtro: {title}.", @@ -394,6 +441,9 @@ "follow_suggestions.who_to_follow": "Quem seguir", "followed_tags": "Hashtags seguidas", "footer.about": "Sobre", + "footer.about_mastodon": "Sobre o mastodon", + "footer.about_server": "Sobre {domain}", + "footer.about_this_server": "Sobre", "footer.directory": "Diretório de perfis", "footer.get_app": "Baixe o app", "footer.keyboard_shortcuts": "Atalhos de teclado", @@ -404,8 +454,8 @@ "generic.saved": "Salvo", "getting_started.heading": "Primeiros passos", "hashtag.admin_moderation": "Abrir interface de moderação para #{name}", - "hashtag.browse": "Buscar postagens em #{hashtag}", - "hashtag.browse_from_account": "Procurar mensagens de @{name} em #{hashtag}", + "hashtag.browse": "Buscar publicações em #{hashtag}", + "hashtag.browse_from_account": "Buscar publicações de @{name} em #{hashtag}", "hashtag.column_header.tag_mode.all": "e {additional}", "hashtag.column_header.tag_mode.any": "ou {additional}", "hashtag.column_header.tag_mode.none": "sem {additional}", @@ -424,14 +474,14 @@ "hashtag.unfeature": "Não destacar no perfil", "hashtag.unfollow": "Parar de seguir hashtag", "hashtags.and_other": "…e {count, plural, one {}other {outros #}}", - "hints.profiles.followers_may_be_missing": "Os seguidores deste perfil podem estar faltando.", - "hints.profiles.follows_may_be_missing": "Os seguidores deste perfil podem estar faltando.", + "hints.profiles.followers_may_be_missing": "Pode haver seguidores deste perfil faltando.", + "hints.profiles.follows_may_be_missing": "Pode haver seguidos por este perfil faltando.", "hints.profiles.posts_may_be_missing": "É possível que algumas publicações deste perfil estejam faltando.", "hints.profiles.see_more_followers": "Ver mais seguidores no {domain}", - "hints.profiles.see_more_follows": "Ver mais seguidores no {domain}", + "hints.profiles.see_more_follows": "Ver mais seguidos no {domain}", "hints.profiles.see_more_posts": "Ver mais publicações em {domain}", "home.column_settings.show_quotes": "Mostrar citações", - "home.column_settings.show_reblogs": "Mostrar boosts", + "home.column_settings.show_reblogs": "Mostrar impulsos", "home.column_settings.show_replies": "Mostrar respostas", "home.hide_announcements": "Ocultar comunicados", "home.pending_critical_update.body": "Por favor, atualize o seu servidor Mastodon o mais rápido possível!", @@ -451,17 +501,19 @@ "ignore_notifications_modal.private_mentions_title": "Ignorar notificações de menções privadas não solicitadas?", "info_button.label": "Ajuda", "info_button.what_is_alt_text": "

O que é texto alternativo?

O texto alternativo fornece descrições de imagens para pessoas com deficiências visuais, conexões de internet de baixa largura de banda ou aquelas que buscam mais contexto.

Você pode melhorar a acessibilidade e a compreensão para todos escrevendo texto alternativo claro, conciso e objetivo.

  • Capture elementos importantes
  • Resuma textos em imagens
  • Use estrutura de frases regular
  • Evite informações redundantes
  • Foque em tendências e descobertas principais em visuais complexos (como diagramas ou mapas)
", + "interaction_modal.action": "Para interagir com o post de {name}, você precisa entrar em sua conta em qualquer servidor Mastodon que você use.", "interaction_modal.go": "Ir", "interaction_modal.no_account_yet": "Não possui uma conta ainda?", "interaction_modal.on_another_server": "Em um servidor diferente", "interaction_modal.on_this_server": "Neste servidor", + "interaction_modal.title": "Faça login para continuar", "interaction_modal.username_prompt": "p. e.x.: {example}", "intervals.full.days": "{number, plural, one {# dia} other {# dias}}", "intervals.full.hours": "{number, plural, one {# hora} other {# horas}}", "intervals.full.minutes": "{number, plural, one {# minuto} other {# minutos}}", "keyboard_shortcuts.back": "voltar", "keyboard_shortcuts.blocked": "abrir usuários bloqueados", - "keyboard_shortcuts.boost": "dar boost", + "keyboard_shortcuts.boost": "Impulsionar a publicação", "keyboard_shortcuts.column": "focar na coluna", "keyboard_shortcuts.compose": "focar no compositor", "keyboard_shortcuts.description": "Descrição", @@ -493,6 +545,7 @@ "keyboard_shortcuts.toggle_hidden": "expandir/ocultar aviso de conteúdo", "keyboard_shortcuts.toggle_sensitivity": "mostrar/ocultar mídia", "keyboard_shortcuts.toot": "compor novo toot", + "keyboard_shortcuts.top": "Mover para o topo da lista", "keyboard_shortcuts.translate": "Para traduzir um post", "keyboard_shortcuts.unfocus": "desfocar de tudo", "keyboard_shortcuts.up": "mover para cima", @@ -584,8 +637,8 @@ "notification.admin.report_statuses": "{name} Reportou {target} para {category}", "notification.admin.report_statuses_other": "{name} denunciou {target}", "notification.admin.sign_up": "{name} se inscreveu", - "notification.admin.sign_up.name_and_others": "{name} e {count, plural, one {# other} other {# outros}}", - "notification.annual_report.message": "O #Wrapstodon do seu {year} está esperando! Desvende seus destaques do ano e momentos memoráveis no Mastodon!", + "notification.admin.sign_up.name_and_others": "{name} e {count, plural, one {# outro} other {# outros}} se inscreveram", + "notification.annual_report.message": "O seu #Wrapstodon de {year} está esperando! Desvende seus destaques do ano e momentos memoráveis no Mastodon!", "notification.annual_report.view": "Ver #Wrapstodon", "notification.favourite": "{name} favoritou sua publicação", "notification.favourite.name_and_others_with_link": "{name} e {count, plural, one {# outro} other {# others}} favoritaram a publicação", @@ -594,7 +647,7 @@ "notification.follow": "{name} te seguiu", "notification.follow.name_and_others": "{name} e {count, plural, one {# outro} other {# outros}} seguiram você", "notification.follow_request": "{name} quer te seguir", - "notification.follow_request.name_and_others": "{name} e {count, plural, one {# other} other {# outros}} pediu para seguir você", + "notification.follow_request.name_and_others": "{name} e {count, plural, one {# outro} other {# outros}} pediram para seguir você", "notification.label.mention": "Menção", "notification.label.private_mention": "Menção privada", "notification.label.private_reply": "Resposta privada", @@ -613,8 +666,8 @@ "notification.moderation_warning.action_suspend": "Sua conta foi suspensa.", "notification.own_poll": "Sua enquete terminou", "notification.poll": "Uma enquete que você votou terminou", - "notification.quoted_update": "{name} Editou um post seu", - "notification.reblog": "{name} deu boost no teu toot", + "notification.quoted_update": "{name} editou uma pulicação que você citou", + "notification.reblog": "{name} impulsionou sua publicação", "notification.reblog.name_and_others_with_link": "{name} e {count, plural, one {# outra} other {# outras}} impulsionaram a publicação", "notification.relationships_severance_event": "Conexões perdidas com {name}", "notification.relationships_severance_event.account_suspension": "Um administrador de {from} suspendeu {target}, o que significa que você não pode mais receber atualizações deles ou interagir com eles.", @@ -658,7 +711,7 @@ "notifications.column_settings.poll": "Enquetes:", "notifications.column_settings.push": "Notificações push", "notifications.column_settings.quote": "Citações:", - "notifications.column_settings.reblog": "Boosts:", + "notifications.column_settings.reblog": "Impulsos:", "notifications.column_settings.show": "Mostrar na coluna", "notifications.column_settings.sound": "Tocar som", "notifications.column_settings.status": "Novos toots:", @@ -666,7 +719,7 @@ "notifications.column_settings.unread_notifications.highlight": "Destacar notificações não lidas", "notifications.column_settings.update": "Editar:", "notifications.filter.all": "Tudo", - "notifications.filter.boosts": "Boosts", + "notifications.filter.boosts": "Impulsionamentos", "notifications.filter.favourites": "Favoritos", "notifications.filter.follows": "Seguidores", "notifications.filter.mentions": "Menções", @@ -737,12 +790,15 @@ "privacy.quote.disabled": "{visibility} Citações desabilitadas", "privacy.quote.limited": "{visibility} Citações limitadas", "privacy.unlisted.additional": "Isso se comporta exatamente como público, exceto que a publicação não aparecerá nos _feeds ao vivo_ ou nas _hashtags_, explorar, ou barra de busca, mesmo que você seja escolhido em toda a conta.", - "privacy.unlisted.short": "Público (silencioso)", + "privacy.unlisted.long": "Oculto para os resultados de pesquisa do Mastodon, tendências e linhas do tempo públicas", + "privacy.unlisted.short": "Público silencioso", "privacy_policy.last_updated": "Atualizado {date}", "privacy_policy.title": "Política de privacidade", + "quote_error.edit": "Citações não podem ser adicionadas durante a edição de uma publicação.", "quote_error.poll": "Citações não permitidas com enquetes.", - "quote_error.quote": "Apenas uma citação por vez é permitido.", - "quote_error.unauthorized": "Você não é autorizado a citar essa publicação.", + "quote_error.private_mentions": "Citações não são permitidas com menções diretas.", + "quote_error.quote": "Somente é permitida uma citação por vez.", + "quote_error.unauthorized": "Você não tem autorização para citar essa publicação.", "quote_error.upload": "Citações não são permitidas com mídias anexadas.", "recommended": "Recomendado", "refresh": "Atualizar", @@ -759,6 +815,9 @@ "relative_time.minutes": "{number}m", "relative_time.seconds": "{number}s", "relative_time.today": "hoje", + "remove_quote_hint.button_label": "Entendi", + "remove_quote_hint.message": "Você pode fazê-lo no menu de opções {icon}.", + "remove_quote_hint.title": "Deseja remover sua citação publicada?", "reply_indicator.attachments": "{count, plural, one {# attachment} other {# attachments}}", "reply_indicator.cancel": "Cancelar", "reply_indicator.poll": "Enquete", @@ -850,13 +909,19 @@ "status.admin_account": "Abrir interface de moderação para @{name}", "status.admin_domain": "Abrir interface de moderação para {domain}", "status.admin_status": "Abrir este toot na interface de moderação", - "status.all_disabled": "Acelerações e citações estão desabilitados", + "status.all_disabled": "Impulsos e citações estão desabilitados", "status.block": "Bloquear @{name}", "status.bookmark": "Salvar", - "status.cancel_reblog_private": "Desfazer boost", - "status.cannot_reblog": "Este toot não pode receber boost", - "status.context.load_new_replies": "Novas respostas disponíveis", - "status.context.loading": "Verificando mais respostas", + "status.cancel_reblog_private": "Desfazer impulso", + "status.cannot_quote": "Você não tem permissão para citar esta publicação", + "status.cannot_reblog": "Esta publicação não pode ser impulsionada", + "status.contains_quote": "Contém citação", + "status.context.loading": "Carregando mais respostas", + "status.context.loading_error": "Não foi possível carregar novas respostas", + "status.context.loading_success": "Novas respostas carregadas", + "status.context.more_replies_found": "Mais respostas encontradas", + "status.context.retry": "Tentar novamente", + "status.context.show": "Mostrar", "status.continued_thread": "Continuação da conversa", "status.copy": "Copiar link", "status.delete": "Excluir", @@ -868,8 +933,8 @@ "status.edited": "Última edição em {date}", "status.edited_x_times": "Editado {count, plural, one {{count} hora} other {{count} vezes}}", "status.embed": "Obter código de incorporação", - "status.favourite": "Favorita", - "status.favourites": "{count, plural, one {favorite} other {favorites}}", + "status.favourite": "Favoritar", + "status.favourites_count": "{count, plural, one {{counter} favorito} other {{counter} favoritos}}", "status.filter": "Filtrar esta publicação", "status.history.created": "{name} criou {date}", "status.history.edited": "{name} editou {date}", @@ -885,25 +950,37 @@ "status.pin": "Fixar", "status.quote": "Citar", "status.quote.cancel": "Cancelar citação", + "status.quote_error.blocked_account_hint.title": "Esta publicação está oculta porque você bloqueou @{name}.", + "status.quote_error.blocked_domain_hint.title": "Esta publicação está oculta porque você bloqueou {domain}.", "status.quote_error.filtered": "Oculto devido a um dos seus filtros", + "status.quote_error.limited_account_hint.action": "Mostrar mesmo assim", + "status.quote_error.limited_account_hint.title": "Esta conta foi oculta pelos moderadores do {domain}.", + "status.quote_error.muted_account_hint.title": "Esta publicação está oculta porque você silenciou @{name}.", "status.quote_error.not_available": "Publicação indisponível", "status.quote_error.pending_approval": "Publicação pendente", + "status.quote_error.pending_approval_popout.body": "No Mastodon, você pode controlar se alguém pode citar você. Esta publicação está pendente enquanto estamos recebendo a aprovação do autor original.", + "status.quote_error.revoked": "Publicação removida pelo autor", "status.quote_followers_only": "Apenas seguidores podem citar sua publicação", "status.quote_manual_review": "Autor irá revisar manualmente", + "status.quote_noun": "Citar", "status.quote_policy_change": "Mude quem pode citar", "status.quote_post_author": "Publicação citada por @{name}", "status.quote_private": "Publicações privadas não podem ser citadas", - "status.quotes": "{count, plural, one {# voto} other {# votos}}", "status.quotes.empty": "Ninguém citou essa publicação até agora. Quando alguém citar aparecerá aqui.", + "status.quotes.local_other_disclaimer": "Citações rejeitadas pelo autor não serão exibidas.", + "status.quotes.remote_other_disclaimer": "Apenas citações do {domain} têm a garantia de serem exibidas aqui. Citações rejeitadas pelo autor não serão exibidas.", + "status.quotes_count": "{count, plural, one {{counter} citação} other {{counter} citações}}", "status.read_more": "Ler mais", - "status.reblog": "Dar boost", - "status.reblog_or_quote": "Acelerar ou citar", - "status.reblogged_by": "{name} deu boost", - "status.reblogs": "{count, plural, one {boost} other {boosts}}", - "status.reblogs.empty": "Nada aqui. Quando alguém der boost, o usuário aparecerá aqui.", + "status.reblog": "Impulsionar", + "status.reblog_or_quote": "Impulsionar ou citar", + "status.reblog_private": "Compartilhar novamente com seus seguidores", + "status.reblogged_by": "{name} impulsionou", + "status.reblogs.empty": "Ninguém impulsionou esta publicação ainda. Quando alguém o fizer, o usuário aparecerá aqui.", + "status.reblogs_count": "{count, plural, one {{counter} impulso} other {{counter} impulsos}}", "status.redraft": "Excluir e rascunhar", "status.remove_bookmark": "Remover do Salvos", "status.remove_favourite": "Remover dos favoritos", + "status.remove_quote": "Remover", "status.replied_in_thread": "Respondido na conversa", "status.replied_to": "Em resposta a {name}", "status.reply": "Responder", @@ -973,8 +1050,12 @@ "video.volume_down": "Diminuir o volume", "video.volume_up": "Aumentar o volume", "visibility_modal.button_title": "Selecionar Visibilidade", + "visibility_modal.direct_quote_warning.text": "Se salvar as configurações atuais, a cotação incorporada será convertida em um link.", + "visibility_modal.direct_quote_warning.title": "Citações não podem ser incorporadas em menções privadas", "visibility_modal.header": "Visibilidade e interação", "visibility_modal.helper.direct_quoting": "Menções privadas escritas no Mastodon.", + "visibility_modal.helper.privacy_editing": "A visibilidade não pode ser alterada após uma publicação ser publicada.", + "visibility_modal.helper.privacy_private_self_quote": "As auto-citações de publicações privadas não podem ser públicas.", "visibility_modal.helper.private_quoting": "Posts somente para seguidores feitos no Mastodon não podem ser citados por outros.", "visibility_modal.helper.unlisted_quoting": "Quando as pessoas citam você, sua publicação também será ocultada das linhas de tempo de tendência.", "visibility_modal.instructions": "Controle quem pode interagir com este post. Você também pode aplicar as configurações para todos os posts futuros navegando para Preferências > Postagem padrão.", diff --git a/app/javascript/mastodon/locales/pt-PT.json b/app/javascript/mastodon/locales/pt-PT.json index af7c70b9fba6bc..9676206e2ceafd 100644 --- a/app/javascript/mastodon/locales/pt-PT.json +++ b/app/javascript/mastodon/locales/pt-PT.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Parar de me notificar das publicações de @{name}", "account.domain_blocking": "A bloquear domínio", "account.edit_profile": "Editar perfil", + "account.edit_profile_short": "Editar", "account.enable_notifications": "Notificar-me das publicações de @{name}", "account.endorse": "Destacar no perfil", "account.familiar_followers_many": "Seguido por {name1}, {name2} e {othersCount, plural,one {mais uma pessoa que conhece} other {# outras pessoas que conhece}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Sem publicações", "account.follow": "Seguir", "account.follow_back": "Seguir também", + "account.follow_back_short": "Seguir de volta", + "account.follow_request": "Pedir para seguir", + "account.follow_request_cancel": "Cancelar pedido", + "account.follow_request_cancel_short": "Cancelar", + "account.follow_request_short": "Pedido", "account.followers": "Seguidores", "account.followers.empty": "Ainda ninguém segue este utilizador.", "account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}", @@ -49,7 +55,7 @@ "account.follows.empty": "Este utilizador ainda não segue ninguém.", "account.follows_you": "Segue-te", "account.go_to_profile": "Ir para o perfil", - "account.hide_reblogs": "Esconder partilhas impulsionadas de @{name}", + "account.hide_reblogs": "Esconder partilhas de @{name}", "account.in_memoriam": "Em Memória.", "account.joined_short": "Juntou-se a", "account.languages": "Alterar idiomas subscritos", @@ -70,11 +76,10 @@ "account.posts_with_replies": "Publicações e respostas", "account.remove_from_followers": "Remover {name} dos seguidores", "account.report": "Denunciar @{name}", - "account.requested": "A aguardar aprovação. Clica para cancelar o pedido para seguir", "account.requested_follow": "{name} pediu para seguir-te", "account.requests_to_follow_you": "Pediu para seguir-te", "account.share": "Partilhar o perfil @{name}", - "account.show_reblogs": "Mostrar partilhas impulsionadas de @{name}", + "account.show_reblogs": "Mostrar partilhas de @{name}", "account.statuses_counter": "{count, plural, one {{counter} publicação} other {{counter} publicações}}", "account.unblock": "Desbloquear @{name}", "account.unblock_domain": "Desbloquear o domínio {domain}", @@ -108,25 +113,52 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Descreve isto para pessoas com problemas de visão…", "alt_text_modal.done": "Concluído", "announcement.announcement": "Mensagem de manutenção", - "annual_report.summary.archetype.booster": "O caçador de frescura", - "annual_report.summary.archetype.lurker": "O espreitador", - "annual_report.summary.archetype.oracle": "O oráculo", - "annual_report.summary.archetype.pollster": "O sondagens", - "annual_report.summary.archetype.replier": "A borboleta social", - "annual_report.summary.followers.followers": "seguidores", - "annual_report.summary.followers.total": "{count} no total", - "annual_report.summary.here_it_is": "Aqui está um resumo do ano {year}:", - "annual_report.summary.highlighted_post.by_favourites": "publicação mais favorita", - "annual_report.summary.highlighted_post.by_reblogs": "publicação mais impulsionada", - "annual_report.summary.highlighted_post.by_replies": "publicação com o maior número de respostas", - "annual_report.summary.highlighted_post.possessive": "{name}", + "annual_report.announcement.action_build": "Criar o meu Wrapstodon", + "annual_report.announcement.action_dismiss": "Não, obrigado", + "annual_report.announcement.action_view": "Ver o meu Wrapstodon", + "annual_report.announcement.description": "Descobre mais sobre o teu envolvimento com o Mastodon durante o último ano.", + "annual_report.announcement.title": "Chegou o Wrapstodon {year}", + "annual_report.nav_item.badge": "Novo", + "annual_report.shared_page.donate": "Doar", + "annual_report.shared_page.footer": "Gerado com {heart} pela equipa do Mastodon", + "annual_report.shared_page.footer_server_info": "{username} utiliza {domain}, uma das muitas comunidades baseadas no Mastodon.", + "annual_report.summary.archetype.booster.desc_public": "{name} permaneceu à procura de publicações para partilhar, promovendo outros criadores com uma precisão perfeita.", + "annual_report.summary.archetype.booster.desc_self": "Permaneceu à procura de publicações para partilhar, promovendo outros criadores com uma precisão perfeita.", + "annual_report.summary.archetype.booster.name": "O Arqueiro", + "annual_report.summary.archetype.die_drei_fragezeichen": "???", + "annual_report.summary.archetype.lurker.desc_public": "Sabemos que {name} esteve por aí, algures, a apreciar o Mastodon na sua maneira discreta.", + "annual_report.summary.archetype.lurker.desc_self": "Sabemos que esteve por aí, algures, a apreciar o Mastodon na sua maneira discreta.", + "annual_report.summary.archetype.lurker.name": "O Estoico", + "annual_report.summary.archetype.oracle.desc_public": "{name} criou mais publicações novas do que respostas, mantendo o Mastodon atualizado e voltado para o futuro.", + "annual_report.summary.archetype.oracle.desc_self": "Criou mais publicações novas do que respostas, mantendo o Mastodon atualizado e voltado para o futuro.", + "annual_report.summary.archetype.oracle.name": "O Oráculo", + "annual_report.summary.archetype.pollster.desc_public": "{name} criou mais sondagens do que outros tipos de publicações, cultivando a curiosidade no Mastodon.", + "annual_report.summary.archetype.pollster.desc_self": "Criou mais sondagens do que outros tipos de publicações, cultivando a curiosidade no Mastodon.", + "annual_report.summary.archetype.pollster.name": "O Questionador", + "annual_report.summary.archetype.replier.desc_public": "{name} respondeu frequentemente às publicações de outras pessoas, polinizando o Mastodon com novas discussões.", + "annual_report.summary.archetype.replier.desc_self": "Respondeu frequentemente às publicações de outras pessoas, polinizando o Mastodon com novas discussões.", + "annual_report.summary.archetype.replier.name": "A Borboleta", + "annual_report.summary.archetype.reveal": "Revelar o meu arquétipo", + "annual_report.summary.archetype.reveal_description": "Obrigado por fazer parte do Mastodon! É hora de descobrir qual arquétipo você encarnou em {year}.", + "annual_report.summary.archetype.title_public": "Arquétipo de {name}", + "annual_report.summary.archetype.title_self": "O seu arquétipo", + "annual_report.summary.close": "Fechar", + "annual_report.summary.copy_link": "Copiar hiperligação", + "annual_report.summary.followers.new_followers": "{count, plural, one {novo seguidor} other {novos seguidores}}", + "annual_report.summary.highlighted_post.boost_count": "Esta publicação foi partilhada {count, plural, one {uma vez} other {# vezes}}.", + "annual_report.summary.highlighted_post.favourite_count": "Esta publicação foi colocada nos favoritos {count, plural, one {uma vez} other {# vezes}}.", + "annual_report.summary.highlighted_post.reply_count": "Esta publicação teve {count, plural, one {uma resposta} other {# respostas}}.", + "annual_report.summary.highlighted_post.title": "Publicação mais popular", "annual_report.summary.most_used_app.most_used_app": "aplicação mais utilizada", "annual_report.summary.most_used_hashtag.most_used_hashtag": "etiqueta mais utilizada", - "annual_report.summary.most_used_hashtag.none": "Nenhuma", + "annual_report.summary.most_used_hashtag.used_count": "Incluiu esta etiqueta {count, plural, one {numa publicação} other {em # publicações}}.", + "annual_report.summary.most_used_hashtag.used_count_public": "{name} incluiu esta etiqueta {count, plural, one {numa publicação} other {em # publicações}}.", "annual_report.summary.new_posts.new_posts": "novas publicações", "annual_report.summary.percentile.text": "Isso coloca-te no topodos utilizadores de {domain}.", "annual_report.summary.percentile.we_wont_tell_bernie": "Este segredo fica entre nós.", - "annual_report.summary.thanks": "Obrigado por fazeres parte do Mastodon!", + "annual_report.summary.share_elsewhere": "Partilhar noutro local", + "annual_report.summary.share_message": "Eu obtive o arquétipo {archetype}!", + "annual_report.summary.share_on_mastodon": "Partilhar no Mastodon", "attachments_list.unprocessed": "(não processado)", "audio.hide": "Ocultar áudio", "block_modal.remote_users_caveat": "Vamos pedir ao servidor {domain} para respeitar a tua decisão. No entanto, não é garantido o seu cumprimento, uma vez que alguns servidores podem tratar os bloqueios de forma diferente. As publicações públicas podem continuar a ser visíveis para utilizadores não autenticados.", @@ -137,9 +169,9 @@ "block_modal.they_will_know": "Ele pode ver que o bloqueaste.", "block_modal.title": "Bloquear utilizador?", "block_modal.you_wont_see_mentions": "Não verás publicações que mencionem este utilizador.", - "boost_modal.combo": "Podes premir {combo} para não voltares a ver isto", - "boost_modal.reblog": "Impulsionar a publicação?", - "boost_modal.undo_reblog": "Não impulsionar a publicação?", + "boost_modal.combo": "Pode clicar em {combo} para não voltar a ver isto", + "boost_modal.reblog": "Partilhar a publicação?", + "boost_modal.undo_reblog": "Deixar de partilhar a publicação?", "bundle_column_error.copy_stacktrace": "Copiar relatório de erros", "bundle_column_error.error.body": "A página solicitada não pôde ser sintetizada. Isto pode ser devido a uma falha no nosso código ou a um problema de compatibilidade com o navegador.", "bundle_column_error.error.title": "Ó, não!", @@ -152,6 +184,8 @@ "bundle_modal_error.close": "Fechar", "bundle_modal_error.message": "Algo correu mal ao carregar este ecrã.", "bundle_modal_error.retry": "Tenta de novo", + "carousel.current": "Slide {current, number} / {max, number}", + "carousel.slide": "Slide {current, number} de {max, number}", "closed_registrations.other_server_instructions": "Visto que o Mastodon é descentralizado, podes criar uma conta noutro servidor e interagir com este na mesma.", "closed_registrations_modal.description": "Neste momento não é possível criar uma conta em {domain}, mas lembramos que não é preciso ter uma conta especificamente em {domain} para usar o Mastodon.", "closed_registrations_modal.find_another_server": "Procurar outro servidor", @@ -168,6 +202,8 @@ "column.edit_list": "Editar lista", "column.favourites": "Favoritos", "column.firehose": "Cronologias em tempo real", + "column.firehose_local": "Cronologia em tempo real para este servidor", + "column.firehose_singular": "Cronologia em tempo real", "column.follow_requests": "Pedidos de seguidores", "column.home": "Início", "column.list_members": "Gerir membros da lista", @@ -187,6 +223,7 @@ "community.column_settings.local_only": "Apenas local", "community.column_settings.media_only": "Apenas multimédia", "community.column_settings.remote_only": "Apenas remoto", + "compose.error.blank_post": "As publicações não podem ficar em branco.", "compose.language.change": "Alterar idioma", "compose.language.search": "Pesquisar idiomas...", "compose.published.body": "Publicado.", @@ -239,12 +276,17 @@ "confirmations.missing_alt_text.secondary": "Publicar mesmo assim", "confirmations.missing_alt_text.title": "Adicionar texto alternativo?", "confirmations.mute.confirm": "Ocultar", + "confirmations.private_quote_notify.cancel": "Voltar à edição", + "confirmations.private_quote_notify.confirm": "Publicar", + "confirmations.private_quote_notify.do_not_show_again": "Não mostrar novamente esta mensagem", + "confirmations.private_quote_notify.message": "A pessoa que está a citar e outras mencionadas serão notificadas e poderão ver a sua publicação, mesmo que não o sigam.", + "confirmations.private_quote_notify.title": "Partilhar com seguidores e utilizadores mencionados?", "confirmations.quiet_post_quote_info.dismiss": "Não me relembre novamente", "confirmations.quiet_post_quote_info.got_it": "Entendido", "confirmations.quiet_post_quote_info.message": "Ao citar uma publicação não listada, a sua publicação não será exibida nos destaques.", "confirmations.quiet_post_quote_info.title": "Citação de publicação não listada", "confirmations.redraft.confirm": "Eliminar e reescrever", - "confirmations.redraft.message": "Tens a certeza de que queres eliminar e tornar a escrever esta publicação? Os favoritos e as publicações impulsionadas perder-se-ão e as respostas à publicação original ficarão órfãs.", + "confirmations.redraft.message": "Tem a certeza que pretende eliminar e tornar a escrever esta publicação? Os favoritos e as partilhas perder-se-ão e as respostas à publicação original ficarão órfãs.", "confirmations.redraft.title": "Eliminar e reescrever publicação?", "confirmations.remove_from_followers.confirm": "Remover seguidor", "confirmations.remove_from_followers.message": "{name} vai parar de seguir-te. Tens a certeza que prentedes continuar?", @@ -252,9 +294,12 @@ "confirmations.revoke_quote.confirm": "Remover publicação", "confirmations.revoke_quote.message": "Esta ação é irreversível.", "confirmations.revoke_quote.title": "Remover publicação?", + "confirmations.unblock.confirm": "Desbloquear", + "confirmations.unblock.title": "Desbloquear {name}?", "confirmations.unfollow.confirm": "Deixar de seguir", - "confirmations.unfollow.message": "De certeza que queres deixar de seguir {name}?", - "confirmations.unfollow.title": "Deixar de seguir o utilizador?", + "confirmations.unfollow.title": "Deixar de seguir {name}?", + "confirmations.withdraw_request.confirm": "Retirar pedido", + "confirmations.withdraw_request.title": "Retirar pedido para seguir {name}?", "content_warning.hide": "Ocultar publicação", "content_warning.show": "Mostrar mesmo assim", "content_warning.show_more": "Mostrar mais", @@ -322,9 +367,10 @@ "empty_column.account_timeline": "Sem publicações por aqui!", "empty_column.account_unavailable": "Perfil indisponível", "empty_column.blocks": "Ainda não bloqueaste nenhum utilizador.", - "empty_column.bookmarked_statuses": "Ainda não tens nenhuma publicação marcada. Quando marcares uma, ela aparecerá aqui.", + "empty_column.bookmarked_statuses": "Ainda não tem nenhuma publicação salva. Quando salvar uma, ela aparecerá aqui.", "empty_column.community": "A cronologia local está vazia. Escreve algo publicamente para começar!", "empty_column.direct": "Ainda não tens qualquer menção privada. Quando enviares ou receberes uma, ela irá aparecer aqui.", + "empty_column.disabled_feed": "Esta cronologia foi desativada pelos administradores do seu servidor.", "empty_column.domain_blocks": "Ainda não há qualquer domínio bloqueado.", "empty_column.explore_statuses": "Não há nada em destaque neste momento. Volte mais tarde!", "empty_column.favourited_statuses": "Ainda não assinalaste qualquer publicação como favorita. Quando o fizeres, ela aparecerá aqui.", @@ -338,6 +384,7 @@ "empty_column.notification_requests": "Tudo limpo! Não há nada aqui. Quando receberes novas notificações, elas aparecerão aqui conforme as tuas configurações.", "empty_column.notifications": "Ainda não tens quaisquer notificações. Quando outras pessoas interagirem contigo, verás isso aqui.", "empty_column.public": "Não há nada aqui! Escreve algo publicamente ou segue outros utilizadores para veres aqui os conteúdos públicos", + "error.no_hashtag_feed_access": "Inscreva-se ou inicie sessão para ver e seguir esta etiqueta.", "error.unexpected_crash.explanation": "Devido a um erro no nosso código ou a um problema de compatibilidade do navegador, esta página não pode ser apresentada corretamente.", "error.unexpected_crash.explanation_addons": "Esta página não pode ser mostrada corretamente. Este erro provavelmente é causado por um complemento do navegador ou ferramentas de tradução automática.", "error.unexpected_crash.next_steps": "Tenta atualizar a página. Se isso não ajudar, podes usar o Mastodon através de um navegador diferente ou uma aplicação nativa.", @@ -349,16 +396,14 @@ "explore.trending_links": "Notícias", "explore.trending_statuses": "Publicações", "explore.trending_tags": "#Etiquetas", + "featured_carousel.current": "Publicação {current, number} / {max, number}", "featured_carousel.header": "{count, plural, one {Publicação Afixada} other {Publicações Afixadas}}", - "featured_carousel.next": "Seguinte", - "featured_carousel.post": "Publicação", - "featured_carousel.previous": "Anterior", - "featured_carousel.slide": "{index} de {total}", + "featured_carousel.slide": "Publicação {current, number} de {max, number}", "filter_modal.added.context_mismatch_explanation": "Esta categoria de filtro não se aplica ao contexto em que acedeste a esta publicação. Se pretenderes que esta publicação seja filtrada também neste contexto, terás que editar o filtro.", "filter_modal.added.context_mismatch_title": "O contexto não coincide!", "filter_modal.added.expired_explanation": "Esta categoria de filtro expirou, tens de alterar a data de validade para que ele seja aplicado.", "filter_modal.added.expired_title": "Filtro expirado!", - "filter_modal.added.review_and_configure": "Para rever e configurar mais detalhadamente esta categoria de filtro, vai a {settings_link}.", + "filter_modal.added.review_and_configure": "Para rever e configurar mais detalhadamente esta categoria de filtro, vá a {settings_link}.", "filter_modal.added.review_and_configure_title": "Definições do filtro", "filter_modal.added.settings_link": "página de definições", "filter_modal.added.short_explanation": "Esta publicação foi adicionada à seguinte categoria de filtro: {title}.", @@ -396,6 +441,9 @@ "follow_suggestions.who_to_follow": "Quem seguir", "followed_tags": "Etiquetas seguidas", "footer.about": "Sobre", + "footer.about_mastodon": "Sobre o Mastodon", + "footer.about_server": "Sobre {domain}", + "footer.about_this_server": "Sobre", "footer.directory": "Diretório de perfis", "footer.get_app": "Obter a aplicação", "footer.keyboard_shortcuts": "Atalhos de teclado", @@ -421,10 +469,10 @@ "hashtag.counter_by_uses": "{count, plural, one {{counter} publicação} other {{counter} publicações}}", "hashtag.counter_by_uses_today": "{count, plural, one {{counter} publicação} other {{counter} publicações}} hoje", "hashtag.feature": "Destacar no perfil", - "hashtag.follow": "Seguir #etiqueta", + "hashtag.follow": "Seguir etiqueta", "hashtag.mute": "Silenciar #{hashtag}", "hashtag.unfeature": "Não destacar no perfil", - "hashtag.unfollow": "Deixar de seguir #etiqueta", + "hashtag.unfollow": "Deixar de seguir a etiqueta", "hashtags.and_other": "…e {count, plural, other {mais #}}", "hints.profiles.followers_may_be_missing": "É possível que não estejam a ser mostrados todos os seguidores deste perfil.", "hints.profiles.follows_may_be_missing": "É possível que não estejam a ser mostrados todos os seguidos por este perfil.", @@ -433,7 +481,7 @@ "hints.profiles.see_more_follows": "Ver mais perfis seguidos em {domain}", "hints.profiles.see_more_posts": "Ver mais publicações em {domain}", "home.column_settings.show_quotes": "Mostrar citações", - "home.column_settings.show_reblogs": "Mostrar impulsos", + "home.column_settings.show_reblogs": "Mostrar partilhas", "home.column_settings.show_replies": "Mostrar respostas", "home.hide_announcements": "Ocultar mensagens de manutenção", "home.pending_critical_update.body": "Atualiza o teu servidor Mastodon assim que possível!", @@ -465,7 +513,7 @@ "intervals.full.minutes": "{number, plural, one {# minuto} other {# minutos}}", "keyboard_shortcuts.back": "voltar atrás", "keyboard_shortcuts.blocked": "abrir a lista de utilizadores bloqueados", - "keyboard_shortcuts.boost": "impulsionar a publicação", + "keyboard_shortcuts.boost": "Partilhar a publicação", "keyboard_shortcuts.column": "focar uma publicação numa das colunas", "keyboard_shortcuts.compose": "focar área de texto da publicação", "keyboard_shortcuts.description": "Descrição", @@ -497,6 +545,7 @@ "keyboard_shortcuts.toggle_hidden": "mostrar / esconder texto atrás do aviso de conteúdo", "keyboard_shortcuts.toggle_sensitivity": "mostrar / ocultar multimédia", "keyboard_shortcuts.toot": "criar uma nova publicação", + "keyboard_shortcuts.top": "Mover para o topo da lista", "keyboard_shortcuts.translate": "traduzir uma publicação", "keyboard_shortcuts.unfocus": "remover o foco da área de texto / pesquisa", "keyboard_shortcuts.up": "mover para cima na lista", @@ -556,7 +605,7 @@ "navigation_bar.advanced_interface": "Abrir na interface web avançada", "navigation_bar.automated_deletion": "Eliminação automática de publicações", "navigation_bar.blocks": "Utilizadores bloqueados", - "navigation_bar.bookmarks": "Marcadores", + "navigation_bar.bookmarks": "Itens salvos", "navigation_bar.direct": "Menções privadas", "navigation_bar.domain_blocks": "Domínios escondidos", "navigation_bar.favourites": "Favoritos", @@ -618,8 +667,8 @@ "notification.own_poll": "A tua sondagem terminou", "notification.poll": "Terminou uma sondagem em que votaste", "notification.quoted_update": "{name} editou uma publicação que citou", - "notification.reblog": "{name} impulsionou a tua publicação", - "notification.reblog.name_and_others_with_link": "{name} e {count, plural, one {# outro} other {# outros}} impulsionaram a tua publicação", + "notification.reblog": "{name} partilhou a sua publicação", + "notification.reblog.name_and_others_with_link": "{name} e {count, plural, one {# outro} other {# outros}} partilharam a sua publicação", "notification.relationships_severance_event": "Perdeu as ligações com {name}", "notification.relationships_severance_event.account_suspension": "Um administrador de {from} suspendeu {target}, o que significa que já não podes receber atualizações dele ou interagir com ele.", "notification.relationships_severance_event.domain_block": "Um administrador de {from} bloqueou {target}, incluindo {followersCount} dos teus seguidores e {followingCount, plural, one {# conta} other {# contas}} que segues.", @@ -662,7 +711,7 @@ "notifications.column_settings.poll": "Resultados da sondagem:", "notifications.column_settings.push": "Notificações \"push\"", "notifications.column_settings.quote": "Citações:", - "notifications.column_settings.reblog": "Impulsos:", + "notifications.column_settings.reblog": "Partilhas:", "notifications.column_settings.show": "Mostrar na coluna", "notifications.column_settings.sound": "Reproduzir som", "notifications.column_settings.status": "Novas publicações:", @@ -670,7 +719,7 @@ "notifications.column_settings.unread_notifications.highlight": "Destacar notificações por ler", "notifications.column_settings.update": "Edições:", "notifications.filter.all": "Todas", - "notifications.filter.boosts": "Impulsos", + "notifications.filter.boosts": "Partilhas", "notifications.filter.favourites": "Favoritos", "notifications.filter.follows": "Seguidores", "notifications.filter.mentions": "Menções", @@ -745,7 +794,9 @@ "privacy.unlisted.short": "Público silencioso", "privacy_policy.last_updated": "Última atualização em {date}", "privacy_policy.title": "Política de privacidade", + "quote_error.edit": "Não é possível adicionar citações ao editar uma publicação.", "quote_error.poll": "Não é permitido citar sondagens.", + "quote_error.private_mentions": "A citação não é permitida em menções privadas.", "quote_error.quote": "Apenas é permitida uma citação de cada vez.", "quote_error.unauthorized": "Não está autorizado a citar esta publicação.", "quote_error.upload": "Não é permitida a citação com anexos de multimédia.", @@ -858,14 +909,19 @@ "status.admin_account": "Abrir a interface de moderação para @{name}", "status.admin_domain": "Abrir interface de moderação para {domain}", "status.admin_status": "Abrir esta publicação na interface de moderação", - "status.all_disabled": "Impulsos e citações estão desativados", + "status.all_disabled": "Partilhas e citações estão desativados", "status.block": "Bloquear @{name}", "status.bookmark": "Guardar nos marcadores", - "status.cancel_reblog_private": "Retirar impulso", + "status.cancel_reblog_private": "Deixar de partilhar", "status.cannot_quote": "Não lhe é permitido citar esta publicação", - "status.cannot_reblog": "Esta publicação não pode ser impulsionada", - "status.context.load_new_replies": "Novas respostas disponíveis", - "status.context.loading": "A verificar por mais respostas", + "status.cannot_reblog": "Esta publicação não pode ser partilhada", + "status.contains_quote": "Contém citação", + "status.context.loading": "A carregar mais respostas", + "status.context.loading_error": "Não foi possível carregar novas respostas", + "status.context.loading_success": "Novas respostas carregadas", + "status.context.more_replies_found": "Foram encontradas mais respostas", + "status.context.retry": "Repetir", + "status.context.show": "Mostrar", "status.continued_thread": "Continuação da conversa", "status.copy": "Copiar hiperligação da publicação", "status.delete": "Eliminar", @@ -878,7 +934,7 @@ "status.edited_x_times": "Editado {count, plural,one {{count} vez} other {{count} vezes}}", "status.embed": "Obter código de incorporação", "status.favourite": "Adicionar aos favoritos", - "status.favourites": "{count, plural, one {favorito} other {favoritos}}", + "status.favourites_count": "{count, plural, one {{counter} favorito} other {{counter} favoritos}}", "status.filter": "Filtrar esta publicação", "status.history.created": "{name} criado em {date}", "status.history.edited": "{name} editado em {date}", @@ -894,27 +950,33 @@ "status.pin": "Afixar no perfil", "status.quote": "Citação", "status.quote.cancel": "Cancelar citação", + "status.quote_error.blocked_account_hint.title": "Esta publicação está oculta porque bloqueou @{name}.", + "status.quote_error.blocked_domain_hint.title": "Esta publicação está oculta porque bloqueou {domain}.", "status.quote_error.filtered": "Oculto devido a um dos seus filtros", "status.quote_error.limited_account_hint.action": "Mostrar na mesma", "status.quote_error.limited_account_hint.title": "Esta conta foi ocultada pelos moderadores de {domain}.", + "status.quote_error.muted_account_hint.title": "Esta publicação está oculta porque silenciou @{name}.", "status.quote_error.not_available": "Publicação indisponível", "status.quote_error.pending_approval": "Publicação pendente", "status.quote_error.pending_approval_popout.body": "No Mastodon, pode controlar se alguém pode citar as suas publicações. Esta publicação está pendente enquanto aguardamos a aprovação do autor original.", "status.quote_error.revoked": "Publicação removida pelo autor", "status.quote_followers_only": "Apenas seguidores podem citar esta publicação", "status.quote_manual_review": "O autor vai proceder a uma revisão manual", + "status.quote_noun": "Citação", "status.quote_policy_change": "Alterar quem pode citar", "status.quote_post_author": "Citou uma publicação de @{name}", "status.quote_private": "Publicações privadas não podem ser citadas", - "status.quotes": "{count, plural, one {citação} other {citações}}", "status.quotes.empty": "Ainda ninguém citou esta publicação. Quando alguém o fizer, aparecerá aqui.", + "status.quotes.local_other_disclaimer": "As citações rejeitadas pelo autor não serão exibidas.", + "status.quotes.remote_other_disclaimer": "Apenas citações de {domain} serão garantidamente exibidas aqui. Citações rejeitadas pelo autor não serão exibidas.", + "status.quotes_count": "{count, plural, one {{counter} citação} other {{counter} citações}}", "status.read_more": "Ler mais", - "status.reblog": "Impulsionar", + "status.reblog": "Partilhar", "status.reblog_or_quote": "Partilhe ou cite", "status.reblog_private": "Partilhe novamente com os seus seguidores", - "status.reblogged_by": "{name} impulsionou", - "status.reblogs": "{count, plural, one {impulso} other {impulsos}}", - "status.reblogs.empty": "Ainda ninguém impulsionou esta publicação. Quando alguém o fizer, aparecerá aqui.", + "status.reblogged_by": "{name} partilhou", + "status.reblogs.empty": "Ainda ninguém partilhou esta publicação. Quando alguém o fizer, aparecerá aqui.", + "status.reblogs_count": "{count, plural, one {{counter} partilha} other {{counter} partilhas}}", "status.redraft": "Eliminar e reescrever", "status.remove_bookmark": "Retirar dos marcadores", "status.remove_favourite": "Remover dos favoritos", @@ -988,12 +1050,14 @@ "video.volume_down": "Diminuir volume", "video.volume_up": "Aumentar volume", "visibility_modal.button_title": "Definir visibilidade", + "visibility_modal.direct_quote_warning.text": "Se guardar as definições atuais, a citação incorporada será convertida numa hiperligação.", + "visibility_modal.direct_quote_warning.title": "Citações não podem ser incorporadas em menções privadas", "visibility_modal.header": "Visibilidade e interação", "visibility_modal.helper.direct_quoting": "As menções privadas criadas no Mastodon não podem ser citadas por outras pessoas.", "visibility_modal.helper.privacy_editing": "A visibilidade não pode ser alterada após a publicação ser publicada.", "visibility_modal.helper.privacy_private_self_quote": "As autocitações de publicações privadas não podem ser tornadas públicas.", "visibility_modal.helper.private_quoting": "As publicações apenas para seguidores criadas no Mastodon não podem ser citadas por outras pessoas.", - "visibility_modal.helper.unlisted_quoting": "Quando as pessoas o citarem, as publicações delas serão também ocultadas das tendências.", + "visibility_modal.helper.unlisted_quoting": "Quando as pessoas o citarem, as respetivas publicações também serão ocultadas dos destaques.", "visibility_modal.instructions": "Controle quem pode interagir com esta publicação. Também pode definir esta configuração para todas as publicações futuras, em Preferências > Padrões de publicação.", "visibility_modal.privacy_label": "Visibilidade", "visibility_modal.quote_followers": "Apenas seguidores", diff --git a/app/javascript/mastodon/locales/ro.json b/app/javascript/mastodon/locales/ro.json index ce3dde8eeaf207..8f487696434cdf 100644 --- a/app/javascript/mastodon/locales/ro.json +++ b/app/javascript/mastodon/locales/ro.json @@ -55,7 +55,6 @@ "account.posts": "Postări", "account.posts_with_replies": "Postări și răspunsuri", "account.report": "Raportează pe @{name}", - "account.requested": "Se așteaptă aprobarea. Apasă pentru a anula cererea de urmărire", "account.requested_follow": "{name} A cerut să vă urmărească", "account.share": "Distribuie profilul lui @{name}", "account.show_reblogs": "Afișează distribuirile de la @{name}", @@ -83,19 +82,8 @@ "alert.unexpected.title": "Ups!", "alt_text_badge.title": "Text alternativ", "announcement.announcement": "Anunț", - "annual_report.summary.archetype.lurker": "Pânditorul", - "annual_report.summary.archetype.oracle": "Oracolul", - "annual_report.summary.archetype.pollster": "Sondatorul", - "annual_report.summary.archetype.replier": "Fluturele social", - "annual_report.summary.followers.followers": "urmăritori", - "annual_report.summary.followers.total": "{count} total", - "annual_report.summary.here_it_is": "Iată rezumatul dvs. al anului {year}:", - "annual_report.summary.highlighted_post.by_favourites": "cea mai favorizată postare", - "annual_report.summary.highlighted_post.by_reblogs": "cea mai boostată postare", - "annual_report.summary.highlighted_post.by_replies": "postarea cu cele mai multe răspunsuri", "annual_report.summary.most_used_app.most_used_app": "cea mai utilizată aplicație", "annual_report.summary.most_used_hashtag.most_used_hashtag": "cel mai utilizat hashtag", - "annual_report.summary.most_used_hashtag.none": "Niciunul", "annual_report.summary.new_posts.new_posts": "postări noi", "attachments_list.unprocessed": "(neprocesate)", "audio.hide": "Ascunde audio", @@ -178,7 +166,6 @@ "confirmations.mute.confirm": "Ignoră", "confirmations.redraft.confirm": "Șterge și scrie din nou", "confirmations.unfollow.confirm": "Dezabonează-te", - "confirmations.unfollow.message": "Ești sigur că vrei să te dezabonezi de la {name}?", "conversation.delete": "Șterge conversația", "conversation.mark_as_read": "Marchează ca citit", "conversation.open": "Vizualizează conversația", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index a22268bb62bce2..e6891eaced2a83 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Не уведомлять о постах пользователя @{name}", "account.domain_blocking": "Домен заблокирован", "account.edit_profile": "Редактировать", + "account.edit_profile_short": "Редактировать", "account.enable_notifications": "Уведомлять о постах пользователя @{name}", "account.endorse": "Рекомендовать в профиле", "account.familiar_followers_many": "В подписках у {name1}, {name2}, и ещё {othersCount, plural, one {# человека, которого вы знаете} other {# человек, которых вы знаете}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Нет постов", "account.follow": "Подписаться", "account.follow_back": "Подписаться в ответ", + "account.follow_back_short": "Подписаться", + "account.follow_request": "Отправить запрос на подписку", + "account.follow_request_cancel": "Отозвать запрос", + "account.follow_request_cancel_short": "Отозвать", + "account.follow_request_short": "Отправить запрос", "account.followers": "Подписчики", "account.followers.empty": "На этого пользователя пока никто не подписан.", "account.followers_counter": "{count, plural, one {{counter} подписчик} few {{counter} подписчика} other {{counter} подписчиков}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Посты и ответы", "account.remove_from_followers": "Убрать {name} из подписчиков", "account.report": "Пожаловаться на @{name}", - "account.requested": "Ожидает подтверждения. Нажмите для отмены запроса", "account.requested_follow": "{name} отправил(а) вам запрос на подписку", "account.requests_to_follow_you": "Отправил(а) вам запрос на подписку", "account.share": "Поделиться профилем @{name}", @@ -108,25 +113,25 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Добавьте описание для людей с нарушениями зрения…", "alt_text_modal.done": "Готово", "announcement.announcement": "Объявление", - "annual_report.summary.archetype.booster": "Репостер", - "annual_report.summary.archetype.lurker": "Молчун", - "annual_report.summary.archetype.oracle": "Гуру", - "annual_report.summary.archetype.pollster": "Опросчик", - "annual_report.summary.archetype.replier": "Душа компании", - "annual_report.summary.followers.followers": "подписчиков", - "annual_report.summary.followers.total": "{count} за всё время", - "annual_report.summary.here_it_is": "Вот ваши итоги {year} года:", - "annual_report.summary.highlighted_post.by_favourites": "пост с наибольшим количеством звёздочек", - "annual_report.summary.highlighted_post.by_reblogs": "самый популярный пост", - "annual_report.summary.highlighted_post.by_replies": "пост с наибольшим количеством ответов", - "annual_report.summary.highlighted_post.possessive": "{name}", + "annual_report.announcement.action_dismiss": "Нет, спасибо", + "annual_report.announcement.action_view": "Посмотреть мой Wrapstodon", + "annual_report.announcement.title": "Wrapstodon {year} уже здесь", + "annual_report.nav_item.badge": "Новый", + "annual_report.shared_page.donate": "Пожертвовать", + "annual_report.shared_page.footer": "Сгенерировано с {heart} командой Mastodon", + "annual_report.summary.archetype.die_drei_fragezeichen": "???", + "annual_report.summary.archetype.reveal": "Показать мой архетип", + "annual_report.summary.archetype.title_public": "Архетип {name}", + "annual_report.summary.archetype.title_self": "Ваш архетип", + "annual_report.summary.close": "Закрыть", + "annual_report.summary.copy_link": "Скопировать ссылку", "annual_report.summary.most_used_app.most_used_app": "наиболее часто используемое приложение", "annual_report.summary.most_used_hashtag.most_used_hashtag": "наиболее часто используемый хештег", - "annual_report.summary.most_used_hashtag.none": "Нет", "annual_report.summary.new_posts.new_posts": "новых постов", "annual_report.summary.percentile.text": "Всё это помещает вас в топпользователей {domain}.", "annual_report.summary.percentile.we_wont_tell_bernie": "Роскомнадзор об этом не узнает.", - "annual_report.summary.thanks": "Спасибо за то, что были вместе с Mastodon!", + "annual_report.summary.share_elsewhere": "Поделиться на других", + "annual_report.summary.share_on_mastodon": "Поделиться в Mastodon", "attachments_list.unprocessed": "(не обработан)", "audio.hide": "Скрыть аудио", "block_modal.remote_users_caveat": "Мы попросим сервер {domain} уважать ваше решение, однако нельзя гарантировать, что он будет соблюдать блокировку, поскольку некоторые серверы могут по-разному обрабатывать запросы. Публичные посты по-прежнему могут быть видны неавторизованным пользователям.", @@ -152,6 +157,7 @@ "bundle_modal_error.close": "Закрыть", "bundle_modal_error.message": "Кое-что пошло не так при загрузке этой страницы.", "bundle_modal_error.retry": "Попробовать снова", + "carousel.slide": "Слайд {current, number} из {max, number}", "closed_registrations.other_server_instructions": "Благодаря тому что Mastodon децентрализован, вы можете взаимодействовать с этим сервером, даже если зарегистрируетесь на другом сервере.", "closed_registrations_modal.description": "Зарегистрироваться на {domain} сейчас не выйдет, но имейте в виду, что вам не нужна учётная запись именно на {domain}, чтобы использовать Mastodon.", "closed_registrations_modal.find_another_server": "Найти другой сервер", @@ -168,6 +174,8 @@ "column.edit_list": "Редактировать список", "column.favourites": "Избранное", "column.firehose": "Живая лента", + "column.firehose_local": "Живая лента этого сервера", + "column.firehose_singular": "Живая лента", "column.follow_requests": "Запросы на подписку", "column.home": "Главная", "column.list_members": "Пользователи в списке", @@ -187,6 +195,7 @@ "community.column_settings.local_only": "Только локальные", "community.column_settings.media_only": "Только с медиафайлами", "community.column_settings.remote_only": "Только с других серверов", + "compose.error.blank_post": "Нельзя опубликовать пустой пост.", "compose.language.change": "Изменить язык", "compose.language.search": "Найти язык...", "compose.published.body": "Пост опубликован.", @@ -239,22 +248,30 @@ "confirmations.missing_alt_text.secondary": "Опубликовать", "confirmations.missing_alt_text.title": "Добавить альтернативный текст?", "confirmations.mute.confirm": "Игнорировать", - "confirmations.quiet_post_quote_info.dismiss": "Больше не показывать", + "confirmations.private_quote_notify.cancel": "Вернуться к редактированию", + "confirmations.private_quote_notify.confirm": "Опубликовать", + "confirmations.private_quote_notify.do_not_show_again": "Не показывать это сообщение снова", + "confirmations.private_quote_notify.message": "Пользователь, которого вы процитировали, а также другие упомянутые пользователи будут уведомлены и смогут просмотреть ваш пост, даже если они не подписаны на вас.", + "confirmations.private_quote_notify.title": "Поделиться с подписчиками и упомянутыми пользователями?", + "confirmations.quiet_post_quote_info.dismiss": "Больше не напоминать", "confirmations.quiet_post_quote_info.got_it": "Понятно", - "confirmations.quiet_post_quote_info.message": "Если вы цитируете публикацию, опубликованную в открытом доступе, ваша публикация будет скрыта от новостных лент.", - "confirmations.quiet_post_quote_info.title": "Цитирование тихих публичных постов", + "confirmations.quiet_post_quote_info.message": "Если ваш пост содержит цитирование «тихого публичного» поста, он будет скрыт из алгоритмических лент.", + "confirmations.quiet_post_quote_info.title": "Цитирование «тихих публичных» постов", "confirmations.redraft.confirm": "Удалить и исправить", "confirmations.redraft.message": "Вы уверены, что хотите удалить этот пост и создать его заново? Взаимодействия, такие как добавление в избранное и продвижение, будут потеряны, а ответы к оригинальному посту перестанут на него ссылаться.", "confirmations.redraft.title": "Удалить и создать пост заново?", "confirmations.remove_from_followers.confirm": "Убрать подписчика", "confirmations.remove_from_followers.message": "Пользователь {name} перестанет быть подписан на вас. Продолжить?", "confirmations.remove_from_followers.title": "Убрать подписчика?", - "confirmations.revoke_quote.confirm": "Удалить публикацию", - "confirmations.revoke_quote.message": "Это действие нельзя будет отменить.", - "confirmations.revoke_quote.title": "Удалить публикацию?", + "confirmations.revoke_quote.confirm": "Убрать пост", + "confirmations.revoke_quote.message": "Это действие невозможно отменить.", + "confirmations.revoke_quote.title": "Убрать пост?", + "confirmations.unblock.confirm": "Разблокировать", + "confirmations.unblock.title": "Разблокировать {name}?", "confirmations.unfollow.confirm": "Отписаться", - "confirmations.unfollow.message": "Вы уверены, что хотите отписаться от {name}?", - "confirmations.unfollow.title": "Отписаться?", + "confirmations.unfollow.title": "Отписаться от {name}?", + "confirmations.withdraw_request.confirm": "Отозвать запрос", + "confirmations.withdraw_request.title": "Отозвать запрос на подписку на {name}?", "content_warning.hide": "Скрыть пост", "content_warning.show": "Всё равно показать", "content_warning.show_more": "Развернуть", @@ -325,6 +342,7 @@ "empty_column.bookmarked_statuses": "У вас пока нет закладок. Когда вы добавляете пост в закладки, он появляется здесь.", "empty_column.community": "Локальная лента пуста. Напишите что-нибудь, чтобы разогреть народ!", "empty_column.direct": "Вы ещё не упоминали кого-либо и сами не были ни разу упомянуты лично. Все личные упоминания будут показаны здесь.", + "empty_column.disabled_feed": "Эта лента была отключена администраторами вашего сервера.", "empty_column.domain_blocks": "Заблокированных доменов пока нет.", "empty_column.explore_statuses": "Сейчас нет популярных постов. Проверьте позже!", "empty_column.favourited_statuses": "Вы ещё не добавили ни одного поста в избранное. Все добавленные вами в избранное посты будут показаны здесь.", @@ -338,6 +356,7 @@ "empty_column.notification_requests": "Здесь ничего нет! Когда вы получите новые уведомления, они здесь появятся согласно вашим настройкам.", "empty_column.notifications": "У вас пока нет уведомлений. Взаимодействуйте с другими, чтобы завести разговор.", "empty_column.public": "Здесь ничего нет! Опубликуйте что-нибудь или подпишитесь на пользователей с других серверов, чтобы заполнить ленту", + "error.no_hashtag_feed_access": "Зарегистрируйтесь или войдите, чтобы читать и подписаться на этот хэштег.", "error.unexpected_crash.explanation": "Из-за несовместимого браузера или ошибки в нашем коде эта страница не может быть корректно отображена.", "error.unexpected_crash.explanation_addons": "Эта страница не может быть корректно отображена. Скорее всего, ошибка вызвана расширением браузера или инструментом автоматического перевода.", "error.unexpected_crash.next_steps": "Попробуйте обновить страницу. Если это не поможет, вы, возможно, всё ещё сможете использовать Mastodon в другом браузере или приложении.", @@ -349,11 +368,9 @@ "explore.trending_links": "Новости", "explore.trending_statuses": "Посты", "explore.trending_tags": "Хештеги", + "featured_carousel.current": "Пост {current, number} / {max, number}", "featured_carousel.header": "{count, plural, other {Закреплённые посты}}", - "featured_carousel.next": "Следующий", - "featured_carousel.post": "Пост", - "featured_carousel.previous": "Предыдущий", - "featured_carousel.slide": "{index} из {total}", + "featured_carousel.slide": "Пост {current, number} из {max, number}", "filter_modal.added.context_mismatch_explanation": "Этот фильтр не применяется в том контексте, в котором вы видели этот пост. Если вы хотите, чтобы пост был отфильтрован в текущем контексте, необходимо редактировать фильтр.", "filter_modal.added.context_mismatch_title": "Несоответствие контекста", "filter_modal.added.expired_explanation": "Этот фильтр истёк. Чтобы он был применён, вам нужно изменить срок действия фильтра.", @@ -396,6 +413,7 @@ "follow_suggestions.who_to_follow": "На кого подписаться", "followed_tags": "Подписки на хештеги", "footer.about": "О проекте", + "footer.about_this_server": "Об этом сервере", "footer.directory": "Каталог профилей", "footer.get_app": "Скачать приложение", "footer.keyboard_shortcuts": "Сочетания клавиш", @@ -479,7 +497,7 @@ "keyboard_shortcuts.home": "перейти к домашней ленте", "keyboard_shortcuts.hotkey": "Горячая клавиша", "keyboard_shortcuts.legend": "показать эту справку", - "keyboard_shortcuts.load_more": "Акцент на кнопке «Загрузить ещё»", + "keyboard_shortcuts.load_more": "фокус на кнопке «Загрузить ещё»", "keyboard_shortcuts.local": "перейти к локальной ленте", "keyboard_shortcuts.mention": "упомянуть автора поста", "keyboard_shortcuts.muted": "открыть список игнорируемых пользователей", @@ -488,7 +506,7 @@ "keyboard_shortcuts.open_media": "открыть медиа", "keyboard_shortcuts.pinned": "перейти к закреплённым постам", "keyboard_shortcuts.profile": "перейти к профилю автора", - "keyboard_shortcuts.quote": "Цитировать пост", + "keyboard_shortcuts.quote": "цитировать пост", "keyboard_shortcuts.reply": "ответить", "keyboard_shortcuts.requests": "перейти к запросам на подписку", "keyboard_shortcuts.search": "перейти к поиску", @@ -612,12 +630,12 @@ "notification.moderation_warning.action_disable": "Ваша учётная запись была отключена.", "notification.moderation_warning.action_mark_statuses_as_sensitive": "Некоторые ваши посты были отмечены как содержимое деликатного характера.", "notification.moderation_warning.action_none": "Модераторы вынесли вам предупреждение.", - "notification.moderation_warning.action_sensitive": "С этого момента все ваши новые посты будут отмечены как содержимое деликатного характера.", + "notification.moderation_warning.action_sensitive": "С этого момента все ваши посты будут отмечены как содержимое деликатного характера.", "notification.moderation_warning.action_silence": "Ваша учётная запись была ограничена.", "notification.moderation_warning.action_suspend": "Ваша учётная запись была заблокирована.", "notification.own_poll": "Ваш опрос завершился", "notification.poll": "Опрос, в котором вы приняли участие, завершился", - "notification.quoted_update": "{name} отредактировал процитированное вами сообщение", + "notification.quoted_update": "{name} отредактировал(а) процитированный вами пост", "notification.reblog": "{name} продвинул(а) ваш пост", "notification.reblog.name_and_others_with_link": "{name} и ещё {count, plural, one {# пользователь} few {# пользователя} other {# пользователей}} продвинули ваш пост", "notification.relationships_severance_event": "Разорвана связь с {name}", @@ -737,18 +755,20 @@ "privacy.private.short": "Для подписчиков", "privacy.public.long": "Для кого угодно в интернете", "privacy.public.short": "Публичный", - "privacy.quote.anyone": "{visibility}, каждый может процитировать", - "privacy.quote.disabled": "{visibility}, цитаты отключены", - "privacy.quote.limited": "{visibility}, цитаты ограничены", + "privacy.quote.anyone": "{visibility}, цитировать разрешено всем", + "privacy.quote.disabled": "{visibility}, без возможности цитирования", + "privacy.quote.limited": "{visibility}, с ограничениями цитирования", "privacy.unlisted.additional": "Похоже на «Публичный» за исключением того, что пост не появится ни в живых лентах, ни в лентах хештегов, ни в разделе «Актуальное», ни в поиске Mastodon, даже если вы разрешили поиск по своим постам в настройках профиля.", - "privacy.unlisted.long": "Скрыто от результатов поиска Mastodon, трендов и публичных графиков", + "privacy.unlisted.long": "Не показывать в результатах поиска Mastodon, трендах и публичных лентах", "privacy.unlisted.short": "Тихий публичный", "privacy_policy.last_updated": "Последнее обновление: {date}", "privacy_policy.title": "Политика конфиденциальности", + "quote_error.edit": "Нельзя добавить цитирование к уже опубликованному посту.", "quote_error.poll": "Цитирование не допускается при голосовании.", + "quote_error.private_mentions": "Цитирование в личных упоминаниях не позволяется.", "quote_error.quote": "Одновременно допускается только одна цитата.", "quote_error.unauthorized": "Вы не имеете права цитировать этот пост.", - "quote_error.upload": "Цитирование с использованием медиа вложений запрещено.", + "quote_error.upload": "Цитирование не позволяется, если к посту прикреплены медиавложения.", "recommended": "Рекомендуется", "refresh": "Обновить", "regeneration_indicator.please_stand_by": "Пожалуйста, подождите.", @@ -765,8 +785,8 @@ "relative_time.seconds": "{number} с.", "relative_time.today": "сегодня", "remove_quote_hint.button_label": "Понятно", - "remove_quote_hint.message": "Вы можете сделать это из меню настроек {icon}.", - "remove_quote_hint.title": "Хотите удалить цитируемый вами пост?", + "remove_quote_hint.message": "Вы можете сделать это из {icon} меню поста.", + "remove_quote_hint.title": "Хотите убрать цитирование вашего поста?", "reply_indicator.attachments": "{count, plural, one {# вложение} few {# вложения} other {# вложений}}", "reply_indicator.cancel": "Отмена", "reply_indicator.poll": "Опрос", @@ -858,17 +878,23 @@ "status.admin_account": "Открыть интерфейс модератора для @{name}", "status.admin_domain": "Открыть интерфейс модератора для {domain}", "status.admin_status": "Открыть этот пост в интерфейсе модератора", - "status.all_disabled": "Бусты и цитаты отключены", + "status.all_disabled": "Нельзя продвинуть или процитировать", "status.block": "Заблокировать @{name}", "status.bookmark": "Добавить в закладки", "status.cancel_reblog_private": "Отменить продвижение", + "status.cannot_quote": "Вы не можете процитировать этот пост", "status.cannot_reblog": "Этот пост не может быть продвинут", - "status.context.load_new_replies": "Доступны новые ответы", - "status.context.loading": "Проверяем, есть ли ещё ответы", + "status.contains_quote": "Цитирует другой пост", + "status.context.loading": "Загрузка ответов", + "status.context.loading_error": "Не удалось загрузить новые ответы", + "status.context.loading_success": "Загружены новые ответы", + "status.context.more_replies_found": "Найдены другие ответы", + "status.context.retry": "Повторить", + "status.context.show": "Показать", "status.continued_thread": "Продолжение предыдущего поста", "status.copy": "Скопировать ссылку на пост", "status.delete": "Удалить", - "status.delete.success": "Пост удален", + "status.delete.success": "Пост удалён", "status.detailed_status": "Подробный просмотр обсуждения", "status.direct": "Упомянуть @{name} лично", "status.direct_indicator": "Личное упоминание", @@ -877,7 +903,7 @@ "status.edited_x_times": "{count, plural, one {{count} изменение} many {{count} изменений} other {{count} изменения}}", "status.embed": "Встроить на свой сайт", "status.favourite": "Добавить в избранное", - "status.favourites": "{count, plural, one {звёздочка} few {звёздочки} other {звёздочек}}", + "status.favourites_count": "{count, plural, one {{counter} звёздочка} few {{counter} звёздочки} other {{counter} звёздочек}}", "status.filter": "Фильтровать этот пост", "status.history.created": "{name} создал(а) пост {date}", "status.history.edited": "{name} отредактировал(а) пост {date}", @@ -893,25 +919,31 @@ "status.pin": "Закрепить в профиле", "status.quote": "Цитировать", "status.quote.cancel": "Отменить цитирование", + "status.quote_error.blocked_account_hint.title": "Этот пост был скрыт, поскольку вы заблокировали @{name}.", + "status.quote_error.blocked_domain_hint.title": "Этот пост был скрыт, поскольку вы заблокировали сервер {domain}.", "status.quote_error.filtered": "Скрыто одним из ваших фильтров", + "status.quote_error.limited_account_hint.action": "Всё равно показать", + "status.quote_error.limited_account_hint.title": "Этот профиль был скрыт модераторами сервера {domain}.", + "status.quote_error.muted_account_hint.title": "Этот пост был скрыт, поскольку вы игнорируете @{name}.", "status.quote_error.not_available": "Пост недоступен", "status.quote_error.pending_approval": "Пост ожидает подтверждения", "status.quote_error.pending_approval_popout.body": "На Mastodon вы можете контролировать, будет ли кто-то цитировать вас. Этот пост находится на рассмотрении, пока мы не получим одобрение автора.", - "status.quote_error.revoked": "Сообщение удалено автором", + "status.quote_error.revoked": "Пост удалён автором", "status.quote_followers_only": "Только подписчики могут цитировать этот пост", "status.quote_manual_review": "Автор будет проверять вручную", - "status.quote_policy_change": "Измените, кто может цитировать", - "status.quote_post_author": "Пост цитировал @{name}", - "status.quote_private": "Приватные записи не могут быть цитированы", - "status.quotes": "{count, plural, one {# голос} few {# голоса} many {# голосов} other {# голосов}}", - "status.quotes.empty": "Никто еще не цитировал этот пост. Когда кто-нибудь это сделает, он появится здесь.", + "status.quote_noun": "Цитата", + "status.quote_policy_change": "Изменить настройки цитирования", + "status.quote_post_author": "Процитировал(а) пост @{name}", + "status.quote_private": "Приватные посты не могут быть процитированы", + "status.quotes.empty": "Никто еще не процитировал этот пост. Все цитирования этого поста будут показаны здесь.", + "status.quotes_count": "{count, plural, one {{counter} цитирование} few {{counter} цитирования} other {{counter} цитирований}}", "status.read_more": "Читать далее", "status.reblog": "Продвинуть", - "status.reblog_or_quote": "Буст или цитата", - "status.reblog_private": "Поделиться снова со своими подписчиками", + "status.reblog_or_quote": "Продвинуть или процитировать", + "status.reblog_private": "Поделиться со своими подписчиками ещё раз", "status.reblogged_by": "{name} продвинул(а)", - "status.reblogs": "{count, plural, one {продвижение} few {продвижения} other {продвижений}}", "status.reblogs.empty": "Никто ещё не продвинул этот пост. Все пользователи, которые продвинут этот пост, будут показаны здесь.", + "status.reblogs_count": "{count, plural, one {{counter} продвижение} few {{counter} продвижения} other {{counter} продвижений}}", "status.redraft": "Удалить и исправить", "status.remove_bookmark": "Убрать из закладок", "status.remove_favourite": "Убрать из избранного", @@ -922,7 +954,7 @@ "status.replyAll": "Ответить в обсуждении", "status.report": "Пожаловаться на @{name}", "status.request_quote": "Запрос на цитирование", - "status.revoke_quote": "Удалить мой пост из поста @{name}", + "status.revoke_quote": "Убрать мой пост из поста @{name}", "status.sensitive_warning": "Медиа деликатного характера", "status.share": "Поделиться", "status.show_less_all": "Свернуть все предупреждения о содержании в ветке", @@ -960,7 +992,7 @@ "upload_button.label": "Прикрепить фото, видео или аудио", "upload_error.limit": "Превышено максимальное количество вложений.", "upload_error.poll": "К опросам нельзя прикреплять файлы.", - "upload_error.quote": "К опросам нельзя прикреплять файлы.", + "upload_error.quote": "К цитированиям нельзя прикреплять файлы.", "upload_form.drag_and_drop.instructions": "Чтобы выбрать вложение, нажмите \"Пробел\" (Space) или \"Ввод\" (Enter). Используйте клавиши со стрелками, чтобы передвинуть вложение в любом направлении. Нажмите \"Пробел\" (Space) или \"Ввод\" (Enter) ещё раз, чтобы переместить вложение на новое место, либо нажмите кнопку \"Выйти\" (Escape) для отмены перемещения.", "upload_form.drag_and_drop.on_drag_cancel": "Перемещение отменено. Вложение {item} было оставлено на прежнем месте.", "upload_form.drag_and_drop.on_drag_end": "Вложение {item} было перемещено.", @@ -985,12 +1017,19 @@ "video.volume_down": "Громкость уменьшена", "video.volume_up": "Громкость увеличена", "visibility_modal.button_title": "Настроить видимость", + "visibility_modal.direct_quote_warning.text": "Если вы сохраните эти настройки, вложенное цитирование будет преобразовано в ссылку.", + "visibility_modal.direct_quote_warning.title": "Нельзя добавить цитирование к личным упоминаниям", "visibility_modal.header": "Видимость и взаимодействие", - "visibility_modal.helper.direct_quoting": "Частные упоминания, созданные на Mastodon не могут быть цитированы другими.", - "visibility_modal.helper.privacy_editing": "После публикации поста его видимость нельзя изменить.", - "visibility_modal.helper.privacy_private_self_quote": "Цитаты из личных сообщений не могут быть опубликованы публично.", - "visibility_modal.helper.private_quoting": "Публикации на Mastodon, доступные исключительно подписчикам, не подлежат цитированию со стороны других пользователей.", - "visibility_modal.helper.unlisted_quoting": "Когда люди цитируют вас, их посты также скрываются из ленты трендов.", - "visibility_modal.instructions": "Регулируйте, кто сможет взаимодействовать с этим сообщением. Также можно установить параметры для всех будущих публикаций, перейдя в Настройки > Стандартные параметры публикации.", - "visibility_modal.privacy_label": "Видимость" + "visibility_modal.helper.direct_quoting": "Посты, созданные в Mastodon как личные упоминания, не могут быть процитированы другими пользователями.", + "visibility_modal.helper.privacy_editing": "Видимость поста невозможно изменить после публикации.", + "visibility_modal.helper.privacy_private_self_quote": "Посты, которые цитируют ваши приватные посты, не могут быть публичными.", + "visibility_modal.helper.private_quoting": "Посты, созданные в Mastodon с видимостью только для подписчиков, не могут быть процитированы другими пользователями.", + "visibility_modal.helper.unlisted_quoting": "Если кто-нибудь процитирует вас, его пост тоже будет скрыт из алгоритмических лент.", + "visibility_modal.instructions": "Определите, кто сможет взаимодействовать с этим постом. Чтобы применить настройки ко всем будущим постам, перейдите в Настройки > Предустановки для новых постов.", + "visibility_modal.privacy_label": "Видимость", + "visibility_modal.quote_followers": "Только подписчики", + "visibility_modal.quote_label": "Кто может цитировать вас", + "visibility_modal.quote_nobody": "Только я", + "visibility_modal.quote_public": "Кто угодно", + "visibility_modal.save": "Сохранить" } diff --git a/app/javascript/mastodon/locales/ry.json b/app/javascript/mastodon/locales/ry.json index 20cc8e8e4eb708..9426a9c5c07662 100644 --- a/app/javascript/mastodon/locales/ry.json +++ b/app/javascript/mastodon/locales/ry.json @@ -51,7 +51,6 @@ "account.posts": "Публикації", "account.posts_with_replies": "Публикації тай удповіді", "account.report": "Скарговати ся на {name}", - "account.requested": "Чекат ся на пудтвердженя. Нажміт убы удмінити запрос на слідованя", "account.requested_follow": "Хосновач {name} просит ся пудписати ся на вас", "account.share": "Пошырити профіл хосновача {name}", "account.show_reblogs": "Указати друленя уд {name}", diff --git a/app/javascript/mastodon/locales/sa.json b/app/javascript/mastodon/locales/sa.json index b9c13df0581965..e7352f1c807adb 100644 --- a/app/javascript/mastodon/locales/sa.json +++ b/app/javascript/mastodon/locales/sa.json @@ -46,7 +46,6 @@ "account.posts": "पत्राणि", "account.posts_with_replies": "पत्राणि प्रत्युत्तराणि च", "account.report": "आविद्यताम् @{name}", - "account.requested": "स्वीकृतिः प्रतीक्ष्यते । नश्यतामित्यस्मिन्नुद्यतां निराकर्तुम् ।", "account.requested_follow": "{name} त्वामनुसर्तुमयाचीत्", "account.share": "@{name} मित्रस्य विवरणं विभाज्यताम्", "account.show_reblogs": "@{name} मित्रस्य प्रकाशनानि दृश्यन्ताम्", @@ -137,7 +136,6 @@ "confirmations.mute.confirm": "निःशब्दम्", "confirmations.redraft.confirm": "मार्जय पुनश्च लिख्यताम्", "confirmations.unfollow.confirm": "अनुसरणं नश्यताम्", - "confirmations.unfollow.message": "निश्चयेनैवाऽनुसरणं नश्यतां {name} मित्रस्य?", "conversation.delete": "वार्तालापं मार्जय", "conversation.mark_as_read": "पठितमित्यङ्क्यताम्", "conversation.open": "वार्तालापो दृश्यताम्", diff --git a/app/javascript/mastodon/locales/sc.json b/app/javascript/mastodon/locales/sc.json index d126379384efd4..5ba3d1cd968268 100644 --- a/app/javascript/mastodon/locales/sc.json +++ b/app/javascript/mastodon/locales/sc.json @@ -69,7 +69,6 @@ "account.posts_with_replies": "Publicatziones e rispostas", "account.remove_from_followers": "Cantzella a {name} dae is sighiduras", "account.report": "Signala @{name}", - "account.requested": "Abetende s'aprovatzione. Incarca pro annullare sa rechesta de sighidura", "account.requested_follow": "{name} at dimandadu de ti sighire", "account.requests_to_follow_you": "Rechestas de sighidura", "account.share": "Cumpartzi su profilu de @{name}", @@ -105,16 +104,10 @@ "alt_text_modal.change_thumbnail": "Càmbia sa miniadura", "alt_text_modal.done": "Fatu", "announcement.announcement": "Annùntziu", - "annual_report.summary.archetype.booster": "Semper a s'ùrtima", - "annual_report.summary.followers.followers": "sighiduras", - "annual_report.summary.followers.total": "{count} totale", - "annual_report.summary.highlighted_post.possessive": "de {name}", "annual_report.summary.most_used_app.most_used_app": "aplicatzione prus impreada", "annual_report.summary.most_used_hashtag.most_used_hashtag": "eticheta prus impreada", - "annual_report.summary.most_used_hashtag.none": "Peruna", "annual_report.summary.new_posts.new_posts": "publicatziones noas", "annual_report.summary.percentile.we_wont_tell_bernie": "No dd'amus a nàrrere a Bernie.", - "annual_report.summary.thanks": "Gràtzias de èssere parte de Mastodon!", "attachments_list.unprocessed": "(non protzessadu)", "audio.hide": "Cua s'àudio", "block_modal.remote_users_caveat": "Amus a pedire a su serbidore {domain} de rispetare sa detzisione tua. Nointames custu, su rispetu no est garantidu ca unos cantos serbidores diant pòdere gestire is blocos de manera diferente. Is publicatzione pùblicas diant pòdere ancora èssere visìbiles a is utentes chi no ant fatu s'atzessu.", @@ -218,8 +211,6 @@ "confirmations.redraft.message": "Seguru chi boles cantzellare e torrare a fàghere custa publicatzione? As a pèrdere is preferidos e is cumpartziduras, e is rispostas a su messàgiu originale ant a abarrare òrfanas.", "confirmations.redraft.title": "Boles cantzellare e torrare a iscrìere sa publicatzione?", "confirmations.unfollow.confirm": "Non sigas prus", - "confirmations.unfollow.message": "Seguru chi non boles sighire prus a {name}?", - "confirmations.unfollow.title": "Boles tzessare de sighire s'utente?", "content_warning.hide": "Cua sa publicatzione", "content_warning.show": "Ammustra·dda su pròpiu", "conversation.delete": "Cantzella arresonada", @@ -295,7 +286,6 @@ "explore.trending_links": "Noas", "explore.trending_statuses": "Publicatziones", "explore.trending_tags": "Etichetas", - "featured_carousel.slide": "{index} de {total}", "filter_modal.added.context_mismatch_title": "Su cuntestu non currispondet.", "filter_modal.added.expired_title": "Filtru iscadidu.", "filter_modal.added.review_and_configure_title": "Cunfiguratziones de filtru", @@ -693,7 +683,6 @@ "status.detailed_status": "Visualizatzione de detàlliu de arresonada", "status.edit": "Modìfica", "status.edited_x_times": "Modificadu {count, plural, one {{count} # borta} other {{count} bortas}}", - "status.favourites": "{count, plural, one {preferidu} other {preferidos}}", "status.load_more": "Càrriga·nde àteros", "status.media_hidden": "Elementos multimediales cuados", "status.mention": "Mèntova a @{name}", diff --git a/app/javascript/mastodon/locales/sco.json b/app/javascript/mastodon/locales/sco.json index 52445feced9435..354c3393ed7d83 100644 --- a/app/javascript/mastodon/locales/sco.json +++ b/app/javascript/mastodon/locales/sco.json @@ -44,7 +44,6 @@ "account.posts": "Posts", "account.posts_with_replies": "Posts an repones", "account.report": "Clype @{name}", - "account.requested": "Haudin fir approval. Chap tae cancel follae request", "account.share": "Share @{name}'s profile", "account.show_reblogs": "Shaw heezes frae @{name}", "account.unblock": "Undingie @{name}", @@ -141,7 +140,6 @@ "confirmations.mute.confirm": "Wheesht", "confirmations.redraft.confirm": "Delete an stert anew", "confirmations.unfollow.confirm": "Unfollae", - "confirmations.unfollow.message": "Ye shuir thit ye'r wantin tae unfollae {name}?", "conversation.delete": "Delete the conversation", "conversation.mark_as_read": "Mairk as seen", "conversation.open": "Luik at conversation", diff --git a/app/javascript/mastodon/locales/si.json b/app/javascript/mastodon/locales/si.json index 107ef3f1cf6901..7b7ec1f8a2bde5 100644 --- a/app/javascript/mastodon/locales/si.json +++ b/app/javascript/mastodon/locales/si.json @@ -68,7 +68,6 @@ "account.posts_with_replies": "ලිපි සහ පිළිතුරු", "account.remove_from_followers": "අනුගාමිකයින්ගෙන් {name} ඉවත් කරන්න", "account.report": "@{name} වාර්තා කරන්න", - "account.requested": "අනුමැතිය බලාපොරොත්තුවෙන්. අනුගමනය කිරීමේ ඉල්ලීම අවලංගු කිරීමට ක්ලික් කරන්න.", "account.requested_follow": "{name} ඔබව අනුගමනය කිරීමට ඉල්ලා ඇත.", "account.requests_to_follow_you": "ඔබව අනුගමනය කිරීමට ඉල්ලීම්", "account.share": "@{name} ගේ පැතිකඩ බෙදාගන්න", @@ -106,25 +105,11 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "දෘශ්‍යාබාධිත පුද්ගලයින් සඳහා මෙය විස්තර කරන්න…", "alt_text_modal.done": "කළා", "announcement.announcement": "නිවේදනය", - "annual_report.summary.archetype.booster": "සිසිල් දඩයක්කාරයා", - "annual_report.summary.archetype.lurker": "සැඟවී සිටින්නා", - "annual_report.summary.archetype.oracle": "ඔරකල්", - "annual_report.summary.archetype.pollster": "ඡන්ද විමසන්නා", - "annual_report.summary.archetype.replier": "සමාජ සමනලයා", - "annual_report.summary.followers.followers": "අනුගාමිකයින්", - "annual_report.summary.followers.total": "මුළු {count}", - "annual_report.summary.here_it_is": "මෙන්න ඔබේ {year} සමාලෝචනය:", - "annual_report.summary.highlighted_post.by_favourites": "වඩාත්ම කැමති පළ කිරීම", - "annual_report.summary.highlighted_post.by_reblogs": "වඩාත්ම ප්‍රවර්ධනය කරන ලද පළ කිරීම", - "annual_report.summary.highlighted_post.by_replies": "වැඩිම පිළිතුරු සහිත පළ කිරීම", - "annual_report.summary.highlighted_post.possessive": "{name}ගේ", "annual_report.summary.most_used_app.most_used_app": "වැඩිපුරම භාවිතා කරන යෙදුම", "annual_report.summary.most_used_hashtag.most_used_hashtag": "වැඩිපුරම භාවිතා කරන ලද හැෂ් ටැගය", - "annual_report.summary.most_used_hashtag.none": "කිසිවක් නැත", "annual_report.summary.new_posts.new_posts": "නව පළ කිරීම්", "annual_report.summary.percentile.text": "එය ඔබව {domain} පරිශීලකයින්ගෙන් ඉහළමඅතරට ගෙන එයි.", "annual_report.summary.percentile.we_wont_tell_bernie": "අපි බර්නිට කියන්නේ නැහැ.", - "annual_report.summary.thanks": "මැස්ටෝඩන් හි කොටසක් වීම ගැන ස්තූතියි!", "attachments_list.unprocessed": "(සකසා නැති)", "audio.hide": "හඬපටය සඟවන්න", "block_modal.remote_users_caveat": "ඔබගේ තීරණයට ගරු කරන ලෙස අපි සේවාදායකයාගෙන් {domain} ඉල්ලා සිටිමු. කෙසේ වෙතත්, සමහර සේවාදායකයන් අවහිර කිරීම් වෙනස් ලෙස හසුරුවන බැවින් අනුකූලතාව සහතික නොවේ. පොදු පළ කිරීම් තවමත් ලොග් වී නොමැති පරිශීලකයින්ට දෘශ්‍යමාන විය හැකිය.", @@ -237,8 +222,6 @@ "confirmations.remove_from_followers.message": "{name} ඔබව අනුගමනය කිරීම නවත්වනු ඇත. ඔබට ඉදිරියට යාමට අවශ්‍ය බව විශ්වාසද?", "confirmations.remove_from_followers.title": "අනුගාමිකයා ඉවත් කරන්නද?", "confirmations.unfollow.confirm": "අනුගමනය නොකරන්න", - "confirmations.unfollow.message": "ඔබට {name}අනුගමනය කිරීම නවත්වන්න අවශ්‍ය බව ඔබට විශ්වාසද?", - "confirmations.unfollow.title": "පරිශීලකයා අනුගමනය නොකරන්නද?", "content_warning.hide": "සටහන සඟවන්න", "content_warning.show": "කෙසේ වෙතත් පෙන්වන්න", "content_warning.show_more": "තවත් පෙන්වන්න", @@ -812,7 +795,6 @@ "status.edited_x_times": "සංශෝධිතයි {count, plural, one {වාර {count}} other {වාර {count}}}", "status.embed": "කාවැද්දූ කේතය ලබා ගන්න", "status.favourite": "ප්‍රියතම", - "status.favourites": "{count, plural, one {ප්‍රියතම} other {ප්‍රියතම}}", "status.filter": "මෙම ලිපිය පෙරන්න", "status.history.created": "{name} නිර්මාණය {date}", "status.history.edited": "{name} සංස්කරණය {date}", @@ -829,7 +811,6 @@ "status.read_more": "තව කියවන්න", "status.reblog": "බූස්ට් කරන්න", "status.reblogged_by": "{name} වැඩි කරන ලදී", - "status.reblogs": "{count, plural, one {බූස්ට්} other {බූස්ට්}}", "status.reblogs.empty": "මෙම සටහන තවම කිසිවෙකු බූස්ට් කර නැත. යමෙකු එසේ කළ විට, ඔවුන් මෙහි පෙන්වනු ඇත.", "status.redraft": "& නැවත කෙටුම්පත මකන්න", "status.remove_bookmark": "පොත්යොමුව ඉවතලන්න", diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json index 771f52618c4390..87282c41df27b9 100644 --- a/app/javascript/mastodon/locales/sk.json +++ b/app/javascript/mastodon/locales/sk.json @@ -1,6 +1,7 @@ { "about.blocks": "Moderované servery", "about.contact": "Kontakt:", + "about.default_locale": "Predvolený", "about.disclaimer": "Mastodon je bezplatný open-source softvér s otvoreným zdrojovým kódom a ochranná známka spoločnosti Mastodon gGmbH.", "about.domain_blocks.no_reason_available": "Dôvod nebol uvedený", "about.domain_blocks.preamble": "Mastodon vo všeobecnosti umožňuje prezerať obsah a komunikovať s používateľmi z akéhokoľvek iného servera vo fediverze. Tu sú uvedené výnimky, ktoré boli urobené na tomto konkrétnom serveri.", @@ -20,13 +21,17 @@ "account.block_domain": "Blokovať doménu {domain}", "account.block_short": "Blokovať", "account.blocked": "Účet blokovaný", + "account.blocking": "Blokované", "account.cancel_follow_request": "Zrušiť žiadosť o sledovanie", "account.copy": "Skopírovať odkaz na profil", "account.direct": "Súkromne označiť @{name}", "account.disable_notifications": "Zrušiť upozornenia na príspevky od @{name}", + "account.domain_blocking": "Blokované domény", "account.edit_profile": "Upraviť profil", + "account.edit_profile_short": "Upraviť", "account.enable_notifications": "Zapnúť upozornenia na príspevky od @{name}", "account.endorse": "Zobraziť na vlastnom profile", + "account.familiar_followers_many": "Sleduje {name1}, {name2} a {othersCount, plural, one {ďalší účet, ktorý poznáte} few {# ďalšie účty, ktoré poznáte} many {#} other {# ďalších účtov, ktoré poznáte}}", "account.familiar_followers_one": "Nasledovanie od {name1}", "account.familiar_followers_two": "Nasledovanie od {name1} a {name2}", "account.featured": "Zviditeľnené", @@ -36,13 +41,19 @@ "account.featured_tags.last_status_never": "Žiadne príspevky", "account.follow": "Sledovať", "account.follow_back": "Sledovať späť", - "account.followers": "Sledovatelia", + "account.follow_back_short": "Sledovať späť", + "account.follow_request": "Požiadať o sledovanie", + "account.follow_request_cancel": "Zrušiť žiadosť", + "account.follow_request_cancel_short": "Zrušiť", + "account.follow_request_short": "Požiadať", + "account.followers": "Sledujúce účty", "account.followers.empty": "Ešte nikto nesleduje tohto užívateľa.", - "account.followers_counter": "{count, plural, one {{counter} sledujúci} other {{counter} sledujúci}}", - "account.following": "Sledovaný účet", + "account.followers_counter": "{count, plural, one {{counter} sledujúci účet} few {{counter} sledujúce účty} many {{counter} followers} other {{counter} sledujúcich účtov}}", + "account.followers_you_know_counter": "{count, one {{counter}, ktorý poznáte}, few {{counter}, ktoré poznáte}, many {{counter}, kterých znáte} other {{counter}, ktoré poznáte}}", + "account.following": "Sledovaných účtov", "account.following_counter": "{count, plural, one {{counter} sledovaných} other {{counter} sledovaných}}", "account.follows.empty": "Tento účet ešte nikoho nesleduje.", - "account.follows_you": "Nasleduje ťa", + "account.follows_you": "Sleduje vás", "account.go_to_profile": "Prejsť na profil", "account.hide_reblogs": "Skryť zdieľania od @{name}", "account.in_memoriam": "In memoriam.", @@ -58,14 +69,15 @@ "account.mute_short": "Stíšiť", "account.muted": "Účet stíšený", "account.muting": "Stíšenie", - "account.mutual": "Nasledujete sa navzájom", + "account.mutual": "Sledujete sa navzájom", "account.no_bio": "Nie je uvedený žiadny popis.", "account.open_original_page": "Otvoriť pôvodnú stránku", "account.posts": "Príspevky", "account.posts_with_replies": "Príspevky a odpovede", + "account.remove_from_followers": "Zrušiť sledovanie od {name}", "account.report": "Nahlásiť @{name}", - "account.requested": "Čaká na schválenie. Žiadosť zrušíte kliknutím sem", "account.requested_follow": "{name} vás chce sledovať", + "account.requests_to_follow_you": "Žiadosti o sledovanie", "account.share": "Zdieľaj profil @{name}", "account.show_reblogs": "Zobrazovať zdieľania od @{name}", "account.statuses_counter": "{count, plural, one {{counter} príspevok} other {{counter} príspevkov}}", @@ -92,26 +104,23 @@ "alert.rate_limited.title": "Priveľa žiadostí", "alert.unexpected.message": "Vyskytla sa nečakaná chyba.", "alert.unexpected.title": "Ups!", - "alt_text_badge.title": "Alternatívny popis", - "alt_text_modal.add_text_from_image": "Pridaj text z obrázka", + "alt_text_badge.title": "Opis", + "alt_text_modal.add_alt_text": "Pridať opis", + "alt_text_modal.add_text_from_image": "Pridať text z obrázka", "alt_text_modal.cancel": "Zrušiť", + "alt_text_modal.change_thumbnail": "Zmeniť náhľad", + "alt_text_modal.describe_for_people_with_hearing_impairments": "Opíšte obsah pre ľudí so sluchovým postihnutím…", + "alt_text_modal.describe_for_people_with_visual_impairments": "Opíšte obsah pre ľudí so zrakovým postihnutím…", "alt_text_modal.done": "Hotovo", "announcement.announcement": "Oznámenie", - "annual_report.summary.archetype.oracle": "Veštec", - "annual_report.summary.followers.followers": "sledovatelia", - "annual_report.summary.followers.total": "{count} celkovo", - "annual_report.summary.here_it_is": "Tu je tvoja {year} v zhrnutí:", - "annual_report.summary.highlighted_post.by_favourites": "najviac obľúbený príspevok", - "annual_report.summary.highlighted_post.by_reblogs": "najviac vyzdvihovaný príspevok", - "annual_report.summary.highlighted_post.by_replies": "príspevok s najviac odpoveďami", "annual_report.summary.most_used_app.most_used_app": "najviac používaná aplikácia", "annual_report.summary.most_used_hashtag.most_used_hashtag": "najviac užívaný hashtag", - "annual_report.summary.most_used_hashtag.none": "Žiaden", "annual_report.summary.new_posts.new_posts": "nové príspevky", + "annual_report.summary.percentile.text": "Takže patríte do topúčtov na {domain}", "annual_report.summary.percentile.we_wont_tell_bernie": "Nepovieme Berniemu.", - "annual_report.summary.thanks": "Vďaka, že si súčasťou Mastodonu!", "attachments_list.unprocessed": "(nespracované)", "audio.hide": "Skryť zvuk", + "block_modal.remote_users_caveat": "Požiadame server {domain} o rešpektovanie vášho rozhodnutia. Nevieme to však zaručiť, keďže niektoré servery pristupujú k blokovaniu inak. Verejné príspevky sa stále môžu zobrazovať neprihláseným ľuďom.", "block_modal.show_less": "Zobraziť menej", "block_modal.show_more": "Zobraziť viac", "block_modal.they_cant_mention": "Nemôžu ťa spomenúť, alebo nasledovať.", @@ -120,7 +129,8 @@ "block_modal.title": "Blokovať užívateľa?", "block_modal.you_wont_see_mentions": "Neuvidíš príspevky, ktoré ich spomínajú.", "boost_modal.combo": "Nabudúce môžete preskočiť stlačením {combo}", - "boost_modal.reblog": "Vyzdvihnúť príspevok?", + "boost_modal.reblog": "Zdieľať príspevok?", + "boost_modal.undo_reblog": "Zrušiť zdieľanie?", "bundle_column_error.copy_stacktrace": "Kopírovať chybovú hlášku", "bundle_column_error.error.body": "Požadovanú stránku nebolo možné vykresliť. Môže to byť spôsobené chybou v našom kóde alebo problémom s kompatibilitou prehliadača.", "bundle_column_error.error.title": "Ale nie!", @@ -131,6 +141,7 @@ "bundle_column_error.routing.body": "Žiadaná stránka nebola nájdená. Ste si istí, že zadaná adresa URL je správna?", "bundle_column_error.routing.title": "404", "bundle_modal_error.close": "Zatvoriť", + "bundle_modal_error.message": "Pri načítavaní tejto obrazovky nastala chyba.", "bundle_modal_error.retry": "Skúsiť znova", "closed_registrations.other_server_instructions": "Keďže Mastodon je decentralizovaný, môžete si vytvoriť účet na inom serveri a stále komunikovať s týmto serverom.", "closed_registrations_modal.description": "Vytvorenie účtu na {domain} nie je v súčasnosti možné, ale myslite na to, že na používanie Mastodonu nepotrebujete účet práve na {domain}.", @@ -146,7 +157,7 @@ "column.directory": "Prehľadávať profily", "column.domain_blocks": "Blokované domény", "column.edit_list": "Uprav zoznam", - "column.favourites": "Obľúbené", + "column.favourites": "Ohviezdičkované", "column.firehose": "Živé kanály", "column.follow_requests": "Žiadosti o sledovanie", "column.home": "Domov", @@ -167,6 +178,7 @@ "community.column_settings.local_only": "Iba miestne", "community.column_settings.media_only": "Iba médiá", "community.column_settings.remote_only": "Iba vzdialené", + "compose.error.blank_post": "Príspevok nemôže byť prázdny.", "compose.language.change": "Zmeniť jazyk", "compose.language.search": "Vyhľadávať jazyky…", "compose.published.body": "Príspevok zverejnený.", @@ -175,7 +187,7 @@ "compose_form.direct_message_warning_learn_more": "Viac informácií", "compose_form.encryption_warning": "Príspevky na Mastodone nie sú šifrované end-to-end. Nezdieľajte cez Mastodon žiadne citlivé informácie.", "compose_form.hashtag_warning": "Tento príspevok nebude zobrazený pod žiadným hashtagom, lebo nie je verejný. Iba verejné príspevky môžu byť nájdené podľa hashtagu.", - "compose_form.lock_disclaimer": "Váš účet nie je {locked}. Ktokoľvek vás môže sledovať a vidieť vaše príspevky pre sledujúcich.", + "compose_form.lock_disclaimer": "Váš účet nie je {locked}. Ktokoľvek vás môže sledovať a vidieť vaše príspevky pre sledujúce účty.", "compose_form.lock_disclaimer.lock": "zamknutý", "compose_form.placeholder": "Na čo práve myslíte?", "compose_form.poll.duration": "Trvanie ankety", @@ -199,6 +211,11 @@ "confirmations.delete_list.confirm": "Vymazať", "confirmations.delete_list.message": "Určite chcete tento zoznam trvalo vymazať?", "confirmations.delete_list.title": "Vymazať zoznam?", + "confirmations.discard_draft.confirm": "Zahodiť a pokračovať", + "confirmations.discard_draft.edit.cancel": "Pokračovať v úpravách", + "confirmations.discard_draft.edit.title": "Zahodiť zmeny v tvojom príspevku?", + "confirmations.discard_draft.post.cancel": "Pokračuj v rozpísanom", + "confirmations.discard_draft.post.title": "Zahodiť tvoj rozpísaný príspevok?", "confirmations.discard_edit_media.confirm": "Zahodiť", "confirmations.discard_edit_media.message": "Máte neuložené zmeny v popise alebo náhľade média, zahodiť ich aj tak?", "confirmations.follow_to_list.confirm": "Nasleduj a pridaj do zoznamu", @@ -207,15 +224,28 @@ "confirmations.logout.confirm": "Odhlásiť sa", "confirmations.logout.message": "Určite sa chcete odhlásiť?", "confirmations.logout.title": "Odhlásiť sa?", - "confirmations.missing_alt_text.secondary": "Odošli aj tak", + "confirmations.missing_alt_text.confirm": "Pridať opis", + "confirmations.missing_alt_text.message": "Váš príspevok obsahuje médiá bez opisu. Pridanie opisu sprístupňuje váš obsah viac ľuďom.", + "confirmations.missing_alt_text.secondary": "Aj tak uverejniť", + "confirmations.missing_alt_text.title": "Pridať opis?", "confirmations.mute.confirm": "Stíšiť", + "confirmations.private_quote_notify.cancel": "Späť na úpravu", + "confirmations.private_quote_notify.confirm": "Zverejni príspevok", + "confirmations.private_quote_notify.do_not_show_again": "Neukazuj mi túto spravu znova", + "confirmations.private_quote_notify.title": "Zdieľať z nasledovateľmi a spomenutými užívateľmi?", + "confirmations.quiet_post_quote_info.dismiss": "Nepripomínaj mi znova", + "confirmations.quiet_post_quote_info.got_it": "Mám to", "confirmations.redraft.confirm": "Vymazať a prepísať", "confirmations.redraft.message": "Určite chcete tento príspevok vymazať a prepísať? Prídete o jeho zdieľania a ohviezdičkovania a odpovede na pôvodný príspevok budú odlúčené.", "confirmations.redraft.title": "Vymazať a prepísať príspevok?", "confirmations.remove_from_followers.confirm": "Odstrániť nasledovateľa", - "confirmations.unfollow.confirm": "Prestať sledovať", - "confirmations.unfollow.message": "Určite chcete prestať sledovať {name}?", - "confirmations.unfollow.title": "Prestať sledovať užívateľa?", + "confirmations.revoke_quote.confirm": "Vymazať príspevok", + "confirmations.revoke_quote.message": "Tento úkon sa nedá navrátiť.", + "confirmations.revoke_quote.title": "Vymazať príspevok?", + "confirmations.unblock.confirm": "Odblokovať", + "confirmations.unblock.title": "Odblokovať {name}?", + "confirmations.unfollow.confirm": "Zrušiť sledovanie", + "confirmations.unfollow.title": "Prestať sledovať {name}?", "content_warning.hide": "Skryť príspevok", "content_warning.show": "Aj tak zobraziť", "content_warning.show_more": "Ukázať viac", @@ -246,6 +276,7 @@ "domain_pill.server": "Server", "domain_pill.their_server": "Ich digitálny domov, kde žijú všetky ich príspevky.", "domain_pill.username": "Používateľské meno", + "dropdown.empty": "Vyber možnosť", "embed.instructions": "Tento príspevok môžete pridať na svoju webovú stránku použitím tohto kódu.", "embed.preview": "Takto bude vyzerať:", "emoji_button.activity": "Aktivita", @@ -268,7 +299,7 @@ "empty_column.account_timeline": "Nie sú tu žiadne príspevky.", "empty_column.account_unavailable": "Profil nedostupný", "empty_column.blocks": "Nemáte blokované žiadne účty.", - "empty_column.bookmarked_statuses": "Ešte nemáte záložku v žiadnom príspevku. Keď si ju do nejakého príspevkuk pridáte, zobrazí sa tu.", + "empty_column.bookmarked_statuses": "Ešte nemáte záložku v žiadnom príspevku. Keď si ju do nejakého príspevku pridáte, zobrazí sa tu.", "empty_column.community": "Miesta časová os je prázdna. Napíšte niečo, aby to tu ožilo!", "empty_column.direct": "Ešte nemáte žiadne súkromné označenia. Keď nejaké pošlete alebo dostanete, zobrazí sa tu.", "empty_column.domain_blocks": "Žiadne domény ešte nie sú blokované.", @@ -294,6 +325,7 @@ "explore.trending_links": "Správy", "explore.trending_statuses": "Príspevky", "explore.trending_tags": "Hashtagy", + "featured_carousel.header": "{count, plural, one {Pripnutý príspevok} few {Pripnuté príspevky} many {} other {Pripnutých príspevkov}}", "filter_modal.added.context_mismatch_explanation": "Táto kategória filtrov sa nevzťahuje na kontext, v ktorom ste získali prístup k tomuto príspevku. Ak chcete, aby sa príspevok filtroval aj v tomto kontexte, budete musieť filter upraviť.", "filter_modal.added.context_mismatch_title": "Nesúlad kontextu!", "filter_modal.added.expired_explanation": "Platnosť tejto kategórie filtra vypršala, aby sa použila, je potrebné zmeniť dátum vypršania platnosti.", @@ -345,6 +377,7 @@ "getting_started.heading": "Začíname", "hashtag.admin_moderation": "Otvor moderovacie rozhranie pre #{name}", "hashtag.browse": "Prehľadávať príspevky pod #{hashtag}", + "hashtag.browse_from_account": "Prehľadávať príspevky od @{name} pre #{hashtag}", "hashtag.column_header.tag_mode.all": "a {additional}", "hashtag.column_header.tag_mode.any": "alebo {additional}", "hashtag.column_header.tag_mode.none": "bez {additional}", @@ -354,12 +387,12 @@ "hashtag.column_settings.tag_mode.any": "Ľubovoľné z týchto", "hashtag.column_settings.tag_mode.none": "Žiaden z týchto", "hashtag.column_settings.tag_toggle": "Vložte dodatočné hashtagy pre tento stĺpec", - "hashtag.counter_by_accounts": "{count, plural, one {{counter} prispievateľ} few {{counter} prispievatelia} many {{counter} prispievateľov} other {{counter} prispievateľov}}", + "hashtag.counter_by_accounts": "{count, plural, one {{counter} prispievajúci účet} few {{counter} prispievajúce účty} many {{counter} participants} other {{counter} prispievajúcich účtov}}", "hashtag.counter_by_uses": "{count, plural, one {{counter} príspevok} few {{counter} príspevky} many {{counter} príspevkov} other {{counter} príspevkov}}", "hashtag.counter_by_uses_today": "{count, plural, one {{counter} príspevok} few {{counter} príspevky} many {{counter} príspevkov} other {{counter} príspevkov}} dnes", "hashtag.follow": "Sledovať hashtag", "hashtag.mute": "Utlmiť #{hashtag}", - "hashtag.unfollow": "Prestať sledovať hashtag", + "hashtag.unfollow": "Zrušiť sledovanie hashtagu", "hashtags.and_other": "…a {count, plural, other {# ďalších}}", "hints.profiles.followers_may_be_missing": "Nasledovatelia tohto profilu môžu chýbať.", "hints.profiles.follows_may_be_missing": "Nasledovatelia tohto profilu môžu chýbať.", @@ -376,7 +409,7 @@ "home.show_announcements": "Zobraziť oznámenia", "ignore_notifications_modal.filter_instead": "Radšej triediť", "ignore_notifications_modal.filter_to_act_users": "Stále budeš môcť akceptovať, odmietnuť, alebo nahlásiť užívateľov", - "ignore_notifications_modal.filter_to_avoid_confusion": "Triedenie pomáha vyvarovať sa možnému zmäteniu", + "ignore_notifications_modal.filter_to_avoid_confusion": "Filtrovanie pomáha vyvarovať sa možnému zmäteniu", "ignore_notifications_modal.ignore": "Ignoruj upozornenia", "ignore_notifications_modal.limited_accounts_title": "Ignorovať oboznámenia z obmedzených účtov?", "ignore_notifications_modal.new_accounts_title": "Nevšímať si oznámenia z nových účtov?", @@ -384,6 +417,7 @@ "ignore_notifications_modal.not_following_title": "Nevšímať si oznámenia od ľudí, ktorých nenasleduješ?", "ignore_notifications_modal.private_mentions_title": "Nevšímať si oznámenia o nevyžiadaných súkromných spomínaniach?", "info_button.label": "Pomoc", + "info_button.what_is_alt_text": "

Čo je to opis?

Opis médií umožňuje ľuďom so zdravotným postihnutím alebo slabým pripojením porozumieť obsahu príspevku.

Zrozumiteľné, stručné a objektívne opisy sprístupňujú a vysvetľujú váš obsah väčšiemu počtu ľudí.

  • Spomeňte dôležité prvky
  • Zhrňte text na obrázkoch a fotkách
  • Píšte bežné vety
  • Vynechajte zbytočné informácie
  • Pri zložitých vizuáloch (napr. nákresoch či mapách) sa sústreďte na kľúčové vzorce a závery
", "interaction_modal.go": "Prejdi", "interaction_modal.no_account_yet": "Ešte nemáš účet?", "interaction_modal.on_another_server": "Na inom serveri", @@ -424,9 +458,10 @@ "keyboard_shortcuts.toggle_hidden": "Zobraziť/skryť text za varovaním o obsahu", "keyboard_shortcuts.toggle_sensitivity": "Zobraziť/skryť médiá", "keyboard_shortcuts.toot": "Vytvoriť nový príspevok", - "keyboard_shortcuts.translate": "preložiť príspevok", + "keyboard_shortcuts.translate": "Preložiť príspevok", "keyboard_shortcuts.unfocus": "Odísť z textového poľa", "keyboard_shortcuts.up": "Posunúť sa vyššie v zozname", + "learn_more_link.learn_more": "Zistiť viac", "lightbox.close": "Zatvoriť", "lightbox.next": "Ďalej", "lightbox.previous": "Späť", @@ -440,7 +475,7 @@ "lists.add_to_list": "Pridaj do zoznamu", "lists.add_to_lists": "Pridaj {name} do zoznamov", "lists.create": "Vytvor", - "lists.create_a_list_to_organize": "Vytvor nový zoznam pre spravovanie tvojej domovskej osi", + "lists.create_a_list_to_organize": "Vytvorte si nový zoznam na organizáciu svojho domovského kanála", "lists.create_list": "Vytvor zoznam", "lists.delete": "Vymazať zoznam", "lists.done": "Hotovo", @@ -450,7 +485,7 @@ "lists.find_users_to_add": "Nájdi užívateľov na pridanie", "lists.list_name": "Názov zoznamu", "lists.new_list_name": "Názov nového zoznamu", - "lists.no_lists_yet": "Ešte žiadne zoznamy.", + "lists.no_lists_yet": "Zatiaľ nemáte žiadne zoznamy.", "lists.no_members_yet": "Zatiaľ bez členov.", "lists.no_results_found": "Žiadne výsledky nenájdené.", "lists.remove_member": "Odstráň", @@ -474,8 +509,10 @@ "mute_modal.you_wont_see_mentions": "Neuvidíš príspevky, ktoré ho/ju spomínajú.", "mute_modal.you_wont_see_posts": "Stále uvidí tvoje príspevky, ale ty neuvidíš jeho/jej.", "navigation_bar.about": "O tomto serveri", + "navigation_bar.account_settings": "Heslo a zabezpečenie", "navigation_bar.administration": "Spravovanie", "navigation_bar.advanced_interface": "Otvoriť v pokročilom webovom rozhraní", + "navigation_bar.automated_deletion": "Automatické mazanie príspevkov", "navigation_bar.blocks": "Blokované účty", "navigation_bar.bookmarks": "Záložky", "navigation_bar.direct": "Súkromné označenia", @@ -484,13 +521,16 @@ "navigation_bar.filters": "Filtrované slová", "navigation_bar.follow_requests": "Žiadosti o sledovanie", "navigation_bar.followed_tags": "Sledované hashtagy", - "navigation_bar.follows_and_followers": "Sledovania a sledovatelia", + "navigation_bar.follows_and_followers": "Sledované a sledujúce účty", + "navigation_bar.import_export": "Import a export", "navigation_bar.lists": "Zoznamy", "navigation_bar.logout": "Odhlásiť sa", "navigation_bar.moderation": "Moderovanie", + "navigation_bar.more": "Viac", "navigation_bar.mutes": "Stíšené účty", "navigation_bar.opened_in_classic_interface": "Príspevky, účty a iné špeciálne stránky sú predvolene otvárané v klasickom webovom rozhraní.", "navigation_bar.preferences": "Nastavenia", + "navigation_bar.privacy_and_reach": "Súkromie a dosah", "navigation_bar.search": "Hľadať", "not_signed_in_indicator.not_signed_in": "Ak chcete získať prístup k tomuto zdroju, prihláste sa.", "notification.admin.report": "Účet {name} nahlásil {target}", @@ -501,24 +541,27 @@ "notification.favourite_pm": "{name} obľúbil/a tvoje súkromné spomenutie", "notification.follow": "{name} vás sleduje", "notification.follow_request": "{name} vás žiada sledovať", - "notification.label.mention": "Zmienka", - "notification.label.private_mention": "Súkromná zmienka", + "notification.label.mention": "Označenie", + "notification.label.private_mention": "Súkromné označenie", "notification.label.private_reply": "Súkromná odpoveď", + "notification.label.quote": "{name} citoval/a tvoj príspevok", "notification.label.reply": "Odpoveď", - "notification.mention": "Zmienka", + "notification.mention": "Označenie", "notification.mentioned_you": "{name} ťa spomenul/a", "notification.moderation-warning.learn_more": "Zisti viac", "notification.moderation_warning": "Dostal/a si varovanie od moderátora", "notification.moderation_warning.action_delete_statuses": "Niektoré z tvojich príspevkov boli odstránené.", "notification.moderation_warning.action_disable": "Tvoj účet bol vypnutý.", - "notification.moderation_warning.action_mark_statuses_as_sensitive": "Niektoré tvoje príspevky boli označené za chúlostivé.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Niektoré vaše príspevky boli označené ako citlivé.", "notification.moderation_warning.action_none": "Tvoj účet dostal upozornenie od moderátora.", - "notification.moderation_warning.action_sensitive": "Tvoje príspevky budú odteraz označované ako chúlostivé.", + "notification.moderation_warning.action_sensitive": "Vaše príspevky budú odteraz označované ako citlivé.", "notification.moderation_warning.action_silence": "Tvoj účet bol obmedzený.", "notification.moderation_warning.action_suspend": "Tvoj účet bol pozastavený.", "notification.own_poll": "Vaša anketa sa skončila", "notification.poll": "Anketa, v ktorej si hlasoval/a, skončila", + "notification.quoted_update": "{name} upravil/a príspevok, ktorý si citoval/a", "notification.reblog": "{name} zdieľa váš príspevok", + "notification.reblog.name_and_others_with_link": "{name} a {count, plural, one {# ďalší človek} few {# ďalší ľudia} many {#} other {# ďalších ľudí}} zdieľa váš príspevok", "notification.relationships_severance_event": "Stratené prepojenia s {name}", "notification.relationships_severance_event.account_suspension": "Správca z {from} pozastavil/a {target}, čo znamená, že od nich viac nemôžeš dostávať aktualizácie, alebo s nimi interaktovať.", "notification.relationships_severance_event.learn_more": "Zisti viac", @@ -546,6 +589,7 @@ "notifications.column_settings.mention": "Označenia:", "notifications.column_settings.poll": "Výsledky ankety:", "notifications.column_settings.push": "Upozornenia push", + "notifications.column_settings.quote": "Citácie:", "notifications.column_settings.reblog": "Zdieľania:", "notifications.column_settings.show": "Zobraziť v stĺpci", "notifications.column_settings.sound": "Prehrať zvuk", @@ -595,7 +639,7 @@ "onboarding.profile.save_and_continue": "Uložiť a pokračovať", "onboarding.profile.title": "Nastavenie profilu", "onboarding.profile.upload_avatar": "Nahrať profilový obrázok", - "onboarding.profile.upload_header": "Nahrať obrázok záhlavia profilu", + "onboarding.profile.upload_header": "Nahrať obrázok v záhlaví profilu", "password_confirmation.exceeds_maxlength": "Potvrdené heslo presahuje maximálnu dĺžku hesla", "password_confirmation.mismatching": "Zadané heslá sa nezhodujú", "picture_in_picture.restore": "Vrátiť späť", @@ -616,10 +660,16 @@ "privacy.private.short": "Sledovatelia", "privacy.public.long": "Ktokoľvek na Mastodone aj mimo neho", "privacy.public.short": "Verejné", + "privacy.quote.anyone": "{visibility}, hocikto môže citovať", + "privacy.quote.disabled": "{visibility}, citovanie nepovolené", + "privacy.quote.limited": "{visibility}, citovanie obmedzené", "privacy.unlisted.additional": "Presne ako verejné, s tým rozdielom, že sa príspevok nezobrazí v živých kanáloch, hashtagoch, objavovaní či vo vyhľadávaní na Mastodone, aj keď máte pre účet objaviteľnosť zapnutú.", "privacy.unlisted.short": "Tiché verejné", "privacy_policy.last_updated": "Posledná úprava {date}", "privacy_policy.title": "Pravidlá ochrany súkromia", + "quote_error.poll": "Citovanie ankiet nieje povolené.", + "quote_error.quote": "Je povolená iba jedna citácia súčasne.", + "quote_error.unauthorized": "Nemáš oprávnenie citovať tento príspevok.", "recommended": "Odporúčané", "refresh": "Obnoviť", "regeneration_indicator.please_stand_by": "Prosím, čakajte.", @@ -635,6 +685,7 @@ "relative_time.minutes": "{number} min", "relative_time.seconds": "{number} sek", "relative_time.today": "Dnes", + "remove_quote_hint.title": "Chceš vymazať svoju citáciu príspevku?", "reply_indicator.attachments": "{count, plural, one {# príloha} few {# prílohy} other {# príloh}}", "reply_indicator.cancel": "Zrušiť", "reply_indicator.poll": "Anketa", @@ -723,6 +774,7 @@ "status.admin_account": "Moderovať @{name}", "status.admin_domain": "Moderovať {domain}", "status.admin_status": "Moderovať príspevok", + "status.all_disabled": "Zdieľania a citácie sú vypnuté", "status.block": "Blokovať @{name}", "status.bookmark": "Pridať záložku", "status.cancel_reblog_private": "Zrušiť zdieľanie", @@ -736,7 +788,7 @@ "status.edit": "Upraviť", "status.edited": "Naposledy upravený {date}", "status.edited_x_times": "Upravený {count, plural, other {{count}×}}", - "status.favourite": "Ohviezdičkované", + "status.favourite": "Ohviezdičkovať", "status.filter": "Filtrovanie tohto príspevku", "status.history.created": "Vytvorené účtom {name} {date}", "status.history.edited": "Upravené účtom {name} {date}", @@ -750,13 +802,15 @@ "status.mute_conversation": "Stíšiť konverzáciu", "status.open": "Rozbaliť príspevok", "status.pin": "Pripnúť na profil", + "status.quote_policy_change": "Zmeňte, kto vás môže citovať", "status.read_more": "Čítaj ďalej", "status.reblog": "Zdieľať", + "status.reblog_or_quote": "Zdieľať alebo citovať", "status.reblogged_by": "{name} zdieľa", "status.reblogs.empty": "Nikto ešte tento príspevok nezdieľal. Keď tak niekto urobí, zobrazí sa to tu.", "status.redraft": "Vymazať a prepísať", "status.remove_bookmark": "Odstrániť záložku", - "status.remove_favourite": "Odstráň z obľúbených", + "status.remove_favourite": "Zrušiť ohviezdičkovanie", "status.replied_in_thread": "Odpovedal/a vo vlákne", "status.replied_to": "Odpoveď na {name}", "status.reply": "Odpovedať", @@ -808,5 +862,10 @@ "video.pause": "Pozastaviť", "video.play": "Prehrať", "video.volume_down": "Hlasitosť nadol", - "video.volume_up": "Hlasitosť nahor" + "video.volume_up": "Hlasitosť nahor", + "visibility_modal.header": "Viditeľnosť a interakcia", + "visibility_modal.quote_followers": "Iba sledujúce účty", + "visibility_modal.quote_label": "Kto vás môže citovať", + "visibility_modal.quote_nobody": "Iba ja", + "visibility_modal.quote_public": "Ktokoľvek" } diff --git a/app/javascript/mastodon/locales/sl.json b/app/javascript/mastodon/locales/sl.json index 9127605dbafed5..30c72c87f0b847 100644 --- a/app/javascript/mastodon/locales/sl.json +++ b/app/javascript/mastodon/locales/sl.json @@ -1,6 +1,7 @@ { "about.blocks": "Moderirani strežniki", "about.contact": "Stik:", + "about.default_locale": "Privzeto", "about.disclaimer": "Mastodon je prosto, odprtokodno programje in blagovna znamka podjetja Mastodon gGmbH.", "about.domain_blocks.no_reason_available": "Razlog ni na voljo", "about.domain_blocks.preamble": "Mastodon vam na splošno omogoča ogled vsebin in interakcijo z uporabniki z vseh drugih strežnikov v fediverzumu. Tu so navedene izjeme, ki jih postavlja ta strežnik.", @@ -8,6 +9,7 @@ "about.domain_blocks.silenced.title": "Omejeno", "about.domain_blocks.suspended.explanation": "Nobeni podatki s tega strežnika ne bodo obdelani, shranjeni ali izmenjani, zaradi česar je nemogoča kakršna koli interakcija ali komunikacija z uporabniki s tega strežnika.", "about.domain_blocks.suspended.title": "Suspendiran", + "about.language_label": "Jezik", "about.not_available": "Ti podatki še niso na voljo na tem strežniku.", "about.powered_by": "Decentraliziran družabni medij, ki ga poganja {mastodon}", "about.rules": "Pravila strežnika", @@ -24,18 +26,27 @@ "account.direct": "Zasebno omeni @{name}", "account.disable_notifications": "Ne obveščaj me več, ko ima @{name} novo objavo", "account.edit_profile": "Uredi profil", + "account.edit_profile_short": "Uredi", "account.enable_notifications": "Obvesti me, ko ima @{name} novo objavo", "account.endorse": "Izpostavi v profilu", + "account.familiar_followers_one": "Sledi {name1}", + "account.featured": "Izpostavljeni", + "account.featured.accounts": "Profili", + "account.featured.hashtags": "Ključniki", "account.featured_tags.last_status_at": "Zadnja objava {date}", "account.featured_tags.last_status_never": "Ni objav", "account.follow": "Sledi", "account.follow_back": "Sledi nazaj", + "account.follow_back_short": "Sledi nazaj", + "account.follow_request_cancel": "Prekliči zahtevo", + "account.follow_request_cancel_short": "Prekliči", "account.followers": "Sledilci", "account.followers.empty": "Nihče še ne sledi temu uporabniku.", "account.followers_counter": "{count, plural, one {{counter} sledilec} two {{counter} sledilca} few {{counter} sledilci} other {{counter} sledilcev}}", "account.following": "Sledim", "account.following_counter": "{count, plural, one {{counter} sleden} two {{counter} sledena} few {{counter} sledeni} other {{counter} sledenih}}", "account.follows.empty": "Ta uporabnik še ne sledi nikomur.", + "account.follows_you": "Vam sledi", "account.go_to_profile": "Pojdi na profil", "account.hide_reblogs": "Skrij izpostavitve od @{name}", "account.in_memoriam": "V spomin.", @@ -50,18 +61,19 @@ "account.mute_notifications_short": "Utišaj obvestila", "account.mute_short": "Utišaj", "account.muted": "Utišan", + "account.mutual": "Drug drugemu sledita", "account.no_bio": "Ni opisa.", "account.open_original_page": "Odpri izvirno stran", "account.posts": "Objave", "account.posts_with_replies": "Objave in odgovori", "account.report": "Prijavi @{name}", - "account.requested": "Čakanje na odobritev. Kliknite, da prekličete prošnjo za sledenje", "account.requested_follow": "{name} vam želi slediti", "account.share": "Deli profil osebe @{name}", "account.show_reblogs": "Pokaži izpostavitve osebe @{name}", "account.statuses_counter": "{count, plural, one {{counter} objava} two {{counter} objavi} few {{counter} objave} other {{counter} objav}}", "account.unblock": "Odblokiraj @{name}", "account.unblock_domain": "Odblokiraj domeno {domain}", + "account.unblock_domain_short": "Odblokiraj", "account.unblock_short": "Odblokiraj", "account.unendorse": "Ne vključi v profil", "account.unfollow": "Ne sledi več", @@ -91,25 +103,11 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Podaj opis za slabovidne ...", "alt_text_modal.done": "Opravljeno", "announcement.announcement": "Oznanilo", - "annual_report.summary.archetype.booster": "Lovec na trende", - "annual_report.summary.archetype.lurker": "Tiholazec", - "annual_report.summary.archetype.oracle": "Orakelj", - "annual_report.summary.archetype.pollster": "Anketar", - "annual_report.summary.archetype.replier": "Priljudnež", - "annual_report.summary.followers.followers": "sledilcev", - "annual_report.summary.followers.total": "skupaj {count}", - "annual_report.summary.here_it_is": "Tule je povzetek vašega leta {year}:", - "annual_report.summary.highlighted_post.by_favourites": "- najpriljubljenejša objava", - "annual_report.summary.highlighted_post.by_reblogs": "- največkrat izpostavljena objava", - "annual_report.summary.highlighted_post.by_replies": "- objava z največ odgovori", - "annual_report.summary.highlighted_post.possessive": "{name}", "annual_report.summary.most_used_app.most_used_app": "najpogosteje uporabljena aplikacija", "annual_report.summary.most_used_hashtag.most_used_hashtag": "največkrat uporabljen ključnik", - "annual_report.summary.most_used_hashtag.none": "Brez", "annual_report.summary.new_posts.new_posts": "nove objave", "annual_report.summary.percentile.text": "S tem ste se uvrstili med zgornjih uporabnikov domene {domain}.", "annual_report.summary.percentile.we_wont_tell_bernie": "Živi duši ne bomo povedali.", - "annual_report.summary.thanks": "Hvala, ker ste del Mastodona!", "attachments_list.unprocessed": "(neobdelano)", "audio.hide": "Skrij zvok", "block_modal.remote_users_caveat": "Strežnik {domain} bomo pozvali, naj spoštuje vašo odločitev. Kljub temu pa ni gotovo, da bo strežnik prošnjo upošteval, saj nekateri strežniki blokiranja obravnavajo drugače. Javne objave bodo morda še vedno vidne neprijavljenim uporabnikom.", @@ -151,6 +149,8 @@ "column.edit_list": "Uredi seznam", "column.favourites": "Priljubljeni", "column.firehose": "Viri v živo", + "column.firehose_local": "Vir v živo za ta strežnik", + "column.firehose_singular": "Vir v živo", "column.follow_requests": "Prošnje za sledenje", "column.home": "Domov", "column.list_members": "Upravljaj člane seznama", @@ -170,6 +170,7 @@ "community.column_settings.local_only": "Samo krajevno", "community.column_settings.media_only": "Samo predstavnosti", "community.column_settings.remote_only": "Samo oddaljeno", + "compose.error.blank_post": "Objava ne sme biti prazna", "compose.language.change": "Spremeni jezik", "compose.language.search": "Poišči jezike ...", "compose.published.body": "Objavljeno.", @@ -202,6 +203,8 @@ "confirmations.delete_list.confirm": "Izbriši", "confirmations.delete_list.message": "Ali ste prepričani, da želite trajno izbrisati ta seznam?", "confirmations.delete_list.title": "Želite izbrisati seznam?", + "confirmations.discard_draft.edit.cancel": "Nadaljuj z urejanjem", + "confirmations.discard_draft.post.cancel": "Nadaljuj na osnutku", "confirmations.discard_edit_media.confirm": "Opusti", "confirmations.discard_edit_media.message": "Spremenjenega opisa predstavnosti ali predogleda niste shranili. Želite spremembe kljub temu opustiti?", "confirmations.follow_to_list.confirm": "Sledi in dodaj na seznam", @@ -215,12 +218,24 @@ "confirmations.missing_alt_text.secondary": "Vseeno objavi", "confirmations.missing_alt_text.title": "Dodam nadomestno besedilo?", "confirmations.mute.confirm": "Utišaj", + "confirmations.private_quote_notify.cancel": "Nazaj k urejanju", + "confirmations.private_quote_notify.confirm": "Objavi objavo", + "confirmations.private_quote_notify.do_not_show_again": "Ne prikazuj mi več tega sporočila", + "confirmations.quiet_post_quote_info.dismiss": "Ne opominjaj me več", + "confirmations.quiet_post_quote_info.got_it": "Razumem", "confirmations.redraft.confirm": "Izbriši in preoblikuj", "confirmations.redraft.message": "Ali ste prepričani, da želite izbrisati to objavo in jo preoblikovati? Izkazi priljubljenosti in izpostavitve bodo izgubljeni, odgovori na izvirno objavo pa bodo osiroteli.", "confirmations.redraft.title": "Želite izbrisati in preoblikovati objavo?", + "confirmations.remove_from_followers.confirm": "Odstrani sledilca", + "confirmations.remove_from_followers.title": "Ali želite odstraniti sledilca?", + "confirmations.revoke_quote.confirm": "Odstrani objavo", + "confirmations.revoke_quote.message": "Tega dejanja ni možno povrniti.", + "confirmations.revoke_quote.title": "Ali želite odstraniti objavo?", + "confirmations.unblock.confirm": "Odblokiraj", + "confirmations.unblock.title": "Ali želite odblokirati {name}?", "confirmations.unfollow.confirm": "Ne sledi več", - "confirmations.unfollow.message": "Ali ste prepričani, da ne želite več slediti {name}?", - "confirmations.unfollow.title": "Želite nehati spremljati uporabnika?", + "confirmations.unfollow.title": "Ali želite prenehati slediti {name}?", + "confirmations.withdraw_request.confirm": "Umakni zahtevo", "content_warning.hide": "Skrij objavo", "content_warning.show": "Vseeno pokaži", "content_warning.show_more": "Pokaži več", @@ -307,6 +322,7 @@ "errors.unexpected_crash.copy_stacktrace": "Kopiraj sled sklada na odložišče", "errors.unexpected_crash.report_issue": "Prijavi težavo", "explore.suggested_follows": "Ljudje", + "explore.title": "V trendu", "explore.trending_links": "Novice", "explore.trending_statuses": "Objave", "explore.trending_tags": "Ključniki", @@ -374,7 +390,10 @@ "hashtag.counter_by_accounts": "{count, plural, one {{counter} udeleženec} two {{counter} udeleženca} few {{counter} udeležencev} other {{counter} udeležencev}}", "hashtag.counter_by_uses": "{count, plural, one {{counter} objava} two {{counter} posts} few {{counter} objavi} other {{counter} objav}}", "hashtag.counter_by_uses_today": "{count, plural, one {{counter} objava} two {{counter} objavi} few {{counter} objav} other {{counter} objav}}", + "hashtag.feature": "Izpostavi v profilu", "hashtag.follow": "Sledi ključniku", + "hashtag.mute": "Utišaj #{hashtag}", + "hashtag.unfeature": "Ne izpostavljaj v profilu", "hashtag.unfollow": "Nehaj slediti ključniku", "hashtags.and_other": "… in še {count, plural, other {#}}", "hints.profiles.followers_may_be_missing": "Sledilci za ta profil morda manjkajo.", @@ -383,6 +402,7 @@ "hints.profiles.see_more_followers": "Pokaži več sledilcev na {domain}", "hints.profiles.see_more_follows": "Pokaži več sledenih ljudi na zbirališču {domain}", "hints.profiles.see_more_posts": "Pokaži več objav na {domain}", + "home.column_settings.show_quotes": "Pokaži citate", "home.column_settings.show_reblogs": "Pokaži izpostavitve", "home.column_settings.show_replies": "Pokaži odgovore", "home.hide_announcements": "Skrij obvestila", @@ -407,6 +427,7 @@ "interaction_modal.no_account_yet": "Še nimate računa?", "interaction_modal.on_another_server": "Na drugem strežniku", "interaction_modal.on_this_server": "Na tem strežniku", + "interaction_modal.title": "Za nadaljevanje se prijavite", "interaction_modal.username_prompt": "Npr. {example}", "intervals.full.days": "{number, plural, one {# dan} two {# dni} few {# dni} other {# dni}}", "intervals.full.hours": "{number, plural, one {# ura} two {# uri} few {# ure} other {# ur}}", @@ -435,6 +456,7 @@ "keyboard_shortcuts.open_media": "Odpri predstavnost", "keyboard_shortcuts.pinned": "Odpri seznam pripetih objav", "keyboard_shortcuts.profile": "Odpri avtorjev profil", + "keyboard_shortcuts.quote": "Citiraj objavo", "keyboard_shortcuts.reply": "Odgovori na objavo", "keyboard_shortcuts.requests": "Odpri seznam s prošnjami za sledenje", "keyboard_shortcuts.search": "Pozornost na iskalno vrstico", @@ -446,6 +468,8 @@ "keyboard_shortcuts.translate": "za prevod objave", "keyboard_shortcuts.unfocus": "Odstrani pozornost z območja za sestavljanje besedila/iskanje", "keyboard_shortcuts.up": "Premakni navzgor po seznamu", + "learn_more_link.got_it": "Razumem", + "learn_more_link.learn_more": "Več o tem", "lightbox.close": "Zapri", "lightbox.next": "Naslednji", "lightbox.previous": "Prejšnji", @@ -495,8 +519,10 @@ "mute_modal.you_wont_see_mentions": "Objav, ki jih omenjajo, ne boste videli.", "mute_modal.you_wont_see_posts": "Še vedno vidijo vaše objave, vi pa ne njihovih.", "navigation_bar.about": "O Mastodonu", + "navigation_bar.account_settings": "Geslo in varnost", "navigation_bar.administration": "Upravljanje", "navigation_bar.advanced_interface": "Odpri v naprednem spletnem vmesniku", + "navigation_bar.automated_deletion": "Samodejno brisanje objav", "navigation_bar.blocks": "Blokirani uporabniki", "navigation_bar.bookmarks": "Zaznamki", "navigation_bar.direct": "Zasebne omembe", @@ -506,12 +532,15 @@ "navigation_bar.follow_requests": "Prošnje za sledenje", "navigation_bar.followed_tags": "Sledeni ključniki", "navigation_bar.follows_and_followers": "Sledenja in sledilci", + "navigation_bar.import_export": "Uvoz in izvoz", "navigation_bar.lists": "Seznami", "navigation_bar.logout": "Odjava", "navigation_bar.moderation": "Moderiranje", + "navigation_bar.more": "Več", "navigation_bar.mutes": "Utišani uporabniki", "navigation_bar.opened_in_classic_interface": "Objave, računi in druge specifične strani se privzeto odprejo v klasičnem spletnem vmesniku.", "navigation_bar.preferences": "Nastavitve", + "navigation_bar.privacy_and_reach": "Zasebnost in dosegljivost", "navigation_bar.search": "Iskanje", "not_signed_in_indicator.not_signed_in": "Za dostop do tega vira se morate prijaviti.", "notification.admin.report": "{name} je prijavil/a {target}", @@ -534,6 +563,7 @@ "notification.label.mention": "Omemba", "notification.label.private_mention": "Zasebna omemba", "notification.label.private_reply": "Zasebni odgovor", + "notification.label.quote": "{name} je citiral/a vašo objavo", "notification.label.reply": "Odgovori", "notification.mention": "Omemba", "notification.mentioned_you": "{name} vas je omenil/a", @@ -591,6 +621,7 @@ "notifications.column_settings.mention": "Omembe:", "notifications.column_settings.poll": "Rezultati ankete:", "notifications.column_settings.push": "Potisna obvestila", + "notifications.column_settings.quote": "Citati:", "notifications.column_settings.reblog": "Izpostavitve:", "notifications.column_settings.show": "Pokaži v stolpcu", "notifications.column_settings.sound": "Predvajaj zvok", @@ -685,6 +716,7 @@ "relative_time.minutes": "{number} m", "relative_time.seconds": "{number} s", "relative_time.today": "danes", + "remove_quote_hint.button_label": "Razumem", "reply_indicator.attachments": "{count, plural, one {# priloga} two {# prilogi} few {# priloge} other {# prilog}}", "reply_indicator.cancel": "Prekliči", "reply_indicator.poll": "Anketa", @@ -738,6 +770,7 @@ "report_notification.categories.violation": "Kršitev pravila", "report_notification.categories.violation_sentence": "kršitev pravila", "report_notification.open": "Odpri prijavo", + "search.clear": "Počisti iskanje", "search.no_recent_searches": "Ni nedavnih iskanj", "search.placeholder": "Iskanje", "search.quick_action.account_search": "Profili, ki se ujemajo z {x}", @@ -779,9 +812,13 @@ "status.bookmark": "Dodaj med zaznamke", "status.cancel_reblog_private": "Prekliči izpostavitev", "status.cannot_reblog": "Te objave ni mogoče izpostaviti", + "status.contains_quote": "Vsebuje citat", + "status.context.retry": "Poskusi znova", + "status.context.show": "Pokaži", "status.continued_thread": "Nadaljevanje niti", "status.copy": "Kopiraj povezavo do objave", "status.delete": "Izbriši", + "status.delete.success": "Objava je izbrisana", "status.detailed_status": "Podroben pogled pogovora", "status.direct": "Zasebno omeni @{name}", "status.direct_indicator": "Zasebna omemba", @@ -790,7 +827,6 @@ "status.edited_x_times": "Urejeno {count, plural, one {#-krat} two {#-krat} few {#-krat} other {#-krat}}", "status.embed": "Pridobite kodo za vgradnjo", "status.favourite": "Priljubljen/a", - "status.favourites": "{count, plural, one {priljubitev} two {priljubitvi} few {priljubitve} other {priljubitev}}", "status.filter": "Filtriraj to objavo", "status.history.created": "{name}: ustvarjeno {date}", "status.history.edited": "{name}: urejeno {date}", @@ -804,14 +840,22 @@ "status.mute_conversation": "Utišaj pogovor", "status.open": "Razširi to objavo", "status.pin": "Pripni na profil", + "status.quote.cancel": "Prekliči citat", + "status.quote_error.filtered": "Skrito zaradi enega od vaših filtrov", + "status.quote_error.limited_account_hint.action": "Vseeno pokaži", + "status.quote_error.limited_account_hint.title": "Ta račun so moderatorji {domain} skrili.", + "status.quote_error.not_available": "Objava ni na voljo", + "status.quote_followers_only": "Samo sledilci lahko citirajo to objavo", + "status.quote_policy_change": "Spremenite, kdo lahko citira", + "status.quote_private": "Zasebnih objav ni možno citirati", "status.read_more": "Preberi več", "status.reblog": "Izpostavi", "status.reblogged_by": "{name} je izpostavil/a", - "status.reblogs": "{count, plural, one {izpostavitev} two {izpostavitvi} few {izpostavitve} other {izpostavitev}}", "status.reblogs.empty": "Nihče še ni izpostavil te objave. Ko se bo to zgodilo, se bodo pojavile tukaj.", "status.redraft": "Izbriši in preoblikuj", "status.remove_bookmark": "Odstrani zaznamek", "status.remove_favourite": "Odstrani iz priljubljenih", + "status.remove_quote": "Odstrani", "status.replied_in_thread": "Odgovor iz niti", "status.replied_to": "Odgovoril/a {name}", "status.reply": "Odgovori", @@ -832,7 +876,10 @@ "subscribed_languages.save": "Shrani spremembe", "subscribed_languages.target": "Spremeni naročene jezike za {target}", "tabs_bar.home": "Domov", + "tabs_bar.menu": "Meni", "tabs_bar.notifications": "Obvestila", + "tabs_bar.publish": "Nova objava", + "tabs_bar.search": "Išči", "terms_of_service.effective_as_of": "Veljavno od {date}", "terms_of_service.title": "Pogoji uporabe", "terms_of_service.upcoming_changes_on": "Spremembe začnejo veljati {date}", @@ -866,6 +913,19 @@ "video.expand": "Razširi video", "video.fullscreen": "Celozaslonski način", "video.hide": "Skrij video", + "video.mute": "Utišaj", "video.pause": "Premor", - "video.play": "Predvajaj" + "video.play": "Predvajaj", + "video.skip_backward": "Preskoči nazaj", + "video.skip_forward": "Preskoči naprej", + "video.unmute": "Odtišaj", + "video.volume_down": "Zmanjšaj glasnost", + "video.volume_up": "Povečaj glasnost", + "visibility_modal.header": "Vidnost in interakcija", + "visibility_modal.privacy_label": "Vidnost", + "visibility_modal.quote_followers": "Samo sledilci", + "visibility_modal.quote_label": "Kdo lahko citira", + "visibility_modal.quote_nobody": "Samo jaz", + "visibility_modal.quote_public": "Vsi", + "visibility_modal.save": "Shrani" } diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json index 56db9a5ac3645c..0c8e1fc071f17c 100644 --- a/app/javascript/mastodon/locales/sq.json +++ b/app/javascript/mastodon/locales/sq.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Resht së njoftuari mua, kur poston @{name}", "account.domain_blocking": "Bllokim përkatësie", "account.edit_profile": "Përpunoni profilin", + "account.edit_profile_short": "Përpunojeni", "account.enable_notifications": "Njoftomë, kur poston @{name}", "account.endorse": "Pasqyrojeni në profil", "account.familiar_followers_many": "Ndjekur nga {name1}, {name2} dhe {othersCount, plural, one {një tjetër që njihni} other {# të tjerë që njihni}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Pa postime", "account.follow": "Ndiqeni", "account.follow_back": "Ndiqe gjithashtu", + "account.follow_back_short": "Ndiqe gjithashtu", + "account.follow_request": "Kërkoni ta ndiqni", + "account.follow_request_cancel": "Anuloje kërkesën", + "account.follow_request_cancel_short": "Anuloje", + "account.follow_request_short": "Kërkoje", "account.followers": "Ndjekës", "account.followers.empty": "Këtë përdorues ende s’e ndjek kush.", "account.followers_counter": "{count, plural, one {{counter} ndjekës} other {{counter} ndjekës}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Mesazhe dhe përgjigje", "account.remove_from_followers": "Hiqe {name} nga ndjekësit", "account.report": "Raportojeni @{name}", - "account.requested": "Në pritje të miratimit. Që të anuloni kërkesën për ndjekje, klikojeni", "account.requested_follow": "{name} ka kërkuar t’ju ndjekë", "account.requests_to_follow_you": "Kërkesa për t’ju ndjekur", "account.share": "Ndajeni profilin e @{name} me të tjerët", @@ -108,20 +113,44 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Përshkruajeni këtë për persona me mangësi shikimi…", "alt_text_modal.done": "U bë", "announcement.announcement": "Lajmërim", - "annual_report.summary.followers.followers": "ndjekës", - "annual_report.summary.followers.total": "{count} gjithsej", - "annual_report.summary.here_it_is": "Ja {year} juaj e shqyrtuar:", - "annual_report.summary.highlighted_post.by_favourites": "potimi më i parapëlqyer", - "annual_report.summary.highlighted_post.by_reblogs": "postimi me më shumë përforcime", - "annual_report.summary.highlighted_post.by_replies": "postimi me më tepër përgjigje", - "annual_report.summary.highlighted_post.possessive": "nga {name}", + "annual_report.announcement.action_dismiss": "Jo, faleminderit", + "annual_report.nav_item.badge": "E re", + "annual_report.shared_page.donate": "Dhuroni", + "annual_report.shared_page.footer": "Hartuar me {heart} nga ekipi i Mastodon-it", + "annual_report.shared_page.footer_server_info": "{username} përdor {domain}, një nga bashkësitë e shumta të bazuara në Mastodon.", + "annual_report.summary.archetype.booster.name": "Harkëtari", + "annual_report.summary.archetype.die_drei_fragezeichen": "???", + "annual_report.summary.archetype.lurker.desc_public": "E dimë se {name} qe vërdallë, diku, duke shijuar Mastodon-in në mënyrën e vet të qetë.", + "annual_report.summary.archetype.lurker.desc_self": "E dimë se qetë vërdallë, diku, duke shijuar Mastodon-in në mënyrën tuaj të qetë.", + "annual_report.summary.archetype.lurker.name": "Stoiku", + "annual_report.summary.archetype.oracle.desc_public": "{name} krijoi postime të reja, më shumë se përgjigje, duke e mbajtur Mastodon-in të freskët dhe me sytë nga ardhmja.", + "annual_report.summary.archetype.oracle.desc_self": "Krijuat postime të reja, më shumë se përgjigje, duke e mbajtur Mastodon-in të freskët dhe me sytë nga e ardhmja.", + "annual_report.summary.archetype.oracle.name": "Orakulli", + "annual_report.summary.archetype.pollster.desc_public": "{name} krijoi më tepër pyetësorë se sa lloje të tjera postimesh, duke kultivuar kureshtjen në Mastodon.", + "annual_report.summary.archetype.pollster.desc_self": "Krijuat më tepër pyetësorë, se sa lloje të tjera postimesh, duke kultivuar kureshtjen në Mastodon.", + "annual_report.summary.archetype.replier.desc_public": "{name} u përgjigj shpesh te postime të njerëzve të tjerë, duke pjalmuar Mastodon-in me diskutime të reja.", + "annual_report.summary.archetype.replier.desc_self": "U përgjigjët shpesh te postime të njerëzve të tjerë, duke pjalmuar Mastodon-in me diskutime të reja.", + "annual_report.summary.archetype.replier.name": "Flutura", + "annual_report.summary.archetype.reveal": "Zbulo me se ngjaj", + "annual_report.summary.archetype.reveal_description": "Faleminderit për qenien pjesë e Mastodon-it! Koha për të mësuar cilin model trupëzuat gjatë {year}.", + "annual_report.summary.archetype.title_public": "Modeli për {name}", + "annual_report.summary.archetype.title_self": "Modeli juaj", + "annual_report.summary.close": "Mbylle", + "annual_report.summary.copy_link": "Kopjoji lidhjen", + "annual_report.summary.followers.new_followers": "{count, plural, one {ndjekës i ri} other {ndjekës të rinj}}", + "annual_report.summary.highlighted_post.boost_count": "Ky postim qe përforcuar {count, plural, one {një herë} other {# herë}}.", + "annual_report.summary.highlighted_post.favourite_count": "Ky postim u vu si i parapëlqyer {count, plural, one {një herë} other {# herë}}.", + "annual_report.summary.highlighted_post.reply_count": "Ky postim pati {count, plural, one {një përgjigje} other {# përgjigje}}.", + "annual_report.summary.highlighted_post.title": "Postimi më popullor", "annual_report.summary.most_used_app.most_used_app": "aplikacioni më i përdorur", "annual_report.summary.most_used_hashtag.most_used_hashtag": "hashtag-u më i përdorur", - "annual_report.summary.most_used_hashtag.none": "Asnjë", + "annual_report.summary.most_used_hashtag.used_count": "Këtë hashtag e përfshitë në {count, plural, one {një postim} other {# postime}}.", + "annual_report.summary.most_used_hashtag.used_count_public": "{name} përfshiu këtë hashtag në {count, plural, one {një postim} other {# postime}}.", "annual_report.summary.new_posts.new_posts": "postime të reja", "annual_report.summary.percentile.text": "Kjo ju vendos te kryesues të përdoruesve të {domain}.", "annual_report.summary.percentile.we_wont_tell_bernie": "Nuk do t’ia themi Bernit.", - "annual_report.summary.thanks": "Faleminderit që jeni pjesë e Mastodon-it!", + "annual_report.summary.share_elsewhere": "Ndajeni me të tjerë gjetkë", + "annual_report.summary.share_on_mastodon": "Ndajeni në Mastodon me të tjerë", "attachments_list.unprocessed": "(e papërpunuar)", "audio.hide": "Fshihe audion", "block_modal.remote_users_caveat": "Do t’i kërkojmë shërbyesit {domain} të respektojë vendimin tuaj. Por, pajtimi s’është i garantuar, ngaqë disa shërbyes mund t’i trajtojnë ndryshe bllokimet. Psotimet publike mundet të jenë ende të dukshme për përdorues pa bërë hyrje në llogari.", @@ -147,6 +176,8 @@ "bundle_modal_error.close": "Mbylle", "bundle_modal_error.message": "Diç shkoi ters, teksa ngarkohej kjo skenë.", "bundle_modal_error.retry": "Riprovoni", + "carousel.current": "Diapozitivi {current, number} / {max, number}", + "carousel.slide": "Diapozitivi {current, number} nga {max, number} gjithsej", "closed_registrations.other_server_instructions": "Ngaqë Mastodon-i është i decentralizuar, mund të krijoni një llogari në një tjetër shërbyes dhe prapë të ndëveproni me këtë këtu.", "closed_registrations_modal.description": "Krijimi i një llogarie te {domain} aktualisht është i pamundur, por kini parasysh se s’keni nevojë për një llogari posaçërisht në {domain} që të përdorni Mastodon-in.", "closed_registrations_modal.find_another_server": "Gjeni shërbyes tjetër", @@ -163,6 +194,8 @@ "column.edit_list": "Përpunoni listën", "column.favourites": "Të parapëlqyer", "column.firehose": "Prurje “live”", + "column.firehose_local": "Prurje “live” për këtë shërbyes", + "column.firehose_singular": "Prurje “live”", "column.follow_requests": "Kërkesa për ndjekje", "column.home": "Kreu", "column.list_members": "Administroni anëtarë liste", @@ -182,6 +215,7 @@ "community.column_settings.local_only": "Vetëm vendore", "community.column_settings.media_only": "Vetëm Media", "community.column_settings.remote_only": "Vetëm të largëta", + "compose.error.blank_post": "Postimi s’mund të jetë i zbrazët.", "compose.language.change": "Ndryshoni gjuhën", "compose.language.search": "Kërkoni te gjuhët…", "compose.published.body": "Postimi u botua.", @@ -234,15 +268,30 @@ "confirmations.missing_alt_text.secondary": "Postoje, sido qoftë", "confirmations.missing_alt_text.title": "Të shtohet tekst alternativ?", "confirmations.mute.confirm": "Heshtoje", + "confirmations.private_quote_notify.cancel": "Mbrapsht te përpunimi", + "confirmations.private_quote_notify.confirm": "Botoje postimin", + "confirmations.private_quote_notify.do_not_show_again": "Mos ma shfaq prapë këtë mesazh", + "confirmations.private_quote_notify.message": "Personi që citoni dhe përmendje të tjera do të njoftohen dhe do të jenë në gjendje të shohin postimin tuaj, edhe pse nuk ju ndjekin.", + "confirmations.private_quote_notify.title": "T’u shfaqet ndjekësve dhe përdoruesve të përmendur?", + "confirmations.quiet_post_quote_info.dismiss": "Mos ma kujto më", + "confirmations.quiet_post_quote_info.got_it": "E mora vesh", + "confirmations.quiet_post_quote_info.message": "Kur citoni një postim publik të heshtuar, postimi juaj do të kalohet i fshehur te rrjedha kohore e gjërave në modë.", + "confirmations.quiet_post_quote_info.title": "Citim postimesh publikë të heshtuar", "confirmations.redraft.confirm": "Fshijeni & rihartojeni", "confirmations.redraft.message": "Jeni i sigurt se doni të fshihet kjo gjendje dhe të rihartohet? Të parapëlqyerit dhe përforcimet do të humbin, ndërsa përgjigjet te postimi origjinal do të bëhen jetime.", "confirmations.redraft.title": "Të fshihet & riharothet postimi?", "confirmations.remove_from_followers.confirm": "Hiqe ndjekësin", "confirmations.remove_from_followers.message": "{name} do të reshtë së ndjekuri ju. Jeni i sigurt se doni të vazhdohet?", "confirmations.remove_from_followers.title": "Të hiqet ndjekësi?", + "confirmations.revoke_quote.confirm": "Hiqe postimin", + "confirmations.revoke_quote.message": "Ky veprim s’mund të zhbëhet.", + "confirmations.revoke_quote.title": "Të hiqet postimi?", + "confirmations.unblock.confirm": "Zhbllokoje", + "confirmations.unblock.title": "Të zhbllojohet {name}?", "confirmations.unfollow.confirm": "Resht së ndjekuri", - "confirmations.unfollow.message": "Jeni i sigurt se doni të mos ndiqet më {name}?", - "confirmations.unfollow.title": "Të ndalet ndjekja e përdoruesit?", + "confirmations.unfollow.title": "Të ndalet ndjekja për {name}?", + "confirmations.withdraw_request.confirm": "Tërhiqeni mbrapsht kërkesën", + "confirmations.withdraw_request.title": "Të tërhiqet mbrapsht kërkesa për ndjeken e {name}?", "content_warning.hide": "Fshihe postimin", "content_warning.show": "Shfaqe, sido qoftë", "content_warning.show_more": "Shfaq më tepër", @@ -284,6 +333,7 @@ "domain_pill.your_handle": "Targa juaj:", "domain_pill.your_server": "Shtëpia juaj dixhitale, kur gjenden krejt postimet tuaja. S’ju pëlqen kjo këtu? Shpërngulni shërbyes kur të doni dhe sillni edhe ndjekësit tuaj.", "domain_pill.your_username": "Identifikuesi juja unik në këtë shërbyes. Është e mundur të gjenden përdorues me të njëjtin emër përdoruesi në shërbyes të ndryshëm.", + "dropdown.empty": "Përzgjidhni një mundësi", "embed.instructions": "Trupëzojeni këtë gjendje në sajtin tuaj duke kopjuar kodin më poshtë.", "embed.preview": "Ja si do të duket:", "emoji_button.activity": "Veprimtari", @@ -312,6 +362,7 @@ "empty_column.bookmarked_statuses": "S’keni faqeruajtur ende ndonjë mesazh. Kur faqeruani një të tillë, ai do të shfaqet këtu.", "empty_column.community": "Rrjedha kohore vendore është e zbrazët. Shkruani diçka publikisht që t’i hyhet valles!", "empty_column.direct": "S’keni ende ndonjë përmendje private. Kur dërgoni ose merrni një të tillë, do të shfaqet këtu.", + "empty_column.disabled_feed": "Kjo prurje është çaktivizuar nga përgjegjësit e shërbyesit tuaj.", "empty_column.domain_blocks": "Ende s’ka përkatësi të fshehura.", "empty_column.explore_statuses": "Asgjë në modë tani. Kontrolloni më vonë!", "empty_column.favourited_statuses": "S’keni ende ndonjë postim të parapëlqyer. Kur të parapëlqeni një të tillë, do të shfaqet këtu.", @@ -325,6 +376,7 @@ "empty_column.notification_requests": "Gjithçka si duhet! S’ka ç’bëhet këtu. Kur merrni njoftime të reja, do të shfaqen këtu, në përputhje me rregullimet tuaja.", "empty_column.notifications": "Ende s’keni ndonjë njoftim. Ndërveproni me të tjerët që të nisë biseda.", "empty_column.public": "S’ka gjë këtu! Shkruani diçka publikisht, ose ndiqni dorazi përdorues prej instancash të tjera, që kjo të mbushet", + "error.no_hashtag_feed_access": "Që të shihni dhe ndiqni hashtag-ë, regjistrohuni, ose bëni hyrjen.", "error.unexpected_crash.explanation": "Për shkak të një të mete në kodin tonë ose të një problemi përputhshmërie të shfletuesit, kjo faqe s’mund të shfaqet saktë.", "error.unexpected_crash.explanation_addons": "Kjo faqe s’u shfaq dot saktë. Ky gabim ka gjasa të jetë shkaktuar nga një shtesë shfletuesi ose një mjet përkthimi të automatizuar.", "error.unexpected_crash.next_steps": "Provoni të freskoni faqen. Nëse kjo s’bën punë, mundeni ende të jeni në gjendje të përdorni Mastodon-in që nga një shfletues tjetër ose nga ndonjë aplikacion origjinal prej projektit.", @@ -336,11 +388,9 @@ "explore.trending_links": "Lajme", "explore.trending_statuses": "Postime", "explore.trending_tags": "Hashtagë", + "featured_carousel.current": "Postimi {current, number} / {max, number}", "featured_carousel.header": "{count, plural, one {Postim i Fiksuar} other {Postime të Fiksuar}}", - "featured_carousel.next": "Pasuesi", - "featured_carousel.post": "Postim", - "featured_carousel.previous": "I mëparshmi", - "featured_carousel.slide": "{index} nga {total}", + "featured_carousel.slide": "Postimi {current, number} nga {max, number} gjithsej", "filter_modal.added.context_mismatch_explanation": "Kjo kategori filtrash nuk aplikohet për kontekstin nën të cilin po merreni me këtë postim. Nëse doni që postimi të filtrohet edhe në këtë kontekst, do t’ju duhet të përpunoni filtrin.", "filter_modal.added.context_mismatch_title": "Mospërputhje kontekstesh!", "filter_modal.added.expired_explanation": "Kjo kategori filtrash ka skaduar, do t’ju duhet të ndryshoni datën e skadimit për të, pa të aplikohet.", @@ -383,6 +433,9 @@ "follow_suggestions.who_to_follow": "Cilët të ndiqen", "followed_tags": "Hashtag-ë të ndjekur", "footer.about": "Mbi", + "footer.about_mastodon": "Mbi Mastodon-in", + "footer.about_server": "Mbi {domain}", + "footer.about_this_server": "Mbi", "footer.directory": "Drejtori profilesh", "footer.get_app": "Merreni aplikacionin", "footer.keyboard_shortcuts": "Shkurtore tastiere", @@ -440,10 +493,12 @@ "ignore_notifications_modal.private_mentions_title": "Të shpërfillen njoftime nga Përmendje Private të pakërkuara?", "info_button.label": "Ndihmë", "info_button.what_is_alt_text": "

Ç’është teksti alternativ?

Teksti alternativ jep përshkrime figurash për persona me mangësi në të parët, lidhje me gjerësi bande të ulët, ose për ata që duan kontekst shtesë.

Mund të përmirësoni përdorimin nga persona me aftësi të kufizuara dhe kuptimin për këto, duke shkruar tekst alternativ të qartë, konciz dhe objektiv.

  • Rrokni elementët e rëndësishëm
  • Përmblidhni tekst në figura
  • Përdorni strukturë të rregullt fjalish
  • Shmangni përsëritje informacioni
  • Në aspekte pamore të ndërlikuara (fjala vjen, diagrame ose harta) përqendrohuni te prirje dhe gjetje gjërash kyçe
", + "interaction_modal.action": "Që të ndërveproni me postimin nga {name}, lypset të bëni hyrjen në llogarinë tuaj, ose në çfarëdo shërbyesi Mastodon që përdorni.", "interaction_modal.go": "Shko", "interaction_modal.no_account_yet": "S’keni ende një llogari?", "interaction_modal.on_another_server": "Në një tjetër shërbyes", "interaction_modal.on_this_server": "Në këtë shërbyes", + "interaction_modal.title": "Që të vazhdohet, bëni hyrjen", "interaction_modal.username_prompt": "P.sh., {example}", "intervals.full.days": "{number, plural, one {# ditë} other {# ditë}}", "intervals.full.hours": "{number, plural, one {# orë} other {# orë}}", @@ -464,6 +519,7 @@ "keyboard_shortcuts.home": "Për hapje rrjedhe kohore vetjake", "keyboard_shortcuts.hotkey": "Tast përkatës", "keyboard_shortcuts.legend": "Për shfaqje të kësaj legjende", + "keyboard_shortcuts.load_more": "Kaloje fokusin te butoni “Ngarko më tepër”", "keyboard_shortcuts.local": "Për hapje rrjedhe kohore vendore", "keyboard_shortcuts.mention": "Për përmendje të autorit", "keyboard_shortcuts.muted": "Për hapje liste përdoruesish të heshtuar", @@ -472,6 +528,7 @@ "keyboard_shortcuts.open_media": "Për hapje mediash", "keyboard_shortcuts.pinned": "Për hapje liste mesazhesh të fiksuar", "keyboard_shortcuts.profile": "Për hapje të profilit të autorit", + "keyboard_shortcuts.quote": "Citoni postim", "keyboard_shortcuts.reply": "Për t’iu përgjigjur një postimi", "keyboard_shortcuts.requests": "Për hapje liste kërkesash për ndjekje", "keyboard_shortcuts.search": "Për kalim fokusi te kërkimi", @@ -480,9 +537,12 @@ "keyboard_shortcuts.toggle_hidden": "Për shfaqje/fshehje teksti pas CW", "keyboard_shortcuts.toggle_sensitivity": "Për shfaqje/fshehje mediash", "keyboard_shortcuts.toot": "Për të filluar një mesazh të ri", + "keyboard_shortcuts.top": "Shpjere në krye të listës", "keyboard_shortcuts.translate": "për të përkthyer një postim", "keyboard_shortcuts.unfocus": "Për heqjen e fokusit nga fusha e hartimit të mesazheve apo kërkimeve", "keyboard_shortcuts.up": "Për ngjitje sipër nëpër listë", + "learn_more_link.got_it": "E mora vesh", + "learn_more_link.learn_more": "Mësoni më tepër", "lightbox.close": "Mbylle", "lightbox.next": "Pasuesja", "lightbox.previous": "E mëparshmja", @@ -547,6 +607,8 @@ "navigation_bar.follows_and_followers": "Ndjekje dhe ndjekës", "navigation_bar.import_export": "Importim dhe eksportim", "navigation_bar.lists": "Lista", + "navigation_bar.live_feed_local": "Pryrje e atypëratyshme (vendore)", + "navigation_bar.live_feed_public": "Prurje e atypëratyshme (publike)", "navigation_bar.logout": "Dalje", "navigation_bar.moderation": "Moderim", "navigation_bar.more": "Më tepër", @@ -581,6 +643,7 @@ "notification.label.mention": "Përmendje", "notification.label.private_mention": "Përmendje private", "notification.label.private_reply": "Përgjigje private", + "notification.label.quote": "{name} citoi postimin tuaj", "notification.label.reply": "Përgjigje", "notification.mention": "Përmendje", "notification.mentioned_you": "{name} ju ka përmendur", @@ -595,6 +658,7 @@ "notification.moderation_warning.action_suspend": "Llogaria juaj është pezulluar.", "notification.own_poll": "Pyetësori juaj ka përfunduar", "notification.poll": "Ka përfunduar një pyetësor në të cilin keni marrë pjesë", + "notification.quoted_update": "{name} përpunoi një postim që keni cituar", "notification.reblog": "{name} përforcoi mesazhin tuaj", "notification.reblog.name_and_others_with_link": "Ju ka përforcuar {name} dhe {count, plural, one {# tjetër} other {# të tjerë}}", "notification.relationships_severance_event": "Lidhje të humbura me {name}", @@ -638,6 +702,7 @@ "notifications.column_settings.mention": "Përmendje:", "notifications.column_settings.poll": "Përfundime pyetësori:", "notifications.column_settings.push": "Njoftime Push", + "notifications.column_settings.quote": "Ctime:", "notifications.column_settings.reblog": "Përforcime:", "notifications.column_settings.show": "Shfaqi në shtylla", "notifications.column_settings.sound": "Luaj një tingull", @@ -713,10 +778,20 @@ "privacy.private.short": "Ndjekës", "privacy.public.long": "Cilido që hyn e del në Mastodon", "privacy.public.short": "Publik", + "privacy.quote.anyone": "{visibility}, mund të citojë cilido", + "privacy.quote.disabled": "{visibility}, citimet janë çaktivizuar", + "privacy.quote.limited": "{visibility}, citime të kufizuara", "privacy.unlisted.additional": "Ky sillet saktësisht si publik, vetëm se postimi s’do të shfaqet në prurje të drejtpërdrejta, ose në hashtag-ë, te eksploroni, apo kërkim në Mastodon, edhe kur keni zgjedhur të jetë për tërë llogarinë.", + "privacy.unlisted.long": "Fshehur nga përfundime kërkimi në Mastodon, rrjedha kohore gjërash në modë dhe publike", "privacy.unlisted.short": "Publik i qetë", "privacy_policy.last_updated": "Përditësuar së fundi më {date}", "privacy_policy.title": "Rregulla Privatësie", + "quote_error.edit": "Kur përpunohet një postim, s’mund të shtohen citime.", + "quote_error.poll": "Me pyetësorët nuk lejohet citim.", + "quote_error.private_mentions": "Citimi nuk lejohet me përmendje të drejtpërdrejta.", + "quote_error.quote": "Lejohet vetëm një citim në herë.", + "quote_error.unauthorized": "S’jen i autorizuar ta citoni këtë postim.", + "quote_error.upload": "Me bashkëngjitjet media nuk lejohet citim.", "recommended": "E rekomanduar", "refresh": "Rifreskoje", "regeneration_indicator.please_stand_by": "Ju lutemi, mos u largoni.", @@ -732,6 +807,9 @@ "relative_time.minutes": "{number}m", "relative_time.seconds": "{number}s", "relative_time.today": "sot", + "remove_quote_hint.button_label": "E mora vesh", + "remove_quote_hint.message": "Këtë mund ta bëni një menuja e mundësive {icon}.", + "remove_quote_hint.title": "Doni të hiqet postimi juaj i cituar?", "reply_indicator.attachments": "{count, plural, one {# bashkëngjitje} other {# bashkëngjitje}}", "reply_indicator.cancel": "Anuloje", "reply_indicator.poll": "Pyetësor", @@ -823,13 +901,23 @@ "status.admin_account": "Hap ndërfaqe moderimi për @{name}", "status.admin_domain": "Hap ndërfaqe moderimi për {domain}", "status.admin_status": "Hape këtë mesazh te ndërfaqja e moderimit", + "status.all_disabled": "Përforcimet dhe citime janë të çaktivizuara", "status.block": "Blloko @{name}", "status.bookmark": "Faqeruaje", "status.cancel_reblog_private": "Shpërforcojeni", + "status.cannot_quote": "S’keni leje të citoni këtë postim", "status.cannot_reblog": "Ky postim s’mund të përforcohet", + "status.contains_quote": "Përmban citim", + "status.context.loading": "Po ngarkohen më tepër përgjigje", + "status.context.loading_error": "S’u ngarkuan dot përgjigje të reja", + "status.context.loading_success": "U ngarkuan përgjigje të reja", + "status.context.more_replies_found": "U gjetën më tepër përgjigje", + "status.context.retry": "Riprovoni", + "status.context.show": "Shfaqe", "status.continued_thread": "Vazhdoi rrjedhën", "status.copy": "Kopjoje lidhjen për te mesazhi", "status.delete": "Fshije", + "status.delete.success": "Postimi u fshi", "status.detailed_status": "Pamje e hollësishme bisede", "status.direct": "Përmendje private për @{name}", "status.direct_indicator": "Përmendje private", @@ -838,7 +926,7 @@ "status.edited_x_times": "Përpunuar {count, plural, one {{count} herë} other {{count} herë}}", "status.embed": "Merrni kod trupëzimi", "status.favourite": "I vini shenjë si të parapëlqyer", - "status.favourites": "{count, plural, one {i parapëlqyer} other {të parapëlqyer}}", + "status.favourites_count": "{count, plural, one {{counter} i parapëlqyer} other {{counter} të parapëlqyer}}", "status.filter": "Filtroje këtë postim", "status.history.created": "{name} u krijua më {date}", "status.history.edited": "{name} u përpunua më {date}", @@ -852,20 +940,46 @@ "status.mute_conversation": "Heshtoje bisedën", "status.open": "Zgjeroje këtë mesazh", "status.pin": "Fiksoje në profil", + "status.quote": "Citojeni", + "status.quote.cancel": "Anuloje citimin", + "status.quote_error.blocked_account_hint.title": "Ky postim është i fshehur, ngaqë keni bllokuar @{name}.", + "status.quote_error.blocked_domain_hint.title": "Ky postim është i fshehur, ngaqë keni bllokuar {domain}.", "status.quote_error.filtered": "Fshehur për shkak të njërit nga filtrat tuaj", + "status.quote_error.limited_account_hint.action": "Shfaqe, sido qoftë", + "status.quote_error.limited_account_hint.title": "Kjo llogari është fshehur nga moderatorët e {domain}.", + "status.quote_error.muted_account_hint.title": "Ky postim është i fshehur, ngaqë keni heshtuar @{name}.", + "status.quote_error.not_available": "Postim që s’mund të kihet", + "status.quote_error.pending_approval": "Postim pezull", + "status.quote_error.pending_approval_popout.body": "Në Mastodon mundeni të kontrolloni nëse dikush ju citon a jo. Ky postim është pezull, teksa po marrim miratimin e autorit origjinal.", + "status.quote_error.revoked": "Postim i hequr nga autori", + "status.quote_followers_only": "Këtë postim mund ta citojnë vetëm ndjekës", + "status.quote_manual_review": "Autori do ta shqyrtojë dorazi", + "status.quote_noun": "Citim", + "status.quote_policy_change": "Ndryshoni cilët mund të citojnë", + "status.quote_post_author": "U citua një postim nga @{name}", + "status.quote_private": "Postimet private s’mund të citohen", + "status.quotes.empty": "Këtë postim ende s’e ka cituar kush. Kur dikush ta bëjë, do të shfaqet këtu.", + "status.quotes.local_other_disclaimer": "Citimet e hedhura poshtë nga autori s’do të shfaqen.", + "status.quotes.remote_other_disclaimer": "Këtu garantohet të shfaqen vetëm citime nga {domain}. Citime të hedhura poshtë nga autori s’do të shfaqen.", + "status.quotes_count": "{count, plural, one {{counter} citim} other {{counter} citime}}", "status.read_more": "Lexoni më tepër", "status.reblog": "Përforcojeni", + "status.reblog_or_quote": "Përforconi ose citoni", + "status.reblog_private": "Rindajeni me ndjekësit tuaj", "status.reblogged_by": "{name} përforcoi", - "status.reblogs": "{count, plural, one {përforcim} other {përforcime}}", "status.reblogs.empty": "Këtë mesazh s’e ka përforcuar njeri deri tani. Kur ta bëjë dikush, kjo do të duket këtu.", + "status.reblogs_count": "{count, plural, one {{counter} përforcim} other {{counter} përforcime}}", "status.redraft": "Fshijeni & rihartojeni", "status.remove_bookmark": "Hiqe faqerojtësin", "status.remove_favourite": "Hiqe nga të parapëlqyerat", + "status.remove_quote": "Hiqe", "status.replied_in_thread": "U përgjigj te rrjedha", "status.replied_to": "Iu përgjigj {name}", "status.reply": "Përgjigjuni", "status.replyAll": "Përgjigjuni rrjedhës", "status.report": "Raportojeni @{name}", + "status.request_quote": "Kërkoni të citohet", + "status.revoke_quote": "Hiqe postimin tim nga postimi i @{name}", "status.sensitive_warning": "Lëndë rezervat", "status.share": "Ndajeni me të tjerë", "status.show_less_all": "Shfaq më pak për të tërë", @@ -903,6 +1017,7 @@ "upload_button.label": "Shtoni figura, një video ose një kartelë audio", "upload_error.limit": "U tejkalua kufi ngarkimi kartelash.", "upload_error.poll": "Me pyetësorët s’lejohet ngarkim kartelash.", + "upload_error.quote": "Nuk lejohet ngarkim kartelash me citime.", "upload_form.drag_and_drop.instructions": "Që të merrni një bashkëngjitje media, shtypni tastin Space ose Enter. Teksa bëhet tërheqje, përdorni tastet shigjetë për ta shpënë bashkëngjitjen media në cilëndo drejtori që doni. Shtypni sërish Space ose Enter që të lihet bashkëngjitja media në pozicionin e vet të ri, ose shtypni Esc, që të anulohet veprimi.", "upload_form.drag_and_drop.on_drag_cancel": "Tërheqja u anulua. Bashkëngjitja media {item} u la.", "upload_form.drag_and_drop.on_drag_end": "Bashkëngjitja media {item} u la.", @@ -925,5 +1040,21 @@ "video.skip_forward": "Anashkalo pasardhësen", "video.unmute": "Hiqi heshtimin", "video.volume_down": "Ulje volumi", - "video.volume_up": "Ngritje volumi" + "video.volume_up": "Ngritje volumi", + "visibility_modal.button_title": "Caktoni dukshmëri", + "visibility_modal.direct_quote_warning.text": "Nëse ruani rregullimet e tanishme, citimi i trupëzuar do të shndërrohet në një lidhje.", + "visibility_modal.direct_quote_warning.title": "Citimet s’mund të trupëzohen në përmendje private", + "visibility_modal.header": "Dukshmëri dhe ndërveprim", + "visibility_modal.helper.direct_quoting": "Përmendje private të krijuara në Mastodon s’mund të citohen nga të tjerë.", + "visibility_modal.helper.privacy_editing": "Dukshmëria s’mund të ndryshohet pasi postimi botohet.", + "visibility_modal.helper.privacy_private_self_quote": "Citimet nga ju vetë të postime private s’mund të bëhen publike.", + "visibility_modal.helper.private_quoting": "Postime vetëm për ndjekësit, të krijuara në Mastodon s’mund të citohen nga të tjerë.", + "visibility_modal.helper.unlisted_quoting": "Kur njerëzit ju citojnë, nga rrjedha kohore e gjërave në modë do të kalohen si të fshehura edhe postimet e tyre.", + "visibility_modal.instructions": "Kontrolloni cilët mund të ndërveprojnë me këtë postim. Rregullime mund të aplikooni edhe mbi krejt postimet e ardshme, që nga Parapëlqime > Parazgjedhje postimi.", + "visibility_modal.privacy_label": "Dukshmëri", + "visibility_modal.quote_followers": "Vetëm ndjekës", + "visibility_modal.quote_label": "Cilët mund të citojnë", + "visibility_modal.quote_nobody": "Thjesht unë", + "visibility_modal.quote_public": "Cilido", + "visibility_modal.save": "Ruaje" } diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json index 8cb2e30cb5f82e..25091d8ab1e3f5 100644 --- a/app/javascript/mastodon/locales/sr-Latn.json +++ b/app/javascript/mastodon/locales/sr-Latn.json @@ -54,7 +54,6 @@ "account.posts": "Objave", "account.posts_with_replies": "Objave i odgovori", "account.report": "Prijavi @{name}", - "account.requested": "Čekanje odobrenja. Kliknite za otkazivanje zahteva za praćenje", "account.requested_follow": "{name} je zatražio da vas prati", "account.share": "Podeli profil korisnika @{name}", "account.show_reblogs": "Prikaži podržavanja od korisnika @{name}", @@ -172,7 +171,6 @@ "confirmations.redraft.confirm": "Izbriši i prepravi", "confirmations.redraft.message": "Da li ste sigurni da želite da izbrišete ovu objavu i da je prepravite? Podržavanja i oznake kao omiljenih će biti izgubljeni, a odgovori će ostati bez originalne objave.", "confirmations.unfollow.confirm": "Otprati", - "confirmations.unfollow.message": "Da li ste sigurni da želite da otpratite korisnika {name}?", "conversation.delete": "Izbriši razgovor", "conversation.mark_as_read": "Označi kao pročitano", "conversation.open": "Prikaži razgovor", @@ -622,7 +620,6 @@ "status.edited": "Poslednje uređivanje {date}", "status.edited_x_times": "Uređeno {count, plural, one {{count} put} other {{count} puta}}", "status.favourite": "Omiljeno", - "status.favourites": "{count, plural, one {# omiljeno} few {# omiljena} other {# omiljenih}}", "status.filter": "Filtriraj ovu objavu", "status.history.created": "{name} napisao/la {date}", "status.history.edited": "{name} uredio/la {date}", @@ -639,7 +636,6 @@ "status.read_more": "Pročitajte više", "status.reblog": "Podrži", "status.reblogged_by": "{name} je podržao/la", - "status.reblogs": "{count, plural, one {# podržavanje} few {# podržavanja} other {# podržavanja}}", "status.reblogs.empty": "Još uvek niko nije podržao ovu objavu. Kada bude podržana, pojaviće se ovde.", "status.redraft": "Izbriši i preoblikuj", "status.remove_bookmark": "Ukloni obeleživač", diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json index 485109105b8e7b..b273bb2ff9d01c 100644 --- a/app/javascript/mastodon/locales/sr.json +++ b/app/javascript/mastodon/locales/sr.json @@ -54,7 +54,6 @@ "account.posts": "Објаве", "account.posts_with_replies": "Објаве и одговори", "account.report": "Пријави @{name}", - "account.requested": "Чекање одобрења. Кликните за отказивање захтева за праћење", "account.requested_follow": "{name} је затражио да вас прати", "account.share": "Подели профил корисника @{name}", "account.show_reblogs": "Прикажи подржавања од корисника @{name}", @@ -172,7 +171,6 @@ "confirmations.redraft.confirm": "Избриши и преправи", "confirmations.redraft.message": "Да ли сте сигурни да желите да избришете ову објаву и да је преправите? Подржавања и ознаке као омиљених ће бити изгубљени, а одговори ће остати без оригиналне објаве.", "confirmations.unfollow.confirm": "Отпрати", - "confirmations.unfollow.message": "Да ли сте сигурни да желите да отпратите корисника {name}?", "conversation.delete": "Избриши разговор", "conversation.mark_as_read": "Означи као прочитано", "conversation.open": "Прикажи разговор", @@ -622,7 +620,6 @@ "status.edited": "Последње уређивање {date}", "status.edited_x_times": "Уређено {count, plural, one {{count} пут} other {{count} пута}}", "status.favourite": "Омиљено", - "status.favourites": "{count, plural, one {# омиљено} few {# омиљена} other {# омиљених}}", "status.filter": "Филтрирај ову објаву", "status.history.created": "{name} написао/ла {date}", "status.history.edited": "{name} уредио/ла {date}", @@ -639,7 +636,6 @@ "status.read_more": "Прочитајте више", "status.reblog": "Подржи", "status.reblogged_by": "{name} је подржао/ла", - "status.reblogs": "{count, plural, one {# подржавање} few {# подржавања} other {# подржавања}}", "status.reblogs.empty": "Још увек нико није подржао ову објаву. Када буде подржана, појавиће се овде.", "status.redraft": "Избриши и преобликуј", "status.remove_bookmark": "Уклони обележивач", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 3bbe6434ed8e39..bbd3b68a7c1a29 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Sluta meddela mig när @{name} skriver ett inlägg", "account.domain_blocking": "Blockerad domän", "account.edit_profile": "Redigera profil", + "account.edit_profile_short": "Redigera", "account.enable_notifications": "Notifiera mig när @{name} gör inlägg", "account.endorse": "Visa på profil", "account.familiar_followers_many": "Följs av {name1},{name2} och {othersCount, plural, one {en till du känner} other {# andra du känner}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Inga inlägg", "account.follow": "Följ", "account.follow_back": "Följ tillbaka", + "account.follow_back_short": "Följ tillbaka", + "account.follow_request": "Begär att följa", + "account.follow_request_cancel": "Avbryt begäran", + "account.follow_request_cancel_short": "Avbryt", + "account.follow_request_short": "Begär", "account.followers": "Följare", "account.followers.empty": "Ingen följer denna användare än.", "account.followers_counter": "{count, plural, one {{counter} följare} other {{counter} följare}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Inlägg och svar", "account.remove_from_followers": "Ta bort {name} från följare", "account.report": "Rapportera @{name}", - "account.requested": "Inväntar godkännande. Klicka för att ta tillbaka din begäran om att få följa", "account.requested_follow": "{name} har begärt att följa dig", "account.requests_to_follow_you": "Fråga om att följa dig", "account.share": "Dela @{name}s profil", @@ -108,25 +113,17 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Beskriv detta för personer med synnedsättning…", "alt_text_modal.done": "Klar", "announcement.announcement": "Kungörelse", - "annual_report.summary.archetype.booster": "Häftighetsjägaren", - "annual_report.summary.archetype.lurker": "Smygaren", - "annual_report.summary.archetype.oracle": "Oraklet", - "annual_report.summary.archetype.pollster": "Frågaren", - "annual_report.summary.archetype.replier": "Den sociala fjärilen", - "annual_report.summary.followers.followers": "följare", - "annual_report.summary.followers.total": "{count} totalt", - "annual_report.summary.here_it_is": "Här är en tillbakablick på ditt {year}:", - "annual_report.summary.highlighted_post.by_favourites": "mest favoritmarkerat inlägg", - "annual_report.summary.highlighted_post.by_reblogs": "mest boostat inlägg", - "annual_report.summary.highlighted_post.by_replies": "inlägg med flest svar", - "annual_report.summary.highlighted_post.possessive": "{name}s", + "annual_report.announcement.action_dismiss": "Nej tack", + "annual_report.nav_item.badge": "Ny", + "annual_report.shared_page.donate": "Donera", + "annual_report.summary.copy_link": "Kopiera länk", "annual_report.summary.most_used_app.most_used_app": "mest använda app", "annual_report.summary.most_used_hashtag.most_used_hashtag": "mest använda hashtag", - "annual_report.summary.most_used_hashtag.none": "Inga", "annual_report.summary.new_posts.new_posts": "nya inlägg", "annual_report.summary.percentile.text": "Det placerar dig i toppbland {domain} användare.", "annual_report.summary.percentile.we_wont_tell_bernie": "Vi berättar inte för Bernie.", - "annual_report.summary.thanks": "Tack för att du är en del av Mastodon!", + "annual_report.summary.share_elsewhere": "Dela någon annanstans", + "annual_report.summary.share_on_mastodon": "Dela på Mastodon", "attachments_list.unprocessed": "(obehandlad)", "audio.hide": "Dölj audio", "block_modal.remote_users_caveat": "Vi kommer att be servern {domain} att respektera ditt beslut. Dock garanteras inte efterlevnad eftersom vissa servrar kan hantera blockeringar på olika sätt. Offentliga inlägg kan fortfarande vara synliga för icke-inloggade användare.", @@ -152,6 +149,8 @@ "bundle_modal_error.close": "Stäng", "bundle_modal_error.message": "Något gick fel när skärmen laddades.", "bundle_modal_error.retry": "Försök igen", + "carousel.current": "Bild{current, number} / {max, number}", + "carousel.slide": "Bild {current, number} av {max, number}", "closed_registrations.other_server_instructions": "Eftersom Mastodon är decentraliserat kan du skapa ett konto på en annan server och fortfarande interagera med denna.", "closed_registrations_modal.description": "Det är för närvarande inte möjligt att skapa ett konto på {domain} men kom ihåg att du inte behöver ett konto specifikt på {domain} för att använda Mastodon.", "closed_registrations_modal.find_another_server": "Hitta en annan server", @@ -168,6 +167,8 @@ "column.edit_list": "Redigera lista", "column.favourites": "Favoriter", "column.firehose": "Direktflöden", + "column.firehose_local": "Direktflöde för den här servern", + "column.firehose_singular": "Direktflöde", "column.follow_requests": "Följarförfrågningar", "column.home": "Hem", "column.list_members": "Hantera listmedlemmar", @@ -187,6 +188,7 @@ "community.column_settings.local_only": "Endast lokalt", "community.column_settings.media_only": "Endast media", "community.column_settings.remote_only": "Endast fjärr", + "compose.error.blank_post": "Inlägget kan inte vara tomt.", "compose.language.change": "Ändra språk", "compose.language.search": "Sök språk...", "compose.published.body": "Inlägget publicerat.", @@ -239,7 +241,15 @@ "confirmations.missing_alt_text.secondary": "Posta ändå", "confirmations.missing_alt_text.title": "Lägg till alt-text?", "confirmations.mute.confirm": "Tysta", + "confirmations.private_quote_notify.cancel": "Tillbaka till redigering", + "confirmations.private_quote_notify.confirm": "Publicera inlägg", + "confirmations.private_quote_notify.do_not_show_again": "Visa inte mig detta meddelande igen", + "confirmations.private_quote_notify.message": "Personen du citerar och andra omnämnda kommer att meddelas och kommer att kunna se ditt inlägg, även om de inte följer dig.", + "confirmations.private_quote_notify.title": "Dela med följare och omnämnda användare?", "confirmations.quiet_post_quote_info.dismiss": "Påminn mig inte igen", + "confirmations.quiet_post_quote_info.got_it": "Jag förstår", + "confirmations.quiet_post_quote_info.message": "När du citerar en tyst offentlig post, kommer ditt inlägg att döljas från trendande tidslinjer.", + "confirmations.quiet_post_quote_info.title": "Citerar tysta offentliga inlägg", "confirmations.redraft.confirm": "Radera & gör om", "confirmations.redraft.message": "Är du säker på att du vill radera detta inlägg och göra om det? Favoritmarkeringar, boostar och svar till det ursprungliga inlägget kommer förlora sitt sammanhang.", "confirmations.redraft.title": "Ta bort & gör om inlägget?", @@ -249,9 +259,12 @@ "confirmations.revoke_quote.confirm": "Ta bort inlägg", "confirmations.revoke_quote.message": "Denna åtgärd kan inte ångras.", "confirmations.revoke_quote.title": "Ta bort inlägg?", + "confirmations.unblock.confirm": "Avblockera", + "confirmations.unblock.title": "Avblockera {name}?", "confirmations.unfollow.confirm": "Avfölj", - "confirmations.unfollow.message": "Är du säker på att du vill avfölja {name}?", - "confirmations.unfollow.title": "Avfölj användare?", + "confirmations.unfollow.title": "Avfölj {name}?", + "confirmations.withdraw_request.confirm": "Återkalla förfrågan", + "confirmations.withdraw_request.title": "Återkalla begäran att följa {name}?", "content_warning.hide": "Dölj inlägg", "content_warning.show": "Visa ändå", "content_warning.show_more": "Visa mer", @@ -321,10 +334,11 @@ "empty_column.blocks": "Du har ännu ej blockerat några användare.", "empty_column.bookmarked_statuses": "Du har inte bokmärkt några inlägg än. När du bokmärker ett inlägg kommer det synas här.", "empty_column.community": "Den lokala tidslinjen är tom. Skriv något offentligt för att sätta bollen i rullning!", - "empty_column.direct": "Du har inga privata omnämninande. När du skickar eller tar emot ett direktmeddelande kommer det att visas här.", + "empty_column.direct": "Du har inga privata omnämnanden. När du skickar eller tar emot ett direktmeddelande kommer det att visas här.", + "empty_column.disabled_feed": "Detta flöde har inaktiverats av dina serveradministratörer.", "empty_column.domain_blocks": "Det finns ännu inga dolda domäner.", "empty_column.explore_statuses": "Ingenting är trendigt just nu. Kom tillbaka senare!", - "empty_column.favourited_statuses": "Du har inga favoritmarkerade inlägg ännu. När du favoritmärker ett så kommer det att dyka upp här.", + "empty_column.favourited_statuses": "Du har inga favoritmarkerade inlägg ännu. När du favoritmarkerar ett så kommer det att dyka upp här.", "empty_column.favourites": "Ingen har favoritmarkerat detta inlägg än. När någon gör det kommer de synas här.", "empty_column.follow_requests": "Du har inga följarförfrågningar än. När du får en kommer den visas här.", "empty_column.followed_tags": "Du följer inga hashtaggar ännu. När du gör det kommer de att dyka upp här.", @@ -335,6 +349,7 @@ "empty_column.notification_requests": "Allt klart! Det finns inget mer här. När du får nya meddelanden visas de här enligt dina inställningar.", "empty_column.notifications": "Du har inga meddelanden än. Interagera med andra för att starta konversationen.", "empty_column.public": "Det finns inget här! Skriv något offentligt, eller följ manuellt användarna från andra instanser för att fylla på det", + "error.no_hashtag_feed_access": "Gå med eller logga in för att visa och följa denna hashtag.", "error.unexpected_crash.explanation": "På grund av en bugg i vår kod eller kompatiblitetsproblem i webbläsaren kan den här sidan inte visas korrekt.", "error.unexpected_crash.explanation_addons": "Denna sida kunde inte visas korrekt. Detta beror troligen på ett webbläsartillägg eller ett automatiskt översättningsverktyg.", "error.unexpected_crash.next_steps": "Prova att ladda om sidan. Om det inte hjälper kan du försöka använda Mastodon med en annan webbläsare eller app.", @@ -346,11 +361,9 @@ "explore.trending_links": "Nyheter", "explore.trending_statuses": "Inlägg", "explore.trending_tags": "Hashtaggar", + "featured_carousel.current": "Inlägg {current, number} / {max, number}", "featured_carousel.header": "{count, plural,one {Fäst inlägg} other {Fästa inlägg}}", - "featured_carousel.next": "Nästa", - "featured_carousel.post": "Inlägg", - "featured_carousel.previous": "Föregående", - "featured_carousel.slide": "{index} av {total}", + "featured_carousel.slide": "Inlägg {current, number} av {max, number}", "filter_modal.added.context_mismatch_explanation": "Denna filterkategori gäller inte för det sammanhang där du har tillgång till det här inlägget. Om du vill att inlägget ska filtreras även i detta sammanhang måste du redigera filtret.", "filter_modal.added.context_mismatch_title": "Misspassning av sammanhang!", "filter_modal.added.expired_explanation": "Denna filterkategori har utgått, du måste ändra utgångsdatum för att den ska kunna tillämpas.", @@ -393,6 +406,9 @@ "follow_suggestions.who_to_follow": "Rekommenderade profiler", "followed_tags": "Följda hashtags", "footer.about": "Om", + "footer.about_mastodon": "Om Mastodon", + "footer.about_server": "Om {domain}", + "footer.about_this_server": "Om", "footer.directory": "Profilkatalog", "footer.get_app": "Skaffa appen", "footer.keyboard_shortcuts": "Tangentbordsgenvägar", @@ -450,10 +466,12 @@ "ignore_notifications_modal.private_mentions_title": "Vill du ignorera aviseringar från oombedda privata omnämnanden?", "info_button.label": "Hjälp", "info_button.what_is_alt_text": "

Vad är alt-text?

alt-text ger bildbeskrivningar för personer med synnedsättning, anslutningar med låg bandbredd eller de som söker extra sammanhang.

Du kan förbättra tillgängligheten och förståelsen för alla genom att skriva en tydlig, koncis och objektiv alt-text.

  • Fånga viktiga element
  • Sammanfatta text i bilder
  • Använd vanlig meningsstruktur
  • Undvik överflödig information
  • Fokus på trender och viktiga resultat i komplexa bilder (som diagram eller kartor)
", + "interaction_modal.action": "För att interagera med {name}s inlägg måste du logga in på ditt konto på vilken Mastodon-server du än använder.", "interaction_modal.go": "Vidare", "interaction_modal.no_account_yet": "Har du inget konto än?", "interaction_modal.on_another_server": "På en annan server", "interaction_modal.on_this_server": "På denna server", + "interaction_modal.title": "Logga in för att fortsätta", "interaction_modal.username_prompt": "T.ex. {example}", "intervals.full.days": "{number, plural, one {# dag} other {# dagar}}", "intervals.full.hours": "{number, plural, one {# timme} other {# timmar}}", @@ -474,6 +492,7 @@ "keyboard_shortcuts.home": "Öppna Hemtidslinjen", "keyboard_shortcuts.hotkey": "Kommando", "keyboard_shortcuts.legend": "Visa denna översikt", + "keyboard_shortcuts.load_more": "Fokusera \"Ladda mer\"-knappen", "keyboard_shortcuts.local": "Öppna lokal tidslinje", "keyboard_shortcuts.mention": "Nämna skaparen", "keyboard_shortcuts.muted": "Öppna listan över tystade användare", @@ -482,6 +501,7 @@ "keyboard_shortcuts.open_media": "Öppna media", "keyboard_shortcuts.pinned": "Öppna listan över fästa inlägg", "keyboard_shortcuts.profile": "Öppna författarens profil", + "keyboard_shortcuts.quote": "Citera inlägg", "keyboard_shortcuts.reply": "Svara på inlägg", "keyboard_shortcuts.requests": "Öppna följförfrågningar", "keyboard_shortcuts.search": "Fokusera sökfältet", @@ -490,6 +510,7 @@ "keyboard_shortcuts.toggle_hidden": "Visa/gömma text bakom CW", "keyboard_shortcuts.toggle_sensitivity": "Visa/gömma media", "keyboard_shortcuts.toot": "Starta nytt inlägg", + "keyboard_shortcuts.top": "Flytta till början av listan", "keyboard_shortcuts.translate": "för att översätta ett inlägg", "keyboard_shortcuts.unfocus": "Avfokusera skrivfält/sökfält", "keyboard_shortcuts.up": "Flytta uppåt i listan", @@ -610,6 +631,7 @@ "notification.moderation_warning.action_suspend": "Ditt konto har stängts av.", "notification.own_poll": "Din röstning har avslutats", "notification.poll": "En enkät som du röstat i har avslutats", + "notification.quoted_update": "{name} redigerade ett inlägg du har citerat", "notification.reblog": "{name} boostade ditt inlägg", "notification.reblog.name_and_others_with_link": "{name} och {count, plural, one {# annan} other {# andra}} har förhöjt ditt inlägg", "notification.relationships_severance_event": "Förlorade kontakter med {name}", @@ -729,10 +751,20 @@ "privacy.private.short": "Följare", "privacy.public.long": "Alla på och utanför Mastodon", "privacy.public.short": "Offentlig", + "privacy.quote.anyone": "{visibility}, vem som helst kan citera", + "privacy.quote.disabled": "{visibility}, citat inaktiverade", + "privacy.quote.limited": "{visibility}, citat begränsade", "privacy.unlisted.additional": "Detta fungerar precis som offentlig, förutom att inlägget inte visas i liveflöden eller hashtaggar, utforska eller Mastodon-sökning, även om du har valt detta för hela kontot.", + "privacy.unlisted.long": "Dold från Mastodon-sökresultat, trendar och offentliga tidslinjer", "privacy.unlisted.short": "Offentlig (begränsad)", "privacy_policy.last_updated": "Senast uppdaterad {date}", "privacy_policy.title": "Integritetspolicy", + "quote_error.edit": "Citat kan inte läggas till när du redigerar ett inlägg.", + "quote_error.poll": "Citat är inte tillåtet med omröstningar.", + "quote_error.private_mentions": "Citering är inte tillåtet med direkta omnämnanden.", + "quote_error.quote": "Endast ett citat åt gången är tillåtet.", + "quote_error.unauthorized": "Du har inte behörighet att citera detta inlägg.", + "quote_error.upload": "Citering är inte tillåtet med mediebilagor.", "recommended": "Rekommenderas", "refresh": "Läs om", "regeneration_indicator.please_stand_by": "Vänligen vänta.", @@ -748,6 +780,9 @@ "relative_time.minutes": "{number}min", "relative_time.seconds": "{number}sek", "relative_time.today": "idag", + "remove_quote_hint.button_label": "Jag förstår", + "remove_quote_hint.message": "Du kan göra det från {icon} alternativmenyn.", + "remove_quote_hint.title": "Vill du ta bort ditt citerade inlägg?", "reply_indicator.attachments": "{count, plural, one {# bilaga} other {# bilagor}}", "reply_indicator.cancel": "Ångra", "reply_indicator.poll": "Omröstning", @@ -839,12 +874,19 @@ "status.admin_account": "Öppet modereringsgränssnitt för @{name}", "status.admin_domain": "Öppet modereringsgränssnitt för @{domain}", "status.admin_status": "Öppna detta inlägg i modereringsgränssnittet", + "status.all_disabled": "Booster och citat är inaktiverade", "status.block": "Blockera @{name}", "status.bookmark": "Bokmärk", "status.cancel_reblog_private": "Sluta boosta", + "status.cannot_quote": "Du har inte tillåtelse att citera detta inlägg", "status.cannot_reblog": "Detta inlägg kan inte boostas", - "status.context.load_new_replies": "Nya svar finns", - "status.context.loading": "Letar efter fler svar", + "status.contains_quote": "Innehåller citat", + "status.context.loading": "Laddar fler svar", + "status.context.loading_error": "Kunde inte ladda nya svar", + "status.context.loading_success": "Nya svar laddade", + "status.context.more_replies_found": "Fler svar hittades", + "status.context.retry": "Försök igen", + "status.context.show": "Visa", "status.continued_thread": "Fortsatt tråd", "status.copy": "Kopiera inläggslänk", "status.delete": "Radera", @@ -857,7 +899,7 @@ "status.edited_x_times": "Redigerad {count, plural, one {{count} gång} other {{count} gånger}}", "status.embed": "Hämta kod för inbäddning", "status.favourite": "Favoritmarkera", - "status.favourites": "{count, plural, one {favorit} other {favoriter}}", + "status.favourites_count": "{count, plural, one {{counter} favorit} other {{counter} favoriter}}", "status.filter": "Filtrera detta inlägg", "status.history.created": "{name} skapade {date}", "status.history.edited": "{name} redigerade {date}", @@ -871,25 +913,45 @@ "status.mute_conversation": "Tysta konversation", "status.open": "Utvidga detta inlägg", "status.pin": "Fäst i profil", + "status.quote": "Citera ", "status.quote.cancel": "Återkalla citering", + "status.quote_error.blocked_account_hint.title": "Detta inlägg är dolt eftersom du har blockerat @{name}.", + "status.quote_error.blocked_domain_hint.title": "Detta inlägg är dolt eftersom du har blockerat {domain}.", "status.quote_error.filtered": "Dolt på grund av ett av dina filter", + "status.quote_error.limited_account_hint.action": "Visa ändå", + "status.quote_error.limited_account_hint.title": "Detta konto har dolts av {domain}s moderatorer.", + "status.quote_error.muted_account_hint.title": "Detta inlägg är dolt eftersom du har tystat @{name}.", "status.quote_error.not_available": "Inlägg ej tillgängligt", "status.quote_error.pending_approval": "Väntande inlägg", + "status.quote_error.pending_approval_popout.body": "På Mastodon kan du styra om någon kan citera dig. Det här inlägget väntar medan vi får den ursprungliga författarens godkännande.", + "status.quote_error.revoked": "Inlägg borttaget av författaren", + "status.quote_followers_only": "Detta inlägg kan bara citeras av följare", + "status.quote_manual_review": "Författaren kommer att granska manuellt", + "status.quote_noun": "Citat", "status.quote_policy_change": "Ändra vem som kan citera", "status.quote_post_author": "Citerade ett inlägg av @{name}", + "status.quote_private": "Privata inlägg kan inte citeras", + "status.quotes.empty": "Ingen har citerat detta inlägg än. När någon gör det kommer det att synas här.", + "status.quotes.local_other_disclaimer": "Citat som avvisats av författaren kommer inte att visas.", + "status.quotes.remote_other_disclaimer": "Endast citat från {domain} är garanterade att visas här. Citat som avvisats av författaren kommer inte att visas.", + "status.quotes_count": "{count, plural, one {{counter} citat} other {{counter} citat}}", "status.read_more": "Läs mer", "status.reblog": "Boosta", + "status.reblog_or_quote": "Boosta eller citera", + "status.reblog_private": "Dela igen med dina följare", "status.reblogged_by": "{name} boostade", - "status.reblogs": "{count, plural, one {# röst} other {# röster}}", "status.reblogs.empty": "Ingen har boostat detta inlägg än. När någon gör det kommer de synas här.", + "status.reblogs_count": "{count, plural, one {{counter} boost} other {{counter} boostar}}", "status.redraft": "Radera & gör om", "status.remove_bookmark": "Ta bort bokmärke", "status.remove_favourite": "Ta bort från Favoriter", + "status.remove_quote": "Ta bort", "status.replied_in_thread": "Svarade i tråden", "status.replied_to": "Svarade på {name}", "status.reply": "Svara", "status.replyAll": "Svara på tråden", "status.report": "Rapportera @{name}", + "status.request_quote": "Begär att få citera", "status.revoke_quote": "Ta bort mitt inlägg från @{name}s inlägg", "status.sensitive_warning": "Känsligt innehåll", "status.share": "Dela", @@ -928,6 +990,7 @@ "upload_button.label": "Lägg till media", "upload_error.limit": "Filöverföringsgränsen överskriden.", "upload_error.poll": "Filuppladdning tillåts inte med omröstningar.", + "upload_error.quote": "Filuppladdning tillåts inte med citat.", "upload_form.drag_and_drop.instructions": "För att plocka upp en mediebilaga, tryck på mellanslag eller enter. Använd piltangenterna för att flytta mediebilagan. Tryck på mellanslag eller enter igen för att släppa mediebilagan i sin nya position, eller tryck på escape för att avbryta.", "upload_form.drag_and_drop.on_drag_cancel": "Flytten avbröts. Mediebilagan {item} släpptes.", "upload_form.drag_and_drop.on_drag_end": "Mediebilagan {item} släpptes.", @@ -952,9 +1015,19 @@ "video.volume_down": "Volym ned", "video.volume_up": "Volym upp", "visibility_modal.button_title": "Ange synlighet", + "visibility_modal.direct_quote_warning.text": "Om du sparar de nuvarande inställningarna kommer det inbäddade citatet bli konverterat till en länk.", + "visibility_modal.direct_quote_warning.title": "Citat kan inte bäddas in i privata omnämnanden", "visibility_modal.header": "Synlighet och interaktion", + "visibility_modal.helper.direct_quoting": "Privata omnämnanden som författats på Mastodon kan inte citeras av andra.", + "visibility_modal.helper.privacy_editing": "Synligheten kan inte ändras efter att ett inlägg är publicerat.", + "visibility_modal.helper.privacy_private_self_quote": "Självcitat av privata inlägg kan inte göras publika.", + "visibility_modal.helper.private_quoting": "Inlägg som endast är för följare och som författats på Mastodon kan inte citeras av andra.", "visibility_modal.helper.unlisted_quoting": "När folk citerar dig, deras inlägg kommer också att döljas från trendiga tidslinjer.", + "visibility_modal.instructions": "Kontrollera vem som kan interagera med detta inlägg. Du kan också använda inställningar för alla framtida inlägg genom att navigera till Preferences > Posting defaults.", + "visibility_modal.privacy_label": "Synlighet", "visibility_modal.quote_followers": "Endast följare", + "visibility_modal.quote_label": "Vem kan citera", + "visibility_modal.quote_nobody": "Bara jag", "visibility_modal.quote_public": "Alla", "visibility_modal.save": "Spara" } diff --git a/app/javascript/mastodon/locales/szl.json b/app/javascript/mastodon/locales/szl.json index 55913be9246493..709b0f9e891273 100644 --- a/app/javascript/mastodon/locales/szl.json +++ b/app/javascript/mastodon/locales/szl.json @@ -19,7 +19,6 @@ "account.mute": "Wycisz @{name}", "account.posts": "Toots", "account.posts_with_replies": "Toots and replies", - "account.requested": "Awaiting approval", "account_note.placeholder": "Click to add a note", "column.pins": "Pinned toot", "community.column_settings.media_only": "Media only", diff --git a/app/javascript/mastodon/locales/ta.json b/app/javascript/mastodon/locales/ta.json index 10d461e9e83481..7664174c17aded 100644 --- a/app/javascript/mastodon/locales/ta.json +++ b/app/javascript/mastodon/locales/ta.json @@ -37,7 +37,6 @@ "account.posts": "டூட்டுகள்", "account.posts_with_replies": "Toots மற்றும் பதில்கள்", "account.report": "@{name} -ஐப் புகாரளி", - "account.requested": "ஒப்புதலுக்காகக் காத்திருக்கிறது. பின்தொடரும் கோரிக்கையை நீக்க அழுத்தவும்", "account.share": "@{name} உடைய விவரத்தை பகிர்", "account.show_reblogs": "காட்டு boosts இருந்து @{name}", "account.unblock": "@{name} மீது தடை நீக்குக", @@ -129,7 +128,6 @@ "confirmations.mute.confirm": "அமைதியாக்கு", "confirmations.redraft.confirm": "பதிவை நீக்கி மறுவரைவு செய்", "confirmations.unfollow.confirm": "விலகு", - "confirmations.unfollow.message": "{name}-ஐப் பின்தொடர்வதை நிச்சயமாக நீங்கள் நிறுத்த விரும்புகிறீர்களா?", "conversation.delete": "உரையாடலை அழி", "conversation.mark_as_read": "படிக்கபட்டதாகக் குறி", "conversation.open": "உரையாடலைக் காட்டு", diff --git a/app/javascript/mastodon/locales/tai.json b/app/javascript/mastodon/locales/tai.json index 3799455050865d..507894ba142b76 100644 --- a/app/javascript/mastodon/locales/tai.json +++ b/app/javascript/mastodon/locales/tai.json @@ -8,7 +8,6 @@ "account.mention": "Thê-khí @{name}", "account.posts": "Huah-siann", "account.posts_with_replies": "Huah-siann kah huê-ìng", - "account.requested": "Tán-thāi phue-tsún", "account_note.placeholder": "Tiám tsi̍t-ē ka-thiam pī-tsù", "column.pins": "Tah thâu-tsîng ê huah-siann", "community.column_settings.media_only": "Kan-na muî-thé", diff --git a/app/javascript/mastodon/locales/te.json b/app/javascript/mastodon/locales/te.json index cbdea417b71543..c4442f74361d94 100644 --- a/app/javascript/mastodon/locales/te.json +++ b/app/javascript/mastodon/locales/te.json @@ -24,7 +24,6 @@ "account.posts": "టూట్లు", "account.posts_with_replies": "టూట్లు మరియు ప్రత్యుత్తరములు", "account.report": "@{name}పై ఫిర్యాదుచేయు", - "account.requested": "ఆమోదం కోసం వేచి ఉంది. అభ్యర్థనను రద్దు చేయడానికి క్లిక్ చేయండి", "account.share": "@{name} యొక్క ప్రొఫైల్ను పంచుకోండి", "account.show_reblogs": "@{name}నుంచి బూస్ట్ లను చూపించు", "account.unblock": "@{name}పై బ్లాక్ ను తొలగించు", @@ -75,7 +74,6 @@ "confirmations.mute.confirm": "మ్యూట్ చేయి", "confirmations.redraft.confirm": "తొలగించు & తిరగరాయు", "confirmations.unfollow.confirm": "అనుసరించవద్దు", - "confirmations.unfollow.message": "{name}ను మీరు ఖచ్చితంగా అనుసరించవద్దనుకుంటున్నారా?", "embed.instructions": "దిగువ కోడ్ను కాపీ చేయడం ద్వారా మీ వెబ్సైట్లో ఈ స్టేటస్ ని పొందుపరచండి.", "embed.preview": "అది ఈ క్రింది విధంగా కనిపిస్తుంది:", "emoji_button.activity": "కార్యకలాపాలు", diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json index a1fade41ca66c0..eb2a814d610e23 100644 --- a/app/javascript/mastodon/locales/th.json +++ b/app/javascript/mastodon/locales/th.json @@ -28,8 +28,11 @@ "account.disable_notifications": "หยุดแจ้งเตือนฉันเมื่อ @{name} โพสต์", "account.domain_blocking": "กำลังปิดกั้นโดเมน", "account.edit_profile": "แก้ไขโปรไฟล์", + "account.edit_profile_short": "แก้ไข", "account.enable_notifications": "แจ้งเตือนฉันเมื่อ @{name} โพสต์", "account.endorse": "แสดงในโปรไฟล์", + "account.familiar_followers_one": "ติดตามโดย {name1}", + "account.familiar_followers_two": "ติดตามโดย {name1} และ {name2}", "account.featured": "น่าสนใจ", "account.featured.accounts": "โปรไฟล์", "account.featured.hashtags": "แฮชแท็ก", @@ -37,6 +40,11 @@ "account.featured_tags.last_status_never": "ไม่มีโพสต์", "account.follow": "ติดตาม", "account.follow_back": "ติดตามกลับ", + "account.follow_back_short": "ติดตามกลับ", + "account.follow_request": "ขอติดตาม", + "account.follow_request_cancel": "ยกเลิกคำขอ", + "account.follow_request_cancel_short": "ยกเลิก", + "account.follow_request_short": "ขอ", "account.followers": "ผู้ติดตาม", "account.followers.empty": "ยังไม่มีใครติดตามผู้ใช้นี้", "account.followers_counter": "{count, plural, other {{counter} ผู้ติดตาม}}", @@ -59,12 +67,13 @@ "account.mute_short": "ซ่อน", "account.muted": "ซ่อนอยู่", "account.muting": "กำลังซ่อน", + "account.mutual": "คุณติดตามกันและกัน", "account.no_bio": "ไม่ได้ให้คำอธิบาย", "account.open_original_page": "เปิดหน้าดั้งเดิม", "account.posts": "โพสต์", "account.posts_with_replies": "โพสต์และการตอบกลับ", + "account.remove_from_followers": "เอา {name} ออกจากผู้ติดตาม", "account.report": "รายงาน @{name}", - "account.requested": "กำลังรอการอนุมัติ คลิกเพื่อยกเลิกคำขอติดตาม", "account.requested_follow": "{name} ได้ขอติดตามคุณ", "account.share": "แชร์โปรไฟล์ของ @{name}", "account.show_reblogs": "แสดงการดันจาก @{name}", @@ -101,23 +110,10 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "อธิบายสิ่งนี้สำหรับผู้คนที่มีความบกพร่องทางการมองเห็น…", "alt_text_modal.done": "เสร็จสิ้น", "announcement.announcement": "ประกาศ", - "annual_report.summary.archetype.booster": "ผู้ล่าความเจ๋ง", - "annual_report.summary.archetype.lurker": "ผู้ซุ่มอ่านข่าว", - "annual_report.summary.archetype.oracle": "ผู้ให้คำปรึกษา", - "annual_report.summary.archetype.pollster": "ผู้สำรวจความคิดเห็น", - "annual_report.summary.archetype.replier": "ผู้ชอบเข้าสังคม", - "annual_report.summary.followers.followers": "ผู้ติดตาม", - "annual_report.summary.followers.total": "รวม {count}", - "annual_report.summary.highlighted_post.by_favourites": "โพสต์ที่ได้รับการชื่นชอบมากที่สุด", - "annual_report.summary.highlighted_post.by_reblogs": "โพสต์ที่ได้รับการดันมากที่สุด", - "annual_report.summary.highlighted_post.by_replies": "โพสต์ที่มีการตอบกลับมากที่สุด", - "annual_report.summary.highlighted_post.possessive": "{name}", "annual_report.summary.most_used_app.most_used_app": "แอปที่ใช้มากที่สุด", "annual_report.summary.most_used_hashtag.most_used_hashtag": "แฮชแท็กที่ใช้มากที่สุด", - "annual_report.summary.most_used_hashtag.none": "ไม่มี", "annual_report.summary.new_posts.new_posts": "โพสต์ใหม่", "annual_report.summary.percentile.we_wont_tell_bernie": "เราจะไม่บอก Bernie", - "annual_report.summary.thanks": "ขอบคุณสำหรับการเป็นส่วนหนึ่งของ Mastodon!", "attachments_list.unprocessed": "(ยังไม่ได้ประมวลผล)", "audio.hide": "ซ่อนเสียง", "block_modal.remote_users_caveat": "เราจะขอให้เซิร์ฟเวอร์ {domain} เคารพการตัดสินใจของคุณ อย่างไรก็ตาม ไม่รับประกันการปฏิบัติตามข้อกำหนดเนื่องจากเซิร์ฟเวอร์บางแห่งอาจจัดการการปิดกั้นแตกต่างกัน โพสต์สาธารณะอาจยังคงปรากฏแก่ผู้ใช้ที่ไม่ได้เข้าสู่ระบบ", @@ -159,6 +155,8 @@ "column.edit_list": "แก้ไขรายการ", "column.favourites": "รายการโปรด", "column.firehose": "ฟีดสด", + "column.firehose_local": "ฟีดสดสำหรับเซิร์ฟเวอร์นี้", + "column.firehose_singular": "ฟีดสด", "column.follow_requests": "คำขอติดตาม", "column.home": "หน้าแรก", "column.list_members": "จัดการสมาชิกของรายการ", @@ -210,6 +208,8 @@ "confirmations.delete_list.confirm": "ลบ", "confirmations.delete_list.message": "คุณแน่ใจหรือไม่ว่าต้องการลบรายการนี้อย่างถาวร?", "confirmations.delete_list.title": "ลบรายการ?", + "confirmations.discard_draft.confirm": "ละทิ้งและดำเนินการต่อ", + "confirmations.discard_draft.post.title": "ละทิ้งโพสต์แบบร่างของคุณ?", "confirmations.discard_edit_media.confirm": "ละทิ้ง", "confirmations.discard_edit_media.message": "คุณมีการเปลี่ยนแปลงคำอธิบายหรือตัวอย่างสื่อที่ยังไม่ได้บันทึก ละทิ้งการเปลี่ยนแปลงเหล่านั้นต่อไป?", "confirmations.follow_to_list.confirm": "ติดตามและเพิ่มไปยังรายการ", @@ -222,12 +222,21 @@ "confirmations.missing_alt_text.secondary": "โพสต์ต่อไป", "confirmations.missing_alt_text.title": "เพิ่มข้อความแสดงแทน?", "confirmations.mute.confirm": "ซ่อน", + "confirmations.private_quote_notify.confirm": "เผยแพร่โพสต์", + "confirmations.quiet_post_quote_info.dismiss": "ไม่ต้องเตือนฉันอีก", + "confirmations.quiet_post_quote_info.got_it": "เข้าใจแล้ว", "confirmations.redraft.confirm": "ลบแล้วร่างใหม่", "confirmations.redraft.message": "คุณแน่ใจหรือไม่ว่าต้องการลบโพสต์นี้แล้วร่างโพสต์ใหม่? รายการโปรดและการดันจะสูญหาย และการตอบกลับโพสต์ดั้งเดิมจะไม่มีความเกี่ยวพัน", "confirmations.redraft.title": "ลบแล้วร่างโพสต์ใหม่?", + "confirmations.remove_from_followers.confirm": "เอาผู้ติดตามออก", + "confirmations.remove_from_followers.title": "เอาผู้ติดตามออก?", + "confirmations.revoke_quote.confirm": "เอาโพสต์ออก", + "confirmations.revoke_quote.title": "เอาโพสต์ออก?", + "confirmations.unblock.confirm": "เลิกปิดกั้น", + "confirmations.unblock.title": "เลิกปิดกั้น {name}?", "confirmations.unfollow.confirm": "เลิกติดตาม", - "confirmations.unfollow.message": "คุณแน่ใจหรือไม่ว่าต้องการเลิกติดตาม {name}?", - "confirmations.unfollow.title": "เลิกติดตามผู้ใช้?", + "confirmations.unfollow.title": "เลิกติดตาม {name}?", + "confirmations.withdraw_request.confirm": "ถอนคำขอ", "content_warning.hide": "ซ่อนโพสต์", "content_warning.show": "แสดงต่อไป", "content_warning.show_more": "แสดงเพิ่มเติม", @@ -268,6 +277,7 @@ "domain_pill.your_handle": "นามของคุณ:", "domain_pill.your_server": "บ้านดิจิทัลของคุณ ที่ซึ่งโพสต์ทั้งหมดของคุณอาศัยอยู่ ไม่ชอบเซิร์ฟเวอร์นี้? ถ่ายโอนเซิร์ฟเวอร์เมื่อใดก็ได้และนำผู้ติดตามของคุณไปด้วยเช่นกัน", "domain_pill.your_username": "ตัวระบุที่ไม่ซ้ำกันของคุณในเซิร์ฟเวอร์นี้ เป็นไปได้ที่จะค้นหาผู้ใช้ที่มีชื่อผู้ใช้เดียวกันในเซิร์ฟเวอร์ที่แตกต่างกัน", + "dropdown.empty": "เลือกตัวเลือก", "embed.instructions": "ฝังโพสต์นี้ในเว็บไซต์ของคุณโดยคัดลอกโค้ดด้านล่าง", "embed.preview": "นี่คือลักษณะของการฝังที่จะปรากฏ:", "emoji_button.activity": "กิจกรรม", @@ -317,10 +327,9 @@ "explore.trending_links": "ข่าว", "explore.trending_statuses": "โพสต์", "explore.trending_tags": "แฮชแท็ก", - "featured_carousel.next": "ถัดไป", - "featured_carousel.post": "โพสต์", - "featured_carousel.previous": "ก่อนหน้า", - "featured_carousel.slide": "{index} จาก {total}", + "featured_carousel.current": "โพสต์ {current, number} / {max, number}", + "featured_carousel.header": "{count, plural, other {โพสต์ที่ปักหมุด}}", + "featured_carousel.slide": "โพสต์ {current, number} จาก {max, number}", "filter_modal.added.context_mismatch_explanation": "หมวดหมู่ตัวกรองนี้ไม่นำไปใช้กับบริบทที่คุณได้เข้าถึงโพสต์นี้ หากคุณต้องการกรองโพสต์ในบริบทนี้ด้วย คุณจะต้องแก้ไขตัวกรอง", "filter_modal.added.context_mismatch_title": "บริบทไม่ตรงกัน!", "filter_modal.added.expired_explanation": "หมวดหมู่ตัวกรองนี้หมดอายุแล้ว คุณจะต้องเปลี่ยนวันหมดอายุสำหรับหมวดหมู่เพื่อนำไปใช้", @@ -363,6 +372,7 @@ "follow_suggestions.who_to_follow": "ติดตามใครดี", "followed_tags": "แฮชแท็กที่ติดตาม", "footer.about": "เกี่ยวกับ", + "footer.about_this_server": "เกี่ยวกับ", "footer.directory": "ไดเรกทอรีโปรไฟล์", "footer.get_app": "รับแอป", "footer.keyboard_shortcuts": "แป้นพิมพ์ลัด", @@ -373,6 +383,8 @@ "generic.saved": "บันทึกแล้ว", "getting_started.heading": "เริ่มต้นใช้งาน", "hashtag.admin_moderation": "เปิดส่วนติดต่อการกลั่นกรองสำหรับ #{name}", + "hashtag.browse": "เรียกดูโพสต์ใน #{hashtag}", + "hashtag.browse_from_account": "เรียกดูโพสต์จาก @{name} ใน #{hashtag}", "hashtag.column_header.tag_mode.all": "และ {additional}", "hashtag.column_header.tag_mode.any": "หรือ {additional}", "hashtag.column_header.tag_mode.none": "โดยไม่มี {additional}", @@ -397,6 +409,7 @@ "hints.profiles.see_more_followers": "ดูผู้ติดตามเพิ่มเติมใน {domain}", "hints.profiles.see_more_follows": "ดูการติดตามเพิ่มเติมใน {domain}", "hints.profiles.see_more_posts": "ดูโพสต์เพิ่มเติมใน {domain}", + "home.column_settings.show_quotes": "แสดงการอ้างอิง", "home.column_settings.show_reblogs": "แสดงการดัน", "home.column_settings.show_replies": "แสดงการตอบกลับ", "home.hide_announcements": "ซ่อนประกาศ", @@ -420,6 +433,7 @@ "interaction_modal.no_account_yet": "ยังไม่มีบัญชี?", "interaction_modal.on_another_server": "ในเซิร์ฟเวอร์อื่น", "interaction_modal.on_this_server": "ในเซิร์ฟเวอร์นี้", + "interaction_modal.title": "ลงชื่อเข้าเพื่อดำเนินการต่อ", "interaction_modal.username_prompt": "เช่น {example}", "intervals.full.days": "{number, plural, other {# วัน}}", "intervals.full.hours": "{number, plural, other {# ชั่วโมง}}", @@ -440,6 +454,7 @@ "keyboard_shortcuts.home": "เปิดเส้นเวลาหน้าแรก", "keyboard_shortcuts.hotkey": "ปุ่มลัด", "keyboard_shortcuts.legend": "แสดงคำอธิบายนี้", + "keyboard_shortcuts.load_more": "โฟกัสปุ่ม \"โหลดเพิ่มเติม\"", "keyboard_shortcuts.local": "เปิดเส้นเวลาในเซิร์ฟเวอร์", "keyboard_shortcuts.mention": "กล่าวถึงผู้สร้าง", "keyboard_shortcuts.muted": "เปิดรายการผู้ใช้ที่ซ่อนอยู่", @@ -448,6 +463,7 @@ "keyboard_shortcuts.open_media": "เปิดสื่อ", "keyboard_shortcuts.pinned": "เปิดรายการโพสต์ที่ปักหมุด", "keyboard_shortcuts.profile": "เปิดโปรไฟล์ของผู้สร้าง", + "keyboard_shortcuts.quote": "อ้างอิงโพสต์", "keyboard_shortcuts.reply": "ตอบกลับโพสต์", "keyboard_shortcuts.requests": "เปิดรายการคำขอติดตาม", "keyboard_shortcuts.search": "โฟกัสแถบค้นหา", @@ -459,6 +475,8 @@ "keyboard_shortcuts.translate": "เพื่อแปลโพสต์", "keyboard_shortcuts.unfocus": "เลิกโฟกัสพื้นที่เขียนข้อความ/การค้นหา", "keyboard_shortcuts.up": "ย้ายขึ้นในรายการ", + "learn_more_link.got_it": "เข้าใจแล้ว", + "learn_more_link.learn_more": "เรียนรู้เพิ่มเติม", "lightbox.close": "ปิด", "lightbox.next": "ถัดไป", "lightbox.previous": "ก่อนหน้า", @@ -523,6 +541,8 @@ "navigation_bar.follows_and_followers": "การติดตามและผู้ติดตาม", "navigation_bar.import_export": "การนำเข้าและการส่งออก", "navigation_bar.lists": "รายการ", + "navigation_bar.live_feed_local": "ฟีดสด (ในเซิร์ฟเวอร์)", + "navigation_bar.live_feed_public": "ฟีดสด (สาธารณะ)", "navigation_bar.logout": "ออกจากระบบ", "navigation_bar.moderation": "การกลั่นกรอง", "navigation_bar.more": "เพิ่มเติม", @@ -531,6 +551,9 @@ "navigation_bar.preferences": "การกำหนดลักษณะ", "navigation_bar.privacy_and_reach": "ความเป็นส่วนตัวและการเข้าถึง", "navigation_bar.search": "ค้นหา", + "navigation_bar.search_trends": "ค้นหา / กำลังนิยม", + "navigation_panel.collapse_lists": "ยุบเมนูรายการ", + "navigation_panel.expand_lists": "ขยายเมนูรายการ", "not_signed_in_indicator.not_signed_in": "คุณจำเป็นต้องเข้าสู่ระบบเพื่อเข้าถึงทรัพยากรนี้", "notification.admin.report": "{name} ได้รายงาน {target}", "notification.admin.report_account": "{name} ได้รายงาน {count, plural, other {# โพสต์}}จาก {target} สำหรับ {category}", @@ -551,6 +574,7 @@ "notification.label.mention": "การกล่าวถึง", "notification.label.private_mention": "การกล่าวถึงแบบส่วนตัว", "notification.label.private_reply": "การตอบกลับแบบส่วนตัว", + "notification.label.quote": "{name} ได้อ้างอิงโพสต์ของคุณ", "notification.label.reply": "การตอบกลับ", "notification.mention": "การกล่าวถึง", "notification.mentioned_you": "{name} ได้กล่าวถึงคุณ", @@ -565,6 +589,7 @@ "notification.moderation_warning.action_suspend": "ระงับบัญชีของคุณแล้ว", "notification.own_poll": "การสำรวจความคิดเห็นของคุณได้สิ้นสุดแล้ว", "notification.poll": "การสำรวจความคิดเห็นที่คุณได้ลงคะแนนได้สิ้นสุดแล้ว", + "notification.quoted_update": "{name} ได้แก้ไขโพสต์ที่คุณได้อ้างอิง", "notification.reblog": "{name} ได้ดันโพสต์ของคุณ", "notification.reblog.name_and_others_with_link": "{name} และ {count, plural, other {# อื่น ๆ}} ได้ดันโพสต์ของคุณ", "notification.relationships_severance_event": "สูญเสียการเชื่อมต่อกับ {name}", @@ -608,6 +633,7 @@ "notifications.column_settings.mention": "การกล่าวถึง:", "notifications.column_settings.poll": "ผลลัพธ์การสำรวจความคิดเห็น:", "notifications.column_settings.push": "การแจ้งเตือนแบบผลัก", + "notifications.column_settings.quote": "การอ้างอิง:", "notifications.column_settings.reblog": "การดัน:", "notifications.column_settings.show": "แสดงในคอลัมน์", "notifications.column_settings.sound": "เล่นเสียง", @@ -683,7 +709,11 @@ "privacy.private.short": "ผู้ติดตาม", "privacy.public.long": "ใครก็ตามที่อยู่ในและนอก Mastodon", "privacy.public.short": "สาธารณะ", + "privacy.quote.anyone": "{visibility}, ใครก็ตามสามารถอ้างอิง", + "privacy.quote.disabled": "{visibility}, ปิดใช้งานการอ้างอิงแล้ว", + "privacy.quote.limited": "{visibility}, จำกัดการอ้างอิงอยู่", "privacy.unlisted.additional": "สิ่งนี้ทำงานเหมือนกับสาธารณะทุกประการ ยกเว้นโพสต์จะไม่ปรากฏในฟีดสดหรือแฮชแท็ก, การสำรวจ หรือการค้นหา Mastodon แม้ว่าคุณได้เลือกรับทั่วทั้งบัญชีก็ตาม", + "privacy.unlisted.long": "ซ่อนจากผลลัพธ์การค้นหา, กำลังนิยม และเส้นเวลาสาธารณะของ Mastodon", "privacy.unlisted.short": "สาธารณะแบบเงียบ", "privacy_policy.last_updated": "อัปเดตล่าสุดเมื่อ {date}", "privacy_policy.title": "นโยบายความเป็นส่วนตัว", @@ -702,6 +732,7 @@ "relative_time.minutes": "{number} นาที", "relative_time.seconds": "{number} วินาที", "relative_time.today": "วันนี้", + "remove_quote_hint.button_label": "เข้าใจแล้ว", "reply_indicator.attachments": "{count, plural, other {# ไฟล์แนบ}}", "reply_indicator.cancel": "ยกเลิก", "reply_indicator.poll": "การสำรวจความคิดเห็น", @@ -796,9 +827,12 @@ "status.bookmark": "เพิ่มที่คั่นหน้า", "status.cancel_reblog_private": "เลิกดัน", "status.cannot_reblog": "ไม่สามารถดันโพสต์นี้", + "status.context.retry": "ลองใหม่", + "status.context.show": "แสดง", "status.continued_thread": "กระทู้ต่อเนื่อง", "status.copy": "คัดลอกลิงก์ไปยังโพสต์", "status.delete": "ลบ", + "status.delete.success": "ลบโพสต์แล้ว", "status.detailed_status": "มุมมองการสนทนาโดยละเอียด", "status.direct": "กล่าวถึง @{name} แบบส่วนตัว", "status.direct_indicator": "การกล่าวถึงแบบส่วนตัว", @@ -807,7 +841,6 @@ "status.edited_x_times": "แก้ไข {count, plural, other {{count} ครั้ง}}", "status.embed": "รับโค้ดฝังตัว", "status.favourite": "ชื่นชอบ", - "status.favourites": "{count, plural, other {รายการโปรด}}", "status.filter": "กรองโพสต์นี้", "status.history.created": "{name} ได้สร้างเมื่อ {date}", "status.history.edited": "{name} ได้แก้ไขเมื่อ {date}", @@ -821,14 +854,16 @@ "status.mute_conversation": "ซ่อนการสนทนา", "status.open": "ขยายโพสต์นี้", "status.pin": "ปักหมุดในโปรไฟล์", + "status.quote_error.limited_account_hint.action": "แสดงต่อไป", + "status.quote_post_author": "อ้างอิงโพสต์โดย @{name}", "status.read_more": "อ่านเพิ่มเติม", "status.reblog": "ดัน", "status.reblogged_by": "{name} ได้ดัน", - "status.reblogs": "{count, plural, other {การดัน}}", "status.reblogs.empty": "ยังไม่มีใครดันโพสต์นี้ เมื่อใครสักคนดัน เขาจะปรากฏที่นี่", "status.redraft": "ลบแล้วร่างใหม่", "status.remove_bookmark": "เอาที่คั่นหน้าออก", "status.remove_favourite": "เอาออกจากรายการโปรด", + "status.remove_quote": "เอาออก", "status.replied_in_thread": "ตอบกลับในกระทู้", "status.replied_to": "ตอบกลับ {name}", "status.reply": "ตอบกลับ", @@ -887,5 +922,12 @@ "video.mute": "ปิดเสียง", "video.pause": "หยุดชั่วคราว", "video.play": "เล่น", - "video.unmute": "เลิกปิดเสียง" + "video.unmute": "เลิกปิดเสียง", + "visibility_modal.button_title": "ตั้งการมองเห็น", + "visibility_modal.header": "การมองเห็นและการโต้ตอบ", + "visibility_modal.privacy_label": "การมองเห็น", + "visibility_modal.quote_followers": "ผู้ติดตามเท่านั้น", + "visibility_modal.quote_nobody": "แค่ฉัน", + "visibility_modal.quote_public": "ใครก็ตาม", + "visibility_modal.save": "บันทึก" } diff --git a/app/javascript/mastodon/locales/tok.json b/app/javascript/mastodon/locales/tok.json index dbc6b4b871c73d..8faa4fa17f7dec 100644 --- a/app/javascript/mastodon/locales/tok.json +++ b/app/javascript/mastodon/locales/tok.json @@ -4,10 +4,10 @@ "about.default_locale": "ante ala", "about.disclaimer": "ilo Mastodon la jan ale li lawa e ona li pana e pona tawa ona. kulupu esun Mastodon gGmbH li lawa e nimi ona.", "about.domain_blocks.no_reason_available": "mi sona ala e tan", - "about.domain_blocks.preamble": "ilo Masoton li ken e ni: sina lukin e toki jan pi ma ilo mute. sina ken toki tawa ona lon kulupu ma. taso, ma ni li ken ala e ni tawa ma ni:", + "about.domain_blocks.preamble": "ilo Mastodon li ken e ni: sina lukin e toki jan pi ma ilo mute. sina ken toki tawa ona lon kulupu ma. taso, ma ni li ken ala e ni tawa ma ni:", "about.domain_blocks.silenced.explanation": "sina lukin ala e toki e jan tan ma ni. taso, sina wile la, sina ken ni.", "about.domain_blocks.silenced.title": "ken lukin lili ", - "about.domain_blocks.suspended.explanation": "sona ale pi ma ni li kama pali ala, li kama esun ala, li kama awen ala la sina ken ala toki tawa jan pi ma ni.", + "about.domain_blocks.suspended.explanation": "sona ale pi ma ni li kama pali ala, li kama esun ala, li kama awen ala la, sina ken ala toki tawa jan pi ma ni.", "about.domain_blocks.suspended.title": "weka", "about.language_label": "toki", "about.not_available": "lon kulupu ni la sina ken alasa ala e sona ni.", @@ -24,11 +24,12 @@ "account.blocking": "mi len e jan ni", "account.cancel_follow_request": "o kute ala", "account.copy": "o pali same e linja pi lipu jan", - "account.direct": "len la o mu e @{name}", + "account.direct": "o mu len e @{name}", "account.disable_notifications": "@{name} li toki la o mu ala e mi", "account.domain_blocking": "mi len e ma ni", "account.edit_profile": "o ante e lipu mi", - "account.enable_notifications": "@{name} li toki la o toki e toki ona tawa mi", + "account.edit_profile_short": "o ante", + "account.enable_notifications": "@{name} li toki la o mu e mi", "account.endorse": "lipu jan la o suli e ni", "account.familiar_followers_many": "{name1} en {name2} en {othersCount, plural, other {jan ante #}} li kute e jan ni", "account.familiar_followers_one": "{name1} li kute e jan ni", @@ -40,13 +41,17 @@ "account.featured_tags.last_status_never": "toki ala li lon", "account.follow": "o kute", "account.follow_back": "jan ni li kute e sina. o kute", + "account.follow_back_short": "o kute", + "account.follow_request": "o wile kute", + "account.follow_request_cancel": "o wile ala kute", + "account.follow_request_cancel_short": "o wile ala kute", "account.followers": "jan kute", "account.followers.empty": "jan ala li kute e jan ni", "account.followers_counter": "{count, plural, other {jan {counter} li kute e ona}}", "account.followers_you_know_counter": "jan {counter} pi kute sama", "account.following": "sina kute e jan ni", "account.following_counter": "{count, plural, other {ona li kute e jan {counter}}}", - "account.follows.empty": "jan ni li kute e jan ala", + "account.follows.empty": "jan ni li kute ala e jan", "account.follows_you": "ona li kute e sina", "account.go_to_profile": "o tawa lipu jan", "account.hide_reblogs": "o lukin ala e pana toki tan @{name}", @@ -56,7 +61,7 @@ "account.link_verified_on": "{date} la mi sona e ni: jan seme li jo e lipu ni", "account.locked_info": "sina wile kute e jan ni la ona o toki e ken", "account.media": "sitelen", - "account.mention": "o mu e jan @{name}", + "account.mention": "o mu e @{name}", "account.moved_to": "jan ni la lipu sin li ni:", "account.mute": "o len e @{name}", "account.mute_notifications_short": "o kute ala e mu tan jan ni", @@ -69,21 +74,20 @@ "account.posts": "toki suli", "account.posts_with_replies": "toki ale", "account.remove_from_followers": "sijelo kute la o weka e sijelo \"{name}\".", - "account.report": "jan @{name} la o toki e ike tawa lawa", - "account.requested": "jan ni o ken e kute sina", - "account.requested_follow": "jan {name} li wile kute e sina", + "account.report": "@{name} la o toki e jaki tawa lawa", + "account.requested_follow": "{name} li wile kute e sina", "account.requests_to_follow_you": "jan ni li wile kute e sina", - "account.share": "o pana e lipu jan @{name}", + "account.share": "o pana e lipu tan @{name}", "account.show_reblogs": "o lukin e pana toki tan @{name}", "account.statuses_counter": "{count, plural, other {toki {counter}}}", - "account.unblock": "o len ala e jan {name}", + "account.unblock": "o len ala e {name}", "account.unblock_domain": "o len ala e ma {domain}", - "account.unblock_domain_short": "o len ala e jan ni", + "account.unblock_domain_short": "o len ala", "account.unblock_short": "o len ala", "account.unendorse": "lipu jan la o suli ala e ni", "account.unfollow": "o kute ala", "account.unmute": "o len ala e @{name}", - "account.unmute_notifications_short": "o kute e mu tan jan ni", + "account.unmute_notifications_short": "o kute e mu", "account.unmute_short": "o len ala", "account_note.placeholder": "o luka e ni la sona pi sina taso", "admin.dashboard.daily_retention": "nanpa pi awen jan lon tenpo suno", @@ -108,28 +112,14 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "jan li ken ala lukin la o pana e toki pi sona lukin…", "alt_text_modal.done": "o pana", "announcement.announcement": "toki suli", - "annual_report.summary.archetype.booster": "jan ni li alasa e pona", - "annual_report.summary.archetype.lurker": "jan ni li lukin taso", - "annual_report.summary.archetype.oracle": "jan ni li sona suli", - "annual_report.summary.archetype.pollster": "jan ni li wile sona e pilin jan", - "annual_report.summary.archetype.replier": "jan ni li toki tawa jan mute", - "annual_report.summary.followers.followers": "jan kute sina", - "annual_report.summary.followers.total": "ale la {count}", - "annual_report.summary.here_it_is": "toki lili la tenpo sike nanpa {year} li sama ni tawa sina:", - "annual_report.summary.highlighted_post.by_favourites": "toki pi pona suli", - "annual_report.summary.highlighted_post.by_reblogs": "toki pi pana suli", - "annual_report.summary.highlighted_post.by_replies": "toki li jo e toki kama pi nanpa wan", - "annual_report.summary.highlighted_post.possessive": "tan jan {name}", "annual_report.summary.most_used_app.most_used_app": "ilo pi kepeken suli", "annual_report.summary.most_used_hashtag.most_used_hashtag": "kulupu toki pi kepeken suli", - "annual_report.summary.most_used_hashtag.none": "ala", "annual_report.summary.new_posts.new_posts": "toki suli sin", "annual_report.summary.percentile.text": "ni la sina nanpa sewipi jan ale lon {domain}.", "annual_report.summary.percentile.we_wont_tell_bernie": "sona ni li len tawa ale.", - "annual_report.summary.thanks": "sina jan pi kulupu Mastodon la sina pona a!", "attachments_list.unprocessed": "(nasin open)", "audio.hide": "o len e kalama", - "block_modal.remote_users_caveat": "mi pana e wile sina tawa ma {domain}. taso, o sona: ma li ken kepeken nasin len ante la pakala li ken lon. toki pi lukin ale la jan pi ma ala li ken lukin.", + "block_modal.remote_users_caveat": "mi pana e wile sina tawa ma {domain}. taso ma li ken kepeken nasin ante la pakala li ken. jan li awen ken lukin e toki kepeken sijelo ala.", "block_modal.show_less": "o lili e toki", "block_modal.show_more": "o suli e toki", "block_modal.they_cant_mention": "ona li ken ala toki tawa sina li ken ala kute e sina.", @@ -152,6 +142,8 @@ "bundle_modal_error.close": "o pini", "bundle_modal_error.message": "ilo li wile kama e ijo ni, taso pakala li lon.", "bundle_modal_error.retry": "o alasa sin", + "carousel.current": "lipu {current, number} / {max, number}", + "carousel.slide": "lipu {current, number} lon {max, number}", "closed_registrations.other_server_instructions": "kulupu Mastodon la lawa mute li lon. sina ken pali e sijelo lon ma ante la sina awen ken lukin e ijo pi ma ni.", "closed_registrations_modal.description": "tenpo ni la, sina ken ala pali e jan lon ma {domain}. taso sina wile kepeken ilo Mastodon la, sina ken pali e jan lon ma ante lon ala ma {domain}.", "closed_registrations_modal.find_another_server": "o alasa e ma ante", @@ -168,7 +160,9 @@ "column.edit_list": "o ante e kulupu", "column.favourites": "ijo pona", "column.firehose": "toki pi tenpo ni", - "column.follow_requests": "wile alasa pi jan ante", + "column.firehose_local": "ma ni la toki pi tenpo ni", + "column.firehose_singular": "toki pi tenpo ni", + "column.follow_requests": "wile kute", "column.home": "lipu open", "column.list_members": "o ante e kulupu jan", "column.lists": "kulupu lipu", @@ -176,25 +170,26 @@ "column.notifications": "mu pi sona sin", "column.pins": "toki sewi", "column.public": "toki pi ma poka ale", - "column_back_button.label": "o tawa monsi", - "column_header.hide_settings": "o len e lawa", + "column_back_button.label": "o weka", + "column_header.hide_settings": "o len e nasin lawa", "column_header.moveLeft_settings": "poki toki ni o tawa ni ←", "column_header.moveRight_settings": "poki toki ni o tawa ni →", "column_header.pin": "o sewi", - "column_header.show_settings": "o lukin e lawa", + "column_header.show_settings": "o lukin e nasin lawa", "column_header.unpin": "o sewi ala", "column_search.cancel": "o ala", "community.column_settings.local_only": "toki tan ni taso", "community.column_settings.media_only": "sitelen taso", "community.column_settings.remote_only": "toki tan ante taso", + "compose.error.blank_post": "toki li wile.", "compose.language.change": "o ante e nasin toki", "compose.language.search": "o alasa e nasin toki...", - "compose.published.body": "toki li pana.", + "compose.published.body": "mi pana e toki.", "compose.published.open": "o lukin", - "compose.saved.body": "ilo li awen e ijo pana sina.", - "compose_form.direct_message_warning_learn_more": "o kama sona", + "compose.saved.body": "mi awen e toki sina.", + "compose_form.direct_message_warning_learn_more": "o sona", "compose_form.encryption_warning": "ilo Mastodon la toki li len ala. o pana ala e sona suli pi ken pakala.", - "compose_form.lock_disclaimer": "lipu sina li open, li {locked} ala. jan ale li ken kama kute e sina, li ken lukin e toki sama ni.", + "compose_form.lock_disclaimer": "lipu sina li {locked} ala. jan ale li ken kama kute e sina li ken lukin e toki sama ni.", "compose_form.lock_disclaimer.lock": "pini", "compose_form.placeholder": "sina wile toki e seme?", "compose_form.poll.duration": "tenpo pana", @@ -238,6 +233,9 @@ "confirmations.missing_alt_text.secondary": "o pana a", "confirmations.missing_alt_text.title": "o pana ala pana e toki pi sona lukin?", "confirmations.mute.confirm": "o len", + "confirmations.private_quote_notify.confirm": "o pana e toki ni tawa ale", + "confirmations.private_quote_notify.do_not_show_again": "o toki ala e toki ni", + "confirmations.quiet_post_quote_info.dismiss": "o toki ala e ni tawa mi", "confirmations.quiet_post_quote_info.got_it": "sona", "confirmations.redraft.confirm": "o weka o pali sin e toki", "confirmations.redraft.message": "pali sin e toki ni la sina wile ala wile weka e ona? sina ni la suli pi toki ni en wawa pi toki ni li weka. kin la toki lon toki ni li jo e mama ala.", @@ -247,9 +245,10 @@ "confirmations.remove_from_followers.title": "o kama ala kama kute ala e jan?", "confirmations.revoke_quote.confirm": "o weka e toki tan lipu Mastodon", "confirmations.revoke_quote.title": "sina wile weka ala weka e toki?", + "confirmations.unblock.confirm": "o len ala", + "confirmations.unblock.title": "o len ala e {name}?", "confirmations.unfollow.confirm": "o kute ala", - "confirmations.unfollow.message": "sina o wile ala wile pini kute e jan {name}?", - "confirmations.unfollow.title": "sina wile ala wile pini kute?", + "confirmations.unfollow.title": "o kute ala e {name}?", "content_warning.hide": "o len", "content_warning.show": "o lukin a", "content_warning.show_more": "o lukin", @@ -290,10 +289,12 @@ "domain_pill.your_handle": "nimi sina:", "domain_pill.your_server": "ni li ma sina lon ilo. toki ale sina li lon ma ni. ma li ike tawa sina la, sina ken tawa ma ante. ni la jan kute sina li tawa sama.", "domain_pill.your_username": "ni li nimi sina. ma sina la, sina taso li jo e ona. jan mute li lon ma ante la, ona li ken jo e nimi sama.", + "dropdown.empty": "o wile e ijo wan", "embed.instructions": "o pana e toki ni la, toki li lon lipu ante. ", "embed.preview": "ni li jo e sitelen ni:", "emoji_button.activity": "musi", "emoji_button.clear": "o weka", + "emoji_button.custom": "pali sin", "emoji_button.flags": "len ma", "emoji_button.food": "moku", "emoji_button.label": "o pana e sitelen pilin", @@ -329,11 +330,10 @@ "explore.trending_links": "sin", "explore.trending_statuses": "toki", "explore.trending_tags": "kulupu pi lipu suli", + "featured_carousel.current": "toki{current, number} / {max, number}", "featured_carousel.header": "{count, plural, other {toki sewi}}", - "featured_carousel.next": "kama", - "featured_carousel.post": "toki", - "featured_carousel.previous": "pini", - "featured_carousel.slide": "lipu {total} la lipu nanpa {index}", + "featured_carousel.slide": "toki {current, number} lon {max, number}", + "filter_modal.added.review_and_configure_title": "o alasa e lawa", "filter_modal.added.settings_link": "lipu lawa", "filter_modal.select_filter.expired": "tenpo pini", "filter_modal.select_filter.search": "o alasa anu pali", @@ -374,8 +374,10 @@ "hashtag.counter_by_accounts": "{count, plural, other {jan {counter}}}", "hashtag.counter_by_uses": "{count, plural, other {toki {counter}}}", "hashtag.counter_by_uses_today": "{count, plural, other {toki poka {counter}}}", + "hashtag.feature": "lipu jan la o suli e ni", "hashtag.follow": "o kute e kulupu lipu", "hashtag.mute": "o kute ala e kulupu #{hashtag}", + "hashtag.unfeature": "lipu jan la o suli ala e ni", "hashtag.unfollow": "o kute ala e kulupu lipu", "hints.profiles.followers_may_be_missing": "jan kute li ken weka.", "hints.profiles.see_more_followers": "o lukin e jan ni lon ma {domain}: ona li kute e jan ni.", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index 9d61ed20531467..98291db5c0d051 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -24,10 +24,11 @@ "account.blocking": "Engelleme", "account.cancel_follow_request": "Takip isteğini geri çek", "account.copy": "Gönderi bağlantısını kopyala", - "account.direct": "@{name} kullanıcısına özel olarak değin", + "account.direct": "@{name} kullanıcısından özel olarak bahset", "account.disable_notifications": "@{name} kişisinin gönderi bildirimlerini kapat", "account.domain_blocking": "Alan adını engelleme", "account.edit_profile": "Profili düzenle", + "account.edit_profile_short": "Düzenle", "account.enable_notifications": "@{name} kişisinin gönderi bildirimlerini aç", "account.endorse": "Profilimde öne çıkar", "account.familiar_followers_many": "{name1}, {name2}, {othersCount, plural, one {# diğer} other {# diğer}} bildiğiniz kişi tarafından takip ediliyor", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Gönderi yok", "account.follow": "Takip et", "account.follow_back": "Geri takip et", + "account.follow_back_short": "Geri takip et", + "account.follow_request": "Takip isteği gönder", + "account.follow_request_cancel": "İsteği iptal et", + "account.follow_request_cancel_short": "İptal", + "account.follow_request_short": "İstek", "account.followers": "Takipçi", "account.followers.empty": "Henüz kimse bu kullanıcıyı takip etmiyor.", "account.followers_counter": "{count, plural, one {{counter} takipçi} other {{counter} takipçi}}", @@ -49,7 +55,7 @@ "account.follows.empty": "Bu kullanıcı henüz kimseyi takip etmiyor.", "account.follows_you": "Seni takip ediyor", "account.go_to_profile": "Profile git", - "account.hide_reblogs": "@{name} kişisinin boostlarını gizle", + "account.hide_reblogs": "@{name} kişisinin yeniden paylaşımlarını gizle", "account.in_memoriam": "Hatırasına.", "account.joined_short": "Katıldı", "account.languages": "Abone olunan dilleri değiştir", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Gönderiler ve yanıtlar", "account.remove_from_followers": "{name} takipçilerinden kaldır", "account.report": "@{name} adlı kişiyi bildir", - "account.requested": "Onay bekleniyor. Takip isteğini iptal etmek için tıklayın", "account.requested_follow": "{name} size takip isteği gönderdi", "account.requests_to_follow_you": "Size takip isteği gönderdi", "account.share": "@{name} adlı kişinin profilini paylaş", @@ -108,25 +113,51 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Bunu görme bozukluğu yaşayan kişiler için betimleyin…", "alt_text_modal.done": "Tamamlandı", "announcement.announcement": "Duyuru", - "annual_report.summary.archetype.booster": "Trend takipçisi", - "annual_report.summary.archetype.lurker": "Gizli meraklı", - "annual_report.summary.archetype.oracle": "Kahin", - "annual_report.summary.archetype.pollster": "Anketör", - "annual_report.summary.archetype.replier": "Sosyal kelebek", - "annual_report.summary.followers.followers": "takipçiler", - "annual_report.summary.followers.total": "{count} toplam", - "annual_report.summary.here_it_is": "İşte {year} yılı değerlendirmeniz:", - "annual_report.summary.highlighted_post.by_favourites": "en çok beğenilen gönderi", - "annual_report.summary.highlighted_post.by_reblogs": "en çok paylaşılan gönderi", - "annual_report.summary.highlighted_post.by_replies": "en çok yanıt alan gönderi", - "annual_report.summary.highlighted_post.possessive": "{name}", + "annual_report.announcement.action_build": "Wrapstodon'umu Oluştur", + "annual_report.announcement.action_dismiss": "Hayır teşekkürler", + "annual_report.announcement.action_view": "Wrapstodon'umu Görüntüle", + "annual_report.announcement.description": "Mastodon'daki geçen yıldaki etkileşimleriniz hakkında daha fazlasını öğrenin.", + "annual_report.announcement.title": "{year} Wrapstodon'u yayında", + "annual_report.shared_page.donate": "Bağış Yap", + "annual_report.shared_page.footer": "Mastodon ekibi tarafından {heart} ile oluşturulmuştur", + "annual_report.shared_page.footer_server_info": "{username}, Mastodon tarafından desteklenen birçok topluluktan biri olan {domain} kullanıyor.", + "annual_report.summary.archetype.booster.desc_public": "{name} mükemmel bir hedefle diğer içerik üreticilerini desteklemek için paylaşımları yeniden yayınlamaya devam etti.", + "annual_report.summary.archetype.booster.desc_self": "Mükemmel bir hedefle diğer içerik üreticilerini desteklemek için paylaşımları yeniden yayınlamaya devam ettin.", + "annual_report.summary.archetype.booster.name": "Okçu", + "annual_report.summary.archetype.die_drei_fragezeichen": "???", + "annual_report.summary.archetype.lurker.desc_public": "{name} orada, bir yerlerde, kendi sessiz tarzıyla Mastodon'un tadını çıkarıyor, biliyoruz.", + "annual_report.summary.archetype.lurker.desc_self": "Orada, bir yerlerde, kendi sessiz tarzınla Mastodon'un tadını çıkarıyorsun, biliyoruz.", + "annual_report.summary.archetype.lurker.name": "Stoacı", + "annual_report.summary.archetype.oracle.desc_public": "{name} yanıtlardan daha çok yeni gönderi oluşturarak Mastodon'u taze ve geleceğe dönük tuttu.", + "annual_report.summary.archetype.oracle.desc_self": "Yanıtlardan daha çok yeni gönderi oluşturarak Mastodon'u taze ve geleceğe dönük tuttun.", + "annual_report.summary.archetype.oracle.name": "Kahin", + "annual_report.summary.archetype.pollster.desc_public": "{name} diğer gönderi türlerinden çok anket oluşturarak Mastodon'da merak uyandırdı.", + "annual_report.summary.archetype.pollster.desc_self": "Diğer gönderi türlerinden çok anket oluşturarak Mastodon'da merak uyandırdın.", + "annual_report.summary.archetype.pollster.name": "Meraklı", + "annual_report.summary.archetype.replier.desc_public": "{name} sık sık diğer kullanıcıların gönderilerine yanıt vererek Mastodon'a yeni tartışmalar kazandırdı.", + "annual_report.summary.archetype.replier.desc_self": "Sık sık diğer kullanıcıların gönderilerine yanıt vererek Mastodon'a yeni tartışmalar kazandırdın.", + "annual_report.summary.archetype.replier.name": "Kelebek", + "annual_report.summary.archetype.reveal": "Arketipimi Göster", + "annual_report.summary.archetype.reveal_description": "Mastodon'un bir parçası olduğun için teşekkürler! {year} yılında hangi arketipi temsil ettiğini öğrenme zamanı geldi.", + "annual_report.summary.archetype.title_public": "{name} kullanıcısının arketipi", + "annual_report.summary.archetype.title_self": "Arketipin", + "annual_report.summary.close": "Kapat", + "annual_report.summary.copy_link": "Bağlantıyı kopyala", + "annual_report.summary.followers.new_followers": "{count, plural, one {1 yeni takipçi} other {# yeni takipçi}}", + "annual_report.summary.highlighted_post.boost_count": "Bu gönderi {count, plural, one {1 kez} other {# kez}} yeniden paylaşıldı.", + "annual_report.summary.highlighted_post.favourite_count": "Bu gönderi {count, plural, one {1 kez} other {# kez}} beğenildi.", + "annual_report.summary.highlighted_post.reply_count": "Bu gönderi {count, plural, one {1 yanıt} other {# yanıt}} aldı.", + "annual_report.summary.highlighted_post.title": "En popüler gönderi", "annual_report.summary.most_used_app.most_used_app": "en çok kullanılan uygulama", "annual_report.summary.most_used_hashtag.most_used_hashtag": "en çok kullanılan etiket", - "annual_report.summary.most_used_hashtag.none": "Yok", + "annual_report.summary.most_used_hashtag.used_count": "Bu etiketi {count, plural, one {1 gönderi} other {# gönderi}}de kullandınız.", + "annual_report.summary.most_used_hashtag.used_count_public": "{name} bu etiketi {count, plural, one {1 gönderi} other {# gönderi}}de kullandı.", "annual_report.summary.new_posts.new_posts": "yeni gönderiler", "annual_report.summary.percentile.text": "{domain} kullanıcılarınınüst dilimindesiniz", "annual_report.summary.percentile.we_wont_tell_bernie": "Bernie'ye söylemeyiz.", - "annual_report.summary.thanks": "Mastodon'un bir parçası olduğunuz için teşekkürler!", + "annual_report.summary.share_elsewhere": "Başka yerde paylaş", + "annual_report.summary.share_message": "{archetype} arketipindeyim!", + "annual_report.summary.share_on_mastodon": "Mastodon'da Paylaş", "attachments_list.unprocessed": "(işlenmemiş)", "audio.hide": "Sesi gizle", "block_modal.remote_users_caveat": "{domain} sunucusundan kararınıza saygı duymasını isteyeceğiz. Ancak, Uymaları garanti değildir çünkü bazı sunucular engellemeyi farklı şekilde yapıyorlar. Herkese açık gönderiler giriş yapmamış kullanıcılara görüntülenmeye devam edebilir.", @@ -152,6 +183,8 @@ "bundle_modal_error.close": "Kapat", "bundle_modal_error.message": "Bu ekran yüklenirken bir şeyler ters gitti.", "bundle_modal_error.retry": "Tekrar deneyin", + "carousel.current": "Slayt {current, number} / {max, number}", + "carousel.slide": "Slayt {current, number} / {max, number}", "closed_registrations.other_server_instructions": "Mastodon merkeziyetsiz olduğu için, başka bir sunucuda bir hesap oluşturabilir ve bu sunucuyla etkileşimde bulunmaya devam edebilirsiniz.", "closed_registrations_modal.description": "{domain} adresinde hesap oluşturmak şu an mümkün değil ancak unutmayın ki Mastodon kullanmak için özellikle {domain} adresinde hesap oluşturmanız gerekmez.", "closed_registrations_modal.find_another_server": "Başka sunucu bul", @@ -168,6 +201,8 @@ "column.edit_list": "Listeyi düzenle", "column.favourites": "Gözdelerin", "column.firehose": "Anlık Akışlar", + "column.firehose_local": "Bu sunucunun canlı akışı", + "column.firehose_singular": "Canlı akış", "column.follow_requests": "Takip istekleri", "column.home": "Anasayfa", "column.list_members": "Liste üyelerini yönet", @@ -187,6 +222,7 @@ "community.column_settings.local_only": "Sadece yerel", "community.column_settings.media_only": "Sadece medya", "community.column_settings.remote_only": "Sadece uzak", + "compose.error.blank_post": "Gönderiler boş bırakılamaz.", "compose.language.change": "Dili değiştir", "compose.language.search": "Dilleri ara...", "compose.published.body": "Gönderi yayınlandı.", @@ -239,6 +275,11 @@ "confirmations.missing_alt_text.secondary": "Yine de gönder", "confirmations.missing_alt_text.title": "Alternatif metin ekle?", "confirmations.mute.confirm": "Sessize al", + "confirmations.private_quote_notify.cancel": "Düzenlemeye dön", + "confirmations.private_quote_notify.confirm": "Gönderiyi yayınla", + "confirmations.private_quote_notify.do_not_show_again": "Bu iletiyi bana tekrar gösterme", + "confirmations.private_quote_notify.message": "Alıntı yaptığınız kişi ve diğer bahsedilen kişiler, sizi takip etmiyor olsalar bile bildirim alacak ve gönderinizi görüntüleyebileceklerdir.", + "confirmations.private_quote_notify.title": "Takipçiler ve bahsedilen kullanıcılarla paylaş?", "confirmations.quiet_post_quote_info.dismiss": "Bana bir daha hatırlatma", "confirmations.quiet_post_quote_info.got_it": "Anladım", "confirmations.quiet_post_quote_info.message": "Sessiz bir genel gönderiyi alıntıladığınızda, gönderiniz öne çıkan zaman çizelgelerinden gizlenir.", @@ -252,9 +293,12 @@ "confirmations.revoke_quote.confirm": "Gönderiyi kaldır", "confirmations.revoke_quote.message": "Bu işlem geri alınamaz.", "confirmations.revoke_quote.title": "Gönderiyi silmek ister misiniz?", + "confirmations.unblock.confirm": "Engeli kaldır", + "confirmations.unblock.title": "{name} için engel kaldırılsın mı?", "confirmations.unfollow.confirm": "Takibi bırak", - "confirmations.unfollow.message": "{name} adlı kullanıcıyı takibi bırakmak istediğinden emin misin?", - "confirmations.unfollow.title": "Kullanıcıyı takipten çık?", + "confirmations.unfollow.title": "{name} takipten çıkılsın mı?", + "confirmations.withdraw_request.confirm": "İsteği geri çek", + "confirmations.withdraw_request.title": "{name} takip isteği geri çekilsin mi?", "content_warning.hide": "Gönderiyi gizle", "content_warning.show": "Yine de göster", "content_warning.show_more": "Daha fazla göster", @@ -325,6 +369,7 @@ "empty_column.bookmarked_statuses": "Henüz yer imine eklediğin toot yok. Bir tanesi yer imine eklendiğinde burada görünür.", "empty_column.community": "Yerel zaman çizelgesi boş. Daha fazla eğlence için herkese açık bir gönderi paylaşın!", "empty_column.direct": "Henüz doğrudan değinmeniz yok. Bir tane gönderdiğinizde veya aldığınızda burada listelenecek.", + "empty_column.disabled_feed": "Bu akış sunucu yöneticileri tarafından devre dışı bırakılmıştır.", "empty_column.domain_blocks": "Henüz engellenmiş bir alan adı yok.", "empty_column.explore_statuses": "Şu an öne çıkan birşey yok. Daha sonra tekrar bakın!", "empty_column.favourited_statuses": "Henüz bir gönderiyi favorilerinize eklememişsiniz. Bir gönderiyi favorilerinize eklediğinizde burada görünecek.", @@ -338,6 +383,7 @@ "empty_column.notification_requests": "Hepsi tamam! Burada yeni bir şey yok. Yeni bildirim aldığınızda, ayarlarınıza göre burada görüntülenecekler.", "empty_column.notifications": "Henüz bildiriminiz yok. Sohbete başlamak için başkalarıyla etkileşim kurun.", "empty_column.public": "Burada hiçbir şey yok! Herkese açık bir şeyler yazın veya burayı doldurmak için diğer sunuculardaki kullanıcıları takip edin", + "error.no_hashtag_feed_access": "Bu etiketi görüntülemek ve takip etmek için katılın veya giriş yapın.", "error.unexpected_crash.explanation": "Bizim kodumuzdaki bir hatadan ya da tarayıcı uyumluluk sorunundan dolayı, bu sayfa düzgün görüntülenemedi.", "error.unexpected_crash.explanation_addons": "Bu sayfa doğru görüntülenemedi. Bu hata büyük olasılıkla bir tarayıcı eklentisinden veya otomatik çeviri araçlarından kaynaklanır.", "error.unexpected_crash.next_steps": "Sayfayı yenilemeyi deneyin. Eğer bu yardımcı olmazsa, Mastodon'u farklı bir tarayıcı ya da yerel uygulama üzerinden kullanabilirsiniz.", @@ -349,11 +395,9 @@ "explore.trending_links": "Haberler", "explore.trending_statuses": "Gönderiler", "explore.trending_tags": "Etiketler", + "featured_carousel.current": "Gönderi {current, number} / {max, number}", "featured_carousel.header": "{count, plural, one {{counter} Sabitlenmiş Gönderi} other {{counter} Sabitlenmiş Gönderi}}", - "featured_carousel.next": "Sonraki", - "featured_carousel.post": "Gönderi", - "featured_carousel.previous": "Önceki", - "featured_carousel.slide": "{index}/{total}", + "featured_carousel.slide": "Gönderi {current, number} / {max, number}", "filter_modal.added.context_mismatch_explanation": "Bu süzgeç kategorisi, bu gönderide eriştiğin bağlama uymuyor. Eğer gönderinin bu bağlamda da filtrelenmesini istiyorsanız, süzgeci düzenlemeniz gerekiyor.", "filter_modal.added.context_mismatch_title": "Bağlam uyumsuzluğu!", "filter_modal.added.expired_explanation": "Bu süzgeç kategorisinin süresi dolmuş, süzgeci uygulamak için bitiş tarihini değiştirmeniz gerekiyor.", @@ -396,6 +440,9 @@ "follow_suggestions.who_to_follow": "Takip edebileceklerin", "followed_tags": "Takip edilen etiketler", "footer.about": "Hakkında", + "footer.about_mastodon": "Mastodon Hakkında", + "footer.about_server": "{domain} Hakkında", + "footer.about_this_server": "Hakkında", "footer.directory": "Profil dizini", "footer.get_app": "Uygulamayı indir", "footer.keyboard_shortcuts": "Klavye kısayolları", @@ -497,6 +544,7 @@ "keyboard_shortcuts.toggle_hidden": "CW'den önceki yazıyı göstermek/gizlemek için", "keyboard_shortcuts.toggle_sensitivity": "Medyayı göstermek/gizlemek için", "keyboard_shortcuts.toot": "Yeni bir gönderi başlat", + "keyboard_shortcuts.top": "Listenin üstüne taşı", "keyboard_shortcuts.translate": "bir gönderiyi çevirmek için", "keyboard_shortcuts.unfocus": "Aramada bir gönderiye odaklanmamak için", "keyboard_shortcuts.up": "Listede yukarıya çıkmak için", @@ -745,7 +793,9 @@ "privacy.unlisted.short": "Sessizce herkese açık", "privacy_policy.last_updated": "Son güncelleme {date}", "privacy_policy.title": "Gizlilik Politikası", + "quote_error.edit": "Gönderi düzenlenirken alıntılar eklenemez.", "quote_error.poll": "Anketlerde alıntıya izin verilmez.", + "quote_error.private_mentions": "Doğrudan atıfta bulunarak alıntı yapma izni yoktur.", "quote_error.quote": "Bir seferde tek bir alıntıya izin var.", "quote_error.unauthorized": "Bu gönderiyi alıntılamaya yetkiniz yok.", "quote_error.upload": "Medya eklentilerini alıntılamaya izin yok.", @@ -865,8 +915,12 @@ "status.cannot_quote": "Bu gönderiyi alıntılamaya izniniz yok", "status.cannot_reblog": "Bu gönderi yeniden paylaşılamaz", "status.contains_quote": "Alıntı içeriyor", - "status.context.load_new_replies": "Yeni yanıtlar mevcut", - "status.context.loading": "Daha fazla yanıt için kontrol ediliyor", + "status.context.loading": "Daha fazla yanıt yükleniyor", + "status.context.loading_error": "Yeni yanıtlar yüklenemiyor", + "status.context.loading_success": "Yeni yanıtlar yüklendi", + "status.context.more_replies_found": "Daha fazla yanıt bulundu", + "status.context.retry": "Yeniden dene", + "status.context.show": "Göster", "status.continued_thread": "Devam eden akış", "status.copy": "Gönderi bağlantısını kopyala", "status.delete": "Sil", @@ -879,7 +933,7 @@ "status.edited_x_times": "{count, plural, one {{count} kez} other {{count} kez}} düzenlendi", "status.embed": "Gömme kodunu al", "status.favourite": "Favori", - "status.favourites": "{count, plural, one {beğeni} other {beğeni}}", + "status.favourites_count": "{count, plural, one {{counter} favori} other {{counter} favori}}", "status.filter": "Bu gönderiyi süzgeçle", "status.history.created": "{name} oluşturdu {date}", "status.history.edited": "{name} düzenledi {date}", @@ -895,9 +949,12 @@ "status.pin": "Profile sabitle", "status.quote": "Teklif", "status.quote.cancel": "Teklifi iptal et", + "status.quote_error.blocked_account_hint.title": "Bu gönderi @{name} kişisini engellediğiniz için gizlenmiştir.", + "status.quote_error.blocked_domain_hint.title": "Bu gönderi {domain} adresini engellediğiniz için gizlenmiştir.", "status.quote_error.filtered": "Bazı filtrelerinizden dolayı gizlenmiştir", "status.quote_error.limited_account_hint.action": "Yine de göster", "status.quote_error.limited_account_hint.title": "Bu hesap {domain} moderatörleri tarafından gizlendi.", + "status.quote_error.muted_account_hint.title": "Bu gönderi @{name} kişisini sessize aldığınız için gizlenmiştir.", "status.quote_error.not_available": "Gönderi kullanılamıyor", "status.quote_error.pending_approval": "Gönderi beklemede", "status.quote_error.pending_approval_popout.body": "Mastodon'da, birinin sizi alıntılayıp alıntılayamayacağını kontrol edebilirsiniz. Bu gönderi, orijinal yazarın onayını alma sürecinde beklemede.", @@ -908,15 +965,17 @@ "status.quote_policy_change": "Kimin alıntı yapabileceğini değiştirin", "status.quote_post_author": "@{name} adlı kullanıcının bir gönderisini alıntıladı", "status.quote_private": "Özel gönderiler alıntılanamaz", - "status.quotes": "{count, plural, one {# alıntı} other {# alıntı}}", "status.quotes.empty": "Henüz hiç kimse bu gönderiyi alıntılamadı. Herhangi bir kullanıcı alıntıladığında burada görüntülenecek.", + "status.quotes.local_other_disclaimer": "Yazar tarafından reddedilen alıntılar gösterilmez.", + "status.quotes.remote_other_disclaimer": "Yalnızca {domain} adresinden gelen alıntılar burada gösterilir. Yazar tarafından reddedilen alıntılar gösterilmez.", + "status.quotes_count": "{count, plural, one {{counter} alıntı} other {{counter} alıntı}}", "status.read_more": "Devamını okuyun", "status.reblog": "Yeniden paylaş", "status.reblog_or_quote": "Yükselt veya alıntıla", "status.reblog_private": "Takipçilerinizle tekrar paylaşın", "status.reblogged_by": "{name} yeniden paylaştı", - "status.reblogs": "{count, plural, one {yeniden paylaşım} other {yeniden paylaşım}}", "status.reblogs.empty": "Henüz hiç kimse bu gönderiyi yeniden paylaşmadı. Herhangi bir kullanıcı yeniden paylaştığında burada görüntülenecek.", + "status.reblogs_count": "{count, plural, one {{counter} yeniden paylaşım} other {{counter} yeniden paylaşım}}", "status.redraft": "Sil,Düzenle ve yeniden-paylaş", "status.remove_bookmark": "Yer işaretini kaldır", "status.remove_favourite": "Favorilerden kaldır", @@ -990,6 +1049,8 @@ "video.volume_down": "Sesi kıs", "video.volume_up": "Sesi yükselt", "visibility_modal.button_title": "Görünürlüğü ayarla", + "visibility_modal.direct_quote_warning.text": "Mevcut ayarları kaydederseniz, gömülü alıntı bir bağlantıya dönüştürülür.", + "visibility_modal.direct_quote_warning.title": "Alıntılar özel bahsetmelere eklenemez", "visibility_modal.header": "Görünürlük ve etkileşim", "visibility_modal.helper.direct_quoting": "Mastodon'da özel değiniler başkaları tarafından alıntılanamaz.", "visibility_modal.helper.privacy_editing": "Gönderi yayınlandıktan sonra görünürlük değiştirilemez.", diff --git a/app/javascript/mastodon/locales/tt.json b/app/javascript/mastodon/locales/tt.json index 4c454c37fb23be..6123b1999703d8 100644 --- a/app/javascript/mastodon/locales/tt.json +++ b/app/javascript/mastodon/locales/tt.json @@ -49,7 +49,6 @@ "account.posts": "Язма", "account.posts_with_replies": "Язма һәм җавап", "account.report": "@{name} кулланучыга шикаять итү", - "account.requested": "Awaiting approval", "account.requested_follow": "{name} Сезгә язылу соравын җиберде", "account.share": "@{name} профиле белән уртаклашу", "account.show_reblogs": "Күрсәтергә көчәйтү нче @{name}", @@ -147,7 +146,6 @@ "confirmations.mute.confirm": "Тавышсыз", "confirmations.redraft.confirm": "Бетерү & эшкәртү", "confirmations.unfollow.confirm": "Язылуны туктату", - "confirmations.unfollow.message": "Сез язылудан баш тартырга телисез {name}?", "conversation.delete": "Сөйләшүне бетерегез", "conversation.mark_as_read": "Укылганны Ничек билгеләргә", "conversation.open": "Сөйләшүне карау", diff --git a/app/javascript/mastodon/locales/ug.json b/app/javascript/mastodon/locales/ug.json index 527457ca378a60..25010a8339a404 100644 --- a/app/javascript/mastodon/locales/ug.json +++ b/app/javascript/mastodon/locales/ug.json @@ -9,11 +9,27 @@ "account.badges.group": "گۇرۇپپا", "account.block": "@{name} نى توس", "account.block_domain": "{domain} دائىرىنى توس", + "account.block_short": "توس", + "account.blocked": "توسۇلدى", + "account.blocking": "توسۇۋاتىدۇ", "account.cancel_follow_request": "ئەگىشىش ئىلتىماسىدىن ۋاز كەچ", + "account.copy": "تەرجىمىھال ئۇلانمىسىنى كۆچۈر", + "account.direct": "@{name} نى يوشۇرۇن ئاتا", + "account.edit_profile_short": "تەھرىر", + "account.follow_request_cancel_short": "ۋاز كەچ", + "account.follow_request_short": "ئىلتىماس", + "account.followers": "ئەگەشكۈچى", + "account.followers.empty": "تېخى ھېچكىم بۇ كىشىگە ئەگەشمىدى.", + "account.followers_counter": "{count, plural, one {{counter} ئەگەشكۈچى} other {{counter} ئەگەشكۈچى}}", + "account.followers_you_know_counter": "تونۇيدىغىنىڭىز {counter}", + "account.following": "ئەگىشىۋاتىدۇ", + "account.following_counter": "{count, plural, one {{counter} ئەگىشىۋاتىدۇ} other {{counter} ئەگىشىۋاتىدۇ}}", + "account.follows.empty": "بۇ ئىشلەتكۈچى تېخى ھېچكىمگە ئەگەشمىدى.", + "account.follows_you": "سىزگە ئەگەشتى", + "account.go_to_profile": "تەرجىمىھالغا يۆتكەل", "account.posts": "يازما", "account.posts_with_replies": "يازما ۋە ئىنكاس", "account.report": "@{name} نى پاش قىل", - "account.requested": "Awaiting approval", "account_note.placeholder": "چېكىلسە ئىزاھات قوشىدۇ", "column.pins": "چوققىلانغان يازما", "community.column_settings.media_only": "ۋاسىتەلا", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index 913ef41d74495b..38ec3d3e1c21f8 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -26,8 +26,12 @@ "account.direct": "Особиста згадка @{name}", "account.disable_notifications": "Не повідомляти мене про дописи @{name}", "account.edit_profile": "Редагувати профіль", + "account.edit_profile_short": "Редагувати", "account.enable_notifications": "Повідомляти мене про дописи @{name}", "account.endorse": "Рекомендувати у моєму профілі", + "account.familiar_followers_many": "Має серед підисників {name1}, {name2} та ще {othersCount, plural, one {# відомого вам користувача} few {# відомих вам користувачів} many {# відомих вам користувачів} other {# відомих вам користувачів}}", + "account.familiar_followers_one": "Має серед підписників {name1}", + "account.familiar_followers_two": "Має серед підписників {name1} та {name2}", "account.featured": "Рекомендоване", "account.featured.accounts": "Профілі", "account.featured.hashtags": "Хештеги", @@ -35,10 +39,16 @@ "account.featured_tags.last_status_never": "Немає дописів", "account.follow": "Підписатися", "account.follow_back": "Стежити також", + "account.follow_back_short": "Підписатись навзаєм", + "account.follow_request": "Запит на підписку", + "account.follow_request_cancel": "Скасувати запит", + "account.follow_request_cancel_short": "Скасувати", + "account.follow_request_short": "Запит", "account.followers": "Підписники", "account.followers.empty": "Ніхто ще не підписаний на цього користувача.", "account.followers_counter": "{count, plural, one {{counter} підписник} few {{counter} підписники} many {{counter} підписників} other {{counter} підписник}}", - "account.following": "Ви стежите", + "account.followers_you_know_counter": "{count, plural, one {{counter} відомий вам} few {{counter} відомі вам} many {{counter} відомих вам} other {{counter} відомі вам}}", + "account.following": "Підписки", "account.following_counter": "{count, plural, one {{counter} підписка} few {{counter} підписки} many {{counter} підписок} other {{counter} підписка}}", "account.follows.empty": "Цей користувач ще ні на кого не підписався.", "account.follows_you": "Підписаний(-а) на вас", @@ -56,13 +66,15 @@ "account.mute_notifications_short": "Не сповіщати", "account.mute_short": "Ігнорувати", "account.muted": "Приховується", + "account.mutual": "Підписані навзаєм", "account.no_bio": "Немає опису.", "account.open_original_page": "Відкрити оригінальну сторінку", "account.posts": "Дописи", "account.posts_with_replies": "Дописи й відповіді", + "account.remove_from_followers": "Вилучити {name} із підписників", "account.report": "Поскаржитися на @{name}", - "account.requested": "Очікує підтвердження. Натисніть, щоб скасувати запит на підписку", "account.requested_follow": "{name} надсилає запит на стеження", + "account.requests_to_follow_you": "Просить дозвіл на вас підписатись", "account.share": "Поділитися профілем @{name}", "account.show_reblogs": "Показати поширення від @{name}", "account.statuses_counter": "{count, plural, one {{counter} допис} few {{counter} дописи} many {{counter} дописів} other {{counter} допис}}", @@ -98,25 +110,11 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Опишіть цю ідею для людей із порушеннями зору…", "alt_text_modal.done": "Готово", "announcement.announcement": "Оголошення", - "annual_report.summary.archetype.booster": "Мисливець на дописи", - "annual_report.summary.archetype.lurker": "Причаєнець", - "annual_report.summary.archetype.oracle": "Оракул", - "annual_report.summary.archetype.pollster": "Опитувач", - "annual_report.summary.archetype.replier": "Душа компанії", - "annual_report.summary.followers.followers": "підписники", - "annual_report.summary.followers.total": "Загалом {count}", - "annual_report.summary.here_it_is": "Ось ваші підсумки {year} року:", - "annual_report.summary.highlighted_post.by_favourites": "найуподобаніші дописи", - "annual_report.summary.highlighted_post.by_reblogs": "найпоширюваніші дописи", - "annual_report.summary.highlighted_post.by_replies": "найкоментованіші дописи", - "annual_report.summary.highlighted_post.possessive": "{name}", "annual_report.summary.most_used_app.most_used_app": "найчастіше використовуваний застосунок", "annual_report.summary.most_used_hashtag.most_used_hashtag": "найчастіший хештег", - "annual_report.summary.most_used_hashtag.none": "Немає", "annual_report.summary.new_posts.new_posts": "нові дописи", "annual_report.summary.percentile.text": "Це виводить вас у топ користувачів Mastodon.", "annual_report.summary.percentile.we_wont_tell_bernie": "Ми не скажемо Bernie.", - "annual_report.summary.thanks": "Дякуємо, що ви є частиною Mastodon!", "attachments_list.unprocessed": "(не оброблено)", "audio.hide": "Сховати аудіо", "block_modal.remote_users_caveat": "Ми попросимо сервер {domain} поважати ваше рішення. Однак дотримання вимог не гарантується, оскільки деякі сервери можуть обробляти блоки по-різному. Загальнодоступні дописи все ще можуть бути видимими для користувачів, які не увійшли в систему.", @@ -177,6 +175,7 @@ "community.column_settings.local_only": "Лише локальні", "community.column_settings.media_only": "Лише з медіа", "community.column_settings.remote_only": "Лише віддалені", + "compose.error.blank_post": "Допис не може бути порожнім.", "compose.language.change": "Змінити мову", "compose.language.search": "Шукати мови...", "compose.published.body": "Допис опубліковано.", @@ -224,15 +223,30 @@ "confirmations.missing_alt_text.secondary": "Все одно опублікувати", "confirmations.missing_alt_text.title": "Додати альтернативний текст?", "confirmations.mute.confirm": "Приховати", + "confirmations.private_quote_notify.cancel": "Назад до редагування", + "confirmations.private_quote_notify.confirm": "Оприлюднити допис", + "confirmations.private_quote_notify.do_not_show_again": "Більше не показувати цього повідомлення", + "confirmations.private_quote_notify.message": "Ті, кого ви цитуєте чи згадуєте, отримають сповіщення й зможуть переглянути ваш допис, навіть якщо не підписані на вас.", + "confirmations.private_quote_notify.title": "Поширити підписникам і згаданим користувачам?", + "confirmations.quiet_post_quote_info.dismiss": "Більше не нагадувати", + "confirmations.quiet_post_quote_info.got_it": "Зрозуміло", + "confirmations.quiet_post_quote_info.message": "Цитати тихого публічного допису не буде показано в стрічках трендів.", + "confirmations.quiet_post_quote_info.title": "Цитування тихих публічних дописів", "confirmations.redraft.confirm": "Видалити та виправити", "confirmations.redraft.message": "Ви впевнені, що хочете видалити цей допис та переписати його? Додавання у вибране та поширення буде втрачено, а відповіді на оригінальний допис залишаться без першоджерела.", "confirmations.redraft.title": "Видалити та переробити допис?", + "confirmations.remove_from_followers.confirm": "Вилучити підписника", + "confirmations.remove_from_followers.message": "Точно відписати {name} від вас?", + "confirmations.remove_from_followers.title": "Вилучити підписника?", "confirmations.revoke_quote.confirm": "Видалити публікацію", "confirmations.revoke_quote.message": "Цю дію не можна скасувати.", "confirmations.revoke_quote.title": "Видалити публікацію?", + "confirmations.unblock.confirm": "Розблокувати", + "confirmations.unblock.title": "Розблокувати {name}?", "confirmations.unfollow.confirm": "Відписатися", - "confirmations.unfollow.message": "Ви впевнені, що хочете відписатися від {name}?", - "confirmations.unfollow.title": "Відписатися від користувача?", + "confirmations.unfollow.title": "Відписатись від {name}?", + "confirmations.withdraw_request.confirm": "Відкликати запит", + "confirmations.withdraw_request.title": "Відкликати запит на підписку на {name}?", "content_warning.hide": "Сховати допис", "content_warning.show": "Усе одно показати", "content_warning.show_more": "Показати більше", @@ -325,7 +339,6 @@ "explore.trending_links": "Новини", "explore.trending_statuses": "Дописи", "explore.trending_tags": "Хештеґи", - "featured_carousel.next": "Далі", "filter_modal.added.context_mismatch_explanation": "Ця категорія фільтра не застосовується до контексту, в якому ви отримали доступ до цього допису. Якщо ви хочете, щоб дописи також фільтрувалися за цим контекстом, вам доведеться редагувати фільтр.", "filter_modal.added.context_mismatch_title": "Невідповідність контексту!", "filter_modal.added.expired_explanation": "Категорія цього фільтра застаріла, Вам потрібно змінити дату закінчення терміну дії, щоб застосувати її.", @@ -368,6 +381,7 @@ "follow_suggestions.who_to_follow": "На кого підписатися", "followed_tags": "Відстежувані хештеґи", "footer.about": "Про проєкт", + "footer.about_this_server": "Про сервер", "footer.directory": "Каталог профілів", "footer.get_app": "Завантажити застосунок", "footer.keyboard_shortcuts": "Комбінації клавіш", @@ -400,6 +414,7 @@ "hints.profiles.see_more_followers": "Переглянути більше підписників на {domain}", "hints.profiles.see_more_follows": "Переглянути більше підписок на {domain}", "hints.profiles.see_more_posts": "Переглянути більше дописів на {domain}", + "home.column_settings.show_quotes": "Показувати цитати", "home.column_settings.show_reblogs": "Показувати поширення", "home.column_settings.show_replies": "Показувати відповіді", "home.hide_announcements": "Приховати оголошення", @@ -446,6 +461,7 @@ "keyboard_shortcuts.home": "Відкрити домашню стрічку", "keyboard_shortcuts.hotkey": "Гаряча клавіша", "keyboard_shortcuts.legend": "Показати легенду", + "keyboard_shortcuts.load_more": "Фокусуватися на кнопці «Завантажити більше»", "keyboard_shortcuts.local": "Відкрити локальну стрічку", "keyboard_shortcuts.mention": "Згадати автора", "keyboard_shortcuts.muted": "Відкрити список прихованих користувачів", @@ -454,7 +470,7 @@ "keyboard_shortcuts.open_media": "Відкрити медіа", "keyboard_shortcuts.pinned": "Відкрити список закріплених дописів", "keyboard_shortcuts.profile": "Відкрити профіль автора", - "keyboard_shortcuts.quote": "Цитувати пост", + "keyboard_shortcuts.quote": "Цитувати допис", "keyboard_shortcuts.reply": "Відповісти", "keyboard_shortcuts.requests": "Відкрити список охочих підписатися", "keyboard_shortcuts.search": "Сфокусуватися на пошуку", @@ -568,6 +584,7 @@ "notification.label.mention": "Згадка", "notification.label.private_mention": "Особиста згадка", "notification.label.private_reply": "Приватна відповідь", + "notification.label.quote": "{name} цитує ваш допис", "notification.label.reply": "Відповідь", "notification.mention": "Згадка", "notification.mentioned_you": "{name} згадує вас", @@ -582,6 +599,7 @@ "notification.moderation_warning.action_suspend": "Ваш обліковий запис було заблоковано.", "notification.own_poll": "Ваше опитування завершилося", "notification.poll": "Опитування, в якому ви проголосували, завершено", + "notification.quoted_update": "{name} редагує цитований вами допис", "notification.reblog": "{name} поширює ваш допис", "notification.reblog.name_and_others_with_link": "{name} та {count, plural, one {# інший} few {# інших} many {# інших} other {# інший}} поширили ваш допис", "notification.relationships_severance_event": "Втрачено з'єднання з {name}", @@ -625,6 +643,7 @@ "notifications.column_settings.mention": "Згадки:", "notifications.column_settings.poll": "Результати опитування:", "notifications.column_settings.push": "Push-сповіщення", + "notifications.column_settings.quote": "Цитати:", "notifications.column_settings.reblog": "Поширення:", "notifications.column_settings.show": "Показати в стовпчику", "notifications.column_settings.sound": "Відтворювати звуки", @@ -700,10 +719,20 @@ "privacy.private.short": "Підписники", "privacy.public.long": "Усі з Mastodon", "privacy.public.short": "Публічно", + "privacy.quote.anyone": "{visibility}, будь-хто може цитувати", + "privacy.quote.disabled": "{visibility}, цитування вимкнено", + "privacy.quote.limited": "{visibility}, цитування обмежено", "privacy.unlisted.additional": "Має таку ж поведінку, як у людей, але повідомлення не з'являтимуться у стрічках або хештегах, оглядах, або пошуку Mastodon, навіть якщо ви використовуєте облікові записи.", + "privacy.unlisted.long": "Сховано з результатів пошуку Mastodon, трендових і публічних стрічок", "privacy.unlisted.short": "Без додавання до стрічки", "privacy_policy.last_updated": "Оновлено {date}", "privacy_policy.title": "Політика приватності", + "quote_error.edit": "Цитат не можна додавати під час редагування допису.", + "quote_error.poll": "Одночасно цитувати й опитувати не можна.", + "quote_error.private_mentions": "Одночасно цитувати й особисто когось згадувати не можна.", + "quote_error.quote": "Дозволено лише одну цитату.", + "quote_error.unauthorized": "Вам не дозволено цитувати цей допис.", + "quote_error.upload": "Одночасно цитувати й додавати мультимедіа не можна.", "recommended": "Рекомендовано", "refresh": "Оновити", "regeneration_indicator.please_stand_by": "Будь ласка, очікуйте.", @@ -719,6 +748,9 @@ "relative_time.minutes": "{number}х", "relative_time.seconds": "{number}с", "relative_time.today": "сьогодні", + "remove_quote_hint.button_label": "Зрозуміло", + "remove_quote_hint.message": "Це можна зробити за допомогою {icon} меню параметрів.", + "remove_quote_hint.title": "Хочете вилучити допис із цитати?", "reply_indicator.attachments": "{count, plural, one {# вкладення} few {# вкладення} many {# вкладень} other {# вкладення}}", "reply_indicator.cancel": "Скасувати", "reply_indicator.poll": "Опитування", @@ -796,7 +828,7 @@ "search_results.no_search_yet": "Спробуйте пошукати дописи, профілі або хештеґи.", "search_results.see_all": "Показати все", "search_results.statuses": "Дописів", - "search_results.title": "Шукати \"{q}\"", + "search_results.title": "Пошук «{q}»", "server_banner.about_active_users": "Люди, які використовують цей сервер протягом останніх 30 днів (Щомісячні Активні Користувачі)", "server_banner.active_users": "активні користувачі", "server_banner.administered_by": "Адміністратор:", @@ -810,15 +842,23 @@ "status.admin_account": "Відкрити інтерфейс модерації для @{name}", "status.admin_domain": "Відкрити інтерфейс модерації для {domain}", "status.admin_status": "Відкрити цей допис в інтерфейсі модерації", + "status.all_disabled": "Поширення й цитування вимкнено", "status.block": "Заблокувати @{name}", "status.bookmark": "Додати до закладок", "status.cancel_reblog_private": "Скасувати поширення", + "status.cannot_quote": "Вам не дозволено цитувати цей допис", "status.cannot_reblog": "Цей допис не може бути поширений", - "status.context.load_new_replies": "Доступні нові відповіді", - "status.context.loading": "Перевірка додаткових відповідей", + "status.contains_quote": "Містить цитату", + "status.context.loading": "Завантаження відповідей", + "status.context.loading_error": "Не вдалося завантажити нові відповіді", + "status.context.loading_success": "Нові відповіді завантажено", + "status.context.more_replies_found": "Знайдено більше відповідей", + "status.context.retry": "Повторити", + "status.context.show": "Показати", "status.continued_thread": "Продовження у потоці", "status.copy": "Копіювати посилання на допис", "status.delete": "Видалити", + "status.delete.success": "Допис видалено", "status.detailed_status": "Детальний вигляд бесіди", "status.direct": "Особиста згадка @{name}", "status.direct_indicator": "Особиста згадка", @@ -827,7 +867,7 @@ "status.edited_x_times": "Відредаговано {count, plural, one {{count} раз} few {{count} рази} many {{counter} разів} other {{counter} разів}}", "status.embed": "Отримати код вставки", "status.favourite": "Уподобане", - "status.favourites": "{count, plural, one {вподобання} few {вподобання} many {вподобань} other {вподобання}}", + "status.favourites_count": "{count, plural, one {{counter} вподобання} few {{counter} вподобання} many {{counter} вподобань} other {{counter} вподобання}}", "status.filter": "Фільтрувати цей допис", "status.history.created": "{name} створює {date}", "status.history.edited": "{name} змінює {date}", @@ -841,27 +881,50 @@ "status.mute_conversation": "Ігнорувати розмову", "status.open": "Розгорнути допис", "status.pin": "Закріпити у профілі", + "status.quote": "Цитувати", + "status.quote.cancel": "Скасувати цитування", + "status.quote_error.blocked_account_hint.title": "Допис сховано, бо ви блокуєте @{name}.", + "status.quote_error.blocked_domain_hint.title": "Допис сховано, бо ви блокуєте {domain}.", "status.quote_error.filtered": "Приховано через один з ваших фільтрів", - "status.quote_error.not_available": "Пост недоступний", + "status.quote_error.limited_account_hint.action": "Усе одно показати", + "status.quote_error.limited_account_hint.title": "Обліковий запис сховали модератори {domain}.", + "status.quote_error.muted_account_hint.title": "Допис сховано, бо ви приховуєте @{name}.", + "status.quote_error.not_available": "Допис недоступний", + "status.quote_error.pending_approval": "Цитати ще не схвалено", + "status.quote_error.pending_approval_popout.body": "Mastodon дає вам змогу контролювати, чи можна вас цитувати. Допис буде показано, коли автор схвалить його цитування.", + "status.quote_error.revoked": "Автор допису не дозволив його цитувати", + "status.quote_followers_only": "Цитувати можна лише підписникам", + "status.quote_manual_review": "Автор матиме схвалити вручну", + "status.quote_noun": "Цитата", + "status.quote_policy_change": "Змінити, кому можна цитувати", "status.quote_post_author": "Цитований допис @{name}", + "status.quote_private": "Приватних повідомлень цитувати не можна", + "status.quotes.empty": "Допису ще не цитували. Цитати буде показано тут.", + "status.quotes.local_other_disclaimer": "Відхилених автором цитат показано не буде.", + "status.quotes.remote_other_disclaimer": "Цитати за межами {domain} показуються не завжди. Відхилених автором цитат показано не буде.", + "status.quotes_count": "{count, plural, one {{counter} цитата} few {{counter} цитати} many {{counter} цитат} other {{counter} цитати}}", "status.read_more": "Дізнатися більше", "status.reblog": "Поширити", + "status.reblog_or_quote": "Поширити чи цитувати", + "status.reblog_private": "Поширити підписникам ще раз", "status.reblogged_by": "{name} поширює", - "status.reblogs": "{count, plural, one {поширення} few {поширення} many {поширень} other {поширення}}", "status.reblogs.empty": "Ніхто ще не поширив цей допис. Коли хтось це зроблять, вони будуть зображені тут.", + "status.reblogs_count": "{count, plural, one {{counter} поширення} few {{counter} поширення} many {{counter} поширень} other {{counter} поширення}}", "status.redraft": "Видалити та виправити", "status.remove_bookmark": "Видалити закладку", "status.remove_favourite": "Вилучити з улюбленого", + "status.remove_quote": "Вилучити", "status.replied_in_thread": "Відповідь у потоці", "status.replied_to": "Відповідь для {name}", "status.reply": "Відповісти", "status.replyAll": "Відповісти на ланцюжок", "status.report": "Поскаржитися на @{name}", + "status.request_quote": "Попросити дозвіл цитувати", "status.revoke_quote": "Видалити мою публікацію з допису @{name}", "status.sensitive_warning": "Делікатний вміст", "status.share": "Поділитися", - "status.show_less_all": "Згорнути для всіх", - "status.show_more_all": "Розгорнути для всіх", + "status.show_less_all": "Згорнути всі", + "status.show_more_all": "Розгорнути всі", "status.show_original": "Показати оригінал", "status.title.with_attachments": "{user} розміщує {{attachmentCount, plural, one {вкладення} few {{attachmentCount} вкладення} many {{attachmentCount} вкладень} other {{attachmentCount} вкладень}}", "status.translate": "Перекласти", @@ -895,6 +958,7 @@ "upload_button.label": "Додати зображення, відео або аудіо", "upload_error.limit": "Ви перевищили ліміт завантаження файлів.", "upload_error.poll": "Не можна завантажувати файли до опитувань.", + "upload_error.quote": "Додавати файли й цитувати одночасно не можна.", "upload_form.drag_and_drop.instructions": "Щоб вибрати медіавкладення, натисніть пробіл або Enter. Під час перетягування, використайте клавіші зі стрілками для переміщення вкладення в будь-якому напрямку. Натисніть пробіл або Enter знову, щоб залишити медіавкладення в новому положенні, або натисніть клавішу Escape, щоб скасувати.", "upload_form.drag_and_drop.on_drag_cancel": "Перетягування скасовано. Медіавкладення {item} прибрано.", "upload_form.drag_and_drop.on_drag_end": "Медіавкладення {item} прибрано.", @@ -910,10 +974,28 @@ "video.expand": "Розгорнути відео", "video.fullscreen": "На весь екран", "video.hide": "Приховати відео", + "video.mute": "Вимкнути звук", "video.pause": "Призупинити", "video.play": "Програвати", + "video.skip_backward": "Перемотати назад", + "video.skip_forward": "Перемотати вперед", + "video.unmute": "Увімкнути звук", + "video.volume_down": "Тихіше", + "video.volume_up": "Гучніше", + "visibility_modal.button_title": "Налаштувати видимість", + "visibility_modal.direct_quote_warning.text": "При збереженні поточних налаштувань цитату буде замінено на посилання.", + "visibility_modal.direct_quote_warning.title": "Цитат не можна вкладати в особисті згадки", + "visibility_modal.header": "Видимість і взаємодія", + "visibility_modal.helper.direct_quoting": "Mastodon не дозволяє цитувати особисті згадки.", "visibility_modal.helper.privacy_editing": "Видимість не може бути змінена після публікації повідомлення.", + "visibility_modal.helper.privacy_private_self_quote": "Цитати власних приватних повідомлень не можна робити публічними.", + "visibility_modal.helper.private_quoting": "Mastodon не дозволяє цитувати дописи, адресовані лише підписникам.", + "visibility_modal.helper.unlisted_quoting": "Цитати вашого допису також буде сховано зі стрічок трендів.", + "visibility_modal.instructions": "Налаштуйте, кому можна взаємодіяти з цим дописом. Для майбутніх дописів це можна налаштувати, відкривши Налаштування > Усталені налаштування дописів.", + "visibility_modal.privacy_label": "Видимість", "visibility_modal.quote_followers": "Тільки для підписників", + "visibility_modal.quote_label": "Кому можна цитувати", + "visibility_modal.quote_nobody": "Лише мені", "visibility_modal.quote_public": "Будь-хто", "visibility_modal.save": "Зберегти" } diff --git a/app/javascript/mastodon/locales/ur.json b/app/javascript/mastodon/locales/ur.json index b1db5d819f6ce5..0448f69c63cbbe 100644 --- a/app/javascript/mastodon/locales/ur.json +++ b/app/javascript/mastodon/locales/ur.json @@ -46,7 +46,6 @@ "account.posts": "ٹوٹ", "account.posts_with_replies": "ٹوٹ اور جوابات", "account.report": "@{name} اطلاع کریں", - "account.requested": "منظوری کا منتظر۔ درخواستِ پیروی منسوخ کرنے کیلئے کلک کریں", "account.requested_follow": "{name} آپ کو فالو کرنا چھاتا ہے۔", "account.share": "@{name} کے مشخص کو بانٹیں", "account.show_reblogs": "@{name} کی افزائشات کو دکھائیں", @@ -123,7 +122,6 @@ "confirmations.mute.confirm": "خاموش", "confirmations.redraft.confirm": "ڈیلیٹ کریں اور دوبارہ ڈرافٹ کریں", "confirmations.unfollow.confirm": "پیروی ترک کریں", - "confirmations.unfollow.message": "کیا واقعی آپ {name} کی پیروی ترک کرنا چاہتے ہیں؟", "conversation.delete": "گفتگو کو ڈیلیٹ کریں", "conversation.mark_as_read": "بطور پڑھا ہوا دکھائیں", "conversation.open": "گفتگو دیکھیں", diff --git a/app/javascript/mastodon/locales/uz.json b/app/javascript/mastodon/locales/uz.json index d4b37a32f7b7bc..2a8b817576534e 100644 --- a/app/javascript/mastodon/locales/uz.json +++ b/app/javascript/mastodon/locales/uz.json @@ -44,7 +44,6 @@ "account.posts": "Postlar", "account.posts_with_replies": "Xabarlar va javoblar", "account.report": "@{name} xabar berish", - "account.requested": "Tasdiqlash kutilmoqda. Kuzatuv soʻrovini bekor qilish uchun bosing", "account.requested_follow": "{name} sizni kuzatishni soʻradi", "account.share": "@{name} profilini ulashing", "account.show_reblogs": "@{name} dan bootlarni ko'rsatish", @@ -134,7 +133,6 @@ "confirmations.mute.confirm": "Ovozsiz", "confirmations.redraft.confirm": "O'chirish va qayta loyihalash", "confirmations.unfollow.confirm": "Kuzatishni To'xtatish", - "confirmations.unfollow.message": "Haqiqatan ham {name} obunasini bekor qilmoqchimisiz?", "conversation.delete": "Suhbatni o'chirish", "conversation.mark_as_read": "O'qilgan deb belgilang", "conversation.open": "Suhbatni ko'rish", diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index 99a5dc9051bf3d..a5feaebc447724 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Tắt thông báo khi @{name} đăng tút", "account.domain_blocking": "Máy chủ đang chủ", "account.edit_profile": "Sửa hồ sơ", + "account.edit_profile_short": "Sửa", "account.enable_notifications": "Nhận thông báo khi @{name} đăng tút", "account.endorse": "Nêu bật người này", "account.familiar_followers_many": "Theo dõi bởi {name1}, {name2} và {othersCount, plural, other {# người khác mà bạn biết}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Chưa có tút", "account.follow": "Theo dõi", "account.follow_back": "Theo dõi lại", + "account.follow_back_short": "Theo dõi lại", + "account.follow_request": "Yêu cầu theo dõi", + "account.follow_request_cancel": "Hủy yêu cầu", + "account.follow_request_cancel_short": "Hủy bỏ", + "account.follow_request_short": "Yêu cầu", "account.followers": "Người theo dõi", "account.followers.empty": "Chưa có người theo dõi nào.", "account.followers_counter": "{count, plural, other {{counter} Người theo dõi}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Trả lời", "account.remove_from_followers": "Xóa người theo dõi {name}", "account.report": "Báo cáo @{name}", - "account.requested": "Đang chờ chấp thuận. Nhấp vào đây để hủy yêu cầu theo dõi", "account.requested_follow": "{name} yêu cầu theo dõi bạn", "account.requests_to_follow_you": "Yêu cầu theo dõi bạn", "account.share": "Chia sẻ @{name}", @@ -108,25 +113,52 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Mô tả cho người khiếm thị…", "alt_text_modal.done": "Xong", "announcement.announcement": "Có gì mới?", - "annual_report.summary.archetype.booster": "Hiệp sĩ ngầu", - "annual_report.summary.archetype.lurker": "Kẻ rình mò", - "annual_report.summary.archetype.oracle": "Nhà tiên tri", - "annual_report.summary.archetype.pollster": "Chuyên gia khảo sát", - "annual_report.summary.archetype.replier": "Bướm xã hội", - "annual_report.summary.followers.followers": "người theo dõi", - "annual_report.summary.followers.total": "tổng {count}", - "annual_report.summary.here_it_is": "Nhìn lại năm {year} của bạn:", - "annual_report.summary.highlighted_post.by_favourites": "tút được thích nhiều nhất", - "annual_report.summary.highlighted_post.by_reblogs": "tút được đăng lại nhiều nhất", - "annual_report.summary.highlighted_post.by_replies": "tút được trả lời nhiều nhất", - "annual_report.summary.highlighted_post.possessive": "{name}", + "annual_report.announcement.action_build": "Tạo Wrapstodon của tôi", + "annual_report.announcement.action_dismiss": "Không, cảm ơn", + "annual_report.announcement.action_view": "Xem Wrapstodon của tôi", + "annual_report.announcement.description": "Tìm hiểu thêm về mức độ tương tác của bạn trên Mastodon trong năm qua.", + "annual_report.announcement.title": "Wrapstodon {year} đã đến", + "annual_report.nav_item.badge": "Mới", + "annual_report.shared_page.donate": "Quyên góp", + "annual_report.shared_page.footer": "Tạo bằng {heart} bởi đội ngũ Mastodon", + "annual_report.shared_page.footer_server_info": "{username} ở {domain}, một trong nhiều cộng đồng Mastodon.", + "annual_report.summary.archetype.booster.desc_public": "{name} thích tìm kiếm và đăng lại các tút, lan tỏa những người sáng tạo khác với mục tiêu hoàn hảo.", + "annual_report.summary.archetype.booster.desc_self": "Bạn thích tìm kiếm và đăng lại các tút, lan tỏa những người sáng tạo khác với mục tiêu hoàn hảo.", + "annual_report.summary.archetype.booster.name": "Cung Thủ", + "annual_report.summary.archetype.die_drei_fragezeichen": "???", + "annual_report.summary.archetype.lurker.desc_public": "Chúng tôi biết {name} đang ở đâu đó ngoài kia, tận hưởng Mastodon theo cách riêng của họ.", + "annual_report.summary.archetype.lurker.desc_self": "Chúng tôi biết bạn đang ở đâu đó ngoài kia, tận hưởng Mastodon theo cách riêng.", + "annual_report.summary.archetype.lurker.name": "Lãng Nhân", + "annual_report.summary.archetype.oracle.desc_public": "{name} tạo ra nhiều tút mới hơn là trả lời, giúp Mastodon luôn mới mẻ và hướng tới tương lai.", + "annual_report.summary.archetype.oracle.desc_self": "Bạn tạo ra nhiều tút mới hơn là trả lời, giúp Mastodon luôn mới mẻ và hướng tới tương lai.", + "annual_report.summary.archetype.oracle.name": "Tiên Tri", + "annual_report.summary.archetype.pollster.desc_public": "{name} tạo ra nhiều cuộc vốt hơn các loại tút khác, khơi dậy sự tò mò trên Mastodon.", + "annual_report.summary.archetype.pollster.desc_self": "Bạn tạo ra nhiều cuộc vốt hơn các loại tút khác, khơi dậy sự tò mò trên Mastodon.", + "annual_report.summary.archetype.pollster.name": "Mọt Sách", + "annual_report.summary.archetype.replier.desc_public": "{name} thường xuyên trả lời tút của người khác, mang đến cho Mastodon những cuộc thảo luận mới.", + "annual_report.summary.archetype.replier.desc_self": "Bạn thường xuyên trả lời tút của người khác, mang đến cho Mastodon những cuộc thảo luận mới.", + "annual_report.summary.archetype.replier.name": "Bướm Xinh", + "annual_report.summary.archetype.reveal": "Bật mí nguyên mẫu của tôi", + "annual_report.summary.archetype.reveal_description": "Cảm ơn bạn đã là một phần của Mastodon! Đã đến lúc tìm hiểu xem bạn thuộc nguyên mẫu nào vào năm {year}.", + "annual_report.summary.archetype.title_public": "Nguyên mẫu của {name}", + "annual_report.summary.archetype.title_self": "Nguyên mẫu của bạn", + "annual_report.summary.close": "Đóng", + "annual_report.summary.copy_link": "Sao chép liên kết", + "annual_report.summary.followers.new_followers": "{count, plural, other {người theo dõi mới}}", + "annual_report.summary.highlighted_post.boost_count": "Tút này được đăng lại {count, plural, other {# lần}}.", + "annual_report.summary.highlighted_post.favourite_count": "Tút này được thích {count, plural, other {# lần}}.", + "annual_report.summary.highlighted_post.reply_count": "Tút này có {count, plural, other {# lượt trả lời}}.", + "annual_report.summary.highlighted_post.title": "Tút nổi tiếng nhất", "annual_report.summary.most_used_app.most_used_app": "app dùng nhiều nhất", "annual_report.summary.most_used_hashtag.most_used_hashtag": "hashtag dùng nhiều nhất", - "annual_report.summary.most_used_hashtag.none": "Không có", + "annual_report.summary.most_used_hashtag.used_count": "Bạn đã dùng hashtag này trong {count, plural, other {# tút}}.", + "annual_report.summary.most_used_hashtag.used_count_public": "{name} đã dùng hashtag này trong {count, plural, other {# tút}}.", "annual_report.summary.new_posts.new_posts": "tút mới", "annual_report.summary.percentile.text": "Bạn thuộc topthành viên của {domain}.", "annual_report.summary.percentile.we_wont_tell_bernie": "Chúng tôi sẽ không kể cho Bernie.", - "annual_report.summary.thanks": "Cảm ơn đã trở thành một phần của Mastodon!", + "annual_report.summary.share_elsewhere": "Chia sẻ nơi khác", + "annual_report.summary.share_message": "Tôi là điển hình {archetype}!", + "annual_report.summary.share_on_mastodon": "Chia sẻ lên Mastodon", "attachments_list.unprocessed": "(chưa xử lí)", "audio.hide": "Ẩn âm thanh", "block_modal.remote_users_caveat": "Chúng tôi sẽ yêu cầu {domain} tôn trọng quyết định của bạn. Tuy nhiên, việc tuân thủ không được đảm bảo vì một số máy chủ có thể xử lý việc chặn theo cách khác nhau. Các tút công khai vẫn có thể hiển thị đối với người dùng chưa đăng nhập.", @@ -152,6 +184,8 @@ "bundle_modal_error.close": "Đóng", "bundle_modal_error.message": "Đã có lỗi xảy ra trong khi tải màn hình này.", "bundle_modal_error.retry": "Thử lại", + "carousel.current": "Slide {current, number} / {max, number}", + "carousel.slide": "Slide {current, number} trong {max, number}", "closed_registrations.other_server_instructions": "Tạo tài khoản trên máy chủ khác và vẫn tương tác với máy chủ này.", "closed_registrations_modal.description": "{domain} hiện tắt đăng ký, nhưng hãy lưu ý rằng bạn không cần một tài khoản riêng trên {domain} để sử dụng Mastodon.", "closed_registrations_modal.find_another_server": "Tìm máy chủ khác", @@ -168,6 +202,8 @@ "column.edit_list": "Sửa danh sách", "column.favourites": "Những tút đã thích", "column.firehose": "Bảng tin", + "column.firehose_local": "Bảng tin máy chủ này", + "column.firehose_singular": "Bảng tin", "column.follow_requests": "Yêu cầu theo dõi", "column.home": "Trang chủ", "column.list_members": "Những người trong danh sách", @@ -187,6 +223,7 @@ "community.column_settings.local_only": "Chỉ máy chủ của bạn", "community.column_settings.media_only": "Chỉ hiện tút có media", "community.column_settings.remote_only": "Chỉ người ở máy chủ khác", + "compose.error.blank_post": "Không thể để trống.", "compose.language.change": "Chọn ngôn ngữ tút", "compose.language.search": "Tìm ngôn ngữ...", "compose.published.body": "Tút đã được đăng.", @@ -239,6 +276,11 @@ "confirmations.missing_alt_text.secondary": "Đăng luôn", "confirmations.missing_alt_text.title": "Thêm văn bản thay thế?", "confirmations.mute.confirm": "Ẩn", + "confirmations.private_quote_notify.cancel": "Quay lại chỉnh sửa", + "confirmations.private_quote_notify.confirm": "Đăng tút", + "confirmations.private_quote_notify.do_not_show_again": "Không hiện thông báo này nữa", + "confirmations.private_quote_notify.message": "Người mà bạn trích dẫn và những người được bạn nhắc đến khác sẽ được thông báo và có thể xem tút của bạn, ngay cả khi họ không theo dõi bạn.", + "confirmations.private_quote_notify.title": "Chia sẻ với người được nhắc đến và người theo dõi?", "confirmations.quiet_post_quote_info.dismiss": "Không nhắc lại nữa", "confirmations.quiet_post_quote_info.got_it": "Đã hiểu", "confirmations.quiet_post_quote_info.message": "Khi trích dẫn một tút hạn chế, tút của bạn sẽ bị ẩn khỏi dòng thời gian thịnh hành.", @@ -252,9 +294,12 @@ "confirmations.revoke_quote.confirm": "Gỡ tút", "confirmations.revoke_quote.message": "Hành động này không thể hoàn tác.", "confirmations.revoke_quote.title": "Gỡ tút?", + "confirmations.unblock.confirm": "Bỏ chặn", + "confirmations.unblock.title": "Bỏ chặn {name}?", "confirmations.unfollow.confirm": "Bỏ theo dõi", - "confirmations.unfollow.message": "Bạn có chắc muốn bỏ theo dõi {name}?", - "confirmations.unfollow.title": "Bỏ theo dõi", + "confirmations.unfollow.title": "Bỏ theo dõi {name}?", + "confirmations.withdraw_request.confirm": "Thu hồi yêu cầu", + "confirmations.withdraw_request.title": "Thu hồi yêu cầu theo dõi {name}?", "content_warning.hide": "Thu gọn", "content_warning.show": "Vẫn xem", "content_warning.show_more": "Mở rộng", @@ -314,8 +359,8 @@ "emoji_button.search_results": "Kết quả tìm kiếm", "emoji_button.symbols": "Biểu tượng", "emoji_button.travel": "Du lịch", - "empty_column.account_featured.me": "Bạn chưa nêu bật gì. Bạn có biết rằng, bạn có thể giới thiệu hashtag thường dùng, lẫn hồ sơ của bạn bè trên trang cá nhân của mình không?", - "empty_column.account_featured.other": "{acct} chưa nêu bật gì. Bạn có biết rằng, bạn có thể giới thiệu hashtag thường dùng, lẫn hồ sơ của bạn bè trên trang cá nhân của mình không?", + "empty_column.account_featured.me": "Bạn chưa nêu bật gì. Bạn có biết rằng, bạn có thể giới thiệu hashtag thường dùng và hồ sơ của bạn bè trên trang cá nhân của mình không?", + "empty_column.account_featured.other": "{acct} chưa nêu bật gì. Bạn có biết rằng, bạn có thể giới thiệu hashtag thường dùng và hồ sơ của bạn bè trên trang cá nhân của mình không?", "empty_column.account_featured_other.unknown": "Người này chưa nêu bật nội dung gì.", "empty_column.account_hides_collections": "Người này đã chọn ẩn thông tin", "empty_column.account_suspended": "Tài khoản vô hiệu hóa", @@ -325,6 +370,7 @@ "empty_column.bookmarked_statuses": "Bạn chưa lưu tút nào. Nếu có, nó sẽ hiển thị ở đây.", "empty_column.community": "Máy chủ của bạn chưa có tút nào công khai. Bạn hãy thử viết gì đó đi!", "empty_column.direct": "Bạn chưa có tin nhắn riêng nào. Khi bạn gửi hoặc nhận một tin nhắn riêng, nó sẽ xuất hiện ở đây.", + "empty_column.disabled_feed": "Bảng tin này bị vô hiệu hóa bởi quản trị viên máy chủ của bạn.", "empty_column.domain_blocks": "Chưa ẩn bất kỳ máy chủ nào.", "empty_column.explore_statuses": "Chưa có gì hot. Kiểm tra lại sau!", "empty_column.favourited_statuses": "Bạn chưa thích tút nào. Hãy thử đi, nó sẽ xuất hiện ở đây.", @@ -338,6 +384,7 @@ "empty_column.notification_requests": "Sạch sẽ! Không còn gì ở đây. Khi bạn nhận được thông báo mới, chúng sẽ xuất hiện ở đây theo cài đặt của bạn.", "empty_column.notifications": "Bạn chưa có thông báo nào. Hãy thử theo dõi hoặc nhắn riêng cho ai đó.", "empty_column.public": "Trống trơn! Bạn hãy viết gì đó hoặc bắt đầu theo dõi những người khác", + "error.no_hashtag_feed_access": "Tham gia hoặc đăng nhập để xem và theo dõi hashtag này.", "error.unexpected_crash.explanation": "Trang này có thể không hiển thị chính xác do lỗi lập trình Mastodon hoặc vấn đề tương thích trình duyệt.", "error.unexpected_crash.explanation_addons": "Trang này không thể hiển thị do xung khắc với add-on của trình duyệt hoặc công cụ tự động dịch ngôn ngữ.", "error.unexpected_crash.next_steps": "Hãy thử làm mới trang. Nếu vẫn không được, bạn hãy vào Mastodon bằng một ứng dụng di động hoặc trình duyệt khác.", @@ -349,11 +396,9 @@ "explore.trending_links": "Tin tức", "explore.trending_statuses": "Tút", "explore.trending_tags": "Hashtag", + "featured_carousel.current": "Tút {current, number} / {max, number}", "featured_carousel.header": "{count, plural, other {Tút đã ghim}}", - "featured_carousel.next": "Sau", - "featured_carousel.post": "Tút", - "featured_carousel.previous": "Trước", - "featured_carousel.slide": "{index} trong {total}", + "featured_carousel.slide": "Tút {current, number} trong {max, number}", "filter_modal.added.context_mismatch_explanation": "Danh mục bộ lọc này không áp dụng cho ngữ cảnh mà bạn đã truy cập tút này. Nếu bạn muốn tút cũng được lọc trong ngữ cảnh này, bạn sẽ phải chỉnh sửa bộ lọc.", "filter_modal.added.context_mismatch_title": "Bối cảnh không phù hợp!", "filter_modal.added.expired_explanation": "Danh mục bộ lọc này đã hết hạn, bạn sẽ cần thay đổi ngày hết hạn để áp dụng.", @@ -391,11 +436,14 @@ "follow_suggestions.personalized_suggestion": "Gợi ý cá nhân hóa", "follow_suggestions.popular_suggestion": "Người nổi tiếng", "follow_suggestions.popular_suggestion_longer": "Nổi tiếng trên {domain}", - "follow_suggestions.similar_to_recently_followed_longer": "Tương đồng những người mà bạn theo dõi", + "follow_suggestions.similar_to_recently_followed_longer": "Tương tự những người mà bạn theo dõi", "follow_suggestions.view_all": "Xem tất cả", "follow_suggestions.who_to_follow": "Gợi ý theo dõi", "followed_tags": "Hashtag theo dõi", "footer.about": "Giới thiệu", + "footer.about_mastodon": "Về Mastodon", + "footer.about_server": "Về {domain}", + "footer.about_this_server": "Giới thiệu", "footer.directory": "Danh bạ", "footer.get_app": "Ứng dụng", "footer.keyboard_shortcuts": "Phím tắt", @@ -497,6 +545,7 @@ "keyboard_shortcuts.toggle_hidden": "ẩn/hiện nội dung ẩn", "keyboard_shortcuts.toggle_sensitivity": "ẩn/hiện ảnh hoặc video", "keyboard_shortcuts.toot": "soạn tút mới", + "keyboard_shortcuts.top": "Chuyển đến đầu danh sách", "keyboard_shortcuts.translate": "dịch tút", "keyboard_shortcuts.unfocus": "đưa con trỏ ra khỏi ô soạn thảo hoặc ô tìm kiếm", "keyboard_shortcuts.up": "di chuyển lên trên danh sách", @@ -745,7 +794,9 @@ "privacy.unlisted.short": "Hạn chế", "privacy_policy.last_updated": "Cập nhật lần cuối {date}", "privacy_policy.title": "Chính sách bảo mật", + "quote_error.edit": "Không thể thêm trích dẫn khi sửa tút.", "quote_error.poll": "Không thể trích dẫn vốt.", + "quote_error.private_mentions": "Không thể trích dẫn với lượt nhắc trực tiếp.", "quote_error.quote": "Chỉ được trích dẫn một lần.", "quote_error.unauthorized": "Bạn không được cấp quyền trích dẫn tút này.", "quote_error.upload": "Không thể trích dẫn với media đính kèm.", @@ -865,8 +916,12 @@ "status.cannot_quote": "Bạn không được phép trích dẫn tút này", "status.cannot_reblog": "Không thể đăng lại tút này", "status.contains_quote": "Chứa trích dẫn", - "status.context.load_new_replies": "Có những trả lời mới", - "status.context.loading": "Kiểm tra nhiều trả lời hơn", + "status.context.loading": "Tải thêm các trả lời", + "status.context.loading_error": "Không thể tải những trả lời mới", + "status.context.loading_success": "Đã tải những lượt trả lời mới", + "status.context.more_replies_found": "Có trả lời mới", + "status.context.retry": "Thử lại", + "status.context.show": "Hiện", "status.continued_thread": "Tiếp tục chủ đề", "status.copy": "Sao chép URL", "status.delete": "Xóa", @@ -879,7 +934,7 @@ "status.edited_x_times": "Đã sửa {count, plural, other {{count} lần}}", "status.embed": "Nhúng", "status.favourite": "Thích", - "status.favourites": "{count, plural, other {thích}}", + "status.favourites_count": "{count, plural, other {{counter} thích}}", "status.filter": "Lọc tút này", "status.history.created": "{name} đăng {date}", "status.history.edited": "{name} đã sửa {date}", @@ -895,9 +950,12 @@ "status.pin": "Ghim lên hồ sơ", "status.quote": "Trích dẫn", "status.quote.cancel": "Bỏ trích dẫn", + "status.quote_error.blocked_account_hint.title": "Tút này bị ẩn vì bạn đã chặn @{name}.", + "status.quote_error.blocked_domain_hint.title": "Tút này bị ẩn vì bạn đã chặn {domain}.", "status.quote_error.filtered": "Bị ẩn vì một bộ lọc của bạn", "status.quote_error.limited_account_hint.action": "Vẫn xem", "status.quote_error.limited_account_hint.title": "Người này đã bị ẩn bởi quản trị viên {domain}.", + "status.quote_error.muted_account_hint.title": "Tút này bị ẩn vì bạn đã ẩn @{name}.", "status.quote_error.not_available": "Tút không khả dụng", "status.quote_error.pending_approval": "Tút đang chờ duyệt", "status.quote_error.pending_approval_popout.body": "Trên Mastodon, bạn có thể kiểm soát việc ai đó có thể trích dẫn tút của bạn hay không. Tút này đang chờ phê duyệt từ tác giả gốc.", @@ -908,15 +966,17 @@ "status.quote_policy_change": "Thay đổi người có thể trích dẫn", "status.quote_post_author": "Trích dẫn từ tút của @{name}", "status.quote_private": "Không thể trích dẫn nhắn riêng", - "status.quotes": "{count, plural, other {trích dẫn}}", "status.quotes.empty": "Tút này chưa có ai trích dẫn. Nếu có, nó sẽ hiển thị ở đây.", + "status.quotes.local_other_disclaimer": "Những trích dẫn bị tác giả từ chối sẽ không được hiển thị.", + "status.quotes.remote_other_disclaimer": "Chỉ những trích dẫn từ {domain} mới được hiển thị ở đây. Những trích dẫn bị tác giả từ chối sẽ không được hiển thị.", + "status.quotes_count": "{count, plural, other {{counter} trích dẫn}}", "status.read_more": "Đọc tiếp", "status.reblog": "Đăng lại", "status.reblog_or_quote": "Đăng lại hoặc trích dẫn", "status.reblog_private": "Chia sẻ lại với người theo dõi của bạn", "status.reblogged_by": "{name} đăng lại", - "status.reblogs": "{count, plural, other {đăng lại}}", "status.reblogs.empty": "Tút này chưa có ai đăng lại. Nếu có, nó sẽ hiển thị ở đây.", + "status.reblogs_count": "{count, plural, other {{counter} đăng lại}}", "status.redraft": "Xóa và viết lại", "status.remove_bookmark": "Bỏ lưu", "status.remove_favourite": "Bỏ thích", @@ -990,13 +1050,15 @@ "video.volume_down": "Giảm âm lượng", "video.volume_up": "Tăng âm lượng", "visibility_modal.button_title": "Thay đổi quyền riêng tư", + "visibility_modal.direct_quote_warning.text": "Nếu bạn lưu cài đặt hiện tại, trích dẫn được nhúng sẽ được chuyển đổi thành liên kết.", + "visibility_modal.direct_quote_warning.title": "Không thể nhúng trích dẫn vào Nhắn Riêng", "visibility_modal.header": "Hiển thị và tương tác", - "visibility_modal.helper.direct_quoting": "Nhắn riêng trên Mastodon không thể được người khác trích dẫn.", + "visibility_modal.helper.direct_quoting": "Không thể trích dẫn tút Nhắn riêng trên Mastodon.", "visibility_modal.helper.privacy_editing": "Không thể thay đổi chế độ hiển thị sau khi một tút đã đăng.", "visibility_modal.helper.privacy_private_self_quote": "Không thể công khai trích dẫn tút của bản thân hoặc tút riêng tư.", - "visibility_modal.helper.private_quoting": "Tút chỉ dành cho người theo dõi trên Mastodon không thể được người khác trích dẫn.", + "visibility_modal.helper.private_quoting": "Không thể trích dẫn tút chỉ dành cho người theo dõi trên Mastodon.", "visibility_modal.helper.unlisted_quoting": "Khi ai đó trích dẫn bạn, tút của họ cũng sẽ bị ẩn khỏi bảng tin công khai.", - "visibility_modal.instructions": "Kiểm soát những ai có thể tương tác với tút này. Bạn cũng có thể áp dụng cài đặt cho tất cả các tút trong tương lai bằng cách điều hướng đến Thiết lập > Đăng.", + "visibility_modal.instructions": "Kiểm soát những ai có thể tương tác với tút này. Bạn cũng có thể đặt sẵn cho tất cả tút trong tương lai bằng cách truy cập Thiết lập > Mặc định cho tút.", "visibility_modal.privacy_label": "Hiển thị", "visibility_modal.quote_followers": "Chỉ người theo dõi", "visibility_modal.quote_label": "Ai có thể trích dẫn", diff --git a/app/javascript/mastodon/locales/zgh.json b/app/javascript/mastodon/locales/zgh.json index ff37d75a07c42b..7317cd7282f6e2 100644 --- a/app/javascript/mastodon/locales/zgh.json +++ b/app/javascript/mastodon/locales/zgh.json @@ -14,7 +14,6 @@ "account.muted": "ⵉⵜⵜⵓⵥⵉⵥⵏ", "account.posts": "Toots", "account.posts_with_replies": "Toots and replies", - "account.requested": "Awaiting approval", "account.share": "ⴱⴹⵓ ⵉⴼⵔⵙ ⵏ @{name}", "account.unfollow": "ⴽⴽⵙ ⴰⴹⴼⴼⵓⵕ", "account_note.placeholder": "Click to add a note", @@ -49,7 +48,6 @@ "confirmations.logout.message": "ⵉⵙ ⵏⵉⵜ ⵜⵅⵙⴷ ⴰⴷ ⵜⴼⴼⵖⴷ?", "confirmations.mute.confirm": "ⵥⵥⵉⵥⵏ", "confirmations.unfollow.confirm": "ⴽⴽⵙ ⴰⴹⴼⴼⵓⵕ", - "confirmations.unfollow.message": "ⵉⵙ ⵏⵉⵜ ⵜⵅⵙⴷ ⴰⴷ ⵜⴽⴽⵙⴷ ⴰⴹⴼⴼⵓⵕ ⵉ {name}?", "conversation.delete": "ⴽⴽⵙ ⴰⵎⵙⴰⵡⴰⵍ", "conversation.with": "ⴰⴽⴷ {names}", "embed.instructions": "Embed this status on your website by copying the code below.", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index c69e72eced44ee..232d8ec6774902 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -28,6 +28,7 @@ "account.disable_notifications": "当 @{name} 发布嘟文时不要通知我", "account.domain_blocking": "正在屏蔽中的域名", "account.edit_profile": "修改个人资料", + "account.edit_profile_short": "编辑", "account.enable_notifications": "当 @{name} 发布嘟文时通知我", "account.endorse": "在个人资料中推荐此用户", "account.familiar_followers_many": "被 {name1}、{name2}、及 {othersCount, plural, other {其他你认识的 # 人}} 关注", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "暂无嘟文", "account.follow": "关注", "account.follow_back": "回关", + "account.follow_back_short": "回关", + "account.follow_request": "请求关注", + "account.follow_request_cancel": "取消请求", + "account.follow_request_cancel_short": "取消", + "account.follow_request_short": "请求", "account.followers": "关注者", "account.followers.empty": "目前无人关注此用户。", "account.followers_counter": "{count, plural, other {{counter} 关注者}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "嘟文和回复", "account.remove_from_followers": "从关注者中移除 {name}", "account.report": "举报 @{name}", - "account.requested": "正在等待对方同意。点击取消发送关注请求", "account.requested_follow": "{name} 向你发送了关注请求", "account.requests_to_follow_you": "请求关注你", "account.share": "分享 @{name} 的个人资料", @@ -108,25 +113,52 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "请为视力障碍人士描述此内容…", "alt_text_modal.done": "完成", "announcement.announcement": "公告", - "annual_report.summary.archetype.booster": "潮流捕手", - "annual_report.summary.archetype.lurker": "吃瓜群众", - "annual_report.summary.archetype.oracle": "未卜先知", - "annual_report.summary.archetype.pollster": "民调专家", - "annual_report.summary.archetype.replier": "社交蝴蝶", - "annual_report.summary.followers.followers": "关注者", - "annual_report.summary.followers.total": "共 {count} 人", - "annual_report.summary.here_it_is": "您的 {year} 年度回顾在此:", - "annual_report.summary.highlighted_post.by_favourites": "最受欢迎的嘟文", - "annual_report.summary.highlighted_post.by_reblogs": "传播最广的嘟文", - "annual_report.summary.highlighted_post.by_replies": "评论最多的嘟文", - "annual_report.summary.highlighted_post.possessive": "{name} 的", + "annual_report.announcement.action_build": "构建我的 Wrapstodon 年度回顾", + "annual_report.announcement.action_dismiss": "不了,谢谢", + "annual_report.announcement.action_view": "查看我的 Wrapstodon 年度回顾", + "annual_report.announcement.description": "探索更多关于您过去一年在 Mastodon 上的互动情况。", + "annual_report.announcement.title": "Wrapstodon {year} 年度回顾来啦", + "annual_report.nav_item.badge": "新", + "annual_report.shared_page.donate": "捐助", + "annual_report.shared_page.footer": "由 Mastodon 团队用 {heart} 生成", + "annual_report.shared_page.footer_server_info": "{username} 使用 {domain},运行 Mastodon 的众多社区之一。", + "annual_report.summary.archetype.booster.desc_public": "{name}持续寻找值得转嘟的嘟文,以精准眼光放大其他创作者的影响力。", + "annual_report.summary.archetype.booster.desc_self": "你持续寻找值得转嘟的嘟文,以精准眼光放大其他创作者的影响力。", + "annual_report.summary.archetype.booster.name": "转发游侠", + "annual_report.summary.archetype.die_drei_fragezeichen": "???", + "annual_report.summary.archetype.lurker.desc_public": "我们知道{name}曾在联邦宇宙的某个角落驻足,以自己的方式静静享受着Mastodon。", + "annual_report.summary.archetype.lurker.desc_self": "我们知道你曾在联邦宇宙的某个角落驻足,以自己的方式静静享受着Mastodon。", + "annual_report.summary.archetype.lurker.name": "吃瓜群众", + "annual_report.summary.archetype.oracle.desc_public": "{name}发布的新嘟文远多于回复,为Mastodon注入无限活力与未来。", + "annual_report.summary.archetype.oracle.desc_self": "你发布的新嘟文远多于回复,为Mastodon注入无限活力与未来。", + "annual_report.summary.archetype.oracle.name": "创作先知", + "annual_report.summary.archetype.pollster.desc_public": "{name}发起投票的次数远多于其他嘟文类型,不断激发Mastodon上大家的好奇心。", + "annual_report.summary.archetype.pollster.desc_self": "你发起投票的次数远多于其他嘟文类型,不断激发Mastodon上大家的好奇心。", + "annual_report.summary.archetype.pollster.name": "探求先锋", + "annual_report.summary.archetype.replier.desc_public": "{name}经常回复其他人的嘟文,为Mastodon培育新讨论的萌芽。", + "annual_report.summary.archetype.replier.desc_self": "你经常回复其他人的嘟文,为Mastodon培育新讨论的萌芽。", + "annual_report.summary.archetype.replier.name": "社交蝴蝶", + "annual_report.summary.archetype.reveal": "揭示我的画像", + "annual_report.summary.archetype.reveal_description": "感谢你成为Mastodon的一份子!是时候揭晓你{year}年的用户画像啦。", + "annual_report.summary.archetype.title_public": "{name}的画像", + "annual_report.summary.archetype.title_self": "你的画像", + "annual_report.summary.close": "关闭", + "annual_report.summary.copy_link": "复制链接", + "annual_report.summary.followers.new_followers": "{count, plural, other {位新关注者}}", + "annual_report.summary.highlighted_post.boost_count": "此嘟文被转嘟了 {count, plural, other {# 次}}。", + "annual_report.summary.highlighted_post.favourite_count": "此嘟文收到了 {count, plural, other {# 次}}喜欢。", + "annual_report.summary.highlighted_post.reply_count": "此嘟文收到了 {count, plural, other {# 条回复}}。", + "annual_report.summary.highlighted_post.title": "最热门的嘟文", "annual_report.summary.most_used_app.most_used_app": "最常用的应用", "annual_report.summary.most_used_hashtag.most_used_hashtag": "最常用的话题", - "annual_report.summary.most_used_hashtag.none": "无", + "annual_report.summary.most_used_hashtag.used_count": "你在 {count, plural, other {# 条嘟文}}中使用了此话题标签。", + "annual_report.summary.most_used_hashtag.used_count_public": "{name} 在 {count, plural, other {# 条嘟文}}中使用了此话题标签。", "annual_report.summary.new_posts.new_posts": "新嘟文", "annual_report.summary.percentile.text": "这使你跻身 {domain} 用户的前", "annual_report.summary.percentile.we_wont_tell_bernie": " ", - "annual_report.summary.thanks": "感谢您成为 Mastodon 的一员!", + "annual_report.summary.share_elsewhere": "分享到其他地方", + "annual_report.summary.share_message": "我今年的画像是{archetype}!", + "annual_report.summary.share_on_mastodon": "在Mastodon上分享", "attachments_list.unprocessed": "(未处理)", "audio.hide": "隐藏音频", "block_modal.remote_users_caveat": "我们将要求站点 {domain} 尊重你的决定。然而,我们无法保证对方一定遵从,因为某些站点可能会以不同的方案处理屏蔽操作。公开嘟文仍然可能对未登录用户可见。", @@ -152,6 +184,8 @@ "bundle_modal_error.close": "关闭", "bundle_modal_error.message": "加载此页面时发生了错误。", "bundle_modal_error.retry": "重试", + "carousel.current": "幻灯片 {current, number} / {max, number}", + "carousel.slide": "第 {current, number} 张幻灯片,共 {max, number} 张", "closed_registrations.other_server_instructions": "基于 Mastodon 去中心化的特性,你可以其他服务器上创建账号,并继续与此服务器互动。", "closed_registrations_modal.description": "你目前无法在 {domain} 上创建账号,但请注意,使用 Mastodon 并非需要专门在 {domain} 上注册账号。", "closed_registrations_modal.find_another_server": "查找其他服务器", @@ -168,6 +202,8 @@ "column.edit_list": "编辑列表", "column.favourites": "喜欢", "column.firehose": "实时动态", + "column.firehose_local": "此服务器的实时动态", + "column.firehose_singular": "实时动态", "column.follow_requests": "关注请求", "column.home": "主页", "column.list_members": "管理列表成员", @@ -187,6 +223,7 @@ "community.column_settings.local_only": "仅限本站", "community.column_settings.media_only": "仅媒体", "community.column_settings.remote_only": "仅外站", + "compose.error.blank_post": "嘟文内容不能为空。", "compose.language.change": "更改语言", "compose.language.search": "搜索语言...", "compose.published.body": "嘟文已发布。", @@ -239,6 +276,11 @@ "confirmations.missing_alt_text.secondary": "就这样发布", "confirmations.missing_alt_text.title": "添加替代文本?", "confirmations.mute.confirm": "隐藏", + "confirmations.private_quote_notify.cancel": "返回编辑", + "confirmations.private_quote_notify.confirm": "发布嘟文", + "confirmations.private_quote_notify.do_not_show_again": "不再显示此消息", + "confirmations.private_quote_notify.message": "你所引用与提及的用户将收到通知且能够查看你的嘟文,即便这些用户没有关注你。", + "confirmations.private_quote_notify.title": "是否和关注者和提及的用户分享此内容?", "confirmations.quiet_post_quote_info.dismiss": "不再提醒", "confirmations.quiet_post_quote_info.got_it": "明白了", "confirmations.quiet_post_quote_info.message": "当你引用悄悄公开的嘟文时,你的嘟文会从热门时间线上隐藏。", @@ -252,9 +294,12 @@ "confirmations.revoke_quote.confirm": "移除嘟文", "confirmations.revoke_quote.message": "此操作无法撤销。", "confirmations.revoke_quote.title": "移除嘟文?", + "confirmations.unblock.confirm": "取消屏蔽", + "confirmations.unblock.title": "取消屏蔽 {name} 吗?", "confirmations.unfollow.confirm": "取消关注", - "confirmations.unfollow.message": "你确定要取消关注 {name} 吗?", - "confirmations.unfollow.title": "确定要取消关注用户?", + "confirmations.unfollow.title": "取关 {name} 吗?", + "confirmations.withdraw_request.confirm": "撤回请求", + "confirmations.withdraw_request.title": "撤回关注 {name} 的请求吗?", "content_warning.hide": "隐藏嘟文", "content_warning.show": "仍要显示", "content_warning.show_more": "展开", @@ -281,7 +326,7 @@ "domain_block_modal.they_wont_know": "对方不会知道自己被屏蔽。", "domain_block_modal.title": "确定要屏蔽此域名?", "domain_block_modal.you_will_lose_num_followers": "你将失去 {followersCount, plural, other {{followersCountDisplay} 名关注者}}和 {followingCount, plural, other {{followingCountDisplay} 名关注}}。", - "domain_block_modal.you_will_lose_relationships": "你将丢失该站点上的所有关注与关注者。", + "domain_block_modal.you_will_lose_relationships": "你将丢失该站点上的全部关注与关注者。", "domain_block_modal.you_wont_see_posts": "你将不会看到此服务器上用户的嘟文或通知。", "domain_pill.activitypub_lets_connect": "它可以让你与不同社交应用上的人交流互动,而不仅限于 Mastodon。", "domain_pill.activitypub_like_language": "ActivityPub 好比 Mastodon 与其它社交网络交流时使用的语言。", @@ -294,7 +339,7 @@ "domain_pill.who_they_are": "用户名可以表明用户的身份和其所在站点,这样你就可以通过在社交网络和人们互动。", "domain_pill.who_you_are": "用户名可以表明你的身份和你所在的站点,这样人们就可以通过在社交网络与你互动。", "domain_pill.your_handle": "你的用户名:", - "domain_pill.your_server": "你的数字家园,你的所有嘟文都在此存储。不喜欢这里吗?你可以随时迁移到其它服务器,并带上你的关注者。", + "domain_pill.your_server": "你的数字家园,你的全部嘟文都在此存储。不喜欢这里吗?你可以随时迁移到其它服务器,并带上你的关注者。", "domain_pill.your_username": "你在此服务器上的唯一标识。不同服务器上可能存在相同用户名的用户。", "dropdown.empty": "选项", "embed.instructions": "复制下列代码以在你的网站中嵌入此嘟文。", @@ -325,6 +370,7 @@ "empty_column.bookmarked_statuses": "你还没有给任何嘟文添加书签。添加书签后的嘟文会显示在这里。", "empty_column.community": "本站时间线还没有内容,写点什么并公开发布,让它活跃起来吧!", "empty_column.direct": "你还未使用过私下提及。当你发出或者收到私下提及时,它将显示在此。", + "empty_column.disabled_feed": "此动态已被你的服务器管理员禁用。", "empty_column.domain_blocks": "暂且没有被屏蔽的站点。", "empty_column.explore_statuses": "目前没有热门内容,稍后再来看看吧!", "empty_column.favourited_statuses": "你没有喜欢过任何嘟文。喜欢过的嘟文会显示在这里。", @@ -338,6 +384,7 @@ "empty_column.notification_requests": "一扫而空!这里没有任何未读通知。当收到新的通知时,将根据你的设置显示在这里。", "empty_column.notifications": "你还没有收到过任何通知,快和其他用户互动吧。", "empty_column.public": "这里什么都没有!写一些公开的嘟文,或者关注其他服务器的用户后,这里就会有嘟文出现了", + "error.no_hashtag_feed_access": "加入或登录以查看和关注此话题标签。", "error.unexpected_crash.explanation": "此页面无法正确显示,这可能是因为我们的代码中有错误,也可能是因为浏览器兼容问题。", "error.unexpected_crash.explanation_addons": "此页面无法正确显示,这个错误很可能是由浏览器附加组件或自动翻译工具造成的。", "error.unexpected_crash.next_steps": "刷新一下页面试试。如果没用,你可以换个浏览器或者用本地应用。", @@ -349,11 +396,9 @@ "explore.trending_links": "新闻", "explore.trending_statuses": "嘟文", "explore.trending_tags": "话题", + "featured_carousel.current": "嘟文 {current, number} / {max, number}", "featured_carousel.header": "{count, plural, other {# 条置顶嘟文}}", - "featured_carousel.next": "下一步", - "featured_carousel.post": "发嘟", - "featured_carousel.previous": "上一步", - "featured_carousel.slide": "第 {index} 共 {total}", + "featured_carousel.slide": "第 {current, number} 条嘟文,共 {max, number} 条", "filter_modal.added.context_mismatch_explanation": "这条过滤规则不适用于你当前访问此嘟文的场景。要在此场景下过滤嘟文,你必须编辑此过滤规则。", "filter_modal.added.context_mismatch_title": "场景不匹配!", "filter_modal.added.expired_explanation": "此过滤规则类别已过期,你需要修改到期日期才能应用。", @@ -396,6 +441,9 @@ "follow_suggestions.who_to_follow": "推荐关注", "followed_tags": "已关注话题", "footer.about": "关于", + "footer.about_mastodon": "关于 Mastodon", + "footer.about_server": "关于 {domain}", + "footer.about_this_server": "关于本站", "footer.directory": "用户列表", "footer.get_app": "获取应用", "footer.keyboard_shortcuts": "快捷键", @@ -497,6 +545,7 @@ "keyboard_shortcuts.toggle_hidden": "显示或隐藏被折叠的正文", "keyboard_shortcuts.toggle_sensitivity": "显示/隐藏媒体", "keyboard_shortcuts.toot": "发送新嘟文", + "keyboard_shortcuts.top": "移动到列表顶部", "keyboard_shortcuts.translate": "翻译嘟文", "keyboard_shortcuts.unfocus": "取消输入/搜索", "keyboard_shortcuts.up": "在列表中让光标上移", @@ -653,9 +702,9 @@ "notifications.column_settings.admin.sign_up": "新注册:", "notifications.column_settings.alert": "桌面通知", "notifications.column_settings.favourite": "喜欢:", - "notifications.column_settings.filter_bar.advanced": "显示所有类别", + "notifications.column_settings.filter_bar.advanced": "显示全部类别", "notifications.column_settings.filter_bar.category": "快速筛选栏", - "notifications.column_settings.follow": "新粉丝:", + "notifications.column_settings.follow": "新关注者:", "notifications.column_settings.follow_request": "新关注请求:", "notifications.column_settings.group": "分组", "notifications.column_settings.mention": "提及:", @@ -745,7 +794,9 @@ "privacy.unlisted.short": "悄悄公开", "privacy_policy.last_updated": "最近更新于 {date}", "privacy_policy.title": "隐私政策", + "quote_error.edit": "编辑嘟文时无法添加引用。", "quote_error.poll": "不允许引用投票嘟文。", + "quote_error.private_mentions": "不允许引用私下提及嘟文。", "quote_error.quote": "一次只能引用一条嘟文。", "quote_error.unauthorized": "你没有权限引用此嘟文。", "quote_error.upload": "不允许引用有媒体附件的嘟文。", @@ -798,9 +849,9 @@ "report.reasons.spam_description": "恶意链接、虚假互动或重复回复", "report.reasons.violation": "违反服务器规则", "report.reasons.violation_description": "你清楚它违反了特定的规则", - "report.rules.subtitle": "选择所有适用选项", + "report.rules.subtitle": "选择全部适用选项", "report.rules.title": "违反了哪些规则?", - "report.statuses.subtitle": "选择所有适用选项", + "report.statuses.subtitle": "选择全部适用选项", "report.statuses.title": "是否有任何嘟文可以支持这一报告?", "report.submit": "提交", "report.target": "举报 {target}", @@ -851,7 +902,7 @@ "server_banner.is_one_of_many": "{domain} 是可用于参与联邦宇宙的众多独立 Mastodon 站点之一。", "server_banner.server_stats": "服务器统计数据:", "sign_in_banner.create_account": "创建账号", - "sign_in_banner.follow_anyone": "关注联邦宇宙中的任何人,并按时间顺序查看所有内容。没有算法、广告或诱导链接。", + "sign_in_banner.follow_anyone": "关注联邦宇宙中的任何人,并按时间顺序查看全部内容。没有算法、广告或诱导链接。", "sign_in_banner.mastodon_is": "Mastodon 是了解最新动态的最佳途径。", "sign_in_banner.sign_in": "登录", "sign_in_banner.sso_redirect": "登录或注册", @@ -862,11 +913,15 @@ "status.block": "屏蔽 @{name}", "status.bookmark": "添加到书签", "status.cancel_reblog_private": "取消转嘟", - "status.cannot_quote": "你无法引用此嘟文", + "status.cannot_quote": "你无权引用这条嘟文", "status.cannot_reblog": "不能转嘟这条嘟文", "status.contains_quote": "包含引用", - "status.context.load_new_replies": "有新回复", - "status.context.loading": "正在检查更多回复", + "status.context.loading": "正在加载更多回复", + "status.context.loading_error": "无法加载新回复", + "status.context.loading_success": "已加载新回复", + "status.context.more_replies_found": "已找到更多回复", + "status.context.retry": "重试", + "status.context.show": "显示", "status.continued_thread": "上接嘟文串", "status.copy": "复制嘟文链接", "status.delete": "删除", @@ -879,7 +934,7 @@ "status.edited_x_times": "共编辑 {count, plural, other {{count} 次}}", "status.embed": "获取嵌入代码", "status.favourite": "喜欢", - "status.favourites": "{count, plural, other {次喜欢}}", + "status.favourites_count": "{count, plural, other {{counter} 次喜欢}}", "status.filter": "过滤此嘟文", "status.history.created": "{name} 创建于 {date}", "status.history.edited": "{name} 编辑于 {date}", @@ -895,9 +950,12 @@ "status.pin": "在个人资料页面置顶", "status.quote": "引用", "status.quote.cancel": "取消引用", + "status.quote_error.blocked_account_hint.title": "由于你已屏蔽@{name},此嘟文已隐藏。", + "status.quote_error.blocked_domain_hint.title": "由于你已屏蔽{domain},此嘟文已隐藏。", "status.quote_error.filtered": "已根据你的筛选器过滤", "status.quote_error.limited_account_hint.action": "仍然显示", "status.quote_error.limited_account_hint.title": "此账号已被 {domain} 管理员隐藏。", + "status.quote_error.muted_account_hint.title": "由于你已设置隐藏@{name},此嘟文已隐藏。", "status.quote_error.not_available": "嘟文不可用", "status.quote_error.pending_approval": "嘟文待发布", "status.quote_error.pending_approval_popout.body": "在Mastodon上,你可以控制其他人引用你嘟文的权限。此嘟文在得到原作者的同意后就会发布。", @@ -908,15 +966,17 @@ "status.quote_policy_change": "更改谁可以引用", "status.quote_post_author": "引用了 @{name} 的嘟文", "status.quote_private": "不能引用私人嘟文", - "status.quotes": "{count, plural, other {引用嘟文}}", "status.quotes.empty": "还没有人引用过此条嘟文。引用此嘟文的人会显示在这里。", + "status.quotes.local_other_disclaimer": "遭作者拒绝的引用将不会显示。", + "status.quotes.remote_other_disclaimer": "此处仅保证会显示来自 {domain} 的引用。遭作者拒绝的引用将不会显示。", + "status.quotes_count": "{count, plural, other {{counter} 次引用}}", "status.read_more": "查看更多", "status.reblog": "转嘟", "status.reblog_or_quote": "转嘟或引用", "status.reblog_private": "向关注者再次分享", "status.reblogged_by": "{name} 转嘟了", - "status.reblogs": "{count, plural, other {次转嘟}}", "status.reblogs.empty": "没有人转嘟过此条嘟文。如果有人转嘟了,就会显示在这里。", + "status.reblogs_count": "{count, plural, other {{counter} 次转嘟}}", "status.redraft": "删除并重新编辑", "status.remove_bookmark": "移除书签", "status.remove_favourite": "从喜欢列表中移除", @@ -939,7 +999,7 @@ "status.uncached_media_warning": "预览不可用", "status.unmute_conversation": "恢复此对话的通知提醒", "status.unpin": "在个人资料页面取消置顶", - "subscribed_languages.lead": "更改此选择后,只有选定语言的嘟文才会出现在你的主页和列表时间线上。选择「无」将显示所有语言的嘟文。", + "subscribed_languages.lead": "更改此选择后,只有选定语言的嘟文才会出现在你的主页和列表时间线上。选择「无」将显示全部语言的嘟文。", "subscribed_languages.save": "保存更改", "subscribed_languages.target": "更改 {target} 的订阅语言", "tabs_bar.home": "主页", @@ -990,13 +1050,15 @@ "video.volume_down": "音量减小", "video.volume_up": "提高音量", "visibility_modal.button_title": "设置可见性", + "visibility_modal.direct_quote_warning.text": "如果你保存当前设置,引用的嘟文将被转换为链接。", + "visibility_modal.direct_quote_warning.title": "引用嘟文无法嵌入到私下提及中", "visibility_modal.header": "可见性和互动", "visibility_modal.helper.direct_quoting": "Mastodon上发布的私下提及无法被他人引用。", "visibility_modal.helper.privacy_editing": "嘟文发布后便无法更改可见性。", "visibility_modal.helper.privacy_private_self_quote": "自我引用的私人嘟文无法设置为公开。", "visibility_modal.helper.private_quoting": "Mastodon上发布的仅限关注者可见的嘟文无法被他人引用。", "visibility_modal.helper.unlisted_quoting": "当其他人引用你时,他们的嘟文也会从热门时间线上隐藏。", - "visibility_modal.instructions": "控制谁可以和此嘟文互动。你也可以前往偏好设置 > 发布默认值将此设置应用到所有未来发布的嘟文。", + "visibility_modal.instructions": "控制谁可以和此嘟文互动。你也可以前往偏好设置 > 发布默认值将此设置应用到全部未来发布的嘟文。", "visibility_modal.privacy_label": "可见性", "visibility_modal.quote_followers": "仅关注者", "visibility_modal.quote_label": "谁可以引用", diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json index 1637e1cf589219..8d86bd981d2afc 100644 --- a/app/javascript/mastodon/locales/zh-HK.json +++ b/app/javascript/mastodon/locales/zh-HK.json @@ -28,6 +28,7 @@ "account.disable_notifications": "當 @{name} 發文時不要再通知我", "account.domain_blocking": "封鎖網域", "account.edit_profile": "修改個人檔案", + "account.edit_profile_short": "編輯", "account.enable_notifications": "當 @{name} 發文時通知我", "account.endorse": "在個人檔案中推薦對方", "account.familiar_followers_many": "{name1}、{name2} 及{othersCount, plural, other {你認識的 # 人}}已追蹤", @@ -40,12 +41,18 @@ "account.featured_tags.last_status_never": "暫無文章", "account.follow": "關注", "account.follow_back": "追蹤對方", + "account.follow_back_short": "追蹤對方", + "account.follow_request": "追蹤請求", + "account.follow_request_cancel": "取消請求", + "account.follow_request_cancel_short": "取消", + "account.follow_request_short": "請求", "account.followers": "追蹤者", "account.followers.empty": "尚未有人追蹤這位使用者。", "account.followers_counter": "{count, plural, other {{counter} 個追蹤者}}", "account.followers_you_know_counter": "你認識的 {counter} 人", "account.following": "正在追蹤", "account.follows.empty": "這位使用者尚未追蹤任何人。", + "account.follows_you": "正追蹤你", "account.go_to_profile": "前往個人檔案", "account.hide_reblogs": "隱藏 @{name} 的轉推", "account.in_memoriam": "謹此悼念。", @@ -60,12 +67,14 @@ "account.mute_notifications_short": "靜音通知", "account.mute_short": "靜音", "account.muted": "靜音", + "account.muting": "靜音", + "account.mutual": "你們已互相追蹤", "account.no_bio": "未提供描述。", "account.open_original_page": "打開原始頁面", "account.posts": "帖文", "account.posts_with_replies": "帖文與回覆", + "account.remove_from_followers": "移除追蹤者{name}", "account.report": "檢舉 @{name}", - "account.requested": "正在等待核准。按一下以取消追蹤請求", "account.requested_follow": "{name} 要求追蹤你", "account.share": "分享 @{name} 的個人檔案", "account.show_reblogs": "顯示 @{name} 的轉推", @@ -96,7 +105,6 @@ "alt_text_modal.cancel": "取消", "alt_text_modal.done": "完成", "announcement.announcement": "公告", - "annual_report.summary.thanks": "感謝您成為 Mastodon 的一份子!", "attachments_list.unprocessed": "(未處理)", "audio.hide": "隱藏音訊", "block_modal.remote_users_caveat": "我們會要求 {domain} 伺服器尊重你的決定。然而,由於部份伺服器可能以不同方式處理封鎖,因此無法保證一定會成功。公開帖文仍然有機會被未登入的使用者看見。", @@ -131,8 +139,11 @@ "column.direct": "私人提及", "column.directory": "瀏覽個人資料", "column.domain_blocks": "封鎖的服務站", + "column.edit_list": "編輯列表", "column.favourites": "最愛", "column.firehose": "即時動態", + "column.firehose_local": "本伺服器的即時動態", + "column.firehose_singular": "即時動態", "column.follow_requests": "追蹤請求", "column.home": "主頁", "column.lists": "列表", @@ -183,17 +194,28 @@ "confirmations.delete_list.confirm": "刪除", "confirmations.delete_list.message": "你確定要永久刪除這列表嗎?", "confirmations.delete_list.title": "刪除列表?", + "confirmations.discard_draft.post.title": "要捨棄文章的草稿?", "confirmations.discard_edit_media.confirm": "捨棄", "confirmations.discard_edit_media.message": "您在媒體描述或預覽有尚未儲存的變更。確定要捨棄它們嗎?", + "confirmations.follow_to_list.title": "追蹤使用者?", "confirmations.logout.confirm": "登出", "confirmations.logout.message": "確定要登出嗎?", "confirmations.logout.title": "登出?", + "confirmations.missing_alt_text.confirm": "新增替代文字", + "confirmations.missing_alt_text.message": "你的貼文尚未附上替代文字。新增描述有助更多人存取其內容。", "confirmations.mute.confirm": "靜音", + "confirmations.quiet_post_quote_info.dismiss": "不再提醒", + "confirmations.quiet_post_quote_info.got_it": "知道了", "confirmations.redraft.confirm": "刪除並編輯", "confirmations.redraft.message": "你確定要移除並重新起草這篇帖文嗎?你將會失去最愛和轉推,而回覆也會與原始帖文斷開連接。", + "confirmations.revoke_quote.message": "此操作無法還原。", + "confirmations.revoke_quote.title": "移除貼文?", + "confirmations.unblock.confirm": "解除封鎖", + "confirmations.unblock.title": "解除封鎖 {name}?", "confirmations.unfollow.confirm": "取消追蹤", - "confirmations.unfollow.message": "真的不要繼續追蹤 {name} 了嗎?", - "confirmations.unfollow.title": "取消追蹤使用者?", + "confirmations.unfollow.title": "取消追蹤 {name}?", + "confirmations.withdraw_request.confirm": "撤回請求", + "confirmations.withdraw_request.title": "撤回追蹤{name}的請求?", "content_warning.hide": "隱藏嘟文", "content_warning.show": "仍要顯示", "content_warning.show_more": "顯示更多", @@ -435,6 +457,8 @@ "navigation_bar.follows_and_followers": "追蹤及追蹤者", "navigation_bar.import_export": "匯入及匯出", "navigation_bar.lists": "列表", + "navigation_bar.live_feed_local": "即時動態(本地)", + "navigation_bar.live_feed_public": "即時動態(公開)", "navigation_bar.logout": "登出", "navigation_bar.more": "更多", "navigation_bar.mutes": "靜音名單", @@ -667,7 +691,6 @@ "status.edited": "最後編輯於 {date}", "status.edited_x_times": "Edited {count, plural, one {{count} 次} other {{count} 次}}", "status.favourite": "最愛", - "status.favourites": "{count, plural, one {則最愛} other {則最愛}}", "status.filter": "篩選此帖文", "status.history.created": "{name} 於 {date} 建立", "status.history.edited": "{name} 於 {date} 編輯", @@ -684,7 +707,6 @@ "status.read_more": "閱讀更多", "status.reblog": "轉推", "status.reblogged_by": "{name} 轉推", - "status.reblogs": "{count, plural, one {則轉推} other {則轉推}}", "status.reblogs.empty": "還未有人轉推。有的話會顯示在這裡。", "status.redraft": "刪除並編輯", "status.remove_bookmark": "移除書籤", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index e4f9dcea29bb52..78d191efa38123 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -1,60 +1,66 @@ { - "about.blocks": "被限制的伺服器", + "about.blocks": "受管制的伺服器", "about.contact": "聯絡我們:", "about.default_locale": "預設", "about.disclaimer": "Mastodon 是一個自由的開源軟體,是 Mastodon gGmbH 之註冊商標。", "about.domain_blocks.no_reason_available": "無法存取的原因", - "about.domain_blocks.preamble": "Mastodon 基本上允許您瀏覽聯邦宇宙中任何伺服器的內容並與使用者互動。以下是在本伺服器上設定的例外。", - "about.domain_blocks.silenced.explanation": "一般來說您不會看到來自這個伺服器的個人檔案和內容,除非您明確搜尋或主動跟隨對方。", + "about.domain_blocks.preamble": "Mastodon 基本上允許您瀏覽聯邦宇宙中任何伺服器的內容並與使用者互動。以下是於本伺服器上設定之例外。", + "about.domain_blocks.silenced.explanation": "一般來說您不會看到來自這個伺服器的個人檔案與內容,除非您明確地檢視或著跟隨此個人檔案。", "about.domain_blocks.silenced.title": "已受限", "about.domain_blocks.suspended.explanation": "來自此伺服器的資料都不會被處理、儲存或交換,也無法和此伺服器上的使用者互動與交流。", "about.domain_blocks.suspended.title": "已停權", "about.language_label": "語言", "about.not_available": "無法於本伺服器上使用此資訊。", - "about.powered_by": "由 {mastodon} 提供的去中心化社群媒體", + "about.powered_by": "由 {mastodon} 提供之去中心化社群媒體", "about.rules": "伺服器規則", "account.account_note_header": "個人備註", "account.add_or_remove_from_list": "自列表中新增或移除", "account.badges.bot": "機器人", "account.badges.group": "群組", "account.block": "封鎖 @{name}", - "account.block_domain": "封鎖來自 {domain} 網域的所有內容", + "account.block_domain": "封鎖來自 {domain} 網域之所有內容", "account.block_short": "封鎖", "account.blocked": "已封鎖", "account.blocking": "封鎖中", "account.cancel_follow_request": "收回跟隨請求", "account.copy": "複製個人檔案連結", - "account.direct": " @{name}", - "account.disable_notifications": "取消來自 @{name} 嘟文的通知", + "account.direct": "私訊 @{name}", + "account.disable_notifications": "取消來自 @{name} 嘟文之通知", "account.domain_blocking": "封鎖中網域", "account.edit_profile": "編輯個人檔案", - "account.enable_notifications": "當 @{name} 嘟文時通知我", - "account.endorse": "於個人檔案推薦對方", - "account.familiar_followers_many": "被 {name1}、{name2}、及 {othersCount, plural, other {其他您認識的 # 人}} 跟隨", + "account.edit_profile_short": "編輯", + "account.enable_notifications": "當 @{name} 發嘟時通知我", + "account.endorse": "於個人檔案推薦", + "account.familiar_followers_many": "被 {name1}、{name2}、及{othersCount, plural, other {其他您認識的 # 人}} 跟隨", "account.familiar_followers_one": "被 {name1} 跟隨", "account.familiar_followers_two": "被 {name1} 與 {name2} 跟隨", "account.featured": "精選內容", "account.featured.accounts": "個人檔案", "account.featured.hashtags": "主題標籤", - "account.featured_tags.last_status_at": "上次嘟文於 {date}", + "account.featured_tags.last_status_at": "上次發嘟於 {date}", "account.featured_tags.last_status_never": "沒有嘟文", "account.follow": "跟隨", "account.follow_back": "跟隨回去", + "account.follow_back_short": "跟隨回去", + "account.follow_request": "跟隨", + "account.follow_request_cancel": "取消跟隨請求", + "account.follow_request_cancel_short": "取消", + "account.follow_request_short": "跟隨請求", "account.followers": "跟隨者", - "account.followers.empty": "尚未有人跟隨這位使用者。", - "account.followers_counter": "被 {count, plural, other {{count} 人}}跟隨", - "account.followers_you_know_counter": "{counter} 位您知道的跟隨者", + "account.followers.empty": "尚未有人跟隨此使用者。", + "account.followers_counter": "被 {count, plural, other {{counter} 人}}跟隨", + "account.followers_you_know_counter": "{counter} 位您認識的跟隨者", "account.following": "跟隨中", - "account.following_counter": "正在跟隨 {count,plural,other {{count} 人}}", - "account.follows.empty": "這位使用者尚未跟隨任何人。", + "account.following_counter": "正在跟隨 {count,plural,other {{counter} 人}}", + "account.follows.empty": "此使用者尚未跟隨任何人。", "account.follows_you": "已跟隨您", "account.go_to_profile": "前往個人檔案", - "account.hide_reblogs": "隱藏來自 @{name} 的轉嘟", + "account.hide_reblogs": "隱藏來自 @{name} 之轉嘟", "account.in_memoriam": "謹此悼念。", "account.joined_short": "加入時間", "account.languages": "變更訂閱的語言", "account.link_verified_on": "已於 {date} 檢查此連結的擁有者權限", - "account.locked_info": "此帳號的隱私狀態設定為鎖定。該擁有者會手動審核能跟隨此帳號的人。", + "account.locked_info": "此帳號的隱私狀態被設為鎖定。該擁有者會手動審核能跟隨此帳號的人。", "account.media": "媒體", "account.mention": "提及 @{name}", "account.moved_to": "{name} 目前的新帳號為:", @@ -70,12 +76,11 @@ "account.posts_with_replies": "嘟文與回覆", "account.remove_from_followers": "自跟隨者中移除 {name}", "account.report": "檢舉 @{name}", - "account.requested": "正在等候審核。按一下以取消跟隨請求", "account.requested_follow": "{name} 要求跟隨您", "account.requests_to_follow_you": "要求跟隨您", "account.share": "分享 @{name} 的個人檔案", "account.show_reblogs": "顯示來自 @{name} 的轉嘟", - "account.statuses_counter": "{count, plural, other {{count} 則嘟文}}", + "account.statuses_counter": "{count, plural, other {{counter} 則嘟文}}", "account.unblock": "解除封鎖 @{name}", "account.unblock_domain": "解除封鎖網域 {domain}", "account.unblock_domain_short": "解除封鎖", @@ -85,7 +90,7 @@ "account.unmute": "解除靜音 @{name}", "account.unmute_notifications_short": "解除靜音推播通知", "account.unmute_short": "解除靜音", - "account_note.placeholder": "按此新增備註", + "account_note.placeholder": "點擊以新增備註", "admin.dashboard.daily_retention": "註冊後使用者存留率(日)", "admin.dashboard.monthly_retention": "註冊後使用者存留率(月)", "admin.dashboard.retention.average": "平均", @@ -100,65 +105,94 @@ "alert.unexpected.message": "發生非預期的錯誤。", "alert.unexpected.title": "哎呀!", "alt_text_badge.title": "ALT 說明文字", - "alt_text_modal.add_alt_text": "新增說明文字", - "alt_text_modal.add_text_from_image": "自圖片新增說明文字", + "alt_text_modal.add_alt_text": "新增 ALT 說明文字", + "alt_text_modal.add_text_from_image": "自圖片新增 ALT 說明文字", "alt_text_modal.cancel": "取消", "alt_text_modal.change_thumbnail": "變更預覽圖", "alt_text_modal.describe_for_people_with_hearing_impairments": "替聽覺障礙人士描述...", "alt_text_modal.describe_for_people_with_visual_impairments": "替視覺障礙人士描述...", "alt_text_modal.done": "完成", "announcement.announcement": "公告", - "annual_report.summary.archetype.booster": "酷炫獵人", - "annual_report.summary.archetype.lurker": "潛水高手", - "annual_report.summary.archetype.oracle": "先知", - "annual_report.summary.archetype.pollster": "民調專家", - "annual_report.summary.archetype.replier": "社交菁英", - "annual_report.summary.followers.followers": "跟隨者", - "annual_report.summary.followers.total": "總共 {count}", - "annual_report.summary.here_it_is": "以下是您的 {year} 年度回顧:", - "annual_report.summary.highlighted_post.by_favourites": "最多被加到最愛的嘟文", - "annual_report.summary.highlighted_post.by_reblogs": "最多轉嘟的嘟文", - "annual_report.summary.highlighted_post.by_replies": "最多回覆的嘟文", - "annual_report.summary.highlighted_post.possessive": "{name} 的", + "annual_report.announcement.action_build": "建立我的 Mastodon 年度回顧 (Wrapstodon)", + "annual_report.announcement.action_dismiss": "不需要,謝謝", + "annual_report.announcement.action_view": "檢視我的 Mastodon 年度回顧 (Wrapstodon)", + "annual_report.announcement.description": "探索更多關於您過去一年於 Mastodon 上的互動情況。", + "annual_report.announcement.title": "您的 Mastodon 年度回顧 {year} 已抵達", + "annual_report.nav_item.badge": "新報告", + "annual_report.shared_page.donate": "捐款", + "annual_report.shared_page.footer": "由 Mastodon 團隊 {heart} 產生", + "annual_report.shared_page.footer_server_info": "{username} 使用 {domain},運行 Mastodon 的眾多社群之一。", + "annual_report.summary.archetype.booster.desc_public": "{name} 持續追尋值得轉嘟的嘟文,以精準眼光放大其他創作者之影響力。", + "annual_report.summary.archetype.booster.desc_self": "您持續追尋值得轉嘟的嘟文,以精準眼光放大其他創作者之影響力。", + "annual_report.summary.archetype.booster.name": "弓箭手", + "annual_report.summary.archetype.die_drei_fragezeichen": "???", + "annual_report.summary.archetype.lurker.desc_public": "我們知道 {name} 曾於聯邦宇宙的某個角落駐足,以靜謐的方式獨享 Mastodon。", + "annual_report.summary.archetype.lurker.desc_self": "我們知道您曾於聯邦宇宙的某個角落駐足,以靜謐的方式獨享 Mastodon。", + "annual_report.summary.archetype.lurker.name": "斯多葛主義信徒", + "annual_report.summary.archetype.oracle.desc_public": "{name} 發表的新嘟文多於回嘟,使 Mastodon 注入活力並展望未來。", + "annual_report.summary.archetype.oracle.desc_self": "您發表的新嘟文多於回嘟,使 Mastodon 注入活力並展望未來。", + "annual_report.summary.archetype.oracle.name": "先知", + "annual_report.summary.archetype.pollster.desc_public": "{name} 發起投票多於其他嘟文類型,使 Mastodon 充滿探索與好奇。", + "annual_report.summary.archetype.pollster.desc_self": "您發起投票多於其他嘟文類型,使 Mastodon 充滿探索與好奇。", + "annual_report.summary.archetype.pollster.name": "探究者", + "annual_report.summary.archetype.replier.desc_public": "{name} 經常回覆他人嘟文,替 Mastodon 帶來源源不絕的新討論。", + "annual_report.summary.archetype.replier.desc_self": "您經常回覆他人嘟文,替 Mastodon 帶來源源不絕的新討論。", + "annual_report.summary.archetype.replier.name": "花蝴蝶", + "annual_report.summary.archetype.reveal": "揭露我的類型稱號", + "annual_report.summary.archetype.reveal_description": "感謝您成為 Mastodon 的一分子!是時候揭曉您於 {year} 年份的代表類型。", + "annual_report.summary.archetype.title_public": "{name} 的類型稱號", + "annual_report.summary.archetype.title_self": "您的類型稱號", + "annual_report.summary.close": "關閉", + "annual_report.summary.copy_link": "複製連結", + "annual_report.summary.followers.new_followers": "{count, plural, other {位新跟隨者}}", + "annual_report.summary.highlighted_post.boost_count": "此嘟文被轉嘟 {count, plural, other {# 次}}。", + "annual_report.summary.highlighted_post.favourite_count": "此嘟文被加入最愛 {count, plural, other {# 次}}。", + "annual_report.summary.highlighted_post.reply_count": "此嘟文獲得 {count, plural, other {# 則回覆}}。", + "annual_report.summary.highlighted_post.title": "最受歡迎的嘟文", "annual_report.summary.most_used_app.most_used_app": "最常使用的應用程式", "annual_report.summary.most_used_hashtag.most_used_hashtag": "最常使用的主題標籤", - "annual_report.summary.most_used_hashtag.none": "無最常用之主題標籤", + "annual_report.summary.most_used_hashtag.used_count": "您於 {count, plural, other {# 則嘟文}}中使用此主題標籤。", + "annual_report.summary.most_used_hashtag.used_count_public": "{name} 於 {count, plural, other {# 則嘟文}}中使用此主題標籤。", "annual_report.summary.new_posts.new_posts": "新嘟文", "annual_report.summary.percentile.text": "這讓您成為前{domain} 的使用者。", "annual_report.summary.percentile.we_wont_tell_bernie": "我們不會告訴 Bernie。", - "annual_report.summary.thanks": "感謝您成為 Mastodon 的一份子!", - "attachments_list.unprocessed": "(未經處理)", + "annual_report.summary.share_elsewhere": "分享至其他地方", + "annual_report.summary.share_message": "我獲得 {archetype} 稱號!", + "annual_report.summary.share_on_mastodon": "於 Mastodon 上分享", + "attachments_list.unprocessed": "(未處理)", "audio.hide": "隱藏音訊", "block_modal.remote_users_caveat": "我們會要求 {domain} 伺服器尊重您的決定。然而,我們無法保證所有伺服器皆會遵守,某些伺服器可能以不同方式處理封鎖。未登入之使用者仍可能看見您的公開嘟文。", "block_modal.show_less": "減少顯示", "block_modal.show_more": "顯示更多", "block_modal.they_cant_mention": "他們無法提及或跟隨您。", - "block_modal.they_cant_see_posts": "他們無法讀取您的嘟文,且您不會見到他們的。", + "block_modal.they_cant_see_posts": "他們無法讀取您的嘟文,且您不會見到他們的嘟文。", "block_modal.they_will_know": "他們能見到他們已被封鎖。", "block_modal.title": "是否封鎖該使用者?", - "block_modal.you_wont_see_mentions": "您不會見到提及他們的嘟文。", - "boost_modal.combo": "下次您可以按 {combo} 跳過", + "block_modal.you_wont_see_mentions": "您將不會見到提及他們的嘟文。", + "boost_modal.combo": "您下次可以按 {combo} 跳過", "boost_modal.reblog": "是否要轉嘟?", "boost_modal.undo_reblog": "是否要取消轉嘟?", "bundle_column_error.copy_stacktrace": "複製錯誤報告", - "bundle_column_error.error.body": "無法繪製請求的頁面。這可能是因為我們程式碼中的臭蟲或是瀏覽器的相容問題。", + "bundle_column_error.error.body": "無法繪製請求的頁面。這可能是因為我們程式碼中的臭蟲或是瀏覽器相容問題。", "bundle_column_error.error.title": "糟糕!", "bundle_column_error.network.body": "嘗試載入此頁面時發生錯誤。這可能是因為您的網際網路連線或此伺服器有暫時性的問題。", "bundle_column_error.network.title": "網路錯誤", "bundle_column_error.retry": "重試", "bundle_column_error.return": "返回首頁", - "bundle_column_error.routing.body": "找不到請求的頁面。您確定網址列中的 URL 是正確的嗎?", + "bundle_column_error.routing.body": "找不到請求的頁面。您是否確定網址列中的 URL 是正確的?", "bundle_column_error.routing.title": "404", "bundle_modal_error.close": "關閉", "bundle_modal_error.message": "載入此畫面時發生錯誤。", "bundle_modal_error.retry": "重試", - "closed_registrations.other_server_instructions": "因為 Mastodon 是去中心化的,所以您也能於其他伺服器上建立帳號,並仍然與這個伺服器互動。", + "carousel.current": "頁面 {current, number} / {max, number}", + "carousel.slide": "{max, number} 頁中之第 {current, number} 頁", + "closed_registrations.other_server_instructions": "因為 Mastodon 是去中心化的,所以您也能於其他伺服器上建立帳號,並仍然與此伺服器互動。", "closed_registrations_modal.description": "目前無法於 {domain} 建立新帳號,但也請別忘了,您並不一定需要有 {domain} 伺服器的帳號,也能使用 Mastodon。", "closed_registrations_modal.find_another_server": "尋找另一個伺服器", "closed_registrations_modal.preamble": "Mastodon 是去中心化的,所以無論您於哪個伺服器新增帳號,都可以與此伺服器上的任何人跟隨及互動。您甚至能自行架設自己的伺服器!", "closed_registrations_modal.title": "註冊 Mastodon", "column.about": "關於", - "column.blocks": "已封鎖的使用者", + "column.blocks": "已封鎖使用者", "column.bookmarks": "書籤", "column.community": "本站時間軸", "column.create_list": "建立列表", @@ -168,11 +202,13 @@ "column.edit_list": "編輯列表", "column.favourites": "最愛", "column.firehose": "即時內容", + "column.firehose_local": "本站伺服器之即時內容", + "column.firehose_singular": "即時內容", "column.follow_requests": "跟隨請求", "column.home": "首頁", "column.list_members": "管理列表成員", "column.lists": "列表", - "column.mutes": "已靜音的使用者", + "column.mutes": "已靜音使用者", "column.notifications": "推播通知", "column.pins": "釘選的嘟文", "column.public": "聯邦時間軸", @@ -184,9 +220,10 @@ "column_header.show_settings": "顯示設定", "column_header.unpin": "取消釘選", "column_search.cancel": "取消", - "community.column_settings.local_only": "只顯示本站", - "community.column_settings.media_only": "只顯示媒體", - "community.column_settings.remote_only": "只顯示遠端", + "community.column_settings.local_only": "僅顯示本站", + "community.column_settings.media_only": "僅顯示媒體", + "community.column_settings.remote_only": "僅顯示遠端", + "compose.error.blank_post": "嘟文無法為空白。", "compose.language.change": "變更語言", "compose.language.search": "搜尋語言...", "compose.published.body": "發嘟成功。", @@ -194,14 +231,14 @@ "compose.saved.body": "已儲存嘟文。", "compose_form.direct_message_warning_learn_more": "了解更多", "compose_form.encryption_warning": "Mastodon 上的嘟文並未進行端到端加密。請不要透過 Mastodon 分享任何敏感資訊。", - "compose_form.hashtag_warning": "由於這則嘟文設定為非公開,將不會列於任何主題標籤下。只有公開的嘟文才能藉由主題標籤被找到。", - "compose_form.lock_disclaimer": "您的帳號尚未 {locked}。任何人皆能跟隨您並看到您設定成只對跟隨者顯示的嘟文。", + "compose_form.hashtag_warning": "由於這則嘟文設定為「不公開」,它將不被列於任何主題標籤下。只有公開的嘟文才能藉由主題標籤被找到。", + "compose_form.lock_disclaimer": "您的帳號尚未 {locked}。任何人皆能跟隨您並看到您設定成僅有跟隨者可見的嘟文。", "compose_form.lock_disclaimer.lock": "上鎖", "compose_form.placeholder": "正在想些什麼嗎?", "compose_form.poll.duration": "投票期限", "compose_form.poll.multiple": "多選", "compose_form.poll.option_placeholder": "選項 {number}", - "compose_form.poll.single": "單一選擇", + "compose_form.poll.single": "單選", "compose_form.poll.switch_to_multiple": "變更投票為允許多個選項", "compose_form.poll.switch_to_single": "變更投票為允許單一選項", "compose_form.poll.type": "投票方式", @@ -221,7 +258,7 @@ "confirmations.delete_list.title": "是否刪除該列表?", "confirmations.discard_draft.confirm": "捨棄並繼續", "confirmations.discard_draft.edit.cancel": "恢復編輯", - "confirmations.discard_draft.edit.message": "繼續將會捨棄任何您對正在編輯的此嘟文進行之任何變更。", + "confirmations.discard_draft.edit.message": "繼續將捨棄任何您對正在編輯的此嘟文進行之任何變更。", "confirmations.discard_draft.edit.title": "是否捨棄對您的嘟文之變更?", "confirmations.discard_draft.post.cancel": "恢復草稿", "confirmations.discard_draft.post.message": "繼續將捨棄您正在撰寫中之嘟文。", @@ -234,27 +271,35 @@ "confirmations.logout.confirm": "登出", "confirmations.logout.message": "您確定要登出嗎?", "confirmations.logout.title": "您確定要登出嗎?", - "confirmations.missing_alt_text.confirm": "新增說明文字", - "confirmations.missing_alt_text.message": "您的嘟文中的多媒體內容未附上說明文字。添加說明文字描述能幫助更多人存取您的內容。", + "confirmations.missing_alt_text.confirm": "新增 ALT 說明文字", + "confirmations.missing_alt_text.message": "您的嘟文中的多媒體內容未附上 ALT 說明文字。添加說明文字描述能幫助更多人存取您的內容。", "confirmations.missing_alt_text.secondary": "仍要發嘟", - "confirmations.missing_alt_text.title": "是否新增說明文字?", + "confirmations.missing_alt_text.title": "是否新增 ALT 說明文字?", "confirmations.mute.confirm": "靜音", + "confirmations.private_quote_notify.cancel": "返回至編輯", + "confirmations.private_quote_notify.confirm": "發表嘟文", + "confirmations.private_quote_notify.do_not_show_again": "不再顯示此訊息", + "confirmations.private_quote_notify.message": "您所引用與提及之使用者將被通知且能檢視您的嘟文,即便他們並無跟隨您。", + "confirmations.private_quote_notify.title": "是否和跟隨者與提及使用者分享此內容?", "confirmations.quiet_post_quote_info.dismiss": "不要再提醒我", "confirmations.quiet_post_quote_info.got_it": "了解", "confirmations.quiet_post_quote_info.message": "當引用不於公開時間軸顯示之嘟文時,您的嘟文將自熱門時間軸隱藏。", "confirmations.quiet_post_quote_info.title": "引用不於公開時間軸顯示之嘟文", "confirmations.redraft.confirm": "刪除並重新編輯", - "confirmations.redraft.message": "您確定要刪除這則嘟文並重新編輯嗎?您將失去這則嘟文之轉嘟及最愛,且對此嘟文之回覆會變成獨立的嘟文。", + "confirmations.redraft.message": "您確定要刪除這則嘟文並重新編輯嗎?您將失去此嘟文之轉嘟及最愛,且對原嘟文之回覆將變成獨立嘟文。", "confirmations.redraft.title": "是否刪除並重新編輯該嘟文?", "confirmations.remove_from_followers.confirm": "移除跟隨者", - "confirmations.remove_from_followers.message": "{name} 將會停止跟隨您。您確定要繼續嗎?", + "confirmations.remove_from_followers.message": "{name} 將停止跟隨您。您確定要繼續嗎?", "confirmations.remove_from_followers.title": "是否移除該跟隨者?", "confirmations.revoke_quote.confirm": "移除嘟文", "confirmations.revoke_quote.message": "此動作無法復原。", - "confirmations.revoke_quote.title": "您是否確定移除嘟文?", + "confirmations.revoke_quote.title": "是否移除該嘟文?", + "confirmations.unblock.confirm": "解除封鎖", + "confirmations.unblock.title": "是否解除封鎖 {name}?", "confirmations.unfollow.confirm": "取消跟隨", - "confirmations.unfollow.message": "您確定要取消跟隨 {name} 嗎?", - "confirmations.unfollow.title": "是否取消跟隨該使用者?", + "confirmations.unfollow.title": "是否取消跟隨 {name}?", + "confirmations.withdraw_request.confirm": "收回跟隨請求", + "confirmations.withdraw_request.title": "是否收回對 {name} 之跟隨請求?", "content_warning.hide": "隱藏嘟文", "content_warning.show": "仍要顯示", "content_warning.show_more": "顯示更多", @@ -266,7 +311,7 @@ "copypaste.copied": "已複製", "copypaste.copy_to_clipboard": "複製到剪貼簿", "directory.federated": "來自已知聯邦宇宙", - "directory.local": "僅來自 {domain} 網域", + "directory.local": "僅來自 {domain}", "directory.new_arrivals": "新人", "directory.recently_active": "最近活躍", "disabled_account_banner.account_settings": "帳號設定", @@ -280,9 +325,9 @@ "domain_block_modal.they_cant_follow": "來自此伺服器之使用者將無法跟隨您。", "domain_block_modal.they_wont_know": "他們不會知道他們已被封鎖。", "domain_block_modal.title": "是否封鎖該網域?", - "domain_block_modal.you_will_lose_num_followers": "您將會失去 {followersCount, plural, other {{followersCountDisplay} 個跟隨者}} 與 {followingCount, plural, other {{followingCountDisplay} 個您跟隨之帳號}}.", + "domain_block_modal.you_will_lose_num_followers": "您將失去 {followersCount, plural, other {{followersCountDisplay} 個跟隨者}} 與 {followingCount, plural, other {{followingCountDisplay} 個您跟隨之帳號}}。", "domain_block_modal.you_will_lose_relationships": "您將失去所有的跟隨者與您自此伺服器跟隨之帳號。", - "domain_block_modal.you_wont_see_posts": "您不會見到來自此伺服器使用者之任何嘟文或推播通知。", + "domain_block_modal.you_wont_see_posts": "您將不會見到來自此伺服器使用者之任何嘟文或推播通知。", "domain_pill.activitypub_lets_connect": "它使您能於 Mastodon 及其他不同的社群應用程式與人連結及互動。", "domain_pill.activitypub_like_language": "ActivityPub 像是 Mastodon 與其他社群網路溝通時所用的語言。", "domain_pill.server": "伺服器", @@ -296,8 +341,8 @@ "domain_pill.your_handle": "您的帳號:", "domain_pill.your_server": "您數位世界的家,您所有的嘟文都在這裡。不喜歡這台伺服器嗎?您能隨時搬家至其他伺服器並且仍保有您的跟隨者。", "domain_pill.your_username": "您於您的伺服器中獨一無二的識別。於不同的伺服器上可能找到具有相同帳號的使用者。", - "dropdown.empty": "選項", - "embed.instructions": "若您欲於您的網站嵌入此嘟文,請複製以下程式碼。", + "dropdown.empty": "請選擇一個選項", + "embed.instructions": "請複製以下程式碼以於您的網站嵌入此嘟文。", "embed.preview": "它將顯示成這樣:", "emoji_button.activity": "活動", "emoji_button.clear": "清除", @@ -321,10 +366,11 @@ "empty_column.account_suspended": "帳號已被停權", "empty_column.account_timeline": "這裡還沒有嘟文!", "empty_column.account_unavailable": "無法取得個人檔案", - "empty_column.blocks": "您還沒有封鎖任何使用者。", + "empty_column.blocks": "您尚未封鎖任何使用者。", "empty_column.bookmarked_statuses": "您還沒有新增任何書籤。當您新增書籤時,它將於此顯示。", "empty_column.community": "本站時間軸是空的。快公開嘟些文搶頭香啊!", "empty_column.direct": "您還沒有收到任何私訊。當您私訊別人或收到私訊時,它將於此顯示。", + "empty_column.disabled_feed": "此內容已被您的伺服器管理員停用。", "empty_column.domain_blocks": "尚未封鎖任何網域。", "empty_column.explore_statuses": "目前沒有熱門討論,請稍候再回來看看!", "empty_column.favourited_statuses": "您還沒有加過任何嘟文至最愛。當您收藏嘟文時,它將於此顯示。", @@ -332,38 +378,37 @@ "empty_column.follow_requests": "您還沒有收到任何跟隨請求。當您收到的跟隨請求時,它將於此顯示。", "empty_column.followed_tags": "您還沒有跟隨任何主題標籤。當您跟隨主題標籤時,它們將於此顯示。", "empty_column.hashtag": "這個主題標籤下什麼也沒有。", - "empty_column.home": "您的首頁時間軸是空的!跟隨更多人來將它填滿吧!", + "empty_column.home": "您的首頁時間軸是空的!跟隨更多人將它填滿吧!", "empty_column.list": "這份列表下什麼也沒有。當此列表的成員嘟出新的嘟文時,它們將顯示於此。", - "empty_column.mutes": "您尚未靜音任何使用者。", + "empty_column.mutes": "您還沒有靜音任何使用者。", "empty_column.notification_requests": "清空啦!已經沒有任何推播通知。當您收到新推播通知時,它們將依照您的設定於此顯示。", "empty_column.notifications": "您還沒有收到任何推播通知,當您與別人開始互動時,它將於此顯示。", - "empty_column.public": "這裡什麼都沒有!嘗試寫些公開的嘟文,或者跟隨其他伺服器的使用者後,就會有嘟文出現了", + "empty_column.public": "這裡什麼都沒有!嘗試寫些公開的嘟文,或著自己跟隨其他伺服器的使用者後就會有嘟文出現了", + "error.no_hashtag_feed_access": "加入或登入 Mastodon 以檢視與跟隨此主題標籤。", "error.unexpected_crash.explanation": "由於發生系統故障或瀏覽器相容性問題,無法正常顯示此頁面。", "error.unexpected_crash.explanation_addons": "此頁面無法被正常顯示,這可能是由瀏覽器附加元件或網頁自動翻譯工具造成的。", - "error.unexpected_crash.next_steps": "請嘗試重新整理頁面。如果狀況沒有改善,您可以使用不同的瀏覽器或應用程式以檢視來使用 Mastodon。", - "error.unexpected_crash.next_steps_addons": "請嘗試關閉它們然後重新整理頁面。如果狀況沒有改善,您可以使用不同的瀏覽器或應用程式來檢視來使用 Mastodon。", - "errors.unexpected_crash.copy_stacktrace": "複製 stacktrace 到剪貼簿", + "error.unexpected_crash.next_steps": "請嘗試重新整理頁面。如果狀況沒有改善,您可以透過不同的瀏覽器或應用程式以使用 Mastodon。", + "error.unexpected_crash.next_steps_addons": "請嘗試關閉它們然後重新整理頁面。如果狀況沒有改善,您可以透過不同的瀏覽器或應用程式以使用 Mastodon。", + "errors.unexpected_crash.copy_stacktrace": "複製 stacktrace 至剪貼簿", "errors.unexpected_crash.report_issue": "回報問題", "explore.suggested_follows": "使用者", "explore.title": "熱門", "explore.trending_links": "最新消息", "explore.trending_statuses": "嘟文", "explore.trending_tags": "主題標籤", + "featured_carousel.current": "嘟文 {current, number} / {max, number}", "featured_carousel.header": "{count, plural, other {# 則釘選嘟文}}", - "featured_carousel.next": "下一個", - "featured_carousel.post": "嘟文", - "featured_carousel.previous": "上一個", - "featured_carousel.slide": "{total} 中的 {index}", + "featured_carousel.slide": "{max, number} 則嘟文中之第 {current, number} 則", "filter_modal.added.context_mismatch_explanation": "此過濾器類別不是用您所存取嘟文的情境。若您想要此嘟文被於此情境被過濾,您必須編輯過濾器。", "filter_modal.added.context_mismatch_title": "不符合情境!", "filter_modal.added.expired_explanation": "此過濾器類別已失效,您需要更新過期日期以套用。", "filter_modal.added.expired_title": "過期的過濾器!", - "filter_modal.added.review_and_configure": "要檢視與進一步設定此過濾器類別,請至 {settings_link}。", + "filter_modal.added.review_and_configure": "如欲檢視與進一步設定此過濾器類別,請至 {settings_link}。", "filter_modal.added.review_and_configure_title": "過濾器設定", "filter_modal.added.settings_link": "設定頁面", "filter_modal.added.short_explanation": "此嘟文已被新增至以下過濾器類別:{title}。", "filter_modal.added.title": "已新增過濾器!", - "filter_modal.select_filter.context_mismatch": "不是用目前情境", + "filter_modal.select_filter.context_mismatch": "不適用目前情境", "filter_modal.select_filter.expired": "已過期", "filter_modal.select_filter.prompt_new": "新類別:{name}", "filter_modal.select_filter.search": "搜尋或新增", @@ -381,13 +426,13 @@ "follow_requests.unlocked_explanation": "即便您的帳號未被鎖定,{domain} 的管理員認為您可能想要自己審核這些帳號的跟隨請求。", "follow_suggestions.curated_suggestion": "精選內容", "follow_suggestions.dismiss": "不再顯示", - "follow_suggestions.featured_longer": "{domain} 團隊精選", + "follow_suggestions.featured_longer": "{domain} 團隊精心挑選", "follow_suggestions.friends_of_friends_longer": "受您跟隨之使用者愛戴的風雲人物", - "follow_suggestions.hints.featured": "這個個人檔案是 {domain} 管理團隊精心挑選的。", - "follow_suggestions.hints.friends_of_friends": "這個個人檔案於您跟隨的帳號中很受歡迎。", - "follow_suggestions.hints.most_followed": "這個個人檔案是 {domain} 中最受歡迎的帳號之一。", - "follow_suggestions.hints.most_interactions": "這個個人檔案最近於 {domain} 受到非常多關注。", - "follow_suggestions.hints.similar_to_recently_followed": "這個個人檔案與您最近跟隨之帳號類似。", + "follow_suggestions.hints.featured": "此個人檔案是 {domain} 管理團隊精心挑選。", + "follow_suggestions.hints.friends_of_friends": "此個人檔案於您跟隨的帳號中很受歡迎。", + "follow_suggestions.hints.most_followed": "此個人檔案是 {domain} 中最受歡迎的帳號之一。", + "follow_suggestions.hints.most_interactions": "此個人檔案最近於 {domain} 受到非常多關注。", + "follow_suggestions.hints.similar_to_recently_followed": "此個人檔案與您最近跟隨之帳號類似。", "follow_suggestions.personalized_suggestion": "個人化推薦", "follow_suggestions.popular_suggestion": "熱門推薦", "follow_suggestions.popular_suggestion_longer": "{domain} 上的人氣王", @@ -396,6 +441,9 @@ "follow_suggestions.who_to_follow": "推薦跟隨帳號", "followed_tags": "已跟隨主題標籤", "footer.about": "關於", + "footer.about_mastodon": "關於 Mastodon", + "footer.about_server": "關於 {domain}", + "footer.about_this_server": "關於", "footer.directory": "個人檔案目錄", "footer.get_app": "取得應用程式", "footer.keyboard_shortcuts": "鍵盤快速鍵", @@ -417,9 +465,9 @@ "hashtag.column_settings.tag_mode.any": "任一", "hashtag.column_settings.tag_mode.none": "全不", "hashtag.column_settings.tag_toggle": "將額外標籤加入到這個欄位", - "hashtag.counter_by_accounts": "{count, plural, one {{counter} 名} other {{counter} 名}}參與者", - "hashtag.counter_by_uses": "{count, plural, one {{counter} 則} other {{counter} 則}}嘟文", - "hashtag.counter_by_uses_today": "本日有 {count, plural, one {{counter} 則} other {{counter} 則}}嘟文", + "hashtag.counter_by_accounts": "{count, plural, other {{counter} 名參與者}}", + "hashtag.counter_by_uses": "{count, plural, other {{counter} 則嘟文}}", + "hashtag.counter_by_uses_today": "本日有 {count, plural, other {{counter} 則嘟文}}", "hashtag.feature": "於個人檔案推薦", "hashtag.follow": "跟隨主題標籤", "hashtag.mute": "靜音 #{hashtag}", @@ -497,6 +545,7 @@ "keyboard_shortcuts.toggle_hidden": "顯示或隱藏於內容警告之後的嘟文", "keyboard_shortcuts.toggle_sensitivity": "顯示或隱藏媒體", "keyboard_shortcuts.toot": "發個新嘟文", + "keyboard_shortcuts.top": "移至列表最上方", "keyboard_shortcuts.translate": "翻譯嘟文", "keyboard_shortcuts.unfocus": "跳離文字撰寫區塊或搜尋框", "keyboard_shortcuts.up": "向上移動", @@ -511,7 +560,7 @@ "limited_account_hint.title": "此個人檔案已被 {domain} 的管理員隱藏。", "link_preview.author": "來自 {name}", "link_preview.more_from_author": "來自 {name} 之更多內容", - "link_preview.shares": "{count, plural, other {{count} 則嘟文}}", + "link_preview.shares": "{count, plural, other {{counter} 則嘟文}}", "lists.add_member": "新增", "lists.add_to_list": "新增至列表", "lists.add_to_lists": "新增 {name} 至列表", @@ -543,13 +592,13 @@ "moved_to_account_banner.text": "您的帳號 {disabledAccount} 目前已停用,因為您已搬家至 {movedToAccount}。", "mute_modal.hide_from_notifications": "於推播通知中隱藏", "mute_modal.hide_options": "隱藏選項", - "mute_modal.indefinite": "直到我解除靜音他們", + "mute_modal.indefinite": "直到我解除靜音", "mute_modal.show_options": "顯示選項", "mute_modal.they_can_mention_and_follow": "他們仍可提及或跟隨您,但您不會見到他們。", "mute_modal.they_wont_know": "他們不會知道他們已被靜音。", "mute_modal.title": "是否靜音該使用者?", - "mute_modal.you_wont_see_mentions": "您不會見到提及他們的嘟文。", - "mute_modal.you_wont_see_posts": "他們仍可讀取您的嘟文,但您不會見到他們的。", + "mute_modal.you_wont_see_mentions": "您將不會見到提及他們的嘟文。", + "mute_modal.you_wont_see_posts": "他們仍可讀取您的嘟文,但您不會見到他們的嘟文。", "navigation_bar.about": "關於", "navigation_bar.account_settings": "密碼與安全性", "navigation_bar.administration": "管理介面", @@ -590,11 +639,11 @@ "notification.admin.sign_up": "{name} 已經註冊", "notification.admin.sign_up.name_and_others": "{name} 與{count, plural, other {其他 # 個人}}已註冊", "notification.annual_report.message": "您的 {year} #Wrapstodon 正等著您!揭開您 Mastodon 上的年度精彩時刻與值得回憶的難忘時光!", - "notification.annual_report.view": "檢視 #Wrapstodon", + "notification.annual_report.view": "檢視 #Wrapstodon 年度回顧", "notification.favourite": "{name} 已將您的嘟文加入最愛", "notification.favourite.name_and_others_with_link": "{name} 與{count, plural, other {其他 # 個人}}已將您的嘟文加入最愛", - "notification.favourite_pm": "{name} 將您的私人提及加入最愛", - "notification.favourite_pm.name_and_others_with_link": "{name} 與{count, plural, other {其他 # 個人}}已將您的私人提及加入最愛", + "notification.favourite_pm": "{name} 將您的私訊加入最愛", + "notification.favourite_pm.name_and_others_with_link": "{name} 與{count, plural, other {其他 # 個人}}已將您的私訊加入最愛", "notification.follow": "{name} 已跟隨您", "notification.follow.name_and_others": "{name} 與{count, plural, other {其他 # 個人}}已跟隨您", "notification.follow_request": "{name} 要求跟隨您", @@ -612,7 +661,7 @@ "notification.moderation_warning.action_disable": "您的帳號已被停用。", "notification.moderation_warning.action_mark_statuses_as_sensitive": "某些您的嘟文已被標記為敏感內容。", "notification.moderation_warning.action_none": "您的帳號已收到管理員警告。", - "notification.moderation_warning.action_sensitive": "即日起,您的嘟文將會被標記為敏感內容。", + "notification.moderation_warning.action_sensitive": "即日起,您的嘟文將被標記為敏感內容。", "notification.moderation_warning.action_silence": "您的帳號已被限制。", "notification.moderation_warning.action_suspend": "您的帳號已被停權。", "notification.own_poll": "您的投票已結束", @@ -630,11 +679,11 @@ "notification_requests.accept": "接受", "notification_requests.accept_multiple": "{count, plural, other {接受 # 則請求...}}", "notification_requests.confirm_accept_multiple.button": "{count, plural, other {接受請求}}", - "notification_requests.confirm_accept_multiple.message": "您將接受 {count, plural, other {# 則推播通知請求}}。您確定要繼續?", - "notification_requests.confirm_accept_multiple.title": "接受推播通知請求?", + "notification_requests.confirm_accept_multiple.message": "您將接受 {count, plural, other {# 則推播通知請求}}。您確定要繼續嗎?", + "notification_requests.confirm_accept_multiple.title": "是否接受推播通知請求?", "notification_requests.confirm_dismiss_multiple.button": "{count, plural, other {忽略請求}}", - "notification_requests.confirm_dismiss_multiple.message": "您將忽略 {count, plural, other {# 則推播通知請求}}。您將不再能輕易存取{count, plural, other {這些}}推播通知。您確定要繼續?", - "notification_requests.confirm_dismiss_multiple.title": "忽略推播通知請求?", + "notification_requests.confirm_dismiss_multiple.message": "您將忽略 {count, plural, other {# 則推播通知請求}}。您將不再能輕易存取{count, plural, other {這些}}推播通知。您確定要繼續嗎?", + "notification_requests.confirm_dismiss_multiple.title": "是否忽略推播通知請求?", "notification_requests.dismiss": "關閉", "notification_requests.dismiss_multiple": "{count, plural, other {忽略 # 則請求...}}", "notification_requests.edit_selection": "編輯", @@ -692,7 +741,7 @@ "notifications.policy.filter_limited_accounts_title": "受管制帳號", "notifications.policy.filter_new_accounts.hint": "新增於過去 {days, plural, other {# 日}}", "notifications.policy.filter_new_accounts_title": "新帳號", - "notifications.policy.filter_not_followers_hint": "包含最近 {days, plural, other {# 日}} 內跟隨您之使用者", + "notifications.policy.filter_not_followers_hint": "包含最近 {days, plural, other {# 日}}內跟隨您之使用者", "notifications.policy.filter_not_followers_title": "未跟隨您之使用者", "notifications.policy.filter_not_following_hint": "直至您手動核准他們", "notifications.policy.filter_not_following_title": "您未跟隨之使用者", @@ -740,12 +789,14 @@ "privacy.quote.anyone": "{visibility},任何人皆可引用", "privacy.quote.disabled": "{visibility},停用引用嘟文", "privacy.quote.limited": "{visibility},受限的引用嘟文", - "privacy.unlisted.additional": "此與公開嘟文完全相同,但嘟文不會出現於即時內容或主題標籤、探索、及 Mastodon 搜尋中,即使您在帳戶設定中選擇加入。", + "privacy.unlisted.additional": "此與公開嘟文完全相同,但嘟文不會出現於即時內容或主題標籤、探索、及 Mastodon 搜尋中,即使您於帳戶設定中選擇加入。", "privacy.unlisted.long": "不顯示於 Mastodon 之搜尋結果、熱門趨勢、及公開時間軸上", "privacy.unlisted.short": "不公開", "privacy_policy.last_updated": "最後更新:{date}", "privacy_policy.title": "隱私權政策", + "quote_error.edit": "編輯嘟文時無法新增引用嘟文。", "quote_error.poll": "無法引用投票嘟文。", + "quote_error.private_mentions": "無法引用私訊嘟文。", "quote_error.quote": "一次僅能引用一則嘟文。", "quote_error.unauthorized": "您不被授權允許引用此嘟文。", "quote_error.upload": "無法引用多媒體內容嘟文。", @@ -771,7 +822,7 @@ "reply_indicator.cancel": "取消", "reply_indicator.poll": "投票", "report.block": "封鎖", - "report.block_explanation": "您將不再看到他們的嘟文。他們將無法看到您的嘟文或是跟隨您。他們會發現他們已被封鎖。", + "report.block_explanation": "您將不再看到他們的嘟文。他們將無法檢視您的嘟文或是跟隨您。他們會發現他們已被封鎖。", "report.categories.legal": "合法性", "report.categories.other": "其他", "report.categories.spam": "垃圾訊息", @@ -785,7 +836,7 @@ "report.forward": "轉寄到 {target}", "report.forward_hint": "這個帳號屬於其他伺服器。要向該伺服器發送匿名的檢舉訊息嗎?", "report.mute": "靜音", - "report.mute_explanation": "您將不再看到他們的嘟文。他們仍能可以跟隨您以及察看您的嘟文,並且不會知道他們已被靜音。", + "report.mute_explanation": "您將不再看到他們的嘟文。他們仍能可以跟隨您以及檢視您的嘟文,並且不會知道他們已被靜音。", "report.next": "繼續", "report.placeholder": "其他備註", "report.reasons.dislike": "我不喜歡", @@ -810,7 +861,7 @@ "report.thanks.title_actionable": "感謝您的檢舉,我們將會著手處理。", "report.unfollow": "取消跟隨 @{name}", "report.unfollow_explanation": "您正在跟隨此帳號。如不欲於首頁時間軸再見到他們的嘟文,請取消跟隨。", - "report_notification.attached_statuses": "{count, plural, one {{count} 則} other {{count} 則}} 嘟文", + "report_notification.attached_statuses": "已附加 {count, plural, other {{count} 則嘟文}}", "report_notification.categories.legal": "合法性", "report_notification.categories.legal_sentence": "違法內容", "report_notification.categories.other": "其他", @@ -865,8 +916,12 @@ "status.cannot_quote": "您不被允許引用此嘟文", "status.cannot_reblog": "這則嘟文無法被轉嘟", "status.contains_quote": "包含引用嘟文", - "status.context.load_new_replies": "有新回嘟", - "status.context.loading": "正在檢查更多回嘟", + "status.context.loading": "讀取更多回嘟", + "status.context.loading_error": "無法讀取新回嘟", + "status.context.loading_success": "已讀取新回嘟", + "status.context.more_replies_found": "已有更多回嘟", + "status.context.retry": "再試一次", + "status.context.show": "顯示", "status.continued_thread": "接續討論串", "status.copy": "複製嘟文連結", "status.delete": "刪除", @@ -876,10 +931,10 @@ "status.direct_indicator": "私訊", "status.edit": "編輯", "status.edited": "上次編輯於 {date}", - "status.edited_x_times": "已編輯 {count, plural, one {{count} 次} other {{count} 次}}", + "status.edited_x_times": "已編輯 {count, plural, other {{count} 次}}", "status.embed": "取得嵌入程式碼", "status.favourite": "最愛", - "status.favourites": "{count, plural, other {則最愛}}", + "status.favourites_count": "{count, plural, other {{counter} 則最愛}}", "status.filter": "過濾此嘟文", "status.history.created": "{name} 於 {date} 建立", "status.history.edited": "{name} 於 {date} 修改", @@ -895,12 +950,15 @@ "status.pin": "釘選至個人檔案頁面", "status.quote": "引用", "status.quote.cancel": "取消引用嘟文", + "status.quote_error.blocked_account_hint.title": "由於您已封鎖 @{name},此嘟文已被隱藏。", + "status.quote_error.blocked_domain_hint.title": "由於您已封鎖 {domain},此嘟文已被隱藏。", "status.quote_error.filtered": "由於您的過濾器,該嘟文被隱藏", "status.quote_error.limited_account_hint.action": "仍要顯示", "status.quote_error.limited_account_hint.title": "此個人檔案已被 {domain} 的管理員隱藏。", + "status.quote_error.muted_account_hint.title": "由於您已靜音 @{name},此嘟文已被隱藏。", "status.quote_error.not_available": "無法取得該嘟文", - "status.quote_error.pending_approval": "嘟文正在發送中", - "status.quote_error.pending_approval_popout.body": "您能於 Mastodon 控制是否允許引用您的嘟文。此嘟文正在等待原始作者核准。", + "status.quote_error.pending_approval": "嘟文正在等候審核中", + "status.quote_error.pending_approval_popout.body": "您能於 Mastodon 控制是否允許引用您的嘟文。此嘟文正在等待原始作者審核。", "status.quote_error.revoked": "嘟文已被作者刪除", "status.quote_followers_only": "只有我的跟隨者能引用此嘟文", "status.quote_manual_review": "嘟文作者將人工審閱", @@ -908,15 +966,17 @@ "status.quote_policy_change": "變更可以引用的人", "status.quote_post_author": "已引用 @{name} 之嘟文", "status.quote_private": "無法引用私人嘟文", - "status.quotes": "{count, plural, other {# 則引用嘟文}}", "status.quotes.empty": "目前尚無人引用此嘟文。當有人引用時,它將於此顯示。", + "status.quotes.local_other_disclaimer": "被作者拒絕之引用嘟文將不被顯示。", + "status.quotes.remote_other_disclaimer": "僅有來自 {domain} 之引用嘟文保證將於此顯示。被作者拒絕之引用嘟文將不被顯示。", + "status.quotes_count": "{count, plural, other {{counter} 則引用嘟文}}", "status.read_more": "閱讀更多", "status.reblog": "轉嘟", "status.reblog_or_quote": "轉嘟或引用", "status.reblog_private": "再次與跟隨者分享", "status.reblogged_by": "{name} 已轉嘟", - "status.reblogs": "{count, plural, other {則轉嘟}}", "status.reblogs.empty": "還沒有人轉嘟過這則嘟文。當有人轉嘟時,它們將於此顯示。", + "status.reblogs_count": "{count, plural, other {{counter} 則轉嘟}}", "status.redraft": "刪除並重新編輯", "status.remove_bookmark": "自書籤中移除", "status.remove_favourite": "自最愛中移除", @@ -951,11 +1011,11 @@ "terms_of_service.title": "服務條款", "terms_of_service.upcoming_changes_on": "{date} 起即將發生之異動", "time_remaining.days": "剩餘 {number, plural, other {# 天}}", - "time_remaining.hours": "剩餘{number, plural, other {# 小時}}", - "time_remaining.minutes": "剩餘{number, plural, other {# 分鐘}}", + "time_remaining.hours": "剩餘 {number, plural, other {# 小時}}", + "time_remaining.minutes": "剩餘 {number, plural, other {# 分鐘}}", "time_remaining.moments": "剩餘時間", - "time_remaining.seconds": "剩餘{number, plural, other {# 秒}}", - "trends.counter_by_accounts": "{count, plural, one {{counter} 人} other {{counter} 人}}於過去 {days, plural, one {日} other {{days} 日}} 之間", + "time_remaining.seconds": "剩餘 {number, plural, other {# 秒}}", + "trends.counter_by_accounts": "{count, plural, other {{counter} 人}}於過去 {days, plural, other {{days} 日}}之間", "trends.trending_now": "現正熱門趨勢", "ui.beforeunload": "如果離開 Mastodon,您的草稿將會不見。", "units.short.billion": "{count}B", @@ -966,7 +1026,7 @@ "upload_error.limit": "已達到檔案上傳限制。", "upload_error.poll": "不允許於投票時上傳檔案。", "upload_error.quote": "引用嘟文無法上傳檔案。", - "upload_form.drag_and_drop.instructions": "請按空白鍵或 Enter 鍵取多媒體附加檔案。使用方向鍵移動多媒體附加檔案。按下空白鍵或 Enter 鍵於新位置放置多媒體附加檔案,或按下 ESC 鍵取消。", + "upload_form.drag_and_drop.instructions": "請按空白鍵或 Enter 鍵選取多媒體附加檔案。使用方向鍵移動多媒體附加檔案。按下空白鍵或 Enter 鍵於新位置放置多媒體附加檔案,或按下 ESC 鍵取消。", "upload_form.drag_and_drop.on_drag_cancel": "移動已取消。多媒體附加檔案 {item} 已被放置。", "upload_form.drag_and_drop.on_drag_end": "多媒體附加檔案 {item} 已被放置。", "upload_form.drag_and_drop.on_drag_over": "多媒體附加檔案 {item} 已被移動。", @@ -986,12 +1046,14 @@ "video.play": "播放", "video.skip_backward": "上一部", "video.skip_forward": "下一部", - "video.unmute": "取消靜音", + "video.unmute": "解除靜音", "video.volume_down": "降低音量", "video.volume_up": "提高音量", "visibility_modal.button_title": "設定可見性", + "visibility_modal.direct_quote_warning.text": "若您儲存目前設定,引用嘟文將被轉為連結。", + "visibility_modal.direct_quote_warning.title": "引用嘟文無法被內嵌於私訊中", "visibility_modal.header": "可見性與互動", - "visibility_modal.helper.direct_quoting": "Mastodon 上發佈之私人提及嘟文無法被其他使用者引用。", + "visibility_modal.helper.direct_quoting": "Mastodon 上發佈之私訊嘟文無法被其他使用者引用。", "visibility_modal.helper.privacy_editing": "嘟文發布後無法變更可見性。", "visibility_modal.helper.privacy_private_self_quote": "自我引用之私嘟無法設為公開可見。", "visibility_modal.helper.private_quoting": "Mastodon 上發佈之僅限跟隨者嘟文無法被其他使用者引用。", diff --git a/app/javascript/mastodon/main.tsx b/app/javascript/mastodon/main.tsx index 456cc21c318805..965645f9e69d5e 100644 --- a/app/javascript/mastodon/main.tsx +++ b/app/javascript/mastodon/main.tsx @@ -9,11 +9,7 @@ import { me, reduceMotion } from 'mastodon/initial_state'; import ready from 'mastodon/ready'; import { store } from 'mastodon/store'; -import { - isProduction, - isDevelopment, - isModernEmojiEnabled, -} from './utils/environment'; +import { isProduction, isDevelopment } from './utils/environment'; function main() { perf.start('main()'); @@ -33,10 +29,8 @@ function main() { }); } - if (isModernEmojiEnabled()) { - const { initializeEmoji } = await import('@/mastodon/features/emoji'); - initializeEmoji(); - } + const { initializeEmoji } = await import('./features/emoji/index'); + await initializeEmoji(); const root = createRoot(mountNode); root.render(); @@ -61,9 +55,8 @@ function main() { 'Notification' in window && Notification.permission === 'granted' ) { - const registerPushNotifications = await import( - 'mastodon/actions/push_notifications' - ); + const registerPushNotifications = + await import('mastodon/actions/push_notifications'); store.dispatch(registerPushNotifications.register()); } diff --git a/app/javascript/mastodon/models/account.ts b/app/javascript/mastodon/models/account.ts index 3b0c41be8183ce..bd099b23342fd9 100644 --- a/app/javascript/mastodon/models/account.ts +++ b/app/javascript/mastodon/models/account.ts @@ -8,11 +8,10 @@ import type { ApiAccountRoleJSON, ApiAccountJSON, } from 'mastodon/api_types/accounts'; -import emojify from 'mastodon/features/emoji/emoji'; import { unescapeHTML } from 'mastodon/utils/html'; -import { CustomEmojiFactory, makeEmojiMap } from './custom_emoji'; -import type { CustomEmoji, EmojiMap } from './custom_emoji'; +import { CustomEmojiFactory } from './custom_emoji'; +import type { CustomEmoji } from './custom_emoji'; // AccountField interface AccountFieldShape extends Required { @@ -43,10 +42,9 @@ const AccountRoleFactory = ImmutableRecord({ }); // Account -export interface AccountShape - extends Required< - Omit - > { +export interface AccountShape extends Required< + Omit +> { emojis: ImmutableList; fields: ImmutableList; roles: ImmutableList; @@ -102,17 +100,11 @@ export const accountDefaultValues: AccountShape = { const AccountFactory = ImmutableRecord(accountDefaultValues); -function createAccountField( - jsonField: ApiAccountFieldJSON, - emojiMap: EmojiMap, -) { +function createAccountField(jsonField: ApiAccountFieldJSON) { return AccountFieldFactory({ ...jsonField, - name_emojified: emojify( - escapeTextContentForBrowser(jsonField.name), - emojiMap, - ), - value_emojified: emojify(jsonField.value, emojiMap), + name_emojified: escapeTextContentForBrowser(jsonField.name), + value_emojified: jsonField.value, value_plain: unescapeHTML(jsonField.value), }); } @@ -120,8 +112,6 @@ function createAccountField( export function createAccountFromServerJSON(serverJSON: ApiAccountJSON) { const { moved, ...accountJSON } = serverJSON; - const emojiMap = makeEmojiMap(accountJSON.emojis); - const displayName = accountJSON.display_name.trim().length === 0 ? accountJSON.username @@ -134,7 +124,7 @@ export function createAccountFromServerJSON(serverJSON: ApiAccountJSON) { ...accountJSON, moved: moved?.id, fields: ImmutableList( - serverJSON.fields.map((field) => createAccountField(field, emojiMap)), + serverJSON.fields.map((field) => createAccountField(field)), ), emojis: ImmutableList( serverJSON.emojis.map((emoji) => CustomEmojiFactory(emoji)), @@ -142,11 +132,8 @@ export function createAccountFromServerJSON(serverJSON: ApiAccountJSON) { roles: ImmutableList( serverJSON.roles?.map((role) => AccountRoleFactory(role)), ), - display_name_html: emojify( - escapeTextContentForBrowser(displayName), - emojiMap, - ), - note_emojified: emojify(accountNote, emojiMap), + display_name_html: escapeTextContentForBrowser(displayName), + note_emojified: accountNote, note_plain: unescapeHTML(accountNote), url: accountJSON.url?.startsWith('http://') || diff --git a/app/javascript/mastodon/models/annual_report.ts b/app/javascript/mastodon/models/annual_report.ts index c0a101e6c872da..863debfc1f358a 100644 --- a/app/javascript/mastodon/models/annual_report.ts +++ b/app/javascript/mastodon/models/annual_report.ts @@ -16,9 +16,9 @@ export interface TimeSeriesMonth { } export interface TopStatuses { - by_reblogs: number; - by_favourites: number; - by_replies: number; + by_reblogs: string; + by_favourites: string; + by_replies: string; } export type Archetype = @@ -37,8 +37,24 @@ interface AnnualReportV1 { archetype: Archetype; } -export interface AnnualReport { - year: number; - schema_version: number; - data: AnnualReportV1; +interface AnnualReportV2 { + archetype: Archetype; + time_series: TimeSeriesMonth[]; + top_hashtags: NameAndCount[]; + top_statuses: TopStatuses; } + +export type AnnualReport = { + year: number; +} & ( + | { + schema_version: 1; + data: AnnualReportV1; + } + | { + schema_version: 2; + data: AnnualReportV2; + share_url: string | null; + account_id: string; + } +); diff --git a/app/javascript/mastodon/models/custom_emoji.ts b/app/javascript/mastodon/models/custom_emoji.ts index 5297dcd4704286..19ca951a5cab21 100644 --- a/app/javascript/mastodon/models/custom_emoji.ts +++ b/app/javascript/mastodon/models/custom_emoji.ts @@ -11,6 +11,7 @@ export const CustomEmojiFactory = ImmutableRecord({ static_url: '', url: '', category: '', + featured: false, visible_in_picker: false, }); diff --git a/app/javascript/mastodon/models/notification_group.ts b/app/javascript/mastodon/models/notification_group.ts index 7b88f904298b27..bb8799c0ecab6b 100644 --- a/app/javascript/mastodon/models/notification_group.ts +++ b/app/javascript/mastodon/models/notification_group.ts @@ -11,23 +11,27 @@ import type { import type { ApiReportJSON } from 'mastodon/api_types/reports'; // Maximum number of avatars displayed in a notification group -// This corresponds to the max lenght of `group.sampleAccountIds` +// This corresponds to the max length of `group.sampleAccountIds` export const NOTIFICATIONS_GROUP_MAX_AVATARS = 8; -interface BaseNotificationGroup - extends Omit { +interface BaseNotificationGroup extends Omit< + BaseNotificationGroupJSON, + 'sample_account_ids' +> { sampleAccountIds: string[]; partial: boolean; } -interface BaseNotificationWithStatus - extends BaseNotificationGroup { +interface BaseNotificationWithStatus< + Type extends NotificationWithStatusType, +> extends BaseNotificationGroup { type: Type; statusId: string | undefined; } -interface BaseNotification - extends BaseNotificationGroup { +interface BaseNotification< + Type extends NotificationType, +> extends BaseNotificationGroup { type: Type; } @@ -53,26 +57,25 @@ export type AccountWarningAction = | 'sensitive' | 'silence' | 'suspend'; -export interface AccountWarning - extends Omit { +export interface AccountWarning extends Omit< + ApiAccountWarningJSON, + 'target_account' +> { targetAccountId: string; } -export interface NotificationGroupModerationWarning - extends BaseNotification<'moderation_warning'> { +export interface NotificationGroupModerationWarning extends BaseNotification<'moderation_warning'> { moderationWarning: AccountWarning; } type AccountRelationshipSeveranceEvent = ApiAccountRelationshipSeveranceEventJSON; -export interface NotificationGroupSeveredRelationships - extends BaseNotification<'severed_relationships'> { +export interface NotificationGroupSeveredRelationships extends BaseNotification<'severed_relationships'> { event: AccountRelationshipSeveranceEvent; } type AnnualReportEvent = ApiAnnualReportEventJSON; -export interface NotificationGroupAnnualReport - extends BaseNotification<'annual_report'> { +export interface NotificationGroupAnnualReport extends BaseNotification<'annual_report'> { annualReport: AnnualReportEvent; } @@ -80,8 +83,7 @@ interface Report extends Omit { targetAccountId: string; } -export interface NotificationGroupAdminReport - extends BaseNotification<'admin.report'> { +export interface NotificationGroupAdminReport extends BaseNotification<'admin.report'> { report: Report; } diff --git a/app/javascript/mastodon/models/notification_request.ts b/app/javascript/mastodon/models/notification_request.ts index bd98d213d402b4..03e007c41e260c 100644 --- a/app/javascript/mastodon/models/notification_request.ts +++ b/app/javascript/mastodon/models/notification_request.ts @@ -1,7 +1,9 @@ import type { ApiNotificationRequestJSON } from 'mastodon/api_types/notifications'; -export interface NotificationRequest - extends Omit { +export interface NotificationRequest extends Omit< + ApiNotificationRequestJSON, + 'account' | 'notifications_count' +> { account_id: string; notifications_count: number; } diff --git a/app/javascript/mastodon/models/poll.ts b/app/javascript/mastodon/models/poll.ts index 6f5655680d6beb..154788700c7460 100644 --- a/app/javascript/mastodon/models/poll.ts +++ b/app/javascript/mastodon/models/poll.ts @@ -1,10 +1,9 @@ import escapeTextContentForBrowser from 'escape-html'; import type { ApiPollJSON, ApiPollOptionJSON } from 'mastodon/api_types/polls'; -import emojify from 'mastodon/features/emoji/emoji'; -import { CustomEmojiFactory, makeEmojiMap } from './custom_emoji'; -import type { CustomEmoji, EmojiMap } from './custom_emoji'; +import { CustomEmojiFactory } from './custom_emoji'; +import type { CustomEmoji } from './custom_emoji'; interface PollOptionTranslation { title: string; @@ -17,21 +16,19 @@ export interface PollOption extends ApiPollOptionJSON { translation: PollOptionTranslation | null; } -export function createPollOptionTranslationFromServerJSON( - translation: { title: string }, - emojiMap: EmojiMap, -) { +export function createPollOptionTranslationFromServerJSON(translation: { + title: string; +}) { return { ...translation, - titleHtml: emojify( - escapeTextContentForBrowser(translation.title), - emojiMap, - ), + titleHtml: escapeTextContentForBrowser(translation.title), } as PollOptionTranslation; } -export interface Poll - extends Omit { +export interface Poll extends Omit< + ApiPollJSON, + 'emojis' | 'options' | 'own_votes' +> { emojis: CustomEmoji[]; options: PollOption[]; own_votes?: number[]; @@ -50,8 +47,6 @@ export function createPollFromServerJSON( serverJSON: ApiPollJSON, previousPoll?: Poll, ) { - const emojiMap = makeEmojiMap(serverJSON.emojis); - return { ...pollDefaultValues, ...serverJSON, @@ -60,20 +55,15 @@ export function createPollFromServerJSON( const option = { ...optionJSON, voted: serverJSON.own_votes?.includes(index) || false, - titleHtml: emojify( - escapeTextContentForBrowser(optionJSON.title), - emojiMap, - ), + titleHtml: escapeTextContentForBrowser(optionJSON.title), } as PollOption; const prevOption = previousPoll?.options[index]; if (prevOption?.translation && prevOption.title === option.title) { const { translation } = prevOption; - option.translation = createPollOptionTranslationFromServerJSON( - translation, - emojiMap, - ); + option.translation = + createPollOptionTranslationFromServerJSON(translation); } return option; diff --git a/app/javascript/mastodon/permissions.ts b/app/javascript/mastodon/permissions.ts index d7695d2f5c7e02..a83e1d77a7de68 100644 --- a/app/javascript/mastodon/permissions.ts +++ b/app/javascript/mastodon/permissions.ts @@ -1,3 +1,4 @@ +export const PEMRISSION_VIEW_FEEDS = 0x0000000000100000; export const PERMISSION_INVITE_USERS = 0x0000000000010000; export const PERMISSION_MANAGE_USERS = 0x0000000000000400; export const PERMISSION_MANAGE_TAXONOMIES = 0x0000000000000100; @@ -22,3 +23,19 @@ export function canManageReports(permissions: number) { (permissions & PERMISSION_MANAGE_REPORTS) === PERMISSION_MANAGE_REPORTS ); } + +export const canViewFeed = ( + signedIn: boolean, + permissions: number, + setting: 'public' | 'authenticated' | 'disabled' | undefined, +) => { + switch (setting) { + case 'public': + return true; + case 'authenticated': + return signedIn; + case 'disabled': + default: + return (permissions & PEMRISSION_VIEW_FEEDS) === PEMRISSION_VIEW_FEEDS; + } +}; diff --git a/app/javascript/mastodon/polyfills/index.ts b/app/javascript/mastodon/polyfills/index.ts index 0ff0dd72690cc8..00da2042ed8007 100644 --- a/app/javascript/mastodon/polyfills/index.ts +++ b/app/javascript/mastodon/polyfills/index.ts @@ -2,9 +2,6 @@ // If there are no polyfills, then this is just Promise.resolve() which means // it will execute in the same tick of the event loop (i.e. near-instant). -// eslint-disable-next-line import/extensions -- This file is virtual so it thinks it has an extension -import 'vite/modulepreload-polyfill'; - import { loadIntlPolyfills } from './intl'; function importExtraPolyfills() { @@ -17,9 +14,10 @@ export function loadPolyfills() { const needsExtraPolyfills = !window.requestIdleCallback; return Promise.all([ + loadVitePreloadPolyfill(), loadIntlPolyfills(), // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- those properties might not exist in old browsers, even if they are always here in types - needsExtraPolyfills && importExtraPolyfills(), + needsExtraPolyfills ? importExtraPolyfills() : Promise.resolve(), loadEmojiPolyfills(), ]); } @@ -31,5 +29,13 @@ async function loadEmojiPolyfills() { } } +// Loads Vite's module preload polyfill for older browsers, but not in a Worker context. +function loadVitePreloadPolyfill() { + if (typeof document === 'undefined') return; + // @ts-expect-error -- This is a virtual module provided by Vite. + // eslint-disable-next-line import/extensions + return import('vite/modulepreload-polyfill'); +} + // Null unless polyfill is needed. export let emojiRegexPolyfill: RegExp | null = null; diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index f27c3bb06bf265..e413f2893da860 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -1,11 +1,14 @@ import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable'; import { + changeComposeVisibility, changeUploadCompose, quoteCompose, quoteComposeCancel, setComposeQuotePolicy, -} from 'mastodon/actions/compose_typed'; + pasteLinkCompose, + cancelPasteLinkCompose, +} from '@/mastodon/actions/compose_typed'; import { timelineDelete } from 'mastodon/actions/timelines_typed'; import { @@ -38,7 +41,6 @@ import { COMPOSE_SENSITIVITY_CHANGE, COMPOSE_SPOILERNESS_CHANGE, COMPOSE_SPOILER_TEXT_CHANGE, - COMPOSE_VISIBILITY_CHANGE, COMPOSE_LANGUAGE_CHANGE, COMPOSE_COMPOSING_CHANGE, COMPOSE_EMOJI_INSERT, @@ -93,6 +95,7 @@ const initialState = ImmutableMap({ quoted_status_id: null, quote_policy: 'public', default_quote_policy: 'public', // Set in hydration. + fetching_link: null, }); const initialPoll = ImmutableMap({ @@ -315,7 +318,11 @@ const calculateProgress = (loaded, total) => Math.min(Math.round((loaded / total /** @type {import('@reduxjs/toolkit').Reducer} */ export const composeReducer = (state = initialState, action) => { - if (changeUploadCompose.fulfilled.match(action)) { + if (changeComposeVisibility.match(action)) { + return state + .set('privacy', action.payload) + .set('idempotencyKey', uuid()); + } else if (changeUploadCompose.fulfilled.match(action)) { return state .set('is_changing_upload', false) .update('media_attachments', list => list.map(item => { @@ -331,20 +338,34 @@ export const composeReducer = (state = initialState, action) => { return state.set('is_changing_upload', false); } else if (quoteCompose.match(action)) { const status = action.payload; + const isDirect = state.get('privacy') === 'direct'; return state - .set('quoted_status_id', status.get('id')) - .set('spoiler', status.get('sensitive')) - .set('spoiler_text', status.get('spoiler_text')) - .update('privacy', (visibility) => ['public', 'unlisted'].includes(visibility) && status.get('visibility') === 'private' ? 'private' : visibility); + .set('quoted_status_id', isDirect ? null : status.get('id')) + .update('spoiler', spoiler => (spoiler) || !!status.get('spoiler_text')) + .update('spoiler_text', (spoiler_text) => spoiler_text || status.get('spoiler_text')) + .update('privacy', (visibility) => { + if (['public', 'unlisted'].includes(visibility) && status.get('visibility') === 'private') { + return 'private'; + } + return visibility; + }); } else if (quoteComposeCancel.match(action)) { return state.set('quoted_status_id', null); } else if (setComposeQuotePolicy.match(action)) { return state.set('quote_policy', action.payload); + } else if (pasteLinkCompose.pending.match(action)) { + return state.set('fetching_link', action.meta.requestId); + } else if (pasteLinkCompose.fulfilled.match(action) || pasteLinkCompose.rejected.match(action)) { + return action.meta.requestId === state.get('fetching_link') ? state.set('fetching_link', null) : state; + } else if (cancelPasteLinkCompose.match(action)) { + return state.set('fetching_link', null); } switch(action.type) { case STORE_HYDRATE: - return hydrate(state, action.state.get('compose')); + if (action.state.get('compose')) + return hydrate(state, action.state.get('compose')); + return state; case COMPOSE_MOUNT: return state .set('mounted', state.get('mounted') + 1) @@ -383,10 +404,6 @@ export const composeReducer = (state = initialState, action) => { return state .set('spoiler_text', action.text) .set('idempotencyKey', uuid()); - case COMPOSE_VISIBILITY_CHANGE: - return state - .set('privacy', action.value) - .set('idempotencyKey', uuid()); case COMPOSE_CHANGE: return state .set('text', action.text) @@ -403,6 +420,7 @@ export const composeReducer = (state = initialState, action) => { map.set('caretPosition', null); map.set('preselectDate', new Date()); map.set('idempotencyKey', uuid()); + map.set('quoted_status_id', null); map.update('media_attachments', list => list.filter(media => media.get('unattached'))); @@ -514,7 +532,7 @@ export const composeReducer = (state = initialState, action) => { map.set('sensitive', action.status.get('sensitive')); map.set('language', action.status.get('language')); map.set('id', null); - map.set('quoted_status_id', action.status.getIn(['quote', 'quoted_status'])); + map.set('quoted_status_id', action.status.getIn(['quote', 'quoted_status'], null)); // Mastodon-authored posts can be expected to have at most one automatic approval policy map.set('quote_policy', action.status.getIn(['quote_approval', 'automatic', 0]) || 'nobody'); @@ -551,7 +569,7 @@ export const composeReducer = (state = initialState, action) => { map.set('idempotencyKey', uuid()); map.set('sensitive', action.status.get('sensitive')); map.set('language', action.status.get('language')); - map.set('quoted_status_id', action.status.getIn(['quote', 'quoted_status'])); + map.set('quoted_status_id', action.status.getIn(['quote', 'quoted_status'], null)); // Mastodon-authored posts can be expected to have at most one automatic approval policy map.set('quote_policy', action.status.getIn(['quote_approval', 'automatic', 0]) || 'nobody'); diff --git a/app/javascript/mastodon/reducers/contexts.ts b/app/javascript/mastodon/reducers/contexts.ts index cf378b4c048cb7..0331c8083a4132 100644 --- a/app/javascript/mastodon/reducers/contexts.ts +++ b/app/javascript/mastodon/reducers/contexts.ts @@ -13,7 +13,12 @@ import type { import type { Status } from 'mastodon/models/status'; import { blockAccountSuccess, muteAccountSuccess } from '../actions/accounts'; -import { fetchContext, completeContextRefresh } from '../actions/statuses'; +import { + fetchContext, + completeContextRefresh, + showPendingReplies, + clearPendingReplies, +} from '../actions/statuses'; import { TIMELINE_UPDATE } from '../actions/timelines'; import { compareId } from '../compare_id'; @@ -26,52 +31,84 @@ interface TimelineUpdateAction extends UnknownAction { interface State { inReplyTos: Record; replies: Record; + pendingReplies: Record< + string, + Pick[] + >; refreshing: Record; } const initialState: State = { inReplyTos: {}, replies: {}, + pendingReplies: {}, refreshing: {}, }; +const addReply = ( + state: Draft, + { id, in_reply_to_id }: Pick, +) => { + if (!in_reply_to_id) { + return; + } + + if (!state.inReplyTos[id]) { + const siblings = (state.replies[in_reply_to_id] ??= []); + const index = siblings.findIndex((sibling) => compareId(sibling, id) < 0); + siblings.splice(index + 1, 0, id); + state.inReplyTos[id] = in_reply_to_id; + } +}; + const normalizeContext = ( state: Draft, id: string, { ancestors, descendants }: ApiContextJSON, ): void => { - const addReply = ({ - id, - in_reply_to_id, - }: { - id: string; - in_reply_to_id?: string; - }) => { - if (!in_reply_to_id) { - return; - } - - if (!state.inReplyTos[id]) { - const siblings = (state.replies[in_reply_to_id] ??= []); - const index = siblings.findIndex((sibling) => compareId(sibling, id) < 0); - siblings.splice(index + 1, 0, id); - state.inReplyTos[id] = in_reply_to_id; - } - }; + ancestors.forEach((item) => { + addReply(state, item); + }); // We know in_reply_to_id of statuses but `id` itself. // So we assume that the status of the id replies to last ancestors. - - ancestors.forEach(addReply); - if (ancestors[0]) { - addReply({ + addReply(state, { id, in_reply_to_id: ancestors[ancestors.length - 1]?.id, }); } - descendants.forEach(addReply); + descendants.forEach((item) => { + addReply(state, item); + }); +}; + +const applyPrefetchedReplies = (state: Draft, statusId: string) => { + const pendingReplies = state.pendingReplies[statusId]; + if (pendingReplies?.length) { + pendingReplies.forEach((item) => { + addReply(state, item); + }); + delete state.pendingReplies[statusId]; + } +}; + +const storePrefetchedReplies = ( + state: Draft, + statusId: string, + { descendants }: ApiContextJSON, +): void => { + descendants.forEach(({ id, in_reply_to_id }) => { + if (!in_reply_to_id) { + return; + } + const isNewReply = !state.replies[in_reply_to_id]?.includes(id); + if (isNewReply) { + const pendingReplies = (state.pendingReplies[statusId] ??= []); + pendingReplies.push({ id, in_reply_to_id }); + } + }); }; const deleteFromContexts = (state: Draft, ids: string[]): void => { @@ -129,12 +166,33 @@ const updateContext = (state: Draft, status: ApiStatusJSON): void => { export const contextsReducer = createReducer(initialState, (builder) => { builder .addCase(fetchContext.fulfilled, (state, action) => { - normalizeContext(state, action.meta.arg.statusId, action.payload.context); + const currentReplies = state.replies[action.meta.arg.statusId] ?? []; + const hasReplies = currentReplies.length > 0; + // Ignore prefetchOnly if there are no replies - then we can load them immediately + if (action.payload.prefetchOnly && hasReplies) { + storePrefetchedReplies( + state, + action.meta.arg.statusId, + action.payload.context, + ); + } else { + normalizeContext( + state, + action.meta.arg.statusId, + action.payload.context, + ); - if (action.payload.refresh) { - state.refreshing[action.meta.arg.statusId] = action.payload.refresh; + if (action.payload.refresh && !action.payload.prefetchOnly) { + state.refreshing[action.meta.arg.statusId] = action.payload.refresh; + } } }) + .addCase(showPendingReplies, (state, action) => { + applyPrefetchedReplies(state, action.payload.statusId); + }) + .addCase(clearPendingReplies, (state, action) => { + delete state.pendingReplies[action.payload.statusId]; + }) .addCase(completeContextRefresh, (state, action) => { delete state.refreshing[action.payload.statusId]; }) diff --git a/app/javascript/mastodon/reducers/index.ts b/app/javascript/mastodon/reducers/index.ts index 19ecbbfff406d3..7343f5e164f426 100644 --- a/app/javascript/mastodon/reducers/index.ts +++ b/app/javascript/mastodon/reducers/index.ts @@ -33,6 +33,7 @@ import { relationshipsReducer } from './relationships'; import { searchReducer } from './search'; import server from './server'; import settings from './settings'; +import { sliceReducers } from './slices'; import status_lists from './status_lists'; import statuses from './statuses'; import { suggestionsReducer } from './suggestions'; @@ -80,6 +81,7 @@ const reducers = { notificationPolicy: notificationPolicyReducer, notificationRequests: notificationRequestsReducer, navigation: navigationReducer, + ...sliceReducers, }; // We want the root state to be an ImmutableRecord, which is an object with a defined list of keys, diff --git a/app/javascript/mastodon/reducers/modal.ts b/app/javascript/mastodon/reducers/modal.ts index e287626ff2f78b..dfdff7cf0379ef 100644 --- a/app/javascript/mastodon/reducers/modal.ts +++ b/app/javascript/mastodon/reducers/modal.ts @@ -41,7 +41,7 @@ const popModal = ( modalType === state.get('stack').get(0)?.get('modalType') ) { return state - .set('ignoreFocus', !!ignoreFocus) + .set('ignoreFocus', ignoreFocus) .update('stack', (stack) => stack.shift()); } else { return state; diff --git a/app/javascript/mastodon/reducers/polls.ts b/app/javascript/mastodon/reducers/polls.ts index aadf6741c13677..ac0917bd2016bd 100644 --- a/app/javascript/mastodon/reducers/polls.ts +++ b/app/javascript/mastodon/reducers/polls.ts @@ -1,7 +1,6 @@ import type { Reducer } from '@reduxjs/toolkit'; import { importPolls } from 'mastodon/actions/importer/polls'; -import { makeEmojiMap } from 'mastodon/models/custom_emoji'; import { createPollOptionTranslationFromServerJSON } from 'mastodon/models/poll'; import type { Poll } from 'mastodon/models/poll'; @@ -20,16 +19,11 @@ const statusTranslateSuccess = (state: PollsState, pollTranslation?: Poll) => { if (!poll) return; - const emojiMap = makeEmojiMap(poll.emojis); - pollTranslation.options.forEach((item, index) => { const option = poll.options[index]; if (!option) return; - option.translation = createPollOptionTranslationFromServerJSON( - item, - emojiMap, - ); + option.translation = createPollOptionTranslationFromServerJSON(item); }); }; diff --git a/app/javascript/mastodon/reducers/slices/annual_report.ts b/app/javascript/mastodon/reducers/slices/annual_report.ts new file mode 100644 index 00000000000000..798fe7cc9b0eb7 --- /dev/null +++ b/app/javascript/mastodon/reducers/slices/annual_report.ts @@ -0,0 +1,125 @@ +import type { PayloadAction } from '@reduxjs/toolkit'; +import { createSlice } from '@reduxjs/toolkit'; + +import { + importFetchedAccounts, + importFetchedStatuses, +} from '@/mastodon/actions/importer'; +import type { ApiAnnualReportState } from '@/mastodon/api/annual_report'; +import { + apiGetAnnualReport, + apiGetAnnualReportState, + apiRequestGenerateAnnualReport, +} from '@/mastodon/api/annual_report'; +import { wrapstodon } from '@/mastodon/initial_state'; +import type { AnnualReport } from '@/mastodon/models/annual_report'; +import { + createAppThunk, + createDataLoadingThunk, +} from '@/mastodon/store/typed_functions'; + +interface AnnualReportState { + year?: number; + state?: ApiAnnualReportState; + report?: AnnualReport; +} + +const annualReportSlice = createSlice({ + name: 'annualReport', + initialState: { + year: wrapstodon?.year, + state: wrapstodon?.state, + } as AnnualReportState, + reducers: { + setReport(state, action: PayloadAction) { + state.report = action.payload; + state.state = 'available'; + }, + }, + extraReducers(builder) { + builder + .addCase(fetchReportState.fulfilled, (state, action) => { + state.state = action.payload; + }) + .addCase(generateReport.pending, (state) => { + state.state = 'generating'; + }) + .addCase(getReport.fulfilled, (state, action) => { + if (action.payload) { + state.report = action.payload; + } + }); + }, +}); + +export const annualReport = annualReportSlice.reducer; +export const { setReport } = annualReportSlice.actions; + +// Called on initial load to check if we need to refresh the report state. +export const checkAnnualReport = createAppThunk( + `${annualReportSlice.name}/checkAnnualReport`, + (_arg: unknown, { dispatch, getState }) => { + const { state, year } = getState().annualReport; + const me = getState().meta.get('me') as string; + + // If we have a state, we only need to fetch it again to poll for changes. + const needsStateRefresh = !state || state === 'generating'; + + if (!year || !me || !needsStateRefresh) { + return; + } + void dispatch(fetchReportState()); + }, +); + +const fetchReportState = createDataLoadingThunk( + `${annualReportSlice.name}/fetchReportState`, + async (_arg: unknown, { getState }) => { + const { year } = getState().annualReport; + if (!year) { + throw new Error('Year is not set'); + } + return apiGetAnnualReportState(year); + }, + ({ state, refresh }, { dispatch }) => { + if (state === 'generating' && refresh) { + window.setTimeout(() => { + void dispatch(fetchReportState()); + }, 1_000 * refresh.retry); + } + + return state; + }, + { useLoadingBar: false }, +); + +// Triggers the generation of the annual report. +export const generateReport = createDataLoadingThunk( + `${annualReportSlice.name}/generateReport`, + async (_arg: unknown, { getState }) => { + const { year } = getState().annualReport; + if (!year) { + throw new Error('Year is not set'); + } + return apiRequestGenerateAnnualReport(year); + }, + (_arg: unknown, { dispatch }) => { + void dispatch(fetchReportState()); + }, +); + +export const getReport = createDataLoadingThunk( + `${annualReportSlice.name}/getReport`, + async (_arg: unknown, { getState }) => { + const { year } = getState().annualReport; + if (!year) { + throw new Error('Year is not set'); + } + return apiGetAnnualReport(year); + }, + (data, { dispatch }) => { + dispatch(importFetchedStatuses(data.statuses)); + dispatch(importFetchedAccounts(data.accounts)); + return data.annual_reports[0]; + }, +); diff --git a/app/javascript/mastodon/reducers/slices/index.ts b/app/javascript/mastodon/reducers/slices/index.ts new file mode 100644 index 00000000000000..dfea3951275ab2 --- /dev/null +++ b/app/javascript/mastodon/reducers/slices/index.ts @@ -0,0 +1,5 @@ +import { annualReport } from './annual_report'; + +export const sliceReducers = { + annualReport, +}; diff --git a/app/javascript/mastodon/reducers/statuses.js b/app/javascript/mastodon/reducers/statuses.js index a334709db0a88f..cf17c066c3722d 100644 --- a/app/javascript/mastodon/reducers/statuses.js +++ b/app/javascript/mastodon/reducers/statuses.js @@ -65,6 +65,10 @@ const statusTranslateUndo = (state, id) => { }); }; +const removeStatusStub = (state, id) => { + return state.getIn([id, 'id']) ? state.deleteIn([id, 'isLoading']) : state.delete(id); +} + /** @type {ImmutableMap} */ const initialState = ImmutableMap(); @@ -92,11 +96,10 @@ export default function statuses(state = initialState, action) { return state.setIn([action.id, 'isLoading'], true); case STATUS_FETCH_FAIL: { if (action.parentQuotePostId && action.error.status === 404) { - return state - .delete(action.id) + return removeStatusStub(state, action.id) .setIn([action.parentQuotePostId, 'quote', 'state'], 'deleted') } else { - return state.delete(action.id); + return removeStatusStub(state, action.id); } } case STATUS_IMPORT: diff --git a/app/javascript/mastodon/reducers/timelines.js b/app/javascript/mastodon/reducers/timelines.js index b07281ab877827..e915fa7070a3cd 100644 --- a/app/javascript/mastodon/reducers/timelines.js +++ b/app/javascript/mastodon/reducers/timelines.js @@ -1,6 +1,6 @@ import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable'; -import { timelineDelete } from 'mastodon/actions/timelines_typed'; +import { timelineDelete, isNonStatusId } from 'mastodon/actions/timelines_typed'; import { blockAccountSuccess, @@ -19,7 +19,6 @@ import { TIMELINE_MARK_AS_PARTIAL, TIMELINE_INSERT, TIMELINE_GAP, - TIMELINE_SUGGESTIONS, disconnectTimeline, } from '../actions/timelines'; import { compareId } from '../compare_id'; @@ -36,7 +35,6 @@ const initialTimeline = ImmutableMap({ items: ImmutableList(), }); -const isPlaceholder = value => value === TIMELINE_GAP || value === TIMELINE_SUGGESTIONS; const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial, isLoadingRecent, usePendingItems) => { // This method is pretty tricky because: @@ -69,20 +67,20 @@ const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial, is // First, find the furthest (if properly sorted, oldest) item in the timeline that is // newer than the oldest fetched one, as it's most likely that it delimits the gap. // Start the gap *after* that item. - const lastIndex = oldIds.findLastIndex(id => !isPlaceholder(id) && compareId(id, newIds.last()) >= 0) + 1; + const lastIndex = oldIds.findLastIndex(id => !isNonStatusId(id) && compareId(id, newIds.last()) >= 0) + 1; // Then, try to find the furthest (if properly sorted, oldest) item in the timeline that // is newer than the most recent fetched one, as it delimits a section comprised of only // items older or within `newIds` (or that were deleted from the server, so should be removed // anyway). // Stop the gap *after* that item. - const firstIndex = oldIds.take(lastIndex).findLastIndex(id => !isPlaceholder(id) && compareId(id, newIds.first()) > 0) + 1; + const firstIndex = oldIds.take(lastIndex).findLastIndex(id => !isNonStatusId(id) && compareId(id, newIds.first()) > 0) + 1; let insertedIds = ImmutableOrderedSet(newIds).withMutations(insertedIds => { // It is possible, though unlikely, that the slice we are replacing contains items older // than the elements we got from the API. Get them and add them back at the back of the // slice. - const olderIds = oldIds.slice(firstIndex, lastIndex).filter(id => !isPlaceholder(id) && compareId(id, newIds.last()) < 0); + const olderIds = oldIds.slice(firstIndex, lastIndex).filter(id => !isNonStatusId(id) && compareId(id, newIds.last()) < 0); insertedIds.union(olderIds); // Make sure we aren't inserting duplicates diff --git a/app/javascript/mastodon/selectors/index.js b/app/javascript/mastodon/selectors/index.js index 1e68d49aa69f80..166c3e3e3a6df0 100644 --- a/app/javascript/mastodon/selectors/index.js +++ b/app/javascript/mastodon/selectors/index.js @@ -14,7 +14,7 @@ const getStatusInputSelectors = [ (state, { id }) => state.getIn(['accounts', state.getIn(['statuses', id, 'account'])]), (state, { id }) => state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'account'])]), getFilters, - (_, { contextType }) => ['detailed', 'bookmarks', 'favourites'].includes(contextType), + (_, { contextType }) => ['detailed', 'bookmarks', 'favourites', 'search'].includes(contextType), ]; function getStatusResultFunction( @@ -32,7 +32,11 @@ function getStatusResultFunction( }; } - if (statusBase.get('isLoading')) { + // When a status is loading, a `isLoading` property is set + // A status can be loading because it is not known yet (in which case it will only contain `isLoading`) + // or because it is being re-fetched; in the latter case, `visibility` will always be set to a non-empty + // string. + if (statusBase.get('isLoading') && !statusBase.get('visibility')) { return { status: null, loadingState: 'loading', @@ -74,7 +78,7 @@ function getStatusResultFunction( map.set('matched_filters', filtered); map.set('matched_media_filters', mediaFiltered); }), - loadingState: 'complete' + loadingState: statusBase.get('isLoading') ? 'loading' : 'complete' }; } diff --git a/app/javascript/mastodon/settings.js b/app/javascript/mastodon/settings.js deleted file mode 100644 index f4883dc406cdb5..00000000000000 --- a/app/javascript/mastodon/settings.js +++ /dev/null @@ -1,51 +0,0 @@ -export default class Settings { - - constructor(keyBase = null) { - this.keyBase = keyBase; - } - - generateKey(id) { - return this.keyBase ? [this.keyBase, `id${id}`].join('.') : id; - } - - set(id, data) { - const key = this.generateKey(id); - try { - const encodedData = JSON.stringify(data); - localStorage.setItem(key, encodedData); - return data; - } catch { - return null; - } - } - - get(id) { - const key = this.generateKey(id); - try { - const rawData = localStorage.getItem(key); - return JSON.parse(rawData); - } catch { - return null; - } - } - - remove(id) { - const data = this.get(id); - if (data) { - const key = this.generateKey(id); - try { - localStorage.removeItem(key); - } catch { - // ignore if the key is not found - } - } - return data; - } - -} - -export const pushNotificationsSetting = new Settings('mastodon_push_notification_data'); -export const tagHistory = new Settings('mastodon_tag_history'); -export const bannerSettings = new Settings('mastodon_banner_settings'); -export const searchHistory = new Settings('mastodon_search_history'); -export const playerSettings = new Settings('mastodon_player'); diff --git a/app/javascript/mastodon/settings.ts b/app/javascript/mastodon/settings.ts new file mode 100644 index 00000000000000..87c67a6e04a466 --- /dev/null +++ b/app/javascript/mastodon/settings.ts @@ -0,0 +1,68 @@ +import type { RecentSearch } from './models/search'; + +export class Settings> { + keyBase: string | null; + + constructor(keyBase: string | null = null) { + this.keyBase = keyBase; + } + + private generateKey(id: string | number | symbol): string { + const idStr = typeof id === 'string' ? id : String(id); + return this.keyBase ? [this.keyBase, `id${idStr}`].join('.') : idStr; + } + + set(id: K, data: T[K]): T[K] | null { + const key = this.generateKey(id); + try { + const encodedData = JSON.stringify(data); + localStorage.setItem(key, encodedData); + return data; + } catch { + return null; + } + } + + get(id: K): T[K] | null { + const key = this.generateKey(id); + try { + const rawData = localStorage.getItem(key); + if (rawData === null) return null; + return JSON.parse(rawData) as T[K]; + } catch { + return null; + } + } + + remove(id: K): T[K] | null { + const data = this.get(id); + if (data !== null) { + const key = this.generateKey(id); + try { + localStorage.removeItem(key); + } catch { + // ignore if the key is not found + } + } + return data; + } +} + +export const pushNotificationsSetting = new Settings< + Record +>('mastodon_push_notification_data'); +export const tagHistory = new Settings>( + 'mastodon_tag_history', +); +export const bannerSettings = new Settings>( + 'mastodon_banner_settings', +); +export const searchHistory = new Settings>( + 'mastodon_search_history', +); +export const playerSettings = new Settings<{ volume: number; muted: boolean }>( + 'mastodon_player', +); +export const wrapstodonSettings = new Settings< + Record +>('wrapstodon'); diff --git a/app/javascript/mastodon/store/typed_functions.ts b/app/javascript/mastodon/store/typed_functions.ts index 4d7341b0c829bd..79bca08a52f06b 100644 --- a/app/javascript/mastodon/store/typed_functions.ts +++ b/app/javascript/mastodon/store/typed_functions.ts @@ -42,7 +42,7 @@ interface AppThunkConfig { } export type AppThunkApi = Pick< GetThunkAPI, - 'getState' | 'dispatch' + 'getState' | 'dispatch' | 'requestId' >; interface AppThunkOptions { @@ -60,7 +60,7 @@ type AppThunk = ( type AppThunkCreator = ( arg: Arg, - api: AppThunkApi, + api: Pick, extra?: ExtraArg, ) => Returned; @@ -143,10 +143,10 @@ export function createAsyncThunk( name, async ( arg: Arg, - { getState, dispatch, fulfillWithValue, rejectWithValue }, + { getState, dispatch, requestId, fulfillWithValue, rejectWithValue }, ) => { try { - const result = await creator(arg, { dispatch, getState }); + const result = await creator(arg, { dispatch, getState, requestId }); return fulfillWithValue(result, { useLoadingBar: options.useLoadingBar, @@ -205,7 +205,7 @@ export function createDataLoadingThunk( thunkOptions?: AppThunkOptions, ): ReturnType>; -// Overload when the `onData` method returns nothing, then the mayload is the `onData` result +// Overload when the `onData` method returns nothing, then the payload is the `onData` result export function createDataLoadingThunk( name: string, loadData: LoadData, @@ -280,10 +280,11 @@ export function createDataLoadingThunk< return createAsyncThunk( name, - async (arg, { getState, dispatch }) => { + async (arg, { getState, dispatch, requestId }) => { const data = await loadData(arg, { dispatch, getState, + requestId, }); if (!onData) return data as Returned; @@ -291,6 +292,7 @@ export function createDataLoadingThunk< const result = await onData(data, { dispatch, getState, + requestId, discardLoadData: discardLoadDataInPayload, actionArg: arg, }); diff --git a/app/javascript/mastodon/stream.js b/app/javascript/mastodon/stream.js index 59b2fd75828f4a..27fbc25ba5e953 100644 --- a/app/javascript/mastodon/stream.js +++ b/app/javascript/mastodon/stream.js @@ -138,10 +138,15 @@ const channelNameWithInlineParams = (channelName, params) => { return `${channelName}&${Object.keys(params).map(key => `${key}=${params[key]}`).join('&')}`; }; +/** + * @typedef {import('mastodon/store').AppDispatch} Dispatch + * @typedef {import('mastodon/store').GetState} GetState + */ + /** * @param {string} channelName * @param {Object.} params - * @param {function(Function, Function): { onConnect: (function(): void), onReceive: (function(StreamEvent): void), onDisconnect: (function(): void) }} callbacks + * @param {function(Dispatch, GetState): { onConnect: (function(): void), onReceive: (function(StreamEvent): void), onDisconnect: (function(): void) }} callbacks * @returns {function(): void} */ // @ts-expect-error @@ -229,7 +234,7 @@ const handleEventSourceMessage = (e, received) => { * @param {string} streamingAPIBaseURL * @param {string} accessToken * @param {string} channelName - * @param {{ connected: Function, received: function(StreamEvent): void, disconnected: Function, reconnected: Function }} callbacks + * @param {{ connected: function(): void, received: function(StreamEvent): void, disconnected: function(): void, reconnected: function(): void }} callbacks * @returns {WebSocketClient | EventSource} */ const createConnection = (streamingAPIBaseURL, accessToken, channelName, { connected, received, disconnected, reconnected }) => { @@ -242,12 +247,9 @@ const createConnection = (streamingAPIBaseURL, accessToken, channelName, { conne // @ts-expect-error const ws = new WebSocketClient(`${streamingAPIBaseURL}/api/v1/streaming/?${params.join('&')}`, accessToken); - // @ts-expect-error ws.onopen = connected; ws.onmessage = e => received(JSON.parse(e.data)); - // @ts-expect-error ws.onclose = disconnected; - // @ts-expect-error ws.onreconnect = reconnected; return ws; diff --git a/app/javascript/mastodon/utils/__tests__/__snapshots__/html-test.ts.snap b/app/javascript/mastodon/utils/__tests__/__snapshots__/html-test.ts.snap index a579efa406b1ce..ea4561bc610c33 100644 --- a/app/javascript/mastodon/utils/__tests__/__snapshots__/html-test.ts.snap +++ b/app/javascript/mastodon/utils/__tests__/__snapshots__/html-test.ts.snap @@ -26,9 +26,11 @@ exports[`html > htmlStringToComponents > handles nested elements 1`] = ` exports[`html > htmlStringToComponents > ignores empty text nodes 1`] = ` [

+ lorem ipsum +

, ] `; @@ -37,6 +39,7 @@ exports[`html > htmlStringToComponents > respects allowedTags option 1`] = ` [

lorem + dolor diff --git a/app/javascript/mastodon/utils/__tests__/html-test.ts b/app/javascript/mastodon/utils/__tests__/html-test.ts index 6c08cc7cbfc07b..a48a8b572b3d1b 100644 --- a/app/javascript/mastodon/utils/__tests__/html-test.ts +++ b/app/javascript/mastodon/utils/__tests__/html-test.ts @@ -48,19 +48,26 @@ describe('html', () => { const input = '

lorem ipsum

'; const onText = vi.fn((text: string) => text); html.htmlStringToComponents(input, { onText }); - expect(onText).toHaveBeenCalledExactlyOnceWith('lorem ipsum'); + expect(onText).toHaveBeenCalledExactlyOnceWith('lorem ipsum', {}); }); it('calls onElement callback', () => { const input = '

lorem ipsum

'; - const onElement = vi.fn( - (element: HTMLElement, children: React.ReactNode[]) => - React.createElement(element.tagName.toLowerCase(), {}, ...children), + const onElement = vi.fn( + (element, props, children) => + React.createElement( + element.tagName.toLowerCase(), + props, + ...children, + ), ); html.htmlStringToComponents(input, { onElement }); expect(onElement).toHaveBeenCalledExactlyOnceWith( expect.objectContaining({ tagName: 'P' }), + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + expect.objectContaining({ key: expect.any(String) }), expect.arrayContaining(['lorem ipsum']), + {}, ); }); @@ -70,7 +77,10 @@ describe('html', () => { const output = html.htmlStringToComponents(input, { onElement }); expect(onElement).toHaveBeenCalledExactlyOnceWith( expect.objectContaining({ tagName: 'P' }), + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + expect.objectContaining({ key: expect.any(String) }), expect.arrayContaining(['lorem ipsum']), + {}, ); expect(output).toMatchSnapshot(); }); @@ -88,15 +98,16 @@ describe('html', () => { 'href', 'https://example.com', 'a', + {}, ); - expect(onAttribute).toHaveBeenCalledWith('target', '_blank', 'a'); - expect(onAttribute).toHaveBeenCalledWith('rel', 'nofollow', 'a'); + expect(onAttribute).toHaveBeenCalledWith('target', '_blank', 'a', {}); + expect(onAttribute).toHaveBeenCalledWith('rel', 'nofollow', 'a', {}); }); it('respects allowedTags option', () => { const input = '

lorem ipsum dolor

'; const output = html.htmlStringToComponents(input, { - allowedTags: new Set(['p', 'em']), + allowedTags: { p: {}, em: {} }, }); expect(output).toMatchSnapshot(); }); diff --git a/app/javascript/mastodon/utils/environment.ts b/app/javascript/mastodon/utils/environment.ts index 2d544417e3d65e..95075454f235fe 100644 --- a/app/javascript/mastodon/utils/environment.ts +++ b/app/javascript/mastodon/utils/environment.ts @@ -1,4 +1,4 @@ -import initialState from '../initial_state'; +import { initialState } from '../initial_state'; export function isDevelopment() { if (typeof process !== 'undefined') @@ -12,19 +12,8 @@ export function isProduction() { else return import.meta.env.PROD; } -export type Features = 'modern_emojis' | 'fasp' | 'http_message_signatures'; +export type Features = 'fasp' | 'http_message_signatures'; export function isFeatureEnabled(feature: Features) { return initialState?.features.includes(feature) ?? false; } - -export function isModernEmojiEnabled() { - try { - return ( - isFeatureEnabled('modern_emojis') && - localStorage.getItem('experiments')?.split(',').includes('modern_emojis') - ); - } catch { - return false; - } -} diff --git a/app/javascript/mastodon/utils/html.ts b/app/javascript/mastodon/utils/html.ts index 16863223007abf..c87b5a34cfc10e 100644 --- a/app/javascript/mastodon/utils/html.ts +++ b/app/javascript/mastodon/utils/html.ts @@ -1,5 +1,7 @@ import React from 'react'; +import htmlConfig from '../../config/html-tags.json'; + // NB: This function can still return unsafe HTML export const unescapeHTML = (html: string) => { const wrapper = document.createElement('div'); @@ -10,64 +12,60 @@ export const unescapeHTML = (html: string) => { return wrapper.textContent; }; +interface AllowedTag { + /* True means allow, false disallows global attributes, string renames the attribute name for React. */ + attributes?: Record; + /* If false, the tag cannot have children. Undefined or true means allowed. */ + children?: boolean; +} + +type AllowedTagsType = { + [Tag in keyof React.ReactHTML]?: AllowedTag; +}; + +const globalAttributes: Record = htmlConfig.global; +const defaultAllowedTags: AllowedTagsType = htmlConfig.tags; + interface QueueItem { node: Node; parent: React.ReactNode[]; depth: number; } -interface Options { +export type OnElementHandler< + Arg extends Record = Record, +> = ( + element: HTMLElement, + props: Record, + children: React.ReactNode[], + extra: Arg, +) => React.ReactNode; + +export type OnAttributeHandler< + Arg extends Record = Record, +> = ( + name: string, + value: string, + tagName: string, + extra: Arg, +) => [string, unknown] | undefined | null; + +export interface HTMLToStringOptions< + Arg extends Record = Record, +> { maxDepth?: number; - onText?: (text: string) => React.ReactNode; - onElement?: ( - element: HTMLElement, - children: React.ReactNode[], - ) => React.ReactNode; - onAttribute?: ( - name: string, - value: string, - tagName: string, - ) => [string, unknown] | null; - allowedTags?: Set; + onText?: (text: string, extra: Arg) => React.ReactNode; + onElement?: OnElementHandler; + onAttribute?: OnAttributeHandler; + allowedTags?: AllowedTagsType; + extraArgs?: Arg; } -const DEFAULT_ALLOWED_TAGS: ReadonlySet = new Set([ - 'a', - 'abbr', - 'b', - 'blockquote', - 'br', - 'cite', - 'code', - 'del', - 'dfn', - 'dl', - 'dt', - 'em', - 'h1', - 'h2', - 'h3', - 'h4', - 'h5', - 'h6', - 'hr', - 'i', - 'li', - 'ol', - 'p', - 'pre', - 'small', - 'span', - 'strong', - 'sub', - 'sup', - 'time', - 'u', - 'ul', -]); - -export function htmlStringToComponents( + +let uniqueIdCounter = 0; + +export function htmlStringToComponents>( htmlString: string, - options: Options = {}, + options: HTMLToStringOptions = {}, ) { const wrapper = document.createElement('template'); wrapper.innerHTML = htmlString; @@ -79,10 +77,11 @@ export function htmlStringToComponents( const { maxDepth = 10, - allowedTags = DEFAULT_ALLOWED_TAGS, + allowedTags = defaultAllowedTags, onAttribute, onElement, onText, + extraArgs = {} as Arg, } = options; while (queue.length > 0) { @@ -109,9 +108,9 @@ export function htmlStringToComponents( // Text can be added directly if it has any non-whitespace content. case Node.TEXT_NODE: { const text = node.textContent; - if (text && text.trim() !== '') { + if (text) { if (onText) { - parent.push(onText(text)); + parent.push(onText(text, extraArgs)); } else { parent.push(text); } @@ -127,7 +126,9 @@ export function htmlStringToComponents( } // If the tag is not allowed, skip it and its children. - if (!allowedTags.has(node.tagName.toLowerCase())) { + const tagName = node.tagName.toLowerCase(); + const tagInfo = allowedTags[tagName as keyof typeof allowedTags]; + if (!tagInfo) { continue; } @@ -135,9 +136,58 @@ export function htmlStringToComponents( const children: React.ReactNode[] = []; let element: React.ReactNode = undefined; + // Generate props from attributes. + const key = `html-${uniqueIdCounter++}`; // Get the current key and then increment it. + const props: Record = { key }; + for (const attr of node.attributes) { + let name = attr.name.toLowerCase(); + + // Custom attribute handler. + if (onAttribute) { + const result = onAttribute(name, attr.value, tagName, extraArgs); + // Rewrite this attribute. + if (result) { + const [cbName, value] = result; + props[cbName] = value; + continue; + } else if (result === null) { + // Explicitly remove this attribute. + continue; + } + } + + // Check global attributes first, then tag-specific ones. + const globalAttr = globalAttributes[name]; + const tagAttr = tagInfo.attributes?.[name]; + + // Exit if neither global nor tag-specific attribute is allowed. + if (!globalAttr && !tagAttr) { + continue; + } + + // Rename if needed. + if (typeof tagAttr === 'string') { + name = tagAttr; + } else if (typeof globalAttr === 'string') { + name = globalAttr; + } + + let value: string | boolean | number = attr.value; + + // Handle boolean attributes. + if (value === 'true') { + value = true; + } else if (value === 'false') { + value = false; + } + + props[name] = value; + } + // If onElement is provided, use it to create the element. if (onElement) { - const component = onElement(node, children); + const component = onElement(node, props, children, extraArgs); + // Check for undefined to allow returning null. if (component !== undefined) { element = component; @@ -146,26 +196,10 @@ export function htmlStringToComponents( // If the element wasn't created, use the default conversion. if (element === undefined) { - const props: Record = {}; - for (const attr of node.attributes) { - if (onAttribute) { - const result = onAttribute( - attr.name, - attr.value, - node.tagName.toLowerCase(), - ); - if (result) { - const [name, value] = result; - props[name] = value; - } - } else { - props[attr.name] = attr.value; - } - } element = React.createElement( - node.tagName.toLowerCase(), + tagName, props, - children, + tagInfo.children !== false ? children : undefined, ); } diff --git a/app/javascript/mastodon/utils/links.ts b/app/javascript/mastodon/utils/links.ts new file mode 100644 index 00000000000000..00821fa8d14de3 --- /dev/null +++ b/app/javascript/mastodon/utils/links.ts @@ -0,0 +1,94 @@ +import { on } from 'delegated-events'; + +export function setupLinkListeners() { + on('click', 'a[data-confirm]', handleConfirmLink); + + // We don't want to target links with data-confirm here, as those are handled already. + on('click', 'a[data-method]:not([data-confirm])', handleMethodLink); + + // We also want to target buttons with data-confirm that are not inside forms. + on('click', ':not(form) button[data-confirm]:not([form])', handleConfirmLink); +} + +function handleConfirmLink(event: MouseEvent) { + const target = event.currentTarget; + if ( + !(target instanceof HTMLAnchorElement) && + !(target instanceof HTMLButtonElement) + ) { + return; + } + const message = target.dataset.confirm; + if (!message || !window.confirm(message)) { + event.preventDefault(); + return; + } + + if (target.dataset.method) { + handleMethodLink(event); + } +} + +function handleMethodLink(event: MouseEvent) { + const anchor = event.currentTarget; + if (!(anchor instanceof HTMLAnchorElement)) { + return; + } + + const method = anchor.dataset.method?.toLowerCase(); + if (!method) { + return; + } + event.preventDefault(); + + // Create and submit a form with the specified method. + const form = document.createElement('form'); + form.method = 'post'; + form.action = anchor.href; + + // Add the hidden _method input to simulate other HTTP methods. + const methodInput = document.createElement('input'); + methodInput.type = 'hidden'; + methodInput.name = '_method'; + methodInput.value = method; + form.appendChild(methodInput); + + // Add CSRF token if available for same-origin requests. + const csrf = getCSRFToken(); + if (csrf && !isCrossDomain(anchor.href)) { + const csrfInput = document.createElement('input'); + csrfInput.type = 'hidden'; + csrfInput.name = csrf.param; + csrfInput.value = csrf.token; + form.appendChild(csrfInput); + } + + // The form needs to be in the document to be submitted. + form.style.display = 'none'; + document.body.appendChild(form); + + // We use requestSubmit to ensure any form submit handlers are properly invoked. + form.requestSubmit(); +} + +function getCSRFToken() { + const param = document.querySelector( + 'meta[name="csrf-param"]', + ); + const token = document.querySelector( + 'meta[name="csrf-token"]', + ); + if (param && token) { + return { param: param.content, token: token.content }; + } + return null; +} + +function isCrossDomain(href: string) { + const link = document.createElement('a'); + link.href = href; + return ( + link.protocol !== window.location.protocol || + link.host !== window.location.host + ); +} diff --git a/app/javascript/mastodon/utils/theme.ts b/app/javascript/mastodon/utils/theme.ts new file mode 100644 index 00000000000000..767d97cf5c94da --- /dev/null +++ b/app/javascript/mastodon/utils/theme.ts @@ -0,0 +1,13 @@ +export function getUserTheme() { + const { userTheme } = document.documentElement.dataset; + return userTheme; +} + +export function isDarkMode() { + const { userTheme } = document.documentElement.dataset; + return ( + (userTheme === 'system' && + window.matchMedia('(prefers-color-scheme: dark)').matches) || + userTheme !== 'mastodon-light' + ); +} diff --git a/app/javascript/mastodon/utils/types.ts b/app/javascript/mastodon/utils/types.ts index 24b9ee180f17fe..019b074813857b 100644 --- a/app/javascript/mastodon/utils/types.ts +++ b/app/javascript/mastodon/utils/types.ts @@ -14,3 +14,11 @@ export type SomeRequired = T & Required>; export type SomeOptional = Pick> & Partial>; + +export type RequiredExcept = SomeOptional, K>; + +export type OmitValueType = { + [K in keyof T as T[K] extends V ? never : K]: T[K]; +}; + +export type AnyFunction = (...args: never) => unknown; diff --git a/app/javascript/material-icons/400-24px/audio.svg b/app/javascript/material-icons/400-24px/audio.svg new file mode 100644 index 00000000000000..417e47c18fee37 --- /dev/null +++ b/app/javascript/material-icons/400-24px/audio.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/skins/vanilla/win95/common.scss b/app/javascript/skins/vanilla/win95/common.scss deleted file mode 100644 index 3eb1dd903ccd15..00000000000000 --- a/app/javascript/skins/vanilla/win95/common.scss +++ /dev/null @@ -1 +0,0 @@ -@use '@/styles/win95'; diff --git a/app/javascript/skins/vanilla/win95/names.yml b/app/javascript/skins/vanilla/win95/names.yml deleted file mode 100644 index 53b771c5e46521..00000000000000 --- a/app/javascript/skins/vanilla/win95/names.yml +++ /dev/null @@ -1,8 +0,0 @@ -en: - skins: - vanilla: - win95: Masto95 -cs: - skins: - vanilla: - win95: Windows 95 diff --git a/app/javascript/styles/application.scss b/app/javascript/styles/application.scss index b328d8ee34229c..cc23627a15ac9b 100644 --- a/app/javascript/styles/application.scss +++ b/app/javascript/styles/application.scss @@ -1,27 +1 @@ -@use 'mastodon/functions'; -@use 'mastodon/mixins'; -@use 'mastodon/variables'; -@use 'mastodon/css_variables'; -@use 'fonts/roboto'; -@use 'fonts/roboto-mono'; - -@use 'mastodon/reset'; -@use 'mastodon/basics'; -@use 'mastodon/branding'; -@use 'mastodon/containers'; -@use 'mastodon/lists'; -@use 'mastodon/widgets'; -@use 'mastodon/forms'; -@use 'mastodon/accounts'; -@use 'mastodon/components'; -@use 'mastodon/polls'; -@use 'mastodon/modal'; -@use 'mastodon/emoji_picker'; -@use 'mastodon/annual_reports'; -@use 'mastodon/about'; -@use 'mastodon/tables'; -@use 'mastodon/admin'; -@use 'mastodon/dashboard'; -@use 'mastodon/rtl'; -@use 'mastodon/accessibility'; -@use 'mastodon/rich_text'; +@use 'common'; diff --git a/app/javascript/styles/common.scss b/app/javascript/styles/common.scss new file mode 100644 index 00000000000000..aca615414f17aa --- /dev/null +++ b/app/javascript/styles/common.scss @@ -0,0 +1,24 @@ +@use 'mastodon/variables'; +@use 'mastodon/mixins'; +@use 'fonts/roboto'; +@use 'fonts/roboto-mono'; + +@use 'mastodon/reset'; +@use 'mastodon/theme'; +@use 'mastodon/basics'; +@use 'mastodon/branding'; +@use 'mastodon/containers'; +@use 'mastodon/lists'; +@use 'mastodon/widgets'; +@use 'mastodon/forms'; +@use 'mastodon/accounts'; +@use 'mastodon/components'; +@use 'mastodon/polls'; +@use 'mastodon/modal'; +@use 'mastodon/emoji_picker'; +@use 'mastodon/about'; +@use 'mastodon/tables'; +@use 'mastodon/admin'; +@use 'mastodon/dashboard'; +@use 'mastodon/rtl'; +@use 'mastodon/rich_text'; diff --git a/app/javascript/styles/contrast.scss b/app/javascript/styles/contrast.scss index 367be051f1a3b7..93bbfed0805579 100644 --- a/app/javascript/styles/contrast.scss +++ b/app/javascript/styles/contrast.scss @@ -1,3 +1,2 @@ -@use 'contrast/variables'; -@use 'application'; -@use 'contrast/diff'; +@use 'common'; +@use 'mastodon/high-contrast'; diff --git a/app/javascript/styles/contrast/variables.scss b/app/javascript/styles/contrast/variables.scss deleted file mode 100644 index e057352ba3851a..00000000000000 --- a/app/javascript/styles/contrast/variables.scss +++ /dev/null @@ -1,25 +0,0 @@ -@use '../mastodon/functions' as *; - -// Dependent colors -$black: #000; - -$classic-base-color: hsl(240deg, 16%, 19%); -$classic-primary-color: hsl(240deg, 29%, 70%); -$classic-secondary-color: hsl(255deg, 25%, 88%); -$classic-highlight-color: hsl(240deg, 100%, 69%); - -$ui-base-color: $classic-base-color !default; -$ui-primary-color: $classic-primary-color !default; -$ui-secondary-color: $classic-secondary-color !default; -$ui-highlight-color: $classic-highlight-color !default; - -@use '../mastodon/variables' with ( - $darker-text-color: lighten($ui-primary-color, 20%), - $dark-text-color: lighten($ui-primary-color, 12%), - $secondary-text-color: lighten($ui-secondary-color, 6%), - $highlight-text-color: lighten($ui-highlight-color, 10%), - $action-button-color: lighten($ui-base-color, 50%), - $inverted-text-color: $black, - $lighter-text-color: darken($ui-base-color, 6%), - $light-text-color: $classic-primary-color -); diff --git a/app/javascript/styles/entrypoints/mailer.scss b/app/javascript/styles/entrypoints/mailer.scss index 7d2a54afae0ca2..fcbbd66f4c7747 100644 --- a/app/javascript/styles/entrypoints/mailer.scss +++ b/app/javascript/styles/entrypoints/mailer.scss @@ -88,6 +88,14 @@ table + p { padding: 24px; } +.email-inner-nested-card-td { + border-radius: 12px; + padding: 18px; + overflow: hidden; + background-color: #fff; + border: 1px solid #dfdee3; +} + // Account .email-account-banner-table { background-color: #f3f2f5; @@ -559,12 +567,29 @@ table + p { } } +.email-quote-header-img { + width: 34px; + + img { + width: 34px; + height: 34px; + border-radius: 8px; + overflow: hidden; + } +} + .email-status-header-text { padding-left: 16px; padding-right: 16px; vertical-align: middle; } +.email-quote-header-text { + padding-left: 14px; + padding-right: 14px; + vertical-align: middle; +} + .email-status-header-name { font-size: 16px; font-weight: 600; @@ -578,6 +603,19 @@ table + p { color: #746a89; } +.email-quote-header-name { + font-size: 14px; + font-weight: 600; + line-height: 18px; + color: #17063b; +} + +.email-quote-header-handle { + font-size: 13px; + line-height: 18px; + color: #746a89; +} + .email-status-content { padding-top: 24px; } @@ -589,6 +627,10 @@ table + p { } .email-status-prose { + .quote-inline { + display: none; + } + p { font-size: 14px; line-height: 20px; diff --git a/app/javascript/styles/mastodon-light.scss b/app/javascript/styles/mastodon-light.scss index b530616a3c3808..cc23627a15ac9b 100644 --- a/app/javascript/styles/mastodon-light.scss +++ b/app/javascript/styles/mastodon-light.scss @@ -1,4 +1 @@ -@use 'mastodon-light/variables'; -@use 'mastodon-light/css_variables'; -@use 'application'; -@use 'mastodon-light/diff'; +@use 'common'; diff --git a/app/javascript/styles/mastodon-light/css_variables.scss b/app/javascript/styles/mastodon-light/css_variables.scss deleted file mode 100644 index d96c368b251eae..00000000000000 --- a/app/javascript/styles/mastodon-light/css_variables.scss +++ /dev/null @@ -1,23 +0,0 @@ -@use 'sass:color'; -@use '../mastodon/variables' as *; -@use 'variables' as *; -@use '../mastodon/functions' as *; - -body { - --dropdown-border-color: hsl(240deg, 25%, 88%); - --dropdown-background-color: #fff; - --modal-border-color: hsl(240deg, 25%, 88%); - --modal-background-color: var(--background-color); - --background-border-color: hsl(240deg, 25%, 88%); - --background-color: #fff; - --background-color-tint: rgba(255, 255, 255, 80%); - --background-filter: blur(10px); - --surface-variant-background-color: #f1ebfb; - --surface-border-color: #cac4d0; - --on-surface-color: #{color.adjust($ui-base-color, $alpha: -0.65)}; - --rich-text-container-color: rgba(255, 216, 231, 100%); - --rich-text-text-color: rgba(114, 47, 83, 100%); - --rich-text-decorations-color: rgba(255, 175, 212, 100%); - --input-placeholder-color: #{color.adjust($dark-text-color, $alpha: -0.5)}; - --input-background-color: #{darken($ui-base-color, 10%)}; -} diff --git a/app/javascript/styles/mastodon-light/diff.scss b/app/javascript/styles/mastodon-light/diff.scss deleted file mode 100644 index fe3265f357fba8..00000000000000 --- a/app/javascript/styles/mastodon-light/diff.scss +++ /dev/null @@ -1,526 +0,0 @@ -// Notes! -// Sass color functions, "darken" and "lighten" are automatically replaced. -@use 'sass:color'; -@use '../mastodon/functions' as *; -@use '../mastodon/variables' as *; - -.simple_form .button.button-tertiary { - color: $highlight-text-color; - - &:hover, - &:focus, - &:active { - color: $white; - } -} - -.status-card__actions button, -.status-card__actions a { - color: color.change($white, $alpha: 0.8); - - &:hover, - &:active, - &:focus { - color: $white; - } -} - -// Change default background colors of columns -.interaction-modal { - background: $white; - border: 1px solid var(--background-border-color); -} - -.rules-list li::before { - background: $ui-highlight-color; -} - -.directory__card__img { - background: lighten($ui-base-color, 12%); -} - -.account__header { - background: $white; -} - -.column-header__button.active { - color: $ui-highlight-color; - - &:hover, - &:active, - &:focus { - color: $ui-highlight-color; - } -} - -.icon-button:disabled { - color: darken($action-button-color, 25%); -} - -.getting-started__footer a { - color: $ui-secondary-color; - text-decoration: underline; -} - -.confirmation-modal__secondary-button, -.confirmation-modal__cancel-button, -.mute-modal__cancel-button, -.block-modal__cancel-button { - color: lighten($ui-base-color, 26%); - - &:hover, - &:focus, - &:active { - color: $primary-text-color; - } -} - -.getting-started .navigation-bar { - border-top: 1px solid var(--background-border-color); - border-bottom: 1px solid var(--background-border-color); - - @media screen and (max-width: $no-gap-breakpoint) { - border-top: 0; - } -} - -.search__input, -.search__popout, -.setting-text, -.report-dialog-modal__textarea, -.audio-player { - border: 1px solid var(--background-border-color); -} - -.report-dialog-modal .dialog-option .poll__input { - color: $white; -} - -.upload-progress__backdrop { - background: $ui-base-color; -} - -// Change the background colors of statuses -.focusable:focus-visible { - background: lighten($white, 4%); -} - -.account-gallery__item a { - background-color: $ui-base-color; -} - -// Change the text colors on inverted background -.actions-modal ul li:not(:empty) a.active, -.actions-modal ul li:not(:empty) a.active button, -.actions-modal ul li:not(:empty) a:active, -.actions-modal ul li:not(:empty) a:active button, -.actions-modal ul li:not(:empty) a:focus, -.actions-modal ul li:not(:empty) a:focus button, -.actions-modal ul li:not(:empty) a:hover, -.actions-modal ul li:not(:empty) a:hover button, -.simple_form button:not(.button, .link-button) { - color: $white; -} - -.compare-history-modal .report-modal__target, -.report-dialog-modal .poll__option.dialog-option { - border-bottom-color: lighten($ui-base-color, 4%); -} - -.report-dialog-modal__container { - border-top-color: lighten($ui-base-color, 4%); -} - -.dialog-modal__content__preview { - background: #fff; - border-bottom: 1px solid var(--modal-border-color); -} - -.reactions-bar__item:hover, -.reactions-bar__item:focus, -.reactions-bar__item:active { - background-color: $ui-base-color; -} - -.reactions-bar__item.active { - background-color: color.mix($white, $ui-highlight-color, 80%); - border-color: color.mix( - lighten($ui-base-color, 8%), - $ui-highlight-color, - 80% - ); -} - -.media-modal__overlay .picture-in-picture__footer { - border: 0; -} - -.picture-in-picture__header { - border-bottom: 0; -} - -.announcements, -.picture-in-picture__footer { - border-top: 0; -} - -.icon-with-badge__badge { - border-color: $white; - color: $white; -} - -.column-settings__hashtags .column-select__option { - color: $white; -} - -.dashboard__quick-access, -.focal-point__preview strong, -.admin-wrapper .content__heading__tabs a.selected { - color: $white; -} - -.flash-message.warning { - color: lighten($gold-star, 16%); -} - -.boost-modal__action-bar, -.confirmation-modal__action-bar, -.mute-modal__action-bar, -.block-modal__action-bar, -.onboarding-modal__paginator, -.error-modal__footer { - background: darken($ui-base-color, 6%); - - .onboarding-modal__nav, - .error-modal__nav { - &:hover, - &:focus, - &:active { - background-color: darken($ui-base-color, 12%); - } - } -} - -.display-case__case { - background: $white; -} - -.embed-modal .embed-modal__container .embed-modal__html { - background: $white; - border: 1px solid var(--background-border-color); - - &:focus { - border-color: lighten($ui-base-color, 12%); - background: $white; - } -} - -.react-toggle-track { - background: $ui-primary-color; -} - -.react-toggle:hover:not(.react-toggle--disabled) .react-toggle-track { - background: lighten($ui-primary-color, 10%); -} - -.react-toggle.react-toggle--checked:hover:not(.react-toggle--disabled) - .react-toggle-track { - background: lighten($ui-highlight-color, 10%); -} - -// Change the default color used for the text in an empty column or on the error column -.empty-column-indicator, -.error-column { - color: $primary-text-color; - background: $white; -} - -// Change the default colors used on some parts of the profile pages -.activity-stream-tabs { - background: $white; - border-bottom-color: lighten($ui-base-color, 8%); -} - -.nothing-here, -.page-header, -.directory__tag > a, -.directory__tag > div { - background: $white; - border: 1px solid var(--background-border-color); -} - -.picture-in-picture-placeholder { - background: $white; - border-color: lighten($ui-base-color, 8%); - color: lighten($ui-base-color, 8%); -} - -.directory__tag > a { - &:hover, - &:active, - &:focus { - background: $ui-base-color; - } -} - -.batch-table { - &__toolbar, - &__row, - .nothing-here { - border-color: lighten($ui-base-color, 8%); - } -} - -.accounts-grid { - .account-grid-card { - .controls { - .icon-button { - color: $darker-text-color; - } - } - - .name { - a { - color: $primary-text-color; - } - } - - .username { - color: $darker-text-color; - } - - .account__header__content { - color: $primary-text-color; - } - } -} - -.simple_form { - .warning { - box-shadow: none; - background: color.change($error-red, $alpha: 0.5); - text-shadow: none; - } - - .recommended { - border-color: $ui-highlight-color; - color: $ui-highlight-color; - background-color: color.change($ui-highlight-color, $alpha: 0.1); - } - - input[type='text'], - input[type='number'], - input[type='email'], - input[type='password'], - input[type='url'], - input[type='datetime-local'], - textarea { - background: darken($ui-base-color, 10%); - } - - select { - background: darken($ui-base-color, 10%) - url("data:image/svg+xml;utf8,") - no-repeat right 8px center / auto 14px; - } -} - -.compose-form .compose-form__warning { - border-color: $ui-highlight-color; - background-color: color.change($ui-highlight-color, $alpha: 0.1); - - &, - a { - color: $ui-highlight-color; - } -} - -.status__content, -.reply-indicator__content { - a { - color: $highlight-text-color; - } -} - -.notification__filter-bar button.active::after, -.account__section-headline a.active::after { - border-color: transparent transparent $white; -} - -.activity-stream, -.nothing-here, -.directory__tag > a, -.directory__tag > div, -.card > a, -.page-header, -.compose-form, -.compose-form__warning { - box-shadow: none; -} - -.card { - &__img { - background: darken($ui-base-color, 10%); - } - - & > a { - &:hover, - &:active, - &:focus { - .card__bar { - background: darken($ui-base-color, 10%); - } - } - } -} - -.status__wrapper-direct { - background-color: color.change($ui-highlight-color, $alpha: 0.1); - - &:focus { - background-color: color.change($ui-highlight-color, $alpha: 0.15); - } -} - -.compose-form__actions .icon-button.active, -.dropdown-button.active, -.language-dropdown__dropdown__results__item:focus, -.language-dropdown__dropdown__results__item.active, -.language-dropdown__dropdown__results__item:focus - .language-dropdown__dropdown__results__item__common-name, -.language-dropdown__dropdown__results__item.active - .language-dropdown__dropdown__results__item__common-name { - color: $white; -} - -.privacy-dropdown__option, -.visibility-dropdown__option { - &:focus, - &.active { - --dropdown-text-color: #{$white}; - } -} - -.compose-form .spoiler-input__input { - color: lighten($ui-highlight-color, 8%); -} - -.emoji-mart-search input, -.language-dropdown__dropdown .emoji-mart-search input, -.poll__option input[type='text'] { - background: darken($ui-base-color, 10%); -} - -.dropdown-button.warning { - border-color: #b3261e; - color: #b3261e; - - &.active { - background-color: #f9dedc; - } -} - -.search__popout__menu__item { - &:hover, - &:active, - &:focus, - &.active { - color: $white; - - mark, - .icon-button { - color: $white; - } - } -} - -.inline-follow-suggestions { - background-color: color.change($ui-highlight-color, $alpha: 0.1); - border-bottom-color: color.change($ui-highlight-color, $alpha: 0.3); - - &.focusable:focus-visible { - background: color.change($ui-highlight-color, $alpha: 0.1); - } -} - -.inline-follow-suggestions__body__scrollable__card { - background: $white; -} - -.inline-follow-suggestions__body__scroll-button__icon { - color: $white; -} - -a.sparkline { - &:hover, - &:focus, - &:active { - background: darken($ui-base-color, 10%); - } -} - -.dashboard__counters { - & > div { - & > a { - &:hover, - &:focus, - &:active { - background: darken($ui-base-color, 10%); - } - } - } -} - -.directory { - &__tag { - & > a { - &:hover, - &:focus, - &:active { - background: darken($ui-base-color, 10%); - } - } - } -} - -.strike-entry { - &:hover, - &:focus, - &:active { - background: darken($ui-base-color, 10%); - } -} - -.setting-text { - background: darken($ui-base-color, 10%); -} - -.report-dialog-modal__textarea { - background: darken($ui-base-color, 10%); -} - -.autosuggest-account { - .display-name__account { - color: $dark-text-color; - } -} - -.notification-group--annual-report { - .notification-group__icon, - .notification-group__main .link-button { - color: var(--indigo-3); - } -} - -@supports not selector(::-webkit-scrollbar) { - html { - scrollbar-color: color.change($action-button-color, $alpha: 0.25) - var(--background-border-color); - } -} - -.custom-scrollbars { - ::-webkit-scrollbar-thumb { - opacity: 0.25; - } -} diff --git a/app/javascript/styles/mastodon-light/variables.scss b/app/javascript/styles/mastodon-light/variables.scss deleted file mode 100644 index a6a5e5556b58aa..00000000000000 --- a/app/javascript/styles/mastodon-light/variables.scss +++ /dev/null @@ -1,46 +0,0 @@ -@use 'sass:color'; - -@use '../mastodon/functions' with ( - $darken-multiplier: 1, - $lighten-multiplier: -1 -); - -$black: #000; // Black -$white: #fff; // White -$blurple-500: #6364ff; // Brand purple -$grey-600: hsl(240deg, 8%, 33%); // Trout -$grey-100: hsl(240deg, 51%, 90%); // Topaz - -$classic-base-color: hsl(240deg, 16%, 19%); -$classic-secondary-color: hsl(255deg, 25%, 88%); -$classic-highlight-color: $blurple-500; - -@use '../mastodon/variables' with ( - $success-green: color.adjust( - hsl(138deg, 32%, 35%), - $lightness: 8%, - $space: hsl - ), - $base-overlay-background: $white, - - $ui-base-color: $classic-secondary-color, - $ui-base-lighter-color: hsl(250deg, 24%, 75%), - $ui-secondary-color: $classic-base-color, - - $ui-button-secondary-color: $grey-600, - $ui-button-secondary-border-color: $grey-600, - $ui-button-secondary-focus-color: $white, - $ui-button-tertiary-color: $blurple-500, - $ui-button-tertiary-border-color: $blurple-500, - - $primary-text-color: $black, - $darker-text-color: $classic-base-color, - $lighter-text-color: $classic-base-color, - $highlight-text-color: $classic-highlight-color, - $dark-text-color: hsl(240deg, 16%, 32%), - $light-text-color: hsl(240deg, 16%, 32%), - $inverted-text-color: $black, - - $action-button-color: hsl(240deg, 16%, 45%), - $emojis-requiring-inversion: 'chains' -); diff --git a/app/javascript/styles/mastodon/_functions.scss b/app/javascript/styles/mastodon/_functions.scss deleted file mode 100644 index a9911edb9d1d5b..00000000000000 --- a/app/javascript/styles/mastodon/_functions.scss +++ /dev/null @@ -1,31 +0,0 @@ -@use 'sass:color'; -@use 'sass:string'; -@use 'sass:meta'; - -$darken-multiplier: -1 !default; -$lighten-multiplier: 1 !default; - -// Invert darkened and lightened colors -@function darken($color, $amount) { - @return color.adjust( - $color, - $lightness: $amount * $darken-multiplier, - $space: hsl - ); -} - -@function lighten($color, $amount) { - @return color.adjust( - $color, - $lightness: $amount * $lighten-multiplier, - $space: hsl - ); -} - -@function hex-color($color) { - @if meta.type-of($color) == 'color' { - $color: string.slice(color.ie-hex-str($color), 4); - } - - @return '%23' + string.unquote($color); -} diff --git a/app/javascript/styles/mastodon/_mixins.scss b/app/javascript/styles/mastodon/_mixins.scss index d66fa405819ef7..effbe82c3d8581 100644 --- a/app/javascript/styles/mastodon/_mixins.scss +++ b/app/javascript/styles/mastodon/_mixins.scss @@ -1,33 +1,30 @@ -@use 'sass:color'; -@use 'variables' as *; - @mixin search-input { outline: 0; box-sizing: border-box; width: 100%; box-shadow: none; font-family: inherit; - background: var(--input-background-color); - color: $darker-text-color; + background: var(--color-bg-secondary); + color: var(--color-text-primary); border-radius: 4px; - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-on-bg-secondary); font-size: 17px; line-height: normal; margin: 0; } @mixin search-popout { - background: $simple-background-color; + background: var(--color-bg-elevated); border-radius: 4px; padding: 10px 14px; padding-bottom: 14px; margin-top: 10px; - color: $light-text-color; - box-shadow: 2px 4px 15px color.change($base-shadow-color, $alpha: 0.4); + color: var(--color-text-secondary); + box-shadow: 2px 4px 15px var(--color-shadow-primary); h4 { text-transform: uppercase; - color: $light-text-color; + color: var(--color-text-secondary); font-size: 13px; font-weight: 500; margin-bottom: 10px; @@ -43,6 +40,6 @@ em { font-weight: 500; - color: $inverted-text-color; + color: var(--color-text-primary); } } diff --git a/app/javascript/styles/mastodon/_variables.scss b/app/javascript/styles/mastodon/_variables.scss index 816a9d9998c089..d561f714547625 100644 --- a/app/javascript/styles/mastodon/_variables.scss +++ b/app/javascript/styles/mastodon/_variables.scss @@ -1,91 +1,3 @@ -@use 'sass:color'; -@use 'functions' as *; - -// Commonly used web colors -$black: #000; // Black -$white: #fff; // White -$red-600: #b7253d !default; // Deep Carmine -$red-500: #df405a !default; // Cerise -$blurple-600: #563acc; // Iris -$blurple-500: #6364ff; // Brand purple -$blurple-400: #7477fd; // Medium slate blue -$blurple-300: #858afa; // Faded Blue -$grey-600: hsl(240deg, 8%, 33%); // Trout -$grey-100: hsl(240deg, 51%, 90%); // Topaz - -$success-green: #79bd9a !default; // Padua -$error-red: $red-500 !default; // Cerise -$warning-red: #ff5050 !default; // Sunset Orange -$gold-star: #ca8f04 !default; // Dark Goldenrod - -$red-bookmark: $warning-red; - -// Values from the classic Mastodon UI -$classic-base-color: hsl(240deg, 16%, 19%); -$classic-primary-color: hsl(240deg, 29%, 70%); -$classic-secondary-color: hsl(255deg, 25%, 88%); -$classic-highlight-color: $blurple-500; - -// Variables for defaults in UI -$base-shadow-color: $black !default; -$base-overlay-background: $black !default; -$base-border-color: $white !default; -$simple-background-color: $white !default; -$valid-value-color: $success-green !default; -$error-value-color: $error-red !default; - -// Tell UI to use selected colors -$ui-base-color: $classic-base-color !default; // Darkest -$ui-base-lighter-color: lighten( - $ui-base-color, - 26% -) !default; // Lighter darkest -$ui-primary-color: $classic-primary-color !default; // Lighter -$ui-secondary-color: $classic-secondary-color !default; // Lightest -$ui-highlight-color: $classic-highlight-color !default; -$ui-button-color: $white !default; -$ui-button-background-color: $blurple-500 !default; -$ui-button-focus-background-color: $blurple-600 !default; -$ui-button-focus-outline-color: $blurple-400 !default; -$ui-button-focus-outline: solid 2px $ui-button-focus-outline-color !default; - -$ui-button-disabled-color: color.adjust( - $ui-button-background-color, - $alpha: -0.3 -) !default; - -$ui-button-secondary-color: $blurple-500 !default; -$ui-button-secondary-border-color: $blurple-500 !default; -$ui-button-secondary-focus-border-color: $blurple-300 !default; -$ui-button-secondary-focus-color: $blurple-300 !default; - -$ui-button-tertiary-color: $blurple-300 !default; -$ui-button-tertiary-border-color: $blurple-300 !default; -$ui-button-tertiary-focus-background-color: $blurple-600 !default; -$ui-button-tertiary-focus-color: $white !default; - -$ui-button-destructive-background-color: $red-500 !default; -$ui-button-destructive-focus-background-color: $red-600 !default; - -$ui-button-icon-focus-outline: $ui-button-focus-outline !default; -$ui-button-icon-hover-background-color: rgba(140, 141, 255, 40%) !default; - -// Variables for texts -$primary-text-color: $white !default; -$darker-text-color: $ui-primary-color !default; -$dark-text-color: $ui-base-lighter-color !default; -$secondary-text-color: $ui-secondary-color !default; -$highlight-text-color: lighten($ui-highlight-color, 8%) !default; -$action-button-color: $ui-base-lighter-color !default; -$action-button-focus-color: lighten($ui-base-lighter-color, 4%) !default; -$passive-text-color: $gold-star !default; -$active-passive-text-color: $success-green !default; - -// For texts on inverted backgrounds -$inverted-text-color: $ui-base-color !default; -$lighter-text-color: $ui-base-lighter-color !default; -$light-text-color: $ui-primary-color !default; - // Keep this filter a SCSS variable rather than // a CSS Custom Property due to this Safari bug: // https://github.com/mdn/browser-compat-data/issues/25914#issuecomment-2676190245 @@ -108,8 +20,3 @@ $no-columns-breakpoint: 600px; $font-sans-serif: 'mastodon-font-sans-serif' !default; $font-display: 'mastodon-font-display' !default; $font-monospace: 'mastodon-font-monospace' !default; - -$emojis-requiring-inversion: 'back' 'copyright' 'curly_loop' 'currency_exchange' - 'end' 'heavy_check_mark' 'heavy_division_sign' 'heavy_dollar_sign' - 'heavy_minus_sign' 'heavy_multiplication_x' 'heavy_plus_sign' 'on' - 'registered' 'soon' 'spider' 'telephone_receiver' 'tm' 'top' 'wavy_dash' !default; diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss index ba0605b79e9188..0bb2c8c9eb21fb 100644 --- a/app/javascript/styles/mastodon/about.scss +++ b/app/javascript/styles/mastodon/about.scss @@ -1,5 +1,4 @@ @use 'variables' as *; -@use 'functions' as *; $maximum-width: 1235px; $fluid-breakpoint: $maximum-width + 20px; @@ -28,7 +27,7 @@ $fluid-breakpoint: $maximum-width + 20px; li { position: relative; - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); padding: 1em 1.75em; padding-inline-start: 3em; font-weight: 500; @@ -68,8 +67,8 @@ $fluid-breakpoint: $maximum-width + 20px; position: absolute; inset-inline-start: 0; top: 1em; - background: $highlight-text-color; - color: $ui-base-color; + background: var(--color-bg-brand-base); + color: var(--color-text-on-brand-base); border-radius: 50%; width: 4ch; height: 4ch; @@ -85,13 +84,13 @@ $fluid-breakpoint: $maximum-width + 20px; } &__text { - color: $primary-text-color; + color: var(--color-text-primary); } &__hint { font-size: 14px; font-weight: 400; - color: $darker-text-color; + color: var(--color-text-secondary); } } @@ -104,21 +103,21 @@ $fluid-breakpoint: $maximum-width + 20px; > label { font-size: 14px; font-weight: 600; - color: $primary-text-color; + color: var(--color-text-primary); } - > select { + select { appearance: none; box-sizing: border-box; font-size: 14px; - color: $primary-text-color; + color: var(--color-text-primary); display: block; width: 100%; outline: 0; font-family: inherit; resize: vertical; - background: $ui-base-color; - border: 1px solid var(--background-border-color); + background: var(--color-bg-secondary); + border: 1px solid var(--color-border-primary); border-radius: 4px; padding-inline-start: 10px; padding-inline-end: 30px; @@ -128,18 +127,4 @@ $fluid-breakpoint: $maximum-width + 20px; font-size: 16px; } } - - &::after { - display: block; - position: absolute; - width: 15px; - height: 15px; - content: ''; - mask: url("data:image/svg+xml;utf8,") - no-repeat 50% 50%; - mask-size: contain; - right: 8px; - background-color: lighten($ui-base-color, 12%); - pointer-events: none; - } } diff --git a/app/javascript/styles/mastodon/accessibility.scss b/app/javascript/styles/mastodon/accessibility.scss deleted file mode 100644 index 7cd2d4eae39781..00000000000000 --- a/app/javascript/styles/mastodon/accessibility.scss +++ /dev/null @@ -1,13 +0,0 @@ -@use 'variables' as *; - -%emoji-color-inversion { - filter: invert(1); -} - -.emojione { - @each $emoji in $emojis-requiring-inversion { - &[title=':#{$emoji}:'] { - @extend %emoji-color-inversion; - } - } -} diff --git a/app/javascript/styles/mastodon/accounts.scss b/app/javascript/styles/mastodon/accounts.scss index da4b9bdaa81d41..d1c35e3f9ec067 100644 --- a/app/javascript/styles/mastodon/accounts.scss +++ b/app/javascript/styles/mastodon/accounts.scss @@ -1,6 +1,5 @@ @use 'sass:color'; @use 'variables' as *; -@use 'functions' as *; .card { & > a { @@ -14,7 +13,7 @@ &:active, &:focus { .card__bar { - background: $ui-base-color; + background: var(--color-bg-brand-softer); } } } @@ -22,8 +21,8 @@ &__img { height: 130px; position: relative; - background: $ui-base-color; - border: 1px solid var(--background-border-color); + background: var(--color-bg-secondary); + border: 1px solid var(--color-border-primary); border-bottom: none; img { @@ -45,8 +44,8 @@ display: flex; justify-content: flex-start; align-items: center; - background: var(--background-color); - border: 1px solid var(--background-border-color); + background: var(--color-bg-primary); + border: 1px solid var(--color-border-primary); border-top: none; .avatar { @@ -61,7 +60,7 @@ display: block; margin: 0; border-radius: 4px; - background: darken($ui-base-color, 8%); + background: var(--color-bg-secondary); object-fit: cover; } } @@ -76,7 +75,7 @@ strong { font-size: 15px; - color: $primary-text-color; + color: var(--color-text-primary); font-weight: 500; overflow: hidden; text-overflow: ellipsis; @@ -85,7 +84,7 @@ span { display: block; font-size: 14px; - color: $darker-text-color; + color: var(--color-text-secondary); font-weight: 400; overflow: hidden; text-overflow: ellipsis; @@ -106,7 +105,7 @@ .page, .gap { font-size: 14px; - color: $primary-text-color; + color: var(--color-text-primary); font-weight: 500; display: inline-block; padding: 6px 10px; @@ -114,9 +113,9 @@ } .current { - background: $simple-background-color; + color: var(--color-bg-primary); + background: var(--color-text-primary); border-radius: 100px; - color: $inverted-text-color; cursor: default; margin: 0 10px; } @@ -128,7 +127,7 @@ .older, .newer { text-transform: uppercase; - color: $secondary-text-color; + color: var(--color-text-primary); } .older { @@ -143,7 +142,7 @@ .disabled { cursor: default; - color: lighten($inverted-text-color, 10%); + color: var(--color-text-disabled); } @media screen and (width <= 700px) { @@ -161,9 +160,8 @@ } .nothing-here { - background: $ui-base-color; - box-shadow: 0 0 15px color.change($base-shadow-color, $alpha: 0.2); - color: $darker-text-color; + color: var(--color-text-secondary); + background: var(--color-bg-primary); font-size: 14px; font-weight: 500; text-align: center; @@ -174,6 +172,15 @@ border-radius: 4px; padding: 20px; min-height: 30vh; + border: 1px solid var(--color-border-primary); + + @media screen and (min-width: ($no-gap-breakpoint - 1)) { + border-top: 0; + } + + &--no-toolbar { + border-top: 1px solid var(--color-border-primary); + } &--under-tabs { border-radius: 0 0 4px 4px; @@ -196,7 +203,7 @@ font-size: 12px; line-height: 12px; font-weight: 500; - color: $ui-secondary-color; + color: var(--color-text-primary); text-overflow: ellipsis; white-space: nowrap; overflow: hidden; @@ -206,16 +213,24 @@ .simple_form .overridden, .simple_form .recommended, .simple_form .not_recommended { - background-color: color.change($ui-secondary-color, $alpha: 0.1); - border: 1px solid color.change($ui-secondary-color, $alpha: 0.5); + background-color: var(--color-bg-secondary); + border: 1px solid var(--color-border-primary); +} + +.information-badge { + &.superapp { + color: var(--color-text-success); + background-color: var(--color-bg-success-softer); + border-color: var(--color-border-on-bg-success-softer); + } } .account-role { display: inline-flex; padding: 4px; padding-inline-end: 8px; - border: 1px solid $highlight-text-color; - color: $highlight-text-color; + border: 1px solid var(--color-text-brand); + color: var(--color-text-brand); font-weight: 500; font-size: 12px; letter-spacing: 0.5px; @@ -238,18 +253,10 @@ } } -.information-badge { - &.superapp { - color: $success-green; - background-color: color.change($success-green, $alpha: 0.1); - border-color: color.change($success-green, $alpha: 0.5); - } -} - .simple_form .not_recommended { - color: lighten($error-red, 12%); - background-color: rgba(lighten($error-red, 12%), 0.1); - border-color: rgba(lighten($error-red, 12%), 0.5); + color: var(--color-text-error); + background-color: var(--color-bg-error-softer); + border-color: var(--color-border-on-bg-error-softer); } .account__header__fields { @@ -257,14 +264,14 @@ padding: 0; margin: 15px -15px -15px; border: 0 none; - border-top: 1px solid lighten($ui-base-color, 12%); - border-bottom: 1px solid lighten($ui-base-color, 12%); + border-top: 1px solid var(--color-border-primary); + border-bottom: 1px solid var(--color-border-primary); font-size: 14px; line-height: 20px; dl { display: flex; - border-bottom: 1px solid lighten($ui-base-color, 12%); + border-bottom: 1px solid var(--color-border-primary); } dt, @@ -282,17 +289,17 @@ font-weight: 500; width: 120px; flex: 0 0 auto; - color: $secondary-text-color; - background: rgba(darken($ui-base-color, 8%), 0.5); + color: var(--color-text-primary); + background: var(--color-bg-secondary); } dd { flex: 1 1 auto; - color: $darker-text-color; + color: var(--color-text-secondary); } a { - color: $highlight-text-color; + color: var(--color-text-brand); text-decoration: none; &:hover, @@ -303,16 +310,16 @@ } .verified { - border: 1px solid color.change($valid-value-color, $alpha: 0.5); - background: color.change($valid-value-color, $alpha: 0.25); + border: 1px solid var(--color-border-on-bg-success-softer); + background: var(--color-bg-success-softer); a { - color: $valid-value-color; + color: var(--color-text-success); font-weight: 500; } &__mark { - color: $valid-value-color; + color: var(--color-text-success); } } @@ -327,10 +334,10 @@ .pending-account { &__header { - color: $darker-text-color; + color: var(--color-text-secondary); a { - color: $ui-secondary-color; + color: var(--color-text-primary); text-decoration: none; &:hover, @@ -341,7 +348,7 @@ } strong { - color: $primary-text-color; + color: var(--color-text-primary); font-weight: 700; } @@ -356,7 +363,7 @@ } .batch-table__row--muted { - color: lighten($ui-base-color, 26%); + color: var(--color-text-tertiary); } .batch-table__row--muted .pending-account__header, @@ -365,7 +372,7 @@ &, a, strong { - color: lighten($ui-base-color, 26%); + color: var(--color-text-tertiary); } } @@ -377,12 +384,12 @@ tbody td.accounts-table__extra, &__count, &__count small { - color: lighten($ui-base-color, 26%); + color: var(--color-text-tertiary); } } .batch-table__row--attention { - color: $gold-star; + color: var(--color-text-warning); } .batch-table__row--attention .pending-account__header, @@ -391,7 +398,7 @@ &, a, strong { - color: $gold-star; + color: var(--color-text-warning); } } @@ -399,6 +406,6 @@ tbody td.accounts-table__extra, &__count, &__count small { - color: $gold-star; + color: var(--color-text-warning); } } diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index 781e8511f5186a..9a8c565beb727a 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -1,6 +1,5 @@ @use 'sass:color'; @use 'sass:math'; -@use 'functions' as *; @use 'variables' as *; $no-columns-breakpoint: 890px; @@ -13,6 +12,7 @@ $content-width: 840px; box-sizing: border-box; width: 100%; min-height: 100vh; + min-height: 100dvh; padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); @@ -25,6 +25,7 @@ $content-width: 840px; .sidebar-wrapper { min-height: 100vh; + min-height: 100dvh; overflow: hidden; pointer-events: none; flex: 1 1 auto; @@ -32,7 +33,6 @@ $content-width: 840px; &__inner { display: flex; justify-content: flex-end; - background: var(--background-color); height: 100%; } } @@ -44,8 +44,8 @@ $content-width: 840px; &__toggle { display: none; - background: var(--background-color); - border-bottom: 1px solid lighten($ui-base-color, 4%); + background: var(--color-bg-primary); + border-bottom: 1px solid var(--color-border-primary); align-items: center; &__logo { @@ -59,7 +59,7 @@ $content-width: 840px; &__icon { display: block; - color: $darker-text-color; + color: var(--color-text-secondary); text-decoration: none; flex: 0 0 auto; font-size: 18px; @@ -68,7 +68,7 @@ $content-width: 840px; border-radius: 4px; &:focus { - background: $ui-base-color; + background: var(--color-bg-brand-softer); } .material-close { @@ -122,20 +122,23 @@ $content-width: 840px; align-items: center; gap: 6px; padding: 15px; - color: $darker-text-color; + color: color-mix( + in oklab, + var(--color-text-primary), + var(--color-text-secondary) + ); text-decoration: none; transition: all 200ms linear; transition-property: color, background-color; &:hover { - color: $primary-text-color; + color: var(--color-text-primary); transition: all 100ms linear; transition-property: color, background-color; } } ul { - background: var(--background-color); margin: 0; a { @@ -145,12 +148,12 @@ $content-width: 840px; } .warning a { - color: $gold-star; + color: var(--color-text-warning); font-weight: 700; } .simple-navigation-active-leaf a { - color: $highlight-text-color; + color: var(--color-text-brand); border-bottom: 0; } } @@ -161,6 +164,7 @@ $content-width: 840px; width: 100%; max-width: $content-width; flex: 1 1 auto; + isolation: isolate; } @media screen and (max-width: ($content-width + $sidebar-width)) { @@ -225,7 +229,7 @@ $content-width: 840px; align-items: center; padding: 7px 10px; border-radius: 4px; - color: $darker-text-color; + color: var(--color-text-secondary); text-decoration: none; font-weight: 500; gap: 5px; @@ -238,13 +242,13 @@ $content-width: 840px; &:hover, &:focus, &:active { - background: lighten($ui-base-color, 4%); + background: var(--color-bg-secondary); } &.selected { font-weight: 700; - color: $primary-text-color; - background: $ui-highlight-color; + color: var(--color-text-on-brand-base); + background: var(--color-bg-brand-base); } } } @@ -268,7 +272,7 @@ $content-width: 840px; font-size: 12px; display: block; font-weight: 500; - color: $darker-text-color; + color: var(--color-text-secondary); line-height: 18px; } @@ -279,14 +283,14 @@ $content-width: 840px; } h2 { - color: $secondary-text-color; + color: var(--color-text-primary); font-size: 24px; line-height: 36px; font-weight: 700; } h3 { - color: $secondary-text-color; + color: var(--color-text-primary); font-size: 20px; line-height: 28px; font-weight: 400; @@ -297,37 +301,32 @@ $content-width: 840px; text-transform: uppercase; font-size: 13px; font-weight: 700; - color: $darker-text-color; - padding-bottom: 8px; + color: var(--color-text-secondary); + padding-top: 24px; margin-bottom: 8px; - border-bottom: 1px solid var(--background-border-color); + border-top: 1px solid var(--color-border-primary); } h6 { font-size: 16px; - color: $secondary-text-color; + color: var(--color-text-primary); line-height: 28px; font-weight: 500; } .fields-group h6 { - color: $primary-text-color; + color: var(--color-text-primary); font-weight: 500; } - .directory__tag > a, - .directory__tag > div { - box-shadow: none; - } - .directory__tag h4 { font-size: 18px; font-weight: 700; - color: $primary-text-color; + color: var(--color-text-primary); text-transform: none; - padding-bottom: 0; + padding-top: 0; margin-bottom: 0; - border-bottom: 0; + border-top: 0; .comment { display: block; @@ -337,12 +336,12 @@ $content-width: 840px; &.private-comment { display: block; - color: $darker-text-color; + color: var(--color-text-secondary); } &.public-comment { display: block; - color: $secondary-text-color; + color: var(--color-text-primary); } } } @@ -350,11 +349,11 @@ $content-width: 840px; & > p { font-size: 14px; line-height: 21px; - color: $secondary-text-color; + color: var(--color-text-primary); margin-bottom: 20px; strong { - color: $primary-text-color; + color: var(--color-text-primary); font-weight: 500; @each $lang in $cjk-langs { @@ -369,7 +368,7 @@ $content-width: 840px; width: 100%; height: 0; border: 0; - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); margin: 20px 0; &.spacer { @@ -407,7 +406,7 @@ $content-width: 840px; inset-inline-start: 0; bottom: 0; overflow-y: auto; - background: var(--background-color); + background: var(--color-bg-primary); } } @@ -427,7 +426,7 @@ $content-width: 840px; } ul .simple-navigation-active-leaf a { - border-bottom-color: $ui-highlight-color; + border-bottom-color: var(--color-text-brand); } } } @@ -443,10 +442,10 @@ hr.spacer { body, .admin-wrapper .content { .muted-hint { - color: $darker-text-color; + color: var(--color-text-secondary); a { - color: $highlight-text-color; + color: var(--color-text-brand); } } @@ -466,26 +465,34 @@ body, } .positive-hint { - color: $valid-value-color; + color: var(--color-text-success); font-weight: 500; } .negative-hint { - color: $error-value-color; + color: var(--color-text-error); font-weight: 500; } .neutral-hint { - color: $dark-text-color; + color: var(--color-text-primary); font-weight: 500; } .warning-hint { - color: $gold-star; + color: var(--color-text-warning); font-weight: 500; } } +kbd { + font-family: Courier, monospace; + background-color: var(--color-bg-brand-softer); + padding: 4px; + padding-bottom: 2px; + border-radius: 5px; +} + .filters { display: flex; flex-wrap: wrap; @@ -533,21 +540,21 @@ body, a { display: inline-block; - color: $darker-text-color; + color: var(--color-text-secondary); text-decoration: none; text-transform: uppercase; font-size: 12px; font-weight: 500; - border-bottom: 2px solid $ui-base-color; + border-bottom: 2px solid var(--color-bg-secondary); &:hover { - color: $primary-text-color; - border-bottom: 2px solid lighten($ui-base-color, 5%); + color: var(--color-text-primary); + border-bottom: 2px solid var(--color-bg-tertiary); } &.selected { - color: $highlight-text-color; - border-bottom: 2px solid $ui-highlight-color; + color: var(--color-text-brand); + border-bottom: 2px solid var(--color-text-brand); } } } @@ -587,7 +594,7 @@ body, font-weight: 500; font-size: 14px; line-height: 18px; - color: $secondary-text-color; + color: var(--color-text-primary); @each $lang in $cjk-langs { &:lang(#{$lang}) { @@ -646,7 +653,7 @@ body, font-size: 14px; a { - color: $highlight-text-color; + color: var(--color-text-brand); text-decoration: none; &:hover { @@ -676,25 +683,25 @@ body, line-height: 20px; padding: 15px; padding-inline-start: 15px * 2 + 40px; - background: var(--background-color); - border-right: 1px solid var(--background-border-color); - border-left: 1px solid var(--background-border-color); - border-bottom: 1px solid var(--background-border-color); + background: var(--color-bg-primary); + border-right: 1px solid var(--color-border-primary); + border-left: 1px solid var(--color-border-primary); + border-bottom: 1px solid var(--color-border-primary); position: relative; text-decoration: none; - color: $darker-text-color; + color: var(--color-text-secondary); font-size: 14px; &:first-child { border-top-left-radius: 4px; border-top-right-radius: 4px; - border-top: 1px solid var(--background-border-color); + border-top: 1px solid var(--color-border-primary); } &:last-child { border-bottom-left-radius: 4px; border-bottom-right-radius: 4px; - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); } &__avatar { @@ -710,17 +717,17 @@ body, } &__title { - word-wrap: break-word; + overflow-wrap: break-word; } &__timestamp { - color: $dark-text-color; + color: var(--color-text-tertiary); } a, .username, .target { - color: $secondary-text-color; + color: var(--color-text-primary); text-decoration: none; font-weight: 500; } @@ -739,12 +746,12 @@ body, line-height: 20px; padding: 15px; padding-inline-start: 15px * 2 + 40px; - background: var(--background-color); - border: 1px solid var(--background-border-color); + background: var(--color-bg-primary); + border: 1px solid var(--color-border-primary); border-radius: 4px; position: relative; text-decoration: none; - color: $darker-text-color; + color: var(--color-text-secondary); font-size: 14px; margin-bottom: 15px; @@ -761,17 +768,17 @@ body, } &__title { - word-wrap: break-word; + overflow-wrap: break-word; } &__timestamp { - color: $dark-text-color; + color: var(--color-text-secondary); } &:hover, &:focus, &:active { - background: $ui-base-color; + background: var(--color-bg-primary); } } @@ -780,10 +787,10 @@ a.name-tag, a.inline-name-tag, .inline-name-tag { text-decoration: none; - color: $secondary-text-color; + color: var(--color-text-primary); &:hover { - color: $highlight-text-color; + color: var(--color-text-brand); } .username { @@ -793,7 +800,7 @@ a.inline-name-tag, &.suspended { .username { text-decoration: line-through; - color: lighten($error-red, 12%); + color: var(--color-text-error); } .avatar { @@ -826,18 +833,18 @@ a.name-tag, .speech-bubble { margin-bottom: 20px; - border-inline-start: 4px solid $ui-highlight-color; + border-inline-start: 4px solid var(--color-text-brand); &.positive { - border-left-color: $success-green; + border-color: var(--color-text-success); } &.negative { - border-left-color: lighten($error-red, 12%); + border-color: var(--color-text-error); } &.warning { - border-left-color: $gold-star; + border-color: var(--color-text-warning); } &__bubble { @@ -850,7 +857,7 @@ a.name-tag, font-weight: 500; a { - color: $darker-text-color; + color: var(--color-text-secondary); } } @@ -860,13 +867,13 @@ a.name-tag, } time { - color: $dark-text-color; + color: var(--color-text-tertiary); } } .report-card { - background: var(--background-color); - border: 1px solid var(--background-border-color); + background: var(--color-bg-primary); + border: 1px solid var(--color-border-primary); border-radius: 4px; margin-bottom: 20px; @@ -888,7 +895,7 @@ a.name-tag, &__stats { flex: 0 0 auto; font-weight: 500; - color: $darker-text-color; + color: var(--color-text-secondary); text-transform: uppercase; text-align: end; @@ -899,12 +906,12 @@ a.name-tag, &:focus, &:hover, &:active { - color: $highlight-text-color; + color: var(--color-text-brand); } } .red { - color: $error-value-color; + color: var(--color-text-error); } } } @@ -913,7 +920,7 @@ a.name-tag, &__item { display: flex; justify-content: flex-start; - border-top: 1px solid var(--background-border-color); + border-top: 1px solid var(--color-border-primary); &__reported-by, &__assigned { @@ -921,7 +928,7 @@ a.name-tag, flex: 0 0 auto; box-sizing: border-box; width: 150px; - color: $darker-text-color; + color: var(--color-text-secondary); &, .username { @@ -947,10 +954,10 @@ a.name-tag, width: 100%; padding: 15px; text-decoration: none; - color: $darker-text-color; + color: var(--color-text-secondary); &:hover { - color: $highlight-text-color; + color: var(--color-text-brand); } } } @@ -987,18 +994,18 @@ a.name-tag, .account__header__fields, .account__header__content { - background: var(--background-color); - border: 1px solid var(--background-border-color); + background: var(--color-bg-primary); + border: 1px solid var(--color-border-primary); border-radius: 4px; height: 100%; } .account__header__fields { margin: 0; - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); a { - color: $highlight-text-color; + color: var(--color-text-brand); } dl:first-child .verified { @@ -1006,14 +1013,14 @@ a.name-tag, } .verified a { - color: $valid-value-color; + color: var(--color-text-success); } } .account__header__content { box-sizing: border-box; padding: 20px; - color: $primary-text-color; + color: var(--color-text-primary); } } @@ -1024,16 +1031,12 @@ a.name-tag, .applications-list__item, .filters-list__item { padding: 15px 0; - background: var(--background-color); - border: 1px solid var(--background-border-color); + background: var(--color-bg-primary); + border: 1px solid var(--color-border-primary); border-radius: 4px; margin-top: 15px; } -.user-role { - color: var(--user-role-accent); -} - .applications-list { .icon { vertical-align: middle; @@ -1042,13 +1045,13 @@ a.name-tag, .announcements-list, .filters-list { - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); border-radius: 4px; border-bottom: none; &__item { padding: 15px 0; - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); &__title { padding: 0 15px; @@ -1056,12 +1059,12 @@ a.name-tag, font-weight: 500; font-size: 18px; line-height: 1.5; - color: $secondary-text-color; + color: var(--color-text-primary); text-decoration: none; margin-bottom: 10px; &:hover { - color: $highlight-text-color; + color: var(--color-text-brand); } .account-role { @@ -1077,7 +1080,7 @@ a.name-tag, &:hover, &:focus, &:active { - color: $primary-text-color; + color: var(--color-text-primary); } } @@ -1094,7 +1097,7 @@ a.name-tag, &__meta { padding: 0 15px; - color: $dark-text-color; + color: var(--color-text-tertiary); a { color: inherit; @@ -1137,11 +1140,11 @@ a.name-tag, &.expired { .expiration { - color: lighten($error-red, 12%); + color: var(--color-text-error); } .permissions-list__item__icon { - color: $dark-text-color; + color: var(--color-text-secondary); } } } @@ -1173,7 +1176,7 @@ a.name-tag, &__table { &__number { - color: var(--background-color); + color: var(--color-bg-primary); padding: 10px; } @@ -1195,23 +1198,26 @@ a.name-tag, &__label { font-weight: 700; - color: $darker-text-color; + color: var(--color-text-secondary); } &__box { box-sizing: border-box; - background: var(--background-color); - padding: 10px; - font-weight: 500; - color: $primary-text-color; width: 52px; margin: 1px; + padding: 10px; + font-weight: 500; + color: var(--color-text-primary); + background: var(--color-bg-primary); @for $i from 0 through 10 { &--#{10 * $i} { - background-color: rgba( - $ui-highlight-color, - 1 * (math.div(max(1, $i), 10)) + @if $i > 5 { + color: var(--color-text-on-brand-base); + } + + background-color: rgb( + from var(--color-bg-brand-base) r g b / #{math.div(max(1, $i), 10)} ); } } @@ -1222,9 +1228,9 @@ a.name-tag, .sparkline { display: block; text-decoration: none; - background: var(--background-color); + background: var(--color-bg-primary); border-radius: 4px; - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); padding: 0; position: relative; padding-bottom: 55px + 20px; @@ -1242,22 +1248,22 @@ a.name-tag, margin-inline-end: 10px; font-weight: 500; font-size: 28px; - color: $primary-text-color; + color: var(--color-text-primary); } &__change { display: block; font-weight: 500; font-size: 18px; - color: $darker-text-color; + color: var(--color-text-secondary); margin-bottom: -3px; &.positive { - color: $valid-value-color; + color: var(--color-text-success); } &.negative { - color: $error-value-color; + color: var(--color-text-error); } } } @@ -1266,7 +1272,7 @@ a.name-tag, padding: 0 20px; padding-bottom: 10px; text-transform: uppercase; - color: $darker-text-color; + color: var(--color-text-secondary); font-weight: 500; } @@ -1281,12 +1287,12 @@ a.name-tag, } path:first-child { - fill: color.change($highlight-text-color, $alpha: 0.25) !important; + fill: var(--color-graph-primary-fill) !important; fill-opacity: 1 !important; } path:last-child { - stroke: lighten($highlight-text-color, 6%) !important; + stroke: var(--color-graph-primary-stroke) !important; fill: none !important; } } @@ -1296,17 +1302,17 @@ a.sparkline { &:hover, &:focus, &:active { - background: $ui-base-color; + background: var(--color-bg-brand-softer); } } .skeleton { - background-color: var(--background-color); + background-color: var(--color-bg-primary); background-image: linear-gradient( 90deg, - lighten($ui-base-color, 8%), - lighten($ui-base-color, 12%), - lighten($ui-base-color, 8%) + var(--color-bg-primary), + var(--color-bg-secondary), + var(--color-bg-primary) ); background-size: 200px 100%; background-repeat: no-repeat; @@ -1315,6 +1321,10 @@ a.sparkline { line-height: 1; width: 100%; animation: skeleton 1.2s ease-in-out infinite; + + .reduce-motion & { + animation: none; + } } @keyframes skeleton { @@ -1333,7 +1343,7 @@ a.sparkline { } &__item { - border-bottom: 1px solid lighten($ui-base-color, 4%); + border-bottom: 1px solid var(--color-border-primary); &__key { font-weight: 500; @@ -1342,7 +1352,7 @@ a.sparkline { &__value { text-align: end; - color: $darker-text-color; + color: var(--color-text-secondary); padding: 11px 10px; } @@ -1351,14 +1361,13 @@ a.sparkline { width: 8px; height: 8px; border-radius: 50%; - background: $ui-highlight-color; + background: var(--color-text-brand); margin-inline-end: 10px; @for $i from 0 through 10 { &--#{10 * $i} { - background-color: rgba( - $ui-highlight-color, - 1 * (math.div(max(1, $i), 10)) + background-color: rgb( + from var(--color-text-brand) r g b / #{math.div(max(1, $i), 10)} ); } } @@ -1369,11 +1378,11 @@ a.sparkline { } &.negative { - color: $error-value-color; + color: var(--color-text-error); font-weight: 700; .dimension__item__value { - color: $error-value-color; + color: var(--color-text-error); } } } @@ -1381,12 +1390,12 @@ a.sparkline { .report-reason-selector { border-radius: 4px; - background: var(--background-color); + background: var(--color-bg-primary); margin-bottom: 20px; &__category { cursor: pointer; - border-bottom: 1px solid darken($ui-base-color, 8%); + border-bottom: 1px solid var(--color-border-primary); &__label { padding: 15px; @@ -1411,12 +1420,12 @@ a.sparkline { .report-header { display: grid; - grid-gap: 15px; + gap: 15px; grid-template-columns: minmax(0, 1fr) 300px; &__details { &__item { - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); padding: 15px 0; &:last-child { @@ -1447,7 +1456,7 @@ a.sparkline { .account-card { border-radius: 4px; - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); position: relative; &__warning-badge { @@ -1459,7 +1468,7 @@ a.sparkline { background: url('@/images/warning-stripes.svg') repeat-y left, url('@/images/warning-stripes.svg') repeat-y right, - var(--background-color); + var(--color-bg-primary); } &__permalink { @@ -1478,7 +1487,7 @@ a.sparkline { width: 100%; height: 100%; object-fit: cover; - background: darken($ui-base-color, 8%); + background: var(--color-bg-secondary); } } @@ -1496,21 +1505,21 @@ a.sparkline { margin: 0; width: 56px; height: 56px; - background-color: darken($ui-base-color, 8%); + background-color: var(--color-bg-secondary); border-radius: 8px; - border: 1px solid $ui-base-color; + border: 1px solid var(--color-border-media); } } .display-name { - color: $darker-text-color; + color: var(--color-text-secondary); padding-bottom: 15px; font-size: 15px; line-height: 20px; bdi { display: block; - color: $primary-text-color; + color: var(--color-text-primary); font-weight: 700; } } @@ -1521,7 +1530,7 @@ a.sparkline { margin: 8px 0; overflow: hidden; text-overflow: ellipsis; - word-wrap: break-word; + overflow-wrap: break-word; max-height: 21px * 2; position: relative; font-size: 15px; @@ -1539,7 +1548,7 @@ a.sparkline { } a { - color: $secondary-text-color; + color: var(--color-text-primary); text-decoration: none; unicode-bidi: isolate; @@ -1590,14 +1599,14 @@ a.sparkline { &__item { padding: 15px 0; text-align: center; - color: $primary-text-color; + color: var(--color-text-primary); font-weight: 600; font-size: 15px; line-height: 21px; small { display: block; - color: $darker-text-color; + color: var(--color-text-secondary); font-weight: 400; font-size: 13px; line-height: 18px; @@ -1610,11 +1619,11 @@ a.sparkline { margin-bottom: 20px; &__item { - background: var(--background-color); + background: var(--color-bg-primary); position: relative; padding: 15px; padding-inline-start: 15px * 2 + 40px; - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); &:first-child { border-top-left-radius: 4px; @@ -1636,13 +1645,13 @@ a.sparkline { } &__header { - color: $darker-text-color; + color: var(--color-text-secondary); font-size: 15px; line-height: 20px; margin-bottom: 4px; .username { - color: $primary-text-color; + color: var(--color-text-primary); font-weight: 500; margin-inline-end: 5px; @@ -1659,7 +1668,7 @@ a.sparkline { } a.timestamp { - color: $darker-text-color; + color: var(--color-text-secondary); text-decoration: none; &:hover, @@ -1678,9 +1687,9 @@ a.sparkline { &__content { font-size: 15px; line-height: 20px; - word-wrap: break-word; + overflow-wrap: break-word; font-weight: 400; - color: $primary-text-color; + color: var(--color-text-primary); p { margin-bottom: 20px; @@ -1693,7 +1702,7 @@ a.sparkline { } a { - color: $highlight-text-color; + color: var(--color-text-brand); text-decoration: none; &:hover { @@ -1737,7 +1746,7 @@ a.sparkline { &__description { padding: 15px; font-size: 14px; - color: $dark-text-color; + color: var(--color-text-tertiary); } } @@ -1765,7 +1774,7 @@ a.sparkline { float: right; a { - color: $ui-highlight-color; + color: var(--color-text-brand); text-decoration: none; &:hover, @@ -1780,16 +1789,16 @@ a.sparkline { padding: 15px; font-size: 15px; line-height: 20px; - word-wrap: break-word; + overflow-wrap: break-word; font-weight: 400; - color: $primary-text-color; + color: var(--color-text-primary); box-sizing: border-box; min-height: 100%; - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); border-radius: 4px; a { - color: $highlight-text-color; + color: var(--color-text-brand); text-decoration: none; &:hover { @@ -1814,34 +1823,34 @@ a.sparkline { list-style: disc; padding-inline-start: 15px; margin-bottom: 20px; - color: $darker-text-color; + color: var(--color-text-secondary); &:last-child { margin-bottom: 0; } &__text { - color: $primary-text-color; + color: var(--color-text-primary); } } &__statuses-list { border-radius: 4px; - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); font-size: 13px; line-height: 18px; overflow: hidden; &__item { padding: 16px; - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); &:last-child { border-bottom: 0; } &__meta { - color: $darker-text-color; + color: var(--color-text-secondary); } a { @@ -1878,16 +1887,16 @@ a.sparkline { flex: 0 0 auto; width: 4px; height: 21px; - background: lighten($ui-base-color, 8%); + background: var(--color-bg-secondary); margin: 0 2px; border-radius: 2px; &.positive { - background: $valid-value-color; + background: var(--color-bg-success-base); } &.negative { - background: $error-value-color; + background: var(--color-bg-error-base); } } } @@ -1916,9 +1925,9 @@ a.sparkline { align-items: center; width: calc(1.375rem + 1px); height: calc(1.375rem + 1px); - background: $ui-base-color; - border: 1px solid $highlight-text-color; - color: $highlight-text-color; + background: var(--color-bg-primary); + border: 1px solid var(--color-text-brand); + color: var(--color-text-brand); border-radius: 8px; } @@ -1926,7 +1935,7 @@ a.sparkline { position: absolute; content: ''; width: 1px; - background: $highlight-text-color; + background: var(--color-text-brand); bottom: 0; top: calc(1.875rem + 1px); inset-inline-start: 0.6875rem; @@ -1944,14 +1953,14 @@ a.sparkline { &__entry { h5 { font-weight: 500; - color: $primary-text-color; + color: var(--color-text-primary); line-height: 25px; margin-bottom: 16px; } .status { - border: 1px solid lighten($ui-base-color, 4%); - background: $ui-base-color; + border: 1px solid var(--color-border-primary); + background: var(--color-bg-secondary); border-radius: 4px; } } @@ -1960,27 +1969,148 @@ a.sparkline { .status__card { padding: 15px; border-radius: 4px; - background: $ui-base-color; font-size: 15px; line-height: 20px; - word-wrap: break-word; + overflow-wrap: break-word; font-weight: 400; - border: 1px solid lighten($ui-base-color, 4%); - color: $primary-text-color; + border: 1px solid var(--color-border-primary); + color: var(--color-text-primary); box-sizing: border-box; min-height: 100%; + &.status--has-quote { + .quote-inline { + display: none; + } + } + + .status__quote & { + // Remove the border from the .status__card within .status__quote + border: none; + + .display-name__account { + line-height: inherit; + } + + .status__avatar, + .status__avatar .account__avatar { + width: 32px; + height: 32px; + } + } + .status__prepend { padding: 0 0 15px; gap: 4px; align-items: center; } - .status__content { - padding-top: 0; - + > details { summary { - display: list-item; + display: block; + box-sizing: border-box; + color: var(--color-text-primary); + background: var(--color-bg-brand-softer); + border: 1px solid var(--color-border-on-bg-brand-softer); + border-radius: 8px; + padding: 8px 13px; + position: relative; + font-size: 15px; + line-height: 22px; + cursor: pointer; + + &::after { + content: attr(data-show, 'Show more'); + margin-top: 8px; + display: block; + font-size: 15px; + line-height: 20px; + color: var(--color-text-brand); + cursor: pointer; + border: 0; + background: transparent; + padding: 0; + text-decoration: none; + font-weight: 500; + } + + &:hover, + &:focus-visible { + &::after { + text-decoration: underline !important; + } + } + } + + &[open] summary { + margin-bottom: 16px; + + &::after { + content: attr(data-hide, 'Hide post'); + } + } + } + + .preview-card { + position: relative; + max-width: 566px; + + .status-card__image { + &--video { + aspect-ratio: 16 / 9; + } + + &--large { + aspect-ratio: 1.91 / 1; + } + + aspect-ratio: 1; + } + + .spoiler-button__overlay__label { + outline: 1px solid var(--color-border-media); + } + + .hide-button { + // Toggled to appear when the preview-card is unblurred: + display: none; + position: absolute; + top: 5px; + right: 5px; + color: var(--color-text-on-media); + background: var(--color-bg-media); + backdrop-filter: $backdrop-blur-filter; + outline: 1px solid var(--color-border-media); + border: 0; + padding: 3px 12px; + border-radius: 99px; + font-size: 14px; + font-weight: 700; + line-height: 20px; + + &:hover, + &:focus { + background-color: rgb(from var(--color-bg-media-base) r g b / 90%); + } + } + + &.preview-card--image-visible { + .hide-button { + display: block; + } + + .spoiler-button__overlay, + .status-card__image-preview { + display: none; + } + } + } + + .detailed-status__meta { + .detailed-status__application, + .detailed-status__datetime, + .detailed-status__link { + color: inherit; } } } @@ -1988,23 +2118,23 @@ a.sparkline { .admin { &__terms-of-service { &__container { - background: var(--surface-background-color); + background: var(--color-bg-tertiary); border-radius: 8px; - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); overflow: hidden; &__header { padding: 16px; font-size: 14px; line-height: 20px; - color: $secondary-text-color; + color: var(--color-text-primary); display: flex; align-items: center; gap: 12px; } &__body { - background: var(--background-color); + background: var(--color-bg-primary); padding: 16px; overflow-y: scroll; height: 30vh; @@ -2013,7 +2143,7 @@ a.sparkline { &__history { & > li { - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); &:last-child { border-bottom: 0; @@ -2051,14 +2181,14 @@ a.sparkline { width: 8px; height: 8px; border-radius: 50%; - background: $dark-text-color; + background: var(--color-text-tertiary); } &.success { - color: $valid-value-color; + color: var(--color-text-success); .dot-indicator__indicator { - background-color: $valid-value-color; + background-color: var(--color-bg-success-base); } } } diff --git a/app/javascript/styles/mastodon/annual_reports.scss b/app/javascript/styles/mastodon/annual_reports.scss deleted file mode 100644 index 96500a18bb6a16..00000000000000 --- a/app/javascript/styles/mastodon/annual_reports.scss +++ /dev/null @@ -1,342 +0,0 @@ -@use 'variables' as *; - -:root { - --indigo-1: #17063b; - --indigo-2: #2f0c7a; - --indigo-3: #562cfc; - --indigo-5: #858afa; - --indigo-6: #cccfff; - --lime: #baff3b; - --goldenrod-2: #ffc954; -} - -.annual-report { - flex: 0 0 auto; - background: var(--indigo-1); - padding: 24px; - - &__header { - margin-bottom: 16px; - - h1 { - font-size: 25px; - font-weight: 600; - line-height: 30px; - color: var(--lime); - margin-bottom: 8px; - } - - p { - font-size: 16px; - font-weight: 600; - line-height: 20px; - color: var(--indigo-6); - } - } - - &__bento { - display: grid; - gap: 8px; - grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr); - grid-template-rows: minmax(0, auto) minmax(0, 1fr) minmax(0, auto) minmax( - 0, - auto - ); - - &__box { - padding: 16px; - border-radius: 8px; - background: var(--indigo-2); - color: var(--indigo-5); - } - } - - &__summary { - &__most-boosted-post { - grid-column: span 2; - grid-row: span 2; - padding: 0; - - .status__content, - .content-warning { - color: var(--indigo-6); - } - - .detailed-status { - border: 0; - } - - .content-warning { - border: 0; - background: var(--indigo-1); - - .link-button { - color: var(--indigo-5); - } - } - - .detailed-status__meta__line { - border-bottom-color: var(--indigo-3); - } - - .detailed-status__meta { - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - } - - .detailed-status__meta, - .poll__footer, - .poll__link, - .detailed-status .logo, - .detailed-status__display-name { - color: var(--indigo-5); - } - - .detailed-status__meta .animated-number, - .detailed-status__display-name strong { - color: var(--indigo-6); - } - - .poll__chart { - background-color: var(--indigo-3); - - &.leading { - background-color: var(--goldenrod-2); - } - } - - .status-card, - .hashtag-bar { - display: none; - } - } - - &__followers { - grid-column: span 1; - text-align: center; - position: relative; - overflow: hidden; - padding-block-start: 24px; - padding-block-end: 24px; - - --sparkline-gradient-top: rgba(86, 44, 252, 50%); - --sparkline-gradient-bottom: rgba(86, 44, 252, 0%); - - &__foreground { - width: 100%; - height: 100%; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 8px; - position: relative; - z-index: 1; - } - - &__number { - font-size: 31px; - font-weight: 600; - line-height: 37px; - color: var(--lime); - } - - &__label { - font-size: 14px; - font-weight: 600; - line-height: 17px; - color: var(--indigo-6); - } - - &__footnote { - display: block; - font-weight: 400; - opacity: 0.5; - } - - svg { - position: absolute; - bottom: 0; - inset-inline-end: 0; - pointer-events: none; - z-index: 0; - height: 70%; - width: auto; - - path:first-child { - fill: url('#gradient') !important; - fill-opacity: 1 !important; - } - - path:last-child { - stroke: var(--indigo-3) !important; - fill: none !important; - } - } - } - - &__archetype { - grid-column: span 1; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - text-align: center; - gap: 8px; - padding: 0; - - img { - display: block; - width: 100%; - height: auto; - border-radius: 8px; - } - - &__label { - padding: 16px; - padding-bottom: 8px; - font-size: 14px; - line-height: 17px; - font-weight: 600; - color: var(--lime); - } - } - - &__most-used-app { - grid-column: span 1; - text-align: center; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 8px; - box-sizing: border-box; - - &__label { - font-size: 14px; - line-height: 17px; - font-weight: 600; - color: var(--indigo-6); - } - - &__icon { - font-size: 14px; - line-height: 17px; - font-weight: 600; - color: var(--goldenrod-2); - } - } - - &__percentile { - grid-row: span 2; - display: flex; - flex-direction: column; - align-items: center; - justify-content: space-between; - text-align: center; - text-wrap: balance; - padding: 16px 8px; - - &__label { - font-size: 14px; - line-height: 17px; - } - - &__number { - font-size: 54px; - font-weight: 600; - line-height: 73px; - color: var(--goldenrod-2); - } - - &__footnote { - font-size: 11px; - line-height: 14px; - opacity: 0.5; - } - } - - &__new-posts { - grid-column: span 2; - text-align: center; - position: relative; - overflow: hidden; - - &__label { - font-size: 20px; - font-weight: 600; - line-height: 24px; - color: var(--indigo-6); - z-index: 1; - position: relative; - } - - &__number { - font-size: 76px; - font-weight: 600; - line-height: 91px; - color: var(--goldenrod-2); - z-index: 1; - position: relative; - } - - svg { - position: absolute; - inset-inline-start: -7px; - top: -4px; - z-index: 0; - } - } - - &__most-used-hashtag { - grid-column: span 2; - text-align: center; - overflow: hidden; - - &__hashtag { - font-size: 42px; - font-weight: 600; - line-height: 58px; - color: var(--indigo-6); - margin-inline-start: -100%; - margin-inline-end: -100%; - } - - &__label { - font-size: 14px; - font-weight: 600; - line-height: 17px; - } - } - } -} - -.annual-report-modal { - max-width: 600px; - background: var(--indigo-1); - border-radius: 16px; - display: flex; - flex-direction: column; - overflow-y: auto; - - .loading-indicator .circular-progress { - color: var(--lime); - } - - @media screen and (max-width: $no-columns-breakpoint) { - border-bottom: 0; - border-radius: 16px 16px 0 0; - } -} - -.notification-group--annual-report { - .notification-group__icon { - color: var(--lime); - } - - .notification-group__main .link-button { - font-weight: 500; - color: var(--lime); - } -} diff --git a/app/javascript/styles/mastodon/basics.scss b/app/javascript/styles/mastodon/basics.scss index 0dce0b9b3eaa38..8cc06a50b30d49 100644 --- a/app/javascript/styles/mastodon/basics.scss +++ b/app/javascript/styles/mastodon/basics.scss @@ -1,5 +1,25 @@ @use 'variables' as *; -@use 'functions' as *; + +html { + color: var(--color-text-primary); + background: var(--color-bg-ambient); + + &.custom-scrollbars { + scrollbar-color: var(--color-text-secondary) var(--color-bg-secondary); + } + + --outline-focus-default: 2px solid var(--color-text-brand); + --avatar-border-radius: 8px; + --max-media-height-small: 460px; + --max-media-height-large: 566px; + + // Variable for easily inverting directional UI elements, + --text-x-direction: 1; + + &.rtl { + --text-x-direction: -1; + } +} html.has-modal { &, @@ -17,11 +37,11 @@ html.has-modal { body { font-family: $font-sans-serif, sans-serif; - background: var(--background-color); + background: var(--color-bg-ambient); font-size: 13px; line-height: 18px; font-weight: 400; - color: $primary-text-color; + color: var(--color-text-primary); text-rendering: optimizelegibility; // Disable kerning for Japanese text to preserve monospaced alignment for readability @@ -33,7 +53,7 @@ body { -webkit-tap-highlight-color: rgba(0, 0, 0, 0%); -webkit-tap-highlight-color: transparent; - &.system-font { + .system-font & { // system-ui => standard property (Chrome/Android WebView 56+, Opera 43+, Safari 11+) // -apple-system => Safari <11 specific // BlinkMacSystemFont => Chrome <56 on macOS specific @@ -69,6 +89,7 @@ body { &.layout-single-column { height: auto; min-height: 100vh; + min-height: 100dvh; overflow-y: scroll; } @@ -117,6 +138,7 @@ body { &.admin { padding: 0; + background: var(--color-bg-primary); } &.error { @@ -156,7 +178,7 @@ body { a { &:focus { border-radius: 4px; - outline: $ui-button-focus-outline; + outline: var(--outline-focus-default); } &:focus:not(:focus-visible) { @@ -185,7 +207,8 @@ button { } & > noscript { - height: 100vh; + min-height: 100vh; + min-height: 100dvh; } } @@ -193,6 +216,7 @@ button { &, & > div { min-height: 100vh; + min-height: 100dvh; } } @@ -209,7 +233,7 @@ button { font-size: 16px; font-weight: 400; line-height: 1.7; - color: lighten($error-red, 4%); + color: var(--color-text-error); text-align: center; & > div { @@ -225,7 +249,7 @@ button { } a { - color: $highlight-text-color; + color: var(--color-text-brand); &:hover, &:focus, @@ -235,11 +259,11 @@ button { } &__footer { - color: $dark-text-color; + color: var(--color-text-secondary); font-size: 13px; a { - color: $dark-text-color; + color: var(--color-text-secondary); } } @@ -247,7 +271,7 @@ button { display: inline; border: 0; background: transparent; - color: $dark-text-color; + color: var(--color-text-secondary); font: inherit; padding: 0; margin: 0; @@ -264,7 +288,7 @@ button { } &.copied { - color: $valid-value-color; + color: var(--mas-status-success-color); transition: none; } } diff --git a/app/javascript/styles/mastodon/branding.scss b/app/javascript/styles/mastodon/branding.scss index 8e8dd3530bc2be..a7cc9c500e2432 100644 --- a/app/javascript/styles/mastodon/branding.scss +++ b/app/javascript/styles/mastodon/branding.scss @@ -1,5 +1,5 @@ @use 'variables' as *; .logo { - color: $primary-text-color; + color: var(--color-text-primary); } diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index b390a8a8e54d45..47f17f6e79b74e 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -1,6 +1,5 @@ @use 'sass:color'; @use 'variables' as *; -@use 'functions' as *; @use 'mixins' as *; .app-body { @@ -17,7 +16,7 @@ } .inline-alert { - color: $valid-value-color; + color: var(--color-text-success); font-weight: 400; .no-reduce-motion & { @@ -29,7 +28,7 @@ display: block; font-size: 15px; line-height: 20px; - color: $highlight-text-color; + color: var(--color-text-brand); border: 0; background: transparent; padding: 0; @@ -37,7 +36,7 @@ text-decoration: none; &--destructive { - color: $error-value-color; + color: var(--color-text-error); } &:hover, @@ -46,19 +45,19 @@ } &:disabled { - color: $ui-primary-color; + color: var(--color-text-primary); cursor: default; } &:focus-visible { - outline: $ui-button-icon-focus-outline; + outline: var(--outline-focus-default); } } .help-button { - background: $ui-button-background-color; + color: var(--color-text-on-brand-base); + background: var(--color-bg-brand-base); border: 0; - color: $ui-button-color; border-radius: 20px; cursor: pointer; width: 24px; @@ -70,11 +69,11 @@ &:active, &:focus, &:hover { - background-color: $ui-button-focus-background-color; + background: var(--color-bg-brand-base-hover); } &:focus-visible { - outline: $ui-button-icon-focus-outline; + outline: var(--outline-focus-default); } .icon { @@ -84,11 +83,11 @@ } .button { - background-color: $ui-button-background-color; + background-color: var(--color-bg-brand-base); border: 10px none; border-radius: 4px; box-sizing: border-box; - color: $ui-button-color; + color: var(--color-text-on-brand-base); cursor: pointer; display: inline-flex; align-items: center; @@ -111,11 +110,12 @@ &:active, &:focus, &:hover { - background-color: $ui-button-focus-background-color; + background-color: var(--color-bg-brand-base-hover); } &:focus-visible { - outline: $ui-button-icon-focus-outline; + outline: 2px solid var(--color-bg-brand-base); + outline-offset: 2px; } &--compact { @@ -127,13 +127,13 @@ } &--dangerous { - background-color: var(--error-background-color); - color: var(--on-error-color); + background-color: var(--color-bg-error-base); + color: var(--color-text-on-error-base); &:active, &:focus, &:hover { - background-color: var(--error-active-background-color); + background-color: var(--color-bg-error-base-hover); transition: none; } } @@ -142,14 +142,16 @@ &:active, &:focus, &:hover { - background-color: $ui-button-destructive-focus-background-color; + color: var(--color-text-on-error-base); + background-color: var(--color-bg-error-base); transition: none; } } &:disabled, &.disabled { - background-color: $ui-button-disabled-color; + color: var(--color-text-on-disabled); + background-color: var(--color-bg-disabled); cursor: not-allowed; } @@ -158,21 +160,22 @@ } &.copied { - background: $valid-value-color; + color: var(--color-text-on-success-base); + background-color: var(--color-bg-success-base); transition: none; } &.button-secondary { - color: $highlight-text-color; + color: var(--color-text-brand); background: transparent; padding: 6px 17px; - border: 1px solid $highlight-text-color; + border: 1px solid var(--color-text-brand); &:active, &:focus, &:hover { - border-color: lighten($highlight-text-color, 4%); - color: lighten($highlight-text-color, 4%); + border-color: var(--color-text-brand); + color: var(--color-text-brand); background-color: transparent; text-decoration: none; } @@ -181,28 +184,27 @@ &:active, &:focus, &:hover { - border-color: $ui-button-destructive-focus-background-color; - color: $ui-button-destructive-focus-background-color; + border-color: var(--color-text-error); + color: var(--color-text-error); } } &:disabled, &.disabled { - opacity: 0.7; - border-color: $ui-button-disabled-color; - color: $ui-button-disabled-color; + border-color: var(--color-text-disabled); + color: var(--color-text-disabled); &:active, &:focus, &:hover { - border-color: $ui-button-disabled-color; - color: $ui-button-disabled-color; + border-color: var(--color-text-disabled); + color: var(--color-text-disabled); } } } &.button--plain { - color: $highlight-text-color; + color: var(--color-text-brand); background: transparent; padding: 6px; @@ -216,7 +218,7 @@ &:focus, &:hover { border-color: transparent; - color: lighten($highlight-text-color, 4%); + color: var(--color-text-brand-soft); background-color: transparent; text-decoration: none; } @@ -225,57 +227,13 @@ &.disabled { opacity: 0.7; border-color: transparent; - color: $ui-button-disabled-color; + color: var(--color-text-disabled); &:active, &:focus, &:hover { border-color: transparent; - color: $ui-button-disabled-color; - } - } - } - - &.button-tertiary { - background: transparent; - padding: 6px 17px; - color: $ui-button-tertiary-color; - border: 1px solid $ui-button-tertiary-border-color; - - &:active, - &:focus, - &:hover { - background-color: $ui-button-tertiary-focus-background-color; - color: $ui-button-tertiary-focus-color; - border: 0; - padding: 7px 18px; - } - - &:disabled { - opacity: 0.5; - } - - &.button--confirmation { - color: $valid-value-color; - border-color: $valid-value-color; - - &:active, - &:focus, - &:hover { - background: $valid-value-color; - color: $primary-text-color; - } - } - - &.button--destructive { - color: $error-value-color; - border-color: $error-value-color; - - &:active, - &:focus, - &:hover { - background: $error-value-color; - color: $primary-text-color; + color: var(--color-text-disabled); } } } @@ -323,12 +281,17 @@ } .icon-button { + --default-icon-color: var(--color-text-secondary); + --default-bg-color: transparent; + --hover-icon-color: var(--color-text-primary); + --hover-bg-color: var(--color-bg-brand-softer); + display: inline-flex; - color: $action-button-color; + color: var(--default-icon-color); border: 0; padding: 0; border-radius: 4px; - background: transparent; + background: var(--default-bg-color); cursor: pointer; align-items: center; justify-content: center; @@ -345,66 +308,41 @@ &:hover, &:active, &:focus-visible { - color: lighten($action-button-color, 7%); - background-color: color.change($action-button-color, $alpha: 0.15); + color: var(--hover-icon-color); + background-color: var(--hover-bg-color); } &:focus-visible { - outline: $ui-button-icon-focus-outline; + outline: 2px solid var(--color-text-brand); } &.disabled { - color: darken($action-button-color, 13%); - background-color: transparent; + color: var(--color-text-disabled); + background-color: var(--default-bg-color); cursor: default; } &.inverted { - color: $lighter-text-color; - - &:hover, - &:active, - &:focus-visible { - color: darken($lighter-text-color, 7%); - background-color: color.change($lighter-text-color, $alpha: 0.15); - } - - &:focus-visible { - outline: $ui-button-icon-focus-outline; - } - - &.disabled { - color: lighten($lighter-text-color, 7%); - background-color: transparent; - } + --default-icon-color: var(--color-text-primary); + --hover-icon-color: var(--color-text-secondary); } &.active { - color: $highlight-text-color; - - &:hover, - &:active, - &:focus-visible { - color: $highlight-text-color; - background-color: transparent; - } - - &.disabled { - color: lighten($highlight-text-color, 13%); - } + --default-icon-color: var(--color-text-brand); + --hover-icon-color: var(--color-text-brand); + --hover-bg-color: transparent; } &.overlayed { + --default-icon-color: rgb(from var(--color-text-on-media) r g b / 70%); + --default-bg-color: var(--color-bg-media); + --hover-icon-color: var(--color-text-brand); + --hover-bg-color: rgb(from var(--color-bg-media-base) r g b / 90%); + box-sizing: content-box; - background: color.change($black, $alpha: 0.65); backdrop-filter: $backdrop-blur-filter; - color: color.change($white, $alpha: 0.7); border-radius: 4px; padding: 2px; - - &:hover { - background: color.change($black, $alpha: 0.9); - } } &--with-counter { @@ -423,55 +361,14 @@ } &.copied { - border-color: $valid-value-color; - color: $valid-value-color; + color: var(--color-text-success); transition: none; - background-color: color.change($valid-value-color, $alpha: 0.15); - } -} - -.text-icon-button { - color: $lighter-text-color; - border: 0; - border-radius: 4px; - background: transparent; - cursor: pointer; - font-weight: 600; - font-size: 11px; - padding: 0 3px; - line-height: 27px; - white-space: nowrap; - - &:hover, - &:active, - &:focus { - color: darken($lighter-text-color, 7%); - background-color: color.change($lighter-text-color, $alpha: 0.15); - } - - &:focus-visible { - outline: $ui-button-icon-focus-outline; - } - - &.disabled { - color: lighten($lighter-text-color, 20%); - background-color: transparent; - cursor: default; - } - - &.active { - color: $highlight-text-color; - - &:hover, - &:active, - &:focus { - color: $highlight-text-color; - background-color: transparent; - } + background-color: var(--color-bg-success-softer); + border-color: var(--color-border-on-bg-brand-softer); } } -[data-popper-placement] { +body > [data-popper-placement] { z-index: 9999; } @@ -514,10 +411,10 @@ &__suggestions { box-shadow: var(--dropdown-shadow); - background: var(--input-background-color); - border: 1px solid var(--background-border-color); + background: var(--color-bg-elevated); + border: 1px solid var(--color-border-primary); border-radius: 0 0 4px 4px; - color: var(--on-input-color); + color: var(--color-text-primary); font-size: 14px; padding: 0; @@ -530,7 +427,7 @@ font-size: 14px; line-height: 20px; letter-spacing: 0.25px; - color: var(--on-input-color); + color: var(--color-text-primary); &:last-child { border-radius: 0 0 4px 4px; @@ -539,7 +436,7 @@ &:hover, &:focus, &:active { - background: var(--dropdown-border-color); + background: var(--color-bg-secondary); .autosuggest-account .display-name__account { color: inherit; @@ -547,8 +444,8 @@ } &.selected { - background: $ui-highlight-color; - color: $ui-button-color; + color: var(--color-text-on-brand-base); + background: var(--color-bg-brand-base); .autosuggest-account .display-name__account { color: inherit; @@ -584,7 +481,7 @@ display: block; line-height: 16px; font-size: 12px; - color: $ui-primary-color; + color: var(--color-text-secondary); } } @@ -639,28 +536,27 @@ flex-direction: column; flex: 0 1 auto; border-radius: 4px; - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-on-bg-secondary); transition: border-color 300ms linear; position: relative; - background: var(--input-background-color); + background: var(--color-bg-secondary); &.active { transition: none; - border-color: $ui-highlight-color; + border-color: var(--color-text-brand); } } &__warning { - color: $inverted-text-color; - background: $ui-primary-color; - box-shadow: 0 2px 6px color.change($base-shadow-color, $alpha: 0.3); + color: var(--color-text-primary); + background: var(--color-bg-warning-softer); + border: 1px solid var(--color-border-on-bg-warning-softer); padding: 8px 10px; border-radius: 4px; font-size: 13px; font-weight: 400; strong { - color: $inverted-text-color; font-weight: 500; @each $lang in $cjk-langs { @@ -671,7 +567,7 @@ } a { - color: $lighter-text-color; + color: var(--color-text-brand); font-weight: 500; text-decoration: underline; @@ -695,7 +591,7 @@ .autosuggest-input { flex: 1 1 auto; - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); border-width: 1px 0; } } @@ -706,8 +602,8 @@ box-sizing: border-box; width: 100%; margin: 0; - color: $secondary-text-color; - background: var(--input-background-color); + color: var(--color-text-primary); + background: transparent; font-family: inherit; font-size: 14px; padding: 12px; @@ -722,8 +618,8 @@ .spoiler-input__input { padding: 12px 12px - 5px; - background: color.change($ui-highlight-color, $alpha: 0.05); - color: $highlight-text-color; + background: var(--color-bg-brand-softer); + color: var(--color-text-brand); } &__dropdowns { @@ -732,11 +628,6 @@ gap: 8px; margin: 8px; flex-wrap: wrap; - - & > div { - overflow: hidden; - display: flex; - } } &__uploads { @@ -775,16 +666,43 @@ padding: 8px; } - &__preview { + &__preview, + &__visualizer { position: absolute; width: 100%; height: 100%; - border-radius: 6px; z-index: -1; top: 0; + } + + &__preview { + border-radius: 6px; inset-inline-start: 0; } + &__visualizer { + padding: 16px; + box-sizing: border-box; + + .audio-player__visualizer { + margin: 0 auto; + display: block; + height: 100%; + } + + .icon { + position: absolute; + top: 50%; + inset-inline-start: 50%; + transform: translate(-50%, -50%); + opacity: 0.75; + color: var(--player-foreground-color); + filter: var(--overlay-icon-shadow); + width: 48px; + height: 48px; + } + } + &__thumbnail { width: 100%; height: 100%; @@ -796,8 +714,9 @@ .icon-button { flex: 0 0 auto; - color: $white; - background: rgba(0, 0, 0, 75%); + color: var(--color-text-on-media); + background: var(--color-bg-media); + backdrop-filter: $backdrop-blur-filter; border-radius: 6px; font-size: 12px; line-height: 16px; @@ -830,8 +749,8 @@ padding: 8px; .icon-button.active { - color: #ffbe2e; - background: rgba(0, 0, 0, 75%); + color: var(--color-text-on-warning-base); + background: var(--color-bg-warning-base); } } } @@ -848,7 +767,6 @@ align-items: center; flex: 1 1 auto; max-width: 100%; - overflow: hidden; } &__buttons { @@ -880,22 +798,21 @@ .icon-button { box-sizing: content-box; - color: $highlight-text-color; + color: var(--color-text-brand); &:hover, &:focus, &:active { - color: $highlight-text-color; + color: var(--color-text-brand); } &.disabled { - color: $highlight-text-color; - opacity: 0.5; + color: var(--color-text-disabled); } &.active { - background: $ui-highlight-color; - color: $primary-text-color; + background: var(--color-bg-brand-base); + color: var(--color-text-on-brand-base); } } } @@ -919,7 +836,7 @@ .poll__input { width: 17px; height: 17px; - border-color: $darker-text-color; + border-color: var(--color-text-secondary); } &__footer { @@ -932,7 +849,7 @@ &__sep { width: 1px; height: 22px; - background: lighten($ui-base-color, 8%); + background: var(--color-border-primary); flex: 0 0 auto; } } @@ -950,7 +867,7 @@ font-weight: 500; line-height: 16px; letter-spacing: 0.5px; - color: $darker-text-color; + color: var(--color-text-secondary); white-space: nowrap; text-overflow: ellipsis; overflow: hidden; @@ -966,8 +883,8 @@ font-weight: 500; line-height: 20px; letter-spacing: 0.1px; - color: $highlight-text-color; - background-color: var(--input-background-color); + color: var(--color-text-brand); + background-color: var(--color-bg-secondary-solid); white-space: nowrap; text-overflow: ellipsis; overflow: hidden; @@ -978,7 +895,6 @@ .status__quote { margin: 0 8px; max-height: 220px; - overflow: hidden; // Override .status__content .status__content__text.status__content__text--visible .status__content__text.status__content__text { @@ -998,10 +914,10 @@ display: flex; align-items: center; gap: 4px; + color: var(--color-text-brand); background: transparent; - color: $highlight-text-color; + border: 1px solid var(--color-text-brand); border-radius: 6px; - border: 1px solid $highlight-text-color; padding: 4px 8px; font-size: 13px; line-height: normal; @@ -1010,11 +926,15 @@ text-overflow: ellipsis; white-space: nowrap; + &:focus-visible { + outline: var(--outline-focus-default); + outline-offset: 2px; + } + &[disabled] { cursor: default; - color: $highlight-text-color; - border-color: $highlight-text-color; - opacity: 0.5; + color: var(--color-text-disabled); + border-color: var(--color-text-disabled); } .icon { @@ -1031,18 +951,19 @@ } &.active { - background: $ui-highlight-color; - border-color: $ui-highlight-color; - color: $primary-text-color; + color: var(--color-text-on-brand-base); + border-color: var(--color-bg-brand-base); + background: var(--color-bg-brand-base); } &.warning { - border-color: var(--goldenrod-2); - color: var(--goldenrod-2); + color: var(--color-text-warning); + border-color: var(--color-text-warning); &.active { - background-color: var(--goldenrod-2); - color: var(--indigo-1); + color: var(--color-text-on-warning-base); + border-color: var(--color-bg-warning-base); + background-color: var(--color-bg-warning-base); } } } @@ -1053,12 +974,12 @@ font-size: 14px; font-weight: 400; line-height: normal; - color: $darker-text-color; + color: var(--color-text-secondary); flex: 1 0 auto; text-align: end; &.character-counter--over { - color: $error-red; + color: var(--color-text-error); } } @@ -1074,7 +995,7 @@ p { font-size: 15px; line-height: 22px; - color: $secondary-text-color; + color: var(--color-text-primary); margin-bottom: 20px; strong { @@ -1082,7 +1003,7 @@ } a { - color: $secondary-text-color; + color: var(--color-text-primary); text-decoration: none; unicode-bidi: isolate; @@ -1122,14 +1043,14 @@ .edit-indicator__content, .reply-indicator__content { position: relative; - word-wrap: break-word; + overflow-wrap: break-word; font-weight: 400; overflow: hidden; text-overflow: ellipsis; font-size: 15px; line-height: 22px; padding-top: 2px; - color: $primary-text-color; + color: var(--color-text-primary); &:focus { outline: 0; @@ -1160,7 +1081,7 @@ } a { - color: $secondary-text-color; + color: var(--color-text-status-links); text-decoration: none; unicode-bidi: isolate; @@ -1180,7 +1101,7 @@ } a.unhandled-link { - color: $highlight-text-color; + color: var(--color-text-brand); } .status__content__text { @@ -1236,7 +1157,7 @@ inset-inline-start: 50%; top: 4px; transform: translateX(-50%); - background: lighten($ui-base-color, 8%); + background: var(--color-border-primary); width: 2px; height: calc(100% + 32px - 8px); // Account for gap to next element } @@ -1253,12 +1174,12 @@ padding: 0; max-height: 4 * 20px; overflow: hidden; - color: $darker-text-color; + color: var(--color-text-secondary); } &__attachments { margin-top: 4px; - color: $darker-text-color; + color: var(--color-text-secondary); font-size: 12px; line-height: 16px; display: flex; @@ -1274,11 +1195,11 @@ .edit-indicator { border-radius: 4px 4px 0 0; - background: lighten($ui-base-color, 4%); + background: var(--color-bg-tertiary); padding: 12px; overflow-y: auto; flex: 0 0 auto; - border-bottom: 0.5px solid lighten($ui-base-color, 8%); + border-bottom: 1px solid var(--color-border-primary); display: flex; flex-direction: column; gap: 4px; @@ -1287,7 +1208,7 @@ display: flex; justify-content: space-between; align-items: center; - color: $darker-text-color; + color: var(--color-text-secondary); font-size: 12px; line-height: 16px; overflow: hidden; @@ -1320,7 +1241,7 @@ } &__content { - color: $secondary-text-color; + color: var(--color-text-primary); font-size: 14px; line-height: 20px; letter-spacing: 0.25px; @@ -1333,12 +1254,12 @@ overflow: hidden; a { - color: $highlight-text-color; + color: var(--color-text-brand); } } &__attachments { - color: $darker-text-color; + color: var(--color-text-secondary); font-size: 12px; line-height: 16px; opacity: 0.75; @@ -1362,8 +1283,8 @@ } } -.announcements__item__content { - word-wrap: break-word; +.announcements__content { + overflow-wrap: break-word; overflow-y: auto; .emojione { @@ -1382,7 +1303,7 @@ } a { - color: $secondary-text-color; + color: var(--color-text-primary); text-decoration: none; &:hover { @@ -1400,7 +1321,7 @@ } &.unhandled-link { - color: $highlight-text-color; + color: var(--color-text-brand); } } } @@ -1415,7 +1336,7 @@ align-items: center; font-size: 15px; line-height: 22px; - color: $highlight-text-color; + color: var(--color-text-brand); border: 0; background: transparent; padding: 0; @@ -1447,11 +1368,11 @@ line-height: 22px; display: flex; justify-content: space-between; - color: $dark-text-color; + color: var(--color-text-tertiary); } .status__wrapper--filtered { - color: $dark-text-color; + color: var(--color-text-tertiary); border: 0; font-size: inherit; text-align: center; @@ -1461,11 +1382,11 @@ box-sizing: border-box; width: 100%; clear: both; - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); &__button { display: inline; - color: lighten($ui-highlight-color, 8%); + color: var(--color-text-brand); border: 0; background: transparent; padding: 0; @@ -1481,16 +1402,16 @@ .focusable { &:focus-visible { - outline: 2px solid $ui-button-focus-outline-color; + outline: 2px solid var(--color-text-brand); outline-offset: -2px; - background: color.change($ui-highlight-color, $alpha: 0.05); + background: var(--color-bg-brand-softer); } } .status { padding: 16px; min-height: 54px; - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); cursor: auto; opacity: 1; animation: fade 150ms linear; @@ -1528,7 +1449,7 @@ & > .status__content, & > .status__action-bar, & > .media-gallery, - & > .video-player, + & > div > .video-player, & > .audio-player, & > .attachment-list, & > .picture-in-picture-placeholder, @@ -1561,12 +1482,14 @@ } &--first-in-thread { - border-top: 1px solid var(--background-border-color); + border-top: 1px solid var(--color-border-primary); } &__line { + --thread-line-color: var(--color-border-primary); + height: 16px - 4px; - border-inline-start: 2px solid lighten($ui-base-color, 8%); + border-inline-start: 2px solid var(--thread-line-color); width: 0; position: absolute; top: 0; @@ -1583,7 +1506,7 @@ top: 16px - 4px; height: 46px + 4px + 4px; width: 2px; - background: $ui-base-color; + background: var(--thread-line-color); inset-inline-start: -2px; } } @@ -1602,7 +1525,7 @@ content: ''; position: absolute; inset: 0; - background: rgb(from $ui-highlight-color r g b / 20%); + background: var(--color-bg-brand-softer); opacity: 0; animation: fade 0.7s reverse both 0.3s; pointer-events: none; @@ -1616,11 +1539,11 @@ height: 40px; order: 2; flex: 0 0 auto; - color: $dark-text-color; + color: var(--color-text-secondary); } .notification__relative_time { - color: $dark-text-color; + color: var(--color-text-tertiary); float: right; font-size: 14px; padding-bottom: 1px; @@ -1637,7 +1560,7 @@ } .status__display-name { - color: $dark-text-color; + color: var(--color-text-secondary); } .status__info .status__display-name { @@ -1687,7 +1610,7 @@ padding: 0 10px; .detailed-status__display-name { - color: $dark-text-color; + color: var(--color-text-tertiary); span { display: inline; @@ -1724,7 +1647,7 @@ font-size: 15px; line-height: 22px; font-weight: 500; - color: $dark-text-color; + color: var(--color-text-secondary); &__icon { display: flex; @@ -1754,10 +1677,10 @@ .notification-ungrouped--direct, .notification-group--direct, .notification-group--annual-report { - background: color.change($ui-highlight-color, $alpha: 0.05); + background: var(--color-bg-brand-softer); &:focus { - background: color.change($ui-highlight-color, $alpha: 0.1); + background: var(--color-bg-brand-soft); } } @@ -1765,7 +1688,7 @@ .notification-ungrouped--direct { .status__prepend, .notification-ungrouped__header { - color: $highlight-text-color; + color: var(--color-text-brand); } } @@ -1787,7 +1710,7 @@ .detailed-status { padding: 16px; - border-top: 1px solid var(--background-border-color); + border-top: 1px solid var(--color-border-primary); .status__content { font-size: 19px; @@ -1822,7 +1745,7 @@ .logo { width: 40px; height: 40px; - color: $dark-text-color; + color: var(--color-text-tertiary); } } @@ -1849,12 +1772,12 @@ .detailed-status__meta { margin-top: 24px; - color: $dark-text-color; + color: var(--color-text-tertiary); font-size: 14px; line-height: 18px; &__line { - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); padding: 8px 0; display: flex; align-items: center; @@ -1876,14 +1799,14 @@ } .animated-number { - color: $secondary-text-color; + color: var(--color-text-primary); font-weight: 500; } } .detailed-status__action-bar { - border-top: 1px solid var(--background-border-color); - border-bottom: 1px solid var(--background-border-color); + border-top: 1px solid var(--color-border-primary); + border-bottom: 1px solid var(--color-border-primary); display: flex; flex-direction: row; padding: 10px 0; @@ -1892,30 +1815,11 @@ .detailed-status__wrapper-direct { .detailed-status, .detailed-status__action-bar { - background: color.mix($ui-base-color, $ui-highlight-color, 95%); - } - - &:focus-visible { - .detailed-status, - .detailed-status__action-bar { - background: color.mix( - lighten($ui-base-color, 4%), - $ui-highlight-color, - 95% - ); - } - } - - .detailed-status__action-bar { - border-top-color: color.mix( - lighten($ui-base-color, 8%), - $ui-highlight-color, - 95% - ); + background: var(--color-bg-brand-softer); } .status__prepend { - color: $highlight-text-color; + color: var(--color-text-brand); } } @@ -1926,11 +1830,12 @@ --quote-margin: var(--status-gutter-width); position: relative; + overflow: hidden; margin-block-start: 16px; margin-inline-start: calc(var(--quote-margin) + var(--thread-margin, 0px)); border-radius: 12px; - color: var(--nested-card-text); - border: 1px solid var(--surface-border-color); + color: var(--color-text-primary); + border: 1px solid var(--color-border-primary); } .status__quote--error { @@ -1964,8 +1869,8 @@ font-weight: 400; line-height: 20px; letter-spacing: 0.25px; - color: $darker-text-color; - background: var(--surface-variant-background-color); + color: var(--color-text-secondary); + background: var(--color-bg-brand-softer); border-radius: 8px; cursor: default; } @@ -2029,14 +1934,14 @@ .domain { padding: 16px; - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); display: flex; align-items: center; gap: 8px; &__domain-name { flex: 1 1 auto; - color: $primary-text-color; + color: var(--color-text-primary); font-size: 15px; line-height: 21px; font-weight: 500; @@ -2045,14 +1950,14 @@ .account { padding: 16px; - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); .account__display-name { flex: 1 1 auto; display: flex; align-items: center; gap: 10px; - color: $darker-text-color; + color: var(--color-text-secondary); overflow: hidden; text-decoration: none; font-size: 14px; @@ -2063,6 +1968,7 @@ .display-name strong { display: inline; + color: var(--color-text-primary); } } @@ -2087,24 +1993,24 @@ &__domain-pill { display: inline-flex; - background: color.change($highlight-text-color, $alpha: 0.2); + background: var(--color-bg-brand-softer); border-radius: 4px; border: 0; - color: $highlight-text-color; + color: var(--color-text-brand); font-weight: 500; font-size: 12px; line-height: 16px; padding: 4px 8px; &.active { - color: $white; - background: $ui-highlight-color; + color: var(--color-text-on-brand-base); + background: var(--color-bg-brand-base); } &__popout { - background: var(--dropdown-background-color); + background: var(--color-bg-elevated); backdrop-filter: $backdrop-blur-filter; - border: 1px solid var(--dropdown-border-color); + border: 1px solid var(--color-border-primary); box-shadow: var(--dropdown-shadow); max-width: 320px; padding: 16px; @@ -2114,7 +2020,7 @@ gap: 24px; font-size: 14px; line-height: 20px; - color: $darker-text-color; + color: var(--color-text-secondary); .link-button { display: inline; @@ -2130,8 +2036,8 @@ &__icon { width: 40px; height: 40px; - background: $ui-highlight-color; - color: $white; + color: var(--color-text-on-brand-base); + background: var(--color-bg-brand-base); display: flex; align-items: center; justify-content: center; @@ -2142,15 +2048,15 @@ h3 { font-size: 17px; line-height: 22px; - color: $primary-text-color; + color: var(--color-text-primary); } } &__handle { - border: 2px dashed $highlight-text-color; - background: color.change($highlight-text-color, $alpha: 0.1); + border: 2px dashed var(--color-border-on-brand-softer); + background: var(--color-bg-brand-softer); padding: 12px 8px; - color: $highlight-text-color; + color: var(--color-text-brand); border-radius: 4px; &__label { @@ -2186,14 +2092,14 @@ align-items: center; justify-content: center; flex-shrink: 0; - color: $highlight-text-color; + color: var(--color-text-brand); } h6 { font-size: 14px; line-height: 20px; font-weight: 500; - color: $primary-text-color; + color: var(--color-text-primary); } } } @@ -2209,10 +2115,10 @@ -webkit-line-clamp: 1; -webkit-box-orient: vertical; margin-top: 10px; - color: $darker-text-color; + color: var(--color-text-secondary); &--missing { - color: $dark-text-color; + color: var(--color-text-tertiary); } p { @@ -2276,7 +2182,8 @@ display: block; position: relative; border-radius: var(--avatar-border-radius); - background: var(--surface-background-color); + background: var(--color-bg-tertiary); + flex-shrink: 0; img { width: 100%; @@ -2287,7 +2194,7 @@ } &--loading { - background-color: var(--surface-background-color); + background-color: var(--color-bg-tertiary); } &--inline { @@ -2318,8 +2225,9 @@ top: 50%; inset-inline-start: 50%; transform: translate(-50%, -50%); - color: $primary-text-color; - text-shadow: 1px 1px 2px $base-shadow-color; + color: var(--color-text-primary); + text-shadow: 1px 1px 2px + rgb(from var(--color-shadow-primary) r g b / 100%); font-weight: 700; font-size: 15px; } @@ -2338,11 +2246,11 @@ border-radius: $height; min-width: $height - 2 * $h-padding; // to ensure that it is never narrower than a circle line-height: $height + 1px; // to visually center the numbers - background-color: $ui-button-background-color; - color: $white; + color: var(--color-text-on-brand-base); + background: var(--color-bg-brand-base); border-width: 1px; border-style: solid; - border-color: var(--background-color); + border-color: var(--color-bg-primary); font-size: 11px; font-weight: 500; text-align: center; @@ -2380,7 +2288,7 @@ a .account__avatar { } .account__avatar { - box-shadow: 0 0 0 2px var(--background-color); + box-shadow: 0 0 0 2px var(--color-bg-primary); } } @@ -2405,7 +2313,7 @@ a .account__avatar { .account__relationship, .explore-suggestions-card { .icon-button { - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); border-radius: 4px; box-sizing: content-box; padding: 5px; @@ -2445,7 +2353,7 @@ a .account__avatar { .status__display-name, .account__display-name { .display-name strong { - color: $primary-text-color; + color: var(--color-text-primary); } } @@ -2476,7 +2384,7 @@ a.account__display-name { } .detailed-status__display-name { - color: $darker-text-color; + color: var(--color-text-secondary); display: flex; align-items: center; gap: 10px; @@ -2493,7 +2401,7 @@ a.account__display-name { } strong { - color: $primary-text-color; + color: var(--color-text-primary); } } @@ -2501,11 +2409,11 @@ a.account__display-name { .status__content, .status__content p, .status__content a { - color: $dark-text-color; + color: var(--color-text-tertiary); } .status__display-name strong { - color: $dark-text-color; + color: var(--color-text-tertiary); } .status__avatar { @@ -2515,7 +2423,7 @@ a.account__display-name { .notification__report { padding: 16px; - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); display: flex; gap: 10px; @@ -2528,7 +2436,7 @@ a.account__display-name { display: flex; justify-content: space-between; align-items: center; - color: $darker-text-color; + color: var(--color-text-secondary); gap: 10px; font-size: 15px; line-height: 22px; @@ -2551,7 +2459,7 @@ a.account__display-name { } .notification-group--link { - color: $secondary-text-color; + color: var(--color-text-primary); text-decoration: none; .notification-group__main { @@ -2580,7 +2488,7 @@ a.account__display-name { padding: 16px; padding-bottom: 0; cursor: default; - color: $darker-text-color; + color: var(--color-text-secondary); font-size: 15px; line-height: 22px; font-weight: 500; @@ -2589,13 +2497,13 @@ a.account__display-name { gap: 10px; .icon { - color: $highlight-text-color; + color: var(--color-text-brand); width: 18px; height: 18px; } .icon-star { - color: $gold-star; + color: var(--color-text-favourite-highlight); } > span { @@ -2606,11 +2514,11 @@ a.account__display-name { } .icon-button.star-icon.active { - color: $gold-star; + color: var(--color-text-favourite-highlight); } .icon-button.bookmark-icon.active { - color: $red-bookmark; + color: var(--color-text-bookmark-highlight); } .no-reduce-motion .icon-button.star-icon { @@ -2680,10 +2588,11 @@ a.account__display-name { max-height: $media-modal-media-max-height; width: auto; height: auto; - outline: 1px solid var(--media-outline-color); + outline: 1px solid var(--color-border-media); outline-offset: -1px; border-radius: 8px; touch-action: none; + user-select: none; } &--zoomed-in { @@ -2709,7 +2618,7 @@ a.account__display-name { max-height: $media-modal-media-max-height; position: absolute; z-index: 1; - outline: 1px solid var(--media-outline-color); + outline: 1px solid var(--color-border-media); outline-offset: -1px; border-radius: 8px; overflow: hidden; @@ -2760,7 +2669,7 @@ a.account__display-name { .icon-button { padding: 8px; - color: $secondary-text-color; + color: var(--color-text-primary); } .icon-button .icon { @@ -2797,15 +2706,15 @@ a.account__display-name { } .dropdown-menu__separator { - border-bottom: 1px solid var(--dropdown-border-color); + border-bottom: 1px solid var(--color-border-primary); margin: 5px 0; height: 0; } .dropdown-menu { - background: var(--dropdown-background-color); + background: var(--color-bg-elevated); backdrop-filter: $backdrop-blur-filter; - border: 1px solid var(--dropdown-border-color); + border: 1px solid var(--color-border-primary); padding: 4px; border-radius: 4px; box-shadow: var(--dropdown-shadow); @@ -2839,13 +2748,13 @@ a.account__display-name { &__container { &__header { - border-bottom: 1px solid var(--dropdown-border-color); + border-bottom: 1px solid var(--color-border-primary); padding: 10px 14px; padding-bottom: 14px; margin-bottom: 4px; font-size: 13px; line-height: 18px; - color: $darker-text-color; + color: var(--color-text-secondary); } &__list { @@ -2889,11 +2798,11 @@ a.account__display-name { display: block; &--dangerous { - color: $error-value-color; + color: var(--color-text-error); } &--highlighted { - color: $highlight-text-color; + color: var(--color-text-brand); } &-content { @@ -2927,7 +2836,7 @@ a.account__display-name { &:hover, &:active { &:not(:disabled, [aria-disabled='true']) { - background: var(--dropdown-border-color); + background: var(--color-bg-secondary); outline: 0; } } @@ -2935,12 +2844,12 @@ a.account__display-name { button:disabled, button[aria-disabled='true'] { - color: $dark-text-color; + color: var(--color-text-disabled); cursor: default; &:focus { - color: rgb(from $dark-text-color r g b / 70%); - background: var(--dropdown-border-color); + color: var(--color-text-on-disabled); + background: var(--color-bg-disabled); outline: 0; } } @@ -2972,8 +2881,12 @@ a.account__display-name { justify-content: flex-start; position: relative; - &.unscrollable { - overflow-x: hidden; + .layout-multiple-columns & { + overflow-x: auto; + + &.unscrollable { + overflow-x: hidden; + } } &__panels { @@ -2984,6 +2897,8 @@ a.account__display-name { width: 100%; height: 100%; min-height: 100vh; + min-height: 100dvh; + padding-inline: 10px; padding-bottom: env(safe-area-inset-bottom); &__pane { @@ -3014,6 +2929,8 @@ a.account__display-name { flex-direction: column; contain: inline-size layout paint style; container: column / inline-size; + color: var(--color-text-primary); + background-color: var(--color-bg-primary); @media screen and (min-width: $no-gap-breakpoint) { max-width: 600px; @@ -3032,9 +2949,9 @@ a.account__display-name { width: 100%; gap: 8px; padding-bottom: env(safe-area-inset-bottom); - background: var(--background-color); + background: var(--color-bg-primary); backdrop-filter: $backdrop-blur-filter; - border-top: 1px solid var(--background-border-color); + border-top: 1px solid var(--color-border-primary); box-sizing: border-box; .layout-multiple-columns & { @@ -3077,7 +2994,7 @@ a.account__display-name { color: inherit; &.active { - color: $highlight-text-color; + color: var(--color-text-brand); } &:focus { @@ -3085,22 +3002,22 @@ a.account__display-name { } &:focus-visible { - border-top-color: $ui-button-focus-outline-color; + border-top-color: var(--color-text-brand); border-radius: 0; } } } .tabs-bar__wrapper { - background: var(--background-color); + background: var(--color-bg-primary); backdrop-filter: $backdrop-blur-filter; position: sticky; top: 0; z-index: 2; - padding-top: 0; + border-top: 0; @media screen and (min-width: $no-gap-breakpoint) { - padding-top: 10px; + border-top: 10px solid var(--color-bg-ambient); } } @@ -3128,13 +3045,13 @@ a.account__display-name { flex-direction: column; > .scrollable { - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); border-top: 0; border-radius: 0 0 4px 4px; &.about, &.privacy-policy { - border-top: 1px solid var(--background-border-color); + border-top: 1px solid var(--color-border-primary); border-radius: 4px; @media screen and (max-width: $no-gap-breakpoint) { @@ -3146,25 +3063,32 @@ a.account__display-name { } .column__alert { + --alert-height: 54px; + position: sticky; - bottom: 1rem; + bottom: 0; z-index: 10; box-sizing: border-box; display: grid; + grid-template-rows: minmax(var(--alert-height), max-content); + align-items: end; width: 100%; max-width: 360px; - padding-inline: 10px; - margin-top: 1rem; - margin-inline: auto; + padding: 1rem; + margin: auto auto 0; + overflow: clip; + pointer-events: none; @media (max-width: #{$mobile-menu-breakpoint - 1}) { - bottom: 4rem; + // Compensate for mobile menubar + bottom: var(--mobile-bottom-nav-height); } & > * { // Make all nested alerts occupy the same space // rather than stack grid-area: 1 / 1; + pointer-events: initial; } } @@ -3197,7 +3121,7 @@ a.account__display-name { display: flex; flex: 1 1 auto; padding: 13px 3px 11px; - color: $darker-text-color; + color: var(--color-text-secondary); text-decoration: none; text-align: center; font-size: 16px; @@ -3316,7 +3240,9 @@ a.account__display-name { .columns-area__panels { min-height: 100vh; + min-height: 100dvh; gap: 0; + padding-inline: 0; } .columns-area__panels__pane--navigational { @@ -3328,7 +3254,7 @@ a.account__display-name { .navigation-panel { margin: 0; - border-inline-start: 1px solid var(--background-border-color); + border-inline-start: 1px solid var(--color-border-primary); height: 100dvh; } @@ -3378,7 +3304,7 @@ a.account__display-name { .columns-area__panels__pane--navigational .columns-area__panels__pane__inner { pointer-events: auto; - background: var(--background-color); + background: var(--color-bg-primary); position: fixed; width: 284px + 70px; inset-inline-end: -70px; @@ -3409,7 +3335,7 @@ a.account__display-name { .columns-area__panels__pane--overlay { pointer-events: auto; - background: color.change($base-overlay-background, $alpha: 0.5); + background: rgb(from var(--color-bg-overlay) r g b / 50%); z-index: 3; .columns-area__panels__pane__inner { @@ -3428,7 +3354,7 @@ a.account__display-name { gap: 8px; display: flex; flex-direction: column; - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); &:last-child { border-bottom: 0; @@ -3437,7 +3363,7 @@ a.account__display-name { &__source { font-size: 13px; line-height: 16px; - color: $dark-text-color; + color: var(--color-text-tertiary); @container (width >= 400px) { padding-inline-start: 60px; @@ -3478,14 +3404,14 @@ a.account__display-name { .display-name { font-size: 15px; line-height: 20px; - color: $secondary-text-color; + color: var(--color-text-primary); strong { font-weight: 700; } &__account { - color: $darker-text-color; + color: var(--color-text-secondary); display: block; } } @@ -3523,14 +3449,14 @@ a.account__display-name { position: absolute; inset-inline-start: 9px; top: -13px; - background: $ui-highlight-color; - border: 2px solid var(--background-color); + color: var(--color-text-on-brand-base); + background: var(--color-bg-brand-base); + border: 2px solid var(--color-bg-primary); padding: 1px 6px; border-radius: 6px; font-size: 10px; font-weight: 500; line-height: 14px; - color: $primary-text-color; } &__issue-badge { @@ -3538,7 +3464,7 @@ a.account__display-name { inset-inline-start: 11px; bottom: 1px; display: block; - background: $error-red; + background: var(--color-text-error); border-radius: 50%; width: 0.625rem; height: 0.625rem; @@ -3546,7 +3472,7 @@ a.account__display-name { } .column-link--transparent .icon-with-badge__badge { - border-color: var(--background-color); + border-color: var(--color-bg-primary); } .column-title { @@ -3564,7 +3490,7 @@ a.account__display-name { font-size: 16px; line-height: 24px; font-weight: 400; - color: $darker-text-color; + color: var(--color-text-secondary); } @media screen and (width >= 600px) { @@ -3573,11 +3499,11 @@ a.account__display-name { } .copy-paste-text { - background: lighten($ui-base-color, 4%); + background: var(--color-bg-secondary); border-radius: 8px; - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); padding: 16px; - color: $primary-text-color; + color: var(--color-text-primary); font-size: 15px; line-height: 22px; display: flex; @@ -3590,11 +3516,11 @@ a.account__display-name { &.focused { transition: none; outline: 0; - border-color: $highlight-text-color; + border-color: var(--color-text-brand); } &.copied { - border-color: $valid-value-color; + border-color: var(--color-text-success); transition: none; } @@ -3621,7 +3547,7 @@ a.account__display-name { margin-top: -20px; .app-form__avatar-input { - border: 2px solid var(--background-color); + border: 2px solid var(--color-bg-primary); position: absolute; inset-inline-start: -2px; bottom: -40px; @@ -3645,6 +3571,7 @@ a.account__display-name { flex-direction: column; height: calc(100% - 10px); overflow-y: auto; + scrollbar-width: thin; .compose-form { flex: 1 1 auto; @@ -3663,6 +3590,7 @@ a.account__display-name { flex: 1 1 auto; min-height: 0; overflow-y: auto; + scrollbar-width: thin; } &__list-panel { @@ -3674,7 +3602,7 @@ a.account__display-name { &__sep { width: 0; height: 24px; - border-left: 1px solid var(--background-border-color); + border-left: 1px solid var(--color-border-primary); } .column-link { @@ -3774,7 +3702,7 @@ a.account__display-name { flex: 0 0 auto; border: 0; background: transparent; - border-top: 1px solid var(--background-border-color); + border-top: 1px solid var(--color-border-primary); margin: 10px 0; } @@ -3791,14 +3719,14 @@ a.account__display-name { overflow: hidden; display: flex; border-radius: 4px; - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); } .drawer__inner { position: absolute; top: 0; inset-inline-start: 0; - background: var(--background-color); + background: var(--color-bg-primary); box-sizing: border-box; padding: 0; display: flex; @@ -3811,12 +3739,12 @@ a.account__display-name { } .drawer__inner__mastodon { - background: var(--background-color) - url('data:image/svg+xml;utf8,') - no-repeat bottom / 100% auto; + position: relative; + background: var(--color-bg-primary); flex: 1; min-height: 47px; display: none; + contain: content; > img { display: block; @@ -3836,7 +3764,7 @@ a.account__display-name { .drawer__header { flex: 0 0 auto; font-size: 16px; - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); margin-bottom: 10px; display: flex; flex-direction: row; @@ -3846,7 +3774,7 @@ a.account__display-name { a:hover, a:focus, a:active { - color: $primary-text-color; + color: var(--color-text-primary); } } @@ -3855,6 +3783,7 @@ a.account__display-name { overflow-x: hidden; flex: 1 1 auto; -webkit-overflow-scrolling: touch; + scrollbar-width: thin; &.optionally-scrollable { overflow-y: auto; @@ -3892,9 +3821,9 @@ a.account__display-name { box-sizing: border-box; width: 100%; background: transparent; - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); border-radius: 4px 4px 0 0; - color: $highlight-text-color; + color: var(--color-text-brand); cursor: pointer; flex: 0 0 auto; font-size: 16px; @@ -3920,7 +3849,7 @@ a.account__display-name { background: transparent; border: 0; font-family: inherit; - color: $highlight-text-color; + color: var(--color-text-brand); cursor: pointer; white-space: nowrap; font-size: 16px; @@ -3946,17 +3875,23 @@ a.account__display-name { border-radius: 10px; padding: 0; user-select: none; - -webkit-tap-highlight-color: color.change( - $base-overlay-background, - $alpha: 0 - ); -webkit-tap-highlight-color: transparent; } +.react-toggle--focus, +.react-toggle:focus-within { + outline: var(--outline-focus-default); + outline-offset: 2px; + + .react-toggle-track { + border-color: transparent; + } +} + .react-toggle-screenreader-only, .sr-only { border: 0; - clip: rect(0 0 0 0); + clip-path: inset(50%); height: 1px; margin: -1px; overflow: hidden; @@ -3976,15 +3911,25 @@ a.account__display-name { height: 20px; padding: 0; border-radius: 10px; - background-color: $ui-primary-color; -} + background-color: rgb(from var(--color-bg-brand-softer) r g b / 50%); + border: 1px solid rgb(from var(--color-text-brand) r g b / 50%); + box-sizing: border-box; -.react-toggle--focus { - outline: $ui-button-focus-outline; -} + .react-toggle:hover:not(.react-toggle--disabled) & { + background-color: rgb( + from var(--color-bg-brand-softer) r g b / + calc(50% + var(--overlay-strength-brand)) + ); + } + + .react-toggle--checked & { + background-color: var(--color-bg-brand-base); + border-color: var(--color-bg-brand-base); + } -.react-toggle--checked .react-toggle-track { - background-color: $ui-highlight-color; + .react-toggle--checked:not(.react-toggle--disabled):hover & { + background-color: var(--color-bg-brand-base-hover); + } } .react-toggle-track-check, @@ -3999,29 +3944,21 @@ a.account__display-name { width: 16px; height: 16px; border-radius: 50%; - background-color: $ui-button-color; + background-color: var(--color-text-on-brand-base); box-sizing: border-box; transition: all 0.25s ease; transition-property: border-color, left; -} - -.react-toggle--checked .react-toggle-thumb { - inset-inline-start: 32px - 16px - 2px; - border-color: $ui-highlight-color; -} - -.react-toggle:hover:not(.react-toggle--disabled) .react-toggle-track { - background: darken($ui-primary-color, 5%); -} -.react-toggle.react-toggle--checked:hover:not(.react-toggle--disabled) - .react-toggle-track { - background: lighten($ui-highlight-color, 5%); + .react-toggle--checked & { + inset-inline-start: 32px - 16px - 2px; + border-color: var(--color-bg-brand-base); + } } +.follow_requests-unlocked_explanation, .switch-to-advanced { - color: $light-text-color; - background-color: $ui-base-color; + color: var(--color-text-secondary); + background-color: var(--color-bg-secondary); padding: 15px; border-radius: 4px; margin-top: 4px; @@ -4029,8 +3966,8 @@ a.account__display-name { font-size: 13px; line-height: 18px; - .switch-to-advanced__toggle { - color: $ui-button-tertiary-color; + a { + color: var(--color-text-brand); font-weight: bold; } } @@ -4046,20 +3983,24 @@ a.account__display-name { text-decoration: none; overflow: hidden; white-space: nowrap; - color: $secondary-text-color; + color: color-mix( + in oklab, + var(--color-text-primary), + var(--color-text-secondary) + ); background: transparent; border: 0; border-left: 4px solid transparent; box-sizing: border-box; &:hover, - &:focus, - &:active { - color: $primary-text-color; + &:active, + &:focus-visible { + color: var(--color-text-primary); } &.active { - color: $highlight-text-color; + color: var(--color-text-brand); } &:focus { @@ -4067,19 +4008,12 @@ a.account__display-name { } &:focus-visible { - border-color: $ui-button-focus-outline-color; + border-color: var(--color-text-brand); border-radius: 0; } &--logo { - background: transparent; padding: 10px; - - &:hover, - &:focus, - &:active { - background: transparent; - } } } @@ -4089,14 +4023,14 @@ a.account__display-name { font-size: 12px; line-height: 19px; font-weight: 500; - background: $ui-base-color; + background: var(--color-bg-primary); padding: 4px 8px; margin: -6px 10px; } .column-subheading { - background: var(--surface-background-color); - color: $darker-text-color; + background: var(--color-bg-secondary); + color: var(--color-text-secondary); padding: 8px 20px; font-size: 12px; font-weight: 500; @@ -4113,9 +4047,9 @@ a.account__display-name { } .getting-started { - color: $dark-text-color; + color: var(--color-text-tertiary); overflow: auto; - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); border-top: 0; &__trends { @@ -4125,14 +4059,14 @@ a.account__display-name { margin-top: 10px; h4 { - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); padding: 10px; font-size: 12px; text-transform: uppercase; font-weight: 500; a { - color: $darker-text-color; + color: var(--color-text-secondary); text-decoration: none; } } @@ -4142,7 +4076,7 @@ a.account__display-name { padding: 10px; &__current { - color: $darker-text-color; + color: var(--color-text-secondary); } } } @@ -4163,9 +4097,6 @@ a.account__display-name { kbd { display: inline-block; - padding: 3px 5px; - background-color: lighten($ui-base-color, 8%); - border: 1px solid darken($ui-base-color, 4%); } } @@ -4174,11 +4105,11 @@ a.account__display-name { align-items: center; position: relative; font-size: 14px; - color: $darker-text-color; + color: var(--color-text-secondary); margin-top: 14px; text-decoration: none; overflow: hidden; - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); border-radius: 8px; contain: inline-size layout paint style; @@ -4198,7 +4129,7 @@ a.account__display-name { cursor: pointer; & > div { - background: color.change($base-shadow-color, $alpha: 0.6); + background: rgb(from var(--color-shadow-primary) r g b / 60%); border-radius: 8px; padding: 12px 9px; backdrop-filter: $backdrop-blur-filter; @@ -4211,7 +4142,7 @@ a.account__display-name { button, a { display: inline; - color: $secondary-text-color; + color: var(--color-text-primary); background: transparent; border: 0; padding: 0 8px; @@ -4222,7 +4153,7 @@ a.account__display-name { &:hover, &:active, &:focus { - color: $primary-text-color; + color: var(--color-text-primary); } } @@ -4244,7 +4175,7 @@ a.status-card { .status-card__host, .status-card__author, .status-card__description { - color: $highlight-text-color; + color: var(--color-text-brand); } } } @@ -4260,7 +4191,7 @@ a.status-card { .status-card__host, .status-card__author, .status-card__description { - color: $highlight-text-color; + color: var(--color-text-brand); } } } @@ -4290,7 +4221,7 @@ a.status-card { font-weight: 700; font-size: 19px; line-height: 24px; - color: $primary-text-color; + color: var(--color-text-primary); overflow: hidden; white-space: nowrap; text-overflow: ellipsis; @@ -4325,7 +4256,7 @@ a.status-card { display: block; margin-top: 8px; font-size: 14px; - color: $primary-text-color; + color: var(--color-text-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -4348,7 +4279,7 @@ a.status-card { flex: 0 0 auto; width: 120px; aspect-ratio: 1; - background: lighten($ui-base-color, 8%); + background: var(--color-bg-secondary); position: relative; & > .icon { @@ -4382,7 +4313,7 @@ a.status-card { top: 0; inset-inline-start: 0; z-index: 0; - background: $base-overlay-background; + background: var(--color-bg-primary); &--hidden { display: none; @@ -4431,7 +4362,7 @@ a.status-card { display: flex; align-items: center; justify-content: center; - color: $dark-text-color; + color: var(--color-text-primary); background-color: transparent; border: 0; font-size: inherit; @@ -4441,13 +4372,19 @@ a.status-card { box-sizing: border-box; text-decoration: none; - &:hover { - background: var(--on-surface-color); + &--large { + padding-block: 32px; } - &:focus-visible { - outline: 2px solid $ui-button-focus-outline-color; - outline-offset: -2px; + &:is(button) { + &:hover { + background: var(--color-bg-secondary); + } + + &:focus-visible { + outline: 2px solid var(--color-text-brand); + outline-offset: -2px; + } } .icon { @@ -4457,12 +4394,12 @@ a.status-card { } .load-gap { - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); } .timeline-hint { text-align: center; - color: $dark-text-color; + color: var(--color-text-secondary); padding: 16px; box-sizing: border-box; width: 100%; @@ -4474,25 +4411,25 @@ a.status-card { } a { - color: $highlight-text-color; + color: var(--color-text-brand); text-decoration: none; &:hover, &:focus, &:active { text-decoration: underline; - color: lighten($highlight-text-color, 4%); + color: var(--color-text-brand-soft); } } &--with-descendants { - border-top: 1px solid var(--background-border-color); + border-top: 1px solid var(--color-border-primary); } } .regeneration-indicator { - color: $darker-text-color; - border: 1px solid var(--background-border-color); + color: var(--color-text-secondary); + border: 1px solid var(--color-border-primary); border-top: 0; cursor: default; display: flex; @@ -4518,7 +4455,7 @@ a.status-card { font-weight: 500; display: block; margin-bottom: 10px; - color: $darker-text-color; + color: var(--color-text-secondary); } span { @@ -4533,7 +4470,7 @@ a.status-card { z-index: 1; &.active { - box-shadow: 0 1px 0 color.change($highlight-text-color, $alpha: 0.3); + box-shadow: 0 1px 0 var(--color-bg-brand-softer); &::before { display: block; @@ -4549,8 +4486,8 @@ a.status-card { z-index: 1; background: radial-gradient( ellipse, - color.change($ui-highlight-color, $alpha: 0.23) 0%, - color.change($ui-highlight-color, $alpha: 0) 60% + rgb(from var(--color-bg-brand-base) r g b / 23%) 0%, + transparent 60% ); } } @@ -4564,7 +4501,7 @@ a.status-card { .column-header__select-row { border-width: 0 1px 1px; border-style: solid; - border-color: var(--background-border-color); + border-color: var(--color-border-primary); padding: 15px; display: flex; align-items: center; @@ -4580,12 +4517,12 @@ a.status-card { &__mode-button { margin-left: auto; - color: $highlight-text-color; + color: var(--color-text-brand); font-weight: bold; font-size: 14px; &:hover { - color: lighten($highlight-text-color, 6%); + color: var(--color-text-brand-soft); } } } @@ -4593,7 +4530,7 @@ a.status-card { .column-header { display: flex; font-size: 16px; - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); border-radius: 4px 4px 0 0; flex: 0 0 auto; cursor: pointer; @@ -4619,7 +4556,7 @@ a.status-card { flex: 1; &:focus-visible { - outline: $ui-button-icon-focus-outline; + outline: var(--outline-focus-default); } .logo { @@ -4633,18 +4570,18 @@ a.status-card { .column-header__back-button { flex: 1; - color: $highlight-text-color; + color: var(--color-text-brand); &.compact { flex: 0 0 auto; - color: $primary-text-color; + color: var(--color-text-primary); } } &.active { .column-header__icon { - color: $highlight-text-color; - text-shadow: 0 0 10px color.change($highlight-text-color, $alpha: 0.4); + color: var(--color-text-brand); + text-shadow: 0 0 10px var(--color-bg-brand-softer); } } @@ -4684,7 +4621,7 @@ a.status-card { justify-content: center; align-items: center; border: 0; - color: $darker-text-color; + color: var(--color-text-primary); background: transparent; cursor: pointer; font-size: 16px; @@ -4695,23 +4632,23 @@ a.status-card { } &:hover { - color: lighten($darker-text-color, 4%); + color: var(--color-text-secondary); } &:focus-visible { - outline: $ui-button-focus-outline; + outline: var(--outline-focus-default); } &.active { - color: $primary-text-color; + color: var(--color-text-brand); &:hover { - color: $primary-text-color; + color: var(--color-text-brand); } } &:disabled { - color: $dark-text-color; + color: var(--color-text-disabled); cursor: default; } } @@ -4724,16 +4661,16 @@ a.status-card { max-height: 70vh; overflow: hidden; overflow-y: auto; - color: $darker-text-color; + color: var(--color-text-secondary); transition: max-height 150ms ease-in-out, opacity 300ms linear; opacity: 1; z-index: 1; position: relative; - border-left: 1px solid var(--background-border-color); - border-right: 1px solid var(--background-border-color); - border-bottom: 1px solid var(--background-border-color); + border-left: 1px solid var(--color-border-primary); + border-right: 1px solid var(--color-border-primary); + border-bottom: 1px solid var(--color-border-primary); @media screen and (max-width: $no-gap-breakpoint) { border-left: 0; @@ -4754,7 +4691,7 @@ a.status-card { height: 0; background: transparent; border: 0; - border-top: 1px solid var(--background-border-color); + border-top: 1px solid var(--color-border-primary); margin: 10px 0; } } @@ -4766,7 +4703,7 @@ a.status-card { .column-header__setting-btn { &:hover, &:focus { - color: $darker-text-color; + color: var(--color-text-secondary); text-decoration: underline; } } @@ -4807,16 +4744,15 @@ a.status-card { } .column-header__issue-btn { - color: $warning-red; + color: var(--color-text-error); &:hover { - color: $error-red; text-decoration: underline; } } .loading-indicator { - color: $dark-text-color; + color: var(--color-text-secondary); font-size: 12px; font-weight: 400; text-transform: uppercase; @@ -4850,18 +4786,18 @@ a.status-card { } .icon-button .loading-indicator .circular-progress { - color: lighten($ui-base-color, 26%); + color: var(--color-text-tertiary); width: 12px; height: 12px; margin: 6px; } .load-more .loading-indicator .circular-progress { - color: lighten($ui-base-color, 26%); + color: var(--color-text-tertiary); } .circular-progress { - color: lighten($ui-base-color, 26%); + color: var(--color-text-tertiary); animation: 1.4s linear 0s infinite normal none running simple-rotate; circle { @@ -4945,8 +4881,8 @@ a.status-card { .video-error-cover { align-items: center; - background: $base-overlay-background; - color: $primary-text-color; + background: var(--color-bg-primary); + color: var(--color-text-primary); cursor: pointer; display: flex; flex-direction: column; @@ -4984,12 +4920,12 @@ a.status-card { padding: 0; margin: 0; border: 0; - color: $white; + color: var(--color-text-on-media); line-height: 20px; font-size: 14px; &__label { - background-color: color.change($black, $alpha: 0.45); + background-color: var(--color-bg-media); backdrop-filter: $backdrop-blur-filter; border-radius: 8px; padding: 12px 16px; @@ -5009,19 +4945,15 @@ a.status-card { &:hover, &:focus { .spoiler-button__overlay__label { - background-color: color.change($black, $alpha: 0.9); + background-color: rgb(from var(--color-bg-media-base) r g b / 90%); } } } } -.modal-container--preloader { - background: lighten($ui-base-color, 8%); -} - .account--panel { - border-top: 1px solid var(--background-border-color); - border-bottom: 1px solid var(--background-border-color); + border-top: 1px solid var(--color-border-primary); + border-bottom: 1px solid var(--color-border-primary); display: flex; flex-direction: row; padding: 10px 0; @@ -5039,7 +4971,7 @@ a.status-card { &__section { // FIXME: Legacy - color: $darker-text-color; + color: var(--color-text-secondary); cursor: default; display: block; font-weight: 500; @@ -5051,7 +4983,7 @@ a.status-card { section { padding: 16px; - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); &:last-child { border-bottom: 0; @@ -5063,7 +4995,7 @@ a.status-card { line-height: 24px; letter-spacing: 0.5px; font-weight: 500; - color: $primary-text-color; + color: var(--color-text-primary); margin-bottom: 16px; } @@ -5092,7 +5024,11 @@ a.status-card { @include search-input; &::placeholder { - color: lighten($darker-text-color, 4%); + color: color-mix( + in oklab, + var(--color-text-primary), + var(--color-text-secondary) + ); } &::-moz-focus-inner { @@ -5106,7 +5042,7 @@ a.status-card { } &:focus { - background: lighten($ui-base-color, 4%); + background: var(--color-bg-secondary); } @media screen and (width <= 600px) { @@ -5115,7 +5051,7 @@ a.status-card { } &__placeholder { - color: $dark-text-color; + color: var(--color-text-tertiary); padding-inline-start: 2px; font-size: 12px; } @@ -5125,7 +5061,7 @@ a.status-card { } &__multi-value { - background: lighten($ui-base-color, 8%); + background: var(--color-bg-secondary); &__remove { cursor: pointer; @@ -5133,8 +5069,12 @@ a.status-card { &:hover, &:active, &:focus { - background: lighten($ui-base-color, 12%); - color: lighten($darker-text-color, 4%); + background: var(--color-bg-brand-softer); + color: color-mix( + in oklab, + var(--color-text-primary), + var(--color-text-secondary) + ); } } } @@ -5142,33 +5082,31 @@ a.status-card { &__multi-value__label, &__input, &__input-container { - color: $darker-text-color; + color: var(--color-text-secondary); } &__clear-indicator, &__dropdown-indicator { cursor: pointer; transition: none; - color: $dark-text-color; + color: var(--color-text-tertiary); &:hover, &:active, &:focus { - color: lighten($dark-text-color, 4%); + color: var(--color-text-secondary); } } &__indicator-separator { - background-color: lighten($ui-base-color, 8%); + background-color: var(--color-border-primary); } &__menu { @include search-popout; - & { - padding: 0; - background: $ui-secondary-color; - } + padding: 0; + background: var(--color-bg-elevated); } &__menu-list { @@ -5176,13 +5114,14 @@ a.status-card { } &__option { - color: $inverted-text-color; + color: var(--color-text-primary); border-radius: 4px; font-size: 14px; &--is-focused, &--is-selected { - background: darken($ui-secondary-color, 10%); + color: var(--color-text-on-brand-base); + background: var(--color-bg-brand-base); } } } @@ -5201,21 +5140,20 @@ a.status-card { } .setting-toggle__label { - color: $darker-text-color; + color: var(--color-text-secondary); } .limited-account-hint { p { - color: $secondary-text-color; + color: var(--color-text-primary); font-size: 15px; font-weight: 500; margin-bottom: 20px; } } -.empty-column-indicator, -.follow_requests-unlocked_explanation { - color: $dark-text-color; +.empty-column-indicator { + color: var(--color-text-secondary); text-align: center; padding: 20px; font-size: 14px; @@ -5232,7 +5170,7 @@ a.status-card { } a { - color: $highlight-text-color; + color: var(--color-text-brand); text-decoration: none; &:hover { @@ -5253,15 +5191,13 @@ a.status-card { } .follow_requests-unlocked_explanation { - background: var(--surface-background-color); - border-bottom: 1px solid var(--background-border-color); - contain: initial; - flex-grow: 0; + margin: 16px; + margin-bottom: 0; } .error-column { padding: 20px; - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); border-radius: 4px; display: flex; flex: 1 1 auto; @@ -5278,7 +5214,7 @@ a.status-card { &__message { text-align: center; - color: $darker-text-color; + color: var(--color-text-secondary); font-size: 15px; line-height: 22px; @@ -5287,7 +5223,7 @@ a.status-card { line-height: 33px; font-weight: 700; margin-bottom: 15px; - color: $primary-text-color; + color: var(--color-text-primary); } p { @@ -5340,9 +5276,9 @@ a.status-card { position: relative; margin-top: 5px; z-index: 2; - background: var(--dropdown-background-color); + background: var(--color-bg-elevated); backdrop-filter: $backdrop-blur-filter; - border: 1px solid var(--dropdown-border-color); + border: 1px solid var(--color-border-primary); box-shadow: var(--dropdown-shadow); border-radius: 5px; @@ -5367,8 +5303,8 @@ a.status-card { z-index: 4; top: -5px; inset-inline-start: -9px; - background: var(--dropdown-background-color); - border: 1px solid var(--dropdown-border-color); + background: var(--color-bg-elevated); + border: 1px solid var(--color-border-primary); border-radius: 4px; box-shadow: var(--dropdown-shadow); overflow: hidden; @@ -5383,7 +5319,7 @@ a.status-card { &:hover, &:focus, &:active { - background: var(--dropdown-border-color); + background: var(--color-border-primary); } } @@ -5400,7 +5336,7 @@ a.status-card { .upload-area { align-items: center; - background: color.change($base-overlay-background, $alpha: 0.8); + background: rgb(from var(--color-bg-overlay) r g b / 80%); display: flex; height: 100vh; justify-content: center; @@ -5434,8 +5370,8 @@ a.status-card { inset-inline-start: 0; z-index: -1; border-radius: 4px; - background: $ui-base-color; - box-shadow: 0 0 5px color.change($base-shadow-color, $alpha: 0.2); + background: var(--color-bg-elevated); + box-shadow: 0 0 5px var(--color-shadow-primary); } .upload-area__content { @@ -5444,15 +5380,15 @@ a.status-card { align-items: center; justify-content: center; text-align: center; - color: $secondary-text-color; + color: var(--color-text-primary); font-size: 18px; font-weight: 500; - border: 2px dashed $ui-base-lighter-color; + border: 2px dashed var(--color-border-primary); border-radius: 4px; } .upload-progress { - color: $darker-text-color; + color: var(--color-text-secondary); overflow: hidden; display: flex; gap: 8px; @@ -5462,7 +5398,7 @@ a.status-card { .icon { width: 24px; height: 24px; - color: $ui-highlight-color; + color: var(--color-text-brand); } span { @@ -5481,7 +5417,7 @@ a.status-card { width: 100%; height: 6px; border-radius: 6px; - background: var(--background-color); + background: var(--color-bg-primary); position: relative; margin-top: 5px; } @@ -5491,7 +5427,7 @@ a.status-card { inset-inline-start: 0; top: 0; height: 6px; - background: $ui-highlight-color; + background: var(--color-text-brand); border-radius: 6px; } @@ -5525,7 +5461,7 @@ a.status-card { &:focus-visible { img { - outline: $ui-button-icon-focus-outline; + outline: var(--outline-focus-default); } } } @@ -5539,9 +5475,8 @@ a.status-card { .language-dropdown__dropdown, .visibility-dropdown__dropdown { box-shadow: var(--dropdown-shadow); - background: var(--dropdown-background-color); - backdrop-filter: $backdrop-blur-filter; - border: 1px solid var(--dropdown-border-color); + background: var(--color-bg-elevated); + border: 1px solid var(--color-border-primary); padding: 4px; border-radius: 4px; overflow: hidden; @@ -5567,8 +5502,6 @@ a.status-card { .privacy-dropdown__option, .visibility-dropdown__option { - --dropdown-text-color: $primary-text-color; - font-size: 14px; line-height: 20px; letter-spacing: 0.25px; @@ -5578,7 +5511,7 @@ a.status-card { align-items: center; gap: 12px; border-radius: 4px; - color: var(--dropdown-text-color); + color: var(--color-text-primary); // Make sure adjacent hover/active states don't have a meeting radius. &:hover + &:is(:focus, .active), @@ -5597,13 +5530,13 @@ a.status-card { &:hover, &:active { - background: var(--dropdown-border-color); + background: var(--color-bg-secondary); } &:focus, &.active { - background: $ui-highlight-color; - color: var(--dropdown-text-color); + background: var(--color-bg-brand-base); + color: var(--color-text-on-brand-base); outline: 0; .privacy-dropdown__option__content, @@ -5612,7 +5545,7 @@ a.status-card { .visibility-dropdown__option__content, .visibility-dropdown__option__content strong, .visibility-dropdown__option__additional { - color: var(--dropdown-text-color); + color: var(--color-text-on-brand-base); } } @@ -5620,7 +5553,7 @@ a.status-card { display: flex; align-items: center; justify-content: center; - color: $darker-text-color; + color: var(--color-text-secondary); cursor: help; } } @@ -5635,10 +5568,10 @@ a.status-card { .privacy-dropdown__option__content, .visibility-dropdown__option__content { flex: 1 1 auto; - color: $darker-text-color; + color: var(--color-text-secondary); strong { - color: $primary-text-color; + color: var(--color-text-primary); font-weight: 500; display: block; @@ -5657,13 +5590,13 @@ a.status-card { .emoji-mart-search { padding: 10px; - background: var(--dropdown-background-color); + background: var(--color-bg-elevated); input { padding: 8px 12px; - background: $ui-base-color; - border: 1px solid var(--background-border-color); - color: $darker-text-color; + background: var(--color-bg-primary); + border: 1px solid var(--color-border-primary); + color: var(--color-text-secondary); @media screen and (width <= 600px) { font-size: 16px; @@ -5676,7 +5609,7 @@ a.status-card { .emoji-mart-search-icon { inset-inline-end: 15px; opacity: 1; - color: $darker-text-color; + color: var(--color-text-primary); .icon { width: 18px; @@ -5684,13 +5617,13 @@ a.status-card { } &:disabled { - opacity: 0.38; + color: var(--color-text-secondary); } } .emoji-mart-scroll { padding: 0 10px 10px; - background: var(--dropdown-background-color); + background: var(--color-bg-elevated); } &__results { @@ -5699,7 +5632,7 @@ a.status-card { align-items: center; gap: 0.5em; cursor: pointer; - color: $primary-text-color; + color: var(--color-text-primary); font-size: 14px; line-height: 20px; letter-spacing: 0.25px; @@ -5711,23 +5644,23 @@ a.status-card { text-overflow: ellipsis; &__common-name { - color: $darker-text-color; + color: var(--color-text-secondary); font-weight: 400; } &:active, &:hover { - background: var(--dropdown-border-color); + background: var(--color-bg-secondary); } &:focus, &.active { - background: $ui-highlight-color; - color: $primary-text-color; + background: var(--color-bg-brand-base); + color: var(--color-text-on-brand-base); outline: 0; .language-dropdown__dropdown__results__item__common-name { - color: $primary-text-color; + color: var(--color-text-on-brand-base); } } } @@ -5735,6 +5668,24 @@ a.status-card { } } +.visibility-modal { + &__quote-warning { + color: var(--color-text-primary); + background: var(--color-bg-warning-softer); + padding: 16px; + border-radius: 4px; + + h3 { + font-weight: 500; + margin-bottom: 4px; + } + + p { + font-size: 0.8em; + } + } +} + .visibility-dropdown { &__overlay[data-popper-placement] { z-index: 9999; @@ -5754,9 +5705,9 @@ a.status-card { &__button { display: flex; align-items: center; - color: $primary-text-color; - background: var(--input-background-color); - border: 1px solid var(--background-border-color); + color: var(--color-text-primary); + background: var(--color-bg-secondary-solid); + border: 1px solid var(--color-border-primary); padding: 8px 12px; width: 100%; text-align: left; @@ -5779,7 +5730,7 @@ a.status-card { &__helper { margin-top: 4px; font-size: 0.8em; - color: $dark-text-color; + color: var(--color-text-tertiary); } } @@ -5798,8 +5749,8 @@ a.status-card { inset-inline-start: 0; margin-top: -2px; width: 100%; - background: var(--input-background-color); - border: 1px solid var(--background-border-color); + background: var(--color-bg-elevated); + border: 1px solid var(--color-border-primary); border-radius: 0 0 4px 4px; box-shadow: var(--dropdown-shadow); z-index: 99; @@ -5808,7 +5759,7 @@ a.status-card { h4 { text-transform: uppercase; - color: $darker-text-color; + color: var(--color-text-secondary); font-weight: 500; padding: 0 10px; margin-bottom: 10px; @@ -5816,7 +5767,7 @@ a.status-card { .icon-button { padding: 0; - color: $darker-text-color; + color: var(--color-text-secondary); } .icon { @@ -5832,7 +5783,7 @@ a.status-card { } &__message { - color: $darker-text-color; + color: var(--color-text-secondary); padding: 0 10px; } @@ -5843,7 +5794,7 @@ a.status-card { border: 0; font: inherit; background: transparent; - color: $darker-text-color; + color: var(--color-text-secondary); padding: 10px; cursor: pointer; border-radius: 4px; @@ -5865,18 +5816,18 @@ a.status-card { &:focus, &:active, &.selected { - background: $ui-highlight-color; - color: $primary-text-color; + color: var(--color-text-on-brand-base); + background: var(--color-bg-brand-base); .icon-button { - color: $primary-text-color; + color: inherit; } } mark { background: transparent; font-weight: 700; - color: $primary-text-color; + color: var(--color-text-primary); } span { @@ -5939,7 +5890,7 @@ a.status-card { grid-area: 1 / 1; transition: all 100ms linear; transition-property: transform, opacity; - color: $darker-text-color; + color: var(--color-text-secondary); } .search__icon.icon-search { @@ -5968,7 +5919,7 @@ a.status-card { } &:focus-visible { - box-shadow: 0 0 0 2px $ui-button-focus-outline-color; + box-shadow: 0 0 0 2px var(--color-text-brand); } &[aria-hidden='true'] { @@ -5979,19 +5930,19 @@ a.status-card { } .search-results__section { - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); &:last-child { border-bottom: 0; } &__header { - border-bottom: 1px solid var(--background-border-color); - background: var(--surface-background-color); + border-bottom: 1px solid var(--color-border-primary); + background: var(--color-bg-tertiary); padding: 15px; font-weight: 500; font-size: 14px; - color: $darker-text-color; + color: var(--color-text-secondary); display: flex; justify-content: space-between; @@ -6002,7 +5953,7 @@ a.status-card { } button { - color: $highlight-text-color; + color: var(--color-text-brand); padding: 0; border: 0; background: 0; @@ -6024,7 +5975,7 @@ a.status-card { .search-results__info { padding: 20px; - color: $darker-text-color; + color: var(--color-text-secondary); text-align: center; } @@ -6040,7 +5991,7 @@ a.status-card { inset-inline-end: 0; bottom: 0; opacity: 0.9; - background: $base-overlay-background; + background: var(--color-bg-overlay); transition: background 0.5s; } @@ -6088,6 +6039,7 @@ a.status-card { width: 100%; height: 100%; position: relative; + touch-action: pan-y; &__buttons { position: absolute; @@ -6099,7 +6051,14 @@ a.status-card { align-items: center; .icon-button { - color: color.change($white, $alpha: 0.7); + --default-icon-color: var(--color-text-on-media); + --default-bg-color: transparent; + --hover-icon-color: var(--color-text-on-media); + --hover-bg-color: rgb( + from var(--color-text-on-media) r g b / + var(--overlay-strength-secondary) + ); + padding: 8px; .icon { @@ -6107,27 +6066,26 @@ a.status-card { height: 24px; filter: var(--overlay-icon-shadow); } - - &:hover, - &:focus, - &:active { - color: $white; - background-color: color.change($white, $alpha: 0.15); - } - - &:focus { - background-color: color.change($white, $alpha: 0.3); - } } } } .media-modal__closer { + display: flex; position: absolute; top: 0; inset-inline-start: 0; inset-inline-end: 0; bottom: 0; + + > div { + flex-shrink: 0; + overflow: auto; + display: flex; + align-items: center; + justify-content: center; + width: 100%; + } } .media-modal__navigation { @@ -6157,23 +6115,30 @@ a.status-card { background: transparent; box-sizing: border-box; border: 0; - color: color.change($white, $alpha: 0.7); + color: var(--color-text-on-media); cursor: pointer; display: flex; align-items: center; font-size: 24px; height: 20vmax; margin: auto 0; - padding: 30px 15px; + padding: 30px 5px; position: absolute; top: 0; bottom: 0; transform: scaleX(var(--text-x-direction)); - &:hover, - &:focus, - &:active { - color: $white; + .icon { + border-radius: 5px; + padding: 10px; + } + + &:hover .icon, + &:focus .icon, + &:active .icon { + background: rgb( + from var(--color-text-on-media) r g b / var(--overlay-strength-secondary) + ); } } @@ -6201,54 +6166,36 @@ a.status-card { padding: 16px; .icon-button { - color: $white; + --default-icon-color: var(--color-text-on-media); + --default-bg-color: transparent; + --hover-icon-color: var(--color-text-on-media); + --hover-bg-color: rgb( + from var(--color-text-on-media) r g b / var(--overlay-strength-brand) + ); .icon { filter: var(--overlay-icon-shadow); } - &:hover, - &:focus, - &:active { - color: $white; - background-color: color.change($white, $alpha: 0.15); + &.active { + --default-icon-color: var(--color-text-brand); + --hover-icon-color: var(--color-text-brand); + --hover-bg-color: var(--color-bg-brand-soft); } - &:focus { - background-color: color.change($white, $alpha: 0.3); + &.star-icon.active { + --default-icon-color: var(--color-text-favourite-highlight); + --hover-icon-color: var(--color-text-favourite-highlight); + --hover-bg-color: rgb( + from var(--color-text-favourite-highlight) r g b / + var(--overlay-strength-brand) + ); } - &.active { - color: $highlight-text-color; - - &:hover, - &:focus, - &:active { - background: color.change($highlight-text-color, $alpha: 0.15); - } + &.disabled { + --default-icon-color: var(--color-text-on-media); + --default-bg-color: transparent; - &:focus { - background: color.change($highlight-text-color, $alpha: 0.3); - } - } - - &.star-icon.active { - color: $gold-star; - - &:hover, - &:focus, - &:active { - background: color.change($gold-star, $alpha: 0.15); - } - - &:focus { - background: color.change($gold-star, $alpha: 0.3); - } - } - - &.disabled { - color: $white; - background-color: transparent; cursor: default; opacity: 0.4; } @@ -6264,9 +6211,8 @@ a.status-card { .media-modal__page-dot { flex: 0 0 auto; - background-color: $white; + background-color: rgb(from var(--color-text-on-media) r g b / 40%); filter: var(--overlay-icon-shadow); - opacity: 0.4; height: 6px; width: 6px; border-radius: 50%; @@ -6274,15 +6220,16 @@ a.status-card { padding: 0; border: 0; font-size: 0; - transition: opacity 0.2s ease-in-out; + transition: background-color 0.2s ease-in-out; &.active { - opacity: 1; + background-color: var(--color-text-on-media); } &:focus { - outline: 0; - background-color: $highlight-text-color; + opacity: 1; + outline: 2px solid var(--color-text-on-media); + outline-offset: 2px; } } @@ -6290,9 +6237,9 @@ a.status-card { width: 588px; min-height: 478px; flex-direction: column; - background: var(--modal-background-color); + background: var(--color-bg-primary); backdrop-filter: $backdrop-blur-filter; - border: 1px solid var(--modal-border-color); + border: 1px solid var(--color-border-primary); border-radius: 16px; &__error { @@ -6334,9 +6281,9 @@ a.status-card { gap: 8px; padding: 24px; flex-direction: column; - background: var(--modal-background-color); + background: var(--color-bg-primary); backdrop-filter: $backdrop-blur-filter; - border: 1px solid var(--modal-border-color); + border: 1px solid var(--color-border-primary); } &__top { @@ -6362,12 +6309,12 @@ a.status-card { align-items: center; font-size: 14px; line-height: 20px; - color: $darker-text-color; + color: var(--color-text-secondary); &__icon { border-radius: 64px; - background: $ui-highlight-color; - color: $white; + color: var(--color-text-on-brand-base); + background: var(--color-bg-brand-base); display: flex; align-items: center; justify-content: center; @@ -6384,31 +6331,34 @@ a.status-card { h1 { font-size: 22px; line-height: 28px; - color: $primary-text-color; + color: var(--color-text-primary); } } &__confirmation { font-size: 14px; line-height: 20px; - color: $darker-text-color; + color: var(--color-text-secondary); h1 { font-size: 16px; line-height: 24px; - color: $primary-text-color; + color: var(--color-text-primary); font-weight: 500; - margin-bottom: 8px; + + &:not(:only-child) { + margin-bottom: 8px; + } } strong { font-weight: 700; - color: $primary-text-color; + color: var(--color-text-primary); } } &__status { - border: 1px solid var(--modal-border-color); + border: 1px solid var(--color-border-primary); border-radius: 8px; padding: 8px; cursor: pointer; @@ -6418,7 +6368,7 @@ a.status-card { align-items: center; gap: 4px; margin-bottom: 8px; - color: $dark-text-color; + color: var(--color-text-tertiary); bdi { color: inherit; @@ -6429,7 +6379,7 @@ a.status-card { display: -webkit-box; font-size: 15px; line-height: 22px; - color: $dark-text-color; + color: var(--color-text-tertiary); line-clamp: 4; -webkit-line-clamp: 4; -webkit-box-orient: vertical; @@ -6446,7 +6396,7 @@ a.status-card { margin-top: 0; font-size: 15px; line-height: 22px; - color: $dark-text-color; + color: var(--color-text-tertiary); } } @@ -6468,7 +6418,7 @@ a.status-card { } &--deemphasized { - color: $secondary-text-color; + color: var(--color-text-primary); } &__icon { @@ -6520,7 +6470,7 @@ a.status-card { } &.active { - background: var(--modal-background-variant-color); + background: var(--color-bg-secondary); padding-top: 24px; .safety-action-modal__bottom__collapsible { @@ -6538,7 +6488,7 @@ a.status-card { &__hint { font-size: 14px; line-height: 20px; - color: $dark-text-color; + color: var(--color-text-tertiary); } .link-button { @@ -6552,14 +6502,14 @@ a.status-card { width: 588px; max-height: 80vh; flex-direction: column; - background: var(--modal-background-color); + background: var(--color-bg-elevated); backdrop-filter: $backdrop-blur-filter; - border: 1px solid var(--modal-border-color); + border: 1px solid var(--color-border-primary); border-radius: 16px; &__header { box-sizing: border-box; - border-bottom: 1px solid var(--modal-border-color); + border-bottom: 1px solid var(--color-border-primary); display: flex; align-items: center; justify-content: space-between; @@ -6583,7 +6533,7 @@ a.status-card { &__description { margin: 24px 24px 0; - color: $darker-text-color; + color: var(--color-text-secondary); a { color: inherit; @@ -6604,7 +6554,7 @@ a.status-card { align-items: center; justify-content: center; padding: 24px; - background: #000; + background: var(--color-bg-media-base); img { display: block; @@ -6612,7 +6562,7 @@ a.status-card { img, .gifv video { - outline: 1px solid var(--media-outline-color); + outline: 1px solid var(--color-border-media); outline-offset: -1px; border-radius: 8px; } @@ -6636,9 +6586,9 @@ a.status-card { } &__popout { - background: var(--dropdown-background-color); + background: var(--color-bg-elevated); backdrop-filter: $backdrop-blur-filter; - border: 1px solid var(--dropdown-border-color); + border: 1px solid var(--color-border-primary); box-shadow: var(--dropdown-shadow); max-width: 320px; padding: 16px; @@ -6646,7 +6596,7 @@ a.status-card { z-index: 9999 !important; font-size: 14px; line-height: 20px; - color: $darker-text-color; + color: var(--color-text-secondary); } .copy-paste-text { @@ -6658,22 +6608,16 @@ a.status-card { display: inline-flex; align-items: center; gap: 4px; - - kbd { - padding: 3px 5px; - border: 1px solid var(--background-border-color); - border-radius: 4px; - } } .boost-modal, .report-modal, .actions-modal, .compare-history-modal { - background: var(--background-color); - color: $primary-text-color; + background: var(--color-bg-primary); + color: var(--color-text-primary); border-radius: 4px; - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); overflow: hidden; max-width: 90vw; width: 480px; @@ -6697,43 +6641,20 @@ a.status-card { } } -.boost-modal__action-bar { - display: flex; - justify-content: space-between; - align-items: center; - background: $ui-secondary-color; - padding: 15px; - - & > div { - flex: 1 1 auto; - text-align: end; - color: $lighter-text-color; - padding-inline-end: 10px; - } - - .icon { - vertical-align: middle; - } - - .button { - flex: 0 0 auto; - } -} - .report-modal { width: 90vw; max-width: 700px; - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); } .report-dialog-modal { max-width: 90vw; width: 480px; height: 80vh; - background: var(--background-color); - color: $primary-text-color; + background: var(--color-bg-primary); + color: var(--color-text-primary); border-radius: 4px; - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); overflow: hidden; position: relative; flex-direction: column; @@ -6741,7 +6662,7 @@ a.status-card { &__container { box-sizing: border-box; - border-top: 1px solid var(--background-border-color); + border-top: 1px solid var(--color-border-primary); padding: 20px; flex-grow: 1; display: flex; @@ -6771,12 +6692,12 @@ a.status-card { &__lead { font-size: 17px; line-height: 22px; - color: $secondary-text-color; + color: var(--color-text-primary); margin-bottom: 30px; a { text-decoration: none; - color: $highlight-text-color; + color: var(--color-text-brand); font-weight: 500; &:hover { @@ -6801,12 +6722,12 @@ a.status-card { } .status__content a { - color: $highlight-text-color; + color: var(--color-text-brand); } .status__content, .status__content p { - color: $primary-text-color; + color: var(--color-text-primary); } .dialog-option { @@ -6815,8 +6736,7 @@ a.status-card { } .dialog-option .poll__input { - border-color: $darker-text-color; - color: $ui-secondary-color; + color: var(--color-text-secondary); display: inline-flex; align-items: center; justify-content: center; @@ -6829,20 +6749,21 @@ a.status-card { &:active, &:focus, &:hover { - border-color: $valid-value-color; + border-color: var(--color-text-success); border-width: 4px; } &.active { - border-color: $valid-value-color; - background: $valid-value-color; + color: var(--color-text-on-success-base); + background: var(--color-bg-success-base); + border-color: var(--color-bg-success-base); } } .poll__option.dialog-option { padding: 15px 0; flex: 0 0 auto; - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); &:last-child { border-bottom: 0; @@ -6850,13 +6771,13 @@ a.status-card { & > .poll__option__text { font-size: 13px; - color: $secondary-text-color; + color: var(--color-text-primary); strong { font-size: 17px; font-weight: 500; line-height: 22px; - color: $primary-text-color; + color: var(--color-text-primary); display: block; margin-bottom: 4px; @@ -6875,15 +6796,15 @@ a.status-card { display: block; box-sizing: border-box; width: 100%; - color: $primary-text-color; - background: $ui-base-color; + color: var(--color-text-primary); + background: var(--color-bg-secondary); padding: 10px; font-family: inherit; font-size: 17px; line-height: 22px; resize: vertical; border: 0; - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); outline: 0; border-radius: 4px; margin: 20px 0; @@ -6908,16 +6829,16 @@ a.status-card { } .button.button-secondary { - border-color: $ui-button-destructive-background-color; - color: $ui-button-destructive-background-color; + border-color: var(--color-text-error); + color: var(--color-text-error); flex: 0 0 auto; &:hover, &:focus, &:active { - background: $ui-button-destructive-background-color; - border-color: $ui-button-destructive-background-color; - color: $white; + color: var(--color-text-on-error-base); + background: var(--color-bg-error-base); + border-color: var(--color-bg-error-base); } } @@ -6936,55 +6857,6 @@ a.status-card { } } -.report-modal__container { - display: flex; - border-top: 1px solid var(--background-border-color); - - @media screen and (width <= 480px) { - flex-wrap: wrap; - overflow-y: auto; - } -} - -.report-modal__statuses, -.report-modal__comment { - box-sizing: border-box; - width: 50%; - min-width: 50%; - - @media screen and (width <= 480px) { - width: 100%; - } -} - -.report-modal__statuses, -.focal-point-modal__content { - flex: 1 1 auto; - min-height: 20vh; - max-height: 80vh; - overflow-y: auto; - overflow-x: hidden; - - .status__content a { - color: $highlight-text-color; - } - - .status__content, - .status__content p { - color: $inverted-text-color; - } - - @media screen and (width <= 480px) { - max-height: 10vh; - } -} - -.focal-point-modal__content { - @media screen and (width <= 480px) { - max-height: 40vh; - } -} - .setting-divider { background: transparent; border: 0; @@ -6996,9 +6868,9 @@ a.status-card { .actions-modal { border-radius: 8px 8px 0 0; - background: var(--dropdown-background-color); + background: var(--color-bg-elevated); backdrop-filter: $backdrop-blur-filter; - border-color: var(--dropdown-border-color); + border-color: var(--color-border-primary); box-shadow: var(--dropdown-shadow); max-height: 80vh; max-width: 80vw; @@ -7025,7 +6897,7 @@ a.status-card { &:hover, &:active, &:focus { - background: var(--dropdown-border-color); + background: var(--color-bg-brand-softer); } } } @@ -7061,7 +6933,7 @@ a.status-card { .compare-history-modal { .report-modal__target { - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); } &__container { @@ -7071,7 +6943,7 @@ a.status-card { } .status__content { - color: $secondary-text-color; + color: var(--color-text-primary); font-size: 19px; line-height: 24px; @@ -7082,13 +6954,13 @@ a.status-card { } a { - color: $highlight-text-color; + color: var(--color-text-brand); } hr { height: 0.25rem; padding: 0; - background-color: $ui-secondary-color; + background-color: var(--color-text-primary); border: 0; margin: 20px 0; } @@ -7102,7 +6974,7 @@ a.status-card { } .loading-bar { - background-color: $highlight-text-color; + background-color: var(--color-bg-brand-base); height: 3px; position: fixed; top: 0; @@ -7120,10 +6992,10 @@ a.status-card { &__pill { display: block; - color: $white; - border: 0; - background: color.change($black, $alpha: 0.65); + color: var(--color-text-on-media); + background: var(--color-bg-media); backdrop-filter: $backdrop-blur-filter; + border: 0; padding: 3px 12px; border-radius: 99px; font-size: 14px; @@ -7145,10 +7017,10 @@ a.status-card { .relationship-tag { display: block; text-align: center; - color: $white; - border: 0; - background: color.change($black, $alpha: 0.65); + color: var(--color-text-on-media); + background: var(--color-bg-media); backdrop-filter: $backdrop-blur-filter; + border: 0; padding: 3px 8px; border-radius: 4px; font-size: 12px; @@ -7169,8 +7041,8 @@ a.status-card { } .info-tooltip { - color: $white; - background: color.change($black, $alpha: 0.65); + color: var(--color-text-on-media); + background: var(--color-bg-media); backdrop-filter: $backdrop-blur-filter; border-radius: 4px; box-shadow: var(--dropdown-shadow); @@ -7180,18 +7052,12 @@ a.status-card { max-width: 22em; max-height: 30em; overflow-y: auto; + z-index: 10; &--solid { - color: var(--nested-card-text); - background: - /* This is a bit of a silly hack for layering two background colours - * since --nested-card-background is too transparent for a tooltip */ - linear-gradient( - var(--nested-card-background), - var(--nested-card-background) - ), - linear-gradient(var(--background-color), var(--background-color)); - border: var(--nested-card-border); + color: var(--color-text-primary); + background: var(--color-bg-elevated); + border: 1px solid var(--color-border-primary); } h4 { @@ -7216,17 +7082,17 @@ a.status-card { .attachment-list { display: flex; font-size: 14px; - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); border-radius: 4px; margin-top: 16px; overflow: hidden; &__icon { flex: 0 0 auto; - color: $dark-text-color; + color: var(--color-text-tertiary); padding: 8px 18px; cursor: default; - border-inline-end: 1px solid var(--background-border-color); + border-inline-end: 1px solid var(--color-border-primary); display: flex; flex-direction: column; align-items: center; @@ -7249,7 +7115,7 @@ a.status-card { a { text-decoration: none; - color: $dark-text-color; + color: var(--color-text-tertiary); font-weight: 500; &:hover { @@ -7267,7 +7133,7 @@ a.status-card { } .icon { - color: $dark-text-color; + color: var(--color-text-tertiary); vertical-align: middle; } } @@ -7287,6 +7153,24 @@ a.status-card { grid-template-rows: 1fr 1fr; gap: 2px; + &--layout-1 { + // The size of single images is determined by their + // aspect-ratio, applied via inline style attribute + width: initial; + + // Prevent extremely tall images from essentially becoming invisible + min-width: 120px; + max-height: var(--max-media-height-small); + + @container (width > 500px) { + max-height: var(--max-media-height-large); + } + + .detailed-status & { + max-height: calc(2 * var(--max-media-height-large)); + } + } + &--layout-2 { & > .media-gallery__item:nth-child(1) { border-end-end-radius: 0; @@ -7357,7 +7241,7 @@ a.status-card { position: relative; border-radius: 8px; overflow: hidden; - outline: 1px solid var(--media-outline-color); + outline: 1px solid var(--color-border-media); outline-offset: -1px; z-index: 1; @@ -7393,8 +7277,8 @@ a.status-card { } .icon { - color: $white; - filter: var(--overlay-icon-shadow); + color: var(--color-text-on-media); + filter: drop-shadow(0 0 6px var(--color-bg-media-base)); } } @@ -7407,7 +7291,7 @@ a.status-card { cursor: pointer; display: block; text-decoration: none; - color: $secondary-text-color; + color: var(--color-text-primary); position: relative; z-index: -1; @@ -7430,7 +7314,7 @@ a.status-card { top: 0; inset-inline-start: 0; z-index: -2; - background: $base-overlay-background; + background: var(--color-bg-overlay); &--hidden { display: none; @@ -7467,6 +7351,12 @@ a.status-card { } .audio-player { + /* These are only fallback values, the AudioPlayer component inserts + * the real colours dynamically as inline styles */ + --player-foreground-color: var(--color-text-on-media); + --player-background-color: var(--color-bg-media-base); + --player-accent-color: var(--color-text-brand); + box-sizing: border-box; container: audio-player / inline-size; position: relative; @@ -7476,9 +7366,9 @@ a.status-card { width: 100%; aspect-ratio: 16 / 9; color: var(--player-foreground-color); - background: var(--player-background-color, var(--background-color)); + background: var(--player-background-color); border-radius: 8px; - outline: 1px solid var(--media-outline-color); + outline: 1px solid var(--color-border-media); outline-offset: -1px; &__controls { @@ -7611,17 +7501,20 @@ a.status-card { .video-player { overflow: hidden; position: relative; - background: $base-shadow-color; - max-width: 100%; + color: var(--color-text-on-media); + background: var(--color-bg-media); + max-height: var(--max-media-height-small); border-radius: 8px; box-sizing: border-box; - color: $white; display: flex; - align-items: center; - outline: 1px solid var(--media-outline-color); + outline: 1px solid var(--color-border-media); outline-offset: -1px; z-index: 2; + @container (width > 500px) { + max-height: var(--max-media-height-large); + } + video { display: block; z-index: -2; @@ -7652,8 +7545,8 @@ a.status-card { box-sizing: border-box; background: linear-gradient( 0deg, - color.change($base-shadow-color, $alpha: 0.85) 0, - color.change($base-shadow-color, $alpha: 0.45) 60%, + rgb(from var(--color-bg-media-base) r g b / 85%) 0, + rgb(from var(--color-bg-media-base) r g b / 45%) 60%, transparent ); padding: 0 15px; @@ -7693,8 +7586,8 @@ a.status-card { height: 100%; z-index: 4; border: 0; - background: $base-overlay-background; - color: $darker-text-color; + background: var(--color-bg-primary); + color: var(--color-text-secondary); transition: none; pointer-events: none; @@ -7705,7 +7598,11 @@ a.status-card { &:hover, &:active, &:focus { - color: lighten($darker-text-color, 7%); + color: color-mix( + in oklab, + var(--color-text-primary), + var(--color-text-secondary) + ); } } @@ -7749,13 +7646,13 @@ a.status-card { flex: 0 0 auto; background: transparent; border: 0; - color: color.change($white, $alpha: 0.75); + color: rgb(from var(--color-text-on-media) r g b / 75%); font-weight: 500; &:active, &:hover, &:focus { - color: $white; + color: var(--color-text-on-media); } } } @@ -7774,24 +7671,16 @@ a.status-card { &__time-sep, &__time-total, &__time-current { + color: var(--color-text-on-media); font-size: 14px; font-weight: 500; } - &__time-current { - color: $white; - } - &__time-sep { display: inline-block; margin: 0 6px; } - &__time-sep, - &__time-total { - color: $white; - } - &__volume { flex: 0 0 auto; display: inline-flex; @@ -7813,7 +7702,7 @@ a.status-card { &::before { content: ''; width: 50px; - background: color.change($white, $alpha: 0.35); + background: rgb(from var(--color-text-on-media) r g b / 35%); border-radius: 4px; display: block; position: absolute; @@ -7831,7 +7720,7 @@ a.status-card { inset-inline-start: 0; top: 50%; transform: translate(0, -50%); - background: $white; + background: var(--color-text-on-media); } &__handle { @@ -7844,8 +7733,8 @@ a.status-card { inset-inline-start: 0; margin-inline-start: -6px; transform: translate(0, -50%); - background: $white; - box-shadow: 1px 2px 6px color.change($base-shadow-color, $alpha: 0.2); + background: var(--color-text-on-media); + box-shadow: 1px 2px 6px var(--color-shadow-primary); opacity: 0; .no-reduce-motion & { @@ -7865,7 +7754,7 @@ a.status-card { text-decoration: none; font-size: 14px; font-weight: 500; - color: $white; + color: var(--color-text-on-media); &:hover, &:active, @@ -7883,7 +7772,7 @@ a.status-card { &::before { content: ''; width: 100%; - background: color.change($white, $alpha: 0.35); + background: rgb(from var(--color-text-on-media) r g b / 35%); border-radius: 4px; display: block; position: absolute; @@ -7898,11 +7787,11 @@ a.status-card { height: 4px; border-radius: 4px; top: 14px; - background: $white; + background: var(--color-text-on-media); } &__buffer { - background: color.change($white, $alpha: 0.2); + background: rgb(from var(--color-text-on-media) r g b / 20%); } &__handle { @@ -7914,8 +7803,8 @@ a.status-card { height: 12px; top: 10px; margin-inline-start: -6px; - background: $white; - box-shadow: 1px 2px 6px color.change($base-shadow-color, $alpha: 0.2); + background: var(--color-text-on-media); + box-shadow: 1px 2px 6px var(--color-shadow-primary); .no-reduce-motion & { transition: opacity 0.1s ease; @@ -7939,9 +7828,9 @@ a.status-card { top: 50%; inset-inline-start: 50%; transform: translate(-50%, -50%); - background: color.change($base-shadow-color, $alpha: 0.45); + color: var(--color-text-on-media); + background: var(--color-bg-media); backdrop-filter: $backdrop-blur-filter; - color: $white; border-radius: 8px; padding: 16px 24px; display: flex; @@ -7988,7 +7877,7 @@ a.status-card { .scrollable .account-card__title__avatar { img { - border: 2px solid var(--background-color); + border: 2px solid var(--color-bg-primary); } .account__avatar { @@ -8003,7 +7892,7 @@ a.status-card { } .scrollable .account-card__bio::after { - background: linear-gradient(to left, var(--background-color), transparent); + background: linear-gradient(to left, var(--color-bg-primary), transparent); } .account-gallery__container { @@ -8023,7 +7912,7 @@ a.status-card { .notification__filter-bar, .account__section-headline { - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); border-top: 0; cursor: default; display: flex; @@ -8044,7 +7933,7 @@ a.status-card { a { display: block; flex: 1 1 auto; - color: $darker-text-color; + color: var(--color-text-secondary); padding: 15px 0; font-size: 14px; font-weight: 500; @@ -8055,7 +7944,7 @@ a.status-card { white-space: nowrap; &.active { - color: $primary-text-color; + color: var(--color-text-primary); &::before { display: block; @@ -8067,7 +7956,7 @@ a.status-card { width: 40px; height: 3px; border-radius: 4px 4px 0 0; - background: $highlight-text-color; + background: var(--color-text-brand); } } } @@ -8079,7 +7968,7 @@ a.status-card { } .filter-form { - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); &__column { display: flex; @@ -8109,7 +7998,7 @@ a.status-card { text-overflow: ellipsis; cursor: pointer; gap: 10px; - color: $secondary-text-color; + color: var(--color-text-primary); input[type='radio'], input[type='checkbox'] { @@ -8121,7 +8010,7 @@ a.status-card { align-items: center; justify-content: center; position: relative; - border: 2px solid $secondary-text-color; + border: 2px solid var(--color-text-primary); box-sizing: border-box; width: 20px; height: 20px; @@ -8130,7 +8019,7 @@ a.status-card { &.checked, &.indeterminate { - border-color: $ui-highlight-color; + border-color: var(--color-text-brand); } .icon { @@ -8146,7 +8035,7 @@ a.status-card { border-radius: 50%; width: calc(100% - 4px); height: calc(100% - 4px); - background: $ui-highlight-color; + background: var(--color-text-brand); } .check-box { @@ -8157,8 +8046,8 @@ a.status-card { &.checked, &.indeterminate { - background: $ui-highlight-color; - color: $white; + background: var(--color-bg-brand-base); + color: var(--color-text-on-brand-base); } } } @@ -8175,11 +8064,11 @@ noscript { div { font-size: 14px; margin: 30px auto; - color: $secondary-text-color; + color: var(--color-text-primary); max-width: 400px; a { - color: $highlight-text-color; + color: var(--color-text-brand); text-decoration: underline; &:hover { @@ -8207,13 +8096,13 @@ noscript { .follow-request-banner, .account-memorial-banner { padding: 20px; - background: var(--surface-background-color); + background: var(--color-bg-tertiary); display: flex; align-items: center; flex-direction: column; &__message { - color: $darker-text-color; + color: var(--color-text-secondary); padding: 8px 0; padding-top: 0; padding-bottom: 4px; @@ -8250,7 +8139,7 @@ noscript { justify-content: flex-start; gap: 15px; align-items: center; - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); border-top: 0; label { @@ -8274,7 +8163,7 @@ noscript { inset-inline-start: 0; width: 100%; height: 100%; - background: color.change($base-overlay-background, $alpha: 0.5); + background: rgb(from var(--color-bg-overlay) r g b / 50%); } .focal-point { @@ -8294,20 +8183,20 @@ noscript { width: 100px; height: 100px; transform: translate(-50%, -50%); - border: 2px solid #fff; + border: 2px solid var(--color-text-on-media); border-radius: 50%; - box-shadow: 0 0 0 9999em color.change($base-shadow-color, $alpha: 0.35); + box-shadow: 0 0 0 9999em var(--color-shadow-primary); pointer-events: none; } } .account__header__content { - color: $darker-text-color; + color: var(--color-text-secondary); font-size: 14px; font-weight: 400; overflow: hidden; word-break: normal; - word-wrap: break-word; + overflow-wrap: break-word; p { margin-bottom: 20px; @@ -8354,8 +8243,8 @@ noscript { overflow: hidden; height: 145px; position: relative; - background: darken($ui-base-color, 4%); - border-bottom: 1px solid var(--background-border-color); + background: var(--color-bg-tertiary); + border-bottom: 1px solid var(--color-border-primary); img { object-fit: cover; @@ -8369,15 +8258,15 @@ noscript { &__bar { position: relative; padding: 0 20px; - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); .avatar { display: block; flex: 0 0 auto; .account__avatar { - background: var(--background-color); - border: 1px solid var(--background-border-color); + background: var(--color-bg-primary); + border: 1px solid var(--color-border-primary); border-radius: var(--avatar-border-radius); } } @@ -8403,47 +8292,6 @@ noscript { overflow: hidden; margin-inline-start: -2px; // aligns the pfp with content below - &__buttons { - display: flex; - align-items: center; - gap: 8px; - padding-top: 55px; - overflow: hidden; - - .button { - flex-shrink: 1; - white-space: nowrap; - min-width: 80px; - } - - .icon-button { - border: 1px solid var(--background-border-color); - border-radius: 4px; - box-sizing: content-box; - padding: 5px; - - .icon { - width: 24px; - height: 24px; - } - - &.copied { - border-color: $valid-value-color; - } - } - - .optional { - @container account-header (max-width: 372px) { - display: none; - } - - // Fallback for older browsers with no container queries support - @media screen and (max-width: (372px + 55px)) { - display: none; - } - } - } - &__name { margin-top: 16px; margin-bottom: 16px; @@ -8456,7 +8304,7 @@ noscript { h1 { font-size: 17px; line-height: 22px; - color: $primary-text-color; + color: var(--color-text-primary); font-weight: 600; overflow: hidden; white-space: nowrap; @@ -8468,7 +8316,7 @@ noscript { gap: 4px; font-size: 14px; line-height: 20px; - color: $darker-text-color; + color: var(--color-text-secondary); font-weight: 400; overflow: hidden; text-overflow: ellipsis; @@ -8492,21 +8340,84 @@ noscript { } } + &__follow-button { + flex-grow: 1; + } + + &__buttons { + display: flex; + align-items: center; + gap: 8px; + + $button-breakpoint: 420px; + $button-fallback-breakpoint: #{$button-breakpoint} + 55px; + + &--desktop { + margin-top: 55px; + + @container (width < #{$button-breakpoint}) { + display: none; + } + + @supports (not (container-type: inline-size)) { + @media (max-width: #{$button-fallback-breakpoint}) { + display: none; + } + } + } + + &--mobile { + margin-block: 16px; + + @container (width >= #{$button-breakpoint}) { + display: none; + } + + @supports (not (container-type: inline-size)) { + @media (min-width: (#{$button-fallback-breakpoint} + 1px)) { + display: none; + } + } + } + + .button { + flex-shrink: 1; + white-space: nowrap; + min-width: 80px; + } + + .icon-button { + border: 1px solid var(--color-border-primary); + border-radius: 4px; + box-sizing: content-box; + padding: 5px; + + .icon { + width: 24px; + height: 24px; + } + + &.copied { + border-color: var(--color-text-success); + } + } + } + &__bio { .account__header__content { - color: $primary-text-color; + color: var(--color-text-primary); } .account__header__fields { margin: 0; margin-top: 16px; border-radius: 4px; - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); dl { display: block; padding: 11px 16px; - border-bottom-color: var(--background-border-color); + border-bottom-color: var(--color-border-primary); } dd, @@ -8521,15 +8432,15 @@ noscript { width: auto; background: transparent; text-transform: uppercase; - color: $dark-text-color; + color: var(--color-text-tertiary); } dd { - color: $darker-text-color; + color: var(--color-text-secondary); } a { - color: lighten($ui-highlight-color, 8%); + color: var(--color-text-brand); } .icon { @@ -8538,7 +8449,7 @@ noscript { } .verified { - border: 1px solid color.change($valid-value-color, $alpha: 0.5); + border: 1px solid var(--color-text-success); margin-top: -1px; margin-inline: -1px; @@ -8555,7 +8466,7 @@ noscript { dt, dd { - color: $valid-value-color; + color: var(--color-text-success); } dd { @@ -8569,7 +8480,7 @@ noscript { } a { - color: $valid-value-color; + color: var(--color-text-success); } } } @@ -8580,28 +8491,28 @@ noscript { &__links { font-size: 14px; - color: $darker-text-color; + color: var(--color-text-secondary); margin: 0 -10px; padding-top: 16px; padding-bottom: 10px; a { display: inline-block; - color: $darker-text-color; + color: var(--color-text-secondary); text-decoration: none; padding: 5px 10px; font-weight: 500; strong { font-weight: 700; - color: $primary-text-color; + color: var(--color-text-primary); } } } } &__account-note { - color: $primary-text-color; + color: var(--color-text-primary); font-size: 14px; font-weight: 400; margin-bottom: 10px; @@ -8624,7 +8535,7 @@ noscript { display: block; font-size: 12px; font-weight: 500; - color: $darker-text-color; + color: var(--color-text-secondary); text-transform: uppercase; margin-bottom: 5px; } @@ -8633,7 +8544,7 @@ noscript { display: block; box-sizing: border-box; width: calc(100% + 20px); - color: $secondary-text-color; + color: var(--color-text-primary); background: transparent; padding: 10px; margin: 0 -10px; @@ -8645,12 +8556,12 @@ noscript { border-radius: 4px; &::placeholder { - color: $dark-text-color; + color: var(--color-text-tertiary); opacity: 1; } &:focus { - background: $ui-base-color; + background: var(--color-bg-brand-softer); } } } @@ -8660,12 +8571,12 @@ noscript { align-items: center; gap: 10px; margin-block: 16px; - color: $darker-text-color; + color: var(--color-text-secondary); a:any-link { font-weight: 500; text-decoration: none; - color: $primary-text-color; + color: var(--color-text-primary); } } } @@ -8683,7 +8594,7 @@ noscript { .verified-badge { display: inline-flex; align-items: center; - color: $valid-value-color; + color: var(--color-text-success); gap: 4px; overflow: hidden; white-space: nowrap; @@ -8710,12 +8621,12 @@ noscript { display: flex; align-items: center; padding: 16px; - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); gap: 8px; &__name { flex: 1 1 auto; - color: $dark-text-color; + color: var(--color-text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; @@ -8725,7 +8636,7 @@ noscript { } a { - color: $darker-text-color; + color: var(--color-text-primary); text-decoration: none; font-size: 14px; font-weight: 500; @@ -8749,7 +8660,7 @@ noscript { font-size: 24px; font-weight: 500; text-align: end; - color: $secondary-text-color; + color: var(--color-text-primary); text-decoration: none; } @@ -8758,60 +8669,60 @@ noscript { width: 50px; path:first-child { - fill: color.change($highlight-text-color, $alpha: 0.25) !important; + fill: var(--color-graph-primary-fill) !important; fill-opacity: 1 !important; } path:last-child { - stroke: lighten($highlight-text-color, 6%) !important; + stroke: var(--color-graph-primary-stroke) !important; fill: none !important; } } &--requires-review { .trends__item__name { - color: $gold-star; + color: var(--color-text-warning); a { - color: $gold-star; + color: var(--color-text-warning); } } .trends__item__current { - color: $gold-star; + color: var(--color-text-warning); } .trends__item__sparkline { path:first-child { - fill: color.change($gold-star, $alpha: 0.25) !important; + fill: var(--color-graph-warning-fill) !important; } path:last-child { - stroke: lighten($gold-star, 6%) !important; + stroke: var(--color-graph-warning-stroke) !important; } } } &--disabled { .trends__item__name { - color: lighten($ui-base-color, 12%); + color: var(--color-text-disabled); a { - color: lighten($ui-base-color, 12%); + color: var(--color-text-disabled); } } .trends__item__current { - color: lighten($ui-base-color, 12%); + color: var(--color-text-disabled); } .trends__item__sparkline { path:first-child { - fill: rgba(lighten($ui-base-color, 12%), 0.25) !important; + fill: var(--color-graph-disabled-fill) !important; } path:last-child { - stroke: lighten(lighten($ui-base-color, 12%), 6%) !important; + stroke: var(--color-graph-disabled-stroke) !important; } } } @@ -8824,12 +8735,12 @@ noscript { .conversation { display: flex; - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); padding: 5px; padding-bottom: 0; &:focus { - background: lighten($ui-base-color, 2%); + background: var(--color-bg-secondary); outline: 0; } @@ -8843,7 +8754,7 @@ noscript { &__unread { display: inline-block; - background: $highlight-text-color; + background: var(--color-text-brand); border-radius: 50%; width: 0.625rem; height: 0.625rem; @@ -8865,12 +8776,12 @@ noscript { &__relative-time { font-size: 15px; - color: $darker-text-color; + color: var(--color-text-secondary); padding-inline-start: 15px; } &__names { - color: $darker-text-color; + color: var(--color-text-secondary); font-size: 15px; white-space: nowrap; overflow: hidden; @@ -8880,7 +8791,7 @@ noscript { flex-grow: 1; a { - color: $primary-text-color; + color: var(--color-text-primary); text-decoration: none; &:hover, @@ -8898,10 +8809,25 @@ noscript { } .announcements { - background: lighten($ui-base-color, 8%); - font-size: 13px; - display: flex; - align-items: flex-end; + width: calc(100% - 124px); + flex: 0 0 auto; + position: relative; + overflow: hidden; + + .layout-multiple-columns & { + width: 100%; + } + + @media screen and (max-width: (124px + 300px)) { + width: 100%; + } + + &__root { + background: var(--color-bg-brand-softer); + font-size: 13px; + display: flex; + align-items: flex-end; + } &__mastodon { width: 124px; @@ -8912,57 +8838,54 @@ noscript { } } - &__container { - width: calc(100% - 124px); - flex: 0 0 auto; - position: relative; - - @media screen and (max-width: (124px + 300px)) { - width: 100%; - } + &__slides { + display: flex; + flex-wrap: nowrap; + align-items: start; } - &__item { + &__slide { box-sizing: border-box; width: 100%; + flex: 0 0 100%; padding: 15px; position: relative; font-size: 15px; line-height: 20px; - word-wrap: break-word; + overflow-wrap: break-word; font-weight: 400; max-height: 50vh; overflow: hidden; - display: flex; flex-direction: column; + } - &__range { - display: block; - font-weight: 500; - margin-bottom: 10px; - padding-inline-end: 18px; - } + &__range { + display: block; + font-weight: 500; + margin-bottom: 10px; + padding-inline-end: 18px; + } - &__unread { - position: absolute; - top: 19px; - inset-inline-end: 19px; - display: block; - background: $highlight-text-color; - border-radius: 50%; - width: 0.625rem; - height: 0.625rem; - } + &__unread { + position: absolute; + top: 19px; + inset-inline-end: 19px; + display: block; + background: var(--color-text-brand); + border-radius: 50%; + width: 0.625rem; + height: 0.625rem; } &__pagination { padding: 15px; - color: $darker-text-color; + color: var(--color-text-secondary); position: absolute; bottom: 3px; inset-inline-end: 0; display: flex; align-items: center; + z-index: 1; } } @@ -8984,10 +8907,15 @@ noscript { &__item { flex-shrink: 0; - background: lighten($ui-base-color, 12%); - color: $darker-text-color; - border: 0; + background: var(--color-bg-brand-softer); + color: color-mix( + in oklab, + var(--color-text-primary), + var(--color-text-secondary) + ); + border: 1px solid var(--color-border-on-bg-brand-softer); border-radius: 3px; + box-sizing: border-box; margin: 2px; cursor: pointer; user-select: none; @@ -9026,21 +8954,18 @@ noscript { &:hover, &:focus, &:active { - background: lighten($ui-base-color, 16%); + color: var(--color-text-primary); + background: var(--color-bg-brand-soft); transition: all 200ms ease-out; transition-property: background-color, color; - color: lighten($darker-text-color, 4%); } &.active { + color: var(--color-text-brand); + background-color: var(--color-bg-brand-softer); + border-color: var(--color-text-brand); transition: all 100ms ease-in; transition-property: background-color, color; - background-color: color.mix( - lighten($ui-base-color, 12%), - $ui-highlight-color, - 80% - ); - color: lighten($highlight-text-color, 8%); } } @@ -9054,7 +8979,7 @@ noscript { } .emoji-button { - color: $darker-text-color; + color: var(--color-text-secondary); margin: 0; font-size: 16px; width: auto; @@ -9071,7 +8996,11 @@ noscript { &:active, &:focus { opacity: 1; - color: lighten($darker-text-color, 4%); + color: color-mix( + in oklab, + var(--color-text-primary), + var(--color-text-secondary) + ); transition: all 200ms ease-out; transition-property: background-color, color; } @@ -9101,7 +9030,7 @@ noscript { inset-inline-start: 0; width: 100%; height: 100%; - border-inline-start: 4px solid $highlight-text-color; + border-inline-start: 4px solid var(--color-text-brand); pointer-events: none; } } @@ -9116,9 +9045,9 @@ noscript { &__footer { border-radius: 0 0 4px 4px; - background: var(--modal-background-variant-color); + background: var(--color-bg-secondary); backdrop-filter: $backdrop-blur-filter; - border: 1px solid var(--modal-border-color); + border: 1px solid var(--color-border-primary); border-top: 0; padding: 12px; display: flex; @@ -9127,9 +9056,9 @@ noscript { &__header { border-radius: 4px 4px 0 0; - background: var(--modal-background-variant-color); + background: var(--color-bg-secondary); backdrop-filter: $backdrop-blur-filter; - border: 1px solid var(--modal-border-color); + border: 1px solid var(--color-border-primary); border-bottom: 0; padding: 12px; display: flex; @@ -9150,7 +9079,7 @@ noscript { } .display-name { - color: $primary-text-color; + color: var(--color-text-primary); text-decoration: none; strong, @@ -9161,7 +9090,7 @@ noscript { } span { - color: $darker-text-color; + color: var(--color-text-secondary); } } } @@ -9175,7 +9104,7 @@ noscript { .picture-in-picture-placeholder { border-radius: 8px; box-sizing: border-box; - border: 1px dashed var(--background-border-color); + border: 1px dashed var(--color-border-primary); display: flex; flex-direction: column; align-items: center; @@ -9185,7 +9114,7 @@ noscript { line-height: 21px; font-weight: 500; cursor: pointer; - color: $dark-text-color; + color: var(--color-text-tertiary); aspect-ratio: 16 / 9; .icon { @@ -9197,18 +9126,18 @@ noscript { &:hover, &:active, &:focus { - color: $darker-text-color; + color: var(--color-text-secondary); } &:focus-visible { - outline: $ui-button-focus-outline; + outline: var(--outline-focus-default); border-color: transparent; } } .notifications-permission-banner { padding: 30px; - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); display: flex; flex-direction: column; align-items: center; @@ -9229,7 +9158,7 @@ noscript { } p { - color: $darker-text-color; + color: var(--color-text-secondary); margin-bottom: 15px; text-align: center; @@ -9244,7 +9173,7 @@ noscript { .explore__search-header { justify-content: center; align-items: center; - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); border-top: 0; border-bottom: 0; padding: 16px; @@ -9256,19 +9185,18 @@ noscript { } .search__input { - border: 1px solid var(--background-border-color); padding-block: 12px; padding-inline-end: 30px; } .search__popout { - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); } .search__icon { top: 12px; inset-inline-end: 12px; - color: $dark-text-color; + color: var(--color-text-tertiary); } } @@ -9286,7 +9214,7 @@ noscript { flex-direction: column; @media screen and (min-width: $no-gap-breakpoint) { - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); border-top: 0; border-bottom-left-radius: 4px; border-bottom-right-radius: 4px; @@ -9296,9 +9224,9 @@ noscript { .story { display: flex; align-items: center; - color: $primary-text-color; + color: var(--color-text-primary); padding: 16px; - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); gap: 16px; &:last-child { @@ -9309,7 +9237,7 @@ noscript { flex: 1 1 auto; &__publisher { - color: $darker-text-color; + color: var(--color-text-secondary); margin-bottom: 8px; font-size: 14px; line-height: 20px; @@ -9322,32 +9250,26 @@ noscript { font-weight: 500; margin-bottom: 8px; text-decoration: none; - color: $primary-text-color; + color: var(--color-text-primary); &:hover, &:active, &:focus { - color: $highlight-text-color; + color: var(--color-text-brand); } } &__shared { display: flex; - align-items: center; - color: $darker-text-color; + align-items: baseline; + color: var(--color-text-secondary); gap: 8px; justify-content: space-between; font-size: 14px; line-height: 20px; - & > span { - display: flex; - align-items: center; - gap: 4px; - } - &__pill { - background: var(--surface-variant-background-color); + background: var(--color-bg-tertiary); border-radius: 4px; color: inherit; text-decoration: none; @@ -9355,20 +9277,21 @@ noscript { font-size: 12px; font-weight: 500; line-height: 16px; + flex-shrink: 0; } &__author-link { display: inline-flex; align-items: center; gap: 4px; - color: $primary-text-color; + color: var(--color-text-primary); font-weight: 500; text-decoration: none; &:hover, &:active, &:focus { - color: $highlight-text-color; + color: var(--color-text-brand); } } } @@ -9438,7 +9361,7 @@ noscript { &__introduction { font-size: 15px; line-height: 22px; - color: $darker-text-color; + color: var(--color-text-secondary); margin-bottom: 20px; strong { @@ -9465,14 +9388,14 @@ noscript { margin-bottom: 20px; aspect-ratio: 1.9; border: 0; - background: $ui-base-color; + background: var(--color-bg-tertiary); object-fit: cover; } &__description { font-size: 15px; line-height: 22px; - color: $darker-text-color; + color: var(--color-text-secondary); margin-bottom: 20px; } @@ -9490,19 +9413,19 @@ noscript { &__number { font-weight: 600; - color: $primary-text-color; + color: var(--color-text-primary); font-size: 14px; } &__number-label { - color: $darker-text-color; + color: var(--color-text-secondary); font-weight: 500; font-size: 14px; } h4 { text-transform: uppercase; - color: $darker-text-color; + color: var(--color-text-secondary); margin-bottom: 10px; font-weight: 600; } @@ -9533,9 +9456,9 @@ noscript { position: relative; display: block; border-radius: 16px; - background: var(--modal-background-color); + background: var(--color-bg-primary); backdrop-filter: $backdrop-blur-filter; - border: 1px solid var(--modal-border-color); + border: 1px solid var(--color-border-primary); padding: 24px; box-sizing: border-box; @@ -9559,10 +9482,10 @@ noscript { text-align: center; font-size: 17px; line-height: 22px; - color: $darker-text-color; + color: var(--color-text-secondary); strong { - color: $primary-text-color; + color: var(--color-text-primary); font-weight: 700; } } @@ -9587,9 +9510,9 @@ noscript { &__input { @include search-input; - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); padding: 4px 6px; - color: $primary-text-color; + color: var(--color-text-primary); font-size: 16px; line-height: 18px; display: flex; @@ -9605,7 +9528,11 @@ noscript { min-width: 0; &::placeholder { - color: lighten($darker-text-color, 4%); + color: color-mix( + in oklab, + var(--color-text-primary), + var(--color-text-secondary) + ); } &:focus { @@ -9622,11 +9549,11 @@ noscript { margin-top: -1px; padding-top: 5px; padding-bottom: 5px; - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); } &.invalid &__input { - border-color: $error-red; + border-color: var(--color-text-error); } &.expanded .search__popout { @@ -9651,7 +9578,7 @@ noscript { } p { - color: $darker-text-color; + color: var(--color-text-secondary); margin-bottom: 20px; font-size: 15px; } @@ -9682,49 +9609,6 @@ noscript { } } -.copypaste { - display: flex; - align-items: center; - gap: 10px; - - input { - display: block; - font-family: inherit; - background: darken($ui-base-color, 8%); - border: 1px solid $highlight-text-color; - color: $darker-text-color; - border-radius: 4px; - padding: 6px 9px; - line-height: 22px; - font-size: 14px; - transition: border-color 300ms linear; - flex: 1 1 auto; - overflow: hidden; - - &:focus { - outline: 0; - background: darken($ui-base-color, 4%); - } - } - - .button { - flex: 0 0 auto; - transition: background 300ms linear; - } - - &.copied { - input { - border: 1px solid $valid-value-color; - transition: none; - } - - .button { - background: $valid-value-color; - transition: none; - } - } -} - .privacy-policy { padding: 20px; @@ -9738,7 +9622,7 @@ noscript { } .prose { - color: $secondary-text-color; + color: var(--color-text-primary); font-size: 15px; line-height: 22px; @@ -9822,7 +9706,7 @@ noscript { ul > li::before { content: ''; position: absolute; - background-color: $darker-text-color; + background-color: var(--color-text-secondary); border-radius: 50%; width: 0.375em; height: 0.375em; @@ -9873,7 +9757,7 @@ noscript { h6, strong, b { - color: $primary-text-color; + color: var(--color-text-primary); font-weight: 700; } @@ -9883,7 +9767,7 @@ noscript { } a { - color: $highlight-text-color; + color: var(--color-text-brand); text-decoration: underline; &:focus, @@ -9895,14 +9779,14 @@ noscript { code { font-size: 0.875em; - background: darken($ui-base-color, 8%); + background: var(--color-bg-secondary); border-radius: 4px; padding: 0.2em 0.3em; } hr { border: 0; - border-top: 1px solid lighten($ui-base-color, 4%); + border-top: 1px solid var(--color-border-primary); margin-top: 3em; margin-bottom: 3em; } @@ -9940,8 +9824,8 @@ noscript { margin: 10px; margin-bottom: 5px; border-radius: 8px; - border: 1px solid $highlight-text-color; - background: color.change($highlight-text-color, $alpha: 0.15); + border: 1px solid var(--color-border-on-bg-brand-softer); + background: var(--color-bg-brand-softer); overflow: hidden; flex-shrink: 0; @@ -9961,7 +9845,7 @@ noscript { font-size: 15px; line-height: 22px; font-weight: 500; - color: $primary-text-color; + color: var(--color-text-primary); p { margin-bottom: 15px; @@ -9972,7 +9856,7 @@ noscript { } h1 { - color: $highlight-text-color; + color: var(--color-text-brand); font-size: 22px; line-height: 33px; font-weight: 700; @@ -9994,11 +9878,6 @@ noscript { flex-grow: 1; } } - - .button-tertiary { - background: color.change($ui-base-color, $alpha: 0.15); - backdrop-filter: blur(8px); - } } &__action { @@ -10006,22 +9885,22 @@ noscript { padding: 15px 10px; .icon-button { - color: $highlight-text-color; + color: var(--color-text-brand); } } } .warning-banner { - border: 1px solid $warning-red; - background: color.change($warning-red, $alpha: 0.15); + border: 1px solid var(--color-border-on-bg-error-softer); + background: var(--color-bg-error-softer); &__message { h1 { - color: $warning-red; + color: var(--color-text-error); } a { - color: $primary-text-color; + color: var(--color-text-primary); } } } @@ -10069,7 +9948,7 @@ noscript { } p { - color: $dark-text-color; + color: var(--color-text-secondary); margin-bottom: 20px; .version { @@ -10081,7 +9960,7 @@ noscript { } a { - color: $dark-text-color; + color: var(--color-text-secondary); text-decoration: underline; &:hover, @@ -10095,14 +9974,14 @@ noscript { .about { padding: 20px; - border-top: 1px solid var(--background-border-color); + border-top: 1px solid var(--color-border-primary); @media screen and (min-width: $no-gap-breakpoint) { border-radius: 4px; } &__footer { - color: $dark-text-color; + color: var(--color-text-tertiary); text-align: center; font-size: 15px; line-height: 22px; @@ -10116,7 +9995,7 @@ noscript { width: 100%; height: auto; aspect-ratio: 1.9; - background: lighten($ui-base-color, 4%); + background: var(--color-bg-tertiary); border-radius: 8px; margin-bottom: 30px; } @@ -10137,12 +10016,12 @@ noscript { font-size: 16px; line-height: 24px; font-weight: 400; - color: $darker-text-color; + color: var(--color-text-secondary); } } &__meta { - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); border-radius: 4px; display: flex; margin-bottom: 30px; @@ -10158,7 +10037,7 @@ noscript { width: 0; border: 0; border-style: solid; - border-color: var(--background-border-color); + border-color: var(--color-border-primary); border-left-width: 1px; min-height: calc(100% - 60px); flex: 0 0 auto; @@ -10167,7 +10046,7 @@ noscript { h4 { font-size: 15px; text-transform: uppercase; - color: $darker-text-color; + color: var(--color-text-secondary); font-weight: 500; margin-bottom: 20px; } @@ -10218,7 +10097,7 @@ noscript { } &__mail { - color: $primary-text-color; + color: var(--color-text-primary); text-decoration: none; font-weight: 500; @@ -10266,8 +10145,8 @@ noscript { line-height: 22px; padding: 20px; border-radius: 4px; - border: 1px solid var(--background-border-color); - color: $highlight-text-color; + border: 1px solid var(--color-border-primary); + color: var(--color-text-brand); cursor: pointer; width: 100%; background: none; @@ -10278,7 +10157,7 @@ noscript { } &__body { - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); border-top: 0; padding: 20px; font-size: 15px; @@ -10288,17 +10167,17 @@ noscript { &__domain-blocks { margin-top: 30px; - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); border-radius: 4px; &__domain { - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); padding: 10px; font-size: 15px; - color: $darker-text-color; + color: var(--color-text-secondary); &:nth-child(2n) { - background: darken($ui-base-color, 4%); + background: var(--color-bg-secondary); } &:last-child { @@ -10314,7 +10193,7 @@ noscript { } h6 { - color: $secondary-text-color; + color: var(--color-text-primary); font-size: inherit; white-space: nowrap; overflow: hidden; @@ -10349,14 +10228,13 @@ noscript { width: auto; padding: 15px; margin: 0; - color: $white; - background: color.change($black, $alpha: 0.85); + color: var(--color-text-on-inverted); + background: var(--color-bg-inverted); backdrop-filter: blur(8px); - border: 1px solid rgba(lighten($classic-base-color, 4%), 0.85); border-radius: 8px; box-shadow: - 0 10px 15px -3px color.change($base-shadow-color, $alpha: 0.25), - 0 4px 6px -4px color.change($base-shadow-color, $alpha: 0.25); + 0 10px 15px -3px var(--color-shadow-primary), + 0 4px 6px -4px var(--color-shadow-primary); cursor: default; font-size: 15px; line-height: 21px; @@ -10396,7 +10274,7 @@ noscript { background: transparent; text-transform: uppercase; cursor: pointer; - color: $blurple-300; + color: var(--color-text-brand-on-inverted); font-weight: 700; border-radius: 4px; padding: 0 4px; @@ -10404,7 +10282,7 @@ noscript { &:hover, &:focus, &:active { - background: color.change($ui-base-color, $alpha: 0.85); + background: var(--color-bg-brand-softer); } } @@ -10428,18 +10306,18 @@ noscript { margin-inline-start: 2px; svg { - color: $white; + color: var(--color-text-on-media); height: var(--spinner-size); width: var(--spinner-size); } } .hashtag-header { - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); padding: 15px; font-size: 17px; line-height: 22px; - color: $darker-text-color; + color: var(--color-text-secondary); strong { font-weight: 700; @@ -10453,7 +10331,7 @@ noscript { gap: 15px; h1 { - color: $primary-text-color; + color: var(--color-text-primary); white-space: nowrap; text-overflow: ellipsis; overflow: hidden; @@ -10474,7 +10352,7 @@ noscript { } .icon-button { - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); border-radius: 4px; box-sizing: content-box; padding: 5px; @@ -10495,21 +10373,25 @@ noscript { font-size: 12px; line-height: 16px; gap: 6px; - color: $darker-text-color; + color: color-mix( + in oklab, + var(--color-text-primary), + var(--color-text-secondary) + ); a { display: inline-flex; color: inherit; text-decoration: none; padding: 4px 12px; - background: var(--surface-variant-background-color); + background: var(--color-bg-brand-softer); border-radius: 4px; font-weight: 500; &:hover, &:focus, &:active { - background: var(--surface-variant-active-background-color); + background: var(--color-bg-brand-soft); } } @@ -10527,11 +10409,11 @@ noscript { gap: 12px; padding: 16px 0; padding-bottom: 0; - border-bottom: 1px solid var(--background-border-color); - background: color.change($ui-highlight-color, $alpha: 0.05); + border-bottom: 1px solid var(--color-border-primary); + background: var(--color-bg-brand-softer); &.focusable:focus-visible { - background: color.change($ui-highlight-color, $alpha: 0.05); + background: var(--color-bg-brand-softer); } &__header { @@ -10568,7 +10450,7 @@ noscript { border: none; cursor: pointer; top: 0; - color: $primary-text-color; + color: var(--color-text-primary); opacity: 0.5; &.left { @@ -10581,7 +10463,8 @@ noscript { &__icon { border-radius: 50%; - background: $ui-highlight-color; + color: var(--color-text-on-brand-base); + background: var(--color-bg-brand-base); display: flex; align-items: center; justify-content: center; @@ -10598,10 +10481,6 @@ noscript { &:focus, &:active { opacity: 1; - - .inline-follow-suggestions__body__scroll-button__icon { - background: lighten($ui-highlight-color, 4%); - } } } @@ -10619,8 +10498,8 @@ noscript { scrollbar-width: none; &__card { - background: var(--background-color); - border: 1px solid var(--background-border-color); + background: var(--color-bg-primary); + border: 1px solid var(--color-border-primary); border-radius: 4px; display: flex; flex-direction: column; @@ -10657,8 +10536,8 @@ noscript { .account__avatar { flex-shrink: 0; align-self: flex-end; - border: 1px solid var(--background-border-color); - background-color: $ui-base-color; + border: 1px solid var(--color-border-primary); + background-color: var(--color-bg-tertiary); } &__text-stack { @@ -10675,7 +10554,7 @@ noscript { &__source { display: inline-flex; align-items: center; - color: $dark-text-color; + color: var(--color-text-tertiary); gap: 4px; overflow: hidden; white-space: nowrap; @@ -10706,12 +10585,12 @@ noscript { &__html { font-size: 15px; font-weight: 500; - color: $secondary-text-color; + color: var(--color-text-primary); } &__account { font-size: 14px; - color: $darker-text-color; + color: var(--color-text-secondary); } } @@ -10732,16 +10611,16 @@ noscript { .filtered-notifications-banner { display: flex; align-items: center; - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); padding: 16px 24px; gap: 8px; - color: $darker-text-color; + color: var(--color-text-secondary); text-decoration: none; &:hover, &:active, &:focus { - color: $secondary-text-color; + color: var(--color-text-primary); } .notification-group__icon { @@ -10761,8 +10640,8 @@ noscript { } &__badge { - background: $ui-button-background-color; - color: $white; + color: var(--color-text-on-brand-base); + background: var(--color-bg-brand-base); border-radius: 100px; padding: 2px 8px; } @@ -10775,7 +10654,7 @@ noscript { padding: $padding; gap: 8px; position: relative; - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); &__checkbox { position: absolute; @@ -10807,7 +10686,7 @@ noscript { &__name { flex: 1 1 auto; - color: $darker-text-color; + color: var(--color-text-secondary); font-size: 14px; line-height: 20px; overflow: hidden; @@ -10820,7 +10699,7 @@ noscript { font-size: 16px; letter-spacing: 0.5px; line-height: 24px; - color: $secondary-text-color; + color: var(--color-text-primary); bdi { overflow: hidden; @@ -10830,10 +10709,10 @@ noscript { } .filtered-notifications-banner__badge { - background: $ui-button-background-color; + color: var(--color-text-on-brand-base); + background: var(--color-bg-brand-base); border-radius: 4px; padding: 1px 6px; - color: $white; } } @@ -10844,7 +10723,7 @@ noscript { .icon-button { border-radius: 4px; - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); padding: 5px; } } @@ -10857,7 +10736,7 @@ noscript { cursor: pointer; &:hover { - background: var(--on-surface-color); + background: var(--color-bg-secondary); } .notification-request__checkbox { @@ -10878,9 +10757,9 @@ noscript { .more-from-author { box-sizing: border-box; font-size: 14px; - color: $darker-text-color; - background: var(--surface-background-color); - border: 1px solid var(--background-border-color); + color: var(--color-text-secondary); + background: var(--color-bg-secondary); + border: 1px solid var(--color-border-primary); border-top: 0; border-radius: 0 0 8px 8px; padding: 15px; @@ -10892,7 +10771,7 @@ noscript { .logo { width: 16px; height: 16px; - color: $darker-text-color; + color: var(--color-text-secondary); } & > span { @@ -10906,13 +10785,14 @@ noscript { align-items: center; gap: 4px; font-weight: 500; - color: $primary-text-color; + color: var(--color-text-primary); text-decoration: none; + min-width: 0; &:hover, &:focus, &:active { - color: $highlight-text-color; + color: var(--color-text-brand); } } } @@ -10922,7 +10802,7 @@ noscript { align-items: flex-start; gap: 8px; padding: 16px 24px; - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); &__icon { width: 40px; @@ -10930,7 +10810,7 @@ noscript { align-items: center; justify-content: center; flex: 0 0 auto; - color: $dark-text-color; + color: var(--color-text-tertiary); .icon { width: 28px; @@ -10940,25 +10820,25 @@ noscript { &--follow &__icon, &--follow-request &__icon { - color: $highlight-text-color; + color: var(--color-text-brand); } &--favourite &__icon { - color: $gold-star; + color: var(--color-text-favourite-highlight); } &--reblog &__icon { - color: $valid-value-color; + color: var(--color-text-success); } &--relationships-severance-event &__icon, &--admin-report &__icon, &--admin-sign-up &__icon { - color: $dark-text-color; + color: var(--color-text-tertiary); } &--moderation-warning &__icon { - color: $red-bookmark; + color: var(--color-text-bookmark-highlight); } &--follow-request &__actions { @@ -10967,7 +10847,7 @@ noscript { gap: 8px; .icon-button { - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); border-radius: 50%; padding: 1px; } @@ -10997,7 +10877,7 @@ noscript { gap: 2px 8px; font-size: 15px; line-height: 22px; - color: $darker-text-color; + color: var(--color-text-secondary); a { color: inherit; @@ -11006,11 +10886,11 @@ noscript { bdi { font-weight: 700; - color: $primary-text-color; + color: var(--color-text-primary); } time { - color: $dark-text-color; + color: var(--color-text-tertiary); } @container (width < 350px) { @@ -11023,13 +10903,13 @@ noscript { } &__status { - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); border-radius: 8px; padding: 8px; } &__additional-content { - color: $dark-text-color; + color: var(--color-text-tertiary); margin-top: -8px; // to offset the parent's `gap` property font-size: 15px; line-height: 22px; @@ -11051,12 +10931,12 @@ noscript { display: flex; align-items: center; gap: 4px; - color: $dark-text-color; + color: var(--color-text-tertiary); font-size: 15px; line-height: 22px; bdi { - color: $darker-text-color; + color: var(--color-text-secondary); } .account__avatar { @@ -11068,7 +10948,7 @@ noscript { display: -webkit-box; font-size: 15px; line-height: 22px; - color: $darker-text-color; + color: var(--color-text-secondary); -webkit-line-clamp: 4; line-clamp: 4; -webkit-box-orient: vertical; @@ -11089,7 +10969,7 @@ noscript { margin-top: 0; font-size: 15px; line-height: 22px; - color: $dark-text-color; + color: var(--color-text-tertiary); } } } @@ -11104,13 +10984,13 @@ noscript { .notification-ungrouped { padding: 16px 24px; - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); &__header { display: flex; align-items: center; gap: 8px; - color: $dark-text-color; + color: var(--color-text-tertiary); font-size: 15px; line-height: 22px; font-weight: 500; @@ -11194,7 +11074,7 @@ noscript { inset-inline-start: 0; width: 100%; height: 100%; - border-inline-start: 4px solid $highlight-text-color; + border-inline-start: 4px solid var(--color-text-brand); pointer-events: none; } } @@ -11206,9 +11086,9 @@ noscript { .hover-card { box-shadow: var(--dropdown-shadow); - background: var(--modal-background-color); + background: var(--color-bg-primary); backdrop-filter: $backdrop-blur-filter; - border: 1px solid var(--modal-border-color); + border: 1px solid var(--color-border-primary); border-radius: 8px; padding: 16px; width: 270px; @@ -11239,7 +11119,7 @@ noscript { &__numbers { font-size: 15px; line-height: 22px; - color: $secondary-text-color; + color: var(--color-text-primary); strong { font-weight: 700; @@ -11253,7 +11133,7 @@ noscript { } &__bio { - color: $secondary-text-color; + color: var(--color-text-primary); font-size: 14px; line-height: 20px; display: -webkit-box; @@ -11281,7 +11161,7 @@ noscript { &__note { &-label { - color: $dark-text-color; + color: var(--color-text-tertiary); font-size: 12px; font-weight: 500; text-transform: uppercase; @@ -11289,7 +11169,7 @@ noscript { dd { white-space: pre-line; - color: $secondary-text-color; + color: var(--color-text-primary); overflow: hidden; line-clamp: 3; // Not yet supported in browers display: -webkit-box; // The next 3 properties are needed @@ -11309,17 +11189,17 @@ noscript { bdi { font-weight: 500; - color: $primary-text-color; + color: var(--color-text-primary); } &__account { display: block; - color: $dark-text-color; + color: var(--color-text-tertiary); } } .account-fields { - color: $secondary-text-color; + color: var(--color-text-primary); font-size: 14px; line-height: 20px; @@ -11341,7 +11221,7 @@ noscript { dt { flex: 0 1 auto; - color: $dark-text-color; + color: var(--color-text-tertiary); min-width: 0; overflow: hidden; white-space: nowrap; @@ -11366,7 +11246,7 @@ noscript { gap: 4px; overflow: hidden; white-space: nowrap; - color: $valid-value-color; + color: var(--color-text-success); & > span { overflow: hidden; @@ -11390,9 +11270,9 @@ noscript { .content-warning { display: block; box-sizing: border-box; - background: var(--nested-card-background); - color: var(--nested-card-text); - border: var(--nested-card-border); + color: var(--color-text-primary); + background: var(--color-bg-brand-softer); + border: 1px solid var(--color-border-on-bg-brand-softer); border-radius: 8px; padding: 8px (5px + 8px); position: relative; @@ -11412,7 +11292,7 @@ noscript { } &--filter { - color: $darker-text-color; + color: var(--color-text-secondary); p { font-weight: normal; @@ -11420,7 +11300,7 @@ noscript { .filter-name { font-weight: 500; - color: $secondary-text-color; + color: var(--color-text-primary); } } } @@ -11430,7 +11310,7 @@ noscript { align-items: center; gap: 16px; padding-inline-end: 13px; - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); &__title { display: flex; @@ -11440,13 +11320,13 @@ noscript { flex: 1 1 auto; font-size: 16px; line-height: 24px; - color: $secondary-text-color; + color: var(--color-text-secondary); text-decoration: none; &:is(a):hover, &:is(a):focus, &:is(a):active { - color: $primary-text-color; + color: var(--color-text-primary); } input { @@ -11461,7 +11341,7 @@ noscript { color: inherit; &::placeholder { - color: var(--input-placeholder-color); + color: var(--color-text-secondary); opacity: 1; } @@ -11476,16 +11356,16 @@ noscript { display: flex; gap: 12px; align-items: center; - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); border-top: 0; border-bottom: 0; padding: 16px; padding-bottom: 8px; input { - background: var(--input-background-color); - border: 1px solid var(--background-border-color); - color: var(--on-input-color); + background: var(--color-bg-secondary); + border: 1px solid var(--color-border-primary); + color: var(--color-text-primary); padding: 12px; font-size: 16px; line-height: normal; @@ -11494,7 +11374,7 @@ noscript { flex: 1 1 auto; &::placeholder { - color: var(--input-placeholder-color); + color: var(--color-text-secondary); opacity: 1; } @@ -11515,7 +11395,7 @@ noscript { .featured-carousel { overflow: hidden; flex-shrink: 0; - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); touch-action: pan-y; &__slides { @@ -11537,7 +11417,7 @@ noscript { &__header { padding: 8px 16px; - color: $darker-text-color; + color: var(--color-text-secondary); inset-inline-end: 0; display: flex; align-items: center; @@ -11558,4 +11438,10 @@ noscript { height: 16px; } } + + &__pagination { + display: flex; + align-items: center; + gap: 4px; + } } diff --git a/app/javascript/styles/mastodon/containers.scss b/app/javascript/styles/mastodon/containers.scss index 7db9ca409d7328..57c62a29e3579b 100644 --- a/app/javascript/styles/mastodon/containers.scss +++ b/app/javascript/styles/mastodon/containers.scss @@ -27,7 +27,7 @@ display: flex; justify-content: center; align-items: center; - color: $primary-text-color; + color: var(--color-text-primary); text-decoration: none; outline: 0; padding: 12px 16px; @@ -65,7 +65,7 @@ padding: 20px 0; margin-top: 40px; margin-bottom: 10px; - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); @media screen and (width <= 440px) { width: 100%; @@ -89,7 +89,7 @@ .name { flex: 1 1 auto; - color: $secondary-text-color; + color: var(--color-text-primary); .username { display: block; @@ -97,7 +97,7 @@ line-height: 24px; text-overflow: ellipsis; overflow: hidden; - color: $primary-text-color; + color: var(--color-text-primary); } } @@ -148,7 +148,7 @@ } a { - color: $highlight-text-color; + color: var(--color-text-brand); font-weight: 500; text-decoration: none; diff --git a/app/javascript/styles/mastodon/css_variables.scss b/app/javascript/styles/mastodon/css_variables.scss deleted file mode 100644 index 78915ae10eed2d..00000000000000 --- a/app/javascript/styles/mastodon/css_variables.scss +++ /dev/null @@ -1,49 +0,0 @@ -@use 'sass:color'; -@use 'functions' as *; -@use 'variables' as *; - -:root { - --dropdown-border-color: #{lighten($ui-base-color, 4%)}; - --dropdown-background-color: #{rgba(darken($ui-base-color, 8%), 0.9)}; - --dropdown-shadow: - 0 20px 25px -5px #{color.change($base-shadow-color, $alpha: 0.25)}, - 0 8px 10px -6px #{color.change($base-shadow-color, $alpha: 0.25)}; - --modal-background-color: #{rgba(darken($ui-base-color, 8%), 0.7)}; - --modal-background-variant-color: #{color.change($ui-base-color, $alpha: 0.7)}; - --modal-border-color: #{lighten($ui-base-color, 4%)}; - --background-border-color: #{lighten($ui-base-color, 4%)}; - --background-color: #{darken($ui-base-color, 8%)}; - --background-color-tint: #{rgba(darken($ui-base-color, 8%), 0.9)}; - --surface-background-color: #{darken($ui-base-color, 4%)}; - --surface-variant-background-color: #{$ui-base-color}; - --surface-variant-active-background-color: #{lighten($ui-base-color, 4%)}; - --surface-border-color: #{lighten($ui-base-color, 8%)}; - --on-surface-color: #{color.adjust($ui-base-color, $alpha: -0.5)}; - --avatar-border-radius: 8px; - --media-outline-color: #{rgba(#fcf8ff, 0.15)}; - --overlay-icon-shadow: drop-shadow( - 0 0 8px #{color.change($base-shadow-color, $alpha: 0.35)} - ); - --error-background-color: #{darken($error-red, 16%)}; - --error-active-background-color: #{darken($error-red, 12%)}; - --on-error-color: #fff; - --rich-text-container-color: rgba(87, 24, 60, 100%); - --rich-text-text-color: rgba(255, 175, 212, 100%); - --rich-text-decorations-color: rgba(128, 58, 95, 100%); - --nested-card-background: color(from #{$ui-highlight-color} srgb r g b / 5%); - --nested-card-text: #{$secondary-text-color}; - --nested-card-border: 1px solid - color(from #{$ui-highlight-color} srgb r g b / 15%); - --input-placeholder-color: #{$dark-text-color}; - --input-background-color: var(--surface-variant-background-color); - --on-input-color: #{$secondary-text-color}; -} - -body { - // Variable for easily inverting directional UI elements, - --text-x-direction: 1; - - &.rtl { - --text-x-direction: -1; - } -} diff --git a/app/javascript/styles/mastodon/dashboard.scss b/app/javascript/styles/mastodon/dashboard.scss index c99cdc357af19e..db3f0e8a8425e9 100644 --- a/app/javascript/styles/mastodon/dashboard.scss +++ b/app/javascript/styles/mastodon/dashboard.scss @@ -1,4 +1,3 @@ -@use 'functions' as *; @use 'variables' as *; .dashboard__counters { @@ -16,9 +15,9 @@ & > div, & > a { padding: 20px; - background: var(--background-color); + background: var(--color-bg-primary); border-radius: 4px; - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); box-sizing: border-box; height: 100%; } @@ -31,7 +30,7 @@ &:hover, &:focus, &:active { - background: $ui-base-color; + background: var(--color-bg-brand-softer); } } } @@ -41,7 +40,7 @@ text-align: center; font-weight: 500; font-size: 24px; - color: $primary-text-color; + color: var(--color-text-primary); margin-bottom: 20px; line-height: 30px; } @@ -52,7 +51,7 @@ &__label { font-size: 14px; - color: $darker-text-color; + color: var(--color-text-secondary); text-align: center; font-weight: 500; } @@ -61,7 +60,7 @@ .dashboard { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr); - grid-gap: 10px; + gap: 10px; @media screen and (width <= 1350px) { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); @@ -85,8 +84,8 @@ display: flex; align-items: baseline; border-radius: 4px; - background: $ui-button-background-color; - color: $primary-text-color; + background: var(--color-bg-brand-base); + color: var(--color-text-on-brand-base); transition: all 100ms ease-in; font-size: 14px; padding: 8px 16px; @@ -96,18 +95,18 @@ &:active, &:focus, &:hover { - background-color: $ui-button-focus-background-color; + background-color: var(--color-bg-brand-base-hover); transition: all 200ms ease-out; } &.positive { - background: lighten($ui-base-color, 4%); - color: $valid-value-color; + background: var(--color-bg-success-softer); + color: var(--color-text-success); } &.negative { - background: lighten($ui-base-color, 4%); - color: $error-value-color; + background: var(--color-bg-error-softer); + color: var(--color-text-error); } span { diff --git a/app/javascript/styles/mastodon/emoji_picker.scss b/app/javascript/styles/mastodon/emoji_picker.scss index 1fde5de1dc2f9f..ad2f2f630db740 100644 --- a/app/javascript/styles/mastodon/emoji_picker.scss +++ b/app/javascript/styles/mastodon/emoji_picker.scss @@ -1,5 +1,4 @@ @use 'variables' as *; -@use 'functions' as *; .emoji-mart { font-size: 13px; @@ -18,7 +17,8 @@ .emoji-mart-bar { &:first-child { - background: var(--dropdown-border-color); + background: var(--color-bg-tertiary); + border-bottom: 1px solid var(--color-border-primary); } } @@ -39,18 +39,22 @@ cursor: pointer; background: transparent; border: 0; - color: $darker-text-color; + color: var(--color-text-secondary); &:hover { - color: lighten($darker-text-color, 4%); + color: color-mix( + in oklab, + var(--color-text-primary), + var(--color-text-secondary) + ); } } .emoji-mart-anchor-selected { - color: $highlight-text-color; + color: var(--color-text-brand); &:hover { - color: lighten($highlight-text-color, 4%); + color: var(--color-text-brand-soft); } .emoji-mart-anchor-bar { @@ -64,7 +68,7 @@ inset-inline-start: 0; width: 100%; height: 4px; - background-color: $highlight-text-color; + background-color: var(--color-text-brand); } .emoji-mart-anchors { @@ -101,9 +105,9 @@ font-family: inherit; display: block; width: 100%; - background: $ui-base-color; - color: $darker-text-color; - border: 1px solid var(--background-border-color); + background: var(--color-bg-secondary); + color: var(--color-text-secondary); + border: 1px solid var(--color-border-primary); border-radius: 4px; &::-moz-focus-inner { @@ -133,7 +137,6 @@ transition: all 100ms linear; transition-property: opacity; pointer-events: auto; - opacity: 0.7; &:disabled { cursor: default; @@ -141,7 +144,7 @@ } svg { - fill: $darker-text-color; + fill: currentColor; } } @@ -152,6 +155,9 @@ z-index: 1; position: relative; text-align: center; + display: inline-flex !important; + align-items: center; + justify-content: center; } &:hover::before { @@ -162,7 +168,7 @@ inset-inline-start: 0; width: 100%; height: 100%; - background-color: var(--dropdown-border-color); + background-color: var(--color-bg-brand-softer); border-radius: 100%; } } @@ -190,7 +196,7 @@ padding: 0; margin: -1px; overflow: hidden; - clip: rect(0, 0, 0, 0); + clip-path: inset(50%); border: 0; } @@ -222,7 +228,7 @@ .emoji-mart-no-results { font-size: 14px; - color: $dark-text-color; + color: var(--color-text-tertiary); text-align: center; padding: 5px 6px; padding-top: 70px; diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss index b3708b722e3662..9f716c2126d836 100644 --- a/app/javascript/styles/mastodon/forms.scss +++ b/app/javascript/styles/mastodon/forms.scss @@ -1,6 +1,5 @@ @use 'sass:color'; @use 'variables' as *; -@use 'functions' as *; code { font-family: $font-monospace, monospace; @@ -16,7 +15,7 @@ code { .form-section { border-radius: 8px; - background: var(--surface-background-color); + background: var(--color-bg-secondary); padding: 24px; margin-bottom: 24px; } @@ -33,7 +32,7 @@ code { display: block; background: linear-gradient( to bottom, - var(--surface-background-color), + var(--color-bg-secondary-solid), transparent ); position: absolute; @@ -58,14 +57,16 @@ code { width: 40px; height: 40px; border-radius: 50%; - color: $primary-text-color; + color: var(--color-text-primary); &.success { - background: $success-green; + color: var(--color-text-on-success-base); + background: var(--color-bg-success-base); } &.failure { - background: $error-red; + color: var(--color-text-on-error-base); + background: var(--color-bg-error-base); } } @@ -126,7 +127,7 @@ code { font-family: inherit; font-size: 14px; padding-top: 5px; - color: $primary-text-color; + color: var(--color-text-primary); display: block; width: auto; } @@ -147,7 +148,7 @@ code { } label a { - color: $highlight-text-color; + color: var(--color-text-brand); text-decoration: underline; &:hover, @@ -189,7 +190,7 @@ code { .lead { font-size: 17px; line-height: 22px; - color: $secondary-text-color; + color: var(--color-text-primary); margin-bottom: 30px; &.invited-by { @@ -197,7 +198,7 @@ code { } a { - color: $highlight-text-color; + color: var(--color-text-brand); } } @@ -208,22 +209,26 @@ code { } .hint { - color: $darker-text-color; + color: var(--color-text-secondary); a { - color: $highlight-text-color; + color: var(--color-text-brand); } code { border-radius: 3px; padding: 0.2em 0.4em; - background: darken($ui-base-color, 12%); + background: var(--color-bg-secondary); } li { list-style: disc; margin-inline-start: 18px; } + + .icon { + vertical-align: -3px; + } } ul.hint { @@ -238,7 +243,7 @@ code { p.hint { margin-bottom: 15px; - color: $darker-text-color; + color: var(--color-text-secondary); &.subtle-hint { text-align: center; @@ -274,7 +279,7 @@ code { & > label { font-family: inherit; font-size: 14px; - color: $primary-text-color; + color: var(--color-text-primary); font-weight: 500; min-width: 150px; flex: 0 0 auto; @@ -296,10 +301,10 @@ code { .label_input > label { font-family: inherit; font-size: 14px; - color: $primary-text-color; + color: var(--color-text-primary); display: block; margin-bottom: 8px; - word-wrap: break-word; + overflow-wrap: break-word; font-weight: 500; } @@ -318,7 +323,7 @@ code { & > label { font-family: inherit; font-size: 14px; - color: $primary-text-color; + color: var(--color-text-primary); display: block; font-weight: 600; line-height: 20px; @@ -377,7 +382,7 @@ code { .required abbr { text-decoration: none; - color: lighten($error-value-color, 12%); + color: var(--color-text-error); } .fields-group { @@ -470,7 +475,7 @@ code { margin-bottom: 5px; font-family: inherit; font-size: 14px; - color: $primary-text-color; + color: var(--color-text-primary); display: block; width: auto; } @@ -480,7 +485,7 @@ code { label { font-family: inherit; font-size: 14px; - color: $primary-text-color; + color: var(--color-text-primary); display: inline-block; width: auto; position: relative; @@ -501,7 +506,7 @@ code { .input.static .label_input__wrapper { font-size: 14px; padding: 10px; - border: 1px solid $dark-text-color; + border: 1px solid var(--color-border-primary); border-radius: 4px; } @@ -515,14 +520,14 @@ code { box-sizing: border-box; font-size: 14px; line-height: 20px; - color: $primary-text-color; + color: var(--color-text-primary); display: block; width: 100%; outline: 0; font-family: inherit; resize: vertical; - background: $ui-base-color; - border: 1px solid var(--background-border-color); + background: var(--color-bg-secondary); + border: 1px solid var(--color-border-primary); border-radius: 4px; padding: 10px 16px; @@ -531,7 +536,7 @@ code { } &:required:valid { - border-color: $valid-value-color; + border-color: var(--color-text-success); } @media screen and (width <= 600px) { @@ -546,13 +551,13 @@ code { input[type='datetime-local'] { &:focus:invalid:not(:placeholder-shown), &:required:invalid:not(:placeholder-shown) { - border-color: lighten($error-red, 12%); + border-color: var(--color-text-error); } } .input.field_with_errors { label { - color: lighten($error-red, 12%); + color: var(--color-text-error); } input[type='text'], @@ -562,13 +567,13 @@ code { input[type='datetime-local'], textarea, select { - border-color: lighten($error-red, 12%); + border-color: var(--color-text-error); } .error { display: block; font-weight: 500; - color: lighten($error-red, 12%); + color: var(--color-text-error); margin-top: 4px; } } @@ -601,8 +606,8 @@ code { width: 100%; border: 0; border-radius: 4px; - background: $ui-button-background-color; - color: $ui-button-color; + background: var(--color-bg-brand-base); + color: var(--color-text-on-brand-base); font-size: 15px; line-height: 22px; height: auto; @@ -621,21 +626,22 @@ code { &:active, &:focus, &:hover { - background-color: $ui-button-focus-background-color; + background: var(--color-bg-brand-base-hover); } &:disabled, &:disabled:hover { - background-color: $ui-button-disabled-color; + color: var(--color-text-on-disabled); + background: var(--color-bg-disabled); } &.negative { - background: $ui-button-destructive-background-color; + background: var(--color-bg-error-base); &:hover, &:active, &:focus { - background-color: $ui-button-destructive-focus-background-color; + background: var(--color-bg-error-base-hover); } } } @@ -644,16 +650,14 @@ code { appearance: none; box-sizing: border-box; font-size: 14px; - color: $primary-text-color; + color: var(--color-text-primary); display: block; width: 100%; outline: 0; font-family: inherit; resize: vertical; - background: $ui-base-color - url("data:image/svg+xml;utf8,") - no-repeat right 8px center / auto 14px; - border: 1px solid var(--background-border-color); + background: var(--color-bg-secondary); + border: 1px solid var(--color-border-primary); border-radius: 4px; padding-inline-start: 10px; padding-inline-end: 30px; @@ -691,7 +695,7 @@ code { top: 1px; padding: 10px; font-size: 14px; - color: $dark-text-color; + color: var(--color-text-tertiary); font-family: inherit; pointer-events: none; cursor: default; @@ -717,6 +721,29 @@ code { } } +/* Double-chevron icon for custom select components */ +.select-wrapper, +.select .label_input__wrapper { + width: 100%; + + &::after { + --icon-size: 11px; + + content: ''; + position: absolute; + top: 0; + bottom: 0; + inset-inline-end: 9px; + width: var(--icon-size); + background-color: var(--color-text-tertiary); + pointer-events: none; + mask-image: url("data:image/svg+xml;utf8,"); + mask-position: right center; + mask-size: var(--icon-size); + mask-repeat: no-repeat; + } +} + .block-icon { display: block; margin: 0 auto; @@ -725,43 +752,49 @@ code { } .flash-message { - background: var(--background-color); - color: $highlight-text-color; - border: 1px solid $highlight-text-color; + color: var(--color-text-brand); + background: transparent; + border: 1px solid var(--color-text-brand); border-radius: 4px; padding: 15px 10px; margin-bottom: 30px; text-align: center; &.notice { - border: 1px solid color.change($valid-value-color, $alpha: 0.5); - background: color.change($valid-value-color, $alpha: 0.25); - color: $valid-value-color; + border: 1px solid var(--color-border-on-bg-success-softer); + background: var(--color-bg-success-softer); + color: var(--color-text-success); } &.warning { - border: 1px solid color.change($gold-star, $alpha: 0.5); - background: color.change($gold-star, $alpha: 0.25); - color: $gold-star; + border: 1px solid var(--color-border-on-bg-warning-softer); + background: var(--color-bg-warning-softer); + color: var(--color-text-warning); } &.alert { - border: 1px solid color.change($error-value-color, $alpha: 0.5); - background: color.change($error-value-color, $alpha: 0.1); - color: $error-value-color; + border: 1px solid var(--color-border-on-bg-error-softer); + background: var(--color-bg-error-softer); + color: var(--color-text-error); } &.hidden { display: none; } + &.hidden-on-touch-devices { + @media screen and (pointer: coarse) { + display: none; + } + } + a { display: inline-block; - color: $darker-text-color; + color: var(--color-text-secondary); text-decoration: none; &:hover { - color: $primary-text-color; + color: var(--color-text-primary); text-decoration: underline; } } @@ -791,8 +824,8 @@ code { border: 0; padding: 10px; font-family: $font-monospace, monospace; - background: $ui-base-color; - color: $primary-text-color; + background: var(--color-bg-secondary); + color: var(--color-text-primary); font-size: 14px; margin: 0; @@ -807,7 +840,7 @@ code { } &:focus { - background: lighten($ui-base-color, 4%); + background: var(--color-bg-brand-softer); } } @@ -863,7 +896,7 @@ code { text-align: center; a { - color: $darker-text-color; + color: var(--color-text-secondary); text-decoration: none; &:hover { @@ -883,7 +916,7 @@ code { } a { - color: $highlight-text-color; + color: var(--color-text-brand); text-transform: uppercase; text-decoration: none; font-weight: 700; @@ -891,7 +924,7 @@ code { &:hover, &:focus, &:active { - color: lighten($highlight-text-color, 8%); + color: var(--color-text-brand-soft); } } } @@ -899,7 +932,7 @@ code { .oauth-prompt, .follow-prompt { margin-bottom: 30px; - color: $darker-text-color; + color: var(--color-text-secondary); h2 { font-size: 16px; @@ -908,7 +941,7 @@ code { } strong { - color: $secondary-text-color; + color: var(--color-text-primary); font-weight: 500; @each $lang in $cjk-langs { @@ -921,7 +954,7 @@ code { .oauth-prompt { h3 { - color: $ui-secondary-color; + color: var(--color-text-primary); font-size: 17px; line-height: 22px; font-weight: 500; @@ -935,9 +968,9 @@ code { } .permissions-list { - border: 1px solid $ui-base-color; + border: 1px solid var(--color-border-primary); border-radius: 4px; - background: darken($ui-base-color, 4%); + background: var(--color-bg-secondary); margin-bottom: 30px; } @@ -963,10 +996,10 @@ code { .qr-code { flex: 0 0 auto; - background: $simple-background-color; + background: white; padding: 4px; margin: 0 10px 20px 0; - box-shadow: 0 0 15px color.change($base-shadow-color, $alpha: 0.2); + box-shadow: 0 0 15px var(--color-shadow-primary); display: inline-block; svg { @@ -977,7 +1010,7 @@ code { .qr-alternative { margin-bottom: 20px; - color: $secondary-text-color; + color: var(--color-text-primary); flex: 150px; samp { @@ -986,42 +1019,6 @@ code { } } -.simple_form { - .warning { - box-sizing: border-box; - background: color.change($error-value-color, $alpha: 0.5); - color: $primary-text-color; - text-shadow: 1px 1px 0 color.change($base-shadow-color, $alpha: 0.3); - box-shadow: 0 2px 6px color.change($base-shadow-color, $alpha: 0.4); - border-radius: 4px; - padding: 10px; - margin-bottom: 15px; - - a { - color: $primary-text-color; - text-decoration: underline; - - &:hover, - &:focus, - &:active { - text-decoration: none; - } - } - - strong { - font-weight: 600; - display: block; - margin-bottom: 5px; - - @each $lang in $cjk-langs { - &:lang(#{$lang}) { - font-weight: 700; - } - } - } - } -} - .action-pagination { display: flex; flex-wrap: wrap; @@ -1041,7 +1038,7 @@ code { .post-follow-actions { text-align: center; - color: $darker-text-color; + color: var(--color-text-secondary); div { margin-bottom: 4px; @@ -1054,7 +1051,7 @@ code { h4 { font-size: 16px; - color: $primary-text-color; + color: var(--color-text-primary); text-align: center; margin-bottom: 20px; border: 0; @@ -1067,7 +1064,7 @@ code { } .scope-danger { - color: $warning-red; + color: var(--color-text-error); } .form_admin_settings_site_short_description, @@ -1082,8 +1079,9 @@ code { } .input-copy { - background: darken($ui-base-color, 10%); - border: 1px solid darken($ui-base-color, 14%); + color: var(--color-text-primary); + background: var(--color-bg-secondary); + border: 1px solid var(--color-border-primary); border-radius: 4px; display: flex; align-items: center; @@ -1117,11 +1115,11 @@ code { } &.copied { - border-color: $valid-value-color; + border-color: var(--color-text-success); transition: none; button { - background: $valid-value-color; + background: var(--color-bg-success-base); transition: none; } } @@ -1143,8 +1141,8 @@ code { .permissions-list { &__item { padding: 15px; - color: $ui-secondary-color; - border-bottom: 1px solid lighten($ui-base-color, 4%); + color: var(--color-text-primary); + border-bottom: 1px solid var(--color-border-primary); display: flex; align-items: center; @@ -1156,7 +1154,7 @@ code { } &__type { - color: $darker-text-color; + color: var(--color-text-secondary); overflow-wrap: anywhere; } } @@ -1165,7 +1163,7 @@ code { flex: 0 0 auto; font-size: 18px; width: 30px; - color: $valid-value-color; + color: var(--color-text-success); display: flex; align-items: center; } @@ -1220,7 +1218,6 @@ code { align-items: center; padding-bottom: 30px; margin-bottom: 30px; - color: $white; li { flex: 0 0 auto; @@ -1229,11 +1226,11 @@ code { .separator { height: 2px; - background: $ui-base-lighter-color; + background: var(--color-border-primary); flex: 1 1 auto; &.completed { - background: $highlight-text-color; + background: var(--color-text-brand); } } @@ -1243,7 +1240,7 @@ code { width: 30px; height: 30px; border-radius: 50%; - border: 2px solid $ui-base-lighter-color; + border: 2px solid var(--color-border-primary); flex: 0 0 auto; display: flex; align-items: center; @@ -1258,7 +1255,7 @@ code { position: absolute; font-size: 14px; font-weight: 500; - color: $secondary-text-color; + color: var(--color-text-primary); padding-top: 10px; text-align: center; width: 100px; @@ -1281,14 +1278,14 @@ code { } .active .circle { - border-color: $highlight-text-color; + border-color: var(--color-text-brand); &::before { content: ''; width: 10px; height: 10px; border-radius: 50%; - background: $highlight-text-color; + background: var(--color-text-brand); position: absolute; left: 50%; top: 50%; @@ -1297,8 +1294,8 @@ code { } .completed .circle { - border-color: $highlight-text-color; - background: $highlight-text-color; + border-color: var(--color-text-brand); + background: var(--color-text-brand); } } @@ -1309,10 +1306,14 @@ code { &__header-input { display: block; border-radius: 8px; - background: var(--surface-variant-background-color); + background: var(--color-bg-secondary); position: relative; cursor: pointer; + &:hover { + background-color: var(--color-bg-brand-softer); + } + img { position: absolute; width: 100%; @@ -1327,12 +1328,12 @@ code { inset-inline-start: 50%; top: 50%; transform: translate(-50%, -50%); - color: $darker-text-color; + color: var(--color-text-secondary); z-index: 3; } &.selected .icon { - color: $primary-text-color; + color: var(--color-text-primary); transform: none; inset-inline-start: auto; inset-inline-end: 8px; @@ -1341,7 +1342,7 @@ code { } &.invalid img { - outline: 1px solid $error-value-color; + outline: 1px solid var(--color-text-error); outline-offset: -1px; } @@ -1351,14 +1352,10 @@ code { width: 100%; height: 100%; position: absolute; - background: color.change($error-value-color, $alpha: 0.25); + background: var(--color-bg-error-softer); z-index: 2; border-radius: 8px; } - - &:hover { - background-color: var(--surface-variant-active-background-color); - } } &__avatar-input { @@ -1374,7 +1371,7 @@ code { display: flex; align-items: center; gap: 16px; - color: $darker-text-color; + color: var(--color-text-secondary); font-size: 14px; line-height: 20px; @@ -1391,14 +1388,14 @@ code { flex: 1 1 auto; strong { - color: $primary-text-color; + color: var(--color-text-primary); font-weight: 600; } .hint { display: block; font-size: 14px; - color: $darker-text-color; + color: var(--color-text-secondary); } .recommended { @@ -1416,7 +1413,7 @@ code { &__toggle > div { display: flex; - border-inline-start: 1px solid var(--background-border-color); + border-inline-start: 1px solid var(--color-border-primary); padding-inline-start: 16px; } } @@ -1427,19 +1424,19 @@ code { padding: 8px 0; align-items: center; text-decoration: none; - color: $primary-text-color; + color: var(--color-text-primary); margin-bottom: 16px; &__text { flex: 1 1 auto; font-size: 14px; line-height: 20px; - color: $darker-text-color; + color: var(--color-text-secondary); strong { font-weight: 600; display: block; - color: $primary-text-color; + color: var(--color-text-primary); } .icon { diff --git a/app/javascript/styles/contrast/diff.scss b/app/javascript/styles/mastodon/high-contrast.scss similarity index 72% rename from app/javascript/styles/contrast/diff.scss rename to app/javascript/styles/mastodon/high-contrast.scss index 0adf8802eeb38d..f55e7fae3b826a 100644 --- a/app/javascript/styles/contrast/diff.scss +++ b/app/javascript/styles/mastodon/high-contrast.scss @@ -1,5 +1,3 @@ -@use '../mastodon/variables' as *; - .status__content a, .reply-indicator__content a, .edit-indicator__content a, @@ -31,16 +29,6 @@ } } -.status__content a, -.reply-indicator__content a, -.edit-indicator__content a { - color: $highlight-text-color; -} - -.report-dialog-modal__textarea::placeholder { - color: $inverted-text-color; -} - .link-button:disabled { cursor: not-allowed; diff --git a/app/javascript/styles/mastodon/modal.scss b/app/javascript/styles/mastodon/modal.scss index 7d060a2681a39b..6af2a182b63449 100644 --- a/app/javascript/styles/mastodon/modal.scss +++ b/app/javascript/styles/mastodon/modal.scss @@ -1,10 +1,7 @@ @use 'variables' as *; -@use 'functions' as *; .modal-layout { - background: darken($ui-base-color, 4%) - url('data:image/svg+xml;utf8,') - repeat-x bottom fixed; + background: var(--color-bg-brand-softer); display: flex; flex-direction: column; height: 100vh; @@ -38,3 +35,19 @@ margin-top: 0; } } + +.with-zig-zag-decoration { + &::after { + content: ''; + position: absolute; + inset: auto 0 0; + height: 32px; + background-color: var(--color-bg-brand-softer); + + /* Decorative zig-zag pattern at the bottom of the page */ + mask-image: url('data:image/svg+xml;utf8,'); + mask-position: bottom; + mask-repeat: repeat-x; + z-index: -1; + } +} diff --git a/app/javascript/styles/mastodon/polls.scss b/app/javascript/styles/mastodon/polls.scss index b13c9d613292cf..19fb8dd505f72b 100644 --- a/app/javascript/styles/mastodon/polls.scss +++ b/app/javascript/styles/mastodon/polls.scss @@ -1,6 +1,5 @@ @use 'sass:color'; @use 'variables' as *; -@use 'functions' as *; .poll { margin-top: 16px; @@ -14,12 +13,12 @@ &__chart { border-radius: 4px; display: block; - background: darken($ui-primary-color, 5%); + background: rgb(from var(--color-text-brand) r g b / 60%); height: 5px; min-width: 1%; &.leading { - background: $ui-highlight-color; + background: var(--color-text-brand); } } @@ -39,12 +38,12 @@ // duplication &::-moz-progress-bar { border-radius: 4px; - background: darken($ui-primary-color, 5%); + background: rgb(from var(--color-text-brand) r g b / 60%); } &::-webkit-progress-value { border-radius: 4px; - background: darken($ui-primary-color, 5%); + background: rgb(from var(--color-text-brand) r g b / 60%); } } @@ -60,7 +59,6 @@ &__text { display: inline-block; - word-wrap: break-word; overflow-wrap: break-word; max-width: calc(100% - 45px - 25px); } @@ -79,16 +77,16 @@ box-sizing: border-box; width: 100%; font-size: 14px; - color: $secondary-text-color; + color: var(--color-text-primary); outline: 0; font-family: inherit; - background: $ui-base-color; - border: 1px solid $darker-text-color; + background: var(--color-bg-primary); + border: 1px solid var(--color-text-secondary); border-radius: 4px; padding: 8px 12px; &:focus { - border-color: $ui-highlight-color; + border-color: var(--color-text-brand); } @media screen and (width <= 600px) { @@ -112,7 +110,7 @@ &__input { display: block; position: relative; - border: 1px solid $ui-primary-color; + border: 1px solid var(--color-text-secondary); box-sizing: border-box; width: 17px; height: 17px; @@ -126,13 +124,13 @@ &:active, &:focus, &:hover { - border-color: lighten($valid-value-color, 15%); + border-color: var(--color-text-success); border-width: 4px; } &.active { - background-color: $valid-value-color; - border-color: $valid-value-color; + background-color: var(--color-bg-success-base); + border-color: var(--color-text-success); } &::-moz-focus-inner { @@ -146,16 +144,16 @@ } &.disabled { - border-color: $dark-text-color; + border-color: var(--color-text-disabled); &.active { - background: $dark-text-color; + background: var(--color-text-disabled); } &:active, &:focus, &:hover { - border-color: $dark-text-color; + border-color: var(--color-text-disabled); border-width: 1px; } } @@ -166,7 +164,7 @@ &:active, &:focus, &:hover { - border-color: $ui-primary-color; + border-color: var(--color-text-primary); border-width: 1px; } } @@ -190,7 +188,7 @@ &__footer { padding-top: 6px; padding-bottom: 5px; - color: $dark-text-color; + color: var(--color-text-tertiary); } &__link { @@ -199,7 +197,7 @@ padding: 0; margin: 0; border: 0; - color: $dark-text-color; + color: var(--color-text-tertiary); text-decoration: underline; font-size: inherit; @@ -209,7 +207,7 @@ &:active, &:focus { - background-color: color.change($dark-text-color, $alpha: 0.1); + background-color: var(--color-bg-secondary); } } @@ -222,13 +220,13 @@ } .muted .poll { - color: $dark-text-color; + color: var(--color-text-tertiary); &__chart { - background: rgba(darken($ui-primary-color, 14%), 0.7); + background: rgb(from var(--color-text-brand) r g b / 40%); &.leading { - background: color.change($ui-highlight-color, $alpha: 0.5); + background: rgb(from var(--color-text-brand) r g b / 60%); } } } diff --git a/app/javascript/styles/mastodon/reset.scss b/app/javascript/styles/mastodon/reset.scss index 2dce637a0618e5..2c3efbddc46c71 100644 --- a/app/javascript/styles/mastodon/reset.scss +++ b/app/javascript/styles/mastodon/reset.scss @@ -1,5 +1,3 @@ -@use 'variables' as *; - /* http://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 License: none (public domain) @@ -54,33 +52,3 @@ table { border-collapse: collapse; border-spacing: 0; } - -@supports not selector(::-webkit-scrollbar) { - html { - scrollbar-color: $action-button-color var(--background-border-color); - } -} - -.custom-scrollbars { - ::-webkit-scrollbar { - width: 8px; - height: 8px; - } - - ::-webkit-scrollbar-thumb { - background-color: $action-button-color; - border: 2px var(--background-border-color); - border-radius: 12px; - width: 6px; - box-shadow: inset 0 0 0 2px var(--background-border-color); - } - - ::-webkit-scrollbar-track { - background-color: var(--background-border-color); - border-radius: 0px; - } - - ::-webkit-scrollbar-corner { - background: transparent; - } -} diff --git a/app/javascript/styles/mastodon/rtl.scss b/app/javascript/styles/mastodon/rtl.scss index 6aa94a97bcf40a..a78f95f66dc0a6 100644 --- a/app/javascript/styles/mastodon/rtl.scss +++ b/app/javascript/styles/mastodon/rtl.scss @@ -1,7 +1,6 @@ -@use 'functions' as *; @use 'variables' as *; -body.rtl { +html.rtl { direction: rtl; .reactions-bar { @@ -42,12 +41,6 @@ body.rtl { transform: scale(-1, 1); } - .simple_form select { - background: $ui-base-color - url("data:image/svg+xml;utf8,") - no-repeat left 8px center / auto 16px; - } - .dismissable-banner, .warning-banner { &__action { diff --git a/app/javascript/styles/mastodon/tables.scss b/app/javascript/styles/mastodon/tables.scss index 3489d22e5d788d..8e303aff685c1e 100644 --- a/app/javascript/styles/mastodon/tables.scss +++ b/app/javascript/styles/mastodon/tables.scss @@ -1,5 +1,4 @@ @use 'variables' as *; -@use 'functions' as *; .table { width: 100%; @@ -12,13 +11,13 @@ padding: 8px; line-height: 18px; vertical-align: top; - border-bottom: 1px solid var(--background-border-color); + border-bottom: 1px solid var(--color-border-primary); text-align: start; - background: var(--background-color); + background: var(--color-bg-primary); &.critical { font-weight: 700; - color: $gold-star; + color: var(--color-text-warning); } } @@ -33,7 +32,7 @@ & > tbody > tr:nth-child(odd) > td, & > tbody > tr:nth-child(odd) > th { - background: var(--background-color); + background: var(--color-bg-primary); } & > tbody > tr:last-child > td, @@ -42,11 +41,11 @@ } a { - color: $darker-text-color; + color: var(--color-text-secondary); text-decoration: none; &:hover { - color: $highlight-text-color; + color: var(--color-text-brand); } } @@ -84,30 +83,30 @@ & > tbody > tr > td { padding: 11px 10px; background: transparent; - border: 1px solid var(--background-border-color); - color: $secondary-text-color; + border: 1px solid var(--color-border-primary); + color: var(--color-text-primary); } & > tbody > tr > th { - color: $darker-text-color; + color: var(--color-text-secondary); font-weight: 600; } } &.batch-table { & > thead > tr > th { - background: var(--background-color); - border-top: 1px solid var(--background-border-color); - border-bottom: 1px solid var(--background-border-color); + background: var(--color-bg-primary); + border-top: 1px solid var(--color-border-primary); + border-bottom: 1px solid var(--color-border-primary); &:first-child { border-radius: 4px 0 0; - border-inline-start: 1px solid var(--background-border-color); + border-inline-start: 1px solid var(--color-border-primary); } &:last-child { border-radius: 0 4px 0 0; - border-inline-end: 1px solid var(--background-border-color); + border-inline-end: 1px solid var(--color-border-primary); } } } @@ -138,12 +137,12 @@ a.table-action-link { display: inline-block; margin-inline-end: 5px; padding: 0 10px; - color: $darker-text-color; + color: var(--color-text-secondary); font-weight: 500; white-space: nowrap; &:hover { - color: $highlight-text-color; + color: var(--color-text-brand); } &:first-child { @@ -196,8 +195,8 @@ a.table-action-link { position: sticky; top: 0; z-index: 200; - border: 1px solid var(--background-border-color); - background: var(--background-color); + border: 1px solid var(--color-border-primary); + background: var(--color-bg-primary); border-radius: 4px 4px 0 0; height: 47px; align-items: center; @@ -213,13 +212,13 @@ a.table-action-link { } &__select-all { - background: var(--background-color); + background: var(--color-bg-primary); height: 47px; align-items: center; justify-content: center; - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); border-top: 0; - color: $secondary-text-color; + color: var(--color-text-primary); display: none; &.active { @@ -248,7 +247,7 @@ a.table-action-link { background: transparent; border: 0; font: inherit; - color: $highlight-text-color; + color: var(--color-text-brand); border-radius: 4px; font-weight: 700; padding: 8px; @@ -256,16 +255,16 @@ a.table-action-link { &:hover, &:focus, &:active { - background: lighten($ui-base-color, 8%); + background: var(--color-bg-secondary); } } } &__form { padding: 16px; - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); border-top: 0; - background: var(--background-color); + background: var(--color-bg-primary); .fields-row { padding-top: 0; @@ -274,13 +273,13 @@ a.table-action-link { } &__row { - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); border-top: 0; - background: var(--background-color); + background: var(--color-bg-primary); @media screen and (max-width: $no-gap-breakpoint) { .optional &:first-child { - border-top: 1px solid var(--background-border-color); + border-top: 1px solid var(--color-border-primary); } } @@ -331,7 +330,7 @@ a.table-action-link { &__extra { flex: 0 0 auto; text-align: end; - color: $darker-text-color; + color: var(--color-text-secondary); font-weight: 500; } } @@ -356,27 +355,12 @@ a.table-action-link { // Reset the status card to not have borders, background or padding when // inline in the table of statuses - .status__card { + .batch-table__row__content > .status__card { border: none; background: none; padding: 0; } - .nothing-here { - border: 1px solid var(--background-border-color); - border-top: 0; - box-shadow: none; - background: var(--background-color); - - @media screen and (max-width: $no-gap-breakpoint) { - border-top: 1px solid var(--background-border-color); - } - - &--no-toolbar { - border-top: 1px solid var(--background-border-color); - } - } - @media screen and (width <= 870px) { .accounts-table tbody td.optional { display: none; diff --git a/app/javascript/styles/mastodon/theme/_base.scss b/app/javascript/styles/mastodon/theme/_base.scss new file mode 100644 index 00000000000000..94d592aa704e22 --- /dev/null +++ b/app/javascript/styles/mastodon/theme/_base.scss @@ -0,0 +1,27 @@ +@mixin palette { + --color-black: #000; + --color-grey-950: #181821; + --color-grey-800: #292938; + --color-grey-700: #444664; + --color-grey-600: #545778; + --color-grey-500: #696d91; + --color-grey-400: #8b8dac; + --color-grey-300: #b4b6cb; + --color-grey-200: #d8d9e3; + --color-grey-100: #f0f0f5; + --color-grey-50: #f0f1ff; + --color-white: #fff; + --color-indigo-600: #6147e6; + --color-indigo-400: #8886ff; + --color-indigo-300: #a5abfd; + --color-indigo-200: #c8cdfe; + --color-indigo-100: #e0e3ff; + --color-indigo-50: #f0f1ff; + --color-red-500: #ff637e; + --color-red-600: #ec003f; + --color-yellow-400: #ffb900; + --color-yellow-600: #e17100; + --color-yellow-700: #bb4d00; + --color-green-400: #05df72; + --color-green-600: #00a63e; +} diff --git a/app/javascript/styles/mastodon/theme/_dark.scss b/app/javascript/styles/mastodon/theme/_dark.scss new file mode 100644 index 00000000000000..e6fd6d3cc14cd5 --- /dev/null +++ b/app/javascript/styles/mastodon/theme/_dark.scss @@ -0,0 +1,204 @@ +@use 'utils'; + +@mixin tokens { + /* TEXT TOKENS */ + + --color-text-primary: var(--color-grey-50); + --color-text-secondary: var(--color-grey-400); + --color-text-tertiary: var(--color-grey-500); + --color-text-on-inverted: var(--color-grey-950); + --color-text-brand: var(--color-indigo-400); + --color-text-brand-soft: color-mix( + in oklab, + var(--color-text-primary), + var(--color-text-brand) + ); + --color-text-on-brand-base: var(--color-white); + --color-text-brand-on-inverted: var(--color-indigo-600); + --color-text-error: var(--color-red-500); + --color-text-on-error-base: var(--color-white); + --color-text-warning: var(--color-yellow-400); + --color-text-on-warning-base: var(--color-white); + --color-text-success: var(--color-green-400); + --color-text-on-success-base: var(--color-white); + --color-text-disabled: var(--color-grey-600); + --color-text-on-disabled: var(--color-grey-400); + --color-text-bookmark-highlight: var(--color-text-error); + --color-text-favourite-highlight: var(--color-text-warning); + --color-text-on-media: var(--color-white); + --color-text-status-links: color-mix( + in oklab, + var(--color-text-primary), + var(--color-text-secondary) + ); + + /* BACKGROUND TOKENS */ + + // Neutrals + --color-bg-primary: var(--color-grey-950); + --overlay-strength-secondary: 8%; + --color-bg-secondary-base: var(--color-indigo-200); + --color-bg-secondary: #{utils.css-alpha( + var(--color-bg-secondary-base), + var(--overlay-strength-secondary) + )}; + --color-bg-secondary-solid: color-mix( + in srgb, + var(--color-bg-primary), + var(--color-bg-secondary-base) var(--overlay-strength-secondary) + ); + --color-bg-tertiary: color-mix( + in oklab, + var(--color-bg-primary), + var(--color-bg-secondary-base) calc(2 * var(--overlay-strength-secondary)) + ); + + // Utility + --color-bg-ambient: var(--color-bg-primary); + --color-bg-elevated: var(--color-bg-primary); + --color-bg-inverted: var(--color-grey-50); + --color-bg-media-base: var(--color-black); + --color-bg-media-strength: 65%; + --color-bg-media: #{utils.css-alpha( + var(--color-bg-media-base), + var(--color-bg-media-strength) + )}; + --color-bg-overlay: var(--color-black); + --color-bg-disabled: var(--color-grey-700); + + // Brand + --overlay-strength-brand: 10%; + --color-bg-brand-base: var(--color-indigo-600); + --color-bg-brand-base-hover: color-mix( + in oklab, + var(--color-bg-brand-base), + black var(--overlay-strength-brand) + ); + --color-bg-brand-soft: #{utils.css-alpha( + var(--color-bg-brand-base), + calc(var(--overlay-strength-brand) * 1.5) + )}; + --color-bg-brand-softer: #{utils.css-alpha( + var(--color-bg-brand-base), + var(--overlay-strength-brand) + )}; + + // Error + --overlay-strength-error: 12%; + --color-bg-error-base: var(--color-red-600); + --color-bg-error-base-hover: color-mix( + in oklab, + var(--color-bg-error-base), + black var(--overlay-strength-error) + ); + --color-bg-error-soft: #{utils.css-alpha( + var(--color-bg-error-base), + calc(var(--overlay-strength-error) * 1.5) + )}; + --color-bg-error-softer: #{utils.css-alpha( + var(--color-bg-error-base), + var(--overlay-strength-error) + )}; + + // Warning + --overlay-strength-warning: 10%; + --color-bg-warning-base: var(--color-yellow-700); + --color-bg-warning-base-hover: color-mix( + in oklab, + var(--color-bg-warning-base), + black var(--overlay-strength-warning) + ); + --color-bg-warning-soft: #{utils.css-alpha( + var(--color-bg-warning-base), + calc(var(--overlay-strength-warning) * 1.5) + )}; + --color-bg-warning-softer: #{utils.css-alpha( + var(--color-bg-warning-base), + var(--overlay-strength-warning) + )}; + + // Success + --overlay-strength-success: 15%; + --color-bg-success-base: var(--color-green-600); + --color-bg-success-base-hover: color-mix( + in oklab, + var(--color-bg-success-base), + black var(--overlay-strength-success) + ); + --color-bg-success-soft: #{utils.css-alpha( + var(--color-bg-success-base), + calc(var(--overlay-strength-success) * 1.5) + )}; + --color-bg-success-softer: #{utils.css-alpha( + var(--color-bg-success-base), + var(--overlay-strength-success) + )}; + + /* BORDER TOKENS */ + + --border-strength-primary: 18%; + --color-border-primary: #{utils.css-alpha( + var(--color-indigo-200), + var(--border-strength-primary) + )}; + --color-border-media: rgb(252 248 255 / 15%); + --color-border-on-bg-secondary: #{utils.css-alpha( + var(--color-indigo-200), + calc(var(--border-strength-primary) / 1.5) + )}; + --color-border-on-bg-brand-softer: var(--color-border-primary); + --color-border-on-bg-error-softer: #{utils.css-alpha( + var(--color-text-error), + 50% + )}; + --color-border-on-bg-warning-softer: #{utils.css-alpha( + var(--color-text-warning), + 50% + )}; + --color-border-on-bg-success-softer: #{utils.css-alpha( + var(--color-text-success), + 50% + )}; + --color-border-on-bg-inverted: var(--color-border-primary); + + /* SHADOW TOKENS */ + + --shadow-strength-primary: 80%; + --color-shadow-primary: #{utils.css-alpha( + var(--color-black), + var(--shadow-strength-primary) + )}; + --dropdown-shadow: + 0 20px 25px -5px var(--color-shadow-primary), + 0 8px 10px -6px var(--color-shadow-primary); + --overlay-icon-shadow: drop-shadow(0 0 8px var(--color-shadow-primary)); + + /* GRAPHS/CHARTS TOKENS */ + + --color-graph-primary-stroke: var(--color-text-brand); + --color-graph-primary-fill: var(--color-bg-brand-softer); + --color-graph-warning-stroke: var(--color-text-warning); + --color-graph-warning-fill: var(--color-bg-warning-softer); + --color-graph-disabled-stroke: var(--color-text-disabled); + --color-graph-disabled-fill: var(--color-bg-disabled); + + /* LEGACY TOKENS */ + + --rich-text-container-color: rgb(87 24 60 / 100%); + --rich-text-text-color: rgb(255 175 212 / 100%); + --rich-text-decorations-color: rgb(128 58 95 / 100%); +} + +@mixin contrast-overrides { + /* TEXT TOKENS */ + + --color-text-primary: var(--color-grey-50); + --color-text-secondary: var(--color-grey-300); + --color-text-tertiary: var(--color-grey-400); + --color-text-brand: var(--color-indigo-300); + --color-text-status-links: var(--color-text-brand); + + /* BORDER TOKENS */ + + --border-strength-primary: 18%; +} diff --git a/app/javascript/styles/mastodon/theme/_light.scss b/app/javascript/styles/mastodon/theme/_light.scss new file mode 100644 index 00000000000000..f0dc1bdfbc30bf --- /dev/null +++ b/app/javascript/styles/mastodon/theme/_light.scss @@ -0,0 +1,199 @@ +@use 'utils'; + +@mixin tokens { + /* TEXT TOKENS */ + + --color-text-primary: var(--color-grey-950); + --color-text-secondary: var(--color-grey-600); + --color-text-tertiary: var(--color-grey-500); + --color-text-on-inverted: var(--color-white); + --color-text-brand: var(--color-indigo-600); + --color-text-brand-soft: color-mix( + in oklab, + var(--color-text-primary), + var(--color-text-brand) + ); + --color-text-on-brand-base: var(--color-white); + --color-text-brand-on-inverted: var(--color-indigo-400); + --color-text-error: var(--color-red-600); + --color-text-on-error-base: var(--color-white); + --color-text-warning: var(--color-yellow-600); + --color-text-on-warning-base: var(--color-white); + --color-text-success: var(--color-green-600); + --color-text-on-success-base: var(--color-white); + --color-text-disabled: var(--color-grey-300); + --color-text-on-disabled: var(--color-grey-200); + --color-text-bookmark-highlight: var(--color-text-error); + --color-text-favourite-highlight: var(--color-text-warning); + --color-text-on-media: var(--color-white); + --color-text-status-links: var(--color-text-brand); + + /* BACKGROUND TOKENS */ + + // Neutrals + --color-bg-primary: var(--color-white); + --overlay-strength-secondary: 5%; + --color-bg-secondary-base: var(--color-grey-600); + --color-bg-secondary: #{color-mix( + in oklab, + var(--color-bg-primary), + var(--color-bg-secondary-base) var(--overlay-strength-secondary) + )}; + --color-bg-secondary-solid: #{color-mix( + in srgb, + var(--color-bg-primary), + var(--color-bg-secondary-base) var(--overlay-strength-secondary) + )}; + --color-bg-tertiary: #{color-mix( + in oklab, + var(--color-bg-primary), + var(--color-bg-secondary-base) calc(2 * var(--overlay-strength-secondary)) + )}; + + // Utility + --color-bg-ambient: var(--color-bg-primary); + --color-bg-elevated: var(--color-bg-primary); + --color-bg-inverted: var(--color-grey-950); + --color-bg-media-base: var(--color-black); + --color-bg-media-strength: 65%; + --color-bg-media: #{utils.css-alpha( + var(--color-bg-media-base), + var(--color-bg-media-strength) + )}; + --color-bg-overlay: var(--color-bg-primary); + --color-bg-disabled: var(--color-grey-400); + + // Brand + --overlay-strength-brand: 8%; + --color-bg-brand-base: var(--color-indigo-600); + --color-bg-brand-base-hover: color-mix( + in oklab, + var(--color-bg-brand-base), + black var(--overlay-strength-brand) + ); + --color-bg-brand-soft: #{utils.css-alpha( + var(--color-bg-brand-base), + calc(var(--overlay-strength-brand) * 1.5) + )}; + --color-bg-brand-softer: #{utils.css-alpha( + var(--color-bg-brand-base), + var(--overlay-strength-brand) + )}; + + // Error + --overlay-strength-error: 12%; + --color-bg-error-base: var(--color-red-600); + --color-bg-error-base-hover: color-mix( + in oklab, + var(--color-bg-error-base), + black var(--overlay-strength-error) + ); + --color-bg-error-soft: #{utils.css-alpha( + var(--color-bg-error-base), + calc(var(--overlay-strength-error) * 1.5) + )}; + --color-bg-error-softer: #{utils.css-alpha( + var(--color-bg-error-base), + var(--overlay-strength-error) + )}; + + // Warning + --overlay-strength-warning: 10%; + --color-bg-warning-base: var(--color-yellow-700); + --color-bg-warning-base-hover: color-mix( + in oklab, + var(--color-bg-warning-base), + black var(--overlay-strength-warning) + ); + --color-bg-warning-soft: #{utils.css-alpha( + var(--color-bg-warning-base), + calc(var(--overlay-strength-warning) * 1.5) + )}; + --color-bg-warning-softer: #{utils.css-alpha( + var(--color-bg-warning-base), + var(--overlay-strength-warning) + )}; + + // Success + --overlay-strength-success: 15%; + --color-bg-success-base: var(--color-green-600); + --color-bg-success-base-hover: color-mix( + in oklab, + var(--color-bg-success-base), + black var(--overlay-strength-success) + ); + --color-bg-success-soft: #{utils.css-alpha( + var(--color-bg-success-base), + calc(var(--overlay-strength-success) * 1.5) + )}; + --color-bg-success-softer: #{utils.css-alpha( + var(--color-bg-success-base), + var(--overlay-strength-success) + )}; + + /* BORDER TOKENS */ + + --border-strength-primary: 15%; + --color-border-primary: color-mix( + in oklab, + var(--color-bg-primary), + var(--color-grey-950) var(--border-strength-primary) + ); + --color-border-media: rgb(252 248 255 / 15%); + --color-border-on-bg-secondary: var(--color-grey-200); + --color-border-on-bg-brand-softer: var(--color-indigo-200); + --color-border-on-bg-error-softer: #{utils.css-alpha( + var(--color-text-error), + 50% + )}; + --color-border-on-bg-warning-softer: #{utils.css-alpha( + var(--color-text-warning), + 50% + )}; + --color-border-on-bg-success-softer: #{utils.css-alpha( + var(--color-text-success), + 50% + )}; + --color-border-on-bg-inverted: var(--color-border-primary); + + /* SHADOW TOKENS */ + + --shadow-strength-primary: 30%; + --color-shadow-primary: #{utils.css-alpha( + var(--color-black), + var(--shadow-strength-primary) + )}; + --dropdown-shadow: + 0 20px 25px -5px var(--color-shadow-primary), + 0 8px 10px -6px var(--color-shadow-primary); + --overlay-icon-shadow: drop-shadow(0 0 8px var(--color-shadow-primary)); + + /* GRAPHS/CHARTS TOKENS */ + + --color-graph-primary-stroke: var(--color-text-brand); + --color-graph-primary-fill: var(--color-bg-brand-softer); + --color-graph-warning-stroke: var(--color-text-warning); + --color-graph-warning-fill: var(--color-bg-warning-softer); + --color-graph-disabled-stroke: var(--color-text-disabled); + --color-graph-disabled-fill: var(--color-bg-disabled); + + /* LEGACY TOKENS */ + + --rich-text-container-color: rgb(255 216 231 / 100%); + --rich-text-text-color: rgb(114 47 83 / 100%); + --rich-text-decorations-color: rgb(255 175 212 / 100%); +} + +@mixin contrast-overrides { + /* TEXT TOKENS */ + + --color-text-primary: var(--color-black); + --color-text-secondary: var(--color-grey-800); + --color-text-tertiary: var(--color-grey-700); + --color-text-brand: var(--color-indigo-600); + + /* BORDER TOKENS */ + + --border-strength-primary: 30%; + --color-border-on-bg-secondary: var(--color-grey-300); +} diff --git a/app/javascript/styles/mastodon/theme/_utils.scss b/app/javascript/styles/mastodon/theme/_utils.scss new file mode 100644 index 00000000000000..4c207c5094b25f --- /dev/null +++ b/app/javascript/styles/mastodon/theme/_utils.scss @@ -0,0 +1,15 @@ +@function css-alpha($base-color, $amount) { + @return #{rgb(from $base-color r g b / $amount)}; +} + +@mixin invert-on-light { + .invert-on-light { + filter: invert(1); + } +} + +@mixin invert-on-dark { + .invert-on-dark { + filter: invert(1); + } +} diff --git a/app/javascript/styles/mastodon/theme/index.scss b/app/javascript/styles/mastodon/theme/index.scss new file mode 100644 index 00000000000000..a907299887d1be --- /dev/null +++ b/app/javascript/styles/mastodon/theme/index.scss @@ -0,0 +1,53 @@ +@use 'base'; +@use 'dark'; +@use 'light'; +@use 'utils'; + +html { + @include base.palette; + + &:where([data-user-theme='system']) { + color-scheme: dark light; + + @media (prefers-color-scheme: dark) { + @include dark.tokens; + @include utils.invert-on-dark; + + @media (prefers-contrast: more) { + @include dark.contrast-overrides; + } + } + + @media (prefers-color-scheme: light) { + @include light.tokens; + @include utils.invert-on-light; + + @media (prefers-contrast: more) { + @include light.contrast-overrides; + } + } + } +} + +.theme-dark, +html:where( + :not([data-user-theme='mastodon-light'], [data-user-theme='system']) +) { + color-scheme: dark; + + @include dark.tokens; + @include utils.invert-on-dark; +} + +html[data-user-theme='contrast'], +html[data-user-theme='contrast'] .theme-dark { + @include dark.contrast-overrides; +} + +.theme-light, +html:where([data-user-theme='mastodon-light']) { + color-scheme: light; + + @include light.tokens; + @include utils.invert-on-light; +} diff --git a/app/javascript/styles/mastodon/widgets.scss b/app/javascript/styles/mastodon/widgets.scss index 266a9ca93071f2..69c79cd1e6f2a0 100644 --- a/app/javascript/styles/mastodon/widgets.scss +++ b/app/javascript/styles/mastodon/widgets.scss @@ -1,6 +1,5 @@ @use 'sass:color'; @use 'variables' as *; -@use 'functions' as *; .directory { &__tag { @@ -12,24 +11,24 @@ display: flex; align-items: center; justify-content: space-between; - border: 1px solid var(--background-border-color); + border: 1px solid var(--color-border-primary); border-radius: 4px; padding: 15px; text-decoration: none; color: inherit; - box-shadow: 0 0 15px color.change($base-shadow-color, $alpha: 0.2); + box-shadow: 0 0 15px var(--color-shadow-primary); } & > a { &:hover, &:active, &:focus { - background: $ui-base-color; + background: var(--color-bg-primary); } } &.active > a { - background: $ui-highlight-color; + background: var(--color-bg-brand-base); cursor: default; } @@ -42,30 +41,25 @@ flex: 1 1 auto; font-size: 18px; font-weight: 700; - color: $primary-text-color; + color: var(--color-text-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - .fa { - color: $darker-text-color; - } - small { display: block; font-weight: 400; font-size: 15px; margin-top: 8px; - color: $darker-text-color; + color: var(--color-text-secondary); } } &.active h4 { &, - .fa, small, .trends__item__current { - color: $primary-text-color; + color: var(--color-text-primary); } } @@ -75,7 +69,7 @@ } &.active .avatar-stack .account__avatar { - border-color: $ui-highlight-color; + border-color: var(--color-text-brand); } .trends__item__current { @@ -100,7 +94,7 @@ thead th { text-align: center; text-transform: uppercase; - color: $darker-text-color; + color: var(--color-text-secondary); font-weight: 700; padding: 10px; @@ -112,7 +106,7 @@ tbody td { padding: 15px 0; vertical-align: middle; - border-bottom: 1px solid lighten($ui-base-color, 8%); + border-bottom: 1px solid var(--color-border-primary); } tbody tr:last-child td { @@ -124,11 +118,11 @@ text-align: center; font-size: 15px; font-weight: 500; - color: $primary-text-color; + color: var(--color-text-primary); small { display: block; - color: $darker-text-color; + color: var(--color-text-secondary); font-weight: 400; font-size: 14px; } @@ -137,7 +131,7 @@ tbody td.accounts-table__extra { width: 120px; text-align: end; - color: $darker-text-color; + color: var(--color-text-secondary); padding-inline-end: 16px; a { @@ -147,7 +141,7 @@ &:focus, &:hover, &:active { - color: $highlight-text-color; + color: var(--color-text-brand); } } } @@ -164,15 +158,15 @@ .icon { &.active { - color: $highlight-text-color; + color: var(--color-text-brand); } &.passive { - color: $passive-text-color; + color: var(--color-text-warning); } &.active.passive { - color: $active-passive-text-color; + color: var(--color-text-success); } } diff --git a/app/javascript/styles/win95.scss b/app/javascript/styles/win95.scss deleted file mode 100644 index 0a58604de0c37b..00000000000000 --- a/app/javascript/styles/win95.scss +++ /dev/null @@ -1,2687 +0,0 @@ -// win95 theme from cybrespace. - -// Modified by kibi! to use webpack package syntax for urls (eg, -// `url(@/images/…)`) for easy importing into skins. - -@use 'mastodon/functions' as *; - -$win95-bg: #bfbfbf; -$win95-dark-grey: #404040; -$win95-mid-grey: #808080; -$win95-window-header: #00007f; -$win95-tooltip-yellow: #ffffcc; -$win95-blue: blue; -$win95-cyan: #008080; - -@use 'mastodon/variables' as * with ( - $ui-base-lighter-color: $win95-dark-grey, - $ui-highlight-color: $win95-window-header -); - -@use 'application'; - -@mixin win95-border-outset() { - border-left: 2px solid #efefef; - border-top: 2px solid #efefef; - border-right: 2px solid #404040; - border-bottom: 2px solid #404040; - border-radius:0px; -} - -@mixin win95-border-outset-sides-only() { - border-left: 2px solid #efefef; - border-right: 2px solid #404040; - border-radius:0px; -} - -@mixin win95-outset() { - box-shadow: inset -1px -1px 0px #000000, - inset 1px 1px 0px #ffffff, - inset -2px -2px 0px #808080, - inset 2px 2px 0px #dfdfdf; - border-radius:0px; -} - -@mixin win95-outset-no-highlight() { - box-shadow: inset -1px -1px 0px #000000, - inset -2px -2px 0px #808080; - border-radius:0px; -} - -@mixin win95-border-inset() { - border-left: 2px solid #404040; - border-top: 2px solid #404040; - border-right: 2px solid #efefef; - border-bottom: 2px solid #efefef; - border-radius:0px; -} - -@mixin win95-border-slight-inset() { - border-left: 1px solid #404040; - border-top: 1px solid #404040; - border-right: 1px solid #efefef; - border-bottom: 1px solid #efefef; - border-radius:0px; -} - -@mixin win95-inset() { - box-shadow: inset 1px 1px 0px #000000, - inset -1px -1px 0px #ffffff, - inset 2px 2px 0px #808080, - inset -2px -2px 0px #dfdfdf; - border-width:0px; - border-radius:0px; -} - - -@mixin win95-tab() { - box-shadow: inset -1px 0px 0px #000000, - inset 1px 0px 0px #ffffff, - inset 0px 1px 0px #ffffff, - inset 0px 2px 0px #dfdfdf, - inset -2px 0px 0px #808080, - inset 2px 0px 0px #dfdfdf; - border-radius:0px; - border-top-left-radius: 1px; - border-top-right-radius: 1px; -} - -@mixin win95-border-groove() { - border-radius: 0px; - border: 2px groove #bfbfbf; -} - -@mixin win95-reset() { - box-shadow: unset; - border: 0px solid transparent; -} - -@font-face { - font-family:"premillenium"; - src: url('@/fonts/premillenium/MSSansSerif.ttf') format('truetype'); -} - -/* borrowed from cybrespace style: wider columns and full column width images */ - -@media screen and (min-width: 1300px) { - .drawer { - width: 17%; /* Not part of the flex fun */ - max-width: 400px; - min-width: 330px; - } - .layout-multiple-columns .column { - flex-grow: 1 !important; - max-width: 400px; - } -} - -/* Don't show outline around statuses if we're in - * mouse or touch mode (rather than keyboard) */ -[data-whatinput="mouse"], [data-whatinput="touch"] { - .status__content:focus, .status:focus, - .status__wrapper:focus, .status__content__text:focus { - outline: none; - } -} - -/* Less emphatic show more */ -.status__content__read-more-button { - font-size: 14px; - color: $dark-text-color; - - .status__prepend-icon { - padding-right: 4px; - } -} - -/* Show a little arrowey thing after the time in a - * status to signal that you can click it to see - * a detailed view */ -.status time:after, -.detailed-status__datetime span:after { - font: normal normal normal 14px/1 FontAwesome; - content: "\00a0\00a0\f08e"; -} - -/* Don't display the elephant mascot (we have our - * own, thanks) */ -.drawer__inner__mastodon { - display: none; -} - -/* Let the compose area/drawer be short, but - * expand if necessary */ -.drawer .drawer__inner { - overflow: visible; - height:inherit; - background-image: none; -} -.drawer__pager { - overflow-y:auto; -} - -/* Put a reasonable background on the single-column compose form */ -.layout-single-column .compose-panel { - background-color: $ui-base-color; - height: auto; - max-height: 100%; - overflow-y: visible; - margin-top: 65px; -} - -/* Better distinguish the search bar */ -.layout-single-column .compose-panel .search { - position:relative; - top: -55px; - margin-bottom: -55px; -} - -/* Use display: none instead of visibility:hidden - * to hide the suggested follows list on non-mobile */ -@media screen and (min-width: 630px) { - .search-results .trends { - display:none; - } -} - -/* Don't display the weird triangles on the modal layout, - * because they look strange on cybrespace themes. */ -.modal-layout__mastodon { - display:none; -} - -/* main win95 style */ - -html { - scrollbar-color: $win95-mid-grey transparent; -} - -body { - font-size:13px; - font-family: "MS Sans Serif", "premillenium", sans-serif; - color:black; -} - -.ui, -.ui .columns-area, -body.admin { - background: $win95-cyan; -} - -.loading-bar { - height:5px; - background-color: #000080; -} - -.tabs-bar__wrapper { - background-color: $win95-cyan; -} - -.tabs-bar { - background: $win95-bg; - @include win95-outset(); - height: 30px; -} - -.tabs-bar__link { - color:black; - border:2px outset $win95-bg; - border-top-width: 1px; - border-left-width: 1px; - margin:2px; - padding:3px; -} - -.tabs-bar__link.active { - @include win95-inset(); - color:black; -} - -.tabs-bar__link:last-child::before { - content:"Start"; - color:black; - font-weight:bold; - font-size:15px; - width:80%; - display:block; - position:absolute; - right:0px; -} - -.tabs-bar__link:last-child { - position:relative; - flex-basis:60px !important; - font-size:0px; - color:$win95-bg; - - background-image: url("@/images/start.png"); - background-repeat:no-repeat; - background-position:8%; - background-clip:padding-box; - background-size:auto 50%; -} - -.drawer .drawer__inner { - overflow: visible; - height:inherit; - background:$win95-bg; -} - -.drawer:after { - display:block; - content: " "; - - position:absolute; - bottom:15px; - left:15px; - width:132px; - height:117px; - background-image:url("@/images/clippy_wave.gif"), url("@/images/clippy_frame.png"); - background-repeat:no-repeat; - background-position: 4px 20px, 0px 0px; - z-index:0; -} - -.drawer__pager { - overflow-y:auto; - z-index:1; -} - -.privacy-dropdown__dropdown { - z-index:2; -} - -.column > .scrollable { - background: $win95-bg; - @include win95-border-outset(); - border-top-width:0px; -} - -.column-header__wrapper { - color:white; - font-weight:bold; - background:#7f7f7f; -} - -.column-header { - padding:0px; - font-size:13px; - background:#7f7f7f; - @include win95-border-outset(); - border-bottom-width:0px; - color:white; - font-weight:bold; - align-items:baseline; - min-height: 24px; -} - -.column-header > button { - padding: 0px; - min-height: 22px; -} - -.column-header__wrapper.active { - background:$win95-window-header; -} - -.column-header__wrapper.active::before { - display:none; -} -.column-header.active { - box-shadow:unset; - background:$win95-window-header; -} - -.column-header.active .column-header__icon { - color:white; -} - -.column-header__buttons { - max-height: 20px; - margin: 2px; - margin-left: -2px; -} - -.column-header__buttons button { - margin-left: 2px; -} - -.column-header__button { - background: $win95-bg; - color: black; - @include win95-outset(); - - line-height:0px; - font-size:14px; - padding:0px 4px; - - &:hover { - color: black; - } -} - -.column-header__button.active, .column-header__button.active:hover { - @include win95-inset(); - background-color:#7f7f7f; -} - -// selectivity -- needs to override .column-header > button -.column-header .column-header__back-button { - background: $win95-bg; - color: black; - padding:2px; - padding-right: 4px; - max-height: 20px; - min-height: unset; - margin: 2px; - @include win95-outset(); - font-size: 13px; - line-height: 17px; - font-weight:bold; -} - -.column-header__buttons .column-header__back-button { - margin: 0; -} - -.column-back-button { - background:$win95-bg; - color:black; - @include win95-outset(); - font-size:13px; - font-weight:bold; - - padding: 2px; - height: 26px; -} - -.column-back-button--slim-button { - position:absolute; - top:-22px; - right:4px; - max-height:20px; - padding: 1px 6px 0 2px; - box-sizing: border-box; -} - -.column-back-button__icon { - font-size:11px; - margin-top:-3px; -} - -.column-header__collapsible { - border-left:2px outset $win95-bg; - border-right:2px outset $win95-bg; -} - -.column-header__collapsible-inner { - background:$win95-bg; - color:black; -} - -.column-header__collapsible__extra { - color:black; -} - -.column-header__collapsible__extra div[role="group"] { - border: 2px groove #eee; - margin-bottom: 11px; - padding: 4px; -} - -.column-inline-form { - background-color: $win95-bg; - @include win95-border-outset(); - border-bottom-width:0px; - border-top-width:0px; - - align-items: baseline; -} - -.column-inline-form .icon-button { - font-size: 14px!important; - line-height: 17px!important; -} - -.column-inline-form .setting-text { - line-height: 17px; - padding-left: 4px; -} - -.column-settings__section { - color:black; - font-weight:bold; - font-size:11px; -} - -[role="group"] .column-settings__section { - display:inline-block; - background-color:$win95-bg; - position:relative; - - top: -14px; - top: calc(-1em - 0.5ex); - left: 4px; - - padding: 0px 4px; - margin-bottom: 0px; -} - -.setting-meta__label, .setting-toggle__label { - color:black; - font-weight:normal; -} - -.setting-meta__label span:before { - content:"("; -} -.setting-meta__label span:after { - content:")"; -} - -.setting-toggle { - line-height:13px; -} - -.react-toggle .react-toggle-track { - border-radius:0px; - background-color:white; - @include win95-border-inset(); - - width:12px; - height:12px; -} - -.react-toggle:hover:not(.react-toggle--disabled) .react-toggle-track { - background-color:white; -} - -.react-toggle .react-toggle-track-check { - left:2px; - transition:unset; -} - -.react-toggle .react-toggle-track-check svg path { - fill: black; -} - -.react-toggle .react-toggle-track-x { - display:none; -} - -.react-toggle .react-toggle-thumb { - border-radius:0px; - display:none; -} - -.text-btn { - background-color:$win95-bg; - @include win95-outset(); - padding:4px; -} - -.text-btn:hover { - text-decoration:none; - color:black; -} - -.text-btn:active { - @include win95-inset(); -} - -.setting-text { - color:black; - background-color:white; - @include win95-inset(); - font-size:13px; - padding:2px; -} - -.setting-text:active, .setting-text:focus, -.setting-text.light:active, .setting-text.light:focus { - color:black; - border-bottom:2px inset $win95-bg; -} - -.column-header__setting-arrows .column-header__setting-btn { - padding:3px 10px; -} - -.column-header__setting-arrows .column-header__setting-btn:last-child { - padding:3px 10px; -} - -.missing-indicator { - background-color:$win95-bg; - color:black; - @include win95-outset(); -} - -.missing-indicator > div { - background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAARCAYAAAA7bUf6AAAACXBIWXMAAC4jAAAuIwF4pT92AAAAF3pUWHRUaXRsZQAACJnLyy9Jyy/NSwEAD5IDblIFOhoAAAAXelRYdEF1dGhvcgAACJlLzijKz0vMAQALmgLoDsFj8gAAAQpJREFUOMuVlD0OwjAMhd2oQl04Axfo0IGBgYELcAY6cqQuSO0ZOEAZGBg6VKg74gwsEaoESRVHjusI8aQqzY8/PbtOEz1qkFSn2YevlaNOpLMJh2DwvixhuXtOa6/LCh51DUMEFkAsgAZD207Doin8mQ562JpRE5CHBAAhmIqD1L8AqzUUUJkxc6kr3AgAJ+NuvIWRdk7WcrKl0AUqcIBBHOiEbpS4m27mIL5Onfg3k0rgggeQuS2sDOGSahKR+glgqaGLgUJs951NN1q9D72cQqQWR9cr3sm9YcEssEuz6eEuZh2bu0aSOhQ1MBezu2O/+TVSvEFII3qLsZWrSA2AAUQIh1HpyP/kC++zjVSMj6ntAAAAAElFTkSuQmCC') no-repeat; - background-position:center center; -} - -.empty-column-indicator, -.error-column { - background: $win95-bg; - color: black; -} - -.notification__filter-bar { - background: $win95-bg; - @include win95-border-outset-sides-only(); - padding-top: 10px; - padding-left: 10px; - padding-right: 10px; - border-bottom: 2px solid #efefef; - overflow-y: visible; - - button { - background: transparent; - color: black; - padding: 8px 0; - align-self: end; - @include win95-tab(); - - &.active { - color: black; - top: 2px; - background-color: $win95-bg; - - &::before, &::after { - display:none; - } - } - } -} - -.status__wrapper { - border: 2px groove $win95-bg; - margin:4px; -} - -.status { - @include win95-border-slight-inset(); - background-color:white; - margin:4px; - padding-bottom:40px; - margin-bottom:8px; -} - -.status.status-direct { - background:$win95-bg; - &:focus, &:active { - background:$win95-bg; - } - - &:not(.read) { - background: white; - } -} -.focusable:focus .status.status-direct { - background:$win95-bg; -} - -[data-whatinput="mouse"], [data-whatinput="touch"] { - .status__content:focus, .status:focus, - .status__wrapper:focus, .status__content__text:focus { - background-color: $win95-bg; - } - - .status.status-direct, .detailed-status { - &:not(.read) { - .status__content:focus { - background-color: white; - } - } - } -} - -.status__content, .reply-indicator__content { - font-size:13px; - color: black; -} - -.status.light .status__relative-time, -.status.light .display-name span { - color: #7f7f7f; -} - -.status__action-bar { - box-sizing:border-box; - position:absolute; - bottom:-1px; - left:-1px; - background:$win95-bg; - width:calc(100% + 2px); - padding-left:10px; - padding: 4px 2px; - padding-bottom:4px; - border-bottom:2px groove $win95-bg; - border-top:1px outset $win95-bg; - text-align: right; -} - -.status__wrapper .status__action-bar { - border-bottom-width:0px; -} - -.status__action-bar-button { - float:right; -} - -.status__action-bar-dropdown { - margin-left:auto; - margin-right:10px; - - .icon-button { - min-width:28px; - } -} -.status.light .status__content a { - color:blue; -} - -.focusable:focus { - background: $win95-bg; - .detailed-status__action-bar { - background: $win95-bg; - } - - .status, .detailed-status { - background: white; - outline:2px dotted $win95-mid-grey; - } -} - -.dropdown__trigger.icon-button { - padding-right:6px; -} - -.detailed-status__action-bar-dropdown .icon-button { - min-width:28px; -} - -.detailed-status { - background:white; - background-clip:padding-box; - margin:4px; - border: 2px groove $win95-bg; - padding:4px; -} - -.detailed-status__display-name { - color:#7f7f7f; -} - -.detailed-status__display-name strong { - color:black; - font-weight:bold; -} -.account__avatar, -.account__avatar-overlay-base, -.account__header__avatar, -.account__avatar-overlay-overlay { - @include win95-border-slight-inset(); - clip-path:none; - filter: saturate(1.8) brightness(1.1); -} - -.detailed-status__action-bar { - background-color:$win95-bg; - border:0px; - border-bottom:2px groove $win95-bg; - margin-bottom:8px; - justify-items:left; - padding-left:4px; -} - -.icon-button { - background:$win95-bg; - @include win95-border-outset(); - padding:0px 0px 0px 0px; - margin-right:4px; - - color:#3f3f3f; - &.inverted, &:hover, &.inverted:hover, &:active, &:focus { - color:#3f3f3f; - } -} - -.icon-button:active { - @include win95-border-inset(); -} - -.status__action-bar > .icon-button { - padding:0px 15px 0px 0px; - min-width:25px; -} - -.icon-button.star-icon, -.icon-button.star-icon:active { - background:transparent; - border:none; -} - -.icon-button.star-icon.active { - color: $gold-star; - &:active, &:hover, &:focus { - color: $gold-star; - } -} - -.icon-button.star-icon > i { - background:$win95-bg; - @include win95-border-outset(); - padding-bottom:3px; -} - -.icon-button.star-icon:active > i { - @include win95-border-inset(); -} - -.text-icon-button { - color:$win95-dark-grey; -} - -.detailed-status__action-bar-dropdown { - margin-left:auto; - justify-content:right; - padding-right:16px; -} - -.detailed-status__button { - flex:0 0 auto; -} - -.detailed-status__button .icon-button { - padding-left:2px; - padding-right:25px; -} - -.status-card, .status-card.compact, a.status-card, a.status-card.compact { - border-radius:0px; - background:white; - border: 1px solid black; - color:black; - - &:hover { - background-color:white; - } -} - -.status-card__title { - color:blue; - text-decoration:underline; - font-weight:bold; -} - -.load-more { - width:auto; - margin:5px auto; - background: $win95-bg; - @include win95-outset(); - color:black; - padding: 2px 5px; - - &:hover { - background: $win95-bg; - color:black; - } -} - -.status-card__description { - color:black; -} - -.account__display-name strong, .status__display-name strong { - color:black; - font-weight:bold; -} - -.account .account__display-name { - color:black; -} - -.account { - border-bottom: none; -} - -.reply-indicator__content .status__content__spoiler-link, .status__content .status__content__spoiler-link { - background:$win95-bg; - @include win95-outset(); -} - -.reply-indicator__content .status__content__spoiler-link:hover, .status__content .status__content__spoiler-link:hover { - background:$win95-bg; -} - -.reply-indicator__content .status__content__spoiler-link:active, .status__content .status__content__spoiler-link:active { - @include win95-inset(); -} - -.reply-indicator__content a, .status__content a { - color:blue; -} - -.notification { - border: 2px groove $win95-bg; - margin:4px; -} - -.notification__message { - color:black; - font-size:13px; -} - -.notification__display-name { - font-weight:bold; -} - - -.drawer__header { - background: $win95-bg; - @include win95-border-outset(); - justify-content:left; - margin-bottom:0px; - padding-bottom:2px; - border-bottom:2px groove $win95-bg; -} - -.drawer__tab { - color:black; - @include win95-outset(); - padding:5px; - margin:2px; - flex: 0 0 auto; -} - -.drawer__tab:first-child::before { - content:"Start"; - color:black; - font-weight:bold; - font-size:15px; - width:80%; - display:block; - position:absolute; - right:0px; - -} - -.drawer__tab:first-child { - position:relative; - padding:5px 15px; - width:40px; - font-size:0px; - color:$win95-bg; - - background-image: url("@/images/start.png"); - background-repeat:no-repeat; - background-position:8%; - background-clip:padding-box; - background-size:auto 50%; -} - -.drawer__header a:hover { - background-color:transparent; -} - -.drawer__header a:first-child:hover { - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAOCAIAAACpTQvdAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAF3pUWHRBdXRob3IAAAiZS84oys9LzAEAC5oC6A7BY/IAAACWSURBVCiRhVJJDsQgDEuqOfRZ7a1P5gbP4uaJaEjTADMWQhHYjlk4p0wLnNdptdF4KvBUDyGzVwc2xO+uKtH+1o0ytEEmqFpuxlvFCGCxKbNIT56QCi2MzaA/2Mz+mERSOeqzJG2RUxkjdTabgPtFoZ1bZxcKvgPcLZVufAyR9Ni8v5dWDzfFx0giC1RvZFv6l35QQ/Mvv39XXgGzQpoAAAAASUVORK5CYII="); - background-repeat:no-repeat; - background-position:8%; - background-clip:padding-box; - background-size:auto 50%; - transition:unset; -} - -.drawer__tab:first-child { - -} - -.search { - background:$win95-bg; - padding-top:2px; - padding:2px; - border:2px outset $win95-bg; - border-top-width:0px; - border-bottom: 2px groove $win95-bg; - margin-bottom:0px; -} - -.search input { - background-color:white; - color:black; - @include win95-border-slight-inset(); -} - -.search__input:focus { - background-color:white; -} - -.search-popout { - box-shadow: unset; - color:black; - border-radius:0px; - background-color:$win95-tooltip-yellow; - border:1px solid black; - - h4 { - color:black; - text-transform: none; - font-weight:bold; - } -} - -.search-results__header { - background-color: $win95-bg; - color:black; - border-bottom:2px groove $win95-bg; -} - -.search-results__hashtag { - color:blue; -} - -.search-results__section h5:before { - display: none; -} - -.search-results__section h5 { - background: #bfbfbf; - span { - color: black; - padding: 0px 2px; - } -} - -.search-results__section { - border: 3px groove white; - margin: 11px 6px 9px 3px; -} - -.search-results__section .account:hover, -.search-results__section .account:hover .account__display-name, -.search-results__section .account:hover .account__display-name strong, -.search-results__section .search-results__hashtag:hover { - background-color:$win95-window-header; - color:white; -} - -.search__icon .fa { - color:#808080; - - &.active { - opacity:1.0; - } - - &:hover { - color: #808080; - } -} - -.trends__item__name a, -.trends__item__current { - color: black; -} - -.drawer__inner, -.drawer__inner.darker { - background-color:$win95-bg; - border: 2px outset $win95-bg; - border-top-width:0px; -} - -.navigation-bar { - color:black; -} - -.navigation-bar strong { - color:black; - font-weight:bold; -} - -.compose-form .autosuggest-textarea__textarea, -.compose-form .spoiler-input__input { - border-radius:0px; - @include win95-border-slight-inset(); -} - -.compose-form .autosuggest-textarea__textarea { - border-bottom:0px; -} - -.compose-form__uploads-wrapper { - border-radius:0px; - border-bottom:1px inset $win95-bg; - border-top-width:0px; -} - -.compose-form__upload-wrapper { - border-left:1px inset $win95-bg; - border-right:1px inset $win95-bg; -} - -.compose-form .compose-form__buttons-wrapper { - background-color: $win95-bg; - border:2px groove $win95-bg; - margin-top:4px; - padding:4px 8px; -} - -.compose-form__buttons { - background-color:$win95-bg; - border-radius:0px; - box-shadow:unset; -} - -.compose-form__buttons-separator { - border-left: 2px groove $win95-bg; -} - -.compose-form__poll-wrapper .icon-button.disabled { - color: $win95-mid-grey; -} - -.privacy-dropdown.active .privacy-dropdown__value.active, -.advanced-options-dropdown.open .advanced-options-dropdown__value { - background: $win95-bg; -} - -.privacy-dropdown.active .privacy-dropdown__value.active .icon-button { - color: $win95-dark-grey; -} - -.privacy-dropdown.active -.privacy-dropdown__value { - background: $win95-bg; - box-shadow:unset; -} - -.privacy-dropdown__option.active, .privacy-dropdown__option:hover, -.privacy-dropdown__option.active:hover { - background:$win95-window-header; -} - -.privacy-dropdown__dropdown, -.privacy-dropdown.active .privacy-dropdown__dropdown, -.advanced-options-dropdown__dropdown, -.advanced-options-dropdown.open .advanced-options-dropdown__dropdown -{ - box-shadow:unset; - color:black; - @include win95-outset(); - background: $win95-bg; -} - -.privacy-dropdown__option__content { - color:black; -} - -.privacy-dropdown__option__content strong { - font-weight:bold; -} - -.compose-form { - .compose-form__warning::before { - content:"Tip:"; - font-weight:bold; - display:block; - position:absolute; - top:-10px; - background-color:$win95-bg; - font-size:11px; - padding: 0px 5px; - } - - .compose-form__warning { - position:relative; - box-shadow:unset; - border:2px groove $win95-bg; - background-color:$win95-bg; - color:black; - } - - .compose-form__warning a { - color:blue; - } - - .compose-form__warning strong { - color:black; - text-decoration:underline; - } -} - -.compose-form__buttons button.active:last-child { - @include win95-border-inset(); - background: #dfdfdf; - color:#7f7f7f; -} - -.compose-form__upload-thumbnail { - border-radius:0px; - border:2px groove $win95-bg; - background-color:$win95-bg; - padding:2px; - box-sizing:border-box; -} - -.compose-form__upload-thumbnail .icon-button { - max-width:20px; - max-height:20px; - line-height:10px !important; -} - -.compose-form__upload-thumbnail .icon-button::before { - content:"X"; - font-size:13px; - font-weight:bold; - color:black; -} - -.compose-form__upload-thumbnail .icon-button i { - display:none; -} - -.emoji-picker-dropdown__menu { - z-index:2; -} - -.emoji-dialog.with-search { - box-shadow:unset; - border-radius:0px; - background-color:$win95-bg; - border:1px solid black; - box-sizing:content-box; - -} - -.emoji-dialog .emoji-search { - color:black; - background-color:white; - border-radius:0px; - @include win95-inset(); -} - -.emoji-dialog .emoji-search-wrapper { - border-bottom:2px groove $win95-bg; -} - -.emoji-dialog .emoji-category-title { - color:black; - font-weight:bold; -} - -.reply-indicator { - background-color:$win95-bg; - border-radius:3px; - border:2px groove $win95-bg; -} - -.button { - background-color:$win95-bg; - @include win95-outset(); - border-radius:0px; - color:black; - font-weight:bold; - - &:hover, &:focus, &:disabled { - background-color:$win95-bg; - } - - &:active { - @include win95-inset(); - } - - &:disabled { - color: #808080; - text-shadow: 1px 1px 0px #efefef; - - &:active { - @include win95-outset(); - } - } - -} - -.button.button-secondary { - background-color: $win95-bg; -} - -.column-link { - background-color:transparent; - color:black; - &:hover { - background-color: $win95-window-header; - color:white; - } -} - -.column-link__badge { - background-image: url('@/images/alert_badge.png'); - background-repeat: no-repeat; - background-size:contain; - background-color:transparent; - border-radius:0; - box-sizing: border-box; - width: 24px; - height:24px; - padding-top:4px; - padding-left:0px; - padding-right:1px; - text-align:center; - position:relative; - top:2px; -} - -.column-link:hover .column-link__badge { - color:black; -} - -.column-subheading { - background-color:$win95-bg; - color:black; - @include win95-border-outset-sides-only; -} - -.column { - overflow-y:auto; -} - -.getting-started { - background: none; - position:relative; - top:-30px; - padding-top:30px; - z-index:10; - overflow-y:auto; - background-color: $win95-cyan; -} - -.getting-started__wrapper { - padding-top:0px; - - box-shadow: inset -1px 0px 0px #000000, - inset 1px 1px 0px #ffffff, - inset -2px 0px 0px #808080, - inset 2px 2px 0px #dfdfdf; - border-radius:0px; - - background-color:$win95-bg; - border-bottom: 2px groove $win95-bg; - - height: unset !important; - - .navigation-bar { - padding-left: 45px; - } - - .column-subheading { - font-size:0px; - margin:0px; - padding:0px; - background-color: transparent; - color:black; - border-bottom: 2px groove $win95-bg; - text-transform: none; - } - -} - -.column-link { - background-size:32px 32px; - background-repeat:no-repeat; - background-position: 36px 50%; - padding-left:45px; - - &:hover { - background-size:32px 32px; - background-repeat:no-repeat; - background-position: 36px 50%; - } - - i { - font-size: 0px; - width:32px; - } - } - -.getting-started__wrapper::before { - content: "Start"; - display:block; - color:black; - font-weight:bold; - font-size:15px; - position:absolute; - top:0px; - left:0px; - padding:5px 15px; - width:50px; - font-size:16px; - padding-left:25px; - background-color:$win95-bg; - z-index:12; - - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAOCAIAAACpTQvdAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAF3pUWHRBdXRob3IAAAiZS84oys9LzAEAC5oC6A7BY/IAAACWSURBVCiRhVJJDsQgDEuqOfRZ7a1P5gbP4uaJaEjTADMWQhHYjlk4p0wLnNdptdF4KvBUDyGzVwc2xO+uKtH+1o0ytEEmqFpuxlvFCGCxKbNIT56QCi2MzaA/2Mz+mERSOeqzJG2RUxkjdTabgPtFoZ1bZxcKvgPcLZVufAyR9Ni8v5dWDzfFx0giC1RvZFv6l35QQ/Mvv39XXgGzQpoAAAAASUVORK5CYII="); - background-repeat:no-repeat; - background-position:8%; - background-clip:padding-box; - background-size:auto 50%; - - @include win95-border-inset(); -} - - -@media screen and (min-width: 360px) { - .getting-started__wrapper{ - margin-bottom:0px; - } -} - -@media screen and (max-width: 360px) { - .getting-started { - top:0px; - padding-top:0px; - } - - .getting-started__wrapper::before { - display:none; - } -} - -.getting-started__footer { - background-color: $win95-bg; - padding:0px; - padding-bottom:10px; - position:relative; - top:0px; - - @include win95-outset-no-highlight(); - - p { - margin-left: 45px; - } - - ul { - display:block; - li { - cursor:pointer; - display:block; - font-size:0px; - padding:0px; - line-height:0; - a { - padding:15px; - padding-left:77px; - line-height:1em; - font-size:16px; - display:block; - color:black; - background-size:32px 32px; - background-repeat:no-repeat; - background-position: 36px 50%; - &:hover { - text-decoration:none; - } - } - - &:hover { - background-color: $win95-window-header; - a { - color:white; - } - } - } - } -} - -.getting-started__footer::after { - content:"Mastodon 95"; - font-weight:bold; - font-size:23px; - color:white; - line-height:30px; - padding-left:20px; - padding-right:40px; - - left:0px; - box-sizing:border-box; - bottom:-32px; - display:block; - position:absolute; - background-color:#7f7f7f; - width:1000px; - height:32px; - - z-index:11; - - border-left: 2px solid #404040; - border-top: 2px solid #efefef; - border-right: 2px solid #efefef; - border-radius:0px; - - -ms-transform: rotate(-90deg); - - -webkit-transform: rotate(-90deg); - transform: rotate(-90deg); - transform-origin:top left; -} - -.layout-single-column .getting-started__footer::after { - display: none; -} - -.getting-started__wrapper + .flex-spacer { - display:none; -} - -.column-link[href="/web/timelines/home"] { - background-image: url("@/images/icon_home.png"); - &:hover { background-image: url("@/images/icon_home.png"); } -} -.column-link[href="/web/notifications"] { - background-image: url("@/images/icon_notifications.png"); - &:hover { background-image: url("@/images/icon_notifications.png"); } -} -.column-link[href="/web/timelines/public"] { - background-image: url("@/images/icon_public.png"); - &:hover { background-image: url("@/images/icon_public.png"); } -} -.column-link[href="/web/timelines/public/local"] { - background-image: url("@/images/icon_local.png"); - &:hover { background-image: url("@/images/icon_local.png"); } -} -.column-link[href="/web/timelines/direct"] { - background-image: url("@/images/icon_direct.png"); - &:hover { background-image: url("@/images/icon_direct.png"); } -} -.column-link[href="/web/pinned"] { - background-image: url("@/images/icon_pin.png"); - &:hover { background-image: url("@/images/icon_pin.png"); } -} -.column-link[href="/web/favourites"] { - background-image: url("@/images/icon_likes.png"); - &:hover { background-image: url("@/images/icon_likes.png"); } -} -.column-link[href="/web/lists"] { - background-image: url("@/images/icon_lists.png"); - &:hover { background-image: url("@/images/icon_lists.png"); } -} -.column-link[href="/web/follow_requests"] { - background-image: url("@/images/icon_follow_requests.png"); - &:hover { background-image: url("@/images/icon_follow_requests.png"); } -} -.column-link[href="/web/blocks"] { - background-image: url("@/images/icon_blocks.png"); - &:hover { background-image: url("@/images/icon_blocks.png"); } -} -.column-link[href="/web/domain_blocks"] { - background-image: url("@/images/icon_domain_blocks.png"); - &:hover { background-image: url("@/images/icon_domain_blocks.png"); } -} -.column-link[href="/web/mutes"] { - background-image: url("@/images/icon_mutes.png"); - &:hover { background-image: url("@/images/icon_mutes.png"); } -} -.column-link[href="/web/directory"] { - background-image: url("@/images/icon_profile_directory.png"); - &:hover { background-image: url("@/images/icon_profile_directory.png"); } -} -.column-link[href="/web/bookmarks"] { - background-image: url("@/images/icon_bookmarks.png"); - &:hover { background-image: url("@/images/icon_bookmarks.png"); } -} - -.getting-started__footer ul li a[href="/web/keyboard-shortcuts"] { - background-image: url("@/images/icon_keyboard_shortcuts.png"); - &:hover { background-image: url("@/images/icon_keyboard_shortcuts.png"); } -} -.getting-started__footer ul li a[href="/invites"] { - background-image: url("@/images/icon_invite.png"); - &:hover { background-image: url("@/images/icon_invite.png"); } -} -.getting-started__footer ul li a[href="/terms"] { - background-image: url("@/images/icon_tos.png"); - &:hover { background-image: url("@/images/icon_tos.png"); } -} -.getting-started__footer ul li a[href="https://docs.joinmastodon.org"] { - background-image: url("@/images/icon_docs.png"); - &:hover { background-image: url("@/images/icon_docs.png"); } -} -.getting-started__footer ul li a[href="/about/more"] { - background-image: url("@/images/icon_about.png"); - &:hover { background-image: url("@/images/icon_about.png"); } -} -.getting-started__footer ul li a[href="/auth/sign_out"] { - background-image: url("@/images/icon_logout.png"); - &:hover { background-image: url("@/images/icon_logout.png"); } -} -.getting-started__footer ul li a[href="https://joinmastodon.org/apps"] { - background-image: url("@/images/icon_mobile_apps.png"); - &:hover { background-image: url("@/images/icon_mobile_apps.png"); } -} -.getting-started__footer ul li a[href="/settings/applications"] { - background-image: url("@/images/icon_developers.png"); - &:hover { background-image: url("@/images/icon_developers.png"); } -} -.getting-started__footer ul li a[href="/auth/edit"] { - background-image: url("@/images/icon_settings.png"); - &:hover { background-image: url("@/images/icon_settings.png"); } -} - -.column .static-content.getting-started { - display:none; -} - -.keyboard-shortcuts kbd { - background-color: $win95-bg; -} - -.account__header { - background-color:#7f7f7f; -} - -.account__header .account__header__content { - color:white; -} - -.account__header__fields { - border-left: 1px solid black; - border-top: 1px solid black; - - dt { - background-color:$win95-bg; - color:black; - border-top: 1px solid #ffffff; - border-bottom: 1px solid $win95-mid-grey; - border-right: 1px solid $win95-mid-grey; - } - dd { - background-color:white; - border: 1px solid $win95-bg; - color:black; - } - dd,dt { - padding: 5px 8px; - } -} - -.account-authorize__wrapper { - border: 2px groove $win95-bg; - margin: 2px; - padding:2px; -} - -.domain .domain__domain-name strong { - color: black; -} - -.account--panel { - background-color: $win95-bg; - border:0px; - border-top: 2px groove $win95-bg; -} - -.account-authorize .account__header__content { - color:black; - margin:10px; -} - -.account__action-bar__tab > span { - color:black; - font-weight:bold; -} - -.account__action-bar__tab strong { - color:black; -} - -.account__action-bar { - border: unset; -} - -.account__action-bar__tab { - border: 1px outset $win95-bg; -} - -.account__action-bar__tab:active { - @include win95-inset(); -} - -.account__section-headline { - background: $win95-bg; - margin-top: 5px; - - & > a { - @include win95-tab(); - color: black; - padding: 5px; - - &.active { - background: $win95-bg; - @include win95-inset(); - color: black; - - &:before, &:after { - display: none; - } - } - } -} - -.dropdown--active .dropdown__content > ul, -.dropdown-menu { - background:$win95-tooltip-yellow; - border-radius:0px; - border:1px solid black; - box-shadow:unset; - margin-top: 6px; -} - -.dropdown-menu a { - background-color:transparent; -} - -.dropdown-menu__arrow { - &.bottom { - border-bottom-color: $win95-tooltip-yellow; - } - - &.top { - border-top-color: $win95-tooltip-yellow; - } - - &:before { - position: relative; - border: 0 solid transparent; - display: block; - content: ''; - left: -8px; - z-index: -1; - } - - &.bottom::before { - border-bottom-color: black; - border-width: 0 8px 6px; - bottom: 1px; - } - - &.top::before { - border-top-color: black; - border-width: 6px 8px 0; - top: -5px; - } -} - -.dropdown-menu { - margin-top: 6px; -} - -.dropdown--active::after { - display:none; -} - -.dropdown--active .icon-button { - color:black; - @include win95-inset(); -} - -.dropdown--active .dropdown__content > ul > li > a { - background:transparent; -} - -.dropdown--active .dropdown__content > ul > li > a:hover { - background:transparent; - color:black; - text-decoration:underline; -} - -.dropdown__sep, -.dropdown-menu__separator -{ - border-color:#7f7f7f; -} - -.detailed-status__action-bar-dropdown .dropdown--active .dropdown__content.dropdown__left { - left: unset; -} - -.dropdown > .icon-button, .detailed-status__action-bar .icon-button, -.status__action-bar .icon-button, .star-icon i { - /* i don't know what's going on with the inline - styles someone should look at the react code */ - height: 25px !important; - width: 28px !important; - box-sizing: border-box; -} - -.icon-button { - height: auto!important; - width: auto!important; -} - -.status__action-bar-dropdown .icon-button { - position: relative; - top: -1px; -} - -.fa-user-plus, .fa-user-times { - padding: 2px 0px 2px 1px; -} - -.fa-ellipsis-h { - padding-top: 3px; -} - -.status__action-bar-button .fa-floppy-o { - padding-top: 2px; -} - -.notification .status__action-bar { - border-bottom: none; -} - -.notification .status { - margin-bottom: 4px; -} - -.status__wrapper .status { - margin-bottom: 3px; -} - -.status__wrapper { - margin-bottom: 8px; -} - -.status__prepend { - color: black; - font-size: 13px; -} - -.icon-button .fa-retweet { - position: relative; - top: -1px; -} - -.embed-modal, .error-modal, .onboarding-modal, -.actions-modal, .boost-modal, .confirmation-modal, .report-modal { - @include win95-outset(); - background:$win95-bg; -} - -.actions-modal::before, -.boost-modal::before, -.confirmation-modal::before, -.report-modal::before { - content: "Confirmation"; - display:block; - background:$win95-window-header; - color:white; - font-weight:bold; - padding-left:2px; -} - -.boost-modal::before { - content: "Boost confirmation"; -} - -.boost-modal__action-bar > div > span:before { - content: "Tip: "; - font-weight:bold; -} - -.boost-modal__action-bar, .confirmation-modal__action-bar, .report-modal__action-bar { - background:$win95-bg; - margin-top:-15px; -} - -.embed-modal h4, .error-modal h4, .onboarding-modal h4 { - background:$win95-window-header; - color:white; - font-weight:bold; - padding:2px; - font-size:13px; - text-align:left; -} - -.media-modal .media-modal__close { - font-size: 14px!important; - line-height: 17px!important; - margin-right: 4vw; - margin-top: 4vh; -} - -.confirmation-modal__action-bar { - .confirmation-modal__cancel-button { - color:black; - - &:active, - &:focus, - &:hover { - color:black; - } - - &:active { - @include win95-inset(); - } - } -} - -.embed-modal .embed-modal__container .embed-modal__html, -.embed-modal .embed-modal__container .embed-modal__html:focus { - background:white; - color:black; - @include win95-inset(); -} - -.report-modal__target .media-modal__close { - top: 3px; - right: 0px; - font-size: 12px!important; - line-height: 13px!important; -} - -.report-modal__comment p { - font-size: 12px; - margin-bottom: 1em; - padding-left: 3px; -} - -.report-modal__comment .setting-text.light { - border-radius: 0; -} - -.report-modal__container { - margin-right: 2px; -} - -.report-modal::before { - height: 22px; - line-height: 23px; -} - -.status-check-box__status .media-gallery { - margin: unset; -} - -.modal-root__overlay, -.account__header > div { - background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAFnpUWHRUaXRsZQAACJnLzU9JzElKBwALgwLXaCRlPwAAABd6VFh0QXV0aG9yAAAImUvOKMrPS8wBAAuaAugOwWPyAAAAEUlEQVQImWNgYGD4z4AE/gMADwMB/414xEUAAAAASUVORK5CYII='); -} - - -.admin-wrapper::before { - position:absolute; - top:0px; - content:"Control Panel"; - color:white; - background-color:$win95-window-header; - font-size:13px; - font-weight:bold; - width:calc(100%); - margin: 2px; - display:block; - padding:2px; - padding-left:22px; - box-sizing:border-box; -} - -.admin-wrapper { - position:relative; - background: $win95-bg; - @include win95-outset(); - width:70vw; - height:80vh; - min-height:80vh; - margin:10vh auto; - color: black; - padding-top:24px; - flex-direction:column; - overflow:hidden; -} - -@media screen and (max-width: 1120px) { - .admin-wrapper { - width:90vw; - height:95vh; - margin:2.5vh auto; - } -} - -@media screen and (max-width: 740px) { - .admin-wrapper { - width:100vw; - height:95vh; - height:calc(100vh - 24px); - margin:0px 0px 0px 0px; - } -} - -.admin-wrapper .sidebar-wrapper { - position:static; - height:auto; - min-height:auto; - flex: 0 0 auto; - margin:2px; -} - -.admin-wrapper .content-wrapper { - flex: 1 1 auto; - width:calc(100% - 20px); - max-width:calc(100% - 20px); - @include win95-border-outset(); - position:relative; - margin-left:10px; - margin-right:10px; - margin-bottom:40px; - box-sizing:border-box; - overflow-y:scroll; - height: 100%; -} - -.admin-wrapper .content { - background-color: $win95-bg; - width: 100%; - max-width:100%; - min-height:100%; - box-sizing:border-box; - position:relative; -} -.admin-wrapper .content h4 { - color: black; -} - -.admin-wrapper .sidebar { - position:static; - background: $win95-bg; - color:black; - width: 100%; - height:auto; - padding-bottom: 20px; -} - -.admin-wrapper .sidebar .logo { - position:absolute; - top:2px; - left:4px; - width:18px; - height:18px; - margin:0px; -} - -.admin-wrapper .sidebar > ul { - background: $win95-bg; - margin:0px; - margin-left:8px; - color:black; - - & > li { - display:inline-block; - - &#settings, - &#admin { - padding:2px; - border: 0px solid transparent; - } - - &#logout { - position:absolute; - @include win95-outset(); - right:12px; - bottom:10px; - } - - &#web { - display:inline-block; - @include win95-outset(); - position:absolute; - left: 12px; - bottom: 10px; - } - - & > a { - display:inline-block; - @include win95-tab(); - padding:2px 5px; - margin:0px; - color:black; - vertical-align:baseline; - - &.selected { - background: $win95-bg; - color:black; - padding-top: 4px; - padding-bottom:4px; - } - - &:hover { - background: $win95-bg; - color:black; - } - } - - & > ul { - width:calc(100% - 20px); - background: transparent; - position:absolute; - left: 10px; - top:54px; - z-index:3; - - & > li { - background: $win95-bg; - display: inline-block; - vertical-align:baseline; - - & > a { - background: $win95-bg; - @include win95-tab(); - color:black; - padding:2px 5px; - position:relative; - z-index:3; - - &.selected { - background: $win95-bg; - color:black; - padding-bottom:4px; - padding-top: 4px; - padding-right:7px; - margin-left:-2px; - margin-right:-2px; - position:relative; - z-index:4; - - &:first-child { - margin-left:0px; - } - - &:hover { - background: transparent; - color:black; - } - } - - &:hover { - background: $win95-bg; - color:black; - } - } - } - } - } -} - -.admin-wrapper .sidebar ul .simple-navigation-active-leaf a:hover { - background: $win95-bg; -} - -@media screen and (max-width: 1520px) { - .admin-wrapper .sidebar > ul > li > ul { - max-width:1000px; - } - - .admin-wrapper .sidebar { - padding-bottom: 45px; - } -} - -@media screen and (max-width: 600px) { - .admin-wrapper .sidebar > ul > li > ul { - max-width:500px; - } - - .admin-wrapper { - .sidebar { - padding:0px; - padding-bottom: 70px; - width: 100%; - height: auto; - } - .content-wrapper { - overflow:auto; - height:80%; - height:calc(100% - 150px); - } - } -} - -.flash-message { - background-color:$win95-tooltip-yellow; - color:black; - border:1px solid black; - border-radius:0px; - position:absolute; - top:0px; - left:0px; - width:100%; -} - -.admin-wrapper table { - background-color: white; - @include win95-border-slight-inset(); -} - -.admin-wrapper .table th, .table td { - background-color:transparent; -} - -.admin-wrapper button.table-action-link, -.admin-wrapper a.table-action-link, -.admin-wrapper button.table-action-link:hover, -.admin-wrapper a.table-action-link:hover, -.admin-wrapper a.name-tag, -.admin-wrapper .name-tag, -.admin-wrapper a.inline-name-tag, -.admin-wrapper .inline-name-tag, -.admin-wrapper .content h2, -.admin-wrapper .content h3, -.simple_form .input.with_label .label_input > label, -.admin-wrapper .content h6, -.admin-wrapper .content > p, -.admin-wrapper .content .muted-hint, -.simple_form span.hint, -.simple_form h4, -.simple_form .check_boxes .checkbox label, -.simple_form .input.with_label.boolean .label_input > label, -.filters .filter-subset a, -.simple_form .input.radio_buttons .radio label, -a.table-action-link, -a.table-action-link:hover, -.simple_form .input.with_block_label > label, -.simple_form p.hint, -.admin-wrapper .content > p strong, -.simple_form .input.with_floating_label .label_input > label, -.admin-wrapper .content .fields-group h6 { - color:black; -} - -.report-card { - background: white; - border: 1px solid black; - border-radius: 0px; -} - -.report-card__summary__item:hover { - background: white; -} - -.report-card__summary__item__content a { - color: black; -} - -.directory__tag > a, .directory__tag > div, -.directory__tag > a:hover, .directory__tag > a:active, .directory__tag > a:focus { - background: white; - border: 1px solid black; - border-radius: 0px; -} - -.admin-wrapper .content .directory__tag h4 { - color: black; -} - -.simple_form .hint code { - background: $win95-bg; - border-radius: 0px; -} - -.input-copy { - background: transparent; - border: 0px solid transparent; -} - -.table > tbody > tr:nth-child(2n+1) > td, -.table > tbody > tr:nth-child(2n+1) > th { - background-color:white; -} - -.simple_form input[type=text], -.simple_form input[type=number], -.simple_form input[type=email], -.simple_form input[type=password], -.simple_form textarea { - color:black; - background-color:white; - @include win95-border-slight-inset(); - - &:active, &:focus { - background-color:white; - } -} - -.simple_form button, -.simple_form .button, -.simple_form .block-button -{ - background: $win95-bg; - @include win95-outset(); - color:black; - font-weight: normal; - - &:hover { - background: $win95-bg; - } -} - -.simple_form .warning, .table-form .warning -{ - background: $win95-tooltip-yellow; - color:black; - box-shadow: unset; - text-shadow:unset; - border:1px solid black; - - a { - color: blue; - text-decoration:underline; - } -} - -.simple_form button.negative, -.simple_form .button.negative, -.simple_form .block-button.negative -{ - background: $win95-bg; -} - -.simple_form select { - background: white; - border-radius: 0px; - color: black; -} - -.filters .filter-subset { - border: 2px groove $win95-bg; - padding:2px; -} - -.filters .filter-subset a::before { - content: ""; - background-color:white; - border-radius:50%; - border:2px solid black; - border-top-color:#7f7f7f; - border-left-color:#7f7f7f; - border-bottom-color:#f5f5f5; - border-right-color:#f5f5f5; - width:12px; - height:12px; - display:inline-block; - vertical-align:middle; - margin-right:2px; -} - -.filters .filter-subset a.selected::before { - background-color:black; - box-shadow: inset 0 0 0 3px white; -} - -.filters .filter-subset a, -.filters .filter-subset a:hover, -.filters .filter-subset a.selected { - color:black; - border-bottom: 0px solid transparent; -} - -.drawer__inner__mastodon { - display: none; -} - -.list-editor h4 { - padding: 2px; - color: white; - font-size: 14px; - font-weight: bold; - background: #00007f; - border-radius: 0; -} - -.list-editor { - @include win95-border-outset(); - box-shadow: unset; -} - -.list-editor .drawer__inner { - @include win95-inset(); - border-radius: 0; -} - -.batch-table__toolbar { - border-radius: 0px; - background-color:white; - border: 1px solid black; -} - -.batch-table__row { - border: 1px solid black; - background-color: white; - - &:hover { - background-color: white; - } -} - -.batch-table__row:nth-child(2n) { - background-color: white; -} - -.dashboard__counters > div > div, -.dashboard__counters > div > a { - background-color: $win95-bg; - border: 1px solid black; - border-radius: 1px; - color:black; - - &:hover { - background-color: $win95-bg; - } -} - -.dashboard__counters__label, -.dashboard__widgets a:not(.name-tag), -.dashboard__counters__num { - color:black; -} - -.card { - & > a, & > a:hover { - box-shadow: none; - - .card__img { - border-radius: 0px; - border: 1px solid black; - } - - .card__bar { - @include win95-outset(); - background-color: $win95-bg; - - .display-name { - strong, span { - color:black; - } - } - } - } -} - -/* Public layout stuff */ -body { - background: $win95-cyan; -} - -.public-layout { - max-width: 960px; - margin:10px auto; - background: $win95-bg; - padding:0px; - @include win95-outset(); - - .header { - background: $win95-window-header; - @include win95-border-outset(); - height: 22px; - margin: 0px; - padding:0px; - border-radius: 0px; - - .brand { - padding: 2px; - } - - .nav-button { - @include win95-outset(); - background: $win95-bg; - color:black; - margin: 2px; - margin-bottom: 0px; - &:hover { - background: $win95-bg; - color:black; - } - } - } - .footer { - background: none; - &, h4, ul a, .grid .column-2 h4 a { - color: black; - } - } - - .button.logo-button { - @include win95-outset(); - background: $win95-bg; - color:black; - &:hover { - background: $win95-bg; - color:black; - } - svg { - visibility:hidden; - } - &, &:hover { - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAOCAIAAACpTQvdAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAF3pUWHRBdXRob3IAAAiZS84oys9LzAEAC5oC6A7BY/IAAACWSURBVCiRhVJJDsQgDEuqOfRZ7a1P5gbP4uaJaEjTADMWQhHYjlk4p0wLnNdptdF4KvBUDyGzVwc2xO+uKtH+1o0ytEEmqFpuxlvFCGCxKbNIT56QCi2MzaA/2Mz+mERSOeqzJG2RUxkjdTabgPtFoZ1bZxcKvgPcLZVufAyR9Ni8v5dWDzfFx0giC1RvZFv6l35QQ/Mvv39XXgGzQpoAAAAASUVORK5CYII="); - background-repeat:no-repeat; - background-position:8%; - background-clip:padding-box; - background-size:auto 50%; - } - } - - .public-account-header { - @include win95-reset(); - padding: 4px; - .public-account-header__image { - @include win95-border-slight-inset(); - border-radius: 0px; - } - } - - .public-account-header__bar { - &, &:before { - background: transparent; - } - .avatar img { - @include win95-border-slight-inset(); - filter: saturate(1.8) brightness(1.1); - border-radius: 0px; - background: darken($win95-bg, 9.09%); - } - } - .public-account-header__extra__links { - margin-top: 0px; - a, a strong { - color: black; - } - } - - .public-account-header__tabs { - position: relative; - } - - .public-account-header__tabs__name { - display: inline-block; - position: relative; - background: $win95-tooltip-yellow; - border: 1px solid black; - padding: 4px; - margin-bottom: 24px; - - h1, h1 small { - color:black; - text-shadow: unset; - text-overflow: unset; - } - - &:after { - content: ""; - display:block; - position:absolute; - left: 0px; - bottom: -20px; - width: 0px; - height: 0px; - border-left: 20px solid $win95-tooltip-yellow; - border-bottom: 20px solid transparent; - } - &:before { - content: ""; - display:block; - position:absolute; - left: -1px; - bottom: -22px; - width: 0px; - height: 0px; - border-left: 22px solid black; - border-bottom: 22px solid transparent; - } - } - - .public-account-header__tabs__tabs { - .details-counters { - @include win95-border-groove(); - .counter { - .counter-number, .counter-label { - color: black; - } - &:after { - border-bottom-width: 0px; - } - &.active { - @include win95-border-slight-inset(); - } - } - } - } - - .public-account-bio { - @include win95-reset(); - @include win95-border-groove(); - background: $win95-bg; - margin-right: 10px; - .account__header__content, .roles { - color: black; - } - } - .public-account-bio__extra { - color: black; - } - - .status__prepend { - padding-top:5px; - } - .status__content , - .reply-indicator__content { - .status__content__spoiler-link { - color: $win95-dark-grey; - } - } - .account__section-headline { - margin-left: 10px; - } - .card-grid { - margin-left: 10px; - } - .status { - padding: 15px 15px 55px 78px; - } -} - -@media screen and (max-width: 1255px) { - .container { - width: 100%; - padding: 0px; - } -} - -.hero-widget { - box-shadow: none; - @include win95-border-groove(); - background: $win95-bg; - padding: 8px; - margin-right: 10px; - margin-top: 10px; -} -.hero-widget__text { - background: none; - color: black; -} -.hero-widget__img { - background: none; - border-radius: 0px; - border: 1px solid black; - img { - border-radius: 0px; - } -} - -.activity-stream { - @include win95-reset(); - @include win95-border-groove(); - - background: $win95-bg; - margin-top: 10px; - margin-left: 10px; - .entry { - background: none; - &:first-child:last-child, &:first-child { - .detailed-status, .status, .load-more { - border-radius: 0px; - } - } - } -} - -.nothing-here { - @include win95-reset(); - background: transparent; - color: black; -} - -.flash-message.notice { - border: 1px solid black; - background: $win95-tooltip-yellow; - color:black; -} - -.layout-single-column .compose-panel { - background: $win95-bg; -} - -.layout-single-column .status__wrapper .status { - padding-bottom: 50px; -} - -::-webkit-scrollbar { - width: 14px; -} - -::-webkit-scrollbar-track { - background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAAAAABX3VL4AAAADklEQVQIW2P4f4DhwH8ACoADf0PlskQAAAAASUVORK5CYII='); - - &:hover { - background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAAAAABX3VL4AAAADklEQVQIW2P4f4DhwH8ACoADf0PlskQAAAAASUVORK5CYII='); - } -} - -::-webkit-scrollbar-thumb { - background: #bfbfbf; - border-color: #efefef #404040 #404040 #efefef; - border-style: solid; - border-width: 2px; - - &:hover { - background: #bfbfbf; - border-color: #efefef #404040 #404040 #efefef; - border-style: solid; - border-width: 2px; - } - - &:active { - background: #bfbfbf; - border-color: #404040 #efefef #efefef #404040; - } -} - diff --git a/app/javascript/testing/api.ts b/app/javascript/testing/api.ts index 4948d719974ed0..096d2ce30ea14a 100644 --- a/app/javascript/testing/api.ts +++ b/app/javascript/testing/api.ts @@ -1,7 +1,10 @@ +import type { CompactEmoji } from 'emojibase'; import { http, HttpResponse } from 'msw'; import { action } from 'storybook/actions'; -import { relationshipsFactory } from './factories'; +import { toSupportedLocale } from '@/mastodon/features/emoji/locale'; + +import { customEmojiFactory, relationshipsFactory } from './factories'; export const mockHandlers = { mute: http.post<{ id: string }>('/api/v1/accounts/:id/mute', ({ params }) => { @@ -40,6 +43,25 @@ export const mockHandlers = { ); }, ), + emojiCustomData: http.get('/api/v1/custom_emojis', () => { + action('fetching custom emoji data')(); + return HttpResponse.json([customEmojiFactory()]); + }), + emojiData: http.get<{ locale: string }>( + '/packs-dev/emoji/:locale.json', + async ({ params }) => { + const locale = toSupportedLocale(params.locale); + action('fetching emoji data')(locale); + const { default: data } = (await import( + /* @vite-ignore */ + `emojibase-data/${locale}/compact.json` + )) as { + default: CompactEmoji[]; + }; + + return HttpResponse.json([data]); + }, + ), }; export const unhandledRequestHandler = ({ url }: Request) => { diff --git a/app/javascript/testing/factories.ts b/app/javascript/testing/factories.ts index f86aa772dc80f6..26b020d8c26169 100644 --- a/app/javascript/testing/factories.ts +++ b/app/javascript/testing/factories.ts @@ -1,4 +1,4 @@ -import { Map as ImmutableMap } from 'immutable'; +import { Map as ImmutableMap, List } from 'immutable'; import type { ApiRelationshipJSON } from '@/mastodon/api_types/relationships'; import type { ApiStatusJSON } from '@/mastodon/api_types/statuses'; @@ -7,6 +7,7 @@ import type { UnicodeEmojiData, } from '@/mastodon/features/emoji/types'; import { createAccountFromServerJSON } from '@/mastodon/models/account'; +import type { AnnualReport } from '@/mastodon/models/annual_report'; import type { Status } from '@/mastodon/models/status'; import type { ApiAccountJSON } from 'mastodon/api_types/accounts'; @@ -75,16 +76,18 @@ export const statusFactory: FactoryFunction = ({ mentions: [], tags: [], emojis: [], - content: '

This is a test status.

', + contentHtml: data.text ?? '

This is a test status.

', ...data, }); export const statusFactoryState = ( options: FactoryOptions = {}, ) => - ImmutableMap( - statusFactory(options) as unknown as Record, - ) as unknown as Status; + ImmutableMap({ + ...(statusFactory(options) as unknown as Record), + account: options.account?.id ?? '1', + tags: List(options.tags), + }) as unknown as Status; export const relationshipsFactory: FactoryFunction = ({ id, @@ -115,6 +118,7 @@ export function unicodeEmojiFactory( hexcode: 'test', label: 'Test', unicode: '🧪', + shortcodes: ['test_emoji'], ...data, }; } @@ -124,9 +128,125 @@ export function customEmojiFactory( ): CustomEmojiData { return { shortcode: 'custom', - static_url: 'emoji/custom/static', - url: 'emoji/custom', + static_url: '/custom-emoji/logo.svg', + url: '/custom-emoji/logo.svg', visible_in_picker: true, ...data, }; } + +interface AnnualReportState { + state: 'available'; + report: AnnualReport; +} + +interface AnnualReportFactoryOptions { + account_id?: string; + status_id?: string; + archetype?: AnnualReport['data']['archetype']; + year?: number; + top_hashtag?: AnnualReport['data']['top_hashtags'][0]; + without_posts?: boolean; +} + +export function annualReportFactory({ + account_id = '1', + status_id = '1', + archetype = 'lurker', + year, + top_hashtag, + without_posts = false, +}: AnnualReportFactoryOptions = {}): AnnualReportState { + return { + state: 'available', + report: { + schema_version: 2, + share_url: '#', + account_id, + year: year ?? 2025, + data: { + archetype, + time_series: [ + { + month: 1, + statuses: 0, + followers: 0, + following: 0, + }, + { + month: 2, + statuses: 0, + followers: 0, + following: 0, + }, + { + month: 3, + statuses: 0, + followers: 0, + following: 0, + }, + { + month: 4, + statuses: 0, + followers: 0, + following: 0, + }, + { + month: 5, + statuses: without_posts ? 0 : 1, + followers: 1, + following: 3, + }, + { + month: 6, + statuses: without_posts ? 0 : 7, + followers: 1, + following: 0, + }, + { + month: 7, + statuses: without_posts ? 0 : 2, + followers: 0, + following: 0, + }, + { + month: 8, + statuses: without_posts ? 0 : 2, + followers: 0, + following: 0, + }, + { + month: 9, + statuses: without_posts ? 0 : 11, + followers: 0, + following: 1, + }, + { + month: 10, + statuses: without_posts ? 0 : 12, + followers: 0, + following: 1, + }, + { + month: 11, + statuses: without_posts ? 0 : 6, + followers: 0, + following: 1, + }, + { + month: 12, + statuses: without_posts ? 0 : 4, + followers: 0, + following: 0, + }, + ], + top_hashtags: top_hashtag ? [top_hashtag] : [], + top_statuses: { + by_reblogs: status_id, + by_replies: status_id, + by_favourites: status_id, + }, + }, + }, + }; +} diff --git a/app/javascript/types/dom.d.ts b/app/javascript/types/dom.d.ts new file mode 100644 index 00000000000000..fcb1b0346faf13 --- /dev/null +++ b/app/javascript/types/dom.d.ts @@ -0,0 +1,6 @@ +declare namespace React { + interface HTMLAttributes extends AriaAttributes, DOMAttributes { + // Add inert attribute support, which is only in React 19.2. See: https://github.com/facebook/react/pull/24730 + inert?: ''; + } +} diff --git a/app/javascript/types/polymorphic.ts b/app/javascript/types/polymorphic.ts new file mode 100644 index 00000000000000..e58aa7b75eec4a --- /dev/null +++ b/app/javascript/types/polymorphic.ts @@ -0,0 +1,75 @@ +import { forwardRef } from 'react'; +import type { + ElementType, + ComponentPropsWithRef, + ForwardRefRenderFunction, + ReactElement, + Ref, + ForwardRefExoticComponent, +} from 'react'; + +// This complicated type file is based on the following posts: +// - https://www.tsteele.dev/posts/react-polymorphic-forwardref +// - https://www.kripod.dev/blog/behind-the-as-prop-polymorphism-done-well/ +// - https://github.com/radix-ui/primitives/blob/7101e7d6efb2bff13cc6761023ab85aeec73539e/packages/react/polymorphic/src/forwardRefWithAs.ts +// Whenever we upgrade to React 19 or later, we can remove all this because ref is a prop there. + +// Utils +interface AsProp { + as?: As; +} +type PropsOf = ComponentPropsWithRef; + +/** + * Extract the element instance type (e.g. HTMLButtonElement) from ComponentPropsWithRef: + * - For intrinsic elements, look up in JSX.IntrinsicElements + * - For components, infer from `ComponentPropsWithRef` + */ +type ElementRef = + As extends keyof React.JSX.IntrinsicElements + ? React.JSX.IntrinsicElements[As] extends { ref?: Ref } + ? Inst + : never + : ComponentPropsWithRef extends { ref?: Ref } + ? Inst + : never; + +/** + * Merge additional props with intrinsic/element props for `as`. + * Additional props win on conflicts. + */ +type PolymorphicProps< + As extends ElementType, + AdditionalProps extends object = object, +> = AdditionalProps & + AsProp & + Omit, keyof AdditionalProps | 'ref'>; + +/** + * Signature of a component created with `polymorphicForwardRef`. + */ +type PolymorphicWithRef< + DefaultAs extends ElementType, + AdditionalProps extends object = object, +> = ( + props: PolymorphicProps & { ref?: Ref> }, +) => ReactElement | null; + +/** + * The type of `polymorphicForwardRef`. + */ +type PolyRefFunction = < + DefaultAs extends ElementType, + AdditionalProps extends object = object, +>( + render: ForwardRefRenderFunction< + ElementRef, + PolymorphicProps + >, +) => PolymorphicWithRef & + ForwardRefExoticComponent>; + +/** + * Polymorphic `forwardRef` function. + */ +export const polymorphicForwardRef = forwardRef as PolyRefFunction; diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index d5da46a351b3f3..0def7ab50fdc2c 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true class ActivityPub::Activity::Create < ActivityPub::Activity - include FormattingHelper - def perform @account.schedule_refresh_if_stale! @@ -99,9 +97,9 @@ def process_status_params uri: @status_parser.uri, url: @status_parser.url || @status_parser.uri, account: @account, - text: converted_object_type? ? converted_text : (@status_parser.text || ''), + text: @status_parser.processed_text, language: @status_parser.language, - spoiler_text: converted_object_type? ? '' : (@status_parser.spoiler_text || ''), + spoiler_text: @status_parser.processed_spoiler_text, created_at: @status_parser.created_at, edited_at: @status_parser.edited_at && @status_parser.edited_at != @status_parser.created_at ? @status_parser.edited_at : nil, override_timestamps: @options[:override_timestamps], @@ -218,11 +216,11 @@ def process_tags def process_quote @quote_uri = @status_parser.quote_uri - return if @quote_uri.blank? + return unless @status_parser.quote? approval_uri = @status_parser.quote_approval_uri approval_uri = nil if unsupported_uri_scheme?(approval_uri) || TagManager.instance.local_url?(approval_uri) - @quote = Quote.new(account: @account, approval_uri: approval_uri, legacy: @status_parser.legacy_quote?) + @quote = Quote.new(account: @account, approval_uri: approval_uri, legacy: @status_parser.legacy_quote?, state: @status_parser.deleted_quote? ? :deleted : :pending) end def process_hashtag(tag) @@ -405,18 +403,6 @@ def in_reply_to_uri value_or_id(@object['inReplyTo']) end - def converted_text - [formatted_title, @status_parser.spoiler_text.presence, formatted_url].compact.join("\n\n") - end - - def formatted_title - "

#{@status_parser.title}

" if @status_parser.title.present? - end - - def formatted_url - linkify(@status_parser.url || @status_parser.uri) - end - def unsupported_media_type?(mime_type) mime_type.present? && !MediaAttachment.supported_mime_types.include?(mime_type) end diff --git a/app/lib/activitypub/activity/delete.rb b/app/lib/activitypub/activity/delete.rb index ce36cfe763f510..3e77f9b95564e3 100644 --- a/app/lib/activitypub/activity/delete.rb +++ b/app/lib/activitypub/activity/delete.rb @@ -59,9 +59,11 @@ def revoke_quote @quote = Quote.find_by(approval_uri: object_uri, quoted_account: @account) return if @quote.nil? - ActivityPub::Forwarder.new(@account, @json, @quote.status).forward! + ActivityPub::Forwarder.new(@account, @json, @quote.status).forward! if @quote.status.present? + @quote.reject! - DistributionWorker.perform_async(@quote.status_id, { 'update' => true }) + + DistributionWorker.perform_async(@quote.status_id, { 'update' => true }) if @quote.status.present? end def forwarder diff --git a/app/lib/activitypub/activity/quote_request.rb b/app/lib/activitypub/activity/quote_request.rb index 6d386f45dc041e..12f48ebb2b3da0 100644 --- a/app/lib/activitypub/activity/quote_request.rb +++ b/app/lib/activitypub/activity/quote_request.rb @@ -7,7 +7,7 @@ def perform return if non_matching_uri_hosts?(@account.uri, @json['id']) quoted_status = status_from_uri(object_uri) - return if quoted_status.nil? || !quoted_status.account.local? || !quoted_status.distributable? + return if quoted_status.nil? || !quoted_status.account.local? || !quoted_status.distributable? || quoted_status.reblog? if StatusPolicy.new(@account, quoted_status).quote? accept_quote_request!(quoted_status) @@ -36,6 +36,9 @@ def accept_quote_request!(quoted_status) # Ensure the user is notified LocalNotificationWorker.perform_async(quoted_status.account_id, status.quote.id, 'Quote', 'quote') + + # Ensure local followers get to see the post updated with approval + DistributionWorker.perform_async(status.id, { 'update' => true, 'skip_notifications' => true }) end def import_instrument(quoted_status) diff --git a/app/lib/activitypub/activity/update.rb b/app/lib/activitypub/activity/update.rb index 15025ca5e71f95..d94f8767618869 100644 --- a/app/lib/activitypub/activity/update.rb +++ b/app/lib/activitypub/activity/update.rb @@ -1,6 +1,9 @@ # frozen_string_literal: true class ActivityPub::Activity::Update < ActivityPub::Activity + # Updates to unknown objects older than that are ignored + OBJECT_AGE_THRESHOLD = 1.day + def perform @account.schedule_refresh_if_stale! @@ -8,10 +11,8 @@ def perform if equals_or_includes_any?(@object['type'], %w(Application Group Organization Person Service)) update_account - elsif equals_or_includes_any?(@object['type'], %w(Note Question)) + elsif supported_object_type? || converted_object_type? update_status - elsif converted_object_type? - Status.find_by(uri: object_uri, account_id: @account.id) end end @@ -28,6 +29,9 @@ def update_status @status = Status.find_by(uri: object_uri, account_id: @account.id) + # Ignore updates for old unknown objects, since those are updates we are not interested in + return if @status.nil? && object_too_old? + # We may be getting `Create` and `Update` out of order @status ||= ActivityPub::Activity::Create.new(@json, @account, **@options).perform @@ -35,4 +39,10 @@ def update_status ActivityPub::ProcessStatusUpdateService.new.call(@status, @json, @object, request_id: @options[:request_id]) end + + def object_too_old? + @object['published'].present? && @object['published'].to_datetime < OBJECT_AGE_THRESHOLD.ago + rescue Date::Error + false + end end diff --git a/app/lib/activitypub/dereferencer.rb b/app/lib/activitypub/dereferencer.rb index eb99842828c42d..4c4eb3a60902cd 100644 --- a/app/lib/activitypub/dereferencer.rb +++ b/app/lib/activitypub/dereferencer.rb @@ -44,7 +44,7 @@ def perform_request(uri, headers: nil) req = Request.new(:get, uri) - req.add_headers('Accept' => 'application/activity+json, application/ld+json') + req.add_headers('Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams", application/activity+json') req.add_headers(headers) if headers req.on_behalf_of(@signature_actor) if @signature_actor diff --git a/app/lib/activitypub/forwarder.rb b/app/lib/activitypub/forwarder.rb index 3a94f9669abd64..c5ff59fa5ae749 100644 --- a/app/lib/activitypub/forwarder.rb +++ b/app/lib/activitypub/forwarder.rb @@ -27,17 +27,25 @@ def reblogged_by_account_ids @reblogged_by_account_ids ||= @status.reblogs.includes(:account).references(:account).merge(Account.local).pluck(:account_id) end + def quoted_by_account_ids + @quoted_by_account_ids ||= @status.quotes.includes(:account).references(:account).merge(Account.local).pluck(:account_id) + end + + def shared_by_account_ids + reblogged_by_account_ids.concat(quoted_by_account_ids) + end + def signature_account_id @signature_account_id ||= if in_reply_to_local? in_reply_to.account_id else - reblogged_by_account_ids.first + shared_by_account_ids.first end end def inboxes @inboxes ||= begin - arr = inboxes_for_followers_of_reblogged_by_accounts + arr = inboxes_for_followers_of_shared_by_accounts arr += inboxes_for_followers_of_replied_to_account if in_reply_to_local? arr -= [@account.preferred_inbox_url] arr.uniq! @@ -45,8 +53,8 @@ def inboxes end end - def inboxes_for_followers_of_reblogged_by_accounts - Account.where(id: ::Follow.where(target_account_id: reblogged_by_account_ids).select(:account_id)).inboxes + def inboxes_for_followers_of_shared_by_accounts + Account.where(id: ::Follow.where(target_account_id: shared_by_account_ids).select(:account_id)).inboxes end def inboxes_for_followers_of_replied_to_account diff --git a/app/lib/activitypub/parser/interaction_policy_parser.rb b/app/lib/activitypub/parser/interaction_policy_parser.rb new file mode 100644 index 00000000000000..6587b245eeac85 --- /dev/null +++ b/app/lib/activitypub/parser/interaction_policy_parser.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +class ActivityPub::Parser::InteractionPolicyParser + def initialize(json, account) + @json = json + @account = account + end + + def bitmap + flags = 0 + return flags if @json.blank? + + flags |= subpolicy(@json['automaticApproval']) + flags <<= 16 + flags |= subpolicy(@json['manualApproval']) + + flags + end + + private + + def subpolicy(partial_json) + flags = 0 + + allowed_actors = Array(partial_json).dup + allowed_actors.uniq! + + flags |= InteractionPolicy::POLICY_FLAGS[:public] if allowed_actors.delete('as:Public') || allowed_actors.delete('Public') || allowed_actors.delete('https://www.w3.org/ns/activitystreams#Public') + flags |= InteractionPolicy::POLICY_FLAGS[:followers] if allowed_actors.delete(@account.followers_url) + flags |= InteractionPolicy::POLICY_FLAGS[:following] if allowed_actors.delete(@account.following_url) + + includes_target_actor = allowed_actors.delete(ActivityPub::TagManager.instance.uri_for(@account)).present? + + # Any unrecognized actor is marked as unsupported + flags |= InteractionPolicy::POLICY_FLAGS[:unsupported_policy] unless allowed_actors.empty? + + flags |= InteractionPolicy::POLICY_FLAGS[:disabled] if flags.zero? && includes_target_actor + + flags + end +end diff --git a/app/lib/activitypub/parser/status_parser.rb b/app/lib/activitypub/parser/status_parser.rb index 9eb6a16afbe3ad..e0a4f093b03378 100644 --- a/app/lib/activitypub/parser/status_parser.rb +++ b/app/lib/activitypub/parser/status_parser.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true class ActivityPub::Parser::StatusParser + include FormattingHelper include JsonLdHelper NORMALIZED_LOCALE_NAMES = LanguagesHelper::SUPPORTED_LOCALES.keys.index_by(&:downcase).freeze @@ -44,6 +45,16 @@ def text end end + def processed_text + return text || '' unless converted_object_type? + + [ + title.presence && "

#{title}

", + spoiler_text.presence, + linkify(url || uri), + ].compact.join("\n\n") + end + def spoiler_text if @object['summary'].present? @object['summary'] @@ -52,6 +63,12 @@ def spoiler_text end end + def processed_spoiler_text + return '' if converted_object_type? + + spoiler_text || '' + end + def title if @object['name'].present? @object['name'] @@ -120,6 +137,14 @@ def quote_policy flags end + def quote? + %w(quote _misskey_quote quoteUrl quoteUri).any? { |key| @object[key].present? } + end + + def deleted_quote? + @object['quote'].is_a?(Hash) && @object['quote']['type'] == 'Tombstone' + end + def quote_uri %w(quote _misskey_quote quoteUrl quoteUri).filter_map do |key| value_or_id(as_array(@object[key]).first) @@ -139,23 +164,27 @@ def quote_approval_uri as_array(@object['quoteAuthorization']).first end + def converted_object_type? + equals_or_includes_any?(@object['type'], ActivityPub::Activity::CONVERTED_TYPES) + end + private def quote_subpolicy(subpolicy) flags = 0 - allowed_actors = as_array(subpolicy) + allowed_actors = as_array(subpolicy).dup allowed_actors.uniq! - flags |= Status::QUOTE_APPROVAL_POLICY_FLAGS[:public] if allowed_actors.delete('as:Public') || allowed_actors.delete('Public') || allowed_actors.delete('https://www.w3.org/ns/activitystreams#Public') - flags |= Status::QUOTE_APPROVAL_POLICY_FLAGS[:followers] if allowed_actors.delete(@options[:followers_collection]) - flags |= Status::QUOTE_APPROVAL_POLICY_FLAGS[:following] if allowed_actors.delete(@options[:following_collection]) + flags |= InteractionPolicy::POLICY_FLAGS[:public] if allowed_actors.delete('as:Public') || allowed_actors.delete('Public') || allowed_actors.delete('https://www.w3.org/ns/activitystreams#Public') + flags |= InteractionPolicy::POLICY_FLAGS[:followers] if allowed_actors.delete(@options[:followers_collection]) + flags |= InteractionPolicy::POLICY_FLAGS[:following] if allowed_actors.delete(@options[:following_collection]) # Remove the special-meaning actor URI allowed_actors.delete(@options[:actor_uri]) # Any unrecognized actor is marked as unsupported - flags |= Status::QUOTE_APPROVAL_POLICY_FLAGS[:unsupported_policy] unless allowed_actors.empty? + flags |= InteractionPolicy::POLICY_FLAGS[:unsupported_policy] unless allowed_actors.empty? flags end diff --git a/app/lib/activitypub/tag_manager.rb b/app/lib/activitypub/tag_manager.rb index 870cbea7e40613..43574d3657e49d 100644 --- a/app/lib/activitypub/tag_manager.rb +++ b/app/lib/activitypub/tag_manager.rb @@ -39,13 +39,25 @@ def uri_for(target) case target.object_type when :person - target.instance_actor? ? instance_actor_url : account_url(target) + if target.instance_actor? + instance_actor_url + elsif target.numeric_ap_id? + ap_account_url(target.id) + else + account_url(target) + end when :conversation context_url(target) unless target.parent_account_id.nil? || target.parent_status_id.nil? when :note, :comment, :activity - return activity_account_status_url(target.account, target) if target.reblog? + if target.account.numeric_ap_id? + return activity_ap_account_status_url(target.account, target) if target.reblog? + + ap_account_status_url(target.account.id, target) + else + return activity_account_status_url(target.account, target) if target.reblog? - account_status_url(target.account, target) + account_status_url(target.account, target) + end when :emoji emoji_url(target) when :flag @@ -57,7 +69,7 @@ def approval_uri_for(quote, check_approval: true) return quote.approval_uri unless quote.quoted_account&.local? return if check_approval && !quote.accepted? - account_quote_authorization_url(quote.quoted_account, quote) + quote.quoted_account.numeric_ap_id? ? ap_account_quote_authorization_url(quote.quoted_account_id, quote) : account_quote_authorization_url(quote.quoted_account, quote) end def key_uri_for(target) @@ -68,6 +80,10 @@ def uri_for_username(username) account_url(username: username) end + def uri_for_account_id(id) + ap_account_url(id: id) + end + def generate_uri_for(_target) URI.join(root_url, 'payloads', SecureRandom.uuid) end @@ -75,7 +91,7 @@ def generate_uri_for(_target) def activity_uri_for(target) raise ArgumentError, 'target must be a local activity' unless %i(note comment activity).include?(target.object_type) && target.local? - activity_account_status_url(target.account, target) + target.account.numeric_ap_id? ? activity_ap_account_status_url(target.account.id, target) : activity_account_status_url(target.account, target) end def context_uri_for(target, page_params = nil) @@ -87,49 +103,61 @@ def context_uri_for(target, page_params = nil) def replies_uri_for(target, page_params = nil) raise ArgumentError, 'target must be a local activity' unless %i(note comment activity).include?(target.object_type) && target.local? - account_status_replies_url(target.account, target, page_params) + target.account.numeric_ap_id? ? ap_account_status_replies_url(target.account.id, target, page_params) : account_status_replies_url(target.account, target, page_params) end def likes_uri_for(target) raise ArgumentError, 'target must be a local activity' unless %i(note comment activity).include?(target.object_type) && target.local? - account_status_likes_url(target.account, target) + target.account.numeric_ap_id? ? ap_account_status_likes_url(target.account.id, target) : account_status_likes_url(target.account, target) end def shares_uri_for(target) raise ArgumentError, 'target must be a local activity' unless %i(note comment activity).include?(target.object_type) && target.local? - account_status_shares_url(target.account, target) + target.account.numeric_ap_id? ? ap_account_status_shares_url(target.account.id, target) : account_status_shares_url(target.account, target) end def following_uri_for(target, ...) raise ArgumentError, 'target must be a local account' unless target.local? - account_following_index_url(target, ...) + target.numeric_ap_id? ? ap_account_following_index_url(target.id, ...) : account_following_index_url(target, ...) end def followers_uri_for(target, ...) return target.followers_url.presence unless target.local? - account_followers_url(target, ...) + target.numeric_ap_id? ? ap_account_followers_url(target.id, ...) : account_followers_url(target, ...) end def collection_uri_for(target, ...) - raise NotImplementedError unless target.local? + raise ArgumentError, 'target must be a local account' unless target.local? - account_collection_url(target, ...) + target.numeric_ap_id? ? ap_account_collection_url(target.id, ...) : account_collection_url(target, ...) end def inbox_uri_for(target) - raise NotImplementedError unless target.local? + raise ArgumentError, 'target must be a local account' unless target.local? - target.instance_actor? ? instance_actor_inbox_url : account_inbox_url(target) + if target.instance_actor? + instance_actor_inbox_url + elsif target.numeric_ap_id? + ap_account_inbox_url(target.id) + else + account_inbox_url(target) + end end def outbox_uri_for(target, ...) - raise NotImplementedError unless target.local? + raise ArgumentError, 'target must be a local account' unless target.local? - target.instance_actor? ? instance_actor_outbox_url(...) : account_outbox_url(target, ...) + if target.instance_actor? + instance_actor_outbox_url(...) + elsif target.numeric_ap_id? + ap_account_outbox_url(target.id, ...) + else + account_outbox_url(target, ...) + end end # Primary audience of a status @@ -262,10 +290,9 @@ def uri_to_local_account_params(uri) path_params = Rails.application.routes.recognize_path(uri) - # TODO: handle numeric IDs case path_params[:controller] when 'accounts' - [:username, path_params[:username]] + path_params.key?(:username) ? [:username, path_params[:username]] : [:id, path_params[:id]] when 'instance_actors' [:id, -99] end diff --git a/app/lib/annual_report.rb b/app/lib/annual_report.rb index 275cc4b87d3cad..689fbca399f74e 100644 --- a/app/lib/annual_report.rb +++ b/app/lib/annual_report.rb @@ -5,33 +5,54 @@ class AnnualReport SOURCES = [ AnnualReport::Archetype, - AnnualReport::TypeDistribution, AnnualReport::TopStatuses, - AnnualReport::MostUsedApps, - AnnualReport::CommonlyInteractedWithAccounts, AnnualReport::TimeSeries, AnnualReport::TopHashtags, - AnnualReport::MostRebloggedAccounts, - AnnualReport::Percentiles, ].freeze - SCHEMA = 1 + SCHEMA = 2 def self.table_name_prefix 'annual_report_' end + def self.current_campaign + return unless Setting.wrapstodon + + datetime = Time.now.utc + datetime.year if datetime.month == 12 && (10..31).cover?(datetime.day) + end + def initialize(account, year) @account = account @year = year end - def self.prepare(year) - SOURCES.each do |klass| - klass.prepare(year) + def eligible? + with_read_replica do + SOURCES.all? { |klass| klass.new(@account, @year).eligible? } end end + def state + return 'available' if GeneratedAnnualReport.exists?(account_id: @account.id, year: @year) + + async_refresh = AsyncRefresh.new(refresh_key) + + if async_refresh.running? + yield async_refresh if block_given? + 'generating' + elsif AnnualReport.current_campaign == @year && eligible? + 'eligible' + else + 'ineligible' + end + end + + def refresh_key + "wrapstodon:#{@account.id}:#{@year}" + end + def generate return if GeneratedAnnualReport.exists?(account: @account, year: @year) @@ -39,7 +60,8 @@ def generate account: @account, year: @year, schema_version: SCHEMA, - data: data + data: data, + share_key: SecureRandom.hex(8) ) end diff --git a/app/lib/annual_report/commonly_interacted_with_accounts.rb b/app/lib/annual_report/commonly_interacted_with_accounts.rb deleted file mode 100644 index 219c30063a443a..00000000000000 --- a/app/lib/annual_report/commonly_interacted_with_accounts.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -class AnnualReport::CommonlyInteractedWithAccounts < AnnualReport::Source - MINIMUM_INTERACTIONS = 1 - SET_SIZE = 40 - - def generate - { - commonly_interacted_with_accounts: commonly_interacted_with_accounts.map do |(account_id, count)| - { - account_id: account_id.to_s, - count: count, - } - end, - } - end - - private - - def commonly_interacted_with_accounts - report_statuses.not_replying_to_account(@account).group(:in_reply_to_account_id).having(minimum_interaction_count).order(count_all: :desc).limit(SET_SIZE).count - end - - def minimum_interaction_count - Arel.star.count.gt(MINIMUM_INTERACTIONS) - end -end diff --git a/app/lib/annual_report/most_reblogged_accounts.rb b/app/lib/annual_report/most_reblogged_accounts.rb deleted file mode 100644 index df4dedb7344781..00000000000000 --- a/app/lib/annual_report/most_reblogged_accounts.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -class AnnualReport::MostRebloggedAccounts < AnnualReport::Source - MINIMUM_REBLOGS = 1 - SET_SIZE = 10 - - def generate - { - most_reblogged_accounts: most_reblogged_accounts.map do |(account_id, count)| - { - account_id: account_id.to_s, - count: count, - } - end, - } - end - - private - - def most_reblogged_accounts - report_statuses.only_reblogs.joins(reblog: :account).group(accounts: [:id]).having(minimum_reblog_count).order(count_all: :desc).limit(SET_SIZE).count - end - - def minimum_reblog_count - Arel.star.count.gt(MINIMUM_REBLOGS) - end -end diff --git a/app/lib/annual_report/most_used_apps.rb b/app/lib/annual_report/most_used_apps.rb deleted file mode 100644 index a2e1aca452ccee..00000000000000 --- a/app/lib/annual_report/most_used_apps.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -class AnnualReport::MostUsedApps < AnnualReport::Source - SET_SIZE = 10 - - def generate - { - most_used_apps: most_used_apps.map do |(name, count)| - { - name: name, - count: count, - } - end, - } - end - - private - - def most_used_apps - report_statuses.joins(:application).group(oauth_applications: [:name]).order(count_all: :desc).limit(SET_SIZE).count - end -end diff --git a/app/lib/annual_report/percentiles.rb b/app/lib/annual_report/percentiles.rb deleted file mode 100644 index 2b0305c41554fd..00000000000000 --- a/app/lib/annual_report/percentiles.rb +++ /dev/null @@ -1,37 +0,0 @@ -# frozen_string_literal: true - -class AnnualReport::Percentiles < AnnualReport::Source - def self.prepare(year) - AnnualReport::StatusesPerAccountCount.connection.exec_query(<<~SQL.squish, nil, [year, Mastodon::Snowflake.id_at(DateTime.new(year).beginning_of_year), Mastodon::Snowflake.id_at(DateTime.new(year).end_of_year)]) - INSERT INTO annual_report_statuses_per_account_counts (year, account_id, statuses_count) - SELECT $1, account_id, count(*) - FROM statuses - WHERE id BETWEEN $2 AND $3 - AND (local OR uri IS NULL) - GROUP BY account_id - ON CONFLICT (year, account_id) DO NOTHING - SQL - end - - def generate - { - percentiles: { - statuses: 100.0 - ((total_with_fewer_statuses / (total_with_any_statuses + 1.0)) * 100), - }, - } - end - - private - - def statuses_created - @statuses_created ||= report_statuses.count - end - - def total_with_fewer_statuses - @total_with_fewer_statuses ||= AnnualReport::StatusesPerAccountCount.where(year: year).where(statuses_count: ...statuses_created).count - end - - def total_with_any_statuses - @total_with_any_statuses ||= AnnualReport::StatusesPerAccountCount.where(year: year).count - end -end diff --git a/app/lib/annual_report/source.rb b/app/lib/annual_report/source.rb index 86528731f51080..2898c6a5a265fb 100644 --- a/app/lib/annual_report/source.rb +++ b/app/lib/annual_report/source.rb @@ -8,14 +8,14 @@ def initialize(account, year) @year = year end - def self.prepare(_year) - # Use this method if any pre-calculations must be made before individual annual reports are generated - end - def generate raise NotImplementedError end + def eligible? + true + end + protected def report_statuses diff --git a/app/lib/annual_report/time_series.rb b/app/lib/annual_report/time_series.rb index 3f9f0d52e88da7..fc2c5e2ce469ad 100644 --- a/app/lib/annual_report/time_series.rb +++ b/app/lib/annual_report/time_series.rb @@ -3,29 +3,24 @@ class AnnualReport::TimeSeries < AnnualReport::Source def generate { - time_series: (1..12).map do |month| - { - month: month, - statuses: statuses_per_month[month] || 0, - following: following_per_month[month] || 0, - followers: followers_per_month[month] || 0, - } - end, + time_series: [ + { + month: 12, + statuses: statuses_this_year, + followers: followers_this_year, + }, + ], } end private - def statuses_per_month - @statuses_per_month ||= report_statuses.group(:period).pluck(date_part_month.as('period'), Arel.star.count).to_h + def statuses_this_year + @statuses_this_year ||= report_statuses.count end - def following_per_month - @following_per_month ||= annual_relationships_by_month(@account.active_relationships) - end - - def followers_per_month - @followers_per_month ||= annual_relationships_by_month(@account.passive_relationships) + def followers_this_year + @followers_this_year ||= @account.passive_relationships.where(created_in_year, @year).count end def date_part_month @@ -34,14 +29,6 @@ def date_part_month SQL end - def annual_relationships_by_month(relationships) - relationships - .where(created_in_year, @year) - .group(:period) - .pluck(date_part_month.as('period'), Arel.star.count) - .to_h - end - def created_in_year Arel.sql(<<~SQL.squish) DATE_PART('year', created_at) = ? diff --git a/app/lib/annual_report/top_hashtags.rb b/app/lib/annual_report/top_hashtags.rb index 42420a27707120..9ed0057bdd3ede 100644 --- a/app/lib/annual_report/top_hashtags.rb +++ b/app/lib/annual_report/top_hashtags.rb @@ -2,7 +2,7 @@ class AnnualReport::TopHashtags < AnnualReport::Source MINIMUM_TAGGINGS = 1 - SET_SIZE = 40 + SET_SIZE = 1 def generate { @@ -15,6 +15,10 @@ def generate } end + def eligible? + report_statuses.joins(:tags).exists? + end + private def top_hashtags diff --git a/app/lib/annual_report/top_statuses.rb b/app/lib/annual_report/top_statuses.rb index f32bd09a15aee1..06c53e2a487f32 100644 --- a/app/lib/annual_report/top_statuses.rb +++ b/app/lib/annual_report/top_statuses.rb @@ -5,12 +5,16 @@ def generate { top_statuses: { by_reblogs: status_identifier(most_reblogged_status), - by_favourites: status_identifier(most_favourited_status), - by_replies: status_identifier(most_replied_status), + by_favourites: nil, + by_replies: nil, }, } end + def eligible? + report_statuses.distributable_visibility.exists? + end + private def status_identifier(status) @@ -39,7 +43,7 @@ def most_replied_status def base_scope report_statuses - .public_visibility + .distributable_visibility .joins(:status_stat) end end diff --git a/app/lib/annual_report/type_distribution.rb b/app/lib/annual_report/type_distribution.rb deleted file mode 100644 index 0534055c28e357..00000000000000 --- a/app/lib/annual_report/type_distribution.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -class AnnualReport::TypeDistribution < AnnualReport::Source - def generate - { - type_distribution: { - total: report_statuses.count, - reblogs: report_statuses.only_reblogs.count, - replies: report_statuses.where.not(in_reply_to_id: nil).not_replying_to_account(@account).count, - standalone: report_statuses.without_replies.without_reblogs.count, - }, - } - end -end diff --git a/app/lib/application_extension.rb b/app/lib/application_extension.rb index d8090d15bcba33..bc6c7561cca464 100644 --- a/app/lib/application_extension.rb +++ b/app/lib/application_extension.rb @@ -28,7 +28,7 @@ def confirmation_redirect_uri end def redirect_uris - # Doorkeeper stores the redirect_uri value as a newline delimeted list in + # Doorkeeper stores the redirect_uri value as a newline delimited list in # the database: redirect_uri.split end diff --git a/app/lib/attachment_batch.rb b/app/lib/attachment_batch.rb index 374abfac49038d..1443a1ec60cb39 100644 --- a/app/lib/attachment_batch.rb +++ b/app/lib/attachment_batch.rb @@ -112,10 +112,12 @@ def remove_files keys.each_slice(LIMIT) do |keys_slice| logger.debug { "Deleting #{keys_slice.size} objects" } - bucket.delete_objects(delete: { - objects: keys_slice.map { |key| { key: key } }, - quiet: true, - }) + with_overridden_timeout(bucket.client, 120) do + bucket.delete_objects(delete: { + objects: keys_slice.map { |key| { key: key } }, + quiet: true, + }) + end rescue => e retries += 1 @@ -134,6 +136,20 @@ def bucket @bucket ||= records.first.public_send(@attachment_names.first).s3_bucket end + # Currently, the aws-sdk-s3 gem does not offer a way to cleanly override the timeout + # per-request. So we change the client's config instead. As this client will likely + # be re-used for other jobs, restore its original configuration in an `ensure` block. + def with_overridden_timeout(s3_client, longer_read_timeout) + original_timeout = s3_client.config.http_read_timeout + s3_client.config.http_read_timeout = [original_timeout, longer_read_timeout].max + + begin + yield + ensure + s3_client.config.http_read_timeout = original_timeout + end + end + def nullified_attributes @attachment_names.flat_map { |attachment_name| NULLABLE_ATTRIBUTES.map { |attribute| "#{attachment_name}_#{attribute}" } & klass.column_names }.index_with(nil) end diff --git a/app/lib/extractor.rb b/app/lib/extractor.rb index 7e647a75873a46..206d989bf3d6f5 100644 --- a/app/lib/extractor.rb +++ b/app/lib/extractor.rb @@ -54,7 +54,7 @@ def extract_mentions_or_lists_with_indices(text) end def extract_hashtags_with_indices(text, _options = {}) - return [] unless text&.index('#') + return [] unless text&.index(/[##]/) possible_entries = [] diff --git a/app/lib/interaction_policy.rb b/app/lib/interaction_policy.rb new file mode 100644 index 00000000000000..8d0b8dd060b47c --- /dev/null +++ b/app/lib/interaction_policy.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +class InteractionPolicy + POLICY_FLAGS = { + unsupported_policy: (1 << 0), # Not supported by Mastodon + public: (1 << 1), # Everyone is allowed to interact + followers: (1 << 2), # Only followers may interact + following: (1 << 3), # Only accounts followed by the target may interact + disabled: (1 << 4), # All interaction explicitly disabled + }.freeze + + class SubPolicy + def initialize(bitmap) + @bitmap = bitmap + end + + def as_keys + POLICY_FLAGS.keys.select { |key| @bitmap.anybits?(POLICY_FLAGS[key]) }.map(&:to_s) + end + + POLICY_FLAGS.each_key do |key| + define_method :"#{key}?" do + @bitmap.anybits?(POLICY_FLAGS[key]) + end + end + + def missing? + @bitmap.zero? + end + end + + attr_reader :automatic, :manual + + def initialize(bitmap) + @bitmap = bitmap + @automatic = SubPolicy.new(@bitmap >> 16) + @manual = SubPolicy.new(@bitmap & 0xFFFF) + end +end diff --git a/app/lib/permalink_redirector.rb b/app/lib/permalink_redirector.rb index 142a05d10d3daa..19fb3f401c9b6c 100644 --- a/app/lib/permalink_redirector.rb +++ b/app/lib/permalink_redirector.rb @@ -12,21 +12,21 @@ def object @object ||= begin if at_username_status_request? || statuses_status_request? status = Status.find_by(id: second_segment) - status if status&.distributable? && !status&.local? + status if status&.distributable? && !status&.local? && !status&.account&.suspended? elsif at_username_request? username, domain = first_segment.delete_prefix('@').split('@') domain = nil if TagManager.instance.local_domain?(domain) account = Account.find_remote(username, domain) - account unless account&.local? + account if !account&.local? && !account&.suspended? elsif accounts_request? && record_integer_id_request? account = Account.find_by(id: second_segment) - account unless account&.local? + account if !account&.local? && !account&.suspended? end end end def redirect_path - return ActivityPub::TagManager.instance.url_for(object) if object.present? + return ActivityPub::TagManager.instance.url_for(object) || ActivityPub::TagManager.instance.uri_for(object) if object.present? @path.delete_prefix('/deck') if @path.start_with?('/deck') end diff --git a/app/lib/request.rb b/app/lib/request.rb index 4858aa4bc24d4f..06c917c4266e02 100644 --- a/app/lib/request.rb +++ b/app/lib/request.rb @@ -65,6 +65,7 @@ class Request # and 5s timeout on the TLS handshake, meaning the worst case should take # about 15s in total TIMEOUT = { connect_timeout: 5, read_timeout: 10, write_timeout: 10, read_deadline: 30 }.freeze + SAFE_PRESERVED_CHARS = '+,' include RoutingHelper @@ -72,7 +73,7 @@ def initialize(verb, url, **options) raise ArgumentError if url.blank? @verb = verb - @url = Addressable::URI.parse(url).normalize + @url = normalize_preserving_url_encodings(url, SAFE_PRESERVED_CHARS) @http_client = options.delete(:http_client) @allow_local = options.delete(:allow_local) @options = { @@ -148,6 +149,34 @@ def http_client private + # Using code from https://github.com/sporkmonger/addressable/blob/3450895887d0a1770660d8831d1b6fcfed9bd9d6/lib/addressable/uri.rb#L1609-L1635 + # to preserve some URL Encodings while normalizing + def normalize_preserving_url_encodings(url, preserved_chars = SAFE_PRESERVED_CHARS, *flags) + original_uri = Addressable::URI.parse(url) + normalized_uri = original_uri.normalize + + if original_uri.query + modified_query_class = Addressable::URI::CharacterClasses::QUERY.dup + modified_query_class.sub!('\\&', '').sub!('\\;', '') + + pairs = original_uri.query.split('&', -1) + pairs.delete_if(&:empty?).uniq! if flags.include?(:compacted) + pairs.sort! if flags.include?(:sorted) + + normalized_query = pairs.map do |pair| + Addressable::URI.normalize_component( + pair, + modified_query_class, + preserved_chars + ) + end.join('&') + + normalized_uri.query = normalized_query == '' ? nil : normalized_query + end + + normalized_uri + end + def set_common_headers! @headers['User-Agent'] = Mastodon::Version.user_agent @headers['Host'] = @url.host @@ -246,7 +275,7 @@ def require_limit_not_exceeded!(limit) end if ::HTTP::Response.methods.include?(:body_with_limit) && !Rails.env.production? - abort 'HTTP::Response#body_with_limit is already defined, the monkey patch will not be applied' + raise 'HTTP::Response#body_with_limit is already defined, the monkey patch will not be applied' else class ::HTTP::Response include Request::ClientLimit diff --git a/app/lib/signed_request.rb b/app/lib/signed_request.rb index 0ee47ddae125ae..1cea2955f5273b 100644 --- a/app/lib/signed_request.rb +++ b/app/lib/signed_request.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true class SignedRequest - include DomainControlHelper - EXPIRATION_WINDOW_LIMIT = 12.hours CLOCK_SKEW_MARGIN = 1.hour @@ -153,6 +151,7 @@ def initialize(request) 'signature-input' => @request.headers['signature-input'], 'signature' => @request.headers['signature'], }) + @message = Linzer::Message.new(@request.rack_request) end def key_id @@ -174,7 +173,7 @@ def algorithm_supported? def verified?(actor) key = Linzer.new_rsa_v1_5_sha256_public_key(actor.public_key) - Linzer.verify!(@request.rack_request, key:) + Linzer.verify(key, @message, @signature) rescue Linzer::VerifyError false end @@ -187,9 +186,9 @@ def verify_signature_strength! def verify_body_digest! return unless signed_headers.include?('content-digest') - raise Mastodon::SignatureVerificationError, 'Content-Digest header missing' unless @request.headers.key?('content-digest') + raise Mastodon::SignatureVerificationError, 'Content-Digest header missing' if @message.header('content-digest').nil? - digests = Starry.parse_dictionary(@request.headers['content-digest']) + digests = Starry.parse_dictionary(@message.header('content-digest')) raise Mastodon::SignatureVerificationError, "Mastodon only supports SHA-256 in Content-Digest header. Offered algorithms: #{digests.keys.join(', ')}" unless digests.key?('sha-256') received_digest = Base64.strict_encode64(digests['sha-256'].value) @@ -237,7 +236,7 @@ def missing_required_signature_parameters? def initialize(request) @signature = - if Mastodon::Feature.http_message_signatures_enabled? && request.headers['signature-input'].present? + if request.headers['signature-input'].present? HttpMessageSignature.new(request) else HttpSignature.new(request) diff --git a/app/lib/status_cache_hydrator.rb b/app/lib/status_cache_hydrator.rb index 5ba706c4c055b1..b830e509bf360e 100644 --- a/app/lib/status_cache_hydrator.rb +++ b/app/lib/status_cache_hydrator.rb @@ -26,12 +26,7 @@ def hydrate(account_id, nested: false) def hydrate_non_reblog_payload(empty_payload, account_id, nested: false) empty_payload.tap do |payload| - fill_status_payload(payload, @status, account_id, nested:) - - if payload[:poll] - payload[:poll][:voted] = @status.account_id == account_id - payload[:poll][:own_votes] = [] - end + fill_status_payload(payload, @status, account_id, fresh: !nested, nested:) end end @@ -45,26 +40,16 @@ def hydrate_reblog_payload(empty_payload, account_id, nested: false) # used to create the status, we need to hydrate it here too payload[:reblog][:application] = payload_reblog_application if payload[:reblog][:application].nil? && @status.reblog.account_id == account_id - fill_status_payload(payload[:reblog], @status.reblog, account_id, nested:) - - if payload[:reblog][:poll] - if @status.reblog.account_id == account_id - payload[:reblog][:poll][:voted] = true - payload[:reblog][:poll][:own_votes] = [] - else - own_votes = PollVote.where(poll_id: @status.reblog.poll_id, account_id: account_id).pluck(:choice) - payload[:reblog][:poll][:voted] = !own_votes.empty? - payload[:reblog][:poll][:own_votes] = own_votes - end - end + fill_status_payload(payload[:reblog], @status.reblog, account_id, fresh: false, nested:) payload[:filtered] = payload[:reblog][:filtered] payload[:favourited] = payload[:reblog][:favourited] payload[:reblogged] = payload[:reblog][:reblogged] + payload[:quote_approval] = payload[:reblog][:quote_approval] end end - def fill_status_payload(payload, status, account_id, nested: false) + def fill_status_payload(payload, status, account_id, nested: false, fresh: true) payload[:favourited] = Favourite.exists?(account_id: account_id, status_id: status.id) payload[:reblogged] = Status.exists?(account_id: account_id, reblog_of_id: status.id) payload[:muted] = ConversationMute.exists?(account_id: account_id, conversation_id: status.conversation_id) @@ -75,6 +60,21 @@ def fill_status_payload(payload, status, account_id, nested: false) payload[:quote_approval][:current_user] = status.quote_policy_for_account(Account.find_by(id: account_id)) if payload[:quote_approval] payload[:quote] = hydrate_quote_payload(payload[:quote], status.quote, account_id, nested:) if payload[:quote] + if payload[:poll] + if fresh + # If the status is brand new, we don't need to look up votes in database + payload[:poll][:voted] = status.account_id == account_id + payload[:poll][:own_votes] = [] + elsif status.account_id == account_id + payload[:poll][:voted] = true + payload[:poll][:own_votes] = [] + else + own_votes = PollVote.where(poll_id: status.poll_id, account_id: account_id).pluck(:choice) + payload[:poll][:voted] = !own_votes.empty? + payload[:poll][:own_votes] = own_votes + end + end + # Nested statuses are more likely to have a stale cache fill_status_stats(payload, status) if nested end @@ -97,12 +97,12 @@ def hydrate_quote_payload(empty_payload, quote, account_id, nested: false) if quote.quoted_status.nil? payload[nested ? :quoted_status_id : :quoted_status] = nil payload[:state] = 'deleted' - elsif StatusFilter.new(quote.quoted_status, Account.find_by(id: account_id)).filtered_for_quote? - payload[nested ? :quoted_status_id : :quoted_status] = nil - payload[:state] = 'unauthorized' else - payload[:state] = 'accepted' - if nested + filter_state = StatusFilter.new(quote.quoted_status, Account.find_by(id: account_id)).filter_state_for_quote + payload[:state] = filter_state || 'accepted' + if filter_state == 'unauthorized' + payload[nested ? :quoted_status_id : :quoted_status] = nil + elsif nested payload[:quoted_status_id] = quote.quoted_status_id&.to_s else payload[:quoted_status] = StatusCacheHydrator.new(quote.quoted_status).hydrate(account_id, nested: true) diff --git a/app/lib/status_filter.rb b/app/lib/status_filter.rb index dbf7d28b69c1e4..6987fa872e0097 100644 --- a/app/lib/status_filter.rb +++ b/app/lib/status_filter.rb @@ -3,10 +3,9 @@ class StatusFilter attr_reader :status, :account - def initialize(status, account, preloaded_relations = {}) + def initialize(status, account) @status = status @account = account - @preloaded_relations = preloaded_relations end def filtered? @@ -15,10 +14,18 @@ def filtered? blocked_by_policy? || (account_present? && filtered_status?) || silenced_account? end - def filtered_for_quote? - return false if !account.nil? && account.id == status.account_id - - blocked_by_policy? || (account_present? && filtered_status?) + def filter_state_for_quote + if !account.nil? && account.id == status.account_id + nil + elsif blocked_by_policy? + 'unauthorized' + elsif account_present? && blocking_domain? + 'blocked_domain' + elsif account_present? && blocking_account? + 'blocked_account' + elsif account_present? && muting_account? + 'muted_account' + end end private @@ -32,15 +39,15 @@ def filtered_status? end def blocking_account? - @preloaded_relations[:blocking] ? @preloaded_relations[:blocking][status.account_id] : account.blocking?(status.account_id) + account.blocking?(status.account_id) end def blocking_domain? - @preloaded_relations[:domain_blocking_by_domain] ? @preloaded_relations[:domain_blocking_by_domain][status.account_domain] : account.domain_blocking?(status.account_domain) + account.domain_blocking?(status.account_domain) end def muting_account? - @preloaded_relations[:muting] ? @preloaded_relations[:muting][status.account_id] : account.muting?(status.account_id) + account.muting?(status.account_id) end def silenced_account? @@ -52,7 +59,7 @@ def status_account_silenced? end def account_following_status_account? - @preloaded_relations[:following] ? @preloaded_relations[:following][status.account_id] : account&.following?(status.account_id) + account&.following?(status.account_id) end def blocked_by_policy? @@ -60,6 +67,6 @@ def blocked_by_policy? end def policy_allows_show? - StatusPolicy.new(account, status, @preloaded_relations).show? + StatusPolicy.new(account, status).show? end end diff --git a/app/lib/text_formatter.rb b/app/lib/text_formatter.rb index 963cc0d1c43289..10f007ff370dcd 100644 --- a/app/lib/text_formatter.rb +++ b/app/lib/text_formatter.rb @@ -31,7 +31,7 @@ def entities end def to_s - return ''.html_safe if text.blank? + return add_quote_fallback('').html_safe if text.blank? # rubocop:disable Rails/OutputSafety html = rewrite do |entity| if entity[:url] diff --git a/app/lib/vacuum/media_attachments_vacuum.rb b/app/lib/vacuum/media_attachments_vacuum.rb index e5581952905c7e..f9dc62d86b5ac8 100644 --- a/app/lib/vacuum/media_attachments_vacuum.rb +++ b/app/lib/vacuum/media_attachments_vacuum.rb @@ -17,12 +17,16 @@ def perform def vacuum_cached_files! media_attachments_past_retention_period.find_in_batches do |media_attachments| AttachmentBatch.new(MediaAttachment, media_attachments).clear + rescue => e + Rails.logger.error("Skipping batch while removing cached media attachments due to error: #{e}") end end def vacuum_orphaned_records! orphaned_media_attachments.find_in_batches do |media_attachments| AttachmentBatch.new(MediaAttachment, media_attachments).delete + rescue => e + Rails.logger.error("Skipping batch while removing orphaned media attachments due to error: #{e}") end end diff --git a/app/lib/vacuum/preview_cards_vacuum.rb b/app/lib/vacuum/preview_cards_vacuum.rb index 9e34c87c30c386..cc1c9efba9c9b7 100644 --- a/app/lib/vacuum/preview_cards_vacuum.rb +++ b/app/lib/vacuum/preview_cards_vacuum.rb @@ -16,6 +16,8 @@ def perform def vacuum_cached_images! preview_cards_past_retention_period.find_in_batches do |preview_card| AttachmentBatch.new(PreviewCard, preview_card).clear + rescue => e + Rails.logger.error("Skipping batch while removing cached preview cards due to error: #{e}") end end diff --git a/app/models/account.rb b/app/models/account.rb index 3594128f1486fa..6735e98ac08452 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -5,53 +5,55 @@ # Table name: accounts # # id :bigint(8) not null, primary key -# username :string default(""), not null -# domain :string -# private_key :text -# public_key :text default(""), not null -# created_at :datetime not null -# updated_at :datetime not null -# note :text default(""), not null -# display_name :string default(""), not null -# uri :string default(""), not null -# url :string -# avatar_file_name :string +# actor_type :string +# also_known_as :string is an Array +# attribution_domains :string default([]), is an Array # avatar_content_type :string +# avatar_file_name :string # avatar_file_size :integer +# avatar_remote_url :string +# avatar_storage_schema_version :integer # avatar_updated_at :datetime -# header_file_name :string +# discoverable :boolean +# display_name :string default(""), not null +# domain :string +# feature_approval_policy :integer default(0), not null +# featured_collection_url :string +# fields :jsonb +# followers_url :string default(""), not null +# following_url :string default(""), not null # header_content_type :string +# header_file_name :string # header_file_size :integer -# header_updated_at :datetime -# avatar_remote_url :string -# locked :boolean default(FALSE), not null # header_remote_url :string default(""), not null -# last_webfingered_at :datetime +# header_storage_schema_version :integer +# header_updated_at :datetime +# hide_collections :boolean +# id_scheme :integer default("numeric_ap_id") # inbox_url :string default(""), not null +# indexable :boolean default(FALSE), not null +# last_webfingered_at :datetime +# locked :boolean default(FALSE), not null +# memorial :boolean default(FALSE), not null +# note :text default(""), not null # outbox_url :string default(""), not null -# shared_inbox_url :string default(""), not null -# followers_url :string default(""), not null -# following_url :string default(""), not null +# private_key :text # protocol :integer default("ostatus"), not null -# memorial :boolean default(FALSE), not null -# moved_to_account_id :bigint(8) -# featured_collection_url :string -# fields :jsonb -# actor_type :string -# discoverable :boolean -# also_known_as :string is an Array +# public_key :text default(""), not null +# requested_review_at :datetime +# reviewed_at :datetime +# sensitized_at :datetime +# shared_inbox_url :string default(""), not null # silenced_at :datetime # suspended_at :datetime -# hide_collections :boolean -# avatar_storage_schema_version :integer -# header_storage_schema_version :integer # suspension_origin :integer -# sensitized_at :datetime # trendable :boolean -# reviewed_at :datetime -# requested_review_at :datetime -# indexable :boolean default(FALSE), not null -# attribution_domains :string default([]), is an Array +# uri :string default(""), not null +# url :string +# username :string default(""), not null +# created_at :datetime not null +# updated_at :datetime not null +# moved_to_account_id :bigint(8) # class Account < ApplicationRecord @@ -89,6 +91,7 @@ class Account < ApplicationRecord include Account::FaspConcern include Account::FinderConcern include Account::Header + include Account::InteractionPolicyConcern include Account::Interactions include Account::Mappings include Account::Merging @@ -105,6 +108,7 @@ class Account < ApplicationRecord enum :protocol, { ostatus: 0, activitypub: 1 } enum :suspension_origin, { local: 0, remote: 1 }, prefix: true + enum :id_scheme, { username_ap_id: 0, numeric_ap_id: 1 } validates :username, presence: true validates_with UniqueUsernameValidator, if: -> { will_save_change_to_username? } @@ -449,6 +453,10 @@ def ensure_keys! save! end + def featureable? + local? && discoverable? + end + private def prepare_contents diff --git a/app/models/bulk_import.rb b/app/models/bulk_import.rb index e3e46d7b1c6310..8435c245a23fcc 100644 --- a/app/models/bulk_import.rb +++ b/app/models/bulk_import.rb @@ -53,7 +53,7 @@ def self.progress!(bulk_import_id, imported: false) BulkImport.increment_counter(:processed_items, bulk_import_id) BulkImport.increment_counter(:imported_items, bulk_import_id) if imported - # Since the incrementation has been done atomically, concurrent access to `bulk_import` is now bening + # Since the incrementation has been done atomically, concurrent access to `bulk_import` is now benign bulk_import = BulkImport.find(bulk_import_id) bulk_import.update!(state: :finished, finished_at: Time.now.utc) if bulk_import.processed_items == bulk_import.total_items end diff --git a/app/models/collection.rb b/app/models/collection.rb new file mode 100644 index 00000000000000..2e352cbe8761cd --- /dev/null +++ b/app/models/collection.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: collections +# +# id :bigint(8) not null, primary key +# description :text not null +# discoverable :boolean not null +# item_count :integer default(0), not null +# local :boolean not null +# name :string not null +# original_number_of_items :integer +# sensitive :boolean not null +# uri :string +# created_at :datetime not null +# updated_at :datetime not null +# account_id :bigint(8) not null +# tag_id :bigint(8) +# +class Collection < ApplicationRecord + MAX_ITEMS = 25 + + belongs_to :account + belongs_to :tag, optional: true + + has_many :collection_items, dependent: :delete_all + + validates :name, presence: true + validates :description, presence: true + validates :local, inclusion: [true, false] + validates :sensitive, inclusion: [true, false] + validates :discoverable, inclusion: [true, false] + validates :uri, presence: true, if: :remote? + validates :original_number_of_items, + presence: true, + numericality: { greater_than_or_equal: 0 }, + if: :remote? + validate :tag_is_usable + validate :items_do_not_exceed_limit + + scope :with_items, -> { includes(:collection_items).merge(CollectionItem.with_accounts) } + scope :with_tag, -> { includes(:tag) } + + def remote? + !local? + end + + def items_for(account = nil) + result = collection_items.with_accounts + result = result.not_blocked_by(account) unless account.nil? + result + end + + def tag_name + tag&.formatted_name + end + + def tag_name=(new_name) + self.tag = Tag.find_or_create_by_names(new_name).first + end + + private + + def tag_is_usable + return if tag.blank? + + errors.add(:tag_name, :unusable) unless tag.usable? + end + + def items_do_not_exceed_limit + errors.add(:collection_items, :too_many, count: MAX_ITEMS) if collection_items.size > MAX_ITEMS + end +end diff --git a/app/models/collection_item.rb b/app/models/collection_item.rb new file mode 100644 index 00000000000000..5c624165e62cba --- /dev/null +++ b/app/models/collection_item.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: collection_items +# +# id :bigint(8) not null, primary key +# activity_uri :string +# approval_last_verified_at :datetime +# approval_uri :string +# object_uri :string +# position :integer default(1), not null +# state :integer default("pending"), not null +# created_at :datetime not null +# updated_at :datetime not null +# account_id :bigint(8) +# collection_id :bigint(8) not null +# +class CollectionItem < ApplicationRecord + belongs_to :collection, counter_cache: :item_count + belongs_to :account, optional: true + + enum :state, + { pending: 0, accepted: 1, rejected: 2, revoked: 3 }, + validate: true + + delegate :local?, :remote?, to: :collection + + validates :position, numericality: { only_integer: true, greater_than: 0 } + validates :activity_uri, presence: true, if: :local_item_with_remote_account? + validates :approval_uri, absence: true, unless: :local? + validates :account, presence: true, if: :accepted? + validates :object_uri, presence: true, if: -> { account.nil? } + + before_validation :set_position, on: :create + + scope :ordered, -> { order(position: :asc) } + scope :with_accounts, -> { includes(account: [:account_stat, :user]) } + scope :not_blocked_by, ->(account) { where.not(accounts: { id: account.blocking }) } + + def local_item_with_remote_account? + local? && account&.remote? + end + + private + + def set_position + return if position_changed? + + self.position = self.class.where(collection_id:).maximum(:position).to_i + 1 + end +end diff --git a/app/models/concerns/account/associations.rb b/app/models/concerns/account/associations.rb index 62c55da5de1adc..e1684d256070d5 100644 --- a/app/models/concerns/account/associations.rb +++ b/app/models/concerns/account/associations.rb @@ -13,6 +13,8 @@ module Account::Associations has_many :account_warnings has_many :aliases, class_name: 'AccountAlias' has_many :bookmarks + has_many :collections + has_many :collection_items has_many :conversations, class_name: 'AccountConversation' has_many :custom_filters has_many :favourites diff --git a/app/models/concerns/account/interaction_policy_concern.rb b/app/models/concerns/account/interaction_policy_concern.rb new file mode 100644 index 00000000000000..8fe9eda1baf252 --- /dev/null +++ b/app/models/concerns/account/interaction_policy_concern.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +module Account::InteractionPolicyConcern + extend ActiveSupport::Concern + + included do + composed_of :feature_interaction_policy, class_name: 'InteractionPolicy', mapping: { feature_approval_policy: :bitmap } + end + + def feature_policy_as_keys(kind) + raise ArgumentError unless kind.in?(%i(automatic manual)) + return local_feature_policy(kind) if local? + + sub_policy = feature_interaction_policy.send(kind) + sub_policy.as_keys + end + + # Returns `:automatic`, `:manual`, `:unknown`, ':missing` or `:denied` + def feature_policy_for_account(other_account) + return :denied if other_account.nil? || (local? && !discoverable?) + return :automatic if local? + # Post author is always allowed to feature themselves + return :automatic if self == other_account + return :missing if feature_approval_policy.zero? + + automatic_policy = feature_interaction_policy.automatic + following_self = nil + followed_by_self = nil + + return :automatic if automatic_policy.public? + + if automatic_policy.followers? + following_self = followed_by?(other_account) + return :automatic if following_self + end + + if automatic_policy.following? + followed_by_self = following?(other_account) + return :automatic if followed_by_self + end + + # We don't know we are allowed by the automatic policy, considering the manual one + manual_policy = feature_interaction_policy.manual + + return :manual if manual_policy.public? + + if manual_policy.followers? + following_self = followed_by?(other_account) if following_self.nil? + return :manual if following_self + end + + if manual_policy.following? + followed_by_self = following?(other_account) if followed_by_self.nil? + return :manual if followed_by_self + end + + return :unknown if [automatic_policy, manual_policy].any?(&:unsupported_policy?) + + :denied + end + + private + + def local_feature_policy(kind) + return [] if kind == :manual || !discoverable? + + [:public] + end +end diff --git a/app/models/concerns/account/interactions.rb b/app/models/concerns/account/interactions.rb index 4eab55ca3e6b87..c51ccf1229eeb8 100644 --- a/app/models/concerns/account/interactions.rb +++ b/app/models/concerns/account/interactions.rb @@ -123,7 +123,11 @@ def unblock_domain!(other_domain) end def following?(other_account) - active_relationships.exists?(target_account: other_account) + other_id = other_account.is_a?(Account) ? other_account.id : other_account + + preloaded_relation(:following, other_id) do + active_relationships.exists?(target_account: other_account) + end end def following_anyone? @@ -139,15 +143,33 @@ def followed_by?(other_account) end def blocking?(other_account) - block_relationships.exists?(target_account: other_account) + other_id = other_account.is_a?(Account) ? other_account.id : other_account + + preloaded_relation(:blocking, other_id) do + block_relationships.exists?(target_account: other_account) + end + end + + def blocked_by?(other_account) + other_id = other_account.is_a?(Account) ? other_account.id : other_account + + preloaded_relation(:blocked_by, other_id) do + other_account.block_relationships.exists?(target_account: self) + end end def domain_blocking?(other_domain) - domain_blocks.exists?(domain: other_domain) + preloaded_relation(:domain_blocking_by_domain, other_domain) do + domain_blocks.exists?(domain: other_domain) + end end def muting?(other_account) - mute_relationships.exists?(target_account: other_account) + other_id = other_account.is_a?(Account) ? other_account.id : other_account + + preloaded_relation(:muting, other_id) do + mute_relationships.exists?(target_account: other_account) + end end def muting_conversation?(conversation) @@ -215,8 +237,9 @@ def remote_followers_hash(url) def local_followers_hash Rails.cache.fetch("followers_hash:#{id}:local") do digest = "\x00" * 32 - followers.where(domain: nil).pluck_each(:username) do |username| - Xorcist.xor!(digest, Digest::SHA256.digest(ActivityPub::TagManager.instance.uri_for_username(username))) + followers.where(domain: nil).pluck_each(:id_scheme, :id, :username) do |id_scheme, id, username| + uri = id_scheme == 'numeric_ap_id' ? ActivityPub::TagManager.instance.uri_for_account_id(id) : ActivityPub::TagManager.instance.uri_for_username(username) + Xorcist.xor!(digest, Digest::SHA256.digest(uri)) end digest.unpack1('H*') end @@ -225,4 +248,10 @@ def local_followers_hash def normalized_domain(domain) TagManager.instance.normalize_domain(domain) end + + private + + def preloaded_relation(type, key) + @preloaded_relations && @preloaded_relations[type] ? @preloaded_relations[type][key].present? : yield + end end diff --git a/app/models/concerns/account/mappings.rb b/app/models/concerns/account/mappings.rb index c4eddc1fc22129..b8b43cad7c1601 100644 --- a/app/models/concerns/account/mappings.rb +++ b/app/models/concerns/account/mappings.rb @@ -91,6 +91,12 @@ def build_mapping(query, field) end end + def preload_relations!(...) + @preloaded_relations = relations_map(...) + end + + private + def relations_map(account_ids, domains = nil, **options) relations = { blocked_by: Account.blocked_by_map(account_ids, id), diff --git a/app/models/concerns/account/suspensions.rb b/app/models/concerns/account/suspensions.rb index c981fb5a2954cf..4c9ca593ad036d 100644 --- a/app/models/concerns/account/suspensions.rb +++ b/app/models/concerns/account/suspensions.rb @@ -32,6 +32,10 @@ def suspend!(date: Time.now.utc, origin: :local, block_email: true) update!(suspended_at: date, suspension_origin: origin) create_canonical_email_block! if block_email end + + # This terminates all connections for the given account with the streaming + # server: + redis.publish("timeline:system:#{id}", Oj.dump(event: :kill)) if local? end def unsuspend! diff --git a/app/models/concerns/status/fetch_replies_concern.rb b/app/models/concerns/status/fetch_replies_concern.rb index 7ab46481747126..6d65fe41cb831f 100644 --- a/app/models/concerns/status/fetch_replies_concern.rb +++ b/app/models/concerns/status/fetch_replies_concern.rb @@ -4,8 +4,10 @@ module Status::FetchRepliesConcern extend ActiveSupport::Concern # debounce fetching all replies to minimize DoS - FETCH_REPLIES_COOLDOWN_MINUTES = (ENV['FETCH_REPLIES_COOLDOWN_MINUTES'] || 15).to_i.minutes - FETCH_REPLIES_INITIAL_WAIT_MINUTES = (ENV['FETCH_REPLIES_INITIAL_WAIT_MINUTES'] || 5).to_i.minutes + # Period to wait between fetching replies + FETCH_REPLIES_COOLDOWN_MINUTES = 15.minutes + # Period to wait after a post is first created before fetching its replies + FETCH_REPLIES_INITIAL_WAIT_MINUTES = 5.minutes included do scope :created_recently, -> { where(created_at: FETCH_REPLIES_INITIAL_WAIT_MINUTES.ago..) } diff --git a/app/models/concerns/status/interaction_policy_concern.rb b/app/models/concerns/status/interaction_policy_concern.rb index dbac017b33f18b..332feef86b87a5 100644 --- a/app/models/concerns/status/interaction_policy_concern.rb +++ b/app/models/concerns/status/interaction_policy_concern.rb @@ -3,31 +3,22 @@ module Status::InteractionPolicyConcern extend ActiveSupport::Concern - QUOTE_APPROVAL_POLICY_FLAGS = { - unsupported_policy: (1 << 0), - public: (1 << 1), - followers: (1 << 2), - following: (1 << 3), - }.freeze - included do + composed_of :quote_interaction_policy, class_name: 'InteractionPolicy', mapping: { quote_approval_policy: :bitmap } + before_validation :downgrade_quote_policy, if: -> { local? && !distributable? } end def quote_policy_as_keys(kind) - case kind - when :automatic - policy = quote_approval_policy >> 16 - when :manual - policy = quote_approval_policy & 0xFFFF - end + raise ArgumentError unless kind.in?(%i(automatic manual)) - QUOTE_APPROVAL_POLICY_FLAGS.keys.select { |key| policy.anybits?(QUOTE_APPROVAL_POLICY_FLAGS[key]) }.map(&:to_s) + sub_policy = quote_interaction_policy.send(kind) + sub_policy.as_keys end # Returns `:automatic`, `:manual`, `:unknown` or `:denied` - def quote_policy_for_account(other_account, preloaded_relations: {}) - return :denied if other_account.nil? || direct_visibility? + def quote_policy_for_account(other_account) + return :denied if other_account.nil? || direct_visibility? || reblog? following_author = nil followed_by_author = nil @@ -35,35 +26,36 @@ def quote_policy_for_account(other_account, preloaded_relations: {}) # Post author is always allowed to quote themselves return :automatic if account_id == other_account.id - automatic_policy = quote_approval_policy >> 16 - manual_policy = quote_approval_policy & 0xFFFF + automatic_policy = quote_interaction_policy.automatic - return :automatic if automatic_policy.anybits?(QUOTE_APPROVAL_POLICY_FLAGS[:public]) + return :automatic if automatic_policy.public? - if automatic_policy.anybits?(QUOTE_APPROVAL_POLICY_FLAGS[:followers]) - following_author = preloaded_relations[:following] ? preloaded_relations[:following][account_id] : other_account.following?(account) if following_author.nil? + if automatic_policy.followers? + following_author = other_account.following?(account) if following_author.nil? return :automatic if following_author end - if automatic_policy.anybits?(QUOTE_APPROVAL_POLICY_FLAGS[:following]) + if automatic_policy.following? followed_by_author = account.following?(other_account) if followed_by_author.nil? return :automatic if followed_by_author end # We don't know we are allowed by the automatic policy, considering the manual one - return :manual if manual_policy.anybits?(QUOTE_APPROVAL_POLICY_FLAGS[:public]) + manual_policy = quote_interaction_policy.manual + + return :manual if manual_policy.public? - if manual_policy.anybits?(QUOTE_APPROVAL_POLICY_FLAGS[:followers]) - following_author = preloaded_relations[:following] ? preloaded_relations[:following][account_id] : other_account.following?(account) if following_author.nil? + if manual_policy.followers? + following_author = other_account.following?(account) if following_author.nil? return :manual if following_author end - if manual_policy.anybits?(QUOTE_APPROVAL_POLICY_FLAGS[:following]) + if manual_policy.following? followed_by_author = account.following?(other_account) if followed_by_author.nil? return :manual if followed_by_author end - return :unknown if (automatic_policy | manual_policy).anybits?(QUOTE_APPROVAL_POLICY_FLAGS[:unsupported_policy]) + return :unknown if [automatic_policy, manual_policy].any?(&:unsupported_policy?) :denied end diff --git a/app/models/concerns/status/threading_concern.rb b/app/models/concerns/status/threading_concern.rb index 478a139d633be2..3b0a3cd0281938 100644 --- a/app/models/concerns/status/threading_concern.rb +++ b/app/models/concerns/status/threading_concern.rb @@ -8,9 +8,10 @@ def permitted_statuses_from_ids(ids, account, stable: false) statuses = Status.with_accounts(ids).to_a account_ids = statuses.map(&:account_id).uniq domains = statuses.filter_map(&:account_domain).uniq - relations = account&.relations_map(account_ids, domains) || {} - statuses.reject! { |status| StatusFilter.new(status, account, relations).filtered? } + account&.preload_relations!(account_ids, domains) + + statuses.reject! { |status| StatusFilter.new(status, account).filtered? } if stable statuses.sort_by! { |status| ids.index(status.id) } diff --git a/app/models/concerns/user/has_settings.rb b/app/models/concerns/user/has_settings.rb index 4e8d0e64145301..9e99107aa17b35 100644 --- a/app/models/concerns/user/has_settings.rb +++ b/app/models/concerns/user/has_settings.rb @@ -31,6 +31,10 @@ def setting_boost_modal settings['web.reblog_modal'] end + def setting_quick_boosting + settings['web.quick_boosting'] + end + def setting_delete_modal settings['web.delete_modal'] end @@ -47,10 +51,6 @@ def setting_system_font_ui settings['web.use_system_font'] end - def setting_system_emoji_font - settings['web.use_system_emoji_font'] - end - def setting_system_scrollbars_ui settings['web.use_system_scrollbars'] end diff --git a/app/models/conversation.rb b/app/models/conversation.rb index 30d6f13eda7c38..d5d3ddbaac3a29 100644 --- a/app/models/conversation.rb +++ b/app/models/conversation.rb @@ -17,7 +17,7 @@ class Conversation < ApplicationRecord has_many :statuses, dependent: nil - belongs_to :parent_status, class_name: 'Status', optional: true, inverse_of: :conversation + belongs_to :parent_status, class_name: 'Status', optional: true, inverse_of: :owned_conversation belongs_to :parent_account, class_name: 'Account', optional: true scope :local, -> { where(uri: nil) } diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb index 206bb80be4467d..e55cb194ee5138 100644 --- a/app/models/custom_emoji.rb +++ b/app/models/custom_emoji.rb @@ -69,6 +69,10 @@ def object_type :emoji end + def featured? + category&.featured_emoji_id == id + end + def copy! copy = self.class.find_or_initialize_by(domain: nil, shortcode: shortcode) copy.image = image diff --git a/app/models/custom_emoji_category.rb b/app/models/custom_emoji_category.rb index dfcc156080ce4a..cc7db33ece60b9 100644 --- a/app/models/custom_emoji_category.rb +++ b/app/models/custom_emoji_category.rb @@ -4,14 +4,16 @@ # # Table name: custom_emoji_categories # -# id :bigint(8) not null, primary key -# name :string -# created_at :datetime not null -# updated_at :datetime not null +# id :bigint(8) not null, primary key +# name :string +# created_at :datetime not null +# updated_at :datetime not null +# featured_emoji_id :bigint(8) # class CustomEmojiCategory < ApplicationRecord has_many :emojis, class_name: 'CustomEmoji', foreign_key: 'category_id', inverse_of: :category, dependent: nil + belongs_to :featured_emoji, class_name: 'CustomEmoji', optional: true, inverse_of: :category validates :name, presence: true, uniqueness: true diff --git a/app/models/domain_allow.rb b/app/models/domain_allow.rb index 8eab3164e5b2b3..783269bf258831 100644 --- a/app/models/domain_allow.rb +++ b/app/models/domain_allow.rb @@ -33,7 +33,7 @@ def allowed_domains def rule_for(domain) return if domain.blank? - uri = Addressable::URI.new.tap { |u| u.host = domain.delete('/') } + uri = Addressable::URI.new.tap { |u| u.host = domain.strip.delete('/') } find_by(domain: uri.normalized_host) end diff --git a/app/models/export.rb b/app/models/export.rb index 6ed9f60c7c8cf4..b430a2a0d9d18c 100644 --- a/app/models/export.rb +++ b/app/models/export.rb @@ -12,7 +12,7 @@ def initialize(account) def to_bookmarks_csv CSV.generate do |csv| account.bookmarks.includes(:status).reorder(id: :desc).each do |bookmark| - csv << [ActivityPub::TagManager.instance.uri_for(bookmark.status)] + csv << [ActivityPub::TagManager.instance.uri_for(bookmark.status)] if bookmark.status.present? end end end diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb index 6314fe1fd58d9b..af4d4702b2b40e 100644 --- a/app/models/form/admin_settings.rb +++ b/app/models/form/admin_settings.rb @@ -14,7 +14,6 @@ class Form::AdminSettings site_terms registrations_mode closed_registrations_message - timeline_preview bootstrap_timeline_accounts flavour skin @@ -30,7 +29,6 @@ class Form::AdminSettings show_reblogs_in_public_timelines show_replies_in_public_timelines trends - trends_as_landing_page trendable_by_default trending_status_cw show_domain_blocks @@ -48,6 +46,12 @@ class Form::AdminSettings app_icon favicon min_age + local_live_feed_access + remote_live_feed_access + local_topic_feed_access + remote_topic_feed_access + landing_page + wrapstodon ).freeze INTEGER_KEYS = %i( @@ -68,13 +72,13 @@ class Form::AdminSettings show_reblogs_in_public_timelines show_replies_in_public_timelines trends - trends_as_landing_page trendable_by_default trending_status_cw noindex require_invite_text captcha_enabled authorized_fetch + wrapstodon ).freeze UPLOAD_KEYS = %i( @@ -97,20 +101,30 @@ class Form::AdminSettings }.freeze DESCRIPTION_LIMIT = 200 + DOMAIN_BLOCK_AUDIENCES = %w(disabled users all).freeze + REGISTRATION_MODES = %w(open approved none).freeze + FEED_ACCESS_MODES = %w(public authenticated disabled).freeze + ALTERNATE_FEED_ACCESS_MODES = %w(public authenticated).freeze + LANDING_PAGE = %w(trends about local_feed).freeze attr_accessor(*KEYS) - validates :registrations_mode, inclusion: { in: %w(open approved none) }, if: -> { defined?(@registrations_mode) } + validates :registrations_mode, inclusion: { in: REGISTRATION_MODES }, if: -> { defined?(@registrations_mode) } validates :site_contact_email, :site_contact_username, presence: true, if: -> { defined?(@site_contact_username) || defined?(@site_contact_email) } validates :site_contact_username, existing_username: true, if: -> { defined?(@site_contact_username) } validates :bootstrap_timeline_accounts, existing_username: { multiple: true }, if: -> { defined?(@bootstrap_timeline_accounts) } - validates :show_domain_blocks, inclusion: { in: %w(disabled users all) }, if: -> { defined?(@show_domain_blocks) } - validates :show_domain_blocks_rationale, inclusion: { in: %w(disabled users all) }, if: -> { defined?(@show_domain_blocks_rationale) } + validates :show_domain_blocks, inclusion: { in: DOMAIN_BLOCK_AUDIENCES }, if: -> { defined?(@show_domain_blocks) } + validates :show_domain_blocks_rationale, inclusion: { in: DOMAIN_BLOCK_AUDIENCES }, if: -> { defined?(@show_domain_blocks_rationale) } + validates :local_live_feed_access, inclusion: { in: FEED_ACCESS_MODES }, if: -> { defined?(@local_live_feed_access) } + validates :remote_live_feed_access, inclusion: { in: FEED_ACCESS_MODES }, if: -> { defined?(@remote_live_feed_access) } + validates :local_topic_feed_access, inclusion: { in: ALTERNATE_FEED_ACCESS_MODES }, if: -> { defined?(@local_topic_feed_access) } + validates :remote_topic_feed_access, inclusion: { in: FEED_ACCESS_MODES }, if: -> { defined?(@remote_topic_feed_access) } validates :media_cache_retention_period, :content_cache_retention_period, :backups_retention_period, numericality: { only_integer: true }, allow_blank: true, if: -> { defined?(@media_cache_retention_period) || defined?(@content_cache_retention_period) || defined?(@backups_retention_period) } validates :min_age, numericality: { only_integer: true }, allow_blank: true, if: -> { defined?(@min_age) } validates :site_short_description, length: { maximum: DESCRIPTION_LIMIT }, if: -> { defined?(@site_short_description) } validates :status_page_url, url: true, allow_blank: true validate :validate_site_uploads + validates :landing_page, inclusion: { in: LANDING_PAGE }, if: -> { defined?(@landing_page) } KEYS.each do |key| define_method(key) do @@ -165,6 +179,10 @@ def flavour_and_skin=(value) @flavour, @skin = value.split('/', 2) end + def persisted? + true + end + private def cache_digest_value(key) diff --git a/app/models/generated_annual_report.rb b/app/models/generated_annual_report.rb index aba0712fe40f1c..60c7fe40b122a9 100644 --- a/app/models/generated_annual_report.rb +++ b/app/models/generated_annual_report.rb @@ -5,13 +5,14 @@ # Table name: generated_annual_reports # # id :bigint(8) not null, primary key -# account_id :bigint(8) not null -# year :integer not null # data :jsonb not null # schema_version :integer not null +# share_key :string # viewed_at :datetime +# year :integer not null # created_at :datetime not null # updated_at :datetime not null +# account_id :bigint(8) not null # class GeneratedAnnualReport < ApplicationRecord @@ -28,7 +29,12 @@ def view! end def account_ids - data['most_reblogged_accounts'].pluck('account_id') + data['commonly_interacted_with_accounts'].pluck('account_id') + case schema_version + when 1 + data['most_reblogged_accounts'].pluck('account_id') + data['commonly_interacted_with_accounts'].pluck('account_id') + when 2 + [account_id] + end end def status_ids diff --git a/app/models/ip_block.rb b/app/models/ip_block.rb index 4c95ac38dea2ac..b3b678a6a16df6 100644 --- a/app/models/ip_block.rb +++ b/app/models/ip_block.rb @@ -31,9 +31,10 @@ class IpBlock < ApplicationRecord after_commit :reset_cache - def to_log_human_identifier + def to_cidr "#{ip}/#{ip.prefix}" end + alias to_log_human_identifier to_cidr class << self def blocked?(remote_ip) diff --git a/app/models/link_feed.rb b/app/models/link_feed.rb index 29ea430cc0175a..4554796cc5c04c 100644 --- a/app/models/link_feed.rb +++ b/app/models/link_feed.rb @@ -15,18 +15,30 @@ def initialize(preview_card, account, options = {}) # @param [Integer] min_id # @return [Array] def get(limit, max_id = nil, since_id = nil, min_id = nil) + return [] if incompatible_feed_settings? + scope = public_scope scope.merge!(discoverable) scope.merge!(attached_to_preview_card) scope.merge!(account_filters_scope) if account? scope.merge!(language_scope) if account&.chosen_languages.present? + scope.merge!(local_only_scope) if local_only? + scope.merge!(remote_only_scope) if remote_only? scope.to_a_paginated_by_id(limit, max_id: max_id, since_id: since_id, min_id: min_id) end private + def local_feed_setting + Setting.local_topic_feed_access + end + + def remote_feed_setting + Setting.remote_topic_feed_access + end + def attached_to_preview_card Status.joins(:preview_cards_status).where(preview_cards_status: { preview_card_id: @preview_card.id }) end diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index dc75e6f3dd02f8..92485b06eee110 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -4,29 +4,30 @@ # # Table name: media_attachments # -# id :bigint(8) not null, primary key -# status_id :bigint(8) -# file_file_name :string -# file_content_type :string -# file_file_size :integer -# file_updated_at :datetime -# remote_url :string default(""), not null -# created_at :datetime not null -# updated_at :datetime not null -# shortcode :string -# type :integer default("image"), not null -# file_meta :json -# account_id :bigint(8) -# description :text -# scheduled_status_id :bigint(8) -# blurhash :string -# processing :integer -# file_storage_schema_version :integer -# thumbnail_file_name :string -# thumbnail_content_type :string -# thumbnail_file_size :integer -# thumbnail_updated_at :datetime -# thumbnail_remote_url :string +# id :bigint(8) not null, primary key +# blurhash :string +# description :text +# file_content_type :string +# file_file_name :string +# file_file_size :integer +# file_meta :json +# file_storage_schema_version :integer +# file_updated_at :datetime +# processing :integer +# remote_url :string default(""), not null +# shortcode :string +# thumbnail_content_type :string +# thumbnail_file_name :string +# thumbnail_file_size :integer +# thumbnail_remote_url :string +# thumbnail_storage_schema_version :integer +# thumbnail_updated_at :datetime +# type :integer default("image"), not null +# created_at :datetime not null +# updated_at :datetime not null +# account_id :bigint(8) +# scheduled_status_id :bigint(8) +# status_id :bigint(8) # class MediaAttachment < ApplicationRecord diff --git a/app/models/public_feed.rb b/app/models/public_feed.rb index 72e6249c397ca3..a4b2919ef899e8 100644 --- a/app/models/public_feed.rb +++ b/app/models/public_feed.rb @@ -20,6 +20,8 @@ def initialize(account, options = {}) # @param [Integer] min_id # @return [Array] def get(limit, max_id = nil, since_id = nil, min_id = nil) + return [] if incompatible_feed_settings? + scope = public_scope scope.merge!(without_local_only_scope) unless allow_local_only? @@ -42,6 +44,21 @@ def allow_local_only? local_account? && (local_only? || options[:allow_local_only]) end + def incompatible_feed_settings? + (local_only? && !user_has_access_to_feed?(local_feed_setting)) || (remote_only? && !user_has_access_to_feed?(remote_feed_setting)) + end + + def user_has_access_to_feed?(setting) + case setting + when 'public' + true + when 'authenticated' + @account&.user&.functional? + when 'disabled' + @account&.user&.can?(:view_feeds) + end + end + def with_reblogs? options[:with_reblogs] end @@ -50,12 +67,20 @@ def with_replies? options[:with_replies] end + def local_feed_setting + Setting.local_live_feed_access + end + + def remote_feed_setting + Setting.remote_live_feed_access + end + def local_only? - options[:local] && !options[:remote] + (options[:local] && !options[:remote]) || !user_has_access_to_feed?(remote_feed_setting) end def remote_only? - options[:remote] && !options[:local] + (options[:remote] && !options[:local]) || !user_has_access_to_feed?(local_feed_setting) end def account? diff --git a/app/models/quote.rb b/app/models/quote.rb index dcfcd3b353cf6e..e81d427089da8e 100644 --- a/app/models/quote.rb +++ b/app/models/quote.rb @@ -25,7 +25,7 @@ class Quote < ApplicationRecord REFRESH_DEADLINE = 6.hours enum :state, - { pending: 0, accepted: 1, rejected: 2, revoked: 3 }, + { pending: 0, accepted: 1, rejected: 2, revoked: 3, deleted: 4 }, validate: true belongs_to :status @@ -39,6 +39,7 @@ class Quote < ApplicationRecord validates :activity_uri, presence: true, if: -> { account.local? && quoted_account&.remote? } validates :approval_uri, absence: true, if: -> { quoted_account&.local? } validate :validate_visibility + validate :validate_original_quoted_status after_create_commit :increment_counter_caches! after_destroy_commit :decrement_counter_caches! @@ -85,6 +86,10 @@ def validate_visibility errors.add(:quoted_status_id, :visibility_mismatch) end + def validate_original_quoted_status + errors.add(:quoted_status_id, :reblog_unallowed) if quoted_status&.reblog? + end + def set_activity_uri self.activity_uri = [ActivityPub::TagManager.instance.uri_for(account), '/quote_requests/', SecureRandom.uuid].join end diff --git a/app/models/session_activation.rb b/app/models/session_activation.rb index d99ecf8adba43d..55b1428be65b60 100644 --- a/app/models/session_activation.rb +++ b/app/models/session_activation.rb @@ -38,9 +38,7 @@ def active?(id) end def activate(**) - activation = create!(**) - purge_old - activation + create!(**).tap { purge_old } end def deactivate(id) diff --git a/app/models/status.rb b/app/models/status.rb index fb0e96b13432fd..570340c2dfb504 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -161,6 +161,7 @@ class Status < ApplicationRecord around_create Mastodon::Snowflake::Callbacks after_create :set_poll_id + after_create :update_conversation # The `prepend: true` option below ensures this runs before # the `dependent: destroy` callbacks remove relevant records @@ -506,11 +507,16 @@ def set_conversation self.in_reply_to_account_id = carried_over_reply_to_account_id self.conversation_id = thread.conversation_id if conversation_id.nil? elsif conversation_id.nil? - conversation = build_owned_conversation - self.conversation = conversation + build_conversation end end + def update_conversation + return if reply? + + conversation.update!(parent_status: self, parent_account: account) if conversation && conversation.parent_status.nil? + end + def carried_over_reply_to_account_id if thread.account_id == account_id && thread.reply? thread.in_reply_to_account_id diff --git a/app/models/status_edit.rb b/app/models/status_edit.rb index 06358147e3e216..e48abb8866481c 100644 --- a/app/models/status_edit.rb +++ b/app/models/status_edit.rb @@ -53,6 +53,10 @@ def quote underlying_quote end + def with_preview_card? + false + end + def with_media? ordered_media_attachments.any? end diff --git a/app/models/tag.rb b/app/models/tag.rb index dff10111123a7c..9924d132e64b25 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -41,7 +41,7 @@ class Tag < ApplicationRecord HASHTAG_LAST_SEQUENCE = '([[:word:]_]*[[:alpha:]][[:word:]_]*)' HASHTAG_NAME_PAT = "#{HASHTAG_FIRST_SEQUENCE}|#{HASHTAG_LAST_SEQUENCE}".freeze - HASHTAG_RE = %r{(?(term) { where(arel_table[:name].lower.matches(arel_table.lower("#{sanitize_sql_like(Tag.normalize(term))}%"), nil, true)) } # Search with case-sensitive to use B-tree index + normalizes :display_name, with: ->(value) { value.gsub(HASHTAG_INVALID_CHARS_RE, '') } + update_index('tags', :self) def to_param @@ -112,8 +114,11 @@ def find_or_create_by_names(name_or_names) names = Array(name_or_names).map { |str| [normalize(str), str] }.uniq(&:first) names.map do |(normalized_name, display_name)| - tag = matching_name(normalized_name).first || create(name: normalized_name, - display_name: display_name.gsub(HASHTAG_INVALID_CHARS_RE, '')) + tag = begin + matching_name(normalized_name).first || create!(name: normalized_name, display_name:) + rescue ActiveRecord::RecordNotUnique + find_normalized(normalized_name) + end yield tag if block_given? diff --git a/app/models/tag_feed.rb b/app/models/tag_feed.rb index a4d371e4c162dc..f9ed437994a3d7 100644 --- a/app/models/tag_feed.rb +++ b/app/models/tag_feed.rb @@ -23,6 +23,8 @@ def initialize(tag, account, options = {}) # @param [Integer] min_id # @return [Array] def get(limit, max_id = nil, since_id = nil, min_id = nil) + return [] if incompatible_feed_settings? + scope = public_scope scope.merge!(without_local_only_scope) unless local_account? @@ -39,6 +41,14 @@ def get(limit, max_id = nil, since_id = nil, min_id = nil) private + def local_feed_setting + Setting.local_topic_feed_access + end + + def remote_feed_setting + Setting.remote_topic_feed_access + end + def tagged_with_any_scope Status.group(:id).tagged_with(tags_for(Array(@tag.name) | Array(options[:any]))) end diff --git a/app/models/user.rb b/app/models/user.rb index a6496b6ac16be3..1db8b40697b71c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -131,11 +131,12 @@ class User < ApplicationRecord delegate :can?, to: :role - attr_reader :invite_code, :date_of_birth + attr_reader :invite_code attr_writer :current_account attribute :external, :boolean, default: false attribute :bypass_registration_checks, :boolean, default: false + attribute :date_of_birth, :date def self.those_who_can(*any_of_privileges) matching_role_ids = UserRole.that_can(*any_of_privileges).map(&:id) @@ -151,17 +152,6 @@ def self.skip_mx_check? Rails.env.local? end - def date_of_birth=(hash_or_string) - @date_of_birth = begin - if hash_or_string.is_a?(Hash) - day, month, year = hash_or_string.values_at(1, 2, 3) - "#{day}.#{month}.#{year}" - else - hash_or_string - end - end - end - def role if role_id.nil? UserRole.everyone @@ -180,6 +170,10 @@ def valid_invitation? def disable! update!(disabled: true) + + # This terminates all connections for the given account with the streaming + # server: + redis.publish("timeline:system:#{account.id}", Oj.dump(event: :kill)) end def enable! @@ -357,17 +351,22 @@ def revoke_access! end def reset_password! + # First, change password to something random, this revokes sessions and on-going access: + change_password!(SecureRandom.hex) + + # Finally, send a reset password prompt to the user + send_reset_password_instructions + end + + def change_password!(new_password) # First, change password to something random and deactivate all sessions transaction do - update(password: SecureRandom.hex) + update(password: new_password) session_activations.destroy_all end # Then, remove all authorized applications and connected push subscriptions revoke_access! - - # Finally, send a reset password prompt to the user - send_reset_password_instructions end protected diff --git a/app/models/user_role.rb b/app/models/user_role.rb index d567bf5eca53f6..31c8ff20a65286 100644 --- a/app/models/user_role.rb +++ b/app/models/user_role.rb @@ -36,6 +36,7 @@ class UserRole < ApplicationRecord manage_roles: (1 << 17), manage_user_access: (1 << 18), delete_user_data: (1 << 19), + view_feeds: (1 << 20), }.freeze EVERYONE_ROLE_ID = -99 @@ -67,6 +68,7 @@ module Flags manage_blocks manage_taxonomies manage_invites + view_feeds ).freeze, administration: %i( diff --git a/app/models/user_settings.rb b/app/models/user_settings.rb index a25a2fac02c06d..856fc43a744d4f 100644 --- a/app/models/user_settings.rb +++ b/app/models/user_settings.rb @@ -35,12 +35,12 @@ class KeyError < Error; end setting :delete_modal, default: true setting :reblog_modal, default: false setting :favourite_modal, default: false + setting :quick_boosting, default: false setting :missing_alt_text_modal, default: true setting :reduce_motion, default: false setting :expand_content_warnings, default: false setting :display_media, default: 'default', in: %w(default show_all hide_all) setting :auto_play, default: false - setting :use_system_emoji_font, default: false setting :emoji_style, default: 'auto', in: %w(auto native twemoji) end diff --git a/app/policies/account_policy.rb b/app/policies/account_policy.rb index a744af81ded594..ab3b41d6280847 100644 --- a/app/policies/account_policy.rb +++ b/app/policies/account_policy.rb @@ -64,4 +64,8 @@ def unblock_email? def review? role.can?(:manage_taxonomies) end + + def feature? + record.featureable? && !current_account.blocking?(record) && !current_account.blocked_by?(record) + end end diff --git a/app/policies/admin/status_policy.rb b/app/policies/admin/status_policy.rb index c4ba5c26069585..b0b42ce5d816de 100644 --- a/app/policies/admin/status_policy.rb +++ b/app/policies/admin/status_policy.rb @@ -1,12 +1,6 @@ # frozen_string_literal: true class Admin::StatusPolicy < ApplicationPolicy - def initialize(current_account, record, preloaded_relations = {}) - super(current_account, record) - - @preloaded_relations = preloaded_relations - end - def index? role.can?(:manage_reports, :manage_users) end @@ -34,6 +28,6 @@ def eligible_to_show? end def viewable_through_normal_policy? - StatusPolicy.new(current_account, record, @preloaded_relations).show? + StatusPolicy.new(current_account, record).show? end end diff --git a/app/policies/collection_policy.rb b/app/policies/collection_policy.rb new file mode 100644 index 00000000000000..12adfbcad1aca6 --- /dev/null +++ b/app/policies/collection_policy.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +class CollectionPolicy < ApplicationPolicy + def index? + true + end + + def show? + true + end + + def create? + user_signed_in? + end + + def update? + owner? + end + + def destroy? + owner? + end + + private + + def owner? + current_account == record.account + end +end diff --git a/app/policies/status_policy.rb b/app/policies/status_policy.rb index c8146c7e7c5a8f..e5353363f83938 100644 --- a/app/policies/status_policy.rb +++ b/app/policies/status_policy.rb @@ -1,12 +1,6 @@ # frozen_string_literal: true class StatusPolicy < ApplicationPolicy - def initialize(current_account, record, preloaded_relations = {}) - super(current_account, record) - - @preloaded_relations = preloaded_relations - end - def show? return false if author.unavailable? return false if local_only? && (current_account.nil? || !current_account.local?) @@ -22,7 +16,7 @@ def show? # This is about requesting a quote post, not validating it def quote? - show? && record.quote_policy_for_account(current_account, preloaded_relations: @preloaded_relations) != :denied + show? && record.quote_policy_for_account(current_account) != :denied end def reblog? @@ -37,10 +31,6 @@ def destroy? owned? end - def list_quotes? - owned? - end - alias unreblog? destroy? def update? @@ -80,19 +70,19 @@ def author_blocking_domain? def blocking_author? return false if current_account.nil? - @preloaded_relations[:blocking] ? @preloaded_relations[:blocking][author.id] : current_account.blocking?(author) + current_account.blocking?(author) end def author_blocking? return false if current_account.nil? - @preloaded_relations[:blocked_by] ? @preloaded_relations[:blocked_by][author.id] : author.blocking?(current_account) + current_account.blocked_by?(author) end def following_author? return false if current_account.nil? - @preloaded_relations[:following] ? @preloaded_relations[:following][author.id] : current_account.following?(author) + current_account.following?(author) end def author diff --git a/app/presenters/status_relationships_presenter.rb b/app/presenters/status_relationships_presenter.rb index 0807f1c95e8cf1..060a0a8ed6d6ed 100644 --- a/app/presenters/status_relationships_presenter.rb +++ b/app/presenters/status_relationships_presenter.rb @@ -41,14 +41,8 @@ def initialize(statuses, current_account_id = nil, **options) end # This one is currently on-demand as it is only used for quote posts - def preloaded_account_relations - @preloaded_account_relations ||= begin - accounts = @statuses.compact.flat_map { |s| [s.account, s.proper.account, s.proper.quote&.quoted_account] }.uniq.compact - - account_ids = accounts.pluck(:id) - account_domains = accounts.pluck(:domain).uniq - Account.find(@current_account_id).relations_map(account_ids, account_domains) - end + def authoring_accounts + @authoring_accounts ||= @statuses.compact.flat_map { |s| [s.account, s.proper.account, s.proper.quote&.quoted_account] }.uniq.compact end private diff --git a/app/serializers/activitypub/actor_serializer.rb b/app/serializers/activitypub/actor_serializer.rb index ed90fa428f9668..c19d42bfb430cb 100644 --- a/app/serializers/activitypub/actor_serializer.rb +++ b/app/serializers/activitypub/actor_serializer.rb @@ -10,12 +10,16 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer :moved_to, :property_value, :discoverable, :suspended, :memorial, :indexable, :attribution_domains + context_extensions :interaction_policies if Mastodon::Feature.collections_enabled? + attributes :id, :type, :following, :followers, :inbox, :outbox, :featured, :featured_tags, :preferred_username, :name, :summary, :url, :manually_approves_followers, :discoverable, :indexable, :published, :memorial + attribute :interaction_policy, if: -> { Mastodon::Feature.collections_enabled? } + has_one :public_key, serializer: ActivityPub::PublicKeySerializer has_many :virtual_tags, key: :tag @@ -163,6 +167,16 @@ def published object.created_at.midnight.iso8601 end + def interaction_policy + uri = object.discoverable? ? ActivityPub::TagManager::COLLECTIONS[:public] : ActivityPub::TagManager.instance.uri_for(object) + + { + canFeature: { + automaticApproval: [uri], + }, + } + end + class CustomEmojiSerializer < ActivityPub::EmojiSerializer end diff --git a/app/serializers/activitypub/note_serializer.rb b/app/serializers/activitypub/note_serializer.rb index f99d15a6d93b70..76467392ecc913 100644 --- a/app/serializers/activitypub/note_serializer.rb +++ b/app/serializers/activitypub/note_serializer.rb @@ -33,9 +33,9 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer attribute :voters_count, if: :poll_and_voters_count? - attribute :quote, if: :quote? - attribute :quote, key: :_misskey_quote, if: :quote? - attribute :quote, key: :quote_uri, if: :quote? + attribute :quote, if: :nonlegacy_quote? + attribute :quote, key: :_misskey_quote, if: :serializable_quote? + attribute :quote, key: :quote_uri, if: :serializable_quote? attribute :quote_authorization, if: :quote_authorization? attribute :interaction_policy @@ -226,13 +226,21 @@ def quote? object.quote&.present? end + def serializable_quote? + object.quote&.quoted_status&.present? + end + + def nonlegacy_quote? + object.quote.present? && !object.quote.legacy? + end + def quote_authorization? object.quote.present? && ActivityPub::TagManager.instance.approval_uri_for(object.quote).present? end def quote # TODO: handle inlining self-quotes - ActivityPub::TagManager.instance.uri_for(object.quote.quoted_status) + object.quote.quoted_status.present? ? ActivityPub::TagManager.instance.uri_for(object.quote.quoted_status) : { type: 'Tombstone' } end def quote_authorization @@ -243,10 +251,10 @@ def interaction_policy approved_uris = [] # On outgoing posts, only automatic approval is supported - policy = object.quote_approval_policy >> 16 - approved_uris << ActivityPub::TagManager::COLLECTIONS[:public] if policy.anybits?(Status::QUOTE_APPROVAL_POLICY_FLAGS[:public]) - approved_uris << ActivityPub::TagManager.instance.followers_uri_for(object.account) if policy.anybits?(Status::QUOTE_APPROVAL_POLICY_FLAGS[:followers]) - approved_uris << ActivityPub::TagManager.instance.following_uri_for(object.account) if policy.anybits?(Status::QUOTE_APPROVAL_POLICY_FLAGS[:following]) + policy = object.quote_interaction_policy.automatic + approved_uris << ActivityPub::TagManager::COLLECTIONS[:public] if policy.public? + approved_uris << ActivityPub::TagManager.instance.followers_uri_for(object.account) if policy.followers? + approved_uris << ActivityPub::TagManager.instance.following_uri_for(object.account) if policy.following? approved_uris << ActivityPub::TagManager.instance.uri_for(object.account) if approved_uris.empty? { diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb index b5639cd66370b4..f338482ace7f8b 100644 --- a/app/serializers/initial_state_serializer.rb +++ b/app/serializers/initial_state_serializer.rb @@ -33,6 +33,7 @@ def meta store[:me] = object.current_account.id.to_s store[:boost_modal] = object_account_user.setting_boost_modal store[:favourite_modal] = object_account_user.setting_favourite_modal + store[:quick_boosting] = object_account_user.setting_quick_boosting store[:delete_modal] = object_account_user.setting_delete_modal store[:missing_alt_text_modal] = object_account_user.settings['web.missing_alt_text_modal'] store[:auto_play_gif] = object_account_user.setting_auto_play_gif @@ -45,9 +46,9 @@ def meta store[:use_blurhash] = object_account_user.setting_use_blurhash store[:use_pending_items] = object_account_user.setting_use_pending_items store[:default_content_type] = object_account_user.setting_default_content_type - store[:system_emoji_font] = object_account_user.setting_system_emoji_font store[:show_trends] = Setting.trends && object_account_user.setting_trends - store[:emoji_style] = object_account_user.settings['web.emoji_style'] if Mastodon::Feature.modern_emojis_enabled? + store[:emoji_style] = object_account_user.settings['web.emoji_style'] + store[:wrapstodon] = wrapstodon else store[:auto_play_gif] = Setting.auto_play_gif store[:display_media] = Setting.display_media @@ -110,6 +111,16 @@ def features private + def wrapstodon + current_campaign = AnnualReport.current_campaign + return if current_campaign.blank? + + { + year: current_campaign, + state: AnnualReport.new(object.current_account, current_campaign).state, + } + end + def default_meta_store { access_token: object.token, @@ -128,12 +139,15 @@ def default_meta_store sso_redirect: sso_redirect, status_page_url: Setting.status_page_url, streaming_api_base_url: Rails.configuration.x.streaming_api_base_url, - timeline_preview: Setting.timeline_preview, title: instance_presenter.title, - trends_as_landing_page: Setting.trends_as_landing_page, + landing_page: Setting.landing_page, trends_enabled: Setting.trends, version: instance_presenter.version, terms_of_service_enabled: TermsOfService.current.present?, + local_live_feed_access: Setting.local_live_feed_access, + remote_live_feed_access: Setting.remote_live_feed_access, + local_topic_feed_access: Setting.local_topic_feed_access, + remote_topic_feed_access: Setting.remote_topic_feed_access, } end diff --git a/app/serializers/rest/account_serializer.rb b/app/serializers/rest/account_serializer.rb index dad1527d457823..9a6b3fd2ce7fc6 100644 --- a/app/serializers/rest/account_serializer.rb +++ b/app/serializers/rest/account_serializer.rb @@ -20,6 +20,8 @@ class REST::AccountSerializer < ActiveModel::Serializer attribute :memorial, if: :memorial? + attribute :feature_approval, if: -> { Mastodon::Feature.collections_enabled? } + class AccountDecorator < SimpleDelegator def self.model_name Account.model_name @@ -161,4 +163,12 @@ def noindex def moved_and_not_nested? object.moved? end + + def feature_approval + { + automatic: object.feature_policy_as_keys(:automatic), + manual: object.feature_policy_as_keys(:manual), + current_user: object.feature_policy_for_account(current_user&.account), + } + end end diff --git a/app/serializers/rest/admin/ip_block_serializer.rb b/app/serializers/rest/admin/ip_block_serializer.rb index 6a38f8b566f95e..4b782da53a7c88 100644 --- a/app/serializers/rest/admin/ip_block_serializer.rb +++ b/app/serializers/rest/admin/ip_block_serializer.rb @@ -9,6 +9,6 @@ def id end def ip - "#{object.ip}/#{object.ip.prefix}" + object.to_cidr end end diff --git a/app/serializers/rest/annual_report_serializer.rb b/app/serializers/rest/annual_report_serializer.rb index 1fb5ddb5c123c3..85a9c04540fcf0 100644 --- a/app/serializers/rest/annual_report_serializer.rb +++ b/app/serializers/rest/annual_report_serializer.rb @@ -1,5 +1,15 @@ # frozen_string_literal: true class REST::AnnualReportSerializer < ActiveModel::Serializer - attributes :year, :data, :schema_version + include RoutingHelper + + attributes :year, :data, :schema_version, :share_url, :account_id + + def share_url + public_wrapstodon_url(object.account, object.year, object.share_key) if object.share_key.present? + end + + def account_id + object.account_id.to_s + end end diff --git a/app/serializers/rest/base_collection_serializer.rb b/app/serializers/rest/base_collection_serializer.rb new file mode 100644 index 00000000000000..be26aac6fe21a2 --- /dev/null +++ b/app/serializers/rest/base_collection_serializer.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class REST::BaseCollectionSerializer < ActiveModel::Serializer + attributes :id, :uri, :name, :description, :local, :sensitive, + :discoverable, :item_count, :created_at, :updated_at + + belongs_to :tag, serializer: REST::StatusSerializer::TagSerializer + + def id + object.id.to_s + end +end diff --git a/app/serializers/rest/base_quote_serializer.rb b/app/serializers/rest/base_quote_serializer.rb index be9d5cbe6f238e..407bc961f6f1d5 100644 --- a/app/serializers/rest/base_quote_serializer.rb +++ b/app/serializers/rest/base_quote_serializer.rb @@ -8,18 +8,24 @@ def state # Extra states when a status is unavailable return 'deleted' if object.quoted_status.nil? - return 'unauthorized' if status_filter.filtered_for_quote? - object.state + status_filter.filter_state_for_quote || object.state end def quoted_status - object.quoted_status if object.accepted? && object.quoted_status.present? && !status_filter.filtered_for_quote? + object.quoted_status if object.accepted? && object.quoted_status.present? && !object.quoted_status&.reblog? && status_filter.filter_state_for_quote != 'unauthorized' end private def status_filter - @status_filter ||= StatusFilter.new(object.quoted_status, current_user&.account, instance_options[:relationships]&.preloaded_account_relations || {}) + @status_filter ||= begin + if current_user && instance_options[:relationships] + account_ids = instance_options[:relationships].authoring_accounts.pluck(:id) + domains = instance_options[:relationships].authoring_accounts.pluck(:domain).uniq + current_user.account.preload_relations!(account_ids, domains) + end + StatusFilter.new(object.quoted_status, current_user&.account) + end end end diff --git a/app/serializers/rest/collection_item_serializer.rb b/app/serializers/rest/collection_item_serializer.rb new file mode 100644 index 00000000000000..d35a8fdef28622 --- /dev/null +++ b/app/serializers/rest/collection_item_serializer.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class REST::CollectionItemSerializer < ActiveModel::Serializer + delegate :accepted?, to: :object + + attributes :id, :position, :state + + belongs_to :account, serializer: REST::AccountSerializer, if: :accepted? + + def id + object.id.to_s + end +end diff --git a/app/serializers/rest/collection_serializer.rb b/app/serializers/rest/collection_serializer.rb new file mode 100644 index 00000000000000..ce2d8933c4ecd3 --- /dev/null +++ b/app/serializers/rest/collection_serializer.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class REST::CollectionSerializer < REST::BaseCollectionSerializer + belongs_to :account, serializer: REST::AccountSerializer + + has_many :items, serializer: REST::CollectionItemSerializer + + def items + object.items_for(current_user&.account) + end +end diff --git a/app/serializers/rest/custom_emoji_serializer.rb b/app/serializers/rest/custom_emoji_serializer.rb index 33da69da530bd7..a173aeae19af59 100644 --- a/app/serializers/rest/custom_emoji_serializer.rb +++ b/app/serializers/rest/custom_emoji_serializer.rb @@ -8,6 +8,7 @@ class REST::CustomEmojiSerializer < ActiveModel::Serializer attributes :shortcode, :url, :static_url, :visible_in_picker attribute :category, if: :category_loaded? + attribute :featured, if: :category_loaded? def url full_asset_url(object.image.url) @@ -21,6 +22,10 @@ def category object.category.name end + def featured + object.featured? + end + def category_loaded? object.association(:category).loaded? && object.category.present? end diff --git a/app/serializers/rest/instance_serializer.rb b/app/serializers/rest/instance_serializer.rb index 40d1941b6f09e9..1253f5a6aadd6f 100644 --- a/app/serializers/rest/instance_serializer.rb +++ b/app/serializers/rest/instance_serializer.rb @@ -12,7 +12,7 @@ class ContactSerializer < ActiveModel::Serializer attributes :domain, :title, :version, :source_url, :description, :usage, :thumbnail, :icon, :languages, :configuration, - :registrations, :api_versions + :registrations, :api_versions, :wrapstodon has_one :contact, serializer: ContactSerializer has_many :rules, serializer: REST::RuleSerializer @@ -101,6 +101,21 @@ def configuration enabled: TranslationService.configured?, }, + timelines_access: { + live_feeds: { + local: Setting.local_live_feed_access, + remote: Setting.remote_live_feed_access, + }, + hashtag_feeds: { + local: Setting.local_topic_feed_access, + remote: Setting.remote_topic_feed_access, + }, + trending_link_feeds: { + local: Setting.local_topic_feed_access, + remote: Setting.remote_topic_feed_access, + }, + }, + limited_federation: limited_federation?, } end @@ -120,6 +135,10 @@ def api_versions Mastodon::Version.api_versions end + def wrapstodon + AnnualReport.current_campaign + end + private def registrations_enabled? diff --git a/app/serializers/rest/scheduled_status_serializer.rb b/app/serializers/rest/scheduled_status_serializer.rb index 7c54f39c0d137d..cc66c7cc95f218 100644 --- a/app/serializers/rest/scheduled_status_serializer.rb +++ b/app/serializers/rest/scheduled_status_serializer.rb @@ -8,4 +8,11 @@ class REST::ScheduledStatusSerializer < ActiveModel::Serializer def id object.id.to_s end + + def params + object.params.merge( + quoted_status_id: object.params['quoted_status_id']&.to_s, + quote_approval_policy: InteractionPolicy::POLICY_FLAGS.keys.find { |key| object.params['quote_approval_policy']&.anybits?(InteractionPolicy::POLICY_FLAGS[key] << 16) }&.to_s || 'nobody' + ) + end end diff --git a/app/serializers/rest/status_serializer.rb b/app/serializers/rest/status_serializer.rb index da4eb4b125c1b9..505c7f8f23dc43 100644 --- a/app/serializers/rest/status_serializer.rb +++ b/app/serializers/rest/status_serializer.rb @@ -172,9 +172,9 @@ def ordered_mentions def quote_approval { - automatic: object.quote_policy_as_keys(:automatic), - manual: object.quote_policy_as_keys(:manual), - current_user: object.quote_policy_for_account(current_user&.account), + automatic: object.proper.quote_policy_as_keys(:automatic), + manual: object.proper.quote_policy_as_keys(:manual), + current_user: object.proper.quote_policy_for_account(current_user&.account), } end diff --git a/app/services/account_search_service.rb b/app/services/account_search_service.rb index 6f70d530b2f92a..6c24c5da8d46da 100644 --- a/app/services/account_search_service.rb +++ b/app/services/account_search_service.rb @@ -6,7 +6,7 @@ class AccountSearchService < BaseService MENTION_ONLY_RE = /\A#{Account::MENTION_RE}\z/i # Min. number of characters to look for non-exact matches - MIN_QUERY_LENGTH = 5 + MIN_QUERY_LENGTH = 3 class QueryBuilder def initialize(query, account, options = {}) @@ -256,7 +256,7 @@ def from_elasticsearch ActiveRecord::Associations::Preloader.new(records: records, associations: [:account_stat, { user: :role }]).call records - rescue Faraday::ConnectionFailed, Parslet::ParseFailed + rescue Faraday::ConnectionFailed, Parslet::ParseFailed, Errno::ENETUNREACH nil end diff --git a/app/services/activitypub/fetch_all_replies_service.rb b/app/services/activitypub/fetch_all_replies_service.rb index b771b8452656a6..a956e7c706f2b9 100644 --- a/app/services/activitypub/fetch_all_replies_service.rb +++ b/app/services/activitypub/fetch_all_replies_service.rb @@ -3,8 +3,8 @@ class ActivityPub::FetchAllRepliesService < ActivityPub::FetchRepliesService include JsonLdHelper - # Limit of replies to fetch per status - MAX_REPLIES = (ENV['FETCH_REPLIES_MAX_SINGLE'] || 500).to_i + # Max number of replies to fetch - for a single post + MAX_REPLIES = 500 def call(status_uri, collection_or_uri, max_pages: 1, batch_id: nil, request_id: nil) @status_uri = status_uri diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb index eb67daf7e8f24a..f133fbc84aee05 100644 --- a/app/services/activitypub/process_account_service.rb +++ b/app/services/activitypub/process_account_service.rb @@ -107,6 +107,7 @@ def set_immediate_protocol_attributes! @account.uri = @uri @account.actor_type = actor_type @account.created_at = @json['published'] if @json['published'].present? + @account.feature_approval_policy = feature_approval_policy if Mastodon::Feature.collections_enabled? end def valid_collection_uri(uri) @@ -360,4 +361,8 @@ def process_emoji(tag) emoji.image_remote_url = image_url emoji.save end + + def feature_approval_policy + ActivityPub::Parser::InteractionPolicyParser.new(@json.dig('interactionPolicy', 'canFeature'), @account).bitmap + end end diff --git a/app/services/activitypub/process_status_update_service.rb b/app/services/activitypub/process_status_update_service.rb index 66c8da2b604df9..1cdf0b48302546 100644 --- a/app/services/activitypub/process_status_update_service.rb +++ b/app/services/activitypub/process_status_update_service.rb @@ -20,7 +20,6 @@ def call(status, activity_json, object_json, request_id: nil) @request_id = request_id @quote = nil - # Only native types can be updated at the moment return @status if !expected_type? || already_updated_more_recently? if @status_parser.edited_at.present? && (@status.edited_at.nil? || @status_parser.edited_at > @status.edited_at) @@ -74,6 +73,8 @@ def handle_implicit_update! update_quote_approval! update_counts! end + + broadcast_updates! if @status.quote&.state_previously_changed? end def update_interaction_policies! @@ -168,8 +169,8 @@ def update_poll!(allow_significant_changes: true) end def update_immediate_attributes! - @status.text = @status_parser.text || '' - @status.spoiler_text = @status_parser.spoiler_text || '' + @status.text = @status_parser.processed_text + @status.spoiler_text = @status_parser.processed_spoiler_text @status.sensitive = @account.sensitized? || @status_parser.sensitive || false @status.language = @status_parser.language @@ -298,7 +299,7 @@ def update_quote_approval! def update_quote! quote_uri = @status_parser.quote_uri - if quote_uri.present? + if @status_parser.quote? approval_uri = @status_parser.quote_approval_uri approval_uri = nil if unsupported_uri_scheme?(approval_uri) || TagManager.instance.local_url?(approval_uri) @@ -308,7 +309,7 @@ def update_quote! # Revoke the quote while we get a chance… maybe this should be a `before_destroy` hook? RevokeQuoteService.new.call(@status.quote) if @status.quote.quoted_account&.local? && @status.quote.accepted? @status.quote.destroy - quote = Quote.create(status: @status, approval_uri: approval_uri, legacy: @status_parser.legacy_quote?) + quote = Quote.create(status: @status, approval_uri: approval_uri, legacy: @status_parser.legacy_quote?, state: @status_parser.deleted_quote? ? :deleted : :pending) @quote_changed = true else quote = @status.quote @@ -349,7 +350,7 @@ def update_counts! end def expected_type? - equals_or_includes_any?(@json['type'], %w(Note Question)) + equals_or_includes_any?(@json['type'], ActivityPub::Activity::SUPPORTED_TYPES) || equals_or_includes_any?(@json['type'], ActivityPub::Activity::CONVERTED_TYPES) end def record_previous_edit! diff --git a/app/services/activitypub/verify_quote_service.rb b/app/services/activitypub/verify_quote_service.rb index 2badadd425f3e5..4dcf11cdfdae50 100644 --- a/app/services/activitypub/verify_quote_service.rb +++ b/app/services/activitypub/verify_quote_service.rb @@ -73,7 +73,7 @@ def fetch_quoted_post_if_needed!(uri, prefetched_body: nil) status ||= ActivityPub::FetchRemoteStatusService.new.call(uri, on_behalf_of: @quote.account.followers.local.first, prefetched_body:, request_id: @request_id, depth: @depth + 1) - @quote.update(quoted_status: status) if status.present? + @quote.update(quoted_status: status) if status.present? && !status.reblog? rescue Mastodon::RecursionLimitExceededError, Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS => e @fetching_error = e end @@ -91,7 +91,7 @@ def import_quoted_post_if_needed!(uri) status = ActivityPub::FetchRemoteStatusService.new.call(object['id'], prefetched_body: object, on_behalf_of: @quote.account.followers.local.first, request_id: @request_id, depth: @depth) - if status.present? + if status.present? && !status.reblog? @quote.update(quoted_status: status) true else diff --git a/app/services/add_account_to_collection_service.rb b/app/services/add_account_to_collection_service.rb new file mode 100644 index 00000000000000..4477384dd087c7 --- /dev/null +++ b/app/services/add_account_to_collection_service.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class AddAccountToCollectionService + def call(collection, account) + raise ArgumentError unless collection.local? + + @collection = collection + @account = account + + raise Mastodon::NotPermittedError, I18n.t('accounts.errors.cannot_be_added_to_collections') unless AccountPolicy.new(@collection.account, @account).feature? + + create_collection_item + end + + private + + def create_collection_item + @collection.collection_items.create!( + account: @account, + state: :accepted + ) + end +end diff --git a/app/services/backup_service.rb b/app/services/backup_service.rb index 36baa6e5acf8d9..91f6ea4b600ac6 100644 --- a/app/services/backup_service.rb +++ b/app/services/backup_service.rb @@ -63,7 +63,7 @@ def build_archive! dump_actor!(zipfile) end - archive_filename = "#{['archive', Time.now.utc.strftime('%Y%m%d%H%M%S'), SecureRandom.hex(16)].join('-')}.zip" + archive_filename = "#{['archive', Time.current.to_fs(:number), SecureRandom.hex(16)].join('-')}.zip" @backup.dump = ActionDispatch::Http::UploadedFile.new(tempfile: tmp_file, filename: archive_filename) @backup.processed = true diff --git a/app/services/create_collection_service.rb b/app/services/create_collection_service.rb new file mode 100644 index 00000000000000..10843cb9677170 --- /dev/null +++ b/app/services/create_collection_service.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +class CreateCollectionService + def call(params, account) + @account = account + @accounts_to_add = Account.find(params.delete(:account_ids) || []) + @collection = Collection.new(params.merge({ account:, local: true })) + build_items + + @collection.save! + @collection + end + + private + + def build_items + return if @accounts_to_add.empty? + + @account.preload_relations!(@accounts_to_add.map(&:id)) + @accounts_to_add.each do |account_to_add| + raise Mastodon::NotPermittedError, I18n.t('accounts.errors.cannot_be_added_to_collections') unless AccountPolicy.new(@account, account_to_add).feature? + + @collection.collection_items.build(account: account_to_add) + end + end +end diff --git a/app/services/fetch_resource_service.rb b/app/services/fetch_resource_service.rb index 2382e126bd33c7..666eccb9ce5e26 100644 --- a/app/services/fetch_resource_service.rb +++ b/app/services/fetch_resource_service.rb @@ -3,7 +3,7 @@ class FetchResourceService < BaseService include JsonLdHelper - ACCEPT_HEADER = 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams", text/html;q=0.1' + ACCEPT_HEADER = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams", application/activity+json, text/html;q=0.1' ACTIVITY_STREAM_LINK_TYPES = ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].freeze attr_reader :response_code diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb index fa2c728e579aef..8d0986adcb93f3 100644 --- a/app/services/notify_service.rb +++ b/app/services/notify_service.rb @@ -61,10 +61,6 @@ def override_for_sender? NotificationPermission.exists?(account: @recipient, from_account: @sender) end - def from_limited? - @sender.silenced? && not_following? - end - def message? @notification.type == :mention end diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index 5cbf990c4df99e..12bb8a083da5a4 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -96,6 +96,7 @@ def process_status! @status = @account.statuses.new(status_attributes) process_mentions_service.call(@status, save_records: false) safeguard_mentions!(@status) + safeguard_private_mention_quote!(@status) attach_quote!(@status) antispam = Antispam.new(@status) @@ -108,6 +109,16 @@ def process_status! end end + def safeguard_private_mention_quote!(status) + return if @quoted_status.nil? || @visibility.to_sym != :direct + + # The mentions array test here is awkward because the relationship is not persisted at this time + return if @quoted_status.account_id == @account.id || status.mentions.to_a.any? { |mention| mention.account_id == @quoted_status.account_id && !mention.silent } + + status.errors.add(:base, I18n.t('statuses.errors.quoted_user_not_mentioned')) + raise ActiveRecord::RecordInvalid, status + end + def attach_quote!(status) return if @quoted_status.nil? @@ -130,6 +141,7 @@ def safeguard_mentions!(status) def schedule_status! status_for_validation = @account.statuses.build(status_attributes) + safeguard_private_mention_quote!(status_for_validation) antispam = Antispam.new(status_for_validation) antispam.local_preflight_check! diff --git a/app/services/process_mentions_service.rb b/app/services/process_mentions_service.rb index 6906f77e1eeaf0..c2c33689ea0a86 100644 --- a/app/services/process_mentions_service.rb +++ b/app/services/process_mentions_service.rb @@ -71,7 +71,7 @@ def assign_mentions! # Make sure we never mention blocked accounts unless @current_mentions.empty? mentioned_domains = @current_mentions.filter_map { |m| m.account.domain }.uniq - blocked_domains = Set.new(mentioned_domains.empty? ? [] : AccountDomainBlock.where(account_id: @status.account_id, domain: mentioned_domains)) + blocked_domains = Set.new(mentioned_domains.empty? ? [] : AccountDomainBlock.where(account_id: @status.account_id, domain: mentioned_domains).pluck(:domain)) mentioned_account_ids = @current_mentions.map(&:account_id) blocked_account_ids = Set.new(@status.account.block_relationships.where(target_account_id: mentioned_account_ids).pluck(:target_account_id)) diff --git a/app/services/revoke_quote_service.rb b/app/services/revoke_quote_service.rb index 1dd37d0555057a..346fba89709ed8 100644 --- a/app/services/revoke_quote_service.rb +++ b/app/services/revoke_quote_service.rb @@ -21,6 +21,11 @@ def distribute_update! end def distribute_stamp_deletion! + # It is possible the quoted status has been soft-deleted. + # In this case, `signed_activity_json` would fail, but we can just ignore + # that, as we have already federated deletion. + return if @quote.quoted_status.nil? + ActivityPub::DeliveryWorker.push_bulk(inboxes, limit: 1_000) do |inbox_url| [signed_activity_json, @account.id, inbox_url] end diff --git a/app/services/statuses_search_service.rb b/app/services/statuses_search_service.rb index ab8e28f61c6dad..3147349d7071b6 100644 --- a/app/services/statuses_search_service.rb +++ b/app/services/statuses_search_service.rb @@ -29,10 +29,11 @@ def status_search_results results = request.collapse(field: :id).order(id: { order: :desc }).limit(@limit).offset(@offset).objects.compact account_ids = results.map(&:account_id) account_domains = results.map(&:account_domain) - preloaded_relations = @account.relations_map(account_ids, account_domains) - results.reject { |status| StatusFilter.new(status, @account, preloaded_relations).filtered? } - rescue Faraday::ConnectionFailed, Parslet::ParseFailed + @account.preload_relations!(account_ids, account_domains) + + results.reject { |status| StatusFilter.new(status, @account).filtered? } + rescue Faraday::ConnectionFailed, Parslet::ParseFailed, Errno::ENETUNREACH [] end diff --git a/app/services/tag_search_service.rb b/app/services/tag_search_service.rb index 57400b76ad140e..6a4af5c9a0a8cb 100644 --- a/app/services/tag_search_service.rb +++ b/app/services/tag_search_service.rb @@ -30,7 +30,7 @@ def from_elasticsearch definition = definition.filter(elastic_search_filter) if @options[:exclude_unreviewed] ensure_exact_match(definition.limit(@limit).offset(@offset).objects.compact) - rescue Faraday::ConnectionFailed, Parslet::ParseFailed + rescue Faraday::ConnectionFailed, Parslet::ParseFailed, Errno::ENETUNREACH nil end diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml index 1f5f81348e23aa..082f71e69e675e 100644 --- a/app/views/accounts/show.html.haml +++ b/app/views/accounts/show.html.haml @@ -6,6 +6,7 @@ %link{ rel: 'alternate', type: 'application/rss+xml', href: rss_url }/ %link{ rel: 'alternate', type: 'application/activity+json', href: ActivityPub::TagManager.instance.uri_for(@account) }/ + %link{ rel: 'alternate', type: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', href: ActivityPub::TagManager.instance.uri_for(@account) }/ - @account.fields.select(&:verifiable?).each do |field| %link{ rel: 'me', type: 'text/html', href: field.value }/ diff --git a/app/views/admin/custom_emojis/index.html.haml b/app/views/admin/custom_emojis/index.html.haml index b9e11a17d1ea98..b7f2d4f631f9cf 100644 --- a/app/views/admin/custom_emojis/index.html.haml +++ b/app/views/admin/custom_emojis/index.html.haml @@ -9,15 +9,15 @@ .filter-subset %strong= t('admin.accounts.location.title') %ul - %li= filter_link_to t('admin.accounts.location.all'), local: nil, remote: nil + %li= filter_link_to t('admin.accounts.location.all'), local: nil, remote: nil, by_domain: nil %li - if selected? local: '1', remote: nil = filter_link_to t('admin.accounts.location.local'), { local: nil, remote: nil }, { local: '1', remote: nil } - else - = filter_link_to t('admin.accounts.location.local'), local: '1', remote: nil + = filter_link_to t('admin.accounts.location.local'), local: '1', remote: nil, by_domain: nil %li - if selected? remote: '1', local: nil - = filter_link_to t('admin.accounts.location.remote'), { remote: nil, local: nil }, { remote: '1', local: nil } + = filter_link_to t('admin.accounts.location.remote'), { remote: nil, local: nil, by_domain: nil }, { remote: '1', local: nil } - else = filter_link_to t('admin.accounts.location.remote'), remote: '1', local: nil @@ -27,6 +27,8 @@ = form.hidden_field key, value: params[key] if params[key].present? - %i(shortcode by_domain).each do |key| + - next if key == :by_domain && params[:local] == '1' + .input.string.optional = form.text_field key, value: params[key], diff --git a/app/views/admin/domain_blocks/confirm_suspension.html.haml b/app/views/admin/domain_blocks/confirm_suspension.html.haml index e82026d3a2b46b..5b56c1903f66e9 100644 --- a/app/views/admin/domain_blocks/confirm_suspension.html.haml +++ b/app/views/admin/domain_blocks/confirm_suspension.html.haml @@ -18,5 +18,5 @@ domain: @domain_block.domain .actions - = link_to t('.cancel'), admin_instances_path, class: 'button button-tertiary' + = link_to t('.cancel'), admin_instances_path, class: 'button button-secondary' = f.button :submit, t('.confirm'), class: 'button button--dangerous', name: :confirm diff --git a/app/views/admin/ip_blocks/_ip_block.html.haml b/app/views/admin/ip_blocks/_ip_block.html.haml index 3dc6f8f8e57c35..0a1b864c6e284b 100644 --- a/app/views/admin/ip_blocks/_ip_block.html.haml +++ b/app/views/admin/ip_blocks/_ip_block.html.haml @@ -3,7 +3,7 @@ = f.check_box :ip_block_ids, { multiple: true, include_hidden: false }, ip_block.id .batch-table__row__content.pending-account .pending-account__header - %samp= link_to "#{ip_block.ip}/#{ip_block.ip.prefix}", admin_accounts_path(ip: "#{ip_block.ip}/#{ip_block.ip.prefix}") + %samp= link_to ip_block.to_cidr, admin_accounts_path(ip: ip_block.to_cidr) - if ip_block.comment.present? · = ip_block.comment diff --git a/app/views/admin/reports/actions/preview.html.haml b/app/views/admin/reports/actions/preview.html.haml index 5a90bf1b8f6f04..bfd7fd5a409ad4 100644 --- a/app/views/admin/reports/actions/preview.html.haml +++ b/app/views/admin/reports/actions/preview.html.haml @@ -76,7 +76,7 @@ %hr.spacer/ .actions - = link_to t('admin.reports.cancel'), admin_report_path(@report), class: 'button button-tertiary' + = link_to t('admin.reports.cancel'), admin_report_path(@report), class: 'button button-secondary' = form.button t('admin.reports.confirm'), name: :confirm, class: 'button', diff --git a/app/views/admin/roles/_role.html.haml b/app/views/admin/roles/_role.html.haml index ddaca5d8a9f8e6..df813bbaf438e6 100644 --- a/app/views/admin/roles/_role.html.haml +++ b/app/views/admin/roles/_role.html.haml @@ -1,7 +1,7 @@ .announcements-list__item - if can?(:update, role) = link_to edit_admin_role_path(role), class: 'announcements-list__item__title' do - %span.user-role + %span = material_symbol 'group' - if role.everyone? @@ -10,13 +10,12 @@ = role.name - else %span.announcements-list__item__title - %span.user-role - = material_symbol 'group' + = material_symbol 'group' - - if role.everyone? - = t('admin.roles.everyone') - - else - = role.name + - if role.everyone? + = t('admin.roles.everyone') + - else + = role.name .announcements-list__item__action-bar .announcements-list__item__meta diff --git a/app/views/admin/settings/about/show.html.haml b/app/views/admin/settings/about/show.html.haml index 1eb47a0b54e865..c4df98e7041091 100644 --- a/app/views/admin/settings/about/show.html.haml +++ b/app/views/admin/settings/about/show.html.haml @@ -5,7 +5,7 @@ %h2= t('admin.settings.title') = render partial: 'admin/settings/shared/links' -= simple_form_for @admin_settings, url: admin_settings_about_path, html: { method: :patch } do |f| += simple_form_for @admin_settings, url: admin_settings_about_path do |f| = render 'shared/error_messages', object: @admin_settings %p.lead= t('admin.settings.about.preamble') @@ -24,7 +24,7 @@ .fields-row__column.fields-row__column-6.fields-group = f.input :show_domain_blocks, collection_wrapper_tag: 'ul', - collection: %i(disabled users all), + collection: f.object.class::DOMAIN_BLOCK_AUDIENCES, include_blank: false, item_wrapper_tag: 'li', label_method: ->(value) { t("admin.settings.domain_blocks.#{value}") }, @@ -32,7 +32,7 @@ .fields-row__column.fields-row__column-6.fields-group = f.input :show_domain_blocks_rationale, collection_wrapper_tag: 'ul', - collection: %i(disabled users all), + collection: f.object.class::DOMAIN_BLOCK_AUDIENCES, include_blank: false, item_wrapper_tag: 'li', label_method: ->(value) { t("admin.settings.domain_blocks.#{value}") }, diff --git a/app/views/admin/settings/appearance/show.html.haml b/app/views/admin/settings/appearance/show.html.haml index f350956c87a829..6037b852089360 100644 --- a/app/views/admin/settings/appearance/show.html.haml +++ b/app/views/admin/settings/appearance/show.html.haml @@ -5,7 +5,7 @@ %h2= t('admin.settings.title') = render partial: 'admin/settings/shared/links' -= simple_form_for @admin_settings, url: admin_settings_appearance_path, html: { method: :patch } do |f| += simple_form_for @admin_settings, url: admin_settings_appearance_path do |f| = render 'shared/error_messages', object: @admin_settings %p.lead= t('admin.settings.appearance.preamble') diff --git a/app/views/admin/settings/branding/show.html.haml b/app/views/admin/settings/branding/show.html.haml index 62e9c724592d92..22116167eb36b8 100644 --- a/app/views/admin/settings/branding/show.html.haml +++ b/app/views/admin/settings/branding/show.html.haml @@ -5,7 +5,7 @@ %h2= t('admin.settings.title') = render partial: 'admin/settings/shared/links' -= simple_form_for @admin_settings, url: admin_settings_branding_path, html: { method: :patch } do |f| += simple_form_for @admin_settings, url: admin_settings_branding_path do |f| = render 'shared/error_messages', object: @admin_settings %p.lead= t('admin.settings.branding.preamble') @@ -68,5 +68,12 @@ = material_symbol 'delete' = t('admin.site_uploads.delete') + .fields-row + = f.input :landing_page, + collection: f.object.class::LANDING_PAGE, + include_blank: false, + label_method: ->(page) { I18n.t("admin.settings.landing_page.values.#{page}") }, + wrapper: :with_label + .actions = f.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/admin/settings/content_retention/show.html.haml b/app/views/admin/settings/content_retention/show.html.haml index 57485c1108ff3e..c6c39fdf44496a 100644 --- a/app/views/admin/settings/content_retention/show.html.haml +++ b/app/views/admin/settings/content_retention/show.html.haml @@ -5,7 +5,7 @@ %h2= t('admin.settings.title') = render partial: 'admin/settings/shared/links' -= simple_form_for @admin_settings, url: admin_settings_content_retention_path, html: { method: :patch } do |f| += simple_form_for @admin_settings, url: admin_settings_content_retention_path do |f| = render 'shared/error_messages', object: @admin_settings %p.lead= t('admin.settings.content_retention.preamble') diff --git a/app/views/admin/settings/discovery/show.html.haml b/app/views/admin/settings/discovery/show.html.haml index b38813fb1000ee..b32cebf7558755 100644 --- a/app/views/admin/settings/discovery/show.html.haml +++ b/app/views/admin/settings/discovery/show.html.haml @@ -5,7 +5,7 @@ %h2= t('admin.settings.title') = render partial: 'admin/settings/shared/links' -= simple_form_for @admin_settings, url: admin_settings_discovery_path, html: { method: :patch } do |f| += simple_form_for @admin_settings, url: admin_settings_discovery_path do |f| = render 'shared/error_messages', object: @admin_settings %p.lead= t('admin.settings.discovery.preamble') @@ -17,11 +17,6 @@ as: :boolean, wrapper: :with_label - .fields-group - = f.input :trends_as_landing_page, - as: :boolean, - wrapper: :with_label - .fields-group = f.input :trendable_by_default, as: :boolean, @@ -33,10 +28,35 @@ %h4= t('admin.settings.discovery.public_timelines') - .fields-group - = f.input :timeline_preview, - as: :boolean, - wrapper: :with_label + .fields-row + .fields-row__column.fields-row__column-6.fields-group + = f.input :local_live_feed_access, + collection: f.object.class::FEED_ACCESS_MODES, + include_blank: false, + label_method: ->(mode) { I18n.t("admin.settings.feed_access.modes.#{mode}") }, + wrapper: :with_label + + .fields-row__column.fields-row__column-6.fields-group + = f.input :remote_live_feed_access, + collection: f.object.class::FEED_ACCESS_MODES, + include_blank: false, + label_method: ->(mode) { I18n.t("admin.settings.feed_access.modes.#{mode}") }, + wrapper: :with_label + + .fields-row + .fields-row__column.fields-row__column-6.fields-group + = f.input :local_topic_feed_access, + collection: f.object.class::ALTERNATE_FEED_ACCESS_MODES, + include_blank: false, + label_method: ->(mode) { I18n.t("admin.settings.feed_access.modes.#{mode}") }, + wrapper: :with_label + + .fields-row__column.fields-row__column-6.fields-group + = f.input :remote_topic_feed_access, + collection: f.object.class::FEED_ACCESS_MODES, + include_blank: false, + label_method: ->(mode) { I18n.t("admin.settings.feed_access.modes.#{mode}") }, + wrapper: :with_label %h4= t('admin.settings.discovery.privacy') @@ -93,5 +113,12 @@ as: :boolean, wrapper: :with_label + %h4= t('admin.settings.discovery.wrapstodon') + + .fields-group + = f.input :wrapstodon, + as: :boolean, + wrapper: :with_label + .actions = f.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/admin/settings/registrations/show.html.haml b/app/views/admin/settings/registrations/show.html.haml index cb5a3eb6baf727..92f755e9e5831c 100644 --- a/app/views/admin/settings/registrations/show.html.haml +++ b/app/views/admin/settings/registrations/show.html.haml @@ -5,7 +5,7 @@ %h2= t('admin.settings.title') = render partial: 'admin/settings/shared/links' -= simple_form_for @admin_settings, url: admin_settings_registrations_path, html: { method: :patch } do |f| += simple_form_for @admin_settings, url: admin_settings_registrations_path do |f| = render 'shared/error_messages', object: @admin_settings %p.lead= t('admin.settings.registrations.preamble') @@ -18,7 +18,7 @@ .fields-row .fields-row__column.fields-row__column-6.fields-group = f.input :registrations_mode, - collection: %w(open approved none), + collection: f.object.class::REGISTRATION_MODES, include_blank: false, label_method: ->(mode) { I18n.t("admin.settings.registrations_mode.modes.#{mode}") }, warning_hint: I18n.t('admin.settings.registrations_mode.warning_hint'), diff --git a/app/views/admin/shared/_preview_card.html.haml b/app/views/admin/shared/_preview_card.html.haml new file mode 100644 index 00000000000000..c4796dc59cc384 --- /dev/null +++ b/app/views/admin/shared/_preview_card.html.haml @@ -0,0 +1,30 @@ +/# locals: (preview_card:) + +.preview-card + .status-card.expanded + .status-card__image{ class: preview_card_aspect_ratio_classname(preview_card) } + .spoiler-button + %button.hide-button{ type: 'button' }= t('link_preview.potentially_sensitive_content.hide_button') + %button.spoiler-button__overlay{ type: 'button' } + %span.spoiler-button__overlay__label + %span= t('link_preview.potentially_sensitive_content.label') + %span.spoiler-button__overlay__action + %span= t('link_preview.potentially_sensitive_content.action') + %canvas.status-card__image-preview{ 'data-blurhash': preview_card.blurhash, width: 32, height: 32 } + = image_tag preview_card.image.url, alt: '', class: 'status-card__image-image' + = link_to preview_card.url, target: '_blank', rel: 'noopener', data: { confirm: t('link_preview.potentially_sensitive_content.confirm_visit') } do + .status-card__content{ dir: 'auto' } + %span.status-card__host + %span{ lang: preview_card.language } + = preview_card.provider_name + - if preview_card.published_at + · + %time.relative-formatted{ datetime: preview_card.published_at.iso8601, title: l(preview_card.published_at) }= l(preview_card.published_at) + %strong.status-card__title{ title: preview_card.title, lang: preview_card.language } + = preview_card.title + - if preview_card.author_name.present? + %span.status-card__author + = t('link_preview.author_html', name: content_tag(:strong, preview_card.author_name)) + - else + %span.status-card__description{ lang: preview_card.language } + = preview_card.description diff --git a/app/views/admin/shared/_status.html.haml b/app/views/admin/shared/_status.html.haml index c042fd7a2cd2ba..9b5880ab41365e 100644 --- a/app/views/admin/shared/_status.html.haml +++ b/app/views/admin/shared/_status.html.haml @@ -1,6 +1,5 @@ --# locals: (status:) - -.status__card>< +-# locals: (status:, is_quote: false) +.status__card{ class: status_classnames(status, is_quote) } - if status.reblog? .status__prepend = material_symbol('repeat') @@ -10,31 +9,48 @@ = material_symbol('reply') = t('admin.statuses.replied_to_html', acct_link: admin_account_inline_link_to(status.in_reply_to_account, path: status.thread.present? ? admin_account_status_path(status.thread.account_id, status.in_reply_to_id) : nil)) - = render partial: 'admin/shared/status_content', locals: { status: status.proper } + - if is_quote + .status__info + = conditional_link_to can?(:show, status), admin_account_status_path(status.account.id, status), class: 'status__relative-time' do + %span.status__visibility-icon{ title: t("statuses.visibilities.#{status.visibility}") }>< + = material_symbol(visibility_icon(status), whitespace: false) + %time.relative-formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }><= l(status.created_at) + = link_to admin_account_path(status.account.id), class: 'status__display-name' do + .status__avatar + .account__avatar + = image_tag status.account.avatar.url(:original), alt: '', width: 46, height: 46, class: 'avatar' + .display-name + %bdi + %strong.display-name__html.emojify.p-name= display_name(status.account, custom_emojify: true) + %span.display-name__account + = acct(status.account) + + = render partial: 'admin/shared/status_content', locals: { status: status.proper, is_quote: is_quote } - .detailed-status__meta - - if status.application - = status.application.name - · - = conditional_link_to can?(:show, status), admin_account_status_path(status.account.id, status), class: 'detailed-status__datetime' do - %time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }><= l(status.created_at) - - if status.edited? -  · - = conditional_link_to can?(:show, status), admin_account_status_path(status.account.id, status, { anchor: 'history' }), class: 'detailed-status__datetime' do - %span><= t('statuses.edited_at_html', date: content_tag(:time, l(status.edited_at), datetime: status.edited_at.iso8601, title: l(status.edited_at), class: 'relative-formatted')) - - if status.discarded? -  · - %span.negative-hint= t('admin.statuses.deleted') - - unless status.reblog? -  · - %span< - = material_symbol(visibility_icon(status)) - = t("statuses.visibilities.#{status.visibility}") - - if status.proper.sensitive? -  · - = material_symbol('visibility_off') - = t('stream_entries.sensitive_content') - - unless status.direct_visibility? -  · - = link_to ActivityPub::TagManager.instance.url_for(status.proper), class: 'detailed-status__link', target: 'blank', rel: 'noopener' do - = t('admin.statuses.view_publicly') + - unless is_quote + .detailed-status__meta + = conditional_link_to can?(:show, status), admin_account_status_path(status.account.id, status), class: 'detailed-status__datetime' do + %time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }><= l(status.created_at) + - if status.edited? +  · + = conditional_link_to can?(:show, status), admin_account_status_path(status.account.id, status, { anchor: 'history' }), class: 'detailed-status__datetime' do + %span><= t('statuses.edited_at_html', date: content_tag(:time, l(status.edited_at), datetime: status.edited_at.iso8601, title: l(status.edited_at), class: 'relative-formatted')) + - if status.discarded? +  · + %span.negative-hint= t('admin.statuses.deleted') + - if status.application +  · + = status.application.name + - unless status.reblog? +  · + %span< + = material_symbol(visibility_icon(status)) + = t("statuses.visibilities.#{status.visibility}") + - if status.proper.sensitive? +  · + = material_symbol('visibility_off') + = t('stream_entries.sensitive_content') + - unless status.direct_visibility? +  · + = link_to ActivityPub::TagManager.instance.url_for(status.proper), class: 'detailed-status__link', target: 'blank', rel: 'noopener' do + = t('admin.statuses.view_publicly') diff --git a/app/views/admin/shared/_status_attachments.html.haml b/app/views/admin/shared/_status_attachments.html.haml index d34a4221db72aa..c627027bb84d27 100644 --- a/app/views/admin/shared/_status_attachments.html.haml +++ b/app/views/admin/shared/_status_attachments.html.haml @@ -1,3 +1,4 @@ +-# locals: (status:, is_quote: false) - if status.with_poll? .poll %ul @@ -13,6 +14,9 @@ %button.button.button-secondary{ disabled: true } = t('polls.vote') +- if status.with_preview_card? + = render partial: 'admin/shared/preview_card', locals: { preview_card: status.preview_card } + - if status.with_media? - if status.ordered_media_attachments.first.video? = render_video_component(status, visible: false) @@ -20,3 +24,24 @@ = render_audio_component(status) - else = render_media_gallery_component(status, visible: false) + +- if status.quote + - if status.quote.accepted? && status.quote.quoted_status.present? + - if is_quote + .status__quote-author-button + %span= t('statuses.quote_post_author', acct: acct(status.account)) + - else + .status__quote + = render partial: 'admin/shared/status', object: status.quote.quoted_status, locals: { is_quote: true } + - else + .status__quote.status__quote--error + - if status.quote.pending? + %span= t('statuses.quote_error.pending_approval') + - elsif status.quote.revoked? + %span= t('statuses.quote_error.revoked') + - else + %span= t('statuses.quote_error.not_available') + + - if status.quote.quoted_status.present? && can?(:show, status.quote.quoted_status) + = link_to admin_account_status_path(status.quote.quoted_status.account.id, status.quote.quoted_status), class: 'link-button' do + = t('admin.statuses.view_quoted_post') diff --git a/app/views/admin/shared/_status_content.html.haml b/app/views/admin/shared/_status_content.html.haml index aedd84bdd679ef..53e79b152c4770 100644 --- a/app/views/admin/shared/_status_content.html.haml +++ b/app/views/admin/shared/_status_content.html.haml @@ -1,10 +1,18 @@ -.status__content>< - - if status.spoiler_text.present? - %details< - %summary>< - %strong> Content warning: #{prerender_custom_emojis(h(status.spoiler_text), status.emojis)} +-# locals: (status:, is_quote: false) +- if status.spoiler_text.present? + %details< + %summary{ + data: { + show: t('statuses.content_warnings.show'), + hide: t('statuses.content_warnings.hide'), + } + }>< + %strong> + = prerender_custom_emojis(h(status.spoiler_text), status.emojis) + .status__content>< = prerender_custom_emojis(status_content_format(status), status.emojis) - = render partial: 'admin/shared/status_attachments', locals: { status: status.proper } - - else + = render partial: 'admin/shared/status_attachments', locals: { status: status.proper, is_quote: is_quote } +- else + .status__content>< = prerender_custom_emojis(status_content_format(status), status.emojis) - = render partial: 'admin/shared/status_attachments', locals: { status: status.proper } + = render partial: 'admin/shared/status_attachments', locals: { status: status.proper, is_quote: is_quote } diff --git a/app/views/admin/statuses/show.html.haml b/app/views/admin/statuses/show.html.haml index ba5ba819874b66..27af69fb5a0219 100644 --- a/app/views/admin/statuses/show.html.haml +++ b/app/views/admin/statuses/show.html.haml @@ -45,6 +45,9 @@ %tr %th= t('admin.statuses.reblogs') %td= friendly_number_to_human @status.reblogs_count + %tr + %th= t('admin.statuses.quotes') + %td= friendly_number_to_human @status.quotes_count %tr %th= t('admin.statuses.favourites') %td= friendly_number_to_human @status.favourites_count diff --git a/app/views/auth/registrations/_session.html.haml b/app/views/auth/registrations/_session.html.haml index 92e5147593f980..b347d21e40af2c 100644 --- a/app/views/auth/registrations/_session.html.haml +++ b/app/views/auth/registrations/_session.html.haml @@ -1,7 +1,7 @@ %tr %td %span{ title: session.user_agent }< - = material_symbol session_device_icon(session), 'aria-label': session_device_icon(session) + = material_symbol session_device_icon(session)   = t 'sessions.description', browser: t("sessions.browsers.#{session.browser}", default: session.browser.to_s), diff --git a/app/views/auth/registrations/rules.html.haml b/app/views/auth/registrations/rules.html.haml index 59e7c9072fbdcc..a5a96c573786c3 100644 --- a/app/views/auth/registrations/rules.html.haml +++ b/app/views/auth/registrations/rules.html.haml @@ -27,4 +27,4 @@ .stacked-actions - accept_path = @invite_code.present? ? public_invite_url(invite_code: @invite_code, accept: @accept_token) : new_user_registration_path(accept: @accept_token) = link_to t('auth.rules.accept'), accept_path, class: 'button' - = link_to t('auth.rules.back'), root_path, class: 'button button-tertiary' + = link_to t('auth.rules.back'), root_path, class: 'button button-secondary' diff --git a/app/views/filters/_filter.html.haml b/app/views/filters/_filter.html.haml index 15326f300626e8..bf2f4dd7d1d736 100644 --- a/app/views/filters/_filter.html.haml +++ b/app/views/filters/_filter.html.haml @@ -19,9 +19,7 @@ .permissions-list__item__text__title = t('filters.index.keywords', count: filter.keywords.size) .permissions-list__item__text__type - - keywords = filter.keywords.map(&:keyword) - - keywords = keywords.take(5) + ['…'] if keywords.size > 5 # TODO - = keywords.join(', ') + = filter_keywords(filter) - unless filter.statuses.empty? %li.permissions-list__item .permissions-list__item__icon diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index b98e21df669bdf..413c2f21125f6a 100755 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -1,5 +1,5 @@ !!! 5 -%html{ lang: I18n.locale } +%html{ lang: I18n.locale, class: html_classes, 'data-user-theme': current_skin.parameterize, 'data-user-flavour': current_flavour.parameterize } %head %meta{ charset: 'utf-8' }/ %meta{ name: 'viewport', content: 'width=device-width, initial-scale=1, viewport-fit=cover' }/ diff --git a/app/views/layouts/modal.html.haml b/app/views/layouts/modal.html.haml index 71ff21fe5fc991..4f7df22596b6fb 100644 --- a/app/views/layouts/modal.html.haml +++ b/app/views/layouts/modal.html.haml @@ -1,7 +1,7 @@ - content_for :header_tags do = flavoured_vite_typescript_tag 'public.tsx', crossorigin: 'anonymous' -- content_for :body_classes, 'modal-layout compose-standalone' +- content_for :body_classes, 'modal-layout with-zig-zag-decoration compose-standalone' - content_for :content do - if user_signed_in? && !@hide_header diff --git a/app/views/notification_mailer/_nested_quote.html.haml b/app/views/notification_mailer/_nested_quote.html.haml new file mode 100644 index 00000000000000..dc0921c2ed5c8b --- /dev/null +++ b/app/views/notification_mailer/_nested_quote.html.haml @@ -0,0 +1,17 @@ +%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } + %tr + %td.email-quote-header-img + = image_tag full_asset_url(status.account.avatar.url), alt: '', width: 34, height: 34 + %td.email-quote-header-text + %h2.email-quote-header-name + = display_name(status.account) + %p.email-quote-header-handle + @#{status.account.pretty_acct} + +%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } + %tr + %td.email-status-content + = render 'notification_mailer/status_content', status: status + + %p.email-status-footer + = link_to l(status.created_at.in_time_zone(time_zone.presence), format: :with_time_zone), web_url("@#{status.account.pretty_acct}/#{status.id}") diff --git a/app/views/notification_mailer/_status.html.haml b/app/views/notification_mailer/_status.html.haml index bf38dc9aa26ca5..c56c7ec72ca1dc 100644 --- a/app/views/notification_mailer/_status.html.haml +++ b/app/views/notification_mailer/_status.html.haml @@ -11,21 +11,12 @@ %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } %tr %td.email-status-content - .auto-dir - - if status.spoiler_text? - %p.email-status-spoiler - = status.spoiler_text - - .email-status-prose - = status_content_format(status) - - - if status.ordered_media_attachments.size.positive? - %p.email-status-media - - status.ordered_media_attachments.each do |a| - - if status.local? - = link_to full_asset_url(a.file.url(:original)), full_asset_url(a.file.url(:original)) - - else - = link_to a.remote_url, a.remote_url + = render 'notification_mailer/status_content', status: status + - if status.local? && status.quote + %table.email-inner-card-table{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } + %tr + %td.email-inner-nested-card-td + = render 'notification_mailer/nested_quote', status: status.quote.quoted_status, time_zone: time_zone %p.email-status-footer = link_to l(status.created_at.in_time_zone(time_zone.presence), format: :with_time_zone), web_url("@#{status.account.pretty_acct}/#{status.id}") diff --git a/app/views/notification_mailer/_status.text.erb b/app/views/notification_mailer/_status.text.erb index e03e8346c16a25..13711ee74d9c70 100644 --- a/app/views/notification_mailer/_status.text.erb +++ b/app/views/notification_mailer/_status.text.erb @@ -4,5 +4,9 @@ > <% end %> > <%= raw word_wrap(extract_status_plain_text(status), break_sequence: "\n> ") %> +<% if status.local? && status.quote %> +> +>> <%= raw word_wrap(extract_status_plain_text(status.quote.quoted_status), break_sequence: "\n>> ") %> +<% end %> <%= raw t('application_mailer.view')%> <%= web_url("@#{status.account.pretty_acct}/#{status.id}") %> diff --git a/app/views/notification_mailer/_status_content.html.haml b/app/views/notification_mailer/_status_content.html.haml new file mode 100644 index 00000000000000..f95ba8ccba856a --- /dev/null +++ b/app/views/notification_mailer/_status_content.html.haml @@ -0,0 +1,15 @@ +.auto-dir + - if status.spoiler_text? + %p.email-status-spoiler + = status.spoiler_text + + .email-status-prose + = status_content_format(status) + + - if status.ordered_media_attachments.size.positive? + %p.email-status-media + - status.ordered_media_attachments.each do |a| + - if status.local? + = link_to full_asset_url(a.file.url(:original)), full_asset_url(a.file.url(:original)) + - else + = link_to a.remote_url, a.remote_url diff --git a/app/views/settings/imports/show.html.haml b/app/views/settings/imports/show.html.haml index dd18ac2168c8c7..40b06e6efb143f 100644 --- a/app/views/settings/imports/show.html.haml +++ b/app/views/settings/imports/show.html.haml @@ -11,5 +11,5 @@ .simple_form .actions - = link_to t('generic.cancel'), settings_import_path(@bulk_import), method: :delete, class: 'button button-tertiary' + = link_to t('generic.cancel'), settings_import_path(@bulk_import), method: :delete, class: 'button button-secondary' = link_to t('generic.confirm'), confirm_settings_import_path(@bulk_import), method: :post, class: 'button' diff --git a/app/views/settings/migrations/show.html.haml b/app/views/settings/migrations/show.html.haml index 31f7d5e58d754e..296075a28edb52 100644 --- a/app/views/settings/migrations/show.html.haml +++ b/app/views/settings/migrations/show.html.haml @@ -30,8 +30,8 @@ %ul.hint %li.warning-hint= t('migrations.warning.followers') - %li.warning-hint= t('migrations.warning.redirect') %li.warning-hint= t('migrations.warning.other_data') + %li.warning-hint= t('migrations.warning.redirect') %li.warning-hint= t('migrations.warning.backreference_required') %li.warning-hint= t('migrations.warning.cooldown') %li.warning-hint= t('migrations.warning.disabled_account') diff --git a/app/views/settings/preferences/appearance/show.html.haml b/app/views/settings/preferences/appearance/show.html.haml index 17d0173b6bc7c5..7042fa7e751f8b 100644 --- a/app/views/settings/preferences/appearance/show.html.haml +++ b/app/views/settings/preferences/appearance/show.html.haml @@ -21,33 +21,25 @@ selected: current_user.time_zone || Time.zone.tzinfo.name, wrapper: :with_label - - if Mastodon::Feature.modern_emojis_enabled? - .fields-group - = f.simple_fields_for :settings, current_user.settings do |ff| - = ff.input :'web.emoji_style', - collection: %w(auto twemoji native), - include_blank: false, - hint: I18n.t('simple_form.hints.defaults.setting_emoji_style'), - label: I18n.t('simple_form.labels.defaults.setting_emoji_style'), - label_method: ->(emoji_style) { I18n.t("emoji_styles.#{emoji_style}", default: emoji_style) }, - wrapper: :with_label + .fields-group + = f.simple_fields_for :settings, current_user.settings do |ff| + = ff.input :'web.emoji_style', + collection: %w(auto twemoji native), + include_blank: false, + hint: I18n.t('simple_form.hints.defaults.setting_emoji_style'), + label: I18n.t('simple_form.labels.defaults.setting_emoji_style'), + label_method: ->(emoji_style) { I18n.t("emoji_styles.#{emoji_style}", default: emoji_style) }, + wrapper: :with_label, + required: false - unless I18n.locale == :en .flash-message.translation-prompt - #{t 'appearance.localization.body'} #{content_tag(:a, t('appearance.localization.guide_link_text'), href: t('appearance.localization.guide_link'), target: '_blank', rel: 'noopener')} + = t 'appearance.localization.body' + = link_to t('appearance.localization.guide_link_text'), t('appearance.localization.guide_link'), target: '_blank', rel: 'noopener' = link_to t('appearance.localization.glitch_guide_link'), target: '_blank', rel: 'noopener noreferrer' do = t('appearance.localization.glitch_guide_link_text') = f.simple_fields_for :settings, current_user.settings do |ff| - %h4= t 'appearance.advanced_web_interface' - - %p.hint= t 'appearance.advanced_web_interface_hint' - - .fields-group - = ff.input :'web.advanced_layout', - hint: false, - label: I18n.t('simple_form.labels.defaults.setting_advanced_layout'), - wrapper: :with_label %h4= t 'appearance.animations_and_accessibility' .fields-group @@ -62,7 +54,6 @@ = ff.input :'web.disable_swiping', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_disable_swiping') = ff.input :'web.disable_hover_cards', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_disable_hover_cards') = ff.input :'web.use_system_font', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_system_font_ui') - = ff.input :'web.use_system_emoji_font', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_system_emoji_font'), glitch_only: true = ff.input :'web.use_system_scrollbars', wrapper: :with_label, hint: I18n.t('simple_form.hints.defaults.setting_system_scrollbars_ui'), label: I18n.t('simple_form.labels.defaults.setting_system_scrollbars_ui') %h4= t 'appearance.discovery' @@ -70,13 +61,13 @@ .fields-group = ff.input :'web.trends', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_trends') - %h4= t 'appearance.confirmation_dialogs' + %h4= t 'appearance.boosting_preferences' .fields-group - = ff.input :'web.reblog_modal', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_boost_modal') - = ff.input :'web.favourite_modal', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_favourite_modal'), glitch_only: true - = ff.input :'web.delete_modal', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_delete_modal') - = ff.input :'web.missing_alt_text_modal', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_missing_alt_text_modal') + = ff.input :'web.reblog_modal', wrapper: :with_label, hint: I18n.t('simple_form.hints.defaults.setting_boost_modal'), label: I18n.t('simple_form.labels.defaults.setting_boost_modal') + .fields-group + = ff.input :'web.quick_boosting', wrapper: :with_label, hint: t('simple_form.hints.defaults.setting_quick_boosting_html', boost_icon: material_symbol('repeat'), options_icon: material_symbol('more_horiz')), label: I18n.t('simple_form.labels.defaults.setting_quick_boosting') + .flash-message.hidden-on-touch-devices= t('appearance.boosting_preferences_info_html', icon: material_symbol('repeat')) %h4= t 'appearance.sensitive_content' @@ -89,7 +80,8 @@ item_wrapper_tag: 'li', label_method: ->(item) { t("simple_form.hints.defaults.setting_display_media_#{item}") }, label: I18n.t('simple_form.labels.defaults.setting_display_media'), - wrapper: :with_floating_label + wrapper: :with_floating_label, + required: false .fields-group = ff.input :'web.use_blurhash', @@ -100,5 +92,18 @@ .fields-group = ff.input :'web.expand_content_warnings', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_expand_spoilers') + %h4= t 'appearance.advanced_settings' + + .fields-group + = ff.input :'web.advanced_layout', + hint: I18n.t('simple_form.hints.defaults.setting_advanced_layout'), + label: I18n.t('simple_form.labels.defaults.setting_advanced_layout'), + wrapper: :with_label + + .fields-group + = ff.input :'web.delete_modal', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_delete_modal') + = ff.input :'web.missing_alt_text_modal', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_missing_alt_text_modal') + = ff.input :'web.favourite_modal', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_favourite_modal'), glitch_only: true + .actions = f.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/settings/preferences/notifications/show.html.haml b/app/views/settings/preferences/notifications/show.html.haml index 0cd613a4163591..e534c06369c324 100644 --- a/app/views/settings/preferences/notifications/show.html.haml +++ b/app/views/settings/preferences/notifications/show.html.haml @@ -42,4 +42,5 @@ include_blank: false, label_method: ->(setting) { I18n.t("simple_form.labels.notification_emails.software_updates.#{setting}") }, label: I18n.t('simple_form.labels.notification_emails.software_updates.label'), - wrapper: :with_label + wrapper: :with_label, + required: false diff --git a/app/views/settings/privacy/show.html.haml b/app/views/settings/privacy/show.html.haml index e8e5bdb906322f..7e3ae851e42ef9 100644 --- a/app/views/settings/privacy/show.html.haml +++ b/app/views/settings/privacy/show.html.haml @@ -2,8 +2,7 @@ = t('privacy.title') - content_for :heading do - %h2= t('settings.profile') - = render partial: 'settings/shared/profile_navigation' + %h2= t('privacy.title') = simple_form_for @account, url: settings_privacy_path do |f| = render 'shared/error_messages', object: @account diff --git a/app/views/settings/shared/_profile_navigation.html.haml b/app/views/settings/shared/_profile_navigation.html.haml index 2f81cb5cfdccdf..9da8af9c2ee077 100644 --- a/app/views/settings/shared/_profile_navigation.html.haml +++ b/app/views/settings/shared/_profile_navigation.html.haml @@ -2,6 +2,5 @@ = render_navigation renderer: :links do |primary| :ruby primary.item :edit_profile, safe_join([material_symbol('person'), t('settings.edit_profile')]), settings_profile_path - primary.item :privacy_reach, safe_join([material_symbol('lock'), t('privacy.title')]), settings_privacy_path primary.item :verification, safe_join([material_symbol('check'), t('verification.verification')]), settings_verification_path primary.item :featured_tags, safe_join([material_symbol('tag'), t('settings.featured_tags')]), settings_featured_tags_path diff --git a/app/views/settings/verifications/show.html.haml b/app/views/settings/verifications/show.html.haml index 0243e3b806b696..ac8778a7ec5235 100644 --- a/app/views/settings/verifications/show.html.haml +++ b/app/views/settings/verifications/show.html.haml @@ -28,7 +28,7 @@ - @verified_links.each do |field| %li %span.verified-badge - = material_symbol 'check', class: 'verified-badge__mark' + = material_symbol 'check', { class: 'verified-badge__mark' } %span= field.value = simple_form_for @account, url: settings_verification_path, html: { class: 'form-section' } do |f| diff --git a/app/views/severed_relationships/_event.html.haml b/app/views/severed_relationships/_event.html.haml index adc7752cb960d3..f4279559b7078e 100644 --- a/app/views/severed_relationships/_event.html.haml +++ b/app/views/severed_relationships/_event.html.haml @@ -6,7 +6,7 @@ = l(event.created_at) %td= t("severed_relationships.event_type.#{event.type}", target_name: event.target_name) - if event.purged? - %td{ rowspan: 2 }= t('severed_relationships.purged') + %td{ colspan: 2 }= t('severed_relationships.purged') - else %td = render 'download', count: event.following_count, link: following_severed_relationship_path(event, format: :csv) diff --git a/app/views/statuses/_og_image.html.haml b/app/views/statuses/_og_image.html.haml index 1ae97adff67bb7..1f7f57f1562603 100644 --- a/app/views/statuses/_og_image.html.haml +++ b/app/views/statuses/_og_image.html.haml @@ -1,6 +1,6 @@ -- if activity.is_a?(Status) && (activity.non_sensitive_with_media? || (activity.with_media? && Setting.preview_sensitive_media)) +- if status.non_sensitive_with_media? || (status.with_media? && Setting.preview_sensitive_media) - player_card = false - - activity.ordered_media_attachments.each do |media| + - status.ordered_media_attachments.each do |media| - if media.image? = opengraph 'og:image', full_asset_url(media.file.url(:original)) = opengraph 'og:image:type', media.file_content_type diff --git a/app/views/statuses/show.html.haml b/app/views/statuses/show.html.haml index c530b77c331aac..7478562ea2e98e 100644 --- a/app/views/statuses/show.html.haml +++ b/app/views/statuses/show.html.haml @@ -8,6 +8,7 @@ %link{ rel: 'alternate', type: 'application/json+oembed', href: api_oembed_url(url: short_account_status_url(@account, @status), format: 'json') }/ %link{ rel: 'alternate', type: 'application/activity+json', href: ActivityPub::TagManager.instance.uri_for(@status) }/ + %link{ rel: 'alternate', type: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', href: ActivityPub::TagManager.instance.uri_for(@status) }/ = opengraph 'og:site_name', site_title = opengraph 'og:type', 'article' @@ -19,6 +20,6 @@ = opengraph 'profile:username', acct(@account)[1..] = render 'og_description', activity: @status - = render 'og_image', activity: @status, account: @account + = render 'og_image', status: @status, account: @account = render 'shared/web_app' diff --git a/app/views/tags/show.html.haml b/app/views/tags/show.html.haml index bce1b4aa4d160c..0746e42cbcfe0f 100644 --- a/app/views/tags/show.html.haml +++ b/app/views/tags/show.html.haml @@ -1,6 +1,7 @@ - content_for :header_tags do %link{ rel: :alternate, type: 'application/rss+xml', href: tag_url(@tag) }/ %link{ rel: :alternate, type: 'application/activity+json', href: tag_url(@tag) }/ + %link{ rel: :alternate, type: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', href: tag_url(@tag) }/ %meta{ name: 'robots', content: 'noindex' }/ = render partial: 'shared/og' diff --git a/app/views/wrapstodon/_og_description.html.haml b/app/views/wrapstodon/_og_description.html.haml new file mode 100644 index 00000000000000..7b6e04cb8ee86b --- /dev/null +++ b/app/views/wrapstodon/_og_description.html.haml @@ -0,0 +1,4 @@ +- description = t('wrapstodon.description', name: display_name(account)) + +%meta{ name: 'description', content: description }/ += opengraph 'og:description', description diff --git a/app/views/wrapstodon/_og_image.html.haml b/app/views/wrapstodon/_og_image.html.haml new file mode 100644 index 00000000000000..8a52a568ac345a --- /dev/null +++ b/app/views/wrapstodon/_og_image.html.haml @@ -0,0 +1,6 @@ +- if %w(lurker booster pollster replier oracle).include?(report.data['archetype']) + = opengraph 'og:image', frontend_asset_url("images/archetypes/previews/#{report.data['archetype']}.jpg") + = opengraph 'og:image:type', 'image/jpeg' + = opengraph 'og:image:width', 1200 + = opengraph 'og:image:height', 630 + = opengraph 'twitter:card', 'summary_large_image' diff --git a/app/views/wrapstodon/show.html.haml b/app/views/wrapstodon/show.html.haml new file mode 100644 index 00000000000000..7d1c15bf94a80c --- /dev/null +++ b/app/views/wrapstodon/show.html.haml @@ -0,0 +1,19 @@ +- content_for :page_title, t('wrapstodon.title', name: display_name(@account), year: @generated_annual_report.year) + +- content_for :header_tags do + %meta{ name: 'robots', content: 'noindex, noarchive' }/ + + = opengraph 'og:site_name', site_title + = opengraph 'og:type', 'article' + = opengraph 'og:title', t('wrapstodon.title', name: display_name(@account), year: @generated_annual_report.year) + = opengraph 'profile:username', acct(@account)[1..] + + = render 'og_description', account: @account + = render 'og_image', report: @generated_annual_report + + = flavoured_vite_typescript_tag 'wrapstodon.tsx', crossorigin: 'anonymous' + +- content_for :html_classes, 'theme-dark' + +#wrapstodon += render_wrapstodon_share_data @generated_annual_report diff --git a/app/workers/activitypub/delivery_worker.rb b/app/workers/activitypub/delivery_worker.rb index 40b5c42404fede..ade7175c9d52fc 100644 --- a/app/workers/activitypub/delivery_worker.rb +++ b/app/workers/activitypub/delivery_worker.rb @@ -55,7 +55,7 @@ def build_request(http_client) end def synchronization_header - "collectionId=\"#{account_followers_url(@source_account)}\", digest=\"#{@source_account.remote_followers_hash(@inbox_url)}\", url=\"#{account_followers_synchronization_url(@source_account)}\"" + "collectionId=\"#{ActivityPub::TagManager.instance.followers_uri_for(@source_account)}\", digest=\"#{@source_account.remote_followers_hash(@inbox_url)}\", url=\"#{account_followers_synchronization_url(@source_account)}\"" end def perform_request diff --git a/app/workers/activitypub/distribution_worker.rb b/app/workers/activitypub/distribution_worker.rb index 575e110257a577..125278fa51b5ec 100644 --- a/app/workers/activitypub/distribution_worker.rb +++ b/app/workers/activitypub/distribution_worker.rb @@ -1,6 +1,11 @@ # frozen_string_literal: true class ActivityPub::DistributionWorker < ActivityPub::RawDistributionWorker + # Skip followers synchronization for accounts with a large number of followers, + # as this is expensive and people with very large amounts of followers + # necessarily have less control over them to begin with + MAX_FOLLOWERS_FOR_SYNCHRONIZATION = 25_000 + # Distribute a new status or an edit of a status to all the places # where the status is supposed to go or where it was interacted with def perform(status_id) @@ -27,6 +32,6 @@ def activity end def options - { 'synchronize_followers' => @status.private_visibility? } + { 'synchronize_followers' => @status.private_visibility? && @account.followers_count < MAX_FOLLOWERS_FOR_SYNCHRONIZATION } end end diff --git a/app/workers/activitypub/fetch_all_replies_worker.rb b/app/workers/activitypub/fetch_all_replies_worker.rb index 128bfe7e8af5d3..2e91a3e95b8ae3 100644 --- a/app/workers/activitypub/fetch_all_replies_worker.rb +++ b/app/workers/activitypub/fetch_all_replies_worker.rb @@ -11,9 +11,10 @@ class ActivityPub::FetchAllRepliesWorker sidekiq_options queue: 'pull', retry: 3 - # Global max replies to fetch per request (all replies, recursively) - MAX_REPLIES = (ENV['FETCH_REPLIES_MAX_GLOBAL'] || 1000).to_i - MAX_PAGES = (ENV['FETCH_REPLIES_MAX_PAGES'] || 500).to_i + # Max number of replies to fetch - total, recursively through a whole reply tree + MAX_REPLIES = 1000 + # Max number of replies Collection pages to fetch - total + MAX_PAGES = 500 def perform(root_status_id, options = {}) @batch = WorkerBatch.new(options['batch_id']) diff --git a/app/workers/activitypub/refetch_and_verify_quote_worker.rb b/app/workers/activitypub/refetch_and_verify_quote_worker.rb index 0c7ecd9b2ac021..1a8dd40541e9d7 100644 --- a/app/workers/activitypub/refetch_and_verify_quote_worker.rb +++ b/app/workers/activitypub/refetch_and_verify_quote_worker.rb @@ -5,11 +5,12 @@ class ActivityPub::RefetchAndVerifyQuoteWorker include ExponentialBackoff include JsonLdHelper - sidekiq_options queue: 'pull', retry: 3 + sidekiq_options queue: 'pull', retry: 5 def perform(quote_id, quoted_uri, options = {}) quote = Quote.find(quote_id) ActivityPub::VerifyQuoteService.new.call(quote, fetchable_quoted_uri: quoted_uri, request_id: options[:request_id]) + ::DistributionWorker.perform_async(quote.status_id, { 'update' => true }) if quote.state_previously_changed? rescue ActiveRecord::RecordNotFound # Do nothing true diff --git a/app/workers/generate_annual_report_worker.rb b/app/workers/generate_annual_report_worker.rb index 7094c1ab9c7697..7a02cb7fbf9be4 100644 --- a/app/workers/generate_annual_report_worker.rb +++ b/app/workers/generate_annual_report_worker.rb @@ -4,7 +4,11 @@ class GenerateAnnualReportWorker include Sidekiq::Worker def perform(account_id, year) + async_refresh = AsyncRefresh.new("wrapstodon:#{account_id}:#{year}") + AnnualReport.new(Account.find(account_id), year).generate + + async_refresh&.finish! rescue ActiveRecord::RecordNotFound, ActiveRecord::RecordNotUnique true end diff --git a/app/workers/publish_scheduled_status_worker.rb b/app/workers/publish_scheduled_status_worker.rb index bcf20b49431639..0e34aa77916c90 100644 --- a/app/workers/publish_scheduled_status_worker.rb +++ b/app/workers/publish_scheduled_status_worker.rb @@ -23,6 +23,7 @@ def options_with_objects(options) options.tap do |options_hash| options_hash[:application] = Doorkeeper::Application.find(options_hash.delete(:application_id)) if options[:application_id] options_hash[:thread] = Status.find(options_hash.delete(:in_reply_to_id)) if options_hash[:in_reply_to_id] + options_hash[:quoted_status] = Status.find(options_hash.delete(:quoted_status_id)) if options_hash[:quoted_status_id] end end end diff --git a/config/boot.rb b/config/boot.rb index 70ffe22c04d979..29e8e9e349a239 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true unless ENV.key?('RAILS_ENV') - abort <<~ERROR + abort <<~ERROR # rubocop:disable Rails/Exit The RAILS_ENV environment variable is not set. Please set it correctly depending on context: diff --git a/config/initializers/active_record_encryption.rb b/config/initializers/active_record_encryption.rb index 9ae28e401bab4d..3f9125674c3352 100644 --- a/config/initializers/active_record_encryption.rb +++ b/config/initializers/active_record_encryption.rb @@ -13,7 +13,7 @@ value = ENV.fetch(key, '') if value.blank? - abort <<~MESSAGE + abort <<~MESSAGE # rubocop:disable Rails/Exit Mastodon now requires that these variables are set: @@ -28,7 +28,7 @@ next unless Rails.env.production? && value.end_with?('DO_NOT_USE_IN_PRODUCTION') - abort <<~MESSAGE + abort <<~MESSAGE # rubocop:disable Rails/Exit It looks like you are trying to run Mastodon in production with a #{key} value from the test environment. diff --git a/config/initializers/deprecations.rb b/config/initializers/deprecations.rb index 15c701c9c1d77e..520707e59ff415 100644 --- a/config/initializers/deprecations.rb +++ b/config/initializers/deprecations.rb @@ -14,7 +14,7 @@ In addition, as REDIS_NAMESPACE is being used as a prefix for Elasticsearch, please do not forget to set ES_PREFIX to "#{ENV.fetch('REDIS_NAMESPACE')}". MESSAGE - abort message + abort message # rubocop:disable Rails/Exit end if ENV['MASTODON_USE_LIBVIPS'] == 'false' diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb index 516db258dfa6d8..908acb55033fad 100644 --- a/config/initializers/doorkeeper.rb +++ b/config/initializers/doorkeeper.rb @@ -75,6 +75,7 @@ :'write:accounts', :'write:blocks', :'write:bookmarks', + :'write:collections', :'write:conversations', :'write:favourites', :'write:filters', @@ -89,6 +90,7 @@ :'read:accounts', :'read:blocks', :'read:bookmarks', + :'read:collections', :'read:favourites', :'read:filters', :'read:follows', diff --git a/config/initializers/strong_migrations.rb b/config/initializers/strong_migrations.rb index 59053ca18738ee..d722b16a0f4ffc 100644 --- a/config/initializers/strong_migrations.rb +++ b/config/initializers/strong_migrations.rb @@ -1,4 +1,4 @@ # frozen_string_literal: true StrongMigrations.start_after = 2017_09_24_022025 -StrongMigrations.target_version = 13 +StrongMigrations.target_version = 14 diff --git a/config/initializers/vips.rb b/config/initializers/vips.rb index a539d7035c4e1a..09210d60ebe4f5 100644 --- a/config/initializers/vips.rb +++ b/config/initializers/vips.rb @@ -6,7 +6,7 @@ require 'vips' unless Vips.at_least_libvips?(8, 13) - abort <<~ERROR.squish + abort <<~ERROR.squish # rubocop:disable Rails/Exit Incompatible libvips version (#{Vips.version_string}), please install libvips >= 8.13 ERROR end diff --git a/config/locales-glitch/el.yml b/config/locales-glitch/el.yml index 5545b9d839a404..9029aaa2739b67 100644 --- a/config/locales-glitch/el.yml +++ b/config/locales-glitch/el.yml @@ -6,11 +6,13 @@ el: batch_error: 'Παρουσιάστηκε σφάλμα: %{message}' settings: captcha_enabled: + desc_html: Αυτό βασίζεται σε εξωτερικά σκριπτάκια από το hCaptcha, όπου μπορεί να υπάρχει ανησυχία πέρι ασφάλειας και ιδιωτικότητας. Επιπρόσθετα, μπορεί να κάνει τη διαδικασία εγγραφής πολύ λιγότερο προσβάσιμη για κάποια άτομα (ειδικά αυτά με αναπηρίες). Για αυτούς τους λόγους, παρακαλούμε σκεφτείτε άλλου τρόπους εγγραφής όπως με αποδοχή ή με πρόσκληση. title: Να απαιτείται από νέους χρήστες να λύσουν ένα CAPTCHA για να επιβεβαιώσουν τον λογαριασμό τους hide_followers_count: desc_html: Να μην εμφανίζεται ο αριθμός ακολούθων στα προφίλ χρηστών title: Απόκρυψη αριθμού ακολούθων other: + preamble: Διάφορες ρυθμίσεις του glitch-soc που δεν ταιριάζουν στις άλλες κατηγορίες. title: Άλλες outgoing_spoilers: title: Προειδοποίηση περιεχομένου για εξερχόμενα τουτς @@ -20,6 +22,9 @@ el: show_replies_in_public_timelines: desc_html: Εκτός από τις δημόσιες αυτοαπαντήσεις (νήματα), εμφανίζονται δημόσιες απαντήσεις σε τοπικές και δημόσιες ροές. title: Εμφάνιση απαντήσεων σε δημόσιες ροές + trending_status_cw: + desc_html: Όταν είναι ενεργοποιημένες οι αναρτήσεις σε τάση, επιτρέπει τις αναρτήσεις με προειδοποιήσεις περιεχομένου να είναι κατάλληλες για τάση. Οι αλλαγές σε αυτήν τη ρύθμιση δεν είναι αναδρομικές. + title: Να επιτρέπονται αναρτήσεις με προειδοποιήσεις περιεχομένου (CW) για τάση appearance: localization: glitch_guide_link: https://crowdin.com/project/glitch-soc diff --git a/config/locales-glitch/simple_form.el.yml b/config/locales-glitch/simple_form.el.yml index 70e5d13ab8b4cd..a755194a7f3904 100644 --- a/config/locales-glitch/simple_form.el.yml +++ b/config/locales-glitch/simple_form.el.yml @@ -4,6 +4,9 @@ el: glitch_only: glitch-soc hints: defaults: + setting_default_content_type_html: Κατά τη γραφή των τουτς, υποθέτει ότι είναι γραμμένα σε ανεπεξέργαστη HTML, εκτός αν ορίζεται διαφορετικά + setting_default_content_type_markdown: Κατά τη γραφή των τουτς, υποθέτει ότι χρησιμοποιείται το Markdown για μορφοποίηση πλούσιου κειμένου, εκτός αν ορίζεται διαφορετικά + setting_default_content_type_plain: Κατά τη γραφή των τουτς, υποθέτει ότι είναι απλό κείμενο χωρίς ειδική μορφοποίηση, εκτός αν ορίζεται διαφορετικά (προεπιλεγμένη συμπεριφορά Mastodon) setting_default_language: Η γλώσσα των τουτ σας μπορεί να εντοπιστεί αυτόματα, αλλά δεν είναι πάντα ακριβής setting_show_followers_count: Εμφάνιση του αριθμού ακολούθων σας στο προφίλ σας. Αν αποκρύψετε τον αριθμό των ακολούθων σας, θα είναι κρυμμένος ακόμη και από εσάς, και μερικές εφαρμογές μπορεί να εμφανίσουν έναν αρνητικό αριθμό ακολούθων. labels: @@ -13,3 +16,7 @@ el: setting_default_content_type_markdown: Markdown setting_default_content_type_plain: Απλό κείμενο setting_show_followers_count: Δείξε τον αριθμό των ακολούθων σου + notification_emails: + trending_link: Νέος σύνδεσμος σε τάση απαιτεί αναθεώρηση + trending_status: Νέα ανάρτηση σε τάση απαιτεί αναθεώρηση + trending_tag: Νέα ετικέτα σε τάση απαιτεί αναθεώρηση diff --git a/config/locales-glitch/simple_form.fr-CA.yml b/config/locales-glitch/simple_form.fr-CA.yml index 7518e37f86c14a..f46f2a3b0f66f3 100644 --- a/config/locales-glitch/simple_form.fr-CA.yml +++ b/config/locales-glitch/simple_form.fr-CA.yml @@ -17,7 +17,7 @@ fr-CA: setting_default_content_type_markdown: Markdown setting_default_content_type_plain: Texte brut setting_favourite_modal: Afficher une fenêtre de confirmation avant de mettre en favori un post (s'applique uniquement au mode Glitch) - setting_show_followers_count: Montrez votre nombre d'abonnés + setting_show_followers_count: Montrer votre nombre d'abonné·e·s setting_skin: Thème setting_system_emoji_font: Utiliser la police par défaut du système pour les émojis (s'applique uniquement au mode Glitch) notification_emails: diff --git a/config/locales-glitch/simple_form.fr.yml b/config/locales-glitch/simple_form.fr.yml index 09ce41087b9697..e5e6539e3250f7 100644 --- a/config/locales-glitch/simple_form.fr.yml +++ b/config/locales-glitch/simple_form.fr.yml @@ -17,7 +17,7 @@ fr: setting_default_content_type_markdown: Markdown setting_default_content_type_plain: Texte brut setting_favourite_modal: Demander confirmation avant de mettre un post en favori (s'applique uniquement au mode Glitch) - setting_show_followers_count: Montrez votre nombre d'abonnés + setting_show_followers_count: Montrer votre nombre d'abonné·e·s setting_skin: Thème setting_system_emoji_font: Utiliser la police par défaut du système pour les émojis (s'applique uniquement au mode Glitch) notification_emails: diff --git a/config/locales/activerecord.be.yml b/config/locales/activerecord.be.yml index 3ddb3e328eda96..97f95a0e730cb6 100644 --- a/config/locales/activerecord.be.yml +++ b/config/locales/activerecord.be.yml @@ -32,6 +32,12 @@ be: attributes: url: invalid: нядзейсны URL + collection: + attributes: + collection_items: + too_many: занадта шмат, дазволена не больш за %{count} + tag: + unusable: нельга выкарыстаць doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.ca.yml b/config/locales/activerecord.ca.yml index f53f7f364a0f4e..f92aca17a2e9c5 100644 --- a/config/locales/activerecord.ca.yml +++ b/config/locales/activerecord.ca.yml @@ -32,6 +32,12 @@ ca: attributes: url: invalid: no és un enllaç vàlid + collection: + attributes: + collection_items: + too_many: n'hi ha massa. %{count} és el màxim permès + tag: + unusable: pot no ser emprat doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.cs.yml b/config/locales/activerecord.cs.yml index 38708713d2c65b..44390fb32c47a3 100644 --- a/config/locales/activerecord.cs.yml +++ b/config/locales/activerecord.cs.yml @@ -32,6 +32,12 @@ cs: attributes: url: invalid: není platná URL + collection: + attributes: + collection_items: + too_many: je příliš mnoho, ne více než %{count} je povoleno + tag: + unusable: nesmí být použito doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.cy.yml b/config/locales/activerecord.cy.yml index c201016be61243..cfb20eb9d20281 100644 --- a/config/locales/activerecord.cy.yml +++ b/config/locales/activerecord.cy.yml @@ -32,6 +32,12 @@ cy: attributes: url: invalid: nid yw'n URL dilys + collection: + attributes: + collection_items: + too_many: yn ormod, does dim caniatâd i fwy na %{count} + tag: + unusable: does dim caniatâd i'w ddefnyddio doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.da.yml b/config/locales/activerecord.da.yml index 8c405fba13bc4e..ff4e9d97eace7d 100644 --- a/config/locales/activerecord.da.yml +++ b/config/locales/activerecord.da.yml @@ -32,6 +32,12 @@ da: attributes: url: invalid: er ikke en gyldig URL + collection: + attributes: + collection_items: + too_many: er for mange, ikke mere end %{count} er tilladt + tag: + unusable: må ikke anvendes doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.de.yml b/config/locales/activerecord.de.yml index 4ae7aec5dd61a7..b31317ad36f9ff 100644 --- a/config/locales/activerecord.de.yml +++ b/config/locales/activerecord.de.yml @@ -32,6 +32,12 @@ de: attributes: url: invalid: ist keine gültige URL + collection: + attributes: + collection_items: + too_many: zu viele, mehr als %{count} sind nicht erlaubt + tag: + unusable: darf nicht verwendet werden doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.el.yml b/config/locales/activerecord.el.yml index 4bb344bc6dbe15..d6e6bf570d2268 100644 --- a/config/locales/activerecord.el.yml +++ b/config/locales/activerecord.el.yml @@ -32,6 +32,12 @@ el: attributes: url: invalid: δεν είναι έγκυρο URL + collection: + attributes: + collection_items: + too_many: είναι πολλά, δεν επιτρέπονται περισσότερα από %{count} + tag: + unusable: δεν μπορεί να χρησιμοποιηθεί doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.en-GB.yml b/config/locales/activerecord.en-GB.yml index fe10c5c8a75c83..cd94e0942231e2 100644 --- a/config/locales/activerecord.en-GB.yml +++ b/config/locales/activerecord.en-GB.yml @@ -7,7 +7,7 @@ en-GB: options: Choices user: agreement: Service agreement - email: E-mail address + email: Email address locale: Locale password: Password user/account: @@ -26,12 +26,18 @@ en-GB: fields: fields_with_values_missing_labels: contains values with missing labels username: - invalid: must contain only letters, numbers and underscores + invalid: must contain only letters, numbers, and underscores reserved: is reserved admin/webhook: attributes: url: invalid: is not a valid URL + collection: + attributes: + collection_items: + too_many: are too many, no more than %{count} are allowed + tag: + unusable: may not be used doorkeeper/application: attributes: website: @@ -58,7 +64,7 @@ en-GB: date_of_birth: below_limit: is below the age limit email: - blocked: uses a disallowed e-mail provider + blocked: uses a disallowed email provider unreachable: does not seem to exist role_id: elevated: cannot be higher than your current role @@ -74,4 +80,4 @@ en-GB: webhook: attributes: events: - invalid_permissions: cannot include events you don't have the rights to + invalid_permissions: cannot include events to which you don't have the rights diff --git a/config/locales/activerecord.en.yml b/config/locales/activerecord.en.yml index 6940d589cab995..fb5ccce89e2065 100644 --- a/config/locales/activerecord.en.yml +++ b/config/locales/activerecord.en.yml @@ -32,6 +32,12 @@ en: attributes: url: invalid: is not a valid URL + collection: + attributes: + collection_items: + too_many: are too many, no more than %{count} are allowed + tag: + unusable: may not be used doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.eo.yml b/config/locales/activerecord.eo.yml index 96b7ee89fda4a0..c4bee952fbfad1 100644 --- a/config/locales/activerecord.eo.yml +++ b/config/locales/activerecord.eo.yml @@ -32,6 +32,12 @@ eo: attributes: url: invalid: ne estas valida URL + collection: + attributes: + collection_items: + too_many: estas tro multaj, ne pli ol %{count} estas permesataj + tag: + unusable: ne povus esti uzata doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.es-AR.yml b/config/locales/activerecord.es-AR.yml index 62a409a3535923..927f9e4742a5c4 100644 --- a/config/locales/activerecord.es-AR.yml +++ b/config/locales/activerecord.es-AR.yml @@ -32,6 +32,12 @@ es-AR: attributes: url: invalid: no es una dirección web válida + collection: + attributes: + collection_items: + too_many: son demasiados, no se permiten más de %{count} + tag: + unusable: no podrán ser usadas doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.es-MX.yml b/config/locales/activerecord.es-MX.yml index 02384f1c715409..944197783459db 100644 --- a/config/locales/activerecord.es-MX.yml +++ b/config/locales/activerecord.es-MX.yml @@ -32,6 +32,12 @@ es-MX: attributes: url: invalid: no es una dirección URL válida + collection: + attributes: + collection_items: + too_many: son demasiados, no se permiten más de %{count} + tag: + unusable: podría no utilizarse doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.es.yml b/config/locales/activerecord.es.yml index 94f29365e9129d..cb425fecdde21a 100644 --- a/config/locales/activerecord.es.yml +++ b/config/locales/activerecord.es.yml @@ -32,6 +32,12 @@ es: attributes: url: invalid: no es una URL válida + collection: + attributes: + collection_items: + too_many: son demasiados, no se permiten más de %{count} + tag: + unusable: podría no utilizarse doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.et.yml b/config/locales/activerecord.et.yml index 4e49ae968378ac..a1d4a0c38b81d0 100644 --- a/config/locales/activerecord.et.yml +++ b/config/locales/activerecord.et.yml @@ -32,6 +32,12 @@ et: attributes: url: invalid: pole sobiv URL + collection: + attributes: + collection_items: + too_many: on liiga palju, pole lubatud enam, kui %{count} + tag: + unusable: pole võimalik kasutada doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.eu.yml b/config/locales/activerecord.eu.yml index c0ac1bb338b046..4324106061d9a7 100644 --- a/config/locales/activerecord.eu.yml +++ b/config/locales/activerecord.eu.yml @@ -32,6 +32,12 @@ eu: attributes: url: invalid: ez da baliozko URL bat + collection: + attributes: + collection_items: + too_many: gehiegi dira, eta ez dira onartzen %{count} baino gehiago + tag: + unusable: baliteke ez erabiltzea doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.fa.yml b/config/locales/activerecord.fa.yml index c25ef6495967fb..a02395ba6cc6a1 100644 --- a/config/locales/activerecord.fa.yml +++ b/config/locales/activerecord.fa.yml @@ -32,6 +32,12 @@ fa: attributes: url: invalid: نشانی معتبری نیست + collection: + attributes: + collection_items: + too_many: بیش از حد. بیش از %{count} مجاز نیست + tag: + unusable: ممکن است استفاده نشود doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.fi.yml b/config/locales/activerecord.fi.yml index c731688a1fadd6..3698092b6d588a 100644 --- a/config/locales/activerecord.fi.yml +++ b/config/locales/activerecord.fi.yml @@ -32,6 +32,12 @@ fi: attributes: url: invalid: ei ole kelvollinen verkko-osoite + collection: + attributes: + collection_items: + too_many: on liian monta, enintään %{count} sallitaan + tag: + unusable: ei ole käytettävissä doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.fo.yml b/config/locales/activerecord.fo.yml index ce84a1fffd43d7..3f069cd072f8b4 100644 --- a/config/locales/activerecord.fo.yml +++ b/config/locales/activerecord.fo.yml @@ -32,6 +32,12 @@ fo: attributes: url: invalid: er ikki eitt rætt leinki + collection: + attributes: + collection_items: + too_many: er ov nógvir, einans %{count} eru loyvdir + tag: + unusable: kann ikki brúkast doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.fr-CA.yml b/config/locales/activerecord.fr-CA.yml index a966bb5a8ab4bc..b5db3cc78b8619 100644 --- a/config/locales/activerecord.fr-CA.yml +++ b/config/locales/activerecord.fr-CA.yml @@ -32,6 +32,12 @@ fr-CA: attributes: url: invalid: n’est pas un URL valide + collection: + attributes: + collection_items: + too_many: trop d'éléments, seuls %{count} éléments sont permis + tag: + unusable: ne peut pas être utilisé doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.fr.yml b/config/locales/activerecord.fr.yml index 94d2fa3c6f4847..5d5efae04438bc 100644 --- a/config/locales/activerecord.fr.yml +++ b/config/locales/activerecord.fr.yml @@ -32,6 +32,12 @@ fr: attributes: url: invalid: n’est pas une URL valide + collection: + attributes: + collection_items: + too_many: trop d'éléments, seuls %{count} éléments sont permis + tag: + unusable: ne peut pas être utilisé doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.ga.yml b/config/locales/activerecord.ga.yml index 853c705663f4d1..bc8bbf2fb3b5a5 100644 --- a/config/locales/activerecord.ga.yml +++ b/config/locales/activerecord.ga.yml @@ -32,6 +32,12 @@ ga: attributes: url: invalid: nach URL bailí é + collection: + attributes: + collection_items: + too_many: an iomarca, níl cead níos mó ná %{count} a fháil + tag: + unusable: ní fhéadfar a úsáid doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.gl.yml b/config/locales/activerecord.gl.yml index f4e6725565578c..b836c3e97734ad 100644 --- a/config/locales/activerecord.gl.yml +++ b/config/locales/activerecord.gl.yml @@ -32,6 +32,12 @@ gl: attributes: url: invalid: non é un URL válido + collection: + attributes: + collection_items: + too_many: son demasiados, non se permiten máis de %{count} + tag: + unusable: non se debería usar doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.he.yml b/config/locales/activerecord.he.yml index 7dff17493b3cf1..27bfe360d99b39 100644 --- a/config/locales/activerecord.he.yml +++ b/config/locales/activerecord.he.yml @@ -32,6 +32,12 @@ he: attributes: url: invalid: כתובת לא חוקית + collection: + attributes: + collection_items: + too_many: יש יותר מדי, מותרות %{count} לכל היותר + tag: + unusable: לא שמישות doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.hu.yml b/config/locales/activerecord.hu.yml index cf2f50a9f9b6cf..5db36592c8104b 100644 --- a/config/locales/activerecord.hu.yml +++ b/config/locales/activerecord.hu.yml @@ -32,6 +32,12 @@ hu: attributes: url: invalid: nem érvényes URL + collection: + attributes: + collection_items: + too_many: túl sok, legfeljebb %{count} engedélyezett + tag: + unusable: nem használható doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.is.yml b/config/locales/activerecord.is.yml index cff90a3476d627..7fe6835a1d7857 100644 --- a/config/locales/activerecord.is.yml +++ b/config/locales/activerecord.is.yml @@ -32,6 +32,12 @@ is: attributes: url: invalid: er ekki gild vefslóð + collection: + attributes: + collection_items: + too_many: eru of mörg, ekki fleiri en %{count} eru leyfileg + tag: + unusable: gæti verið ekki notað doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.it.yml b/config/locales/activerecord.it.yml index 9ff385f26e88d2..128ee5e5458171 100644 --- a/config/locales/activerecord.it.yml +++ b/config/locales/activerecord.it.yml @@ -32,6 +32,12 @@ it: attributes: url: invalid: non è un URL valido + collection: + attributes: + collection_items: + too_many: sono troppi, non sono consentiti più di %{count} + tag: + unusable: non può essere utilizzato doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.ko.yml b/config/locales/activerecord.ko.yml index 3aa991734b832a..05be3155831d08 100644 --- a/config/locales/activerecord.ko.yml +++ b/config/locales/activerecord.ko.yml @@ -32,6 +32,10 @@ ko: attributes: url: invalid: 올바른 URL이 아닙니다 + collection: + attributes: + tag: + unusable: 사용할 수 없음 doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.lad.yml b/config/locales/activerecord.lad.yml index 8fd23b53fec7f3..8b1fea0d960b7d 100644 --- a/config/locales/activerecord.lad.yml +++ b/config/locales/activerecord.lad.yml @@ -30,6 +30,10 @@ lad: attributes: url: invalid: no es adreso URL valido + collection: + attributes: + tag: + unusable: no se pueden uzar doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.lt.yml b/config/locales/activerecord.lt.yml index 1eec2782f4ae8e..001243ab901c0b 100644 --- a/config/locales/activerecord.lt.yml +++ b/config/locales/activerecord.lt.yml @@ -6,7 +6,7 @@ lt: expires_at: Galutinė data options: Pasirinkimai user: - agreement: Paslaugos sutartis + agreement: Paslaugų sąlygos email: El. laiško adresas locale: Lokali password: Slaptažodis @@ -32,6 +32,12 @@ lt: attributes: url: invalid: nėra tinkamas URL adresas. + collection: + attributes: + collection_items: + too_many: yra per daug, leidžiama ne daugiau kaip %{count} + tag: + unusable: negali būti panaudota doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.nan.yml b/config/locales/activerecord.nan.yml index b1e9dc67bfa5a1..60f5d107176e5c 100644 --- a/config/locales/activerecord.nan.yml +++ b/config/locales/activerecord.nan.yml @@ -14,3 +14,9 @@ nan: username: 用者ê名 user/invite_request: text: 原因 + errors: + models: + terms_of_service: + attributes: + effective_date: + too_soon: 傷緊ah,著khah uànn佇 %{date} diff --git a/config/locales/activerecord.nl.yml b/config/locales/activerecord.nl.yml index b05d6680e73e81..0d6070ce619425 100644 --- a/config/locales/activerecord.nl.yml +++ b/config/locales/activerecord.nl.yml @@ -32,6 +32,12 @@ nl: attributes: url: invalid: is een ongeldige URL + collection: + attributes: + collection_items: + too_many: zijn er te veel, niet meer dan %{count} zijn toegestaan + tag: + unusable: mag niet worden gebruikt doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.nn.yml b/config/locales/activerecord.nn.yml index ae73057dcc4ae4..c5e3acf2ab6843 100644 --- a/config/locales/activerecord.nn.yml +++ b/config/locales/activerecord.nn.yml @@ -32,6 +32,12 @@ nn: attributes: url: invalid: er ikkje ein gyldig URL + collection: + attributes: + collection_items: + too_many: er for mange, du kan ikkje ha meir enn %{count} + tag: + unusable: kan ikkje brukast doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.pt-BR.yml b/config/locales/activerecord.pt-BR.yml index 8b77ed7e834574..7d4223725a77e2 100644 --- a/config/locales/activerecord.pt-BR.yml +++ b/config/locales/activerecord.pt-BR.yml @@ -32,6 +32,12 @@ pt-BR: attributes: url: invalid: não é uma URL válida + collection: + attributes: + collection_items: + too_many: é demais, não é permitido mais que %{count} + tag: + unusable: não pode ser usado doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.pt-PT.yml b/config/locales/activerecord.pt-PT.yml index 397ed492e528a3..76baf556acb8f6 100644 --- a/config/locales/activerecord.pt-PT.yml +++ b/config/locales/activerecord.pt-PT.yml @@ -32,6 +32,12 @@ pt-PT: attributes: url: invalid: não é um URL válido + collection: + attributes: + collection_items: + too_many: são demasiadas, não são permitidas mais do que %{count} + tag: + unusable: não pode ser utilizada doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.ru.yml b/config/locales/activerecord.ru.yml index 84616eb7673581..5b0f46e382a520 100644 --- a/config/locales/activerecord.ru.yml +++ b/config/locales/activerecord.ru.yml @@ -32,6 +32,12 @@ ru: attributes: url: invalid: не является действительным URL + collection: + attributes: + collection_items: + too_many: слишком много, не разрешено более чем %{count} + tag: + unusable: может не быть использованным doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.sk.yml b/config/locales/activerecord.sk.yml index 68a9e447b9099c..1513142bd2f995 100644 --- a/config/locales/activerecord.sk.yml +++ b/config/locales/activerecord.sk.yml @@ -4,7 +4,7 @@ sk: attributes: poll: expires_at: Trvá do - options: Voľby + options: Možnosti user: agreement: Dohoda o poskytovaní služieb email: E-mailová adresa diff --git a/config/locales/activerecord.sq.yml b/config/locales/activerecord.sq.yml index 2683dd014bf74f..428283a0ef05ac 100644 --- a/config/locales/activerecord.sq.yml +++ b/config/locales/activerecord.sq.yml @@ -32,6 +32,12 @@ sq: attributes: url: invalid: s’është URL e vlefshme + collection: + attributes: + collection_items: + too_many: janë shumë, nuk lejohen më tepër se %{count} + tag: + unusable: mund të mos përdoret dot doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.sv.yml b/config/locales/activerecord.sv.yml index 74b939fda489ac..0b96c16a5c3efe 100644 --- a/config/locales/activerecord.sv.yml +++ b/config/locales/activerecord.sv.yml @@ -32,6 +32,12 @@ sv: attributes: url: invalid: är inte en giltig URL + collection: + attributes: + collection_items: + too_many: är för många, fler än %{count} är inte tillåtet + tag: + unusable: får inte användas doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.tr.yml b/config/locales/activerecord.tr.yml index db9317afa2f343..2f5fb50f2dafa0 100644 --- a/config/locales/activerecord.tr.yml +++ b/config/locales/activerecord.tr.yml @@ -32,6 +32,12 @@ tr: attributes: url: invalid: geçerli bir URL değil + collection: + attributes: + collection_items: + too_many: çok fazla, en fazla %{count} olabilir + tag: + unusable: kullanılamaz doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.vi.yml b/config/locales/activerecord.vi.yml index fe810d94e50027..4a52fbce24a016 100644 --- a/config/locales/activerecord.vi.yml +++ b/config/locales/activerecord.vi.yml @@ -32,6 +32,12 @@ vi: attributes: url: invalid: không phải là một URL hợp lệ + collection: + attributes: + collection_items: + too_many: quá nhiều, không được hơn %{count} + tag: + unusable: không được phép dùng doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.zh-CN.yml b/config/locales/activerecord.zh-CN.yml index 2aadcc8af9226e..6a3e2e7aa93ec9 100644 --- a/config/locales/activerecord.zh-CN.yml +++ b/config/locales/activerecord.zh-CN.yml @@ -32,6 +32,12 @@ zh-CN: attributes: url: invalid: 网址无效 + collection: + attributes: + collection_items: + too_many: 数量过多,不能多于 %{count} 个 + tag: + unusable: 不可使用 doorkeeper/application: attributes: website: diff --git a/config/locales/activerecord.zh-TW.yml b/config/locales/activerecord.zh-TW.yml index f8f630ba3cbc2d..3b61e5ab883bdb 100644 --- a/config/locales/activerecord.zh-TW.yml +++ b/config/locales/activerecord.zh-TW.yml @@ -32,6 +32,12 @@ zh-TW: attributes: url: invalid: 不是有效的 URL + collection: + attributes: + collection_items: + too_many: 數量過多,無法多過 %{count} 個 + tag: + unusable: 無法使用 doorkeeper/application: attributes: website: diff --git a/config/locales/af.yml b/config/locales/af.yml index 89ede096e28daf..4f6cacffea7e6a 100644 --- a/config/locales/af.yml +++ b/config/locales/af.yml @@ -93,8 +93,6 @@ af: status: Status title: Webhoeke webhook: Web-hoek - appearance: - advanced_web_interface_hint: As jy die volle wydte van jou skerm gebruik, laat die gevorderde webkoppelvlak jou toe om kolomme met verskillende soorte inligting langs mekaar te rangskik. So kan jy, byvoorbeeld, tegelyk jou tuistoevoer, kennisgewings, gefedereerde tydlyn en nog ander lyste of hutsetikette dophou. auth: apply_for_account: Doen aansoek om ’n rekening logout: Teken uit diff --git a/config/locales/an.yml b/config/locales/an.yml index 4b9385d5f8fcd7..b7806dd17d938f 100644 --- a/config/locales/an.yml +++ b/config/locales/an.yml @@ -680,7 +680,6 @@ an: title: Excluyir per defecto los usuarios d'a indexación d'os motors de busqueda discovery: follow_recommendations: Recomendacions de cuentas - preamble: Exposar conteniu interesant a la superficie ye fundamental pa incorporar nuevos usuarios que pueden no conoixer a dengún Mastodon. Controla cómo funcionan quantas opcions d'escubrimiento en o tuyo servidor. profile_directory: Directorio de perfils public_timelines: Linias de tiempo publicas publish_statistics: Publicar las estatisticas @@ -877,10 +876,7 @@ an: hint_html: Si quiers migrar d'unatra cuenta a esta, aquí puetz creyar un alias, que ye necesario antes de que puedas empecipiar a mover seguidors d'a cuenta anterior ta esta. Esta acción per ella mesma ye inofensiva y reversible. La migración d'a cuenta s'inicia dende la cuenta antiga. remove: Desvincular alias appearance: - advanced_web_interface: Interficie web abanzada - advanced_web_interface_hint: 'Si deseya utilizar tot l''amplo de pantalla, la interficie web abanzada le permite configurar quantas columnas diferents pa veyer tanta información a lo mesmo tiempo como quiera: Inicio, notificacions, linia de tiempo federada, qualsequier numero de listas y etiquetas.' animations_and_accessibility: Animacions y accesibilidat - confirmation_dialogs: Dialogos de confirmación discovery: Descubrir localization: body: Mastodon ye traduciu con la aduya de voluntarios. @@ -1195,7 +1191,6 @@ an: disabled_account: La tuya cuenta actual no será completament utilizable dimpués. Manimenos, tendrás acceso a la exportación de datos asinas como a la reactivación. followers: Esta acción migrará a totz los seguidores d'a cuenta actual a la nueva cuenta only_redirect_html: Alternativament, nomás puetz meter una redirección en o tuyo perfil. - other_data: No se moverán atros datos automaticament redirect: Lo perfil d'a tuya cuenta actual s'actualizará con un aviso de redirección y será excluyiu d'as busquedas moderation: title: Moderación diff --git a/config/locales/ar.yml b/config/locales/ar.yml index f48e9fc9ef9f15..25c53fadee104e 100644 --- a/config/locales/ar.yml +++ b/config/locales/ar.yml @@ -889,7 +889,6 @@ ar: title: عدم السماح مبدئيا لمحركات البحث بفهرسة الملفات التعريفية للمستخدمين discovery: follow_recommendations: اتبع التوصيات - preamble: يُعد إتاحة رؤية المحتوى المثير للاهتمام أمرًا ضروريًا لجذب مستخدمين جدد قد لا يعرفون أي شخص في Mastodon. تحكم في كيفية عمل ميزات الاكتشاف المختلفة على خادمك الخاص. privacy: الخصوصية profile_directory: دليل الصفحات التعريفية public_timelines: الخيوط الزمنية العامة @@ -1253,10 +1252,7 @@ ar: hint_html: إذا كنت ترغب في الانتقال من حساب آخر إلى هذا الحساب الحالي، يمكنك إنشاء اسم مستعار هنا، والذي هو مطلوب قبل أن تتمكن من المضي قدما في نقل متابِعيك من الحساب القديم إلى هذا الحساب. هذا الإجراء بحد ذاته هو غير مؤذي و قابل للعكس. تتم بداية تهجير الحساب من الحساب القديم. remove: إلغاء ربط الكنية appearance: - advanced_web_interface: واجهة الويب المتقدمة - advanced_web_interface_hint: 'إذا كنت ترغب في استخدام عرض شاشتك بأكمله، فواجهة الويب المتقدمة تسمح لك بضبط العديد من الأعمدة المختلفة لرؤية أكبر قدر من المعلومات التي ترغب فيها في آن واحد: الخيط الرئيسي والإشعارات والخيط الزمني الفدرالي وأي عدد من القوائم والوسوم.' animations_and_accessibility: الإتاحة والحركة - confirmation_dialogs: نوافذ التأكيد discovery: الاستكشاف localization: body: ماستدون يُترجِمه متطوّعون. @@ -1802,7 +1798,6 @@ ar: disabled_account: لن يكون حسابك الحالي قابلا للاستخدام بشكل كامل بعد ذلك، ولكن سيكون لديك حق الوصول إلى تصدير البيانات وكذلك إعادة التنشيط. followers: تقوم هذه العملية بنقل كافة المُتابِعين مِن الحساب الحالي إلى الحساب الجديد only_redirect_html: بدلاً من ذلك، يمكنك أن تضع فقط إعادة توجيه على ملفك الشخصي. - other_data: لن يتم نقل أية بيانات أخرى تلقائيا redirect: سيتم تحديث ملفك التعريفي الحالي مع إشعار إعادة توجيه وسيتم استبعاده من عمليات البحث moderation: title: الإشراف diff --git a/config/locales/ast.yml b/config/locales/ast.yml index 81806695b5e445..9bb87ce8e12761 100644 --- a/config/locales/ast.yml +++ b/config/locales/ast.yml @@ -316,7 +316,6 @@ ast: title: Retención del conteníu discovery: follow_recommendations: Recomendación de cuentes - preamble: L'apaición de conteníu interesante ye fundamental p'atrayer persones nueves que nun conozan nada de Mastodon. Controla'l funcionamientu de delles funciones de descubrimientu d'esti sirvidor. profile_directory: Direutoriu de perfiles public_timelines: Llinies de tiempu públiques publish_statistics: Espublizamientu d'estadístiques @@ -326,6 +325,9 @@ ast: all: A tol mundu disabled: A naide users: A los perfiles llocales + feed_access: + modes: + public: Tol mundu registrations: preamble: Controla quién pue crear una cuenta nel sirvidor. title: Rexistros @@ -429,10 +431,7 @@ ast: aliases: empty: Nun tienes nengún nomatu. appearance: - advanced_web_interface: Interfaz web avanzada - advanced_web_interface_hint: 'Si quies asegúrate de que s''use tol llargor de la pantalla, la interfaz web avanzada permítete configurar columnes estremaes pa ver muncha más información al empar: Aniciu, avisos, llinia de tiempu federada y cualesquier cantidá de llistes y etiquetes.' animations_and_accessibility: Animaciones y accesibilidá - confirmation_dialogs: Diálogos de confirmación discovery: Descubrimientu localization: body: Mastodon tradúcenlu voluntarios, diff --git a/config/locales/az.yml b/config/locales/az.yml index 0fa11278cee7dd..1f35a7ada1e7a6 100644 --- a/config/locales/az.yml +++ b/config/locales/az.yml @@ -118,10 +118,7 @@ az: next_steps: Moderasiya qərarını geri almaq üçün etirazı təsdiqləyə, ya da etirazı yox saya bilərsiniz. subject: "%{username}, %{instance} üzərindəki bir moderasiya qərarına etiraz edir" appearance: - advanced_web_interface: Qabaqcıl veb interfeys - advanced_web_interface_hint: 'Tam ekran enini istifadə etmək istəyirsinizsə, qabaqcıl veb interfeys, istədiyiniz qədər məlumatı eyni anda görə bilməyiniz üçün bir çox fərqli sütunu konfiqurasiya etməyinizə imkan verir: Əsas ekran, bildirişlər, birləşmiş zaman xətti, istənilən sayda siyahı və mövzu etiketləri.' animations_and_accessibility: Animasiyalar və erişiləbilənlik - confirmation_dialogs: Təsdiq dialoq pəncərələri discovery: Kəşf et localization: guide_link_text: Hər kəs töhfə verə bilər. diff --git a/config/locales/be.yml b/config/locales/be.yml index cd7bf94e1a2338..77d515b37c92a8 100644 --- a/config/locales/be.yml +++ b/config/locales/be.yml @@ -7,6 +7,8 @@ be: hosted_on: Mastodon месціцца на %{domain} title: Пра нас accounts: + errors: + cannot_be_added_to_collections: Гэты ўліковы запіс нельга дадаць у калекцыі. followers: few: Падпісчыка many: Падпісчыкаў @@ -707,7 +709,7 @@ be: created_at: Створана delete_and_resolve: Выдаліць допісы forwarded: Пераслана - forwarded_replies_explanation: Гэтае паведамленне паступіла ад выдаленага карыстальніка і дакранаецца выдаленага змесціва. Яно было накіраванае вам, бо змесціва паведамлення з'яўляецца адказам аднаму з вашых карыстальнікаў. + forwarded_replies_explanation: Гэтае паведамленне паступіла ад карыстальніка з іншага сервера і дакранаецца змесціва адтуль. Яно было накіраванае вам, бо змесціва паведамлення з'яўляецца адказам аднаму з вашых карыстальнікаў. forwarded_to: Пераслана на %{domain} mark_as_resolved: Пазначыць як вырашаную mark_as_sensitive: Пазначыць як далікатны @@ -824,6 +826,8 @@ be: view_dashboard_description: Дазваляе праглядаць панэль кіравання і розныя метрыкі view_devops: DevOps view_devops_description: Дае доступ да панэляў кіравання Sidekiq і pgHero + view_feeds: Глядзець жывую і тэматычныя стужкі + view_feeds_description: Даць карыстальнікам доступ да жывой і тэматычных стужак, незалежна ад налад сервера title: Ролі rules: add_new: Дадаць правіла @@ -865,17 +869,28 @@ be: title: Перадвызначана выключыць карыстальнікаў з індэксацыі пашуковымі рухавікамі discovery: follow_recommendations: Выконвайце рэкамендацыі - preamble: Прадстаўленне цікавага кантэнту дапамагае прыцягнуць новых карыстальнікаў, якія могуць не ведаць нікога на Mastodon. Кантралюйце працу розных функцый выяўлення на вашым серверы. + preamble: Паказ цікавага кантэнту карысны ў прывабліванні новых карыстальнікаў, якія могуць нікога не ведаць у Mastodon. Кантралюйце, як розныя функцыі выяўлення працуюць на Вашым серверы. privacy: Прыватнасць profile_directory: Дырэкторыя профіляў public_timelines: Публічная паслядоўнасць публікацый publish_statistics: Апублікаваць статыстыку title: Выяўленне trends: Трэнды + wrapstodon: Вынікадон domain_blocks: all: Для ўсіх disabled: Нікому users: Лакальным карыстальнікам, якія ўвайшлі + feed_access: + modes: + authenticated: Толькі аўтэнтыфікаваныя карыстальнікі + disabled: Запатрабаваць адмысловую ролю карыстальніка + public: Усе + landing_page: + values: + about: Падрабязна + local_feed: Тутэйшая стужка + trends: Трэнды registrations: moderation_recommandation: Пераканайцеся, што ў вас ёсць адэкватная і аператыўная каманда мадэратараў, перш чым адчыняць рэгістрацыю для ўсіх жадаючых! preamble: Кантралюйце, хто можа ствараць уліковы запіс на вашым серверы. @@ -929,6 +944,7 @@ be: no_status_selected: Ніводная публікацыя не была зменена, бо ніводная не была выбрана open: Адкрыць допіс original_status: Зыходны допіс + quotes: Цытаты reblogs: Рэпосты replied_to_html: Адказ карыстальніку %{acct_link} status_changed: Допіс зменены @@ -936,6 +952,7 @@ be: title: Допісы карыстальніка - @%{name} trending: Трэндавае view_publicly: Глядзець публічна + view_quoted_post: Паглядзець цытаваны допіс visibility: Бачнасць with_media: З медыя strikes: @@ -1220,10 +1237,10 @@ be: hint_html: Калі вы хочаце перайсці з іншага ўліковага запісу ў гэты, то тут вы можаце стварыць псеўданім, каб перамясціць падпісчыкаў са старога ўліковага запісу ў гэты. Гэта дзеянне з'яўляецца бясшкодным і зварачальным. Перанос уліковага запісу пачынаецца са старога ўліковага запісу. remove: Адмацаваць псеўданім appearance: - advanced_web_interface: Пашыраны вэб-інтэрфейс - advanced_web_interface_hint: 'Калі вы хочаце выкарыстоўваць усю шырыню экрана, пашыраны вэб-інтэрфейс дазваляе вам наладзіць мноства розных слупкоў, каб бачыць столькі інфармацыі, колькі вам трэба: галоўная, апавяшчэнні, глабальная стужка, любая колькасць спісаў і хэштэгаў.' + advanced_settings: Дадатковыя налады animations_and_accessibility: Анімацыі і даступнасць - confirmation_dialogs: Дыялогавыя вокны пацвярджэння + boosting_preferences: Налады пашырэння + boosting_preferences_info_html: "Падказка: незалежна ад налад, камбінацыя Shift + Клік мышы па іконцы %{icon} пашырыць допіс імгненна." discovery: Адкрыцці localization: body: Mastodon перакладаецца добраахвотнікамі. @@ -1665,6 +1682,13 @@ be: expires_at: Дзее да uses: Выкарыстанні title: Запрасіць людзей + link_preview: + author_html: Ад %{name} + potentially_sensitive_content: + action: Націсніце, каб паказаць + confirm_visit: Вы ўпэўненыя, што хочаце адкрыць гэту спасылку? + hide_button: Схаваць + label: Патэнцыйна далікатны кантэнт lists: errors: limit: Вы дасягнулі макс. колькасці спісаў @@ -1729,7 +1753,7 @@ be: disabled_account: Пасля гэтага ваш бягучы ўліковы запіс не будзе цалкам даступны. Аднак у вас будзе доступ да экспарту даных, а таксама да паўторнай актывацыі. followers: Гэтае дзеянне будзе «пераносіць» усіх падпісчыкаў з бягучага ўліковага запісу на новы only_redirect_html: Альбо вы можаце толькі наладзіць перанакіраванне на ваш профіль. - other_data: Ніякія іншыя даныя не будуць перамешчаны аўтаматычна + other_data: Сістэма не будзе аўтаматычна перамяшчаць астатнія даныя (у тым ліку Вашы допісы і спіс уліковых запісаў, на якія Вы падпісаліся) redirect: Профіль вашага бягучага ўліковага запісу будзе абноўлены з паведамленнем аб перанакіраванні і выключаны з пошуку moderation: title: Мадэрацыя @@ -1763,16 +1787,22 @@ be: body: 'Вас згадаў %{name} у:' subject: Вас згадаў %{name} title: Новае згадванне + moderation_warning: + subject: Вы атрымалі папярэджанне ад мадэратараў poll: subject: Апытанне ад %{name} скончылася quote: body: 'Ваш допіс працытаваў карыстальнік %{name}:' subject: Карыстальнік %{name} працытаваў Ваш допіс title: Цытаваць + quoted_update: + subject: "%{name} адрэдагаваў допіс, як Вы цытавалі" reblog: body: "%{name} пашырыў(-ла) Ваш допіс:" subject: "%{name} пашырыў ваш допіс" title: Новае пашырэнне + severed_relationships: + subject: Вы страцілі сувязі праз рашэнне мадэратараў status: subject: Новы допіс ад %{name} update: @@ -1981,6 +2011,9 @@ be: other: "%{count} відэафайла" boosted_from_html: Пашырыў уліковы запіс %{acct_link} content_warning: 'Папярэджанне аб змесціве: %{warning}' + content_warnings: + hide: Схаваць допіс + show: Паказаць усё роўна default_language: Такая, што і мова інтэрфэйсу disallowed_hashtags: few: 'змяшчае недазволеныя хэштэгі: %{tags}' @@ -1991,16 +2024,22 @@ be: errors: in_reply_not_found: Здаецца, допіс, на які вы спрабуеце адказаць, не існуе. quoted_status_not_found: Выглядае, што допісу, які Вы спрабуеце цытаваць, не існуе. + quoted_user_not_mentioned: Немагчыма цытаваць незгаданага карыстальніка ў допісе прыватнага згадвання. over_character_limit: перавышаная колькасць сімвалаў у %{max} pin_errors: direct: Допісы, бачныя толькі згаданым карыстальнікам, нельга замацаваць limit: Вы ўжо замацавалі максімальную колькасць допісаў ownership: Немагчыма замацаваць чужы допіс reblog: Немагчыма замацаваць пашырэнне + quote_error: + not_available: Допіс недаступны + pending_approval: Допіс чакае ўхвалення + revoked: Аўтар выдаліў допіс quote_policies: followers: Толькі падпісчыкі nobody: Толькі я public: Усе + quote_post_author: Цытаваў(-ла) допіс %{acct} title: '%{name}: "%{quote}"' visibilities: direct: Прыватнае згадванне @@ -2237,3 +2276,6 @@ be: not_supported: Гэты браўзер не падтрымлівае ключы бяспекі otp_required: Каб выкарыстоўваць ключы бяспекі, спачатку ўключыце двухфактарную аўтэнтыфікацыю. registered_on: Зарэгістраваны %{date} + wrapstodon: + description: Паглядзіце, як %{name} карыстаў(-ла)ся Mastodon у гэтым годзе! + title: Вынікадон %{year} для %{name} diff --git a/config/locales/bg.yml b/config/locales/bg.yml index 309d634914e72c..2f77de72628a01 100644 --- a/config/locales/bg.yml +++ b/config/locales/bg.yml @@ -825,7 +825,6 @@ bg: title: По подразбиране изключете индексирането от търсачки за вашите потребители discovery: follow_recommendations: Препоръки за следване - preamble: За потребители, които са нови и не познават никого в Mastodon, показването на интересно съдържание е ключово. Настройте начина, по който различни функции по откриване на съдържание работят на вашия сървър. privacy: Поверителност profile_directory: Указател на профила public_timelines: Публични хронологии @@ -836,6 +835,14 @@ bg: all: До всеки disabled: До никого users: До влезнали локални потребители + feed_access: + modes: + authenticated: Само удостоверени потребители + disabled: Изисква особена потребителска роля + public: Всеки + landing_page: + values: + trends: Пламенности registrations: moderation_recommandation: Уверете се, че имате адекватен и реактивен модераторски екип преди да отворите регистриранията за всеки! preamble: Управлява кой може да създава акаунт на сървъра ви. @@ -889,6 +896,7 @@ bg: no_status_selected: Няма промяна, тъй като няма избрани публикации open: Отваряне на публикация original_status: Първообразна публикация + quotes: Цитати reblogs: Блогване пак replied_to_html: Отговорено до %{acct_link} status_changed: Публикацията променена @@ -896,6 +904,7 @@ bg: title: Публикации на акаунт - @%{name} trending: Изгряващи view_publicly: Преглед като публично + view_quoted_post: Преглед на цитираната публикация visibility: Видимост with_media: С мултимедия strikes: @@ -1166,10 +1175,10 @@ bg: hint_html: Ако желаете да се преместите от друг акаунт към този, тук можете да създадете псевдоним, което се изисква преди да можете да пристъпите към преместване на последователите си от стария акаунт към този. Това действие е безопасно и възстановимо. Миграцията към новия акаунт се инициира от стария акаунт. remove: Разкачвне на псевдонима appearance: - advanced_web_interface: Разширен уеб интерфейс - advanced_web_interface_hint: 'Ако искате да употребявате цялата ширина на екрана си, разширеният уеб интерфейс ви позволява да настроите най-различни колони, за да виждате едновременно множество сведения, колкото искате: Начало, известия, федеративен инфоканал, произволен брой списъци и хаштагове.' + advanced_settings: Разширени настройки animations_and_accessibility: Анимация и достъпност - confirmation_dialogs: Диалогов прозорец за потвърждение + boosting_preferences: Настройки за подсилване + boosting_preferences_info_html: "Съвет: Без значение от настройките, Shift + Щрак върху иконата %{icon} Подсилване веднага ще подсили." discovery: Откриване localization: body: Mastodon е преведено от доброволци. @@ -1571,6 +1580,13 @@ bg: expires_at: Изтича на uses: Използвания title: Поканете хора + link_preview: + author_html: От %{name} + potentially_sensitive_content: + action: Щракване за показване + confirm_visit: Наистина ли искате да отворите тази връзка? + hide_button: Скриване + label: Възможно деликатно съдържание lists: errors: limit: Достигнахте максималния брой списъци @@ -1635,7 +1651,6 @@ bg: disabled_account: След това, текущият ви акаунт няма да бъде напълно използваем. Но ще имате достъп до експортиране на данни от него и до повторното му активиране. followers: Това действие ще премести всички последователи от текущия към новия акаунт only_redirect_html: Иначе, можете да просто да оставите пренасочване в своя профил. - other_data: Други данни няма да бъдат автоматично пренасочени redirect: Текущият профил на вашия акаунт ще бъде обновен с бележка за пренасочване и няма да се появява при търсене moderation: title: Mодериране @@ -1723,6 +1738,9 @@ bg: self_vote: Не може да гласувате в свои анкети too_few_options: трябва да има повече от един елемент too_many_options: не може да съдържа повече от %{max} елемента + vote: Гласувам + posting_defaults: + explanation: Тези настройки ще се употребяват като стандартни, когато създавате нови публикации, но може да ги редактирате за всяка публикация в редактора. preferences: other: Друго posting_defaults: По подразбиране за публикации @@ -1878,6 +1896,9 @@ bg: other: "%{count} видеозаписа" boosted_from_html: Раздуто от %{acct_link} content_warning: 'Предупреждение за съдържание: %{warning}' + content_warnings: + hide: Скриване на публ. + show: Показване на още default_language: Същият като езика на интерфейса disallowed_hashtags: one: 'съдържа непозволен хаштаг: %{tags}' @@ -1892,9 +1913,22 @@ bg: limit: Вече сте закачили максималния брой публикации ownership: Публикация на някого другиго не може да бъде закачена reblog: Раздуване не може да бъде закачано + quote_error: + not_available: Неналична публикация + pending_approval: Публикацията чака одобрение + revoked: Премахната публикация от автора + quote_policies: + followers: Само последователи + nobody: Само аз + public: Някой + quote_post_author: Цитирах публикация от %{acct} title: "%{name}: „%{quote}“" visibilities: + direct: Частно споменаване + private: Само последователи public: Публично + public_long: Всеки във и извън Mastodon + unlisted: Тиха публика statuses_cleanup: enabled: Автоматично изтриване на стари публикации enabled_hint: От само себе си трие публикациите ви, щом достигнат указания възрастов праг, освен ако не съвпаднат с някое от изключенията долу diff --git a/config/locales/br.yml b/config/locales/br.yml index c6cb9152522623..30f4a50fbcedf6 100644 --- a/config/locales/br.yml +++ b/config/locales/br.yml @@ -145,6 +145,7 @@ br: destroy_custom_emoji_html: Dilamet eo bet ar fromlun %{target} gant %{name} destroy_email_domain_block_html: Distanket eo bet an domani postel %{target} gant %{name} destroy_status_html: Dilamet eo bet toud %{target} gant %{name} + destroy_user_role_html: Dilamet eo bet ar perzh %{target} gant %{name} disable_custom_emoji_html: Diweredekaet eo bet ar fromlun %{target} gant %{name} enable_custom_emoji_html: Gweredekaet eo bet ar fromlun %{target} gant %{name} resend_user_html: Adkaset eo bet ar postel kadarnaat evit %{target} gant %{name} @@ -372,6 +373,7 @@ br: manage_rules: Merañ reolennoù ar servijer title: Diwar-benn appearance: + preamble: Personelaat etrefas web Mastodon. title: Neuz content_retention: danger_zone: Takad dañjer @@ -776,12 +778,13 @@ br: account: Kont account_settings: Arventennoù ar gont aliases: Aliasoù ar gont + appearance: Neuz back: Distreiñ da vMastodon delete: Dilemel ar gont development: Diorren edit_profile: Kemmañ ar profil export: Ezporzhiañ - featured_tags: Gerioù-klik en a-raok + featured_tags: Penngerioù-klik import: Enporzhiañ import_and_export: Enporzhiañ hag ezporzhiañ notifications: Kemennoù dre bostel diff --git a/config/locales/ca.yml b/config/locales/ca.yml index eae71f330c8ac4..baf33daacf2eb0 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -7,6 +7,8 @@ ca: hosted_on: Mastodon allotjat a %{domain} title: Quant a accounts: + errors: + cannot_be_added_to_collections: Aquest compte no es pot afegir a coŀleccions. followers: one: Seguidor other: Seguidors @@ -190,6 +192,7 @@ ca: create_relay: Crea un repetidor create_unavailable_domain: Crea un domini no disponible create_user_role: Crea Rol + create_username_block: Crear regla de nom d'usuari demote_user: Degrada l'usuari destroy_announcement: Esborra l'anunci destroy_canonical_email_block: Elimina el blocatge del correu electrònic @@ -203,6 +206,7 @@ ca: destroy_status: Elimina la publicació destroy_unavailable_domain: Esborra domini no disponible destroy_user_role: Destrueix Rol + destroy_username_block: Eliminar regla de nom d'usuari disable_2fa_user: Desactiva 2FA disable_custom_emoji: Desactiva l'emoji personalitzat disable_relay: Desactiva el repetidor @@ -237,6 +241,7 @@ ca: update_report: Actualitza l'informe update_status: Actualitza l'estat update_user_role: Actualitza Rol + update_username_block: Actualitzar regla de nom d'usuari actions: approve_appeal_html: "%{name} ha aprovat l'apel·lació a la decisió de moderació de %{target}" approve_user_html: "%{name} ha aprovat el registre de %{target}" @@ -583,6 +588,7 @@ ca: created_msg: S'ha creat la nota de moderació d'instància. description_html: Mireu i deixeu notes per als altres moderadors i per a un mateix destroyed_msg: S'ha esborrat la nota de moderació d'instància. + placeholder: Informació sobre aquesta instància, accions preses o qualsevol altra cosa que us pugui ajudar a moderar-la en el futur. title: Notes de moderació private_comment: Comentari privat public_comment: Comentari públic @@ -829,7 +835,6 @@ ca: title: Exclou per defecte els usuaris de la indexació dels motors de cerca discovery: follow_recommendations: Seguir les recomanacions - preamble: L'aparició de contingut interessant és fonamental per atraure els nous usuaris que podrien no saber res de Mastodon. Controla com funcionen diverses opcions de descobriment en el teu servidor. privacy: Privacitat profile_directory: Directori de perfils public_timelines: Línies de temps públiques @@ -893,6 +898,7 @@ ca: no_status_selected: No s’han canviat els estatus perquè cap no ha estat seleccionat open: Obre la publicació original_status: Publicació original + quotes: Cites reblogs: Impulsos replied_to_html: En resposta a %{acct_link} status_changed: Publicació canviada @@ -900,6 +906,7 @@ ca: title: Publicacions del compte - @%{name} trending: Tendència view_publicly: Vegeu en públic + view_quoted_post: Veure la publicació citada visibility: Visibilitat with_media: Amb contingut multimèdia strikes: @@ -1083,6 +1090,24 @@ ca: other: Emprat per %{count} persones en la darrera setmana title: Recomanacions i tendències trending: Tendència + username_blocks: + add_new: Afegeix-ne una de nova + comparison: + contains: Conté + equals: És igual a + contains_html: Conté %{string} + created_msg: Creada una regla de nom d'usuari + delete: Elimina + edit: + title: Editar regla de nom d'usuari + matches_exactly_html: És igual a %{string} + new: + create: Crea una regla + title: Crear nova regla de nom d'usuari + no_username_block_selected: No s'ha canviat cap regla de nom d'usuari perquè no se n'havia seleccionat cap + not_permitted: No permés + title: Regles de nom d'usuari + updated_msg: Actualitzada una regla de nom d'usuari warning_presets: add_new: Afegir-ne un de nou delete: Elimina @@ -1155,10 +1180,7 @@ ca: hint_html: Si et vols moure des d'un altre compte a aquest, aquí pots crear un àlies, el qual és requerit abans que puguis procedir a moure els seguidors del compte vell a aquest. Aquesta acció és per si mateixa inofensiva i reversible. La migració del compte és iniciada des de'l compte vell. remove: Desvincula l'àlies appearance: - advanced_web_interface: Interfície web avançada - advanced_web_interface_hint: 'Si vols fer ús de tota l''amplada de la teva pantalla, la interfície web avançada et permet configurar diverses columnes per a veure molta més informació al mateix temps: Inici, notificacions, línia de temps federada i qualsevol quantitat de llistes i etiquetes.' animations_and_accessibility: Animacions i accessibilitat - confirmation_dialogs: Diàlegs de confirmació discovery: Descobriment localization: body: Mastodon és traduït per voluntaris. @@ -1560,6 +1582,13 @@ ca: expires_at: Caduca uses: Usos title: Convidar persones + link_preview: + author_html: Per %{name} + potentially_sensitive_content: + action: Feu clic per a mostrar + confirm_visit: Esteu segur que voleu obrir aquest enllaç? + hide_button: Amaga + label: Contingut potencialment sensible lists: errors: limit: Has assolit la quantitat màxima de llistes @@ -1624,7 +1653,6 @@ ca: disabled_account: El teu compte actual no serà plenament utilitzable després. Tanmateix, tindràs accés a exportació de dades així com reactivació. followers: Aquesta acció mourà tots els seguidors des de l'actual al compte nou only_redirect_html: Alternativament, pots posar només una redirecció en el teu perfil. - other_data: Cap altre dada serà moguda automàticament redirect: El perfil del teu compte actual serà actualitzat amb un avís de redirecció i serà exclòs de les cerques moderation: title: Moderació @@ -1658,16 +1686,22 @@ ca: body: "%{name} t'ha mencionat en:" subject: "%{name} t'ha mencionat" title: Nova menció + moderation_warning: + subject: Heu rebut un avís de moderació poll: subject: Ha finalitzat l'enquesta de %{name} quote: body: 'La vostra publicació ha estat citada per %{name}:' subject: "%{name} ha citat la vostra publicació" title: Nova citació + quoted_update: + subject: "%{name} ha editat una publicació que heu citat" reblog: body: "%{name} ha impulsat el teu estat:" subject: "%{name} ha impulsat el teu estat" title: Nou impuls + severed_relationships: + subject: Heu perdut connexions degut a una decisió de moderació status: subject: "%{name} ha publicat" update: @@ -1712,6 +1746,7 @@ ca: self_vote: No pots votar en les teves propies enquestes too_few_options: ha de tenir més d'una opció too_many_options: no pot contenir més de %{max} opcions + vote: Voteu preferences: other: Altres posting_defaults: Valors per defecte de publicació @@ -1867,6 +1902,9 @@ ca: other: "%{count} vídeos" boosted_from_html: Impulsat des de %{acct_link} content_warning: 'Avís de contingut: %{warning}' + content_warnings: + hide: Amaga la publicació + show: Mostra'n més default_language: El mateix que a la llengua de la interfície disallowed_hashtags: one: 'conté una etiqueta no permesa: %{tags}' @@ -1881,13 +1919,19 @@ ca: limit: Ja has fixat el màxim nombre de tuts ownership: No es pot fixar el tut d'algú altre reblog: No es pot fixar un impuls + quote_error: + not_available: Publicació no disponible + pending_approval: Publicació pendent + revoked: Publicació eliminada per l'autor quote_policies: followers: Només seguidors nobody: Només jo public: Qualsevol + quote_post_author: S'ha citat una publicació de @%{acct} title: '%{name}: "%{quote}"' visibilities: direct: Menció privada + private: Només seguidors public: Públic public_long: Tothom dins o fora Mastodon unlisted_long: Amagat dels resultats de cerca de Mastodon, de les tendències i de les línies temporals @@ -2117,3 +2161,6 @@ ca: not_supported: Aquest navegador no suporta claus de seguretat otp_required: Per a usar claus de seguretat, activeu primer l'autenticació de dos factors. registered_on: Registrat en %{date} + wrapstodon: + description: Mira com %{name} ha fet servir Mastodon enguany. + title: Wrapstodon %{year} per a %{name} diff --git a/config/locales/ckb.yml b/config/locales/ckb.yml index 891453a7cef39d..4129efd045971f 100644 --- a/config/locales/ckb.yml +++ b/config/locales/ckb.yml @@ -545,10 +545,7 @@ ckb: hint_html: ئەگەر دەتەوێت لە هەژمارەیەکی ترەوە بگوێزریتەوە بۆ ئەم هەژمارە، لێرەدا دەتوانیت نازناوێک دروست بکەیت، پێش ئەوەی ئەوە بەردەوام بیت لە گواستنەوەی لە هەژمارە کۆنەکە بۆ ئەم هەژمارە پێویستە. ئەم کردەوەیە خۆی لە خۆیدا بێ زەرە و ناگەڕێتەوەگواستنەوەی لە هەژمارەی کۆنە بۆ هەژمارەی نوێ دەستی پێکردووە. remove: سڕینەوەی پەیوەندی ناز ناو appearance: - advanced_web_interface: روخساری پێشکەوتوو - advanced_web_interface_hint: 'ئەگەر دەتەوێت پانی شاشەکە بەکاربێنیت، دەتوانی بە یارمەتی ڕووکاری پێشکەوتوو چەندین ستوونی جیاواز ڕێکبخەیت بۆ بینینی زانیاری زیاتر لە هەمان کات کە دەتەوێت بیبینیت: نووسراوەکانی نووسەرانی دیکە، ئاگانامەکان، پێرستی نووسراوەکانی هەموو شوێنێک، وە هەر ژمارەیەک لە لیستەکان و هاشتاگەکان.' animations_and_accessibility: ئەنیمەیشن و توانایی دەستپێگەیشتن - confirmation_dialogs: پەیامەکانی پەسەندکراو discovery: دۆزینەوە localization: body: ماستۆدۆن لەلایەن خۆبەخشەوە وەردەگێڕێت. @@ -776,7 +773,6 @@ ckb: disabled_account: هەژمارەی ئێستات دوای ئەوە بە تەواوی بەکارناهیێت. هەرچۆنێک بێت، تۆ دەستگەیشتنت دەبێت بۆ ناردنەدەرەوەی داتا و هەروەها دووبارە کاراکردنەوە. followers: ئەم کردارە هەموو شوێنکەوتوانی هەژمارەی ئێستا دەگوازێتەوە بۆ هەژمارەی نوێ only_redirect_html: ئێوە دەتانن هەژمارەکەی خۆتان بیخەنە سەر هەژمارەیەکی دیکە. - other_data: هیچ داتایەکی تر بە خۆکارانە ناگوێزرێتەوە redirect: پرۆفایلی هەژمارەی ئێستات بە ئاگادارییەکی ئاراستەکەراوە نوێ دەکرێتەوە و دووردەکەویت لە گەڕانەکان moderation: title: بەڕێوەبردن diff --git a/config/locales/co.yml b/config/locales/co.yml index 01dcc19ba3adee..e86e96846e32d1 100644 --- a/config/locales/co.yml +++ b/config/locales/co.yml @@ -508,10 +508,7 @@ co: hint_html: Per traslucà da un altru contu à questu, quì pudete creà un pseudonimu o "alias", riquisitu per trasferì l'abbunati da u vechju contu à u novu. St'azzione sola ùn face nunda è pò esse annullata senza prublemi. A migrazione hè principiata dapoi u vechju contu. remove: Sguassà u pseudonimu appearance: - advanced_web_interface: Interfaccia web avanzata - advanced_web_interface_hint: 'S''è voi vulete fà usu di a larghezza sana di u vostru screnu, l''interfaccia web avanzata vi permette di cunfigurà parechje culonne sfarente per vede tutta l''infurmazione chì vulete vede in listessu tempu: Accolta, nutificazione, linea pubblica, è tutti l''hashtag è liste chì vulete.' animations_and_accessibility: Animazione è accessibilità - confirmation_dialogs: Pop-up di cunfirmazione discovery: Scuperta localization: body: Mastodon hè tradottu da una squadra di vuluntari. @@ -741,7 +738,6 @@ co: disabled_account: U contu attuale ùn puderà più esse utilizatu dop'à st'azzione. Però, puderete accede à a spurtazione di dati o riattivà u contu. followers: St'azzione hà da spiazzà tutti l'abbunati di u contu attuale nant'à u novu contu only_redirect_html: Pudete ancu mette solu una ridirezzione nant'à u vostru prufile. - other_data: L'altri dati ùn saranu micca autumaticamente trasferiti redirect: U prufile di u vostru contu attuale sarà messu à ghjornu cù una nutificazione di ridirezzione è sarà sclusu di e ricerche moderation: title: Muderazione diff --git a/config/locales/cs.yml b/config/locales/cs.yml index fd3b579781703e..90f118e82456dc 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -7,6 +7,8 @@ cs: hosted_on: Mastodon na doméně %{domain} title: O službě accounts: + errors: + cannot_be_added_to_collections: Tento účet nelze přidat do kolekcí. followers: few: Sledující many: Sledujících @@ -824,6 +826,8 @@ cs: view_dashboard_description: Umožňuje uživatelům přístup k ovládacímu panelu a různým metrikám view_devops: DevOps view_devops_description: Umožňuje uživatelům přístup k ovládacím panelům Sidekiq a pgHero + view_feeds: Zobrazit živé a tematické kanály + view_feeds_description: Umožňuje uživatelům přístup k živým a tematickým kanálům bez ohledu na nastavení serveru title: Role rules: add_new: Přidat pravidlo @@ -865,17 +869,27 @@ cs: title: Odhlásit uživatele ze standardního indexování vyhledávačů discovery: follow_recommendations: Doporučená sledování - preamble: Povrchový zajímavý obsah je užitečný pro zapojení nových uživatelů, kteří možná neznají žádného Mastodona. Mějte pod kontrolou, jak různé objevovací funkce fungují na vašem serveru. + preamble: Zobrazování zajímavého obsahu je užitečné pro zapojovní nových uživatelů, kteří nemusí na Mastodonu nikoho znát. Kontrolujte, jak fungují různé funkce objevování obsahu na vašem serveru. privacy: Soukromí profile_directory: Adresář profilů public_timelines: Veřejné časové osy publish_statistics: Zveřejnit statistiku - title: Objevujte + title: Objevování trends: Trendy domain_blocks: all: Všem disabled: Nikomu users: Přihlášeným místním uživatelům + feed_access: + modes: + authenticated: Pouze autentifikovaní uživatelé + disabled: Vyžadovat specifickou uživatelskou roli + public: Všichni + landing_page: + values: + about: O službě + local_feed: Místní kanál + trends: Trendy registrations: moderation_recommandation: Před otevřením registrací všem se ujistěte, že máte vhodný a reaktivní moderační tým! preamble: Mějte pod kontrolou, kdo může vytvořit účet na vašem serveru. @@ -929,6 +943,7 @@ cs: no_status_selected: Nebyly změněny žádné příspěvky, neboť žádné nebyly vybrány open: Otevřít příspěvek original_status: Původní příspěvek + quotes: Citace reblogs: Boosty replied_to_html: Odpověděl %{acct_link} status_changed: Příspěvek změněn @@ -936,6 +951,7 @@ cs: title: Příspěvky na účtu - @%{name} trending: Populární view_publicly: Zobrazit veřejně + view_quoted_post: Zobrazit citovaný příspěvek visibility: Viditelnost with_media: S médii strikes: @@ -1220,10 +1236,10 @@ cs: hint_html: Chcete-li se přesunout z jiného účtu na tento, můžete si zde vytvořit alias, který je vyžadován předtím, než můžete pokračovat přesunem sledujících ze starého účtu na tento. Tato akce sama o sobě je neškodná a vratná. Přesun účtu se zahajuje ze starého účtu. remove: Odpojit alias appearance: - advanced_web_interface: Pokročilé webové rozhraní - advanced_web_interface_hint: 'Chcete-li využít celé šířky vaší obrazovky, dovolí vám pokročilé webové rozhraní nastavit si mnoho různých sloupců, takže ve stejnou chvíli uvidíte tolik informací, kolik chcete: domovskou časovou osu, oznámení, federovanou časovou osu a libovolný počet seznamů a hashtagů.' + advanced_settings: Pokročilá nastavení animations_and_accessibility: Animace a přístupnost - confirmation_dialogs: Potvrzovací dialogy + boosting_preferences: Předvolby boostování + boosting_preferences_info_html: "Tip: Bez ohledu na nastavení Shift + Klik na ikonu %{icon} Boost okamžitě boostne." discovery: Objevování localization: body: Mastodon je překládán dobrovolníky. @@ -1665,6 +1681,13 @@ cs: expires_at: Vyprší uses: Použití title: Pozvat lidi + link_preview: + author_html: Podle %{name} + potentially_sensitive_content: + action: Kliknutím zobrazíte + confirm_visit: Opravdu chcete otevřít tento odkaz? + hide_button: Skrýt + label: Potenciálně citlivý obsah lists: errors: limit: Dosáhl jste maximálního počtu seznamů @@ -1729,7 +1752,7 @@ cs: disabled_account: Váš aktuální účet nebude poté zcela použitelný. Budete však mít přístup k datovým exportům a budete ho moci znovu aktivovat. followers: Touto akcí přesunete všechny sledující z aktuálního účtu na nový only_redirect_html: Alternativně můžete nastavit pouze přesměrování na váš profil. - other_data: Žádná další data nebudou přesunuta automaticky + other_data: Žádná další data nebudou automaticky přesunuta (včetně vašich příspěvků a seznamu účtů, které sledujete) redirect: Profil vašeho aktuálního účtu bude aktualizován s oznámením o přesměrování a bude vyloučen z výsledků hledání moderation: title: Moderování @@ -1763,16 +1786,22 @@ cs: body: 'Uživatel %{name} vás zmínil v:' subject: Uživatel %{name} vás zmínil title: Nová zmínka + moderation_warning: + subject: Obdrželi jste moderační varování poll: subject: Anketa od %{name} skončila quote: body: 'Váš příspěvek citoval účet %{name}:' subject: "%{name} citovali váš příspěvek" title: Nová citace + quoted_update: + subject: "%{name} upravili příspěvek, který jste citovali" reblog: body: 'Uživatel %{name} boostnul váš příspěvek:' subject: Uživatel %{name} boostnul váš příspěvek title: Nový boost + severed_relationships: + subject: Ztratili jste spojení, kvůli moderačnímu rozhodnutí status: subject: Nový příspěvek od %{name} update: @@ -1981,6 +2010,9 @@ cs: other: "%{count} videí" boosted_from_html: Boostnuto z %{acct_link} content_warning: 'Varování o obsahu: %{warning}' + content_warnings: + hide: Skrýt příspěvek + show: Zobrazit více default_language: Stejný jako jazyk rozhraní disallowed_hashtags: few: 'obsahoval zakázané hashtagy: %{tags}' @@ -1991,16 +2023,22 @@ cs: errors: in_reply_not_found: Příspěvek, na který se pokoušíte odpovědět, neexistuje. quoted_status_not_found: Zdá se, že příspěvek, který se pokoušíte citovat neexistuje. + quoted_user_not_mentioned: Nelze citovat nezmíněného uživatele v soukromé zmínce. over_character_limit: byl překročen limit %{max} znaků pin_errors: direct: Příspěvky viditelné pouze zmíněným uživatelům nelze připnout limit: Už jste si připnuli maximální počet příspěvků ownership: Nelze připnout příspěvek někoho jiného reblog: Boosty nelze připnout + quote_error: + not_available: Příspěvek není dostupný + pending_approval: Příspěvek čeká na schválení + revoked: Příspěvek odstraněn autorem quote_policies: followers: Pouze sledující nobody: Jen já public: Kdokoliv + quote_post_author: Citovali příspěvek od %{acct} title: "%{name}: „%{quote}“" visibilities: direct: Soukromá zmínka @@ -2237,3 +2275,6 @@ cs: not_supported: Tento prohlížeč nepodporuje bezpečnostní klíče otp_required: Pro použití bezpečnostních klíčů prosím nejprve zapněte dvoufázové ověřování. registered_on: Přidán %{date} + wrapstodon: + description: Podívejte se, jak %{name} používali Mastodon v tomto roce! + title: Wrapstodon %{year} pro %{name} diff --git a/config/locales/cy.yml b/config/locales/cy.yml index 7fe0e7dd85f77a..ab45735c671f01 100644 --- a/config/locales/cy.yml +++ b/config/locales/cy.yml @@ -7,6 +7,8 @@ cy: hosted_on: Mastodon wedi ei weinyddu ar %{domain} title: Ynghylch accounts: + errors: + cannot_be_added_to_collections: Does dim modd ychwanegu'r cyfrif hwn at gasgliadau. followers: few: Dilynwyr many: Dilynwyr @@ -852,6 +854,8 @@ cy: view_dashboard_description: Yn galluogi defnyddwyr i gael mynediad i'r bwrdd gwaith a metrigau amrywiol view_devops: DevOps view_devops_description: Yn caniatáu i ddefnyddwyr gael mynediad i fyrddau gwaith Sidekiq a pgHero + view_feeds: Gweld ffrydiau byw a phynciol + view_feeds_description: Yn caniatáu i ddefnyddwyr gael mynediad at y ffrydiau byw a phynciol beth bynnag yw gosodiadau'r gweinydd title: Rolau rules: add_new: Ychwanegu rheol @@ -893,7 +897,7 @@ cy: title: Eithrio defnyddwyr o fynegai peiriannau chwilio, fel rhagosodiad discovery: follow_recommendations: Dilyn yr argymhellion - preamble: Mae amlygu cynnwys diddorol yn allweddol ar gyfer derbyn defnyddwyr newydd nad ydynt efallai'n gyfarwydd ag unrhyw un Mastodon. Rheolwch sut mae nodweddion darganfod amrywiol yn gweithio ar eich gweinydd. + preamble: Mae amlygu cynnwys diddorol yn allweddol wrth ddenu defnyddwyr newydd sydd ddim o bosib yn adnabod neb ar Mastodon. Rheolwch sut mae gwahanol nodweddion darganfod yn gweithio ar eich gweinydd. privacy: Preifatrwydd profile_directory: Cyfeiriadur proffiliau public_timelines: Ffrydiau cyhoeddus @@ -904,6 +908,16 @@ cy: all: I bawb disabled: I neb users: I ddefnyddwyr lleol wedi'u mewngofnodi + feed_access: + modes: + authenticated: Defnyddwyr dilys yn unig + disabled: Gofyn am rôl defnyddiwr penodol + public: Pawb + landing_page: + values: + about: Ynghylch + local_feed: Ffrwd leol + trends: Trendiau registrations: moderation_recommandation: Gwnewch yn siŵr bod gennych chi dîm cymedroli digonol ac adweithiol cyn i chi agor cofrestriadau i bawb! preamble: Rheoli pwy all greu cyfrif ar eich gweinydd. @@ -957,6 +971,7 @@ cy: no_status_selected: Heb newid postiad gan na ddewiswyd dim un open: Agor postiad original_status: Postiad gwreiddiol + quotes: Dyfyniadau reblogs: Ailflogiadau replied_to_html: Wedi ymateb i %{acct_link} status_changed: Postiad wedi'i newid @@ -964,6 +979,7 @@ cy: title: Postiadau cyfrif - @%{name} trending: Yn trendio view_publicly: Gweld yn gyhoeddus + view_quoted_post: Gweld y postiad wedi'i ddyfynnu visibility: Gwelededd with_media: Gyda chyfryngau strikes: @@ -1258,10 +1274,10 @@ cy: hint_html: Os ydych chi am symud o gyfrif arall i'r un hwn, gallwch greu enw arall yma, sy'n ofynnol cyn y gallwch symud ymlaen i symud dilynwyr o'r hen gyfrif i'r un hwn. Mae'r weithred hon ynddo'i hun yn ddiniwed ac yn wrthdroadwy. Mae'r mudo cyfrif yn cael ei wneud o'r hen gyfrif. remove: Dadgysylltu'r enw arall appearance: - advanced_web_interface: Rhyngwyneb gwe uwch - advanced_web_interface_hint: 'Os ydych chi am ddefnyddio lled eich sgrin gyfan, mae''r rhyngwyneb gwe datblygedig yn caniatáu i chi ffurfweddu llawer o wahanol golofnau i weld faint bynnag o wybodaeth ar yr un pryd ag y dymunwch: Cartref, hysbysiadau, ffrydiau ffederaleiddiwyd, faint bynnag o restrau a hashnodau.' + advanced_settings: Gosodiadau uwch animations_and_accessibility: Animeiddiadau a hygyrchedd - confirmation_dialogs: Deialogau cadarnhau + boosting_preferences: Dewisiadau hybu + boosting_preferences_info_html: "Awgrym: Beth bynnag yw'r gosodiadau, bydd Shift + Clici ar yr eicon Hybu %{icon} yn hybu'n syth." discovery: Darganfod localization: body: Mae Mastodon yn cael ei gyfieithu gan wirfoddolwyr. @@ -1743,6 +1759,13 @@ cy: expires_at: Yn dod i ben ar uses: Defnyddiau title: Gwahodd pobl + link_preview: + author_html: Yn ôl %{name} + potentially_sensitive_content: + action: Cliciwch i ddangos + confirm_visit: Ydych chi'n siŵr eich bod chi eisiau agor y ddolen hon? + hide_button: Cuddio + label: Cynnwys a allai fod yn sensitif lists: errors: limit: Rydych chi wedi cyrraedd y nifer mwyaf o restrau @@ -1807,7 +1830,7 @@ cy: disabled_account: Ni fydd modd defnyddio'ch cyfrif cyfredol yn llawn wedyn. Fodd bynnag, bydd gennych fynediad i allforio data yn ogystal ag ail agor. followers: Bydd y weithred hon yn symud yr holl ddilynwyr o'r cyfrif cyfredol i'r cyfrif newydd only_redirect_html: Fel arall, dim ond ailgyfeiriad y gallwch chi ei osod ar eich proffil. - other_data: Ni fydd unrhyw data arall yn cael ei symud yn awtomatig + other_data: Bydd dim ddata arall yn cael ei symud yn awtomatig (gan gynnwys eich postiadau a'r rhestr o gyfrifon rydych chi'n eu dilyn) redirect: Bydd proffil eich cyfrif presennol yn cael ei diweddaru gyda hysbysiad ailgyfeirio ac yn cael ei eithrio o chwiliadau moderation: title: Cymedroil @@ -1841,16 +1864,22 @@ cy: body: 'Caswoch eich crybwyll gan %{name} yn:' subject: Cawsoch eich crybwyll gan %{name} title: Crywbylliad newydd + moderation_warning: + subject: Rydych wedi derbyn rhybudd gan gymedrolwr poll: subject: Mae arolwg gan %{name} wedi dod i ben quote: body: 'Mae %{name} wedi dyfynnu eich postiad :' subject: Mae %{name} wedi dyfynnu eich postiad title: Dyfyniad newydd + quoted_update: + subject: Golygodd %{name} bostiad rydych chi wedi'i ddyfynnu reblog: body: 'Cafodd eich postiad ei hybu gan %{name}:' subject: Rhoddodd %{name} hwb i'ch postiad title: Hwb newydd + severed_relationships: + subject: Rydych chi wedi colli cysylltiadau oherwydd penderfyniad cymedroli status: subject: "%{name} newydd ei bostio" update: @@ -2065,6 +2094,9 @@ cy: zero: "%{count} fideo" boosted_from_html: Wedi'i hybu o %{acct_link} content_warning: 'Rhybudd cynnwys: %{warning}' + content_warnings: + hide: Cuddio'r postiad + show: Dangos rhagor default_language: Yr un fath a'r iaith rhyngwyneb disallowed_hashtags: few: 'yn cynnwys yr hashnod gwaharddedig: %{tags}' @@ -2077,16 +2109,22 @@ cy: errors: in_reply_not_found: Nid yw'n ymddangos bod y postiad rydych chi'n ceisio ei ateb yn bodoli. quoted_status_not_found: Nid yw'n ymddangos bod y postiad rydych chi'n ceisio'i ddyfynnu yn bodoli. + quoted_user_not_mentioned: Does dim modd dyfynnu defnyddiwr heb ei grybwyll mewn postiad Crybwyll Preifat. over_character_limit: wedi mynd y tu hwnt i'r terfyn nodau o %{max} pin_errors: direct: Nid oes modd pinio postiadau sy'n weladwy i ddefnyddwyr a grybwyllwyd yn unig limit: Rydych chi eisoes wedi pinio uchafswm nifer y postiadau ownership: Nid oes modd pinio postiad rhywun arall reblog: Nid oes modd pinio hwb + quote_error: + not_available: Dyw'r postiad ddim ar gael + pending_approval: Postiad ar ei ffordd + revoked: Postiad wedi'i ddileu gan yr awdur quote_policies: followers: Dilynwyr yn unig nobody: Dim ond fi public: Pawb + quote_post_author: Wedi dyfynnu postiad gan %{acct} title: '%{name}: "%{quote}"' visibilities: direct: Crybwylliad preifat @@ -2325,3 +2363,6 @@ cy: not_supported: Nid yw'r porwr hwn yn cynnal allweddi diogelwch otp_required: I ddefnyddio allweddi diogelwch, galluogwch ddilysu dau ffactor yn gyntaf. registered_on: Cofrestrwyd ar %{date} + wrapstodon: + description: Gweler sut defnyddiodd %{name} Mastodon eleni! + title: Wrapstodon %{year} ar gyfer %{name} diff --git a/config/locales/da.yml b/config/locales/da.yml index 9f15c2a43f0e44..ea5cbe33370ba6 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -7,6 +7,8 @@ da: hosted_on: Mastodon hostet på %{domain} title: Om accounts: + errors: + cannot_be_added_to_collections: Denne konto kan ikke tilføjes til samlinger. followers: one: Følger other: Følgere @@ -66,7 +68,7 @@ da: disabled: Frosset display_name: Visningsnavn domain: Domæne - edit: Redigere + edit: Rediger email: E-mail email_status: E-mailstatus enable: Optø @@ -477,7 +479,7 @@ da: no_file: Ingen fil valgt export_domain_blocks: import: - description_html: En liste over domæneblokeringer er ved at blive importeret. Gennemgå listen meget nøje, især hvis man ikke selv har oprettet den. + description_html: Du er ved at importere en liste over domæneblokeringer. Gennemgå denne liste meget omhyggeligt, især hvis du ikke selv har udarbejdet den. existing_relationships_warning: Eksisterende følge-relationer private_comment_description_html: 'For at man lettere kan holde styr på, hvorfra importerede blokeringer kommer, oprettes disse med flg. private kommentar: %{comment}' private_comment_template: Importeret fra %{source} d. %{date} @@ -514,7 +516,7 @@ da: select_capabilities: Vælg kapaciteter sign_in: Log ind status: Status - title: Fediverse Auxiliary Service Providers + title: Udbydere af Fediverse-hjælpetjenester title: FASP follow_recommendations: description_html: "Følg-anbefalinger hjælpe nye brugere til hurtigt at finde interessant indhold. Når en bruger ikke har interageret nok med andre til at generere personlige følg-anbefalinger, anbefales disse konti i stedet. De revurderes dagligt baseret på en blanding af konti med de flest nylige engagementer og fleste lokale følger-antal for et givet sprog." @@ -796,6 +798,8 @@ da: view_dashboard_description: Tillader brugere at tilgå Dashboard'et og forskellige målinger view_devops: DevOps view_devops_description: Tillader brugere at tilgå Sidekiq- og pgHero-dashboards + view_feeds: Se live- og emne-feeds + view_feeds_description: Giver brugerne adgang til live- og emne-feeds uanset serverindstillinger title: Roller rules: add_new: Tilføj regel @@ -837,17 +841,28 @@ da: title: Fravælg som standard søgemaskineindeksering for brugere discovery: follow_recommendations: Følg-anbefalinger - preamble: At vise interessant indhold er vitalt ifm. at få nye brugere om bord, som måske ikke kender nogen på Mastodon. Styr, hvordan forskellige opdagelsesfunktioner fungerer på serveren. + preamble: At bringe interessant indhold frem er afgørende for at engagere nye brugere, der måske ikke kender nogen på Mastodon. Kontroller, hvordan forskellige funktioner til at finde indhold fungerer på din server. privacy: Fortrolighed profile_directory: Profiloversigt public_timelines: Offentlige tidslinjer publish_statistics: Udgiv statistik title: Opdagelse trends: Trends + wrapstodon: Wrapstodon domain_blocks: all: Til alle disabled: Til ingen users: Til indloggede lokale brugere + feed_access: + modes: + authenticated: Kun godkendte brugere + disabled: Kræv specifik brugerrolle + public: Alle + landing_page: + values: + about: Om + local_feed: Lokalt feed + trends: Trends registrations: moderation_recommandation: Sørg for, at der er et tilstrækkeligt og reaktivt moderationsteam, før registrering åbnes for alle! preamble: Styr, hvem der kan oprette en konto på serveren. @@ -901,6 +916,7 @@ da: no_status_selected: Ingen indlæg ændret (ingen valgt) open: Åbn indlæg original_status: Oprindeligt indlæg + quotes: Citater reblogs: Genblogninger replied_to_html: Besvarede %{acct_link} status_changed: Indlæg ændret @@ -908,6 +924,7 @@ da: title: Kontoindlæg - @%{name} trending: Trender view_publicly: Vis offentligt + view_quoted_post: Vis citeret indlæg visibility: Synlighed with_media: Med medier strikes: @@ -1172,7 +1189,7 @@ da: new_trending_statuses: title: Indlæg, der trender new_trending_tags: - title: Hashtags, der trender + title: Populære hashtags subject: Nye tendenser klar til gennemgang på %{instance} aliases: add_new: Opret alias @@ -1182,10 +1199,10 @@ da: hint_html: Ønsker du at flytte fra en anden konto til denne, kan du hér oprette det alias, der kræves, for at du kan fortsætte med at flytte følgere fra den gamle konto til denne. Denne handling er i sig selv harmløs og reversibel. Kontomigreringen påbegyndes fra den gamle konto. remove: Fjern aliaslinkning appearance: - advanced_web_interface: Avanceret webgrænseflade - advanced_web_interface_hint: 'Ønsker du udnytte hele skærmbredden, lader den avancerede webgrænseflade dig opsætte mange forskellige kolonner for at se så meget information på samme tid som ønsket: Hjem, notifikationer, federeret tidslinje, et hvilket som helst antal lister og hashtags.' + advanced_settings: Avancerede indstillinger animations_and_accessibility: Animationer og tilgængelighed - confirmation_dialogs: Bekræftelsesdialoger + boosting_preferences: Præferencer for fremhævelse + boosting_preferences_info_html: "Tip: Uanset indstillinger vil Skift + Klik på %{icon} fremhæv-ikonet straks fremhæve." discovery: Opdagelse localization: body: Mastodon oversættes af frivillige. @@ -1281,7 +1298,7 @@ da: account_status: Kontostatus confirming: Afventer færdiggørelse af e-mailbekræftelse. functional: Din konto er fuldt funktionel. - pending: Ansøgningen afventer gennemgang af vores personale. Dette kan tage noget tid. Man bør modtage en e-mail, såfremt ansøgningen godkendes. + pending: Ansøgningen afventer gennemgang af vores personale. Dette kan tage noget tid. Du modtager en e-mail, hvis din ansøgning bliver godkendt. redirecting_to: Din konto er inaktiv, da den pt. er omdirigerer til %{acct}. self_destruct: Da %{domain} er under nedlukning, vil kontoadgangen være begrænset. view_strikes: Se tidligere anmeldelser af din konto @@ -1579,14 +1596,21 @@ da: invalid: Denne invitation er ikke gyldig invited_by: 'Du blev inviteret af:' max_uses: - one: 1 benyttelse - other: "%{count} benyttelser" + one: 1 anvendelse + other: "%{count} anvendelser" max_uses_prompt: Ubegrænset prompt: Generér og del links med andre for at give dem adgang til denne server table: expires_at: Udløber - uses: Benyttelser + uses: Anvendelser title: Invitér personer + link_preview: + author_html: Af %{name} + potentially_sensitive_content: + action: Klik for at vise + confirm_visit: Er du sikker på, at du vil åbne dette link? + hide_button: Skjul + label: Potentielt følsomt indhold lists: errors: limit: Maks. listeantal nået @@ -1634,7 +1658,7 @@ da: not_found: kunne ikke findes on_cooldown: Du er på nedkøling followers_count: Følgere på flytningstidspunktet - incoming_migrations: Flytter fra en anden konto + incoming_migrations: Flytning fra en anden konto incoming_migrations_html: For at flytte fra en anden konto til denne skal der først oprettes et kontoalias. moved_msg: Din konto omdirigeres nu til %{acct} og dine følgere overflyttes. not_redirecting: Din konto omdirigerer pt. ikke til nogen anden konto. @@ -1651,7 +1675,7 @@ da: disabled_account: Efterfølgende er din nuværende konto ikke fuldt funktionsdygtig, der er dog adgang til dataeksport samt genaktivering. followers: Denne handling vil flytte alle følgere fra den aktuelle konto til den nye konto only_redirect_html: Alternativt kan du oprette en omdirigering for din profil alene. - other_data: Ingen øvrige data flyttes automatisk + other_data: Ingen andre data flyttes automatisk (herunder dine indlæg og listen over konti, du følger) redirect: Din nuværende kontoprofil opdateres med en omdirigeringsnotits og ekskluderes fra søgninger moderation: title: Moderation @@ -1685,16 +1709,22 @@ da: body: 'Du blev nævnt af %{name} i:' subject: Du blev nævnt af %{name} title: Ny omtale + moderation_warning: + subject: Du har fået en moderationsadvarsel poll: subject: En afstemning fra %{name} er afsluttet quote: body: 'Dit indlæg blev citeret af %{name}:' subject: "%{name} citerede dit indlæg" title: Nyt citat + quoted_update: + subject: "%{name} redigerede et indlæg, du har citeret" reblog: body: 'Dit indlæg blev fremhævet af %{name}:' subject: "%{name} fremhævede dit indlæg" title: Ny fremhævelse + severed_relationships: + subject: Du har mistet forbindelser på grund af en moderationsbeslutning status: subject: "%{name} har netop postet" update: @@ -1880,7 +1910,7 @@ da: user_domain_block: "%{target_name} blev blokeret" lost_followers: Tabte følgere lost_follows: Mistet følger - preamble: Der kan mistes fulgte objekter og følgere, når et domæne blokeres eller moderatorerne beslutter at suspendere en ekstern server. Når det sker, kan der downloades lister over afbrudte relationer til inspektion og mulig import på anden server. + preamble: Du kan miste fulgte og følgere, når du blokerer et domæne, eller når dine moderatorer beslutter at suspendere en fjernserver. Når det sker, kan du downloade lister over afbrudte forhold til inspektion og eventuelt import til en anden server. purged: Oplysninger om denne server er blevet renset af serveradministratoreren. type: Begivenhed statuses: @@ -1897,6 +1927,9 @@ da: other: "%{count} videoer" boosted_from_html: Fremhævet fra %{acct_link} content_warning: 'Indholdsadvarsel: %{warning}' + content_warnings: + hide: Skjul indlæg + show: Vis mere default_language: Samme som UI-sproget disallowed_hashtags: one: 'indeholdte et ikke tilladt hashtag: %{tags}' @@ -1905,16 +1938,22 @@ da: errors: in_reply_not_found: Indlægget, der forsøges besvaret, ser ikke ud til at eksistere. quoted_status_not_found: Indlægget, du forsøger at citere, ser ikke ud til at eksistere. + quoted_user_not_mentioned: Kan ikke citere en ikke-omtalt bruger i et privat omtale-indlæg. over_character_limit: grænsen på %{max} tegn overskredet pin_errors: direct: Indlæg, som kun kan ses af omtalte brugere, kan ikke fastgøres limit: Maksimalt antal indlæg allerede fastgjort ownership: Andres indlæg kan ikke fastgøres reblog: En fremhævelse kan ikke fastgøres + quote_error: + not_available: Indlæg utilgængeligt + pending_approval: Indlæg afventer + revoked: Indlæg fjernet af forfatter quote_policies: followers: Kun følgere nobody: Kun mig public: Alle + quote_post_author: Citerede et indlæg af %{acct} title: '%{name}: "%{quote}"' visibilities: direct: Privat omtale @@ -1927,12 +1966,12 @@ da: enabled: Slet automatisk gamle indlæg enabled_hint: Sletter automatisk dine indlæg, når disse når en bestemt alder, medmindre de matcher en af undtagelserne nedenfor exceptions: Undtagelser - explanation: Sletning af indlæg er en ressourcekrævende operation, hvorfor dette sker gradvist over tid, når serveren ellers ikke er optaget. Indlæg kan derfor blive slettet efter, at de reelt har passeret aldersgrænsen. + explanation: Da sletning af indlæg er en kostbar operation, foregår dette langsomt over tid, når serveren ikke er optaget af andre opgaver. Af denne grund kan dine indlæg blive slettet et stykke tid efter, at de har nået alderstærsklen. ignore_favs: Ignorér favoritter ignore_reblogs: Ignorér fremhævelser interaction_exceptions: Undtagelser baseret på interaktioner interaction_exceptions_explanation: Bemærk, at det ikke garanteres, at indlæg slettes, hvis de når under favorit- eller fremhævelses-tærsklerne efter én gang at været nået over dem. - keep_direct: Behold direkte besked + keep_direct: Behold direkte beskeder keep_direct_hint: Sletter ingen af dine direkte beskeder keep_media: Behold indlæg med medievedhæftninger keep_media_hint: Sletter ingen af dine indlæg med medievedhæftninger @@ -1974,7 +2013,7 @@ da: title: Tjenestevilkårene for %{domain} ændres themes: contrast: Mastodon (høj kontrast) - default: Mastodont (mørkt) + default: Mastodon (mørkt) mastodon-light: Mastodon (lyst) system: Automatisk (benyt systemtema) time: @@ -1994,8 +2033,8 @@ da: edit: Redigér enabled: Tofaktorgodkendelse aktiveret enabled_success: Tofaktorgodkendelse aktiveret - generate_recovery_codes: Generere gendannelseskoder - lost_recovery_codes: Gendannelseskoder muliggør adgang til din konto, hvis du mister din mobil. Ved mistet gendannelseskoder, kan disse regenerere her. Dine gamle gendannelseskoder ugyldiggøres. + generate_recovery_codes: Generer gendannelseskoder + lost_recovery_codes: Gendannelseskoder giver dig mulighed for at få adgang til din konto igen, hvis du mister din telefon. Hvis du har mistet dine gendannelseskoder, kan du generere dem igen her. Dine gamle gendannelseskoder vil blive ugyldige. methods: Tofaktormetoder otp: Godkendelses-app recovery_codes: Sikkerhedskopieret gendannelseskoder @@ -2092,7 +2131,7 @@ da: feature_audience_title: Opbyg et publikum i tillid feature_control: Man ved selv bedst, hvad man ønsker at se på sit hjemmefeed. Ingen algoritmer eller annoncer til at spilde tiden. Følg alle på tværs af enhver Mastodon-server fra en enkelt konto og modtag deres indlæg i kronologisk rækkefølge, og gør dette hjørne af internet lidt mere personligt. feature_control_title: Hold styr på egen tidslinje - feature_creativity: Mastodon understøtter indlæg med lyd, video og billede, tilgængelighedsbeskrivelser, meningsmålinger, indhold advarsler, animerede avatarer, tilpassede emojis, miniaturebeskæringskontrol og mere, for at gøre det lettere at udtrykke sig online. Uanset om man udgiver sin kunst, musik eller podcast, så står Mastodon til rådighed. + feature_creativity: Mastodon understøtter lyd-, video- og billedindlæg, tilgængelighedsbeskrivelser, afstemninger, indholdsadvarsler, animerede avatarer, brugerdefinerede emojis, kontrol over beskæring af miniaturebilleder og meget mere, så du lettere kan udtrykke dig online. Uanset om du udgiver din kunst, din musik eller din podcast, er Mastodon der for dig. feature_creativity_title: Uovertruffen kreativitet feature_moderation: Mastodon lægger beslutningstagning tilbage i brugerens hænder. Hver server opretter deres egne regler og reguleringer, som håndhæves lokalt og ikke ovenfra som virksomhedernes sociale medier, hvilket gør det til den mest fleksible mht. at reagere på behovene hos forskellige persongrupper. Deltag på en server med de regler, man er enige med, eller driv en egen server. feature_moderation_title: Moderering af måden, tingene bør være på @@ -2149,3 +2188,6 @@ da: not_supported: Denne browser understøtter ikke sikkerhedsnøgler otp_required: For at bruge sikkerhedsnøgler skal tofaktorgodkendelse først aktiveres. registered_on: Registreret d. %{date} + wrapstodon: + description: Se hvordan %{name} brugte Mastodon i år! + title: Wrapstodon %{year} for %{name} diff --git a/config/locales/de.yml b/config/locales/de.yml index 0f694493fac7ce..0ecff0e276342d 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -7,6 +7,8 @@ de: hosted_on: Mastodon, gehostet auf %{domain} title: Über accounts: + errors: + cannot_be_added_to_collections: Dieses Konto darf keinen Sammlungen hinzugefügt werden. followers: one: Follower other: Follower @@ -33,7 +35,7 @@ de: created_msg: Moderationshinweis erfolgreich abgespeichert! destroyed_msg: Moderationsnotiz erfolgreich entfernt! accounts: - add_email_domain_block: E-Mail-Domain sperren + add_email_domain_block: E-Mail-Domain blockieren approve: Genehmigen approved_msg: Antrag zur Registrierung von %{username} erfolgreich genehmigt are_you_sure: Bist du dir sicher? @@ -54,15 +56,15 @@ de: title: Rolle für %{username} ändern confirm: Bestätigen confirmed: Bestätigt - confirming: Bestätigung + confirming: Bestätigung ausstehend custom: Angepasst delete: Daten löschen deleted: Gelöscht demote: Zurückstufen - destroyed_msg: Daten von %{username} wurden zum Löschen in die Warteschlange eingereiht + destroyed_msg: Die Daten von %{username} befinden sich nun in der Warteschlange und werden in Kürze gelöscht disable: Einfrieren disable_sign_in_token_auth: E-Mail-Token-Authentisierung deaktivieren - disable_two_factor_authentication: Zwei-Faktor-Authentisierung deaktivieren + disable_two_factor_authentication: Zwei-Faktor-Authentisierung (2FA) deaktivieren disabled: Eingefroren display_name: Anzeigename domain: Domain @@ -80,7 +82,7 @@ de: invite_request_text: Begründung für das Beitreten invited_by: Eingeladen von ip: IP-Adresse - joined: Mitglied seit + joined: Registriert am location: all: Alle local: Lokal @@ -103,12 +105,12 @@ de: most_recent_activity: Letzte Aktivität most_recent_ip: Letzte IP-Adresse no_account_selected: Keine Konten wurden geändert, da keine ausgewählt wurden - no_limits_imposed: Keine Beschränkungen + no_limits_imposed: Keine Einschränkungen no_role_assigned: Keine Rolle zugewiesen not_subscribed: Nicht abonniert pending: Überprüfung ausstehend perform_full_suspension: Sperren - previous_strikes: Vorherige Maßnahmen + previous_strikes: Frühere Maßnahmen previous_strikes_description_html: one: Gegen dieses Konto wurde eine Maßnahme verhängt. other: Gegen dieses Konto wurden %{count} Maßnahmen verhängt. @@ -129,14 +131,14 @@ de: resend_confirmation: already_confirmed: Dieses Profil wurde bereits bestätigt send: Bestätigungslink erneut zusenden - success: Bestätigungslink erfolgreich gesendet! + success: Bestätigungslink erfolgreich verschickt! reset: Zurücksetzen reset_password: Passwort zurücksetzen resubscribe: Erneut abonnieren role: Rolle search: Suchen search_same_email_domain: Andere Konten mit der gleichen E-Mail-Domain - search_same_ip: Andere Konten mit derselben IP-Adresse + search_same_ip: Andere Konten mit der gleichen IP-Adresse security: Sicherheit security_measures: only_password: Nur Passwort @@ -145,7 +147,7 @@ de: sensitized: Mit Inhaltswarnung versehen shared_inbox_url: Geteilte Posteingangsadresse show: - created_reports: Erstellte Meldungen + created_reports: Eingereichte Meldungen targeted_reports: Von Anderen gemeldet silence: Stummschalten silenced: Stummgeschaltet @@ -154,10 +156,10 @@ de: subscribe: Abonnieren suspend: Sperren suspended: Gesperrt - suspension_irreversible: Die Daten dieses Kontos wurden unwiderruflich gelöscht. Du kannst das Konto entsperren, um es wieder zu verwenden, aber es wird keine Daten wiederherstellen, die es davor hatte. + suspension_irreversible: Die Daten dieses Kontos wurden unwiderruflich gelöscht. Du kannst die Kontosperrung aufheben, damit es wieder aktiv ist und funktioniert, aber es werden keine früheren Daten wiederhergestellt. suspension_reversible_hint_html: Das Konto wurde gesperrt und die Daten werden am %{date} vollständig gelöscht. Bis dahin kann das Konto ohne irgendwelche negativen Auswirkungen wiederhergestellt werden. Wenn du alle Daten des Kontos sofort entfernen möchtest, kannst du das nachfolgend tun. title: Konten - unblock_email: E-Mail-Adresse entsperren + unblock_email: Blockierung der E-Mail-Adresse aufheben unblocked_email_msg: E-Mail-Adresse von %{username} erfolgreich entsperrt unconfirmed_email: Unbestätigte E-Mail-Adresse undo_sensitized: Inhaltswarnung aufheben @@ -184,7 +186,7 @@ de: create_canonical_email_block: E-Mail-Sperre erstellen create_custom_emoji: Eigene Emojis erstellen create_domain_allow: Domain erlauben - create_domain_block: Domain sperren + create_domain_block: Domain blockieren create_email_domain_block: E-Mail-Domain-Sperre erstellen create_ip_block: IP-Regel erstellen create_relay: Relais erstellen @@ -209,7 +211,7 @@ de: disable_custom_emoji: Eigenes Emoji deaktivieren disable_relay: Relais deaktivieren disable_sign_in_token_auth_user: E-Mail-Token-Authentisierung für dieses Konto deaktivieren - disable_user: Benutzer*in deaktivieren + disable_user: Profil deaktivieren enable_custom_emoji: Eigenes Emoji aktivieren enable_relay: Relais aktivieren enable_sign_in_token_auth_user: E-Mail-Token-Authentisierung für dieses Konto aktivieren @@ -218,20 +220,20 @@ de: promote_user: Benutzer*in hochstufen publish_terms_of_service: Nutzungsbedingungen veröffentlichen reject_appeal: Einspruch ablehnen - reject_user: Benutzer*in ablehnen + reject_user: Profil ablehnen remove_avatar_user: Profilbild entfernen reopen_report: Meldung wieder eröffnen - resend_user: Bestätigungs-E-Mail erneut senden + resend_user: Bestätigungs-E-Mail erneut zuschicken reset_password_user: Passwort zurücksetzen resolve_report: Meldung klären sensitive_account: Konto mit erzwungener Inhaltswarnung silence_account: Konto stummschalten suspend_account: Konto sperren unassigned_report: Meldung widerrufen - unblock_email_account: E-Mail-Adresse entsperren + unblock_email_account: Blockierung der E-Mail-Adresse aufheben unsensitive_account: Konto mit erzwungener Inhaltswarnung rückgängig machen unsilence_account: Konto nicht mehr stummschalten - unsuspend_account: Konto entsperren + unsuspend_account: Kontosperre aufheben update_announcement: Ankündigung aktualisieren update_custom_emoji: Eigenes Emoji aktualisieren update_domain_block: Domain-Sperre aktualisieren @@ -241,7 +243,7 @@ de: update_user_role: Rolle bearbeiten update_username_block: Regel für Profilnamen aktualisieren actions: - approve_appeal_html: "%{name} hat den Einspruch gegen eine Moderationsentscheidung von %{target} genehmigt" + approve_appeal_html: "%{name} genehmigte den Einspruch gegen eine Moderationsentscheidung von %{target}" approve_user_html: "%{name} genehmigte die Registrierung von %{target}" assigned_to_self_report_html: "%{name} wies sich die Meldung %{target} selbst zu" change_email_user_html: "%{name} änderte die E-Mail-Adresse von %{target}" @@ -255,7 +257,7 @@ de: create_domain_block_html: "%{name} sperrte die Domain %{target}" create_email_domain_block_html: "%{name} sperrte die E-Mail-Domain %{target}" create_ip_block_html: "%{name} erstellte eine IP-Regel für %{target}" - create_relay_html: "%{name} erstellte ein Relay %{target}" + create_relay_html: "%{name} erstellte ein Relais %{target}" create_unavailable_domain_html: "%{name} beendete die Zustellung an die Domain %{target}" create_user_role_html: "%{name} erstellte die Rolle %{target}" create_username_block_html: "%{name} erstellte eine Regel für Profilnamen mit %{target}" @@ -285,7 +287,7 @@ de: memorialize_account_html: "%{name} wandelte das Konto von %{target} in eine Gedenkseite um" promote_user_html: "%{name} beförderte %{target}" publish_terms_of_service_html: "%{name} aktualisierte die Nutzungsbedingungen" - reject_appeal_html: "%{name} hat den Einspruch gegen eine Moderationsentscheidung von %{target} abgelehnt" + reject_appeal_html: "%{name} lehnte den Einspruch gegen eine Moderationsentscheidung von %{target} ab" reject_user_html: "%{name} hat die Registrierung von %{target} abgelehnt" remove_avatar_user_html: "%{name} entfernte das Profilbild von %{target}" reopen_report_html: "%{name} öffnete die Meldung %{target} wieder" @@ -296,14 +298,14 @@ de: silence_account_html: "%{name} schaltete das Konto von %{target} stumm" suspend_account_html: "%{name} sperrte das Konto von %{target}" unassigned_report_html: "%{name} entfernte die Zuweisung der Meldung %{target}" - unblock_email_account_html: "%{name} hat die E-Mail-Adresse von %{target} entsperrt" - unsensitive_account_html: "%{name} hat die Inhaltswarnung für Medien von %{target} aufgehoben" + unblock_email_account_html: "%{name} entsperrte die E-Mail-Adresse von %{target}" + unsensitive_account_html: "%{name} hob die Inhaltswarnung für Medien von %{target} auf" unsilence_account_html: "%{name} hob die Stummschaltung von %{target} auf" unsuspend_account_html: "%{name} entsperrte das Konto von %{target}" update_announcement_html: "%{name} überarbeitete die Ankündigung %{target}" update_custom_emoji_html: "%{name} bearbeitete das Emoji %{target}" update_domain_block_html: "%{name} aktualisierte die Domain-Sperre für %{target}" - update_ip_block_html: "%{name} änderte die Regel für die IP-Adresse %{target}" + update_ip_block_html: "%{name} änderte eine IP-Regel für %{target}" update_report_html: "%{name} überarbeitete die Meldung %{target}" update_status_html: "%{name} überarbeitete einen Beitrag von %{target}" update_user_role_html: "%{name} änderte die Rolle von %{target}" @@ -333,8 +335,8 @@ de: scheduled_for: Geplant für %{time} scheduled_msg: Ankündigung ist zur Veröffentlichung vorgemerkt! title: Ankündigungen - unpublish: Veröffentlichung rückgängig machen - unpublished_msg: Ankündigung ist jetzt nicht mehr sichtbar! + unpublish: Ankündigung zurücknehmen + unpublished_msg: Ankündigung erfolgreich zurückgenommen! updated_msg: Ankündigung erfolgreich aktualisiert! critical_update_pending: Kritisches Update ausstehend custom_emojis: @@ -364,9 +366,9 @@ de: overwrite: Überschreiben shortcode: Shortcode shortcode_hint: Mindestens 2 Zeichen, nur Buchstaben, Ziffern und Unterstriche - title: Eigene Emojis + title: Emojis uncategorized: Unkategorisiert - unlist: Nicht anzeigen + unlist: Ausblenden unlisted: Nicht sichtbar update_failed_msg: Konnte dieses Emoji nicht bearbeiten updated_msg: Emoji erfolgreich bearbeitet! @@ -376,7 +378,7 @@ de: interactions: Interaktionen media_storage: Medien new_users: neue Profile - opened_reports: eingereichte Meldungen + opened_reports: Unbearbeitete Meldungen pending_appeals_html: one: "%{count} ausstehender Einspruch" other: "%{count} ausstehende Einsprüche" @@ -522,7 +524,7 @@ de: status: Status suppress: Folgeempfehlung unterbinden suppressed: Unterdrückt - title: Folgeempfehlungen + title: Follower-Empfehlungen unsuppress: Folgeempfehlung nicht mehr unterbinden instances: audit_log: @@ -796,6 +798,8 @@ de: view_dashboard_description: Gewährt Benutzer*innen den Zugriff auf das Dashboard und verschiedene Metriken view_devops: DevOps view_devops_description: Erlaubt es Benutzer*innen, auf die Sidekiq- und pgHero-Dashboards zuzugreifen + view_feeds: Live-Feeds und Hashtags anzeigen + view_feeds_description: Ermöglicht Nutzer*innen unabhängig von den Servereinstellungen den Zugriff auf die Live-Feeds und Hashtags title: Rollen rules: add_new: Regel hinzufügen @@ -823,7 +827,7 @@ de: preamble: Passe das Webinterface von Mastodon an. title: Design branding: - preamble: Das Branding deines Servers unterscheidet ihn von anderen Servern im Netzwerk. Diese Informationen können in einer Vielzahl von Umgebungen angezeigt werden, z. B. in der Weboberfläche von Mastodon, in nativen Anwendungen, in Linkvorschauen auf anderen Websites und in Messaging-Apps und so weiter. Aus diesem Grund ist es am besten, diese Informationen klar, kurz und prägnant zu halten. + preamble: Das Branding deines Servers unterscheidet ihn von anderen Servern im Netzwerk. Diese Informationen können in einer Vielzahl von Umgebungen angezeigt werden, z. B. im Webinterface von Mastodon, in nativen Anwendungen, in der Linkvorschau auf anderen Websites, in Messaging-Apps und so weiter. Aus diesem Grund ist es am besten, diese Informationen klar, kurz und prägnant zu halten. title: Branding captcha_enabled: desc_html: Dies beruht auf externen Skripten von hCaptcha, die ein Sicherheits- und Datenschutzproblem darstellen könnten. Darüber hinaus kann das den Registrierungsprozess für manche Menschen (insbesondere für Menschen mit Behinderung) erheblich erschweren. Aus diesen Gründen solltest du alternative Maßnahmen in Betracht ziehen, z. B. eine Registrierung basierend auf einer Einladung oder auf Genehmigungen. @@ -837,17 +841,28 @@ de: title: Profile standardmäßig von der Suchmaschinen-Indizierung ausnehmen discovery: follow_recommendations: Follower-Empfehlungen - preamble: Das Auffinden interessanter Inhalte ist wichtig, um neue Nutzer einzubinden, die Mastodon noch nicht kennen. Bestimme, wie verschiedene Suchfunktionen auf deinem Server funktionieren. + preamble: Interessante Inhalte sichtbar zu machen, ist entscheidend, um neue Nutzer*innen, die möglicherweise noch niemanden auf Mastodon kennen, zu gewinnen. Überprüfe, wie die einzelnen Suchfunktionen auf deinem Server funktionieren. privacy: Datenschutz profile_directory: Profilverzeichnis public_timelines: Öffentliche Timeline publish_statistics: Statistiken veröffentlichen title: Entdecken trends: Trends + wrapstodon: Wrapstodon domain_blocks: all: Allen disabled: Niemandem users: Für angemeldete lokale Benutzer*innen + feed_access: + modes: + authenticated: Nur authentifizierte Nutzer*innen + disabled: Bestimmte Rolle erforderlich + public: Alle + landing_page: + values: + about: Über + local_feed: Lokaler Feed + trends: Trends registrations: moderation_recommandation: Bitte vergewissere dich, dass du ein geeignetes und reaktionsschnelles Moderationsteam hast, bevor du die Registrierungen uneingeschränkt zulässt! preamble: Lege fest, wer auf deinem Server ein Konto erstellen darf. @@ -901,6 +916,7 @@ de: no_status_selected: Keine Beiträge wurden geändert, weil keine ausgewählt wurden open: Beitrag öffnen original_status: Ursprünglicher Beitrag + quotes: Zitate reblogs: Geteilte Beiträge replied_to_html: Antwortete %{acct_link} status_changed: Beitrag bearbeitet @@ -908,6 +924,7 @@ de: title: Beiträge des Kontos – @%{name} trending: Trends view_publicly: Öffentlich anzeigen + view_quoted_post: Zitierten Beitrag anzeigen visibility: Sichtbarkeit with_media: Mit Medien strikes: @@ -959,7 +976,7 @@ de: message_html: Ein kritisches Mastodon-Update ist verfügbar – bitte aktualisiere so schnell wie möglich. software_version_patch_check: action: Verfügbare Updates ansehen - message_html: Ein Mastodon-Update für Fehlerkorrekturen ist verfügbar. + message_html: Ein Mastodon-Update mit Bugfixes ist verfügbar. upload_check_privacy_error: action: Für weitere Informationen hier klicken message_html: "Die Konfiguration deines Servers ist fehlerhaft. Die Privatsphäre deiner Benutzer*innen ist gefährdet." @@ -1040,7 +1057,7 @@ de: one: In den vergangenen 7 Tagen von einem Profil geteilt other: In den vergangenen 7 Tagen von %{count} Profilen geteilt title: Angesagte Links - usage_comparison: Heute %{today}-mal und gestern %{yesterday}-mal geteilt + usage_comparison: Heute %{today} × und gestern %{yesterday} × geteilt not_allowed_to_trend: Darf nicht trenden only_allowed: Nur Genehmigte pending_review: Überprüfung ausstehend @@ -1075,17 +1092,17 @@ de: tag_servers_measure: Server tag_uses_measure: insgesamt description_html: Diese Hashtags werden derzeit in vielen Beiträgen verwendet, die dein Server sieht. Dies kann deinen Nutzer*innen helfen, herauszufinden, worüber die Leute im Moment am meisten schreiben. Hashtags werden erst dann öffentlich angezeigt, wenn du sie genehmigst. - listable: Kann vorgeschlagen werden + listable: Darf empfohlen werden no_tag_selected: Keine Hashtags wurden geändert, da keine ausgewählt wurden - not_listable: Wird nicht vorgeschlagen - not_trendable: Wird in den Trends nicht angezeigt - not_usable: Kann nicht verwendet werden + not_listable: Darf nicht vorgeschlagen werden + not_trendable: In Trends nicht erlaubt + not_usable: In Beiträgen nicht erlaubt peaked_on_and_decaying: In den Trends am %{date}, jetzt absteigend title: Angesagte Hashtags - trendable: Darf in den Trends erscheinen + trendable: In Trends erlaubt trending_rank: Platz %{rank} - usable: Darf verwendet werden - usage_comparison: Heute %{today}-mal und gestern %{yesterday}-mal verwendet + usable: In Beiträgen erlaubt + usage_comparison: Heute %{today} × und gestern %{yesterday} × verwendet used_by_over_week: one: In den vergangenen 7 Tagen von einem Profil verwendet other: In den vergangenen 7 Tagen von %{count} Profilen verwendet @@ -1108,7 +1125,7 @@ de: title: Neue Regel für Profilnamen erstellen no_username_block_selected: Keine Regeln für Profilnamen wurden geändert, weil keine ausgewählt wurde(n) not_permitted: Nicht gestattet - title: Regeln für Profilnamen + title: Profilnamen updated_msg: Regel für Profilnamen erfolgreich aktualisiert warning_presets: add_new: Neu hinzufügen @@ -1182,10 +1199,10 @@ de: hint_html: Wenn du von einem anderen Konto auf dieses umziehen möchtest, dann kannst du hier einen Alias erstellen, der erforderlich ist, um deine Follower vom alten Konto auf dieses zu migrieren. Diese Aktion ist harmlos und wi­der­ruf­lich. Der Kontoumzug wird vom alten Konto aus eingeleitet. remove: Alle Aliasse aufheben appearance: - advanced_web_interface: Erweitertes Webinterface - advanced_web_interface_hint: Wenn du mehr aus deiner Bildschirmbreite herausholen möchtest, kannst du mit dem erweiterten Webinterface weitere Spalten hinzufügen und dadurch mehr Informationen auf einmal sehen, z. B. deine Startseite, die Benachrichtigungen, die föderierte Timeline sowie beliebig viele deiner Listen und Hashtags. + advanced_settings: Erweiterte Einstellungen animations_and_accessibility: Animationen und Barrierefreiheit - confirmation_dialogs: Bestätigungsdialoge + boosting_preferences: Teilen + boosting_preferences_info_html: "Tipp: Umschalttaste + Klick beim Teilen-Symbol %{icon} wird den Beitrag immer sofort teilen." discovery: Entdecken localization: body: Mastodon wird von Freiwilligen übersetzt. @@ -1265,13 +1282,13 @@ de: security: Sicherheit set_new_password: Neues Passwort einrichten setup: - email_below_hint_html: Überprüfe deinen Spam-Ordner oder lass dir den Bestätigungslink erneut zusenden. Falls die angegebene E-Mail-Adresse falsch ist, kannst du sie auch korrigieren. - email_settings_hint_html: Klicke auf den Link, den wir an %{email} gesendet haben, um mit Mastodon loszulegen. Wir warten hier solange auf dich. + email_below_hint_html: Überprüfe deinen Spam-/Junk-Ordner oder lass dir den Bestätigungslink erneut zusenden. Falls du die vorherige E-Mail-Adresse falsch eingegeben hast, kannst du sie hier korrigieren. + email_settings_hint_html: Klicke auf den Link, den wir dir an %{email} geschickt haben, um mit Mastodon zu starten. Wir warten hier solange auf dich. link_not_received: Keinen Bestätigungslink erhalten? new_confirmation_instructions_sent: In wenigen Minuten wirst du eine neue E-Mail mit dem Bestätigungslink erhalten! title: Überprüfe dein E-Mail-Postfach sign_in: - preamble_html: Melde dich mit deinen Zugangsdaten für %{domain} an. Falls dein Konto auf einem anderen Server erstellt wurde, ist eine Anmeldung hier nicht möglich. + preamble_html: Melde dich mit deinen Zugangsdaten für %{domain} an. Falls dein Konto auf einem anderen Mastodon-Server erstellt wurde, ist eine Anmeldung hier nicht möglich. title: Bei %{domain} anmelden sign_up: manual_review: Registrierungen für den Server %{domain} werden manuell durch unsere Moderator*innen überprüft. Um uns dabei zu unterstützen, schreibe etwas über dich und sage uns, weshalb du ein Konto auf %{domain} anlegen möchtest. @@ -1491,7 +1508,7 @@ de: too_large: Datei ist zu groß failures: Fehler imported: Importiert - mismatched_types_warning: Möglicherweise hast du den falschen Typ für diesen Import ausgewählt. Bitte überprüfe alles noch einmal. + mismatched_types_warning: Möglicherweise hast du das falsche Format für diesen Import ausgewählt. Bitte überprüfe alles noch einmal. modes: merge: Zusammenführen merge_long: Bestehende Datensätze beibehalten und neue hinzufügen @@ -1587,6 +1604,13 @@ de: expires_at: Läuft ab uses: Verwendungen title: Einladungen + link_preview: + author_html: Von %{name} + potentially_sensitive_content: + action: Zum Anzeigen anklicken + confirm_visit: Möchtest du diesen Link wirklich öffnen? + hide_button: Ausblenden + label: Inhaltswarnung lists: errors: limit: Du hast die maximale Anzahl an Listen erreicht @@ -1651,7 +1675,7 @@ de: disabled_account: Dein altes Konto ist nur noch eingeschränkt verwendbar. Du kannst jedoch deine Daten exportieren und das Konto wieder reaktivieren. followers: Alle Follower werden vom alten zum neuen Konto übertragen only_redirect_html: Alternativ kannst du auch nur eine Weiterleitung zu deinem neuen Konto einrichten, ohne die Follower zu übertragen. - other_data: Keine anderen Daten werden automatisch zum neuen Konto übertragen + other_data: Es werden keine weiteren Daten automatisch übertragen (einschließlich deiner Beiträge und der Liste der Konten, denen du folgst) redirect: Dein altes Konto wird einen Hinweis erhalten, dass du umgezogen bist. Außerdem wird das Profil von Suchanfragen ausgeschlossen moderation: title: Moderation @@ -1685,16 +1709,22 @@ de: body: 'Du wurdest von %{name} erwähnt:' subject: "%{name} erwähnte dich" title: Neue Erwähnung + moderation_warning: + subject: Die Moderator*innen haben dich verwarnt poll: subject: Eine Umfrage von %{name} ist beendet quote: body: 'Dein Beitrag wurde von %{name} zitiert:' subject: "%{name} zitierte deinen Beitrag" title: Neuer zitierter Beitrag + quoted_update: + subject: "%{name} bearbeitete einen von dir zitierten Beitrag" reblog: body: 'Dein Beitrag wurde von %{name} geteilt:' subject: "%{name} teilte deinen Beitrag" title: Dein Beitrag wurde geteilt + severed_relationships: + subject: Du hast die Verbindung aufgrund einer Moderationsentscheidung verloren status: subject: "%{name} veröffentlichte gerade einen Beitrag" update: @@ -1717,8 +1747,8 @@ de: code_hint: Gib den Code ein, den deine 2FA- bzw. TOTP-App generiert hat, um den Vorgang zu bestätigen description_html: Wenn du die Zwei-Faktor-Authentisierung (2FA) mit einer Authentifizierungs-App deines Smartphones aktivierst, benötigst du neben dem regulären Passwort zusätzlich auch den zeitbasierten Code der 2FA-App, um dich anmelden zu können. enable: Aktivieren - instructions_html: "Scanne diesen QR-Code mit einer beliebigen Authentisierungs-App (TOTP). Diese App generiert dann zeitbasierte Codes, die du beim Anmelden zusätzlich zum regulären Passwort eingeben musst." - manual_instructions: Wenn du den QR-Code nicht einscannen kannst, sondern die Zahlenfolge manuell eingeben musst, ist hier der geheime Token für deine 2FA-App. + instructions_html: "Scanne diesen QR-Code mit einer beliebigen Authentisierungs-App (TOTP) ein. Die App generiert dann zeitbasierte Codes, die du beim Anmelden zusätzlich zum regulären Passwort eingeben musst." + manual_instructions: 'Wenn du den QR-Code nicht einscannen kannst, sondern die Zahlenfolge manuell eingeben musst, ist hier der geheime Code für deine 2FA-App:' setup: Einrichten wrong_code: Der eingegebene Code ist ungültig! Laufen Serverzeit und Gerätezeit synchron? pagination: @@ -1741,7 +1771,7 @@ de: too_many_options: darf nicht mehr als %{max} Auswahlfelder enthalten vote: Abstimmen posting_defaults: - explanation: Diese Einstellungen werden standardmäßig für neue Beiträge verwendet. Du kannst sie auch beim Verfassen eines Beitrags individuell anpassen. + explanation: Diese Einstellungen werden standardmäßig für neue Beiträge verwendet, aber du kannst sie für jeden Beitrag individuell anpassen. preferences: other: Erweitert posting_defaults: Standardeinstellungen für Beiträge @@ -1868,7 +1898,7 @@ de: profile: Öffentliches Profil relationships: Follower und Folge ich severed_relationships: Getrennte Beziehungen - statuses_cleanup: Automatische Löschung + statuses_cleanup: Automatisiertes Löschen strikes: Maßnahmen two_factor_authentication: Zwei-Faktor-Authentisierung webauthn_authentication: Sicherheitsschlüssel @@ -1897,6 +1927,9 @@ de: other: "%{count} Videos" boosted_from_html: Geteilt von %{acct_link} content_warning: 'Inhaltswarnung: %{warning}' + content_warnings: + hide: Beitrag ausblenden + show: Beitrag anzeigen default_language: Wie die Sprache des Webinterface disallowed_hashtags: one: 'enthält einen nicht-erlaubten Hashtag: %{tags}' @@ -1905,16 +1938,22 @@ de: errors: in_reply_not_found: Der Beitrag, auf den du antworten möchtest, scheint nicht zu existieren. quoted_status_not_found: Der Beitrag, den du zitieren möchtest, scheint nicht zu existieren. + quoted_user_not_mentioned: Nur ein erwähntes Profil kann in einer privaten Erwähnung zitiert werden. over_character_limit: Begrenzung von %{max} Zeichen überschritten pin_errors: direct: Beiträge, die nur für erwähnte Profile sichtbar sind, können nicht angeheftet werden limit: Du hast bereits die maximale Anzahl an Beiträgen angeheftet ownership: Du kannst nur eigene Beiträge anheften reblog: Du kannst keine geteilten Beiträge anheften + quote_error: + not_available: Beitrag nicht verfügbar + pending_approval: Veröffentlichung ausstehend + revoked: Beitrag durch Autor*in entfernt quote_policies: - followers: Nur Follower + followers: Nur Follower und ich nobody: Nur ich public: Alle + quote_post_author: Zitierte %{acct} title: "%{name}: „%{quote}“" visibilities: direct: Private Erwähnung @@ -1989,7 +2028,7 @@ de: too_many_requests: Der Übersetzungsdienst hat kürzlich zu viele Anfragen erhalten. two_factor_authentication: add: Hinzufügen - disable: Zwei-Faktor-Authentisierung deaktivieren + disable: Zwei-Faktor-Authentisierung (2FA) deaktivieren disabled_success: Zwei-Faktor-Authentisierung erfolgreich deaktiviert edit: Bearbeiten enabled: Zwei-Faktor-Authentisierung (2FA) ist aktiviert @@ -2047,7 +2086,7 @@ de: title: Wichtige Mitteilung warning: appeal: Einspruch erheben - appeal_description: Wenn du glaubst, dass es sich um einen Fehler handelt, kannst du einen Einspruch an die Administration von %{instance} senden. + appeal_description: Solltest du der Meinung sein, dass es sich bei der Sperre um einen Fehler handelt, kannst du Einspruch erheben, in dem du dich an das Team von %{instance} wendest. categories: spam: Spam violation: Inhalt verstößt gegen die folgenden Serverregeln @@ -2057,7 +2096,7 @@ de: mark_statuses_as_sensitive: Ein oder mehrere deiner Beiträge wurden von den Moderator*innen von %{instance} mit einer Inhaltswarnung versehen. Das bedeutet, dass die Medien in den Beiträgen erst angeklickt werden müssen, bevor sie angezeigt werden. Beim Verfassen der nächsten Beiträge kannst du auch selbst eine Inhaltswarnung für hochgeladene Medien festlegen. sensitive: Von nun an werden alle deine hochgeladenen Mediendateien mit einer Inhaltswarnung versehen und hinter einer Warnung versteckt. silence: Du kannst dein Konto weiterhin verwenden, aber nur Personen, die dir bereits folgen, sehen deine Beiträge auf diesem Server. Ebenso kannst du aus verschiedenen Suchfunktionen ausgeschlossen werden. Andere können dir jedoch weiterhin manuell folgen. - suspend: Du kannst dein Konto nicht mehr verwenden und dein Profil und andere Daten sind nicht mehr verfügbar. Du kannst dich immer noch anmelden, um eine Sicherung deiner Daten anzufordern, bis die Daten innerhalb von 30 Tagen vollständig gelöscht wurden. Allerdings werden wir einige Daten speichern, um zu verhindern, dass du die Sperrung umgehst. + suspend: Du kannst dein Konto nicht mehr verwenden und dein Profil und weitere Daten sind nicht mehr verfügbar. Du kannst dich immer noch anmelden, um eine Sicherung deiner Daten anzufordern, bis die Daten innerhalb von 30 Tagen vollständig gelöscht wurden. Allerdings werden wir einige Daten behalten, um zu verhindern, dass du die Kontosperre umgehst. reason: 'Begründung:' statuses: 'Betroffene Beiträge:' subject: @@ -2086,7 +2125,7 @@ de: edit_profile_action: Personalisieren edit_profile_step: Mit einem vollständigen Profil interagieren andere eher mit dir. edit_profile_title: Personalisiere dein Profil - explanation: Hier sind ein paar Tipps, um loszulegen + explanation: Hier sind ein paar Tipps für den Start feature_action: Mehr erfahren feature_audience: Mastodon bietet dir eine einzigartige Möglichkeit, deine Reichweite ohne Mittelsperson zu verwalten. Auf deiner eigenen Infrastruktur bereitgestellt, ermöglicht Mastodon es dir, jedem anderen Mastodon-Server zu folgen und von jedem anderen Server aus gefolgt zu werden. Ebenso steht Mastodon unter deiner Kontrolle. feature_audience_title: Baue deine Reichweite mit Vertrauen auf @@ -2119,8 +2158,8 @@ de: users: follow_limit_reached: Du kannst nicht mehr als %{limit} Profilen folgen go_to_sso_account_settings: Kontoeinstellungen des Identitätsanbieters aufrufen - invalid_otp_token: Ungültiger Code der Zwei-Faktor-Authentisierung - otp_lost_help_html: Wenn du beides nicht mehr weißt, melde dich bitte bei uns unter der E-Mail-Adresse %{email} + invalid_otp_token: Ungültiger Code der Zwei-Faktor-Authentisierung (2FA) + otp_lost_help_html: Wenn du sowohl die E-Mail-Adresse als auch das Passwort nicht mehr weißt, melde dich bitte bei uns unter %{email} rate_limited: Zu viele Authentisierungsversuche. Bitte versuche es später noch einmal. seamless_external_login: Du bist über einen externen Dienst angemeldet, daher sind Passwort- und E-Mail-Einstellungen nicht verfügbar. signed_in_as: 'Angemeldet als:' @@ -2149,3 +2188,6 @@ de: not_supported: Dieser Browser unterstützt keine Sicherheitsschlüssel otp_required: Um Sicherheitsschlüssel zu verwenden, aktiviere zunächst die Zwei-Faktor-Authentisierung. registered_on: Registriert am %{date} + wrapstodon: + description: Sieh dir an, wie %{name} dieses Jahr Mastodon verwendet hat! + title: Wrapstodon %{year} für %{name} diff --git a/config/locales/devise.be.yml b/config/locales/devise.be.yml index 21dd93e31abc17..2425a7bff25b84 100644 --- a/config/locales/devise.be.yml +++ b/config/locales/devise.be.yml @@ -7,6 +7,7 @@ be: send_paranoid_instructions: Калі адрас Вашай электроннай пошты існуе ў нашай базе даных, на працягу некалькіх хвілін Вы атрымаеце ліст з інструкцыямі, каб пацвердзіць Вашу электронную пошту. Калі Вы не знойдзеце ліст, праверце папку са спамам. failure: already_authenticated: Вы ўжо ўвайшлі. + closed_registrations: Ваша спроба рэгістрацыі была заблакіравана праз сеткавую палітыку. Калі Вы лічыце, што гэта было зроблена памылкова, дашліце ліст на %{email}. inactive: Ваш уліковы запіс яшчэ не актываваны. invalid: Няправільны %{authentication_keys} або пароль. last_attempt: У вас ёсць яшчэ адна спроба, перш чым Ваш уліковы запіс будзе заблакіраваны. diff --git a/config/locales/devise.bg.yml b/config/locales/devise.bg.yml index 5cb41fa6163623..612658b6bd722b 100644 --- a/config/locales/devise.bg.yml +++ b/config/locales/devise.bg.yml @@ -7,6 +7,7 @@ bg: send_paranoid_instructions: Ако вашият имейл адрес съществува в нашата база данни, ще получите имейл с указания как да потвърдите имейл адреса си след няколко минути. Проверете спам папката си, ако не сте получили такъв имейл. failure: already_authenticated: Вече сте влезли. + closed_registrations: Вашият опит за регистриране е блокиран заради мрежова политика. Ако вярвате, че е грешка, то свържете се с %{email}. inactive: Акаунтът ви още не е задействан. invalid: Невалиден %{authentication_keys} или парола. last_attempt: Разполагате с още един опит преди акаунтът ви да се заключи. diff --git a/config/locales/devise.ca.yml b/config/locales/devise.ca.yml index f228843863e5e4..924c9cf7fdfad0 100644 --- a/config/locales/devise.ca.yml +++ b/config/locales/devise.ca.yml @@ -9,6 +9,7 @@ ca: Si us plau, verifica la carpeta de la brossa si no l'has rebut. failure: already_authenticated: Ja t'has registrat. + closed_registrations: El vostre intent de registre s'ha blocat per una política de xarxa. Si creieu que és un error, contacteu %{email}. inactive: El teu compte encara no s'ha activat. invalid: "%{authentication_keys} o contrasenya no són vàlids." last_attempt: Tens un intent més abans no es bloqui el teu compte. diff --git a/config/locales/devise.cs.yml b/config/locales/devise.cs.yml index 42ecc1d53e4a2c..e9aa54c65bfa98 100644 --- a/config/locales/devise.cs.yml +++ b/config/locales/devise.cs.yml @@ -7,6 +7,7 @@ cs: send_paranoid_instructions: Pokud je vaše e-mailová adresa v naší databázi, obdržíte za několik minut e-mail s instrukcemi pro potvrzení vaší e-mailové adresy. Pokud tento e-mail neobdržíte, podívejte se prosím také do složky „spam“. failure: already_authenticated: Již jste přihlášeni. + closed_registrations: Váš pokus o registraci byl zablokován z důvodů síťové politiky. Pokud se domníváte, že se jedná o chybu, kontaktujte %{email}. inactive: Váš účet ještě není aktivován. invalid: Neplatné %{authentication_keys} nebo heslo. last_attempt: Máte ještě jeden pokus, než bude váš účet uzamčen. diff --git a/config/locales/devise.cy.yml b/config/locales/devise.cy.yml index da383f70ae4704..7f3564dba35194 100644 --- a/config/locales/devise.cy.yml +++ b/config/locales/devise.cy.yml @@ -7,6 +7,7 @@ cy: send_paranoid_instructions: Os yw eich cyfeiriad e-bost yn bodoli yn ein cronfa ddata, byddwch yn derbyn e-bost gyda chyfarwyddiadau ar sut i gadarnhau eich cyfeiriad e-bost mewn ychydig funudau. Gwiriwch eich ffolder sbam os na dderbynioch yr e-bost hwn. failure: already_authenticated: Rydych chi eisoes wedi mewngofnodi. + closed_registrations: Mae eich ymgais i gofrestru wedi'i rhwystro oherwydd polisi rhwydwaith. Os ydych chi'n credu bod hwn yn wall, cysylltwch â %{email}. inactive: Nid yw eich cyfrif yn weithredol eto. invalid: "%{authentication_keys} neu gyfrinair annilys." last_attempt: Mae gennych un cyfle arall cyn i'ch cyfrif gael ei gloi. diff --git a/config/locales/devise.da.yml b/config/locales/devise.da.yml index e932ba0c9a423e..365a4347ea0cde 100644 --- a/config/locales/devise.da.yml +++ b/config/locales/devise.da.yml @@ -7,6 +7,7 @@ da: send_paranoid_instructions: Findes din e-mailadresse allerede i vores database, skulle du om få minutter modtage en e-mailvejledning til, hvordan din e-mailadresse bekræftes. Tjek spammappen, hvis e-mailen ikke ser ud til at lande i indbakken. failure: already_authenticated: Du er allerede logget ind. + closed_registrations: Dit registreringsforsøg er blevet blokeret på grund af en netværkspolitik. Hvis du mener, at dette er en fejl, så kontakt %{email}. inactive: Din konto er endnu ikke aktiveret. invalid: Ugyldig %{authentication_keys} eller adgangskode. last_attempt: Du har ét forsøg mere, før din konto bliver låst. diff --git a/config/locales/devise.de.yml b/config/locales/devise.de.yml index 706fdfbee1cfc8..a968d474796769 100644 --- a/config/locales/devise.de.yml +++ b/config/locales/devise.de.yml @@ -7,6 +7,7 @@ de: send_paranoid_instructions: Falls deine E-Mail-Adresse in unserer Datenbank hinterlegt ist, wirst du in wenigen Minuten eine E-Mail erhalten. Darin wird erklärt, wie du deine E-Mail-Adresse bestätigen kannst. Schau bitte auch in deinem Spam-Ordner nach, wenn du diese E-Mail nicht erhalten hast. failure: already_authenticated: Du bist bereits angemeldet. + closed_registrations: Deine Registrierung wurde wegen einer Netzwerkrichtlinie abgelehnt. Solltest du die Vermutung haben, dass es sich um einen Fehler handelt, wende dich bitte an %{email}. inactive: Dein Konto wurde noch nicht aktiviert. invalid: "%{authentication_keys} oder Passwort ungültig." last_attempt: Du hast nur noch einen Versuch, bevor dein Zugang gesperrt wird. diff --git a/config/locales/devise.el.yml b/config/locales/devise.el.yml index 106a48a49e603d..86134b9491f40b 100644 --- a/config/locales/devise.el.yml +++ b/config/locales/devise.el.yml @@ -7,7 +7,8 @@ el: send_paranoid_instructions: Αν η διεύθυνση email σου υπάρχει στη βάση μας, θα λάβεις σε μερικά λεπτά ένα email με οδηγίες επιβεβαίωσης της διεύθυνσής σου. Παρακαλούμε έλεγξε το φάκελο με τα ανεπιθύμητα αν δεν το έχεις λάβει. failure: already_authenticated: Έχεις ήδη συνδεθεί. - inactive: Ο λογαριασμός σου δεν έχει ενεργοποιηθεί ακόμα. + closed_registrations: Η προσπάθεια εγγραφής σας έχει αποκλειστεί λόγω μιας πολιτικής δικτύου. Αν πιστεύετε ότι πρόκειται για σφάλμα, επικοινωνήστε με το %{email}. + inactive: Ο λογαριασμός σου δεν έχει ενεργοποιηθεί ακόμη. invalid: Λάθος %{authentication_keys} ή συνθηματικό. last_attempt: Έχεις μια ακόμα προσπάθεια πριν κλειδωθεί ο λογαριασμός σου. locked: Ο λογαριασμός σου κλειδώθηκε. diff --git a/config/locales/devise.en-GB.yml b/config/locales/devise.en-GB.yml index 1127735ca0cab5..c4650577d77717 100644 --- a/config/locales/devise.en-GB.yml +++ b/config/locales/devise.en-GB.yml @@ -7,6 +7,7 @@ en-GB: send_paranoid_instructions: If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes. Please check your spam folder if you didn't receive this email. failure: already_authenticated: You are already logged in. + closed_registrations: Your registration attempt has been blocked due to a network policy. If you believe this is an error, contact %{email}. inactive: Your account is not activated yet. invalid: Invalid %{authentication_keys} or password. last_attempt: You have one more attempt before your account is locked. @@ -22,66 +23,66 @@ en-GB: action: Verify email address action_with_app: Confirm and return to %{app} explanation: You have created an account on %{host} with this email address. You are one click away from activating it. If this wasn't you, please ignore this email. - explanation_when_pending: You applied for an invite to %{host} with this email address. Once you confirm your e-mail address, we will review your application. You can log in to change your details or delete your account, but you cannot access most of the functions until your account is approved. If your application is rejected, your data will be removed, so no further action will be required from you. If this wasn't you, please ignore this email. + explanation_when_pending: You applied for an invite to %{host} with this email address. Once you confirm your email address, we will review your application. You can log in to change your details or delete your account, but you cannot access most of the functions until your account is approved. If your application is rejected, your data will be removed, so no further action will be required from you. If this wasn't you, please ignore this email. extra_html: Please also check out the rules of the server and our terms of service. - subject: 'Mastodon: Confirmation instructions for %{instance}' + subject: 'Mastodon: confirmation instructions for %{instance}' title: Verify email address email_changed: explanation: 'The email address for your account is being changed to:' extra: If you did not change your email, it is likely that someone has gained access to your account. Please change your password immediately or contact the server admin if you're locked out of your account. - subject: 'Mastodon: Email changed' + subject: 'Mastodon: email changed' title: New email address password_change: explanation: The password for your account has been changed. extra: If you did not change your password, it is likely that someone has gained access to your account. Please change your password immediately or contact the server admin if you're locked out of your account. - subject: 'Mastodon: Password changed' + subject: 'Mastodon: password changed' title: Password changed reconfirmation_instructions: explanation: Confirm the new address to change your email. extra: If this change wasn't initiated by you, please ignore this email. The email address for the Mastodon account won't change until you access the link above. - subject: 'Mastodon: Confirm email for %{instance}' + subject: 'Mastodon: confirm email for %{instance}' title: Verify email address reset_password_instructions: action: Change password explanation: You requested a new password for your account. extra: If you didn't request this, please ignore this email. Your password won't change until you access the link above and create a new one. - subject: 'Mastodon: Reset password instructions' + subject: 'Mastodon: reset password instructions' title: Password reset two_factor_disabled: - explanation: Login is now possible using only e-mail address and password. - subject: 'Mastodon: Two-factor authentication disabled' + explanation: Login is now possible using only email address and password. + subject: 'Mastodon: two-factor authentication disabled' subtitle: Two-factor authentication for your account has been disabled. title: 2FA disabled two_factor_enabled: explanation: A token generated by the paired TOTP app will be required for login. - subject: 'Mastodon: Two-factor authentication enabled' + subject: 'Mastodon: two-factor authentication enabled' subtitle: Two-factor authentication has been enabled for your account. title: 2FA enabled two_factor_recovery_codes_changed: explanation: The previous recovery codes have been invalidated and new ones generated. - subject: 'Mastodon: Two-factor recovery codes re-generated' + subject: 'Mastodon: two-factor recovery codes re-generated' subtitle: The previous recovery codes have been invalidated and new ones generated. title: 2FA recovery codes changed unlock_instructions: - subject: 'Mastodon: Unlock instructions' + subject: 'Mastodon: unlock instructions' webauthn_credential: added: explanation: The following security key has been added to your account - subject: 'Mastodon: New security key' + subject: 'Mastodon: new security key' title: A new security key has been added deleted: explanation: The following security key has been deleted from your account - subject: 'Mastodon: Security key deleted' + subject: 'Mastodon: security key deleted' title: One of your security keys has been deleted webauthn_disabled: explanation: Authentication with security keys has been disabled for your account. extra: Login is now possible using only the token generated by the paired TOTP app. - subject: 'Mastodon: Authentication with security keys disabled' + subject: 'Mastodon: authentication with security keys disabled' title: Security keys disabled webauthn_enabled: explanation: Security key authentication has been enabled for your account. extra: Your security key can now be used for login. - subject: 'Mastodon: Security key authentication enabled' + subject: 'Mastodon: security key authentication enabled' title: Security keys enabled omniauth_callbacks: failure: Could not authenticate you from %{kind} because “%{reason}”. diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml index 65095cd788c54d..29cc473e019d7e 100644 --- a/config/locales/devise.en.yml +++ b/config/locales/devise.en.yml @@ -7,6 +7,7 @@ en: send_paranoid_instructions: If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes. Please check your spam folder if you didn't receive this email. failure: already_authenticated: You are already signed in. + closed_registrations: Your registration attempt has been blocked due to a network policy. If you believe this is an error, contact %{email}. inactive: Your account is not activated yet. invalid: Invalid %{authentication_keys} or password. last_attempt: You have one more attempt before your account is locked. diff --git a/config/locales/devise.es-AR.yml b/config/locales/devise.es-AR.yml index f71b8473ec23a6..a4084441a91215 100644 --- a/config/locales/devise.es-AR.yml +++ b/config/locales/devise.es-AR.yml @@ -7,6 +7,7 @@ es-AR: send_paranoid_instructions: Si tu dirección de correo electrónico existe en nuestra base de datos, en unos minutos, vas a recibir un correo electrónico con instrucciones sobre cómo confirmar tu dirección de correo. Si pasa el tiempo y no recibiste ningún mensaje, por favor, revisá tu carpeta de correo basura / no deseado / spam. failure: already_authenticated: Ya iniciaste sesión. + closed_registrations: Tu intento de registro fue bloqueado debido a una política de red. Si creés que esto es un error, ponete en contacto con %{email}. inactive: Tu cuenta todavía no está activada. invalid: "%{authentication_keys} o contraseña no válidas." last_attempt: Tenés un intento más antes de que se bloquee tu cuenta. diff --git a/config/locales/devise.es-MX.yml b/config/locales/devise.es-MX.yml index 2e8ddc2e40ac0b..de2a4a8d3b9d3a 100644 --- a/config/locales/devise.es-MX.yml +++ b/config/locales/devise.es-MX.yml @@ -7,6 +7,7 @@ es-MX: send_paranoid_instructions: Si su dirección de correo electrónico existe en nuestra base de datos, recibirá un correo electrónico con instrucciones sobre cómo confirmar su dirección de correo en pocos minutos. failure: already_authenticated: Usted ya está registrado. + closed_registrations: Su intento de registro ha sido bloqueado debido a una política de red. Si cree que se trata de un error, póngase en contacto con %{email}. inactive: Su cuenta no ha sido activada aún. invalid: "%{authentication_keys} o contraseña inválida." last_attempt: Tiene un intento más antes de que tu cuenta sea bloqueada. diff --git a/config/locales/devise.es.yml b/config/locales/devise.es.yml index ddfc1ba678c6d0..1813ab6d914d17 100644 --- a/config/locales/devise.es.yml +++ b/config/locales/devise.es.yml @@ -7,6 +7,7 @@ es: send_paranoid_instructions: Si su dirección de correo electrónico existe en nuestra base de datos, recibirá un correo electrónico con instrucciones sobre cómo confirmar su dirección de correo en pocos minutos. failure: already_authenticated: Usted ya está registrado. + closed_registrations: Su intento de registro ha sido bloqueado debido a una política de red. Si cree que esto es un error, póngase en contacto con %{email}. inactive: Su cuenta no ha sido activada aún. invalid: "%{authentication_keys} o contraseña inválida." last_attempt: Tiene un intento más antes de que tu cuenta sea bloqueada. diff --git a/config/locales/devise.et.yml b/config/locales/devise.et.yml index eb2e2951da645d..5843761ddba932 100644 --- a/config/locales/devise.et.yml +++ b/config/locales/devise.et.yml @@ -7,6 +7,7 @@ et: send_paranoid_instructions: Kui sinu e-postiaadress on meie andmebaasis, saad paari minuti pärast juhistega e-kirja, kuidas oma e-posti aadress kinnitada. Palun kontrolli oma rämpsposti kausta, kui selline e-kiri ei saabunud. failure: already_authenticated: Oled juba sisse loginud. + closed_registrations: Sinu registreerimiskatse on võrgureeglite alusel blokeeritud. Kui sa arvad, et see poleks pidanud nii olema, siis kirjuta e-posti aadressile %{email}. inactive: Sinu konto pole veel aktiveeritud. invalid: Valed %{authentication_keys} või salasõna. last_attempt: Sul on veel üks katse, enne kui konto lukustatakse. diff --git a/config/locales/devise.eu.yml b/config/locales/devise.eu.yml index 4b3bbea8c6be4b..7189901fd18204 100644 --- a/config/locales/devise.eu.yml +++ b/config/locales/devise.eu.yml @@ -7,6 +7,7 @@ eu: send_paranoid_instructions: Zure eposta helbidea aurretik gure datu-basean bazegoen, eposta mezu bat jasoko duzu minutu batzuk barru zure eposta helbidea berresteko argibideekin. Begiratu zure spam karpetan ez baduzu mezua jaso. failure: already_authenticated: Saioa hasi duzu jada. + closed_registrations: Zure erregistro saiakera blokeatu egin da, sareko politika baten ondorioz. Erabaki okerra dela uste baduzu, kontaktatu %{email}. inactive: Zure kontua ez dago oraindik aktibo. invalid: Baliogabeko %{authentication_keys} edo pasahitza. last_attempt: Saiakera bat geratzen zaizu zure kontua giltzapetu aurretik. diff --git a/config/locales/devise.fa.yml b/config/locales/devise.fa.yml index 71cd7699fe2393..31001d2c1a7ad7 100644 --- a/config/locales/devise.fa.yml +++ b/config/locales/devise.fa.yml @@ -7,6 +7,7 @@ fa: send_paranoid_instructions: اگر نشانی رایانامه‌تان در پایگاه داده‌مان وجود داشته باشد، تا دقایقی دیگر تا دقایقی دیگر رایانامه‌ای با دستورالعمل تأیید نشانی رایانامه‌تان دریافت خواهید کرد. اگر این رایانامه را نگرفتید، لطفاً پوشهٔ هرزنامه‌هایتان را بررسی کنید. failure: already_authenticated: از پیش وارد شده‌اید. + closed_registrations: تلاشتان برا ثبت‌نام به دلیل سیاست شبکه مسدود شد. اگر باور دارید خطایی رخ داده با %{email} تماس بگیرید. inactive: هنوز حسابتان فعّال نشده. invalid: "%{authentication_keys} یا گذرواژه نامعتبر." last_attempt: پیش از آن که حساب شما قفل شود، یک فرصت دیگر دارید. diff --git a/config/locales/devise.fi.yml b/config/locales/devise.fi.yml index d230da1f547fb0..0b256f85c0a238 100644 --- a/config/locales/devise.fi.yml +++ b/config/locales/devise.fi.yml @@ -7,6 +7,7 @@ fi: send_paranoid_instructions: Jos sähköpostiosoitteesi on tiedossammme, saat pian sähköpostiisi ohjeet sen vahvistamiseen. Jos viestiä ei kuulu, tarkista roskapostikansiosi. failure: already_authenticated: Olet jo kirjautunut sisään. + closed_registrations: Rekisteröitymisyrityksesi on estynyt verkkokäytännön vuoksi. Jos uskot, että tämä on virhe, ota yhteys sähköpostiosoitteeseen %{email}. inactive: Tiliäsi ei ole vielä aktivoitu. invalid: Virheellinen %{authentication_keys} tai salasana. last_attempt: Sinulla on vielä yksi yritys ennen kuin tilisi lukitaan. @@ -46,7 +47,7 @@ fi: explanation: Pyysit tilillesi uuden salasanan. extra: Jos et tehnyt pyyntöä itse, voit jättää tämän viestin huomiotta. Salasanaasi ei vaihdeta, ennen kuin painat edellä olevaa linkkiä ja luot uuden salasanan. subject: 'Mastodon: ohjeet salasanan vaihtoon' - title: Salasanan vaihto + title: Salasanan palautus two_factor_disabled: explanation: Sisäänkirjautuminen on nyt mahdollista pelkällä sähköpostiosoitteella ja salasanalla. subject: 'Mastodon: kaksivaiheinen todennus poistettu käytöstä' diff --git a/config/locales/devise.fo.yml b/config/locales/devise.fo.yml index f9783f30cc3301..aebdfc34e66fce 100644 --- a/config/locales/devise.fo.yml +++ b/config/locales/devise.fo.yml @@ -7,6 +7,7 @@ fo: send_paranoid_instructions: Um tín teldupostaddresa longu er í dátugrunninum, fær tú er um fáa minuttir ein teldupost, við frágreiðing um, hvussu hendan kann váttast. Kanna tín Spam faldara, um tú ikki sær henda. failure: already_authenticated: Tú ert longu innskrivað/ur. + closed_registrations: Tín skrásetingarroynd er blokerað vegna ein netverkspolitikk. Kontakta %{email}, um tú heldur, at hetta er ein feilur. inactive: Kontan hjá tær er ikki virkin enn. invalid: Skeivt %{authentication_keys} ella loyniorð. last_attempt: Tú kanst royna einaferð afturat áðrenn kontan verður stongd. diff --git a/config/locales/devise.fr-CA.yml b/config/locales/devise.fr-CA.yml index fbe549743c96ee..1acb5d1770f27b 100644 --- a/config/locales/devise.fr-CA.yml +++ b/config/locales/devise.fr-CA.yml @@ -7,6 +7,7 @@ fr-CA: send_paranoid_instructions: Si votre adresse courriel existe dans notre base de données, vous allez recevoir un courriel contenant les instructions de confirmation de votre adresse. Veuillez vérifier votre dossier de pourriels si vous n'avez pas reçu ce message. failure: already_authenticated: Vous êtes déjà connecté·e. + closed_registrations: Votre tentative d'inscription a été bloquée en raison d'une politique de réseau. Si vous pensez qu'il s'agit d'une erreur, contactez %{email}. inactive: Votre compte n’est pas encore activé. invalid: "%{authentication_keys} ou mot de passe invalide." last_attempt: Vous avez droit à une dernière tentative avant que votre compte ne soit verrouillé. diff --git a/config/locales/devise.fr.yml b/config/locales/devise.fr.yml index 10c49b22e661ff..c2147fcf9fcae8 100644 --- a/config/locales/devise.fr.yml +++ b/config/locales/devise.fr.yml @@ -7,6 +7,7 @@ fr: send_paranoid_instructions: Si votre adresse électronique existe dans notre base de données, vous allez bientôt recevoir un courriel contenant les instructions de confirmation de votre compte. Veuillez, dans le cas où vous ne recevriez pas ce message, vérifier votre dossier d’indésirables. failure: already_authenticated: Vous êtes déjà connecté⋅e. + closed_registrations: Votre tentative d'inscription a été bloquée en raison d'une politique de réseau. Si vous pensez qu'il s'agit d'une erreur, contactez %{email}. inactive: Votre compte n’est pas encore activé. invalid: "%{authentication_keys} ou mot de passe invalide." last_attempt: Vous avez droit à une dernière tentative avant que votre compte ne soit verrouillé. diff --git a/config/locales/devise.ga.yml b/config/locales/devise.ga.yml index 5d03c2a8e69bb2..e0bf43f45746c6 100644 --- a/config/locales/devise.ga.yml +++ b/config/locales/devise.ga.yml @@ -7,6 +7,7 @@ ga: send_paranoid_instructions: Más ann do do r-phost inár mbonneagar, gheobhaidh tú r-phost i gceann cúpla nóiméad faoi conas do sheoladh r-phost a dheimhniú. Féach i d'fhillteán turscair mura bhfuair tú an r-phost seo. failure: already_authenticated: Tá tú sínithe isteach cheana. + closed_registrations: Tá bac curtha ar d’iarracht clárúcháin mar gheall ar pholasaí líonra. Má chreideann tú gur earráid í seo, déan teagmháil le %{email}. inactive: Níl do chuntas gníomhachtaithe fós. invalid: "%{authentication_keys} nó pasfhocal neamhbhailí." last_attempt: Tá iarracht amháin eile agat sula gcuirtear do chuntas faoi ghlas. diff --git a/config/locales/devise.gd.yml b/config/locales/devise.gd.yml index d110bc799b68ba..c7ddce255897c9 100644 --- a/config/locales/devise.gd.yml +++ b/config/locales/devise.gd.yml @@ -7,6 +7,7 @@ gd: send_paranoid_instructions: Ma tha an seòladh puist-d agad san stòr-dàta againn, gheibh thu post-d an ceann corra mionaid le stiùireadh air mar a dhearbhas tu an seòladh puist-d agad. Thoir sùil air pasgan an spama agad mura faigh thu am post-d seo. failure: already_authenticated: Tha thu air do chlàradh a-steach mu thràth. + closed_registrations: Chaidh d’ oidhirp clàraidh a bhacadh ri linn poileasaidh lìonraidh. Ma tha thu dhen bheachd gur e mearachd a th’ ann, cuir fios gu %{email}. inactive: Cha deach an cunntas agad a ghnìomhachadh fhathast. invalid: "%{authentication_keys} no facal-faire mì-dhligheach." last_attempt: Tha aon oidhirp eile agad mus dèid an cunntas agad a ghlasadh. diff --git a/config/locales/devise.gl.yml b/config/locales/devise.gl.yml index 6bbec181b805d4..04aed22dfe6cf2 100644 --- a/config/locales/devise.gl.yml +++ b/config/locales/devise.gl.yml @@ -7,6 +7,7 @@ gl: send_paranoid_instructions: Se o teu enderezo de email xa existira na nosa base de datos, vas recibir un correo coas instrucións de confirmación dentro dalgúns minutos. Por favor, comproba o cartafol de spam se non recibiches o correo. failure: already_authenticated: Xa estás conectada. + closed_registrations: Bloqueouse o teu intento de crear unha conta debido a unha directiva da rede. Se cres que é un erro, contacta con %{email}. inactive: A túa conta aínda non está activada. invalid: "%{authentication_keys} ou contrasinal non validos." last_attempt: Tes un intento máis antes de que a túa conta fique bloqueada. diff --git a/config/locales/devise.he.yml b/config/locales/devise.he.yml index 91eb75f9d1551f..085a4feaf73caa 100644 --- a/config/locales/devise.he.yml +++ b/config/locales/devise.he.yml @@ -7,6 +7,7 @@ he: send_paranoid_instructions: אם כתובת הדוא"ל שלך קיימת במסד הנתונים, יתקבל בדקות הקרובות דוא"ל עם הוראות לאימות כתובתך. יש לבדוק את תיבת הספאם ליתר בטחון אם ההודעה לא הגיעה תוך דקות ספורות. failure: already_authenticated: חשבון זה כבר מחובר. + closed_registrations: נסיון ההרשמה שלך נחסם עקב מדיניות רשת. אם לדידכם מדובר בטעות, אנא צרו קשר עם %{email}. inactive: חשבון זה טרם הופעל. invalid: "%{authentication_keys} או סיסמה לא נכונים." last_attempt: יש לך עוד ניסיון אחד לפני נעילת החשבון. diff --git a/config/locales/devise.hu.yml b/config/locales/devise.hu.yml index 5ea81bef0d18b9..3928b9f4454aba 100644 --- a/config/locales/devise.hu.yml +++ b/config/locales/devise.hu.yml @@ -7,6 +7,7 @@ hu: send_paranoid_instructions: Ha az e-mail címed már szerepel az adatbázisunkban, néhány percen belül kapsz egy levelet az e-mail cím megerősítésére vonatkozó utasításokkal. Kérjük, ellenőrizd a spam mappád, ha nem látod az e-mailt. failure: already_authenticated: Már bejelentkeztél. + closed_registrations: 'A regisztrációs kísérlet egy hálózati házirend miatt blokkolva volt. Ha úgy gondolod, hogy ez hiba, akkor vedd fel a kapcsolatot ezen a címen: %{email}.' inactive: Fiókodat még nem aktiválták. invalid: Helytelen %{authentication_keys} vagy jelszó. last_attempt: Már csak egy próbálkozásod maradt, mielőtt a fiókodat zároljuk. diff --git a/config/locales/devise.ia.yml b/config/locales/devise.ia.yml index eb580dde5daf42..198af01b5600f1 100644 --- a/config/locales/devise.ia.yml +++ b/config/locales/devise.ia.yml @@ -7,6 +7,7 @@ ia: send_paranoid_instructions: Si tu adresse de e-mail existe in nostre base de datos, tu recipera un e-mail con instructiones pro confirmar tu adresse de e-mail in poc minutas. Per favor consulta tu dossier de spam si tu non lo recipe. failure: already_authenticated: Tu ha jam aperite session. + closed_registrations: Tu tentativa de inscription ha essite blocate a causa de un politica de rete. Si tu crede que isto es un error, contacta %{email}. inactive: Tu conto non es ancora activate. invalid: "%{authentication_keys} o contrasigno non valide." last_attempt: Tu ha solmente un altere tentativa ante que tu conto es serrate. diff --git a/config/locales/devise.is.yml b/config/locales/devise.is.yml index a22c6d468f0674..b0c947f5d15600 100644 --- a/config/locales/devise.is.yml +++ b/config/locales/devise.is.yml @@ -7,6 +7,7 @@ is: send_paranoid_instructions: Ef tölvupóstfangið þitt fyrirfinnst í gagnagrunninum okkar, munt þú innan nokkurra mínútna fá tölvupóst með leiðbeiningunum um hvernig eigi að staðfesta tölvupóstfangið þitt. Skoðaðu í ruslpóstmöppuna þína ef þú færð ekki þennan tölvupóst. failure: already_authenticated: Þú ert nú þegar skráð(ur) inn. + closed_registrations: Vegna stefnu varðandi netkerfi var lokað á tilraun þín til skráningar. Ef þú heldur að þetta séu mistök, ættirðu að hafa samband við %{email}. inactive: Aðgangur þinn hefur ekki enn verið virkjaður. invalid: Ógilt %{authentication_keys} eða lykilorð. last_attempt: Þú getur reynt einu sinni í viðbót áður en aðgangnum þínum verður læst. diff --git a/config/locales/devise.it.yml b/config/locales/devise.it.yml index 8497b369bebf26..da5a36a7d1039c 100644 --- a/config/locales/devise.it.yml +++ b/config/locales/devise.it.yml @@ -7,6 +7,7 @@ it: send_paranoid_instructions: Se il tuo indirizzo email esiste nel nostro database, riceverai un'email con le istruzioni su come confermare il tuo indirizzo email in pochi minuti. Sei pregato di controllare la tua cartella dello spam se non hai ricevuto quest'email. failure: already_authenticated: Sei già connesso. + closed_registrations: Il tuo tentativo di registrazione è stato bloccato a causa di una politica di rete. Se ritieni che si tratti di un errore, contatta %{email}. inactive: Il tuo profilo non è ancora attivo. invalid: "%{authentication_keys} o password non valida." last_attempt: Hai un altro tentativo, prima che il tuo profilo venga bloccato. diff --git a/config/locales/devise.ko.yml b/config/locales/devise.ko.yml index b57ef9684c6828..ebdd3ed44993c9 100644 --- a/config/locales/devise.ko.yml +++ b/config/locales/devise.ko.yml @@ -7,6 +7,7 @@ ko: send_paranoid_instructions: 이메일 주소가 저희 데이터베이스에 있는 경우, 몇 분 내에 이메일 주소를 확인하는 방법에 대한 안내가 포함된 이메일을 받을 수 있습니다. 이 이메일을 받지 못했다면 스팸 폴더를 확인해 주세요. failure: already_authenticated: 이미 로그인 된 상태입니다. + closed_registrations: 네트워크 정책에 의해 가입 시도가 차단되었습니다. 오류라고 생각된다면 %{email}에 문의해주세요. inactive: 계정이 아직 활성화 되지 않았습니다. invalid: 올바르지 않은 %{authentication_keys} 혹은 암호입니다. last_attempt: 계정이 잠기기까지 한 번의 시도가 남았습니다. diff --git a/config/locales/devise.lad.yml b/config/locales/devise.lad.yml index 45e475045024a5..b294c1807bcce9 100644 --- a/config/locales/devise.lad.yml +++ b/config/locales/devise.lad.yml @@ -7,6 +7,7 @@ lad: send_paranoid_instructions: Si tu adreso de posta elektronika existe en muestra baza de datos, risiviras una posta elektronika kon instruksyones sobre komo konfirmar tu adreso de posta elektronika en pokos minutos. failure: already_authenticated: Ya te konektates kon tu kuento. + closed_registrations: Tu prova de enrejistrarte tiene sido blokada por a una politika de red. Si piensas ke esto es un yerro, eskrive a %{email}. inactive: Tu kuento ainda no tyene sido aktivado. invalid: "%{authentication_keys} o kod invalido." last_attempt: Aprova una vez mas antes de ke tu kuento sea blokado. diff --git a/config/locales/devise.lv.yml b/config/locales/devise.lv.yml index 4b31c56ee742c1..65aa164c726ce3 100644 --- a/config/locales/devise.lv.yml +++ b/config/locales/devise.lv.yml @@ -9,8 +9,8 @@ lv: already_authenticated: Tu jau pieteicies. inactive: Tavs konts vēl nav aktivizēts. invalid: Nederīga %{authentication_keys} vai parole. - last_attempt: Tev ir vēl viens mēģinājums, pirms tavs konts tiks bloķēts. - locked: Tavs konts ir bloķēts. + last_attempt: Tev ir vēl viens mēģinājums, pirms Tavs konts tiks slēgts. + locked: Tavs konts ir slēgts. not_found_in_database: Nederīga %{authentication_keys} vai parole. omniauth_user_creation_failure: Kļūda šīs identitātes konta izveidošanā. pending: Tavs konts joprojām tiek pārskatīts. @@ -63,7 +63,7 @@ lv: subtitle: Iepriekšējie atkopes kodi tika padarīti par nederīgiem, un tika izveidoti jauni. title: 2FA atkopes kodi nomainīti unlock_instructions: - subject: 'Mastodon: Norādījumi atbloķēšanai' + subject: 'Mastodon: atslēgšanas norādes' webauthn_credential: added: explanation: Tavam kontam ir pievienota šāda drošības atslēga @@ -110,7 +110,7 @@ lv: confirmation_period_expired: jāapstiprina %{period} laikā, lūdzu, pieprasi jaunu expired: ir beidzies derīguma termiņš, lūdzu, pieprasi jaunu not_found: nav atrasts - not_locked: nebija bloķēts + not_locked: nebija slēgts not_saved: one: '1 kļūda liedza saglabāt šo %{resource}:' other: "%{count} kļūdas liedza saglabāt šo %{resource}:" diff --git a/config/locales/devise.nan.yml b/config/locales/devise.nan.yml index 3a8933e37ceb6b..885e509fdea152 100644 --- a/config/locales/devise.nan.yml +++ b/config/locales/devise.nan.yml @@ -7,6 +7,8 @@ nan: pending: Lí ê口座iáu teh審查。 timeout: Lí ê作業階段kàu期ah。請koh登入,繼續完成。 mailer: + confirmation_instructions: + extra_html: Mā請斟酌讀服侍器規定kap服務規定。 two_factor_disabled: title: 雙因素認證關掉ah two_factor_enabled: diff --git a/config/locales/devise.nl.yml b/config/locales/devise.nl.yml index a80b5a7bfa0aa1..1e24d613482e2e 100644 --- a/config/locales/devise.nl.yml +++ b/config/locales/devise.nl.yml @@ -7,6 +7,7 @@ nl: send_paranoid_instructions: Als jouw e-mailadres in de database staat, ontvang je via e-mail instructies hoe je jouw account kunt bevestigen. Kijk tussen je spam wanneer niks werd ontvangen. failure: already_authenticated: Je bent al ingelogd. + closed_registrations: Jouw registratiepoging is geblokkeerd vanwege een netwerkbeleid. Wanneer je denkt dat dit een fout is, neem dan contact op via %{email}. inactive: Jouw account is nog niet geactiveerd. invalid: "%{authentication_keys} of wachtwoord ongeldig." last_attempt: Je hebt nog één poging over voordat jouw account wordt opgeschort. diff --git a/config/locales/devise.nn.yml b/config/locales/devise.nn.yml index cdf358ce839dd7..78e774f9c8502c 100644 --- a/config/locales/devise.nn.yml +++ b/config/locales/devise.nn.yml @@ -7,6 +7,7 @@ nn: send_paranoid_instructions: Om vi har e-postadressa di i databasen vår, får du ein e-post som fortel deg korleis du skal stadfesta e-postadressa om nokre få minutt. Ver venleg og sjekk søppelpostmappa di om du ikkje fekk denne e-posten. failure: already_authenticated: Du er allereie logga inn. + closed_registrations: Forsøket ditt på å registrera deg er blokkert på grunn av ein nettverksregel. Viss du meiner dette er feil, kan du kontakta %{email}. inactive: Kontoen din er ikkje aktiv enno. invalid: Ugyldig %{authentication_keys} eller passord. last_attempt: Du har eitt forsøk igjen før kontoen din vert låst. diff --git a/config/locales/devise.pl.yml b/config/locales/devise.pl.yml index 093ea6961dc5c7..1d5ad09c1bc804 100644 --- a/config/locales/devise.pl.yml +++ b/config/locales/devise.pl.yml @@ -7,6 +7,7 @@ pl: send_paranoid_instructions: Jeśli Twój adres e-mail już istnieje w naszej bazie danych, w ciągu kilku minut otrzymasz wiadomość e-mail z instrukcją jak potwierdzić Twój adres e-mail. Jeżeli nie otrzymano wiadomości, sprawdź folder ze spamem. failure: already_authenticated: Jesteś już zalogowany(-a). + closed_registrations: Twoja próba rejestracji została zablokowana ze względu na politykę sieciową. Jeśli uważasz, że jest to błąd, skontaktuj się z %{email}. inactive: Twoje konto nie zostało jeszcze aktywowane. invalid: Nieprawidłowy %{authentication_keys} lub hasło. last_attempt: Masz jeszcze jedną próbę; Twoje konto zostanie zablokowane jeśli się nie powiedzie. diff --git a/config/locales/devise.pt-BR.yml b/config/locales/devise.pt-BR.yml index aa1190dfd0f3e5..89a3800cf47d52 100644 --- a/config/locales/devise.pt-BR.yml +++ b/config/locales/devise.pt-BR.yml @@ -7,6 +7,7 @@ pt-BR: send_paranoid_instructions: Se o seu endereço de e-mail já existir em nosso banco de dados, você receberá um e-mail com instruções para confirmá-lo dentro de alguns minutos. Verifique sua caixa de spam caso ainda não o tenha recebido. failure: already_authenticated: Você entrou na sua conta. + closed_registrations: Sua tentativa de registro foi bloqueada devido a uma política de rede. Se você acredita que isso é um erro, entre em contato com %{email}. inactive: Sua conta não foi confirmada ainda. invalid: "%{authentication_keys} ou senha inválida." last_attempt: Você tem mais uma tentativa antes de sua conta ser bloqueada. diff --git a/config/locales/devise.pt-PT.yml b/config/locales/devise.pt-PT.yml index 9660c3edb07756..8db3487fb17adf 100644 --- a/config/locales/devise.pt-PT.yml +++ b/config/locales/devise.pt-PT.yml @@ -7,6 +7,7 @@ pt-PT: send_paranoid_instructions: Se o teu endereço de e-mail já existir na nossa base de dados, vais receber um e-mail com as instruções de confirmação dentro de alguns minutos. Verifica na caixa de spam se não recebeste o e-mail. failure: already_authenticated: Já iniciaste a sessão. + closed_registrations: A sua tentativa de registo foi bloqueada devido a uma política de rede. Se acredita que se trata de um erro, contacte %{email}. inactive: A tua conta ainda não está ativada. invalid: "%{authentication_keys} ou palavra-passe inválida." last_attempt: Tens mais uma tentativa antes de a tua conta ser bloqueada. diff --git a/config/locales/devise.ru.yml b/config/locales/devise.ru.yml index cc95f730615a3a..60d731898130d0 100644 --- a/config/locales/devise.ru.yml +++ b/config/locales/devise.ru.yml @@ -7,6 +7,7 @@ ru: send_paranoid_instructions: В течение нескольких минут вы получите письмо с инструкциями по его подтверждению, при условии что на ваш адрес электронной почты зарегистрирована учётная запись. Если письмо не приходит, проверьте папку «Спам». failure: already_authenticated: Вы уже авторизованы. + closed_registrations: Ваша попытка регистрации была заблокирована в связи с сетевой политикой. Если вы считаете, что произошла ошибка, свяжитесь с %{email}. inactive: Ваша учётная запись ещё не активирована. invalid: "%{authentication_keys} или пароль введён неверно." last_attempt: У вас осталась последняя попытка ввода пароля до блокировки учётной записи. diff --git a/config/locales/devise.sk.yml b/config/locales/devise.sk.yml index 08e93456b03738..9d852913d2db61 100644 --- a/config/locales/devise.sk.yml +++ b/config/locales/devise.sk.yml @@ -4,7 +4,7 @@ sk: confirmations: confirmed: Vaša e-mailová adresa bola úspešne potvrdená. send_instructions: O niekoľko minút obdržíte e-mail s pokynmi na potvrdenie svojho účtu. Prosíme, skontrolujte si aj zložku spam, ak ste tento e-mail nedostali. - send_paranoid_instructions: Ak sa vaša e-mailová adresa nachádza v našej databáze, o niekoľko minút obdržíte e-mail s pokynmi na potvrdenie svojho účtu. Prosíme, skontrolujte aj zložku spam, ste tento e-mail nedostali. + send_paranoid_instructions: Ak sa vaša e-mailová adresa nachádza v našej databáze, o niekoľko minút obdržíte e-mail s pokynmi na potvrdenie svojho účtu. Prosíme, skontrolujte aj zložku spam, ak ste tento e-mail nedostali. failure: already_authenticated: Už ste sa prihlásili. inactive: Váš účet ešte nie je aktivovaný. @@ -88,8 +88,8 @@ sk: success: Úspešné overenie z účtu %{kind}. passwords: no_token: Túto stránku nemôžete navštíviť, ak vás sem nepresmeroval e-mail s pokynmi na obnovu hesla. Pokiaľ prichádzate z tohto e-mailu, uistite sa, že ste použili celú adresu URL z e-mailu. - send_instructions: Ak sa vaša emailová adresa nachádza v databáze, o niekoľko minút dostanete e-mail s pokynmi na nastavenie nového hesla. Ak ste ho nedostali, skontrolujte aj priečinok pre spam. - send_paranoid_instructions: Ak sa vaša emailová adresa nachádza v databáze, o niekoľko minút dostanete e-mail s pokynmi na nastavenie nového hesla. Ak ste ho nedostali, skontrolujte aj priečinok pre spam. + send_instructions: Ak sa vaša e-mailová adresa nachádza v databáze, o niekoľko minút dostanete e-mail s pokynmi na nastavenie nového hesla. Ak ste ho nedostali, skontrolujte aj priečinok pre spam. + send_paranoid_instructions: Ak sa vaša e-mailová adresa nachádza v databáze, o niekoľko minút dostanete e-mail s pokynmi na nastavenie nového hesla. Ak ste ho nedostali, skontrolujte aj priečinok pre spam. updated: Vaše heslo bolo úspešne zmenené. Teraz ste sa prihlásili. updated_not_active: Vaše heslo bolo úspešne zmenené. registrations: @@ -97,9 +97,9 @@ sk: update_needs_confirmation: Váš účet bol úspešne zmenený, ale ešte potrebujeme overiť vašu novú e-mailovú adresu. Overíte ju kliknutím na potvrdzovací odkaz zaslaný na váš e-mail. Ak ste e-mail nedostali, skontrolujte svoj priečinok pre spam. updated: Váš účet bol úspešne aktualizovaný. sessions: - already_signed_out: Úspešne ste sa odhlásili. + already_signed_out: Odhlásenie prebehlo úspešne. signed_in: Úspešne ste sa prihlásili. - signed_out: Úspešne ste sa odhlásili. + signed_out: Odhlásenie prebehlo úspešne. unlocks: send_instructions: O niekoľko minút obdržíte e-mail s pokynmi na odomknutie svojho účtu. Prosíme, skontrolujte si aj zložku spam, ak ste tento e-mail nedostali. send_paranoid_instructions: Ak váš účet existuje, o niekoľko minút obdržíte e-mail s pokynmi na jeho odomknutie. Prosíme, skontrolujte si aj zložku spam, ak ste tento e-mail nedostali. diff --git a/config/locales/devise.sl.yml b/config/locales/devise.sl.yml index 8e708f4ebf565c..cb369c191061d0 100644 --- a/config/locales/devise.sl.yml +++ b/config/locales/devise.sl.yml @@ -7,6 +7,7 @@ sl: send_paranoid_instructions: Če vaš e-poštni naslov obstaja v naši zbirki podatkov, boste v nekaj minutah prejeli e-poštno sporočilo z navodili za potrditev vašega e-poštnega naslova. Če niste prejeli e-poštnega sporočila, preverite mapo neželena pošta. failure: already_authenticated: Ste že prijavljeni. + closed_registrations: Vaš poskus registracije je blokiran zaradi omrežnih pravil. Če ste mnenja, da gre za napako, stopite v stik s/z %{email}. inactive: Vaš račun še ni aktiviran. invalid: Neveljavno %{authentication_keys} ali geslo. last_attempt: Pred zaklepom računa imate še en poskus. diff --git a/config/locales/devise.sq.yml b/config/locales/devise.sq.yml index 69ac7bd26d6037..5eba224488bc46 100644 --- a/config/locales/devise.sq.yml +++ b/config/locales/devise.sq.yml @@ -7,6 +7,7 @@ sq: send_paranoid_instructions: Nëse adresa juaj email gjendet në bazën tonë të të dhënave, brenda pak minutash, do të merrni një email me udhëzime se si të ripohoni adresën tuaj email. Ju lutemi, nëse nuk e morët këtë email, kontrolloni dosjen e mesazheve të padëshiruar. failure: already_authenticated: Jeni tashmë i futur. + closed_registrations: Përpjekja juaj për regjistrim është bllokuar për shkak të një rregulli rrjeti. Nëse besoni se kjo është gabim, lidhuni me %{email}. inactive: Llogaria juaj s’është aktivizuar ende. invalid: "%{authentication_keys} ose fjalëkalim i pavlefshëm." last_attempt: Mund të provoni edhe një herë, përpara se llogaria juaj të kyçet. diff --git a/config/locales/devise.sv.yml b/config/locales/devise.sv.yml index 1808cfee5ef60b..e9a7aec73e59be 100644 --- a/config/locales/devise.sv.yml +++ b/config/locales/devise.sv.yml @@ -7,6 +7,7 @@ sv: send_paranoid_instructions: Om din e-postadress finns i vår databas får du ett mail med instruktioner för hur du bekräftar din e-postadress inom några minuter. Kontrollera din spammapp om du inte fick det e-postmeddelandet. failure: already_authenticated: Du har redan loggat in. + closed_registrations: Ditt registreringsförsök har blivit blockerat på grund av en nätverkspolicy. Om du anser att detta är ett fel, kontakta %{email}. inactive: Ditt konto är ännu inte aktiverat. invalid: Ogiltig %{authentication_keys} eller lösenord. last_attempt: Du har ytterligare ett försök innan ditt konto är låst. diff --git a/config/locales/devise.tr.yml b/config/locales/devise.tr.yml index 57162e805527bd..dd3b22a7d0bdc4 100644 --- a/config/locales/devise.tr.yml +++ b/config/locales/devise.tr.yml @@ -7,6 +7,7 @@ tr: send_paranoid_instructions: E-posta adresiniz veritabanımızda varsa, e-posta adresinizi birkaç dakika içinde nasıl doğrulayacağınıza ilişkin talimatları içeren bir e-posta alacaksınız. Bu e-postayı almadıysanız, lütfen spam klasörünüzü kontrol edin. failure: already_authenticated: Zaten oturum açtınız. + closed_registrations: Ağ politikası nedeniyle kayıt girişiminiz engellenmiştir. Bunun bir hata olduğunu düşünüyorsanız %{email} ile iletişime geçin. inactive: Hesabınız henüz etkinleştirilmedi. invalid: Geçersiz %{authentication_keys} ya da parola. last_attempt: Hesabınız kilitlenmeden önce bir kez daha denemeniz gerekir. diff --git a/config/locales/devise.vi.yml b/config/locales/devise.vi.yml index 2b4dc44db98f02..4fb7736f59e687 100644 --- a/config/locales/devise.vi.yml +++ b/config/locales/devise.vi.yml @@ -7,6 +7,7 @@ vi: send_paranoid_instructions: Nếu địa chỉ email của bạn đã tồn tại trong cơ sở dữ liệu của chúng tôi, bạn sẽ nhận được một email hướng dẫn cách xác minh lại địa chỉ email. Xin kiểm tra thư mục thư rác nếu như bạn không thấy email này. failure: already_authenticated: Bạn đã đăng nhập rồi. + closed_registrations: Việc đăng ký của bạn đã bị chặn do chính sách mạng. Nếu bạn cho rằng đây là lỗi, vui lòng liên hệ %{email}. inactive: Tài khoản của bạn chưa được kich hoạt. invalid: "%{authentication_keys} hoặc mật khẩu không khớp." last_attempt: Nếu thử sai lần nữa, tài khoản của bạn sẽ bị khóa. diff --git a/config/locales/devise.zh-CN.yml b/config/locales/devise.zh-CN.yml index c96c4afdcccf98..ffffe0cbf46b44 100644 --- a/config/locales/devise.zh-CN.yml +++ b/config/locales/devise.zh-CN.yml @@ -7,6 +7,7 @@ zh-CN: send_paranoid_instructions: 如果你的邮箱地址存在于我们的数据库中,你将在几分钟内收到一封邮件,内含如何验证邮箱地址的指引。如果你没有收到这封邮件,请检查你的垃圾邮件文件夹。 failure: already_authenticated: 你已登录。 + closed_registrations: 你的注册因为网络政策已被阻止。若您认为这是错误,请联系 %{email}。 inactive: 你还没有激活账号。 invalid: "%{authentication_keys} 无效或密码错误。" last_attempt: 你只有最后一次尝试机会,若未通过,账号将被锁定。 diff --git a/config/locales/devise.zh-TW.yml b/config/locales/devise.zh-TW.yml index ca01b165916b9c..9db5eac3d3f659 100644 --- a/config/locales/devise.zh-TW.yml +++ b/config/locales/devise.zh-TW.yml @@ -7,6 +7,7 @@ zh-TW: send_paranoid_instructions: 如果您的電子郵件存在於我們的資料庫,您將於幾分鐘內收到驗證信。若未收到請檢查垃圾郵件資料夾。 failure: already_authenticated: 您已登入。 + closed_registrations: 您的註冊已因網路政策被阻止。若您認為這是錯誤,煩請聯絡 %{email}。 inactive: 您的帳號尚未啟用。 invalid: 無效的 %{authentication_keys} 或密碼。 last_attempt: 帳號鎖定前,您還有最後一次嘗試機會。 diff --git a/config/locales/doorkeeper.de.yml b/config/locales/doorkeeper.de.yml index 6d0e3010af7523..98a6d45da5f355 100644 --- a/config/locales/doorkeeper.de.yml +++ b/config/locales/doorkeeper.de.yml @@ -101,7 +101,7 @@ de: temporarily_unavailable: Der Autorisierungs-Server ist aufgrund von zwischenzeitlicher Überlastung oder Wartungsarbeiten derzeit nicht in der Lage, die Anfrage zu bearbeiten. unauthorized_client: Der Client ist nicht dazu autorisiert, diese Anfrage mit dieser Methode auszuführen. unsupported_grant_type: Der Autorisierungs-Typ wird nicht vom Autorisierungs-Server unterstützt. - unsupported_response_type: Der Autorisierungs-Server unterstützt diesen Antwort-Typ nicht. + unsupported_response_type: Der Autorisierungsserver unterstützt dieses Antwortformat nicht. flash: applications: create: diff --git a/config/locales/doorkeeper.en-GB.yml b/config/locales/doorkeeper.en-GB.yml index 63a4575e83ce51..5b4b99858cccdb 100644 --- a/config/locales/doorkeeper.en-GB.yml +++ b/config/locales/doorkeeper.en-GB.yml @@ -96,7 +96,7 @@ en-GB: expired: The access token expired revoked: The access token was revoked unknown: The access token is invalid - resource_owner_authenticator_not_configured: Resource Owner find failed due to Doorkeeper.configure.resource_owner_authenticator being unconfiged. + resource_owner_authenticator_not_configured: Resource Owner find failed due to Doorkeeper.configure.resource_owner_authenticator being unconfigured. server_error: The authorisation server encountered an unexpected condition which prevented it from fulfilling the request. temporarily_unavailable: The authorisation server is currently unable to handle the request due to a temporary overloading or maintenance of the server. unauthorized_client: The client is not authorised to perform this request using this method. @@ -130,7 +130,7 @@ en-GB: crypto: End-to-end encryption favourites: Favourites filters: Filters - follow: Follows, Mutes and Blocks + follow: Follows, Mutes, and Blocks follows: Follows lists: Lists media: Media attachments diff --git a/config/locales/doorkeeper.et.yml b/config/locales/doorkeeper.et.yml index b21afa9a791c1a..df10f9a0cec65e 100644 --- a/config/locales/doorkeeper.et.yml +++ b/config/locales/doorkeeper.et.yml @@ -130,11 +130,11 @@ et: crypto: Otspunktkrüpteerimine favourites: Lemmikud filters: Filtrid - follow: Jälgitavad, Vaigistatud ja Blokeeritud + follow: Jälgitavad, summutamised ja blokeerimised follows: Jälgimised lists: Loetelud media: Lisatud meedia - mutes: Vaigistused + mutes: Summutamised notifications: Teavitused profile: Sinu Mastodoni profiil push: Tõuketeated @@ -177,7 +177,7 @@ et: read:filters: näha su filtreid read:follows: näha su jälgimisi read:lists: näha su loetelusid - read:mutes: näha su vaigistusi + read:mutes: näha su summutamisi read:notifications: näha teateid read:reports: näha teavitusi read:search: otsida sinu nimel @@ -186,13 +186,13 @@ et: write:accounts: muuta profiili write:blocks: kontode ja domeenide blokeerimine write:bookmarks: lisada postitusele järjehoidja - write:conversations: vaigista ja kustuta vestlused + write:conversations: summuta ja kustuta vestlused write:favourites: lisada postitusi lemmikuks write:filters: luua filtreid write:follows: jälgida inimesi write:lists: luua loetelusid write:media: üles laadida meediafaile - write:mutes: vaigista inimesi ja vestluseid + write:mutes: summuta inimesi ja vestluseid write:notifications: tühjendada teateid write:reports: teavitada teistest inimestest write:statuses: avaldada postitusi diff --git a/config/locales/doorkeeper.fi.yml b/config/locales/doorkeeper.fi.yml index 7b80882af01e1b..73730a8689072b 100644 --- a/config/locales/doorkeeper.fi.yml +++ b/config/locales/doorkeeper.fi.yml @@ -127,7 +127,7 @@ fi: blocks: Estot bookmarks: Kirjanmerkit conversations: Keskustelut - crypto: Päästä päähän -salaus + crypto: Päästä päähän -⁠salaus favourites: Suosikit filters: Suodattimet follow: Seurattavat, mykistykset ja estot @@ -165,7 +165,7 @@ fi: admin:write:email_domain_blocks: suorita moderointitoimia estetyille sähköpostiverkkotunnuksille admin:write:ip_blocks: suorita moderointitoimia estetyille IP-osoitteille admin:write:reports: suorita moderointitoimia raporteille - crypto: käytä päästä päähän -salausta + crypto: käytä päästä päähän -⁠salausta follow: muokkaa tilin seurantasuhteita profile: lue vain tilisi profiilitietoja push: vastaanota puskuilmoituksesi diff --git a/config/locales/doorkeeper.lv.yml b/config/locales/doorkeeper.lv.yml index 4b8116e6af4ef6..a3d8902af49ed7 100644 --- a/config/locales/doorkeeper.lv.yml +++ b/config/locales/doorkeeper.lv.yml @@ -124,13 +124,13 @@ lv: admin/all: Visas administrēšanas funkcijas admin/reports: Ziņojumu pārvaldīšana all: Pilna piekļuve Tavam Mastodon kontam - blocks: Bloķētie + blocks: Liegtie bookmarks: Grāmatzīmes conversations: Sarunas crypto: Pilnīga šifrēšana favourites: Izlase filters: Filtri - follow: Seko, apklusina un liedz + follow: Sekojumi, apklusināšanas un liegumi follows: Seko lists: Saraksti media: Multividesu pielikumi @@ -184,7 +184,7 @@ lv: read:statuses: skatīt visus ierakstus write: labot visus sava konta datus write:accounts: labot manu profilu - write:blocks: bloķēt kontus un domēnus + write:blocks: liegt kontus un domēnus write:bookmarks: pievienotās grāmatzīmes write:conversations: apklusināt un dzēst sarunas write:favourites: iecienītākās ziņas diff --git a/config/locales/doorkeeper.pt-PT.yml b/config/locales/doorkeeper.pt-PT.yml index 6285794aa52bdb..ae7dc28b620b81 100644 --- a/config/locales/doorkeeper.pt-PT.yml +++ b/config/locales/doorkeeper.pt-PT.yml @@ -125,7 +125,7 @@ pt-PT: admin/reports: Administração de denúncias all: Acesso total à sua conta Mastodon blocks: Bloqueios - bookmarks: Marcadores + bookmarks: Itens Salvos conversations: Conversas crypto: Encriptação ponta a ponta favourites: Favoritos @@ -172,7 +172,7 @@ pt-PT: read: ler todos os dados da tua conta read:accounts: ver as informações da conta read:blocks: ver os teus bloqueios - read:bookmarks: ver os teus marcadores + read:bookmarks: ver os seus itens salvos read:favourites: ver os teus favoritos read:filters: ver os teus filtros read:follows: ver quem segues @@ -185,7 +185,7 @@ pt-PT: write: alterar todos os dados da tua conta write:accounts: alterar o teu perfil write:blocks: bloquear contas e domínios - write:bookmarks: marcar publicações + write:bookmarks: salvar publicações write:conversations: ocultar e eliminar conversas write:favourites: assinalar como favoritas write:filters: criar filtros diff --git a/config/locales/el.yml b/config/locales/el.yml index a17e8700757c6d..ddc822f1ba7e20 100644 --- a/config/locales/el.yml +++ b/config/locales/el.yml @@ -7,6 +7,8 @@ el: hosted_on: Το Mastodon φιλοξενείται στο %{domain} title: Σχετικά accounts: + errors: + cannot_be_added_to_collections: Αυτός ο λογαριασμός δεν μπορεί να προστεθεί σε συλλογές. followers: one: Ακόλουθος other: Ακόλουθοι @@ -308,7 +310,7 @@ el: update_status_html: Ο/Η %{name} ενημέρωσε την ανάρτηση του/της %{target} update_user_role_html: Ο/Η %{name} άλλαξε τον ρόλο %{target} update_username_block_html: "%{name} ενημέρωσε κανόνα για ονόματα χρηστών που περιέχουν %{target}" - deleted_account: διαγεγραμμένος λογαριασμός + deleted_account: λογαριασμός διαγράφηκε empty: Δεν βρέθηκαν αρχεία καταγραφής. filter_by_action: Φιλτράρισμα ανά ενέργεια filter_by_user: Φιλτράρισμα ανά χρήστη @@ -751,7 +753,7 @@ el: description_html: Με τους ρόλους χρηστών, μπορείς να προσαρμόσεις σε ποιες λειτουργίες και περιοχές του Mastodon, οι χρήστες σας μπορούν να έχουν πρόσβαση. edit: Επεξεργασία ρόλου '%{name}' everyone: Προεπιλεγμένα δικαιώματα - everyone_full_description_html: Αυτός είναι ο βασικός ρόλος που επηρεάζει όλους τους χρήστες, ακόμη και εκείνους που δεν έχουν κάποιον ρόλο. Όλοι οι άλλοι ρόλοι κληρονομούν δικαιώματα από αυτόν. + everyone_full_description_html: Αυτός είναι ο βασικός ρόλος που επηρεάζει όλους τους χρήστες, ακόμα και εκείνους που δεν έχουν κάποιον ρόλο. Όλοι οι άλλοι ρόλοι κληρονομούν δικαιώματα από αυτόν. permissions_count: one: "%{count} δικαίωμα" other: "%{count} δικαιώματα" @@ -796,6 +798,8 @@ el: view_dashboard_description: Επιτρέπει στους χρήστες να έχουν πρόσβαση στον ταμπλό πληροφοριών και σε διάφορες μετρήσεις view_devops: DevOps view_devops_description: Επιτρέπει στους χρήστες να έχουν πρόσβαση στα ταμπλό πληροφοριών Sidekiq και pgHero + view_feeds: Προβολή ζωντανών και θεματικών ροών + view_feeds_description: Επιτρέπει στους χρήστες να έχουν πρόσβαση στις ζωντανές και θεματικές ροές ανεξάρτητα από τις ρυθμίσεις του διακομιστή title: Ρόλοι rules: add_new: Προσθήκη κανόνα @@ -803,7 +807,7 @@ el: delete: Διαγραφή description_html: Ενώ οι περισσότεροι ισχυρίζονται ότι έχουν διαβάσει και συμφωνούν με τους όρους της υπηρεσίας, συνήθως οι άνθρωποι δεν διαβάζουν μέχρι μετά την εμφάνιση ενός προβλήματος. Κάνε ευκολότερο να δουν τους κανόνες του διακομιστή σας με μια ματιά παρέχοντας τους σε μια λίστα. Προσπάθησε να κρατήσεις τους μεμονωμένους κανόνες σύντομους και απλούς, αλλά προσπάθησε να μην τους χωρίσεις σε πολλά ξεχωριστά αντικείμενα. edit: Επεξεργασία κανόνα - empty: Δεν έχουν οριστεί ακόμα κανόνες διακομιστή. + empty: Δεν έχουν οριστεί ακόμη κανόνες διακομιστή. move_down: Μετακίνηση κάτω move_up: Μετακίνηση πάνω title: Κανόνες διακομιστή @@ -837,17 +841,28 @@ el: title: Εξαίρεση χρηστών από τις μηχανές αναζήτησης discovery: follow_recommendations: Ακολούθησε τις προτάσεις - preamble: Δημοσιεύοντας ενδιαφέρον περιεχόμενο είναι καθοριστικό για την ενσωμάτωση νέων χρηστών που μπορεί να μη γνωρίζουν κανέναν στο Mastodon. Έλεγξε πώς λειτουργούν διάφορες δυνατότητες ανακάλυψης στον διακομιστή σας. + preamble: Εμφανίζοντας ενδιαφέρον περιεχόμενο είναι καθοριστικό για την ενσωμάτωση νέων χρηστών που μπορεί να μη γνωρίζουν κανέναν στο Mastodon. Ελέγξτε πώς λειτουργούν διάφορες δυνατότητες ανακάλυψης στον διακομιστή σας. privacy: Απόρρητο profile_directory: Κατάλογος προφίλ public_timelines: Δημόσιες ροές publish_statistics: Δημοσίευση στατιστικών title: Ανακάλυψη trends: Τάσεις + wrapstodon: Wrapstodon domain_blocks: all: Για όλους disabled: Για κανέναν users: Προς συνδεδεμένους τοπικούς χρήστες + feed_access: + modes: + authenticated: Πιστοποιημένοι χρήστες μόνο + disabled: Να απαιτείται συγκεκριμένος ρόλος χρήστη + public: Όλοι + landing_page: + values: + about: Σχετικά + local_feed: Τοπική ροή + trends: Τάσεις registrations: moderation_recommandation: Παρακαλώ βεβαιώσου ότι έχεις μια επαρκής και ενεργή ομάδα συντονισμού πριν ανοίξεις τις εγγραφές για όλους! preamble: Έλεγξε ποιος μπορεί να δημιουργήσει ένα λογαριασμό στον διακομιστή σας. @@ -901,6 +916,7 @@ el: no_status_selected: Καμία ανάρτηση δεν άλλαξε αφού καμία δεν ήταν επιλεγμένη open: Άνοιγμα ανάρτησης original_status: Αρχική ανάρτηση + quotes: Παραθέσεις reblogs: Αναδημοσιεύσεις replied_to_html: Απάντησε στον χρήστη %{acct_link} status_changed: Η ανάρτηση άλλαξε @@ -908,6 +924,7 @@ el: title: Αναρτήσεις λογαριασμού - @%{name} trending: Τάσεις view_publicly: Προβολή δημόσια + view_quoted_post: Προβολή παρατιθέμενης ανάρτησης visibility: Ορατότητα with_media: Με πολυμέσα strikes: @@ -1001,7 +1018,7 @@ el: going_live_on_html: Ενεργό, σε ισχύ από %{date} history: Ιστορικό live: Ενεργό - no_history: Δεν υπάρχουν ακόμα καταγεγραμμένες αλλαγές στους όρους παροχής υπηρεσιών. + no_history: Δεν υπάρχουν ακόμη καταγεγραμμένες αλλαγές στους όρους παροχής υπηρεσιών. no_terms_of_service_html: Δεν έχετε ρυθμίσει τους όρους της υπηρεσίας. Οι όροι της υπηρεσίας αποσκοπούν στην παροχή σαφήνειας και την προστασία σου από πιθανές υποχρεώσεις σε διαφορές με τους χρήστες σου. notified_on_html: Οι χρήστες ειδοποιήθηκαν στις %{date} notify_users: Ειδοποίηση χρηστών @@ -1030,7 +1047,7 @@ el: confirm_allow_provider: Σίγουρα θες να επιτρέψεις τους επιλεγμένους παρόχους; confirm_disallow: Σίγουρα θες να απορρίψεις τους επιλεγμένους συνδέσμους; confirm_disallow_provider: Σίγουρα θες να απορρίψεις τους επιλεγμένους παρόχους; - description_html: Αυτοί οι σύνδεσμοι μοιράζονται αρκετά από λογαριασμούς των οποίων τις δημοσιεύσεις, βλέπει ο διακομιστής σας. Μπορεί να βοηθήσει τους χρήστες σας να μάθουν τί συμβαίνει στον κόσμο. Οι σύνδεσμοι δεν εμφανίζονται δημόσια μέχρι να εγκρίνετε τον εκδότη. Μπορείς επίσης να επιτρέψεις ή να απορρίψεις μεμονωμένους συνδέσμους. + description_html: Αυτοί οι σύνδεσμοι κοινοποιούνται αρκετά από λογαριασμούς των οποίων τις αναρτήσεις, βλέπει ο διακομιστής σας. Μπορεί να βοηθήσει τους χρήστες σας να μάθουν τί συμβαίνει στον κόσμο. Οι σύνδεσμοι δεν εμφανίζονται δημόσια μέχρι να εγκρίνετε τον εκδότη. Μπορείτε επίσης να επιτρέψετε ή να απορρίψετε μεμονωμένους συνδέσμους. disallow: Να μην επιτρέπεται ο σύνδεσμος disallow_provider: Να μην επιτρέπεται ο εκδότης no_link_selected: Κανένας σύνδεσμος δεν άλλαξε αφού κανείς δεν επιλέχθηκε @@ -1046,7 +1063,7 @@ el: pending_review: Εκκρεμεί αξιολόγηση preview_card_providers: allowed: Σύνδεσμοι από αυτόν τον εκδότη μπορούν να γίνουν δημοφιλείς - description_html: Αυτοί είναι τομείς από τους οποίους οι σύνδεσμοι συχνά μοιράζονται στον διακομιστή σας. Σύνδεσμοι δεν γίνουν δημοφιλείς δημοσίως εκτός και αν ο τομέας του συνδέσμου εγκριθεί. Η έγκρισή σας (ή απόρριψη) περιλαμβάνει και τους υποτομείς. + description_html: Αυτοί είναι τομείς από τους οποίους οι σύνδεσμοι συχνά κοινοποιούνται στον διακομιστή σας. Οι σύνδεσμοι δεν θα γίνουν δημοφιλείς δημοσίως εκτός και αν ο τομέας του συνδέσμου εγκριθεί. Η έγκρισή σας (ή απόρριψη) περιλαμβάνει και τους υποτομείς. rejected: Σύνδεσμοι από αυτόν τον εκδότη δε θα γίνουν δημοφιλείς title: Εκδότες rejected: Απορρίφθηκε @@ -1063,8 +1080,8 @@ el: no_status_selected: Καμία δημοφιλής ανάρτηση δεν άλλαξε αφού καμία δεν επιλέχθηκε not_discoverable: Ο συντάκτης δεν έχει επιλέξει να είναι ανακαλύψιμος shared_by: - one: Μοιράστηκε ή προστέθηκε στα αγαπημένα μία φορά - other: Μοιράστηκε και προστέθηκε στα αγαπημένα %{friendly_count} φορές + one: Κοινοποιήθηκε ή προστέθηκε στα αγαπημένα μία φορά + other: Κοινοποιήθηκε και προστέθηκε στα αγαπημένα %{friendly_count} φορές title: Αναρτήσεις σε τάση tags: current_score: Τρέχουσα βαθμολογία %{score} @@ -1123,7 +1140,7 @@ el: disable: Απενεργοποίηση disabled: Απενεργοποιημένα edit: Επεξεργασία σημείου τερματισμού - empty: Δεν έχεις ακόμα ρυθμισμένα σημεία τερματισμού webhook. + empty: Δεν έχεις ακόμη ρυθμισμένα σημεία τερματισμού webhook. enable: Ενεργοποίηση enabled: Ενεργό enabled_events: @@ -1182,10 +1199,10 @@ el: hint_html: Αν θέλεις να μετακινηθείς από έναν άλλο λογαριασμό σε αυτόν εδώ, εδώ μπορείς να δημιουργήσεις ένα ψευδώνυμο, πράγμα που απαιτείται πριν προχωρήσεις για να μεταφέρεις τους ακολούθους σου από τον παλιό λογαριασμό σε αυτόν εδώ. Η ενέργεια αυτή είναι ακίνδυνη και αναστρέψιμη.Η μετακόμιση του λογαριασμού ξεκινάει από τον παλιό λογαριασμό. remove: Αποσύνδεση ψευδώνυμου appearance: - advanced_web_interface: Προηγμένη διεπαφή ιστού - advanced_web_interface_hint: 'Αν θέλεις να χρησιμοποιήσεις ολόκληρο το πλάτος της οθόνης σου, η προηγμένη λειτουργία χρήσης σου επιτρέπει να ορίσεις πολλαπλές στήλες ώστε να βλέπεις ταυτόχρονα όση πληροφορία θέλεις: Την αρχική ροή, τις ειδοποιήσεις, τις ομοσπονδιακές ροές και όσες λίστες και ετικέτες θέλεις.' + advanced_settings: Προχωρημένες ρυθμίσεις animations_and_accessibility: Εφέ κινήσεων και προσβασιμότητα - confirmation_dialogs: Ερωτήσεις επιβεβαίωσης + boosting_preferences: Προτιμήσεις ενισχύσεων + boosting_preferences_info_html: "Συμβουλή: Ανεξάρτητα από τις ρυθμίσεις, κάνοντας Shift + Κλικ στο εικονίδιο %{icon} Ενίσχυση θα ενισχύσει άμεσα." discovery: Ανακάλυψη localization: body: Το Mastodon μεταφράζεται από εθελοντές. @@ -1212,7 +1229,7 @@ el: apply_for_account: Ζήτα έναν λογαριασμό captcha_confirmation: help_html: Εάν αντιμετωπίζεις προβλήματα με την επίλυση του CAPTCHA, μπορείς να επικοινωνήσεις μαζί μας μέσω %{email} και μπορούμε να σε βοηθήσουμε. - hint_html: Και κάτι ακόμα! Πρέπει να επιβεβαιώσουμε ότι είσαι άνθρωπος (αυτό γίνεται για να κρατήσουμε μακριά το σπαμ!). Λύσε το CAPTCHA παρακάτω και κάνε κλικ "Συνέχεια". + hint_html: Και κάτι ακόμα! Πρέπει να επιβεβαιώσουμε ότι είσαι άνθρωπος (αυτό γίνεται για να κρατήσουμε μακριά το σπαμ!). Λύσε το CAPTCHA παρακάτω και πάτα "Συνέχεια". title: Ελεγχος ασφαλείας confirmations: awaiting_review: Η διεύθυνση email σου επιβεβαιώθηκε! Το προσωπικό του %{domain} εξετάζει τώρα την εγγραφή σου. Θα λάβεις ένα email εάν εγκρίνουν τον λογαριασμό σου! @@ -1291,7 +1308,7 @@ el: user_privacy_agreement_html: Έχω διαβάσει και συμφωνώ με την πολιτική απορρήτου author_attribution: example_title: Δείγμα κειμένου - hint_html: Γράφεις ειδήσεις ή blog άρθρα εκτός του Mastodon; Έλεγξε πώς μπορείς να πάρεις τα εύσημα όταν μοιράζονται στο Mastodon. + hint_html: Γράφεις ειδήσεις ή άρθρα blog εκτός του Mastodon; Έλεγξε πώς μπορείς να πάρεις τα εύσημα όταν κοινοποιούνται στο Mastodon. instructions: 'Βεβαιώσου ότι ο κώδικας αυτός είναι στο HTML του άρθρου σου:' more_from_html: Περισσότερα από %{name} s_blog: Ιστολόγιο του/της %{name} @@ -1579,14 +1596,21 @@ el: invalid: Αυτή η πρόσκληση δεν είναι έγκυρη invited_by: 'Σε προσκάλεσε ο/η:' max_uses: - one: 1 χρήσης - other: "%{count} χρήσεων" - max_uses_prompt: Απεριόριστη + one: 1 χρήση + other: "%{count} χρήσεις" + max_uses_prompt: Απεριόριστες prompt: Φτιάξε και μοίρασε συνδέσμους με τρίτους για να δώσεις πρόσβαση σε αυτόν τον διακομιστή table: expires_at: Λήγει uses: Χρήσεις title: Προσκάλεσε κόσμο + link_preview: + author_html: Από %{name} + potentially_sensitive_content: + action: Κάνε κλικ για εμφάνιση + confirm_visit: Σίγουρα θες να ανοίξεις αυτόν τον σύνδεσμο; + hide_button: Απόκρυψη + label: Δυνητικά ευαίσθητο περιεχόμενο lists: errors: limit: Έχεις φτάσει το μέγιστο αριθμό λιστών @@ -1651,7 +1675,7 @@ el: disabled_account: Ο τρέχων λογαριασμός σου δε θα είναι πλήρως ενεργός μετά. Πάντως θα έχεις πρόσβαση στην εξαγωγή δεδομένων καθώς και στην επανενεργοποίηση. followers: Αυτή η ενέργεια θα μεταφέρει όλους τους ακόλουθούς σου από τον τρέχοντα λογαριασμό στον νέο λογαριασμό only_redirect_html: Εναλλακτικά, μπορείς απλά να προσθέσεις μια ανακατατεύθυνση στο προφίλ σου. - other_data: Κανένα άλλο δεδομένο δε θα μεταφερθεί αυτόματα + other_data: Δεν θα μετακινηθούν αυτόματα άλλα δεδομένα (συμπεριλαμβανομένου των αναρτήσεων σας και της λίστας των λογαριασμών που ακολουθείτε) redirect: Το προφίλ του τρέχοντος λογαριασμού σου θα ενημερωθεί με μια σημείωση ανακατεύθυνσης και θα εξαιρεθεί από τα αποτελέσματα αναζητήσεων moderation: title: Συντονισμός @@ -1685,16 +1709,22 @@ el: body: 'Επισημάνθηκες από τον/την %{name} στο:' subject: Επισημάνθηκες από τον/την %{name} title: Νέα επισήμανση + moderation_warning: + subject: Έχετε λάβει μία προειδοποίηση συντονισμού poll: subject: Μια δημοσκόπηση του %{name} έληξε quote: body: 'Η ανάρτησή σου παρατέθηκε από %{name}:' subject: Ο/Η %{name} έκανε παράθεση της ανάρτησής σου title: Νέα παράθεση + quoted_update: + subject: Ο χρήστης %{name} επεξεργάστηκε μία ανάρτηση που παρέθεσες reblog: body: 'Η ανάρτησή σου ενισχύθηκε από τον/την %{name}:' subject: Ο/Η %{name} ενίσχυσε την ανάρτηση σου title: Νέα ενίσχυση + severed_relationships: + subject: Έχετε χάσει συνδέσεις λόγω μιας απόφασης συντονισμού status: subject: Ο/Η %{name} μόλις ανέρτησε κάτι update: @@ -1800,7 +1830,7 @@ el: over_total_limit: Έχεις υπερβεί το όριο των %{limit} προγραμματισμένων αναρτήσεων too_soon: η ημερομηνία πρέπει να είναι στο μέλλον self_destruct: - lead_html: Δυστυχώς, το %{domain} κλείνει οριστικά. Αν είχατε λογαριασμό εκεί, δεν θα μπορείτε να συνεχίσετε τη χρήση του, αλλά μπορείτε ακόμα να ζητήσετε ένα αντίγραφο ασφαλείας των δεδομένων σας. + lead_html: Δυστυχώς, το %{domain} κλείνει οριστικά. Αν είχατε λογαριασμό εκεί, δεν θα μπορείτε να συνεχίσετε τη χρήση του, αλλά μπορείτε ακόμη να ζητήσετε ένα αντίγραφο ασφαλείας των δεδομένων σας. title: Αυτός ο διακομιστής κλείνει οριστικά sessions: activity: Τελευταία δραστηριότητα @@ -1859,7 +1889,7 @@ el: development: Ανάπτυξη edit_profile: Επεξεργασία προφίλ export: Εξαγωγή - featured_tags: Παρεχόμενες ετικέτες + featured_tags: Προβεβλημένες ετικέτες import: Εισαγωγή import_and_export: Εισαγωγή και εξαγωγή migrate: Μετακόμιση λογαριασμού @@ -1897,6 +1927,9 @@ el: other: "%{count} βίντεο" boosted_from_html: Ενισχύθηκε από %{acct_link} content_warning: 'Προειδοποίηση περιεχομένου: %{warning}' + content_warnings: + hide: Απόκρυψη ανάρτησης + show: Εμφάνιση περισσότερων default_language: Ίδια με γλώσσα διεπαφής disallowed_hashtags: one: 'περιέχει μη επιτρεπτή ετικέτα: %{tags}' @@ -1905,16 +1938,22 @@ el: errors: in_reply_not_found: Η ανάρτηση στην οποία προσπαθείς να απαντήσεις δεν φαίνεται να υπάρχει. quoted_status_not_found: Η ανάρτηση την οποία προσπαθείς να παραθέσεις δεν φαίνεται να υπάρχει. + quoted_user_not_mentioned: Δεν είναι δυνατή η παράθεση ενός μη επισημασμένου χρήστη σε μια ανάρτηση Ιδιωτικής επισήμανσης. over_character_limit: υπέρβαση μέγιστου ορίου %{max} χαρακτήρων pin_errors: direct: Αναρτήσεις που είναι ορατές μόνο στους αναφερόμενους χρήστες δεν μπορούν να καρφιτσωθούν limit: Έχεις ήδη καρφιτσώσει το μέγιστο αριθμό επιτρεπτών αναρτήσεων ownership: Δεν μπορείς να καρφιτσώσεις ανάρτηση κάποιου άλλου reblog: Οι ενισχύσεις δεν καρφιτσώνονται + quote_error: + not_available: Ανάρτηση μη διαθέσιμη + pending_approval: Ανάρτηση σε αναμονή + revoked: Η ανάρτηση αφαιρέθηκε από τον συντάκτη quote_policies: followers: Μόνο ακόλουθοι nobody: Μόνο εγώ public: Όλοι + quote_post_author: Παράθεση ανάρτησης από %{acct} title: '%{name}: "%{quote}"' visibilities: direct: Ιδιωτική επισήμανση @@ -1931,7 +1970,7 @@ el: ignore_favs: Αγνόηση αγαπημένων ignore_reblogs: Αγνόηση ενισχύσεων interaction_exceptions: Εξαιρέσεις βασισμένες σε αλληλεπιδράσεις - interaction_exceptions_explanation: Σημείωσε ότι δεν υπάρχει εγγύηση για πιθανή διαγραφή αναρτήσεων αν αυτά πέσουν κάτω από το όριο αγαπημένων ή ενισχύσεων ακόμα και αν κάποτε το είχαν ξεπεράσει. + interaction_exceptions_explanation: Σημείωσε ότι δεν υπάρχει εγγύηση για πιθανή διαγραφή αναρτήσεων αν αυτά πέσουν κάτω από το όριο αγαπημένων ή ενισχύσεων ακόμη και αν κάποτε το είχαν ξεπεράσει. keep_direct: Διατήρηση άμεσων μηνυμάτων keep_direct_hint: Δεν διαγράφει κανένα από τα άμεσα μηνύματά σου keep_media: Διατήρηση αναρτήσεων με συνημμένα πολυμέσων @@ -2056,8 +2095,8 @@ el: disable: Δεν μπορείς πλέον να χρησιμοποιήσεις τον λογαριασμό σου, αλλά το προφίλ σου και άλλα δεδομένα παραμένουν άθικτα. Μπορείς να ζητήσεις ένα αντίγραφο ασφαλείας των δεδομένων σου, να αλλάξεις τις ρυθμίσεις του λογαριασμού σου ή να διαγράψεις τον λογαριασμό σου. mark_statuses_as_sensitive: Μερικές από τις αναρτήσεις σου έχουν επισημανθεί ως ευαίσθητες από τους συντονιστές του %{instance}. Αυτό σημαίνει ότι οι άνθρωποι θα πρέπει να πατήσουν τα πολυμέσα στις αναρτήσεις πριν εμφανιστεί μια προεπισκόπηση. Μπορείς να επισημάνεις τα πολυμέσα ως ευαίσθητα όταν δημοσιεύεις στο μέλλον. sensitive: Από δω και στο εξής, όλα τα μεταφορτωμένα αρχεία πολυμέσων σου θα επισημανθούν ως ευαίσθητα και κρυμμένα πίσω από μια προειδοποίηση που πρέπει να πατηθεί. - silence: Μπορείς ακόμα να χρησιμοποιείς τον λογαριασμό σου, αλλά μόνο άτομα που σε ακολουθούν ήδη θα δουν τις αναρτήσεις σου σε αυτόν τον διακομιστή και μπορεί να αποκλειστείς από διάφορες δυνατότητες ανακάλυψης. Ωστόσο, οι άλλοι μπορούν ακόμα να σε ακολουθήσουν με μη αυτόματο τρόπο. - suspend: Δε μπορείς πλέον να χρησιμοποιήσεις τον λογαριασμό σου και το προφίλ σου και άλλα δεδομένα δεν είναι πλέον προσβάσιμα. Μπορείς ακόμα να συνδεθείς για να αιτηθείς αντίγραφο των δεδομένων σου μέχρι να αφαιρεθούν πλήρως σε περίπου 30 μέρες αλλά, θα διατηρήσουμε κάποια βασικά δεδομένα για να σε αποτρέψουμε να παρακάμψεις την αναστολή. + silence: Μπορείς ακόμη να χρησιμοποιείς τον λογαριασμό σου, αλλά μόνο άτομα που σε ακολουθούν ήδη θα δουν τις αναρτήσεις σου σε αυτόν τον διακομιστή και μπορεί να αποκλειστείς από διάφορες δυνατότητες ανακάλυψης. Ωστόσο, οι άλλοι μπορούν ακόμη να σε ακολουθήσουν με μη αυτόματο τρόπο. + suspend: Δε μπορείς πλέον να χρησιμοποιήσεις τον λογαριασμό σου, και το προφίλ σου και άλλα δεδομένα δεν είναι πλέον προσβάσιμα. Μπορείς ακόμη να συνδεθείς για να αιτηθείς αντίγραφο των δεδομένων σου μέχρι να αφαιρεθούν πλήρως σε περίπου 30 μέρες αλλά, θα διατηρήσουμε κάποια βασικά δεδομένα για να σε αποτρέψουμε να παρακάμψεις την αναστολή. reason: 'Αιτιολογία:' statuses: 'Αναφερόμενες αναρτήσεις:' subject: @@ -2112,7 +2151,7 @@ el: post_step: Πες γεια στον κόσμο με κείμενο, φωτογραφίες, βίντεο ή δημοσκοπήσεις. post_title: Κάνε την πρώτη σου ανάρτηση share_step: Πες στους φίλους σου πώς να σε βρουν στο Mastodon. - share_title: Μοιραστείτε το προφίλ σας στο Mastodon + share_title: Κοινοποίησε το προφίλ σου στο Mastodon sign_in_action: Σύνδεση subject: Καλώς ήρθες στο Mastodon title: Καλώς όρισες, %{name}! @@ -2125,11 +2164,11 @@ el: seamless_external_login: Επειδή έχεις συνδεθεί μέσω τρίτης υπηρεσίας, οι ρυθμίσεις συνθηματικού και email δεν είναι διαθέσιμες. signed_in_as: 'Έχεις συνδεθεί ως:' verification: - extra_instructions_html: Συμβουλή: Ο σύνδεσμος στην ιστοσελίδα σου μπορεί να είναι αόρατος. Το σημαντικό μέρος είναι το rel="me" που αποτρέπει την μίμηση σε ιστοσελίδες με περιεχόμενο παραγόμενο από χρήστες. Μπορείς ακόμη να χρησιμοποιήσεις μια ετικέτα συνδέσμου στην κεφαλίδα της σελίδας αντί για a, αλλά ο κώδικας HTML πρέπει να είναι προσβάσιμος χωρίς την εκτέλεση JavaScript. + extra_instructions_html: Συμβουλή: Ο σύνδεσμος στην ιστοσελίδα σου μπορεί να είναι αόρατος. Το σημαντικό μέρος είναι το rel="me" που αποτρέπει την μίμηση σε ιστοσελίδες με περιεχόμενο παραγόμενο από χρήστες. Μπορείς ακόμα να χρησιμοποιήσεις μια ετικέτα συνδέσμου στην κεφαλίδα της σελίδας αντί για a, αλλά ο κώδικας HTML πρέπει να είναι προσβάσιμος χωρίς την εκτέλεση JavaScript. here_is_how: Δείτε πώς hint_html: Η επαλήθευση της ταυτότητας στο Mastodon είναι για όλους. Βασισμένο σε ανοιχτά πρότυπα ιστού, τώρα και για πάντα δωρεάν. Το μόνο που χρειάζεσαι είναι μια προσωπική ιστοσελίδα που ο κόσμος να σε αναγνωρίζει από αυτή. Όταν συνδέεσαι σε αυτήν την ιστοσελίδα από το προφίλ σου, θα ελέγξουμε ότι η ιστοσελίδα συνδέεται πίσω στο προφίλ σου και θα δείξει μια οπτική ένδειξη σε αυτό. instructions_html: Αντέγραψε και επικόλλησε τον παρακάτω κώδικα στην HTML της ιστοσελίδας σου. Στη συνέχεια, πρόσθεσε τη διεύθυνση της ιστοσελίδας σου σε ένα από τα επιπλέον πεδία στο προφίλ σου από την καρτέλα "Επεξεργασία προφίλ" και αποθήκευσε τις αλλαγές. - verification: Πιστοποίηση + verification: Επαλήθευση verified_links: Οι επαληθευμένοι σύνδεσμοι σας website_verification: Επαλήθευση ιστοτόπου webauthn_credentials: @@ -2145,7 +2184,10 @@ el: success: Το κλειδί ασφαλείας σου διαγράφηκε με επιτυχία. invalid_credential: Άκυρο κλειδί ασφαλείας nickname_hint: Βάλε το ψευδώνυμο του νέου κλειδιού ασφαλείας σου - not_enabled: Δεν έχεις ενεργοποιήσει το WebAuthn ακόμα + not_enabled: Δεν έχεις ενεργοποιήσει το WebAuthn ακόμη not_supported: Αυτό το πρόγραμμα περιήγησης δεν υποστηρίζει κλειδιά ασφαλείας otp_required: Για να χρησιμοποιήσεις κλειδιά ασφαλείας, ενεργοποίησε πρώτα την ταυτοποίηση δύο παραγόντων. registered_on: Εγγραφή στις %{date} + wrapstodon: + description: Δείτε πώς ο/η %{name} χρησιμοποίησε το Mastodon φέτος! + title: Wrapstodon %{year} για %{name} diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml index 621a3a4e4fde30..e616adfedc13a6 100644 --- a/config/locales/en-GB.yml +++ b/config/locales/en-GB.yml @@ -7,6 +7,8 @@ en-GB: hosted_on: Mastodon hosted on %{domain} title: About accounts: + errors: + cannot_be_added_to_collections: This account cannot be added to collections. followers: one: Follower other: Followers @@ -102,7 +104,7 @@ en-GB: moderation_notes: Moderation notes most_recent_activity: Most recent activity most_recent_ip: Most recent IP - no_account_selected: No accounts were changed as none were selected + no_account_selected: No accounts were changed, as none were selected no_limits_imposed: No limits imposed no_role_assigned: No role assigned not_subscribed: Not subscribed @@ -154,7 +156,7 @@ en-GB: subscribe: Subscribe suspend: Suspend suspended: Suspended - suspension_irreversible: The data of this account has been irreversibly deleted. You can unsuspend the account to make it usable but it will not recover any data it previously had. + suspension_irreversible: The data of this account has been irreversibly deleted. You can unsuspend the account to make it usable, but it will not recover any data it previously had. suspension_reversible_hint_html: The account has been suspended, and the data will be fully removed on %{date}. Until then, the account can be restored without any ill effects. If you wish to remove all of the account's data immediately, you can do so below. title: Accounts unblock_email: Unblock email address @@ -190,6 +192,7 @@ en-GB: create_relay: Create Relay create_unavailable_domain: Create Unavailable Domain create_user_role: Create Role + create_username_block: Create Username Rule demote_user: Demote User destroy_announcement: Delete Announcement destroy_canonical_email_block: Delete Email Block @@ -203,6 +206,7 @@ en-GB: destroy_status: Delete Post destroy_unavailable_domain: Delete Unavailable Domain destroy_user_role: Destroy Role + destroy_username_block: Delete Username Rule disable_2fa_user: Disable 2FA disable_custom_emoji: Disable Custom Emoji disable_relay: Disable Relay @@ -237,6 +241,7 @@ en-GB: update_report: Update Report update_status: Update Post update_user_role: Update Role + update_username_block: Update Username Rule actions: approve_appeal_html: "%{name} approved moderation decision appeal from %{target}" approve_user_html: "%{name} approved sign-up from %{target}" @@ -255,6 +260,7 @@ en-GB: create_relay_html: "%{name} created a relay %{target}" create_unavailable_domain_html: "%{name} stopped delivery to domain %{target}" create_user_role_html: "%{name} created %{target} role" + create_username_block_html: "%{name} added rule for usernames containing %{target}" demote_user_html: "%{name} demoted user %{target}" destroy_announcement_html: "%{name} deleted announcement %{target}" destroy_canonical_email_block_html: "%{name} unblocked email with the hash %{target}" @@ -268,7 +274,8 @@ en-GB: destroy_status_html: "%{name} removed post by %{target}" destroy_unavailable_domain_html: "%{name} stopped delivery to domain %{target}" destroy_user_role_html: "%{name} deleted %{target} role" - disable_2fa_user_html: "%{name} disabled two factor requirement for user %{target}" + destroy_username_block_html: "%{name} removed rule for usernames containing %{target}" + disable_2fa_user_html: "%{name} disabled two-factor requirement for user %{target}" disable_custom_emoji_html: "%{name} disabled emoji %{target}" disable_relay_html: "%{name} disabled the relay %{target}" disable_sign_in_token_auth_user_html: "%{name} disabled email token authentication for %{target}" @@ -302,6 +309,7 @@ en-GB: update_report_html: "%{name} updated report %{target}" update_status_html: "%{name} updated post by %{target}" update_user_role_html: "%{name} changed %{target} role" + update_username_block_html: "%{name} updated rule for usernames containing %{target}" deleted_account: deleted account empty: No logs found. filter_by_action: Filter by action @@ -320,7 +328,7 @@ en-GB: title: New announcement preview: disclaimer: As users cannot opt out of them, email notifications should be limited to important announcements such as personal data breach or server closure notifications. - explanation_html: 'The email will be sent to %{display_count} users. The following text will be included in the e-mail:' + explanation_html: 'The email will be sent to %{display_count} users. The following text will be included in the email:' title: Preview announcement notification publish: Publish published_msg: Announcement successfully published! @@ -357,7 +365,7 @@ en-GB: not_permitted: You are not permitted to perform this action overwrite: Overwrite shortcode: Shortcode - shortcode_hint: At least 2 characters, only alphanumeric characters and underscores + shortcode_hint: At least two characters, only alphanumeric characters and underscores title: Custom emojis uncategorized: Uncategorised unlist: Unlist @@ -437,7 +445,7 @@ en-GB: private_comment: Private comment private_comment_hint: Comment about this domain limitation for internal use by the moderators. public_comment: Public comment - public_comment_hint: Comment about this domain limitation for the general public, if advertising the list of domain limitations is enabled. + public_comment_hint: Comment about this domain limitation for the general public if advertising the list of domain limitations is enabled. reject_media: Reject media files reject_media_hint: Removes locally stored media files and refuses to download any in the future. Irrelevant for suspensions reject_reports: Reject reports @@ -541,7 +549,7 @@ en-GB: content_policies: comment: Internal note description_html: You can define content policies that will be applied to all accounts from this domain and any of its subdomains. - limited_federation_mode_description_html: You can chose whether to allow federation with this domain. + limited_federation_mode_description_html: You can choose whether to allow federation with this domain. policies: reject_media: Reject media reject_reports: Reject reports @@ -610,15 +618,15 @@ en-GB: created_msg: Successfully added new IP rule delete: Delete expires_in: - '1209600': 2 weeks - '15778476': 6 months - '2629746': 1 month - '31556952': 1 year - '86400': 1 day - '94670856': 3 years + '1209600': two weeks + '15778476': six months + '2629746': one month + '31556952': one year + '86400': one day + '94670856': three years new: title: Create new IP rule - no_ip_block_selected: No IP rules were changed as none were selected + no_ip_block_selected: No IP rules were changed, as none were selected title: IP rules relationships: title: "%{acct}'s relationships" @@ -634,7 +642,7 @@ en-GB: inbox_url: Relay URL pending: Waiting for relay's approval save_and_enable: Save and enable - setup: Setup a relay connection + setup: Set up a relay connection signatures_not_enabled: Relays may not work correctly while secure mode or limited federation mode is enabled status: Status title: Relays @@ -651,12 +659,12 @@ en-GB: actions: delete_description_html: The reported posts will be deleted and a strike will be recorded to help you escalate on future infractions by the same account. mark_as_sensitive_description_html: The media in the reported posts will be marked as sensitive and a strike will be recorded to help you escalate on future infractions by the same account. - other_description_html: See more options for controlling the account's behaviour and customise communication to the reported account. + other_description_html: See more options for controlling the account's behaviour and customising communication to the reported account. resolve_description_html: No action will be taken against the reported account, no strike recorded, and the report will be closed. - silence_description_html: The account will be visible only to those who already follow it or manually look it up, severely limiting its reach. Can always be reverted. Closes all reports against this account. - suspend_description_html: The account and all its contents will be inaccessible and eventually deleted, and interacting with it will be impossible. Reversible within 30 days. Closes all reports against this account. + silence_description_html: The account will be visible only to those who already follow it or manually look it up, severely limiting its reach. This can always be reverted. This closes all reports against this account. + suspend_description_html: The account and all its contents will be inaccessible and eventually deleted, and interacting with it will be impossible. This is reversible within 30 days. This closes all reports against this account. actions_description_html: Decide which action to take to resolve this report. If you take a punitive action against the reported account, an email notification will be sent to them, except when the Spam category is selected. - actions_description_remote_html: Decide which action to take to resolve this report. This will only affect how your server communicates with this remote account and handle its content. + actions_description_remote_html: Decide which action to take to resolve this report. This will only affect how your server communicates with this remote account and handles its content. actions_no_posts: This report doesn't have any associated posts to delete add_to_report: Add more to report already_suspended_badges: @@ -718,7 +726,7 @@ en-GB: suspend_html: Suspend @%{acct}, making their profile and contents inaccessible and impossible to interact with close_report: 'Mark report #%{id} as resolved' close_reports_html: Mark all reports against @%{acct} as resolved - delete_data_html: Delete @%{acct}'s profile and contents 30 days from now unless they get unsuspended in the meantime + delete_data_html: Delete @%{acct}'s profile and contents 30 days from now, unless they get unsuspended in the meantime preview_preamble_html: "@%{acct} will receive a warning with the following contents:" record_strike_html: Record a strike against @%{acct} to help you escalate on future violations from this account send_email_html: Send @%{acct} a warning email @@ -742,7 +750,7 @@ en-GB: moderation: Moderation special: Special delete: Delete - description_html: With user roles, you can customize which functions and areas of Mastodon your users can access. + description_html: With user roles, you can customise which functions and areas of Mastodon your users can access. edit: Edit '%{name}' role everyone: Default permissions everyone_full_description_html: This is the base role affecting all users, even those without an assigned role. All other roles inherit permissions from it. @@ -790,6 +798,8 @@ en-GB: view_dashboard_description: Allows users to access the dashboard and various metrics view_devops: DevOps view_devops_description: Allows users to access Sidekiq and pgHero dashboards + view_feeds: View live and topic feeds + view_feeds_description: Allows users to access the live and topic feeds regardless of server settings title: Roles rules: add_new: Add rule @@ -807,11 +817,11 @@ en-GB: settings: about: manage_rules: Manage server rules - preamble: Provide in-depth information about how the server is operated, moderated, funded. - rules_hint: There is a dedicated area for rules that your users are expected to adhere to. + preamble: Provide in-depth information about how the server is operated, moderated, and funded. + rules_hint: There is a dedicated area for rules to which your users are expected to adhere. title: About allow_referrer_origin: - desc: When your users click links to external sites, their browser may send the address of your Mastodon server as the referrer. Disable this if this would uniquely identify your users, e.g. if this is a personal Mastodon server. + desc: When your users click links to external sites, their browser may send the address of your Mastodon server as the referrer. Disable this if this would uniquely identify your users, eg if this is a personal Mastodon server. title: Allow external sites to see your Mastodon server as a traffic source appearance: preamble: Customise Mastodon's web interface. @@ -831,17 +841,28 @@ en-GB: title: Opt users out of search engine indexing by default discovery: follow_recommendations: Follow recommendations - preamble: Surfacing interesting content is instrumental in onboarding new users who may not know anyone Mastodon. Control how various discovery features work on your server. + preamble: Surfacing interesting content is instrumental in onboarding new users who may not know anyone on Mastodon. Control how various discovery features work on your server. privacy: Privacy profile_directory: Profile directory public_timelines: Public timelines publish_statistics: Publish statistics title: Discovery trends: Trends + wrapstodon: Wrapstodon domain_blocks: all: To everyone disabled: To no one users: To logged-in local users + feed_access: + modes: + authenticated: Authenticated users only + disabled: Require specific user role + public: Everyone + landing_page: + values: + about: About + local_feed: Local feed + trends: Trends registrations: moderation_recommandation: Please make sure you have an adequate and reactive moderation team before you open registrations to everyone! preamble: Control who can create an account on your server. @@ -862,7 +883,7 @@ en-GB: delete: Delete uploaded file destroyed_msg: Site upload successfully deleted! software_updates: - critical_update: Critical — please update quickly + critical_update: Critical – please update quickly description: It is recommended to keep your Mastodon installation up to date to benefit from the latest fixes and features. Moreover, it is sometimes critical to update Mastodon in a timely manner to avoid security issues. For these reasons, Mastodon checks for updates every 30 minutes, and will notify you according to your email notification preferences. documentation_link: Learn more release_notes: Release notes @@ -871,7 +892,7 @@ en-GB: types: major: Major release minor: Minor release - patch: Patch release — bugfixes and easy to apply changes + patch: Patch release – bug fixes and easy to apply changes version: Version statuses: account: Author @@ -895,13 +916,15 @@ en-GB: no_status_selected: No posts were changed as none were selected open: Open post original_status: Original post + quotes: Quotes reblogs: Reblogs replied_to_html: Replied to %{acct_link} status_changed: Post changed status_title: Post by @%{name} - title: Account posts - @%{name} + title: Account posts – @%{name} trending: Trending view_publicly: View publicly + view_quoted_post: View quoted post visibility: Visibility with_media: With media strikes: @@ -953,7 +976,7 @@ en-GB: message_html: A critical Mastodon update is available, please update as quickly as possible. software_version_patch_check: action: See available updates - message_html: A bugfix Mastodon update is available. + message_html: A bug fix Mastodon update is available. upload_check_privacy_error: action: Check here for more information message_html: "Your web server is misconfigured. The privacy of your users is at risk." @@ -1000,7 +1023,7 @@ en-GB: notified_on_html: Users notified on %{date} notify_users: Notify users preview: - explanation_html: 'The email will be sent to %{display_count} users who have signed up before %{date}. The following text will be included in the e-mail:' + explanation_html: 'The email will be sent to %{display_count} users who have signed up before %{date}. The following text will be included in the email:' send_preview: Send preview to %{email} send_to_all: one: Send %{display_count} email @@ -1024,12 +1047,12 @@ en-GB: confirm_allow_provider: Are you sure you want to allow selected providers? confirm_disallow: Are you sure you want to disallow selected links? confirm_disallow_provider: Are you sure you want to disallow selected providers? - description_html: These are links that are currently being shared a lot by accounts that your server sees posts from. It can help your users find out what's going on in the world. No links are displayed publicly until you approve the publisher. You can also allow or reject individual links. + description_html: These are links that are currently being shared a lot by accounts from which your server sees posts. It can help your users find out what's going on in the world. No links are displayed publicly until you approve the publisher. You can also allow or reject individual links. disallow: Disallow link disallow_provider: Disallow publisher - no_link_selected: No links were changed as none were selected + no_link_selected: No links were changed, as none were selected publishers: - no_publisher_selected: No publishers were changed as none were selected + no_publisher_selected: No publishers were changed, as none were selected shared_by_over_week: one: Shared by one person over the last week other: Shared by %{count} people over the last week @@ -1054,7 +1077,7 @@ en-GB: description_html: These are posts that your server knows about that are currently being shared and favourited a lot at the moment. It can help your new and returning users to find more people to follow. No posts are displayed publicly until you approve the author, and the author allows their account to be suggested to others. You can also allow or reject individual posts. disallow: Disallow post disallow_account: Disallow author - no_status_selected: No trending posts were changed as none were selected + no_status_selected: No trending posts were changed, as none were selected not_discoverable: Author has not opted-in to being discoverable shared_by: one: Shared or favourited one time @@ -1070,7 +1093,7 @@ en-GB: tag_uses_measure: total uses description_html: These are hashtags that are currently appearing in a lot of posts that your server sees. It can help your users find out what people are talking the most about at the moment. No hashtags are displayed publicly until you approve them. listable: Can be suggested - no_tag_selected: No tags were changed as none were selected + no_tag_selected: No tags were changed, as none were selected not_listable: Won't be suggested not_trendable: Won't appear under trends not_usable: Cannot be used @@ -1085,6 +1108,25 @@ en-GB: other: Used by %{count} people over the last week title: Recommendations & Trends trending: Trending + username_blocks: + add_new: Add new + block_registrations: Block registrations + comparison: + contains: Contains + equals: Equals + contains_html: Contains %{string} + created_msg: Successfully created username rule + delete: Delete + edit: + title: Edit username rule + matches_exactly_html: Equals %{string} + new: + create: Create rule + title: Create new username rule + no_username_block_selected: No username rules were changed, as none were selected + not_permitted: Not permitted + title: Username rules + updated_msg: Successfully updated username rule warning_presets: add_new: Add new delete: Delete @@ -1157,10 +1199,10 @@ en-GB: hint_html: If you want to move from another account to this one, here you can create an alias, which is required before you can proceed with moving followers from the old account to this one. This action by itself is harmless and reversible. The account migration is initiated from the old account. remove: Unlink alias appearance: - advanced_web_interface: Advanced web interface - advanced_web_interface_hint: 'If you want to make use of your entire screen width, the advanced web interface allows you to configure many different columns to see as much information at the same time as you want: Home, notifications, federated timeline, any number of lists and hashtags.' + advanced_settings: Advanced settings animations_and_accessibility: Animations and accessibility - confirmation_dialogs: Confirmation dialogues + boosting_preferences: Boosting preferences + boosting_preferences_info_html: "Tip: Regardless of settings, Shift + Click on the %{icon} Boost icon will immediately boost." discovery: Discovery localization: body: Mastodon is translated by volunteers. @@ -1204,7 +1246,7 @@ en-GB: description: prefix_invited_by_user: "@%{name} invites you to join this server of Mastodon!" prefix_sign_up: Sign up on Mastodon today! - suffix: With an account, you will be able to follow people, post updates and exchange messages with users from any Mastodon server and more! + suffix: With an account, you will be able to follow people, post updates, and exchange messages with users from any Mastodon server and more! didnt_get_confirmation: Didn't receive a confirmation link? dont_have_your_security_key: Don't have your security key? forgot_password: Forgot your password? @@ -1274,7 +1316,7 @@ en-GB: title: Author attribution challenge: confirm: Continue - hint_html: "Tip: We won't ask you for your password again for the next hour." + hint_html: "Tip: we won't ask you for your password again for the next hour." invalid_password: Invalid password prompt: Confirm password to continue crypto: @@ -1375,7 +1417,7 @@ en-GB: archive_takeout: date: Date download: Download your archive - hint_html: You can request an archive of your posts and uploaded media. The exported data will be in the ActivityPub format, readable by any compliant software. You can request an archive every 7 days. + hint_html: You can request an archive of your posts and uploaded media. The exported data will be in the ActivityPub format, readable by any compliant software. You can request an archive every seven days. in_progress: Compiling your archive... request: Request your archive size: Size @@ -1390,7 +1432,7 @@ en-GB: add_new: Add new errors: limit: You have already featured the maximum number of hashtags - hint_html: "What are featured hashtags? They are displayed prominently on your public profile and allow people to browse your public posts specifically under those hashtags. They are a great tool for keeping track of creative works or long-term projects." + hint_html: "Feature your most important hashtags on your profile. A great tool for keeping track of your creative works and long-term projects, featured hashtags are displayed prominently on your profile and allow quick access to your own posts." filters: contexts: account: Profiles @@ -1529,8 +1571,8 @@ en-GB: muting: Importing muted accounts type: Import type type_groups: - constructive: Follows & Bookmarks - destructive: Blocks & mutes + constructive: Follows and bookmarks + destructive: Blocks and mutes types: blocking: Blocking list bookmarks: Bookmarks @@ -1562,6 +1604,13 @@ en-GB: expires_at: Expires uses: Uses title: Invite people + link_preview: + author_html: By %{name} + potentially_sensitive_content: + action: Click to show + confirm_visit: Are you sure you wish to open this link? + hide_button: Hide + label: Potentially sensitive content lists: errors: limit: You have reached the maximum number of lists @@ -1596,7 +1645,7 @@ en-GB: images_and_video: Cannot attach a video to a post that already contains images not_found: Media %{ids} not found or already attached to another post not_ready: Cannot attach files that have not finished processing. Try again in a moment! - too_many: Cannot attach more than 4 files + too_many: Cannot attach more than four files migrations: acct: Moved to cancel: Cancel redirect @@ -1626,7 +1675,7 @@ en-GB: disabled_account: Your current account will not be fully usable afterwards. However, you will have access to data export as well as re-activation. followers: This action will move all followers from the current account to the new account only_redirect_html: Alternatively, you can only put up a redirect on your profile. - other_data: No other data will be moved automatically + other_data: No other data will be moved automatically (including your posts and the list of accounts you follow) redirect: Your current account's profile will be updated with a redirect notice and be excluded from searches moderation: title: Moderation @@ -1660,12 +1709,22 @@ en-GB: body: 'You were mentioned by %{name} in:' subject: You were mentioned by %{name} title: New mention + moderation_warning: + subject: You have received a moderation warning poll: subject: A poll by %{name} has ended + quote: + body: 'Your post was quoted by %{name}:' + subject: "%{name} quoted your post" + title: New quote + quoted_update: + subject: "%{name} edited a post you have quoted" reblog: body: 'Your post was boosted by %{name}:' subject: "%{name} boosted your post" title: New boost + severed_relationships: + subject: You have lost connections due to a moderation decision status: subject: "%{name} just posted" update: @@ -1710,6 +1769,9 @@ en-GB: self_vote: You cannot vote in your own polls too_few_options: must have more than one item too_many_options: can't contain more than %{max} items + vote: Vote + posting_defaults: + explanation: These settings will be used as defaults when you create new posts, but you can edit them per post within the composer. preferences: other: Other posting_defaults: Posting defaults @@ -1717,11 +1779,11 @@ en-GB: privacy: hint_html: "Customise how you want your profile and your posts to be found. A variety of features in Mastodon can help you reach a wider audience when enabled. Take a moment to review these settings to make sure they fit your use case." privacy: Privacy - privacy_hint_html: Control how much you want to disclose for the benefit of others. People discover interesting profiles and cool apps by browsing other people's follows and seeing which apps they post from, but you may prefer to keep it hidden. + privacy_hint_html: Control how much you want to disclose for the benefit of others. People discover interesting profiles and cool apps by browsing other people's follows and seeing from which apps they post, but you may prefer to keep it hidden. reach: Reach reach_hint_html: Control whether you want to be discovered and followed by new people. Do you want your posts to appear on the Explore screen? Do you want other people to see you in their follow recommendations? Do you want to accept all new followers automatically, or have granular control over each one? search: Search - search_hint_html: Control how you want to be found. Do you want people to find you by what you've publicly posted about? Do you want people outside Mastodon to find your profile when searching the web? Please mind that total exclusion from all search engines cannot be guaranteed for public information. + search_hint_html: Control how you want to be found. Do you want people to find you by what you've publicly posted about? Do you want people outside Mastodon to find your profile when searching the web? Please bear in mind that total exclusion from all search engines cannot be guaranteed for public information. title: Privacy and reach privacy_policy: title: Privacy Policy @@ -1865,6 +1927,9 @@ en-GB: other: "%{count} videos" boosted_from_html: Boosted from %{acct_link} content_warning: 'Content warning: %{warning}' + content_warnings: + hide: Hide post + show: Show more default_language: Same as interface language disallowed_hashtags: one: 'contained a disallowed hashtag: %{tags}' @@ -1872,15 +1937,31 @@ en-GB: edited_at_html: Edited %{date} errors: in_reply_not_found: The post you are trying to reply to does not appear to exist. + quoted_status_not_found: The post you are trying to quote does not appear to exist. + quoted_user_not_mentioned: Cannot quote a non-mentioned user in a Private Mention post. over_character_limit: character limit of %{max} exceeded pin_errors: direct: Posts that are only visible to mentioned users cannot be pinned limit: You have already pinned the maximum number of posts ownership: Someone else's post cannot be pinned reblog: A boost cannot be pinned + quote_error: + not_available: Post unavailable + pending_approval: Post pending + revoked: Post removed by author + quote_policies: + followers: Followers only + nobody: Just me + public: Anyone + quote_post_author: Quoted a post by %{acct} title: '%{name}: "%{quote}"' visibilities: + direct: Private mention + private: Followers only public: Public + public_long: Anyone on and off Mastodon + unlisted: Quiet public + unlisted_long: Hidden from Mastodon search results, trending, and public timelines statuses_cleanup: enabled: Automatically delete old posts enabled_hint: Automatically deletes your posts once they reach a specified age threshold, unless they match one of the exceptions below @@ -1913,9 +1994,9 @@ en-GB: '7889238': 3 months min_age_label: Age threshold min_favs: Keep posts favourited at least - min_favs_hint: Doesn't delete any of your posts that has received at least this number of favourites. Leave blank to delete posts regardless of their number of favourites + min_favs_hint: Doesn't delete any of your posts that have received at least this number of favourites. Leave blank to delete posts regardless of their number of favourites min_reblogs: Keep posts boosted at least - min_reblogs_hint: Doesn't delete any of your posts that has been boosted at least this number of times. Leave blank to delete posts regardless of their number of boosts + min_reblogs_hint: Doesn't delete any of your posts that have been boosted at least this number of times. Leave blank to delete posts regardless of their number of boosts stream_entries: sensitive_content: Sensitive content strikes: @@ -1997,8 +2078,8 @@ en-GB: terms_of_service_changed: agreement: By continuing to use %{domain}, you are agreeing to these terms. If you disagree with the updated terms, you may terminate your agreement with %{domain} at any time by deleting your account. changelog: 'At a glance, here is what this update means for you:' - description: 'You are receiving this e-mail because we''re making some changes to our terms of service at %{domain}. These updates will take effect on %{date}. We encourage you to review the updated terms in full here:' - description_html: You are receiving this e-mail because we're making some changes to our terms of service at %{domain}. These updates will take effect on %{date}. We encourage you to review the updated terms in full here. + description: 'You are receiving this email because we''re making some changes to our terms of service at %{domain}. These updates will take effect on %{date}. We encourage you to review the updated terms in full here:' + description_html: You are receiving this email because we're making some changes to our terms of service at %{domain}. These updates will take effect on %{date}. We encourage you to review the updated terms in full here. sign_off: The %{domain} team subject: Updates to our terms of service subtitle: The terms of service of %{domain} are changing @@ -2061,9 +2142,9 @@ en-GB: follows_title: Who to follow follows_view_more: View more people to follow hashtags_recent_count: - one: "%{people} person in the past 2 days" - other: "%{people} people in the past 2 days" - hashtags_subtitle: Explore what’s trending since past 2 days + one: "%{people} person in the past two days" + other: "%{people} people in the past two days" + hashtags_subtitle: Explore what’s trending since the past two days hashtags_title: Trending hashtags hashtags_view_more: View more trending hashtags post_action: Compose @@ -2083,9 +2164,9 @@ en-GB: seamless_external_login: You are logged in via an external service, so password and email settings are not available. signed_in_as: 'Logged in as:' verification: - extra_instructions_html: Tip: The link on your website can be invisible. The important part is rel="me" which prevents impersonation on websites with user-generated content. You can even use a link tag in the header of the page instead of a, but the HTML must be accessible without executing JavaScript. + extra_instructions_html: Tip: the link on your website can be invisible. The important part is rel="me" which prevents impersonation on websites with user-generated content. You can even use a link tag in the header of the page instead of a, but the HTML must be accessible without executing JavaScript. here_is_how: Here's how - hint_html: "Verifying your identity on Mastodon is for everyone. Based on open web standards, now and forever free. All you need is a personal website that people recognize you by. When you link to this website from your profile, we will check that the website links back to your profile and show a visual indicator on it." + hint_html: "Verifying your identity on Mastodon is for everyone. Based on open web standards, now and forever free. All you need is a personal website that people recognise you by. When you link to this website from your profile, we will check that the website links back to your profile and show a visual indicator on it." instructions_html: Copy and paste the code below into the HTML of your website. Then add the address of your website into one of the extra fields on your profile from the "Edit profile" tab and save changes. verification: Verification verified_links: Your verified links @@ -2107,3 +2188,6 @@ en-GB: not_supported: This browser doesn't support security keys otp_required: To use security keys please enable two-factor authentication first. registered_on: Registered on %{date} + wrapstodon: + description: See how %{name} used Mastodon this year! + title: Wrapstodon %{year} for %{name} diff --git a/config/locales/en.yml b/config/locales/en.yml index 11f8955f2f6ae1..84a556292f77a9 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -7,6 +7,8 @@ en: hosted_on: Mastodon hosted on %{domain} title: About accounts: + errors: + cannot_be_added_to_collections: This account cannot be added to collections. followers: one: Follower other: Followers @@ -796,6 +798,8 @@ en: view_dashboard_description: Allows users to access the dashboard and various metrics view_devops: DevOps view_devops_description: Allows users to access Sidekiq and pgHero dashboards + view_feeds: View live and topic feeds + view_feeds_description: Allows users to access the live and topic feeds regardless of server settings title: Roles rules: add_new: Add rule @@ -837,17 +841,28 @@ en: title: Opt users out of search engine indexing by default discovery: follow_recommendations: Follow recommendations - preamble: Surfacing interesting content is instrumental in onboarding new users who may not know anyone Mastodon. Control how various discovery features work on your server. + preamble: Surfacing interesting content is instrumental in onboarding new users who may not know anyone on Mastodon. Control how various discovery features work on your server. privacy: Privacy profile_directory: Profile directory public_timelines: Public timelines publish_statistics: Publish statistics title: Discovery trends: Trends + wrapstodon: Wrapstodon domain_blocks: all: To everyone disabled: To no one users: To logged-in local users + feed_access: + modes: + authenticated: Authenticated users only + disabled: Require specific user role + public: Everyone + landing_page: + values: + about: About + local_feed: Local feed + trends: Trends registrations: moderation_recommandation: Please make sure you have an adequate and reactive moderation team before you open registrations to everyone! preamble: Control who can create an account on your server. @@ -901,6 +916,7 @@ en: no_status_selected: No posts were changed as none were selected open: Open post original_status: Original post + quotes: Quotes reblogs: Reblogs replied_to_html: Replied to %{acct_link} status_changed: Post changed @@ -908,6 +924,7 @@ en: title: Account posts - @%{name} trending: Trending view_publicly: View publicly + view_quoted_post: View quoted post visibility: Visibility with_media: With media strikes: @@ -1182,10 +1199,10 @@ en: hint_html: If you want to move from another account to this one, here you can create an alias, which is required before you can proceed with moving followers from the old account to this one. This action by itself is harmless and reversible. The account migration is initiated from the old account. remove: Unlink alias appearance: - advanced_web_interface: Advanced web interface - advanced_web_interface_hint: 'If you want to make use of your entire screen width, the advanced web interface allows you to configure many different columns to see as much information at the same time as you want: Home, notifications, federated timeline, any number of lists and hashtags.' + advanced_settings: Advanced settings animations_and_accessibility: Animations and accessibility - confirmation_dialogs: Confirmation dialogs + boosting_preferences: Boosting preferences + boosting_preferences_info_html: "Tip: Regardless of settings, Shift + Click on the %{icon} Boost icon will immediately boost." discovery: Discovery localization: body: Mastodon is translated by volunteers. @@ -1587,6 +1604,13 @@ en: expires_at: Expires uses: Uses title: Invite people + link_preview: + author_html: By %{name} + potentially_sensitive_content: + action: Click to show + confirm_visit: Are you sure you wish to open this link? + hide_button: Hide + label: Potentially sensitive content lists: errors: limit: You have reached the maximum number of lists @@ -1651,7 +1675,7 @@ en: disabled_account: Your current account will not be fully usable afterwards. However, you will have access to data export as well as re-activation. followers: This action will move all followers from the current account to the new account only_redirect_html: Alternatively, you can only put up a redirect on your profile. - other_data: No other data will be moved automatically + other_data: No other data will be moved automatically (including your posts and the list of accounts you follow) redirect: Your current account's profile will be updated with a redirect notice and be excluded from searches moderation: title: Moderation @@ -1685,16 +1709,22 @@ en: body: 'You were mentioned by %{name} in:' subject: You were mentioned by %{name} title: New mention + moderation_warning: + subject: You have received a moderation warning poll: subject: A poll by %{name} has ended quote: body: 'Your post was quoted by %{name}:' subject: "%{name} quoted your post" title: New quote + quoted_update: + subject: "%{name} edited a post you have quoted" reblog: body: 'Your post was boosted by %{name}:' subject: "%{name} boosted your post" title: New boost + severed_relationships: + subject: You have lost connections due to a moderation decision status: subject: "%{name} just posted" update: @@ -1898,6 +1928,9 @@ en: other: "%{count} videos" boosted_from_html: Boosted from %{acct_link} content_warning: 'Content warning: %{warning}' + content_warnings: + hide: Hide post + show: Show more default_language: Same as interface language disallowed_hashtags: one: 'contained a disallowed hashtag: %{tags}' @@ -1906,16 +1939,22 @@ en: errors: in_reply_not_found: The post you are trying to reply to does not appear to exist. quoted_status_not_found: The post you are trying to quote does not appear to exist. + quoted_user_not_mentioned: Cannot quote a non-mentioned user in a Private Mention post. over_character_limit: character limit of %{max} exceeded pin_errors: direct: Posts that are only visible to mentioned users cannot be pinned limit: You have already pinned the maximum number of posts ownership: Someone else's post cannot be pinned reblog: A boost cannot be pinned + quote_error: + not_available: Post unavailable + pending_approval: Post pending + revoked: Post removed by author quote_policies: followers: Followers only nobody: Just me public: Anyone + quote_post_author: Quoted a post by %{acct} title: '%{name}: "%{quote}"' visibilities: direct: Private mention @@ -2150,3 +2189,6 @@ en: not_supported: This browser doesn't support security keys otp_required: To use security keys please enable two-factor authentication first. registered_on: Registered on %{date} + wrapstodon: + description: See how %{name} used Mastodon this year! + title: Wrapstodon %{year} for %{name} diff --git a/config/locales/eo.yml b/config/locales/eo.yml index 812053fa390721..04d3e9955a0911 100644 --- a/config/locales/eo.yml +++ b/config/locales/eo.yml @@ -831,7 +831,6 @@ eo: title: Defaŭltigi, ke uzantoj ne estu aŭtomate aldonitaj al la serĉilo-indekso sen sia konsento discovery: follow_recommendations: Sekvorekomendoj - preamble: Interesa enhavo estas grava por novaj uzantoj kiuj eble ne konas ajn iun. privacy: Privateco profile_directory: Profilujo public_timelines: Publikaj templinioj @@ -1175,10 +1174,7 @@ eo: hint_html: Se vi volas translokiĝi de alia konto al ĉi tie, kreu alinomon. Ĝi estas sekura kaj inversebla. Ĝi komencitas de malnova konto. remove: Malligili alinomon appearance: - advanced_web_interface: Altnivela retpaĝa interfaco - advanced_web_interface_hint: 'Se vi volas uzi la tutan larĝecon de via ekrano, la kompleksa reta interfaco permesas al vi agordi multajn malsamajn kolumnojn por vidi tiom da informoj kiom vi volas samtempe: Hejmo, sciigoj, fratara templinio, kaj ajna kvanto de listoj kaj kradvortoj.' animations_and_accessibility: Animacioj kaj alirebleco - confirmation_dialogs: Konfirmaj fenestroj discovery: Eltrovo localization: body: Mastodon estas tradukita de volontuloj. @@ -1325,7 +1321,7 @@ eo: warning: before: 'Antau ol dauri, legu ĉi tiujn notojn zorgeme:' caches: Enhavo kiu kaŝmemorigitas de aliaj serviloj eble restas - data_removal: Viaj afiŝoj kaj aliaj informoj estos forigita por eterne + data_removal: Viaj afiŝoj kaj aliaj informoj estos forigita por ĉiam email_change_html: Vi povas ŝanĝi vian retadreson sen forigi vian konton email_contact_html: Se ĝi ankoraŭ ne alvenas, vi povas retpoŝti al %{email} por helpo email_reconfirmation_html: Se vi ne ricevas la konfirmretpoŝton, vi povas denove peti @@ -1646,7 +1642,6 @@ eo: disabled_account: Via nuna konto ne estos tute uzebla poste. followers: Ĉi tiu ago translokigos ĉi tiujn sekvantojn de la nuna konto al la nova konto only_redirect_html: Alie, vi povas nur aldoni alidirekton en via profilo. - other_data: Neniu alia datumo estos movita aŭtomate redirect: Via nuna profilo de konto ĝisdatigotas kun alidirektoavizo moderation: title: Moderigado diff --git a/config/locales/es-AR.yml b/config/locales/es-AR.yml index 7e019daf4643cb..573c3ac859241e 100644 --- a/config/locales/es-AR.yml +++ b/config/locales/es-AR.yml @@ -7,6 +7,8 @@ es-AR: hosted_on: Mastodon alojado en %{domain} title: Información accounts: + errors: + cannot_be_added_to_collections: Esta cuenta no puede ser agregada a colecciones. followers: one: Seguidor other: Seguidores @@ -796,6 +798,8 @@ es-AR: view_dashboard_description: Permite a los usuarios acceder al panel de control y varias métricas view_devops: Operadores de desarrollo view_devops_description: Permite a los usuarios acceder a los paneles de Sidekiq y pgHero + view_feeds: Ver líneas temporales y por temas + view_feeds_description: Permite a los usuarios acceder a las líneas temporales en vivo y por temas, sin importar la configuración del servidor title: Roles rules: add_new: Agregar regla @@ -844,10 +848,21 @@ es-AR: publish_statistics: Publicar estadísticas title: Descubrí trends: Tendencias + wrapstodon: MastodonAnual domain_blocks: all: A todos disabled: A nadie users: A usuarios locales con sesiones abiertas + feed_access: + modes: + authenticated: Solo usuarios autenticados + disabled: Requerir un rol de específico de usuario + public: Todos + landing_page: + values: + about: Información + local_feed: Línea temporal local + trends: Tendencias registrations: moderation_recommandation: Por favor, ¡asegurate de tener un equipo de moderación adecuado y reactivo antes de abrir los registros a todos! preamble: Controlá quién puede crear una cuenta en tu servidor. @@ -901,6 +916,7 @@ es-AR: no_status_selected: No se cambió ningún mensaje, ya que ninguno fue seleccionado open: Abrir mensaje original_status: Mensaje original + quotes: Citas reblogs: Adhesiones replied_to_html: Respondido a %{acct_link} status_changed: Mensaje cambiado @@ -908,6 +924,7 @@ es-AR: title: Mensajes de cuenta - @%{name} trending: En tendencia view_publicly: Ver públicamente + view_quoted_post: Ver mensaje citado visibility: Visibilidad with_media: Con medios strikes: @@ -1182,10 +1199,10 @@ es-AR: hint_html: Si querés mudarte desde otra cuenta a esta, acá podés crear un alias, el cual es necesario antes de empezar a mudar seguidores de la cuenta vieja a ésta. Esta acción por sí misma es inofensiva y reversible. La migración de la cuenta se inicia desde la cuenta anterior. remove: Desvincular alias appearance: - advanced_web_interface: Interface web avanzada - advanced_web_interface_hint: 'Si querés hacer uso de todo el ancho de tu pantalla, la interface web avanzada te permite configurar varias columnas diferentes para ver tanta información al mismo tiempo como quieras: "Principal", "Notificaciones", "Línea temporal federada", y cualquier número de listas y etiquetas.' + advanced_settings: Configuración avanzada animations_and_accessibility: Animaciones y accesibilidad - confirmation_dialogs: Diálogos de confirmación + boosting_preferences: Configuración de adhesiones + boosting_preferences_info_html: "Truco: Más allá de la configuración, si mantenés pulsada la tecla Mayúscula y hacés clic en el ícono para adherir %{icon}, vas a adherir inmediatamente." discovery: Descubrí localization: body: Mastodon es localizado por voluntarios. @@ -1587,6 +1604,13 @@ es-AR: expires_at: Vence uses: Usos title: Invitar a gente + link_preview: + author_html: Por %{name} + potentially_sensitive_content: + action: Clic para mostrar + confirm_visit: "¿Está seguro de que querés abrir este enlace?" + hide_button: Ocultar + label: Contenido potencialmente sensible lists: errors: limit: Alcanzaste el número máximo de listas @@ -1651,7 +1675,7 @@ es-AR: disabled_account: Tu cuenta actual no será completamente utilizable luego de esto. Sin embargo, tendrás acceso a la exportación de datos así como a la reactivación. followers: Esta acción mudará a todos los seguidores de la cuenta actual a la cuenta nueva only_redirect_html: Alternativamente, podés poner solamente un redireccionamiento en tu perfil. - other_data: No se mudarán otros datos automáticamente + other_data: No se mudarán otros datos automáticamente (esto incluye tus mensajes y la lista de cuentas que seguís) redirect: El perfil de tu cuenta actual se actualizará con un aviso de redireccionamiento y será excluido de las búsquedas moderation: title: Moderación @@ -1685,16 +1709,22 @@ es-AR: body: 'Fuiste mencionado por %{name} en:' subject: Fuiste mencionado por %{name} title: Nueva mención + moderation_warning: + subject: Recibiste una advertencia de moderación poll: subject: Terminó una encuesta de %{name} quote: body: 'Tu mensaje fue citado por %{name}:' subject: "%{name} citó tu mensaje" title: Nueva cita + quoted_update: + subject: "%{name} editó un mensaje que citaste" reblog: body: "%{name} adhirió a tu mensaje:" subject: "%{name} adhirió a tu mensaje" title: Nueva adhesión + severed_relationships: + subject: Perdiste seguidores y/o cuentas seguidas, debido a una decisión de moderación status: subject: "%{name} acaba de enviar un mensaje" update: @@ -1897,6 +1927,9 @@ es-AR: other: "%{count} videos" boosted_from_html: Adherido desde %{acct_link} content_warning: 'Advertencia de contenido: %{warning}' + content_warnings: + hide: Ocultar mensaje + show: Mostrar más default_language: Igual que el idioma de la interface disallowed_hashtags: one: 'contenía una etiqueta no permitida: %{tags}' @@ -1905,16 +1938,22 @@ es-AR: errors: in_reply_not_found: El mensaje al que intentás responder no existe. quoted_status_not_found: El mensaje al que intentás citar parece que no existe. + quoted_user_not_mentioned: No se puede citar a un usuario no mencionado en un mensaje de mención privada. over_character_limit: se excedió el límite de %{max} caracteres pin_errors: direct: Los mensajes que sólo son visibles para los usuarios mencionados no pueden ser fijados limit: Ya fijaste el número máximo de mensajes ownership: No se puede fijar el mensaje de otra cuenta reblog: No se puede fijar una adhesión + quote_error: + not_available: Mensaje no disponible + pending_approval: Mensaje pendiente + revoked: Mensaje eliminado por el autor quote_policies: followers: Solo seguidores nobody: Solo yo public: Cualquier cuenta + quote_post_author: Se citó un mensaje de %{acct} title: '%{name}: "%{quote}"' visibilities: direct: Mención privada @@ -2149,3 +2188,6 @@ es-AR: not_supported: Este navegador web no soporta llaves de seguridad otp_required: Para usar llaves de seguridad, por favor, primero habilitá la autenticación de dos factores. registered_on: Registrado el %{date} + wrapstodon: + description: "¡Enterate cómo %{name} usó Mastodon este año!" + title: MastodonAnual %{year} para %{name} diff --git a/config/locales/es-MX.yml b/config/locales/es-MX.yml index 2f0a69891f1712..5fcefbce28d5e2 100644 --- a/config/locales/es-MX.yml +++ b/config/locales/es-MX.yml @@ -7,6 +7,8 @@ es-MX: hosted_on: Mastodon alojado en %{domain} title: Acerca de accounts: + errors: + cannot_be_added_to_collections: Esta cuenta no se puede añadir a las colecciones. followers: one: Seguidor other: Seguidores @@ -40,14 +42,14 @@ es-MX: avatar: Foto de perfil by_domain: Dominio change_email: - changed_msg: Correo cambiado exitosamente! + changed_msg: "¡Correo electrónico cambiado correctamente!" current_email: Correo electrónico actual label: Cambiar el correo electrónico new_email: Nuevo correo electrónico submit: Cambiar el correo electrónico title: Cambiar el correo electrónico de %{username} change_role: - changed_msg: Rol cambiado exitosamente! + changed_msg: "¡Rol cambiado correctamente!" edit_roles: Administrar roles de usuario label: Cambiar de rol no_role: Sin rol @@ -105,7 +107,7 @@ es-MX: no_account_selected: Ninguna cuenta se cambió como ninguna fue seleccionada no_limits_imposed: Sin límites impuestos no_role_assigned: Sin rol asignado - not_subscribed: No se está suscrito + not_subscribed: No suscrito pending: Revisión pendiente perform_full_suspension: Suspender previous_strikes: Amonestaciones previas @@ -139,10 +141,10 @@ es-MX: search_same_ip: Otros usuarios con la misma IP security: Seguridad security_measures: - only_password: Sólo contraseña + only_password: Solo contraseña password_and_2fa: Contraseña y 2FA sensitive: Sensible - sensitized: marcado como sensible + sensitized: Marcado como sensible shared_inbox_url: URL de bandeja compartida show: created_reports: Reportes hechos por esta cuenta @@ -161,10 +163,10 @@ es-MX: unblocked_email_msg: Desbloqueo exitoso de la dirección de correo de %{username} unconfirmed_email: Correo electrónico sin confirmar undo_sensitized: Desmarcar como sensible - undo_silenced: Des-silenciar - undo_suspension: Des-suspender + undo_silenced: Deshacer límite + undo_suspension: Deshacer suspensión unsilenced_msg: Se quitó con éxito el límite de la cuenta %{username} - unsubscribe: Desuscribir + unsubscribe: Cancelar suscripción unsuspended_msg: Se quitó con éxito la suspensión de la cuenta de %{username} username: Nombre de usuario view_domain: Ver resumen del dominio @@ -312,7 +314,7 @@ es-MX: empty: No se encontraron registros. filter_by_action: Filtrar por acción filter_by_user: Filtrar por usuario - title: Log de auditoría + title: Registro de auditoría unavailable_instance: "(nombre de dominio no disponible)" announcements: back: Volver a la sección de anuncios @@ -359,7 +361,7 @@ es-MX: listed: Listados new: title: Añadir nuevo emoji personalizado - no_emoji_selected: No se cambió ningún emoji ya que no se seleccionó ninguno + no_emoji_selected: No se modificó ningún emoji, ya que no se seleccionó ninguno not_permitted: No tienes permiso para realizar esta acción overwrite: Sobrescribir shortcode: Código de atajo @@ -373,8 +375,8 @@ es-MX: upload: Subir dashboard: active_users: usuarios activos - interactions: interaccciones - media_storage: Almacenamiento + interactions: interacciones + media_storage: Almacenamiento multimedia new_users: nuevos usuarios opened_reports: informes abiertos pending_appeals_html: @@ -413,7 +415,7 @@ es-MX: confirm_suspension: cancel: Cancelar confirm: Suspender - permanent_action: Deshacer la suspensión no recuperará ningún data o relaciones. + permanent_action: Anular la suspensión no restaurará ningún dato ni relación. preamble_html: Estás a punto de suspender a %{domain} y sus subdominios. remove_all_data: Esto eliminará todo el contenido, multimedia y datos de perfil de las cuentas de este dominio de tu servidor. stop_communication: Tu servidor dejará de comunicarse con estos servidores. @@ -436,7 +438,7 @@ es-MX: silence: Limitar suspend: Suspender title: Nuevo bloque de dominio - no_domain_block_selected: No se cambió ningún bloqueo de dominio ya que ninguno fue seleccionado + no_domain_block_selected: No se modificó ningún bloqueo de dominio, ya que no se seleccionó ninguno not_permitted: No tienes permiso para realizar esta acción obfuscate: Ocultar nombre de dominio obfuscate_hint: Oculta parcialmente el nombre de dominio en la lista si mostrar la lista de limitaciones de dominio está habilitado @@ -624,12 +626,12 @@ es-MX: '94670856': 3 años new: title: Crear nueva regla IP - no_ip_block_selected: No se han cambiado reglas IP ya que no se ha seleccionado ninguna + no_ip_block_selected: No se modificó ninguna regla de IP, ya que no se seleccionó ninguna title: Reglas IP relationships: title: Relaciones de %{acct} relays: - add_new: Añadir un nuevo relés + add_new: Añadir nuevo relé delete: Borrar description_html: Un relés de federación es un servidor intermedio que intercambia grandes volúmenes de publicaciones públicas entre servidores que se suscriben y publican en él. Puede ayudar a servidores pequeños y medianos a descubrir contenido del fediverso, que de otra manera requeriría que los usuarios locales siguiesen manualmente a personas de servidores remotos. disable: Deshabilitar @@ -659,7 +661,7 @@ es-MX: mark_as_sensitive_description_html: Los archivos multimedia en las publicaciones reportadas se marcarán como sensibles y se aplicará una amonestación para ayudarte a escalar las futuras infracciones de la misma cuenta. other_description_html: Ver más opciones para controlar el comportamiento de la cuenta y personalizar la comunicación de la cuenta reportada. resolve_description_html: No se tomarán medidas contra la cuenta denunciada, no se registrará la amonestación, y se cerrará el informe. - silence_description_html: La cuenta será visible sólo para aquellos que ya la sigan o la busquen manualmente, limitando severamente su visibilidad. Siempre puede ser revertido. Cierra todos los reportes contra esta cuenta. + silence_description_html: La cuenta será visible solo para aquellos que ya la sigan o la busquen manualmente, limitando severamente su visibilidad. Siempre puede ser revertido. Cierra todos los reportes contra esta cuenta. suspend_description_html: La cuenta y todos sus contenidos serán inaccesibles y eventualmente eliminados, e interactuar con ella será imposible. Reversible durante 30 días. Cierra todos los reportes contra esta cuenta. actions_description_html: Decide qué medidas tomar para resolver esta denuncia. Si tomas una acción punitiva contra la cuenta denunciada, se le enviará a dicha cuenta una notificación por correo electrónico, excepto cuando se seleccione la categoría Spam. actions_description_remote_html: Decide qué medidas tomar para resolver este reporte. Esto solo afectará a la forma en que tu servidor se comunica con esta cuenta remota y gestiona su contenido. @@ -796,6 +798,8 @@ es-MX: view_dashboard_description: Permite a los usuarios acceder al panel de control y varias métricas view_devops: DevOps view_devops_description: Permite a los usuarios acceder a los paneles de control Sidekiq y pgHero + view_feeds: Ver temas y feed en vivo + view_feeds_description: Permitir a los usuarios acceder a temas y feeds en vivo sin importar la configuración de los servidores title: Roles rules: add_new: Añadir norma @@ -837,17 +841,28 @@ es-MX: title: Optar por los usuarios fuera de la indexación en los motores de búsqueda por defecto discovery: follow_recommendations: Recomendaciones de cuentas - preamble: Exponer contenido interesante a la superficie es fundamental para incorporar nuevos usuarios que pueden no conocer a nadie Mastodon. Controla cómo funcionan varias opciones de descubrimiento en tu servidor. + preamble: Mostrar contenido interesante es fundamental para atraer a nuevos usuarios que quizá no conozcan a nadie en Mastodon. Controla cómo funcionan las distintas funciones de descubrimiento en tu servidor. privacy: Privacidad profile_directory: Directorio de perfiles public_timelines: Lineas de tiempo públicas publish_statistics: Publicar estadísticas title: Descubrimiento trends: Tendencias + wrapstodon: Wrapstodon domain_blocks: all: A todos disabled: A nadie users: Para los usuarios locales que han iniciado sesión + feed_access: + modes: + authenticated: Solo usuarios registrados + disabled: Requerir un rol de usuario específico + public: Todos + landing_page: + values: + about: Acerca de + local_feed: Feed local + trends: Tendencias registrations: moderation_recommandation: "¡Por favor, asegúrate de contar con un equipo de moderación adecuado y activo antes de abrir el registro al público!" preamble: Controla quién puede crear una cuenta en tu servidor. @@ -901,6 +916,7 @@ es-MX: no_status_selected: No se cambió ninguna publicación al no seleccionar ninguna open: Abrir publicación original_status: Publicación original + quotes: Citas reblogs: Impulsos replied_to_html: En respuesta a %{acct_link} status_changed: Publicación cambiada @@ -908,6 +924,7 @@ es-MX: title: Publicaciones de la cuenta - @%{name} trending: En tendencia view_publicly: Ver públicamente + view_quoted_post: Ver publicación citada visibility: Visibilidad with_media: Con multimedia strikes: @@ -1182,10 +1199,10 @@ es-MX: hint_html: Si deseas migrar de otra cuenta a esta, aquí puedes crear un alias, que es necesario para poder mover seguidores de la cuenta anterior a esta. Esta acción por sí misma es inofensiva y reversible. La migración de la cuenta se inicia desde la cuenta anterior. remove: Desvincular alias appearance: - advanced_web_interface: Interfaz web avanzada - advanced_web_interface_hint: 'Si deseas aprovechar todo el ancho de tu pantalla, la interfaz web avanzada te permite configurar muchas columnas diferentes para ver toda la información que quieras al mismo tiempo: inicio, notificaciones, cronología federada, cualquier número de listas y etiquetas.' + advanced_settings: Configuración avanzada animations_and_accessibility: Animaciones y accesibilidad - confirmation_dialogs: Diálogos de confirmación + boosting_preferences: Preferencias de impulso + boosting_preferences_info_html: "Consejo: Sin importar los ajustes, pulsar Mayus al hacer clic en el icono de %{icon} Impulsar, dará impulso inmediatamente." discovery: Descubrir localization: body: Mastodon es traducido con la ayuda de voluntarios. @@ -1587,6 +1604,13 @@ es-MX: expires_at: Expira uses: Usos title: Invitar a gente + link_preview: + author_html: Por %{name} + potentially_sensitive_content: + action: Haz clic para mostrar + confirm_visit: "¿Estás seguro de que deseas abrir este enlace?" + hide_button: Ocultar + label: Contenido potencialmente sensible lists: errors: limit: Has alcanzado la cantidad máxima de listas @@ -1651,7 +1675,7 @@ es-MX: disabled_account: Tu cuenta actual no será completamente utilizable después. Sin embargo, tendrás acceso a la exportación de datos así como a la reactivación. followers: Esta acción migrará a todos los seguidores de la cuenta actual a la nueva cuenta only_redirect_html: Alternativamente, solo puedes poner una redirección en tu perfil. - other_data: No se moverán otros datos automáticamente + other_data: No se transferirán automáticamente otros datos (incluidas tus publicaciones y la lista de cuentas que sigues) redirect: El perfil de tu cuenta actual se actualizará con un aviso de redirección y será excluido de las búsquedas moderation: title: Moderación @@ -1685,16 +1709,22 @@ es-MX: body: 'Fuiste mencionado por %{name} en:' subject: Fuiste mencionado por %{name} title: Nueva mención + moderation_warning: + subject: Has recibido una advertencia de moderación poll: subject: Una encuesta de %{name} ha terminado quote: body: 'Tu publicación fue citada por %{name}:' subject: "%{name} citó tu publicación" title: Nueva cita + quoted_update: + subject: "%{name} editó una publicación que has citado" reblog: body: 'Tu publicación fue impulsada por %{name}:' subject: "%{name} ha impulsado tu publicación" title: Nuevo impulso + severed_relationships: + subject: Has perdido conexiones debido a una decisión de moderación status: subject: "%{name} acaba de publicar" update: @@ -1897,6 +1927,9 @@ es-MX: other: "%{count} vídeos" boosted_from_html: Impulsado desde %{acct_link} content_warning: 'Alerta de contenido: %{warning}' + content_warnings: + hide: Ocultar publicación + show: Mostrar más default_language: Igual que el idioma de la interfaz disallowed_hashtags: one: 'contenía una etiqueta no permitida: %{tags}' @@ -1905,16 +1938,22 @@ es-MX: errors: in_reply_not_found: La publicación a la que estás intentando responder no existe. quoted_status_not_found: La publicación que intentas citar no parece existir. + quoted_user_not_mentioned: No se puede citar a un usuario no mencionado en una mención privada. over_character_limit: Límite de caracteres de %{max} superado pin_errors: direct: Las publicaciones que son visibles solo para los usuarios mencionados no pueden fijarse limit: Ya has fijado el número máximo de publicaciones ownership: La publicación de alguien más no puede fijarse reblog: No se puede fijar una publicación impulsada + quote_error: + not_available: Publicación no disponible + pending_approval: Publicación pendiente + revoked: Publicación eliminada por el autor quote_policies: followers: Solo seguidores nobody: Solo yo public: Cualquiera + quote_post_author: Cita de una publicación de %{acct} title: "%{name}: «%{quote}»" visibilities: direct: Mención privada @@ -2149,3 +2188,6 @@ es-MX: not_supported: Este navegador no soporta claves de seguridad otp_required: Para usar claves de seguridad, por favor habilite primero la autenticación de doble factor. registered_on: Registrado el %{date} + wrapstodon: + description: "¡Ve cómo %{name} usó Mastodon este año!" + title: Wrapstodon %{year} para %{name} diff --git a/config/locales/es.yml b/config/locales/es.yml index a14c924bd00b02..ccee7cab646819 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -7,6 +7,8 @@ es: hosted_on: Mastodon alojado en %{domain} title: Acerca de accounts: + errors: + cannot_be_added_to_collections: Esta cuenta no se puede añadir a las colecciones. followers: one: Seguidor other: Seguidores @@ -517,13 +519,13 @@ es: title: Fediverse Auxiliary Service Providers (Proveedores de Servicio Auxiliares del Fediverso) title: FASP follow_recommendations: - description_html: "Las recomendaciones de cuentas ayudan a los nuevos usuarios a encontrar rápidamente contenido interesante. Cuando un usuario no ha interactuado con otros lo suficiente como para suscitar recomendaciones personalizadas de cuentas a las que seguir, en su lugar se le recomiendan estas cuentas. Se recalculan diariamente a partir de una mezcla de cuentas con el mayor número de interacciones recientes y con el mayor número de seguidores locales con un idioma determinado." + description_html: "Las recomendaciones de cuentas a las que seguir ayudan a los nuevos usuarios a encontrar rápidamente contenido interesante. Cuando un usuario no ha interactuado con otros lo suficiente como para formar recomendaciones personalizadas de seguimiento, estas cuentas se recomiendan en su lugar. Se recalculan diariamente a partir de una mezcla de cuentas con las interacciones más recientes y el mayor número de seguidores para un idioma determinado." language: Para el idioma status: Estado - suppress: Suprimir recomendación de cuentas + suppress: Eliminar recomendación de cuentas a las que seguir suppressed: Suprimida - title: Recomendaciones de cuentas - unsuppress: Restaurar recomendaciones de cuentas + title: Recomendaciones de cuentas a las que seguir + unsuppress: Restaurar recomendaciones de cuentas a las que seguir instances: audit_log: title: Registros de auditoría recientes @@ -796,6 +798,8 @@ es: view_dashboard_description: Permite a los usuarios acceder al panel de control y varias métricas view_devops: DevOps view_devops_description: Permite a los usuarios acceder a los paneles de control Sidekiq y pgHero + view_feeds: Visualización de cronologías y tendencias + view_feeds_description: Permitir a los usuarios acceder a las cronologías públicas y tendencias sin importar la configuración del servidor title: Roles rules: add_new: Añadir norma @@ -837,7 +841,7 @@ es: title: Excluir por defecto a los usuarios de la indexación del motor de búsqueda discovery: follow_recommendations: Recomendaciones de cuentas - preamble: Exponer contenido interesante a la superficie es fundamental para incorporar nuevos usuarios que pueden no conocer a nadie Mastodon. Controla cómo funcionan varias opciones de descubrimiento en tu servidor. + preamble: Exponer contenido interesante es fundamental para incorporar nuevos usuarios que pueden no conocer a nadie en Mastodon. Controla cómo funcionan en tu servidor las diferentes opciones de descubrimiento. privacy: Privacidad profile_directory: Directorio de perfiles public_timelines: Lineas de tiempo públicas @@ -848,6 +852,16 @@ es: all: A todos disabled: A nadie users: Para los usuarios locales que han iniciado sesión + feed_access: + modes: + authenticated: Solo usuarios autenticados + disabled: Requerir un rol de usuario específico + public: Todos + landing_page: + values: + about: Acerca de + local_feed: Cronología local + trends: Tendencias registrations: moderation_recommandation: Por favor, ¡asegúrate de tener un equipo de moderación adecuado y reactivo antes de abrir los registros a todo el mundo! preamble: Controla quién puede crear una cuenta en tu servidor. @@ -901,6 +915,7 @@ es: no_status_selected: No se cambió ninguna publicación al no seleccionar ninguna open: Abrir publicación original_status: Publicación original + quotes: Citas reblogs: Impulsos replied_to_html: Respondió a %{acct_link} status_changed: Publicación cambiada @@ -908,6 +923,7 @@ es: title: Publicaciones de la cuenta - @%{name} trending: En tendencia view_publicly: Ver públicamente + view_quoted_post: Ver publicación citada visibility: Visibilidad with_media: Con multimedia strikes: @@ -1182,10 +1198,10 @@ es: hint_html: Si quieres migrar de otra cuenta a esta, aquí puedes crear un alias, es necesario proceder antes de empezar a mover seguidores de la cuenta anterior a esta. Esta acción por sí misma es inofensiva y reversible. La migración de la cuenta se inicia desde la cuenta antigua. remove: Desvincular alias appearance: - advanced_web_interface: Interfaz web avanzada - advanced_web_interface_hint: 'Si quieres aprovechar todo el ancho de tu pantalla, la interfaz web avanzada te permite configurar muchas columnas diferentes para ver toda la información que quieras al mismo tiempo: Inicio, notificaciones, cronología federada, cualquier número de listas y etiquetas.' + advanced_settings: Ajustes avanzados animations_and_accessibility: Animaciones y accesibilidad - confirmation_dialogs: Diálogos de confirmación + boosting_preferences: Preferencias de impulso + boosting_preferences_info_html: "Consejo: Sin importar los ajustes, pulsar Mayus al hacer clic en el icono de %{icon} Impulsar, dará impulso inmediatamente." discovery: Descubrir localization: body: Mastodon es traducido con la ayuda de voluntarios. @@ -1587,6 +1603,13 @@ es: expires_at: Expira uses: Usos title: Invitar a gente + link_preview: + author_html: Por %{name} + potentially_sensitive_content: + action: Pulsa para mostrar + confirm_visit: "¿Seguro que quieres abrir este enlace?" + hide_button: Ocultar + label: Contenido potencialmente sensible lists: errors: limit: Has alcanzado la cantidad máxima de listas @@ -1651,7 +1674,7 @@ es: disabled_account: Tu cuenta actual no será completamente utilizable después. Sin embargo, tendrás acceso a la exportación de datos así como a la reactivación. followers: Esta acción migrará a todos los seguidores de la cuenta actual a la nueva cuenta only_redirect_html: Alternativamente, solo puedes poner una redirección en tu perfil. - other_data: No se moverán otros datos automáticamente + other_data: Ningún otro dato se moverá automáticamente (esto incluye tus publicaciones y la lista de cuentas que sigues) redirect: El perfil de tu cuenta actual se actualizará con un aviso de redirección y será excluido de las búsquedas moderation: title: Moderación @@ -1685,16 +1708,22 @@ es: body: 'Fuiste mencionado por %{name} en:' subject: Fuiste mencionado por %{name} title: Nueva mención + moderation_warning: + subject: Has recibido una advertencia de moderación poll: subject: Una encuesta de %{name} ha terminado quote: body: 'Tu publicación ha sido citada por %{name}:' subject: "%{name} citó tu publicación" title: Nueva cita + quoted_update: + subject: "%{name} editó una publicación que has citado" reblog: body: 'Tu publicación fue impulsada por %{name}:' subject: "%{name} impulsó tu publicación" title: Nueva difusión + severed_relationships: + subject: Has perdido conexiones debido a una decisión de moderación status: subject: "%{name} acaba de publicar" update: @@ -1897,6 +1926,9 @@ es: other: "%{count} vídeos" boosted_from_html: Impulsado desde %{acct_link} content_warning: 'Alerta de contenido: %{warning}' + content_warnings: + hide: Ocultar publicación + show: Mostrar más default_language: Igual que el idioma de la interfaz disallowed_hashtags: one: 'contenía una etiqueta no permitida: %{tags}' @@ -1905,16 +1937,22 @@ es: errors: in_reply_not_found: La publicación a la que intentas responder no existe. quoted_status_not_found: La publicación que estás intentando citar no parece existir. + quoted_user_not_mentioned: No se puede citar a un usuario no mencionado en una Mención Privada. over_character_limit: Límite de caracteres de %{max} superado pin_errors: direct: Las publicaciones que son visibles solo para los usuarios mencionados no pueden fijarse limit: Ya has fijado el número máximo de publicaciones ownership: La publicación de otra persona no puede fijarse reblog: Una publicación impulsada no puede fijarse + quote_error: + not_available: Publicación no disponible + pending_approval: Publicación pendiente + revoked: Publicación borrada por el autor quote_policies: followers: Solo seguidores nobody: Solo yo public: Cualquiera + quote_post_author: Citando una publicación de %{acct} title: "%{name}: «%{quote}»" visibilities: direct: Mención privada @@ -2149,3 +2187,6 @@ es: not_supported: Este navegador no soporta claves de seguridad otp_required: Para usar claves de seguridad, por favor habilite primero la autenticación de doble factor. registered_on: Registrado el %{date} + wrapstodon: + description: "¡Mira cómo %{name} ha usado Mastodon este año!" + title: Wrapstodon %{year} para %{name} diff --git a/config/locales/et.yml b/config/locales/et.yml index 9607d7661d1c98..27d85f3ab0336b 100644 --- a/config/locales/et.yml +++ b/config/locales/et.yml @@ -384,8 +384,8 @@ et: one: "%{count} ootel raport" other: "%{count} ootel raportit" pending_tags_html: - one: "%{count} ootel silt" - other: "%{count} ootel silti" + one: "%{count} ootel teemaviide" + other: "%{count} ootel teemaviidet" pending_users_html: one: "%{count} ootel kasutaja" other: "%{count} ootel kasutajat" @@ -783,7 +783,7 @@ et: manage_settings: Halda sätteid manage_settings_description: Lubab kasutajatel muuta lehekülje sätteid manage_taxonomies: Halda taksonoomiaid - manage_taxonomies_description: Luba kasutajatel populaarset sisu üle vaadata ning uuendada siltide sätteid + manage_taxonomies_description: Luba kasutajatel populaarset sisu üle vaadata ning uuendada teemaviidete seadistusi manage_user_access: Halda kasutajate ligipääsu manage_user_access_description: Võimaldab kasutajatel keelata teiste kasutajate kaheastmelise autentimise, muuta oma e-posti aadressi ja lähtestada oma parooli manage_users: Kasutajate haldamine @@ -796,6 +796,8 @@ et: view_dashboard_description: Lubab kasutajail pääseda ligi töölauale ja erinevale meetrikale view_devops: DevOps view_devops_description: Lubab kasutajail ligipääsu Sidekiq ja pgHero töölaudadele + view_feeds: Vaata postituste ja teemade voogu reaalajas + view_feeds_description: Sõltumata serveri seadistustest luba kasutajatel vaadata postituste ja teemade voogu reaalajas title: Rollid rules: add_new: Lisa reegel @@ -848,6 +850,16 @@ et: all: Kõigile disabled: Mitte kellelegi users: Sisseloginud kohalikele kasutajatele + feed_access: + modes: + authenticated: Vaid autenditud kasutajad + disabled: Eelda konkreetse kasutajarolli olemasolu + public: Kõik + landing_page: + values: + about: Teave + local_feed: Kohalik sisuvoog + trends: Trendid registrations: moderation_recommandation: Enne kõigi jaoks registreerimise avamist veendu, et oleks olemas adekvaatne ja reageerimisvalmis modereerijaskond! preamble: Kes saab serveril konto luua. @@ -901,6 +913,7 @@ et: no_status_selected: Ühtegi postitust ei muudetud, sest ühtegi polnud valitud open: Ava postitus original_status: Algne postitus + quotes: Tsitaadid reblogs: Jagamised replied_to_html: Vastatud %{acct_link} status_changed: Muudetud postitus @@ -908,6 +921,7 @@ et: title: Postitus kontolt - @%{name} trending: Populaarne view_publicly: Nähtav avalikult + view_quoted_post: Vaata tsiteeritud postitust visibility: Nähtavus with_media: Meediaga strikes: @@ -984,8 +998,8 @@ et: reset: Lähtesta review: Vaata olek üle search: Otsi - title: Märksõnad - updated_msg: Sildi sätted edukalt uuendatud + title: Teemaviited + updated_msg: Teemaviite seadistused on uuendatud terms_of_service: back: Tagasi teenuse tingimustesse changelog: Mis on muutunud @@ -1074,14 +1088,14 @@ et: tag_servers_dimension: Populaarseimad serverid tag_servers_measure: erinevat serverit tag_uses_measure: kasutajaid kokku - description_html: Need sildid ilmuvad praegu paljudes postitutes mida su server näeb. See võib aidata su kasutajatel leida seda millest kõige rohkem parajasti räägitakse. Ühtegi silti ei näidata avalikult, enne nende heaks kiitmist. + description_html: Need teemaviited ilmuvad praegu paljudes postitutes, mida su server näeb. See võib aidata su kasutajatel leida seda millest kõige rohkem parajasti räägitakse. Ühtegi teemaviidet ei näidata enne nende heaks kiitmist avalikult. listable: Võib olla soovitatud no_tag_selected: Silte ei muudetud, kuna ühtegi polnud valitud not_listable: Ei soovitata not_trendable: Ei ilmu trendides not_usable: Ei saa kasutada peaked_on_and_decaying: Populaarseim %{date}, nüüd langemas - title: Trendikad sildid + title: Trendikad teemaviited trendable: Võib ilmuda trendides trending_rank: 'Trendides #%{rank}' usable: Kasutatav @@ -1172,7 +1186,7 @@ et: new_trending_statuses: title: Trendikad postitused new_trending_tags: - title: Trendikad sildid + title: Trendikad teemaviited subject: Uued %{instance} trendid ülevaatuseks aliases: add_new: Pane kolimiseks valmis @@ -1182,10 +1196,10 @@ et: hint_html: Kui soovid konto siia üle kolida, pead esmalt siin määrama kolitava konto aadressi. Seejärel on konto valmis võtma vastu jälgijaid vanalt kontolt. Kolitava konto aadressi määramine on iseenesest kahjutu ja tagasipööratav. Jälgijate tegelik kolimine käivitatakse vanalt kontolt. remove: Loobu suunamise vastuvõtmisest appearance: - advanced_web_interface: Kohandatud veebiliides - advanced_web_interface_hint: 'Kui soovid kasutada kogu ekraani laiust, saab kohandatud veebiliideses seadistada mitut veergu, nii et samal ajal oleks näha nii palju infot kui soovid: Kodu, teavitused, föderatsiooni ajajoon ning kuitahes palju nimekirju ja silte.' + advanced_settings: Täpsemad seadistused animations_and_accessibility: Animatsioonid ja ligipääs - confirmation_dialogs: Kinnitusdialoogid + boosting_preferences: Hooandmise eelistused + boosting_preferences_info_html: "Soovitus: Sõltumata seadistusest, Shift + Klõps %{icon} ikoonil käivitab hooandmise koheselt." discovery: Avastamine localization: body: Mastodoni tõlgivad vabatahtlikud. @@ -1409,13 +1423,13 @@ et: csv: CSV domain_blocks: Domeeni blokeeringud lists: Loetelud - mutes: Oled vaigistanud + mutes: Oled summutanud storage: Meedia hoidla featured_tags: add_new: Lisa uus errors: - limit: Oled jõudnud siltide lubatud maksimumarvuni - hint_html: "Mis on esiletõstetud sildid? Neid silte näidatakse su avalikul profiilil esiletõstetult ning need aitavad teistel leida postitusi, millel on selline silt. See on hea viis, kuidas hoida järge loovtöödel või pikaajalistel projektidel." + limit: Oled jõudnud teemaviidete lubatud maksimumarvuni + hint_html: "Mis on esiletõstetud teemaviited? Neid teemaviiteid näidatakse su avalikul profiilil esiletõstetult ning need aitavad teistel leida postitusi, millel on selline teemaviide. See on hea viis, kuidas hoida järge loovtöödel või pikaajalistel projektidel." filters: contexts: account: Profiilid @@ -1514,8 +1528,8 @@ et: one: Oled asendamas oma loetelusid faili %{filename} sisuga. Uutesse loeteludesse lisatakse kuni %{count} konto. other: Oled asendamas oma loetelusid faili %{filename} sisuga. Uutesse loeteludesse lisatakse kuni %{count} kontot. muting_html: - one: Oled asendamas oma vaigistatud kontode loetelu faili %{filename} sisuga, milles on kuni %{count} konto. - other: Oled asendamas oma vaigistatud kontode loetelu faili %{filename} sisuga, milles on kuni %{count} kontot. + one: Oled asendamas oma summutatud kontode loetelu faili %{filename} sisuga, milles on kuni %{count} konto. + other: Oled asendamas oma summutatud kontode loetelu faili %{filename} sisuga, milles on kuni %{count} kontot. preambles: blocking_html: one: Oled blokeerimas kuni %{count} konto failist %{filename}. @@ -1533,8 +1547,8 @@ et: one: Oled lisamas oma loeteludesse failist %{filename} kuni %{count} konto. Kui pole loetelusi, kuhu lisada, luuakse uued loetelud. other: Oled lisamas oma loeteludesse failist %{filename} kuni %{count} kontot. Kui pole loetelusi, kuhu lisada, luuakse uued loetelud. muting_html: - one: Oled vaigistamas kuni %{count} konto failist %{filename}. - other: Oled vaigistamas kuni %{count} kontot failist %{filename}. + one: Oled summutamas kuni %{count} konto failist %{filename}. + other: Oled summutamas kuni %{count} kontot failist %{filename}. preface: Saad importida mistahes andmeid, mis on eksporditud teisest serverist. Näiteks nimekirja inimestest, keda jälgid ja keda blokeerid. recent_imports: Viimati imporditud states: @@ -1551,11 +1565,11 @@ et: domain_blocking: Blokeeritud domeenide importimine following: Jälgitavate kontode importimine lists: Loetelude importimine - muting: Vaigistatud kontode importimine + muting: Summutatud kontode importimine type: Importimise tüüp type_groups: constructive: Jälgimised & Järjehoidjad - destructive: Blokeerimised & Vaigistamised + destructive: Blokeerimised & summutamised types: blocking: Blokeeringute nimekiri bookmarks: Järjehoidjad @@ -1587,6 +1601,13 @@ et: expires_at: Aegub uses: Kasutust title: Kutsu inimesi + link_preview: + author_html: Autorilt %{name} + potentially_sensitive_content: + action: Vaatamiseks klõpsa + confirm_visit: Kas oled kindel, et soovid selle lingi avada? + hide_button: Peida + label: Võimalik delikaatne sisu lists: errors: limit: Oled jõudnud loetelude lubatud maksimumarvuni @@ -1651,13 +1672,13 @@ et: disabled_account: Pärast seda ei ole konto täielik kasutamine võimalik. Säilib andmete eksportimise ja konto taasaktiveerimise võimalus. followers: See käsklus kolib kõik siinse konto jälgijad üle sinu uue konto jälgijateks only_redirect_html: Teine võimalus on märkida konto ümbersuunatuks. - other_data: Muid kontoandmeid, sh postitusi automaatselt üle ei kanta + other_data: Muid andmeid ei teisaldata automaatselt (sealhulgas sinu postitusi ja jälgitavate kasutajakontode loendit) redirect: Konto eemaldatakse avalikust kataloogist ja märgitakse profiilis ümbersuunatuks moderation: title: Modereerimine move_handler: carry_blocks_over_text: See kasutaja kolis kontolt %{acct}, mis oli keelatud. - carry_mutes_over_text: Kasutaja kolis ümber kontolt %{acct}, mis oli vaigistatud. + carry_mutes_over_text: Kasutaja kolis ümber kontolt %{acct}, mis oli summutatud. copy_account_note_text: 'Kasutaja kolis kontolt %{acct}, kus olid eelnevad märkmed tema kohta:' navigation: toggle_menu: Menüü lülimine @@ -1685,16 +1706,22 @@ et: body: "%{name} mainis sind:" subject: "%{name} mainis sind" title: Uus mainimine + moderation_warning: + subject: Su kasutajakonto on saanud moderaatoritelt hoiatuse poll: subject: "%{name} küsitlus lõppes" quote: body: "%{name} tsiteeris sinu postitust:" subject: "%{name} tsiteeris sinu postitust" title: Uus tsitaat + quoted_update: + subject: "%{name} on muutnud sinu poolt tsiteeritud postitust" reblog: body: "%{name} jagas edasi postitust:" subject: "%{name} jagas postitust" title: Uus jagamine + severed_relationships: + subject: Moderaatorite otsuse tõttu oled kaotanud jälgijaid ja jälgitavaid status: subject: "%{name} postitas äsja" update: @@ -1794,7 +1821,7 @@ et: content_warning: 'Sisuhoiatus:' descriptions: account: "@%{acct} avalikud postitused" - tag: 'Avalikud postitused sildiga #%{hashtag}' + tag: 'Avalikud postitused teemaviitega #%{hashtag}' scheduled_statuses: over_daily_limit: Lubatud ajastatud postituste arv %{limit} päevas on tänaseks ületatud over_total_limit: Oled jõudnud ajastatud postituste lubatud maksimumarvuni %{limit} @@ -1859,7 +1886,7 @@ et: development: Arendus edit_profile: Muuda profiili export: Eksport - featured_tags: Esile toodud sildid + featured_tags: Esile toodud teemaviited import: Impordi import_and_export: Import / eksport migrate: Konto kolimine @@ -1897,24 +1924,33 @@ et: other: "%{count} videot" boosted_from_html: "%{acct_link} jagamine" content_warning: 'Sisu hoiatus: %{warning}' + content_warnings: + hide: Peida postitus + show: Näita rohkem default_language: Kasutajaliidese keelega sama disallowed_hashtags: - one: 'sisaldab ebasobivat silti: %{tags}' - other: 'sisaldab ebasobivaid silte: %{tags}' + one: 'sisaldab ebasobivat teemaviidet: %{tags}' + other: 'sisaldab ebasobivaid teemaviiteid: %{tags}' edited_at_html: Muudetud %{date} errors: in_reply_not_found: Postitus, millele üritad vastata, ei näi enam eksisteerivat. quoted_status_not_found: Postitus, mida üritad tsiteerida, ei näi enam eksisteerivat. + quoted_user_not_mentioned: Privaatses mainimisega postituses ei saa tsiteerida mittemaninitud kasutajat. over_character_limit: tähtmärkide limiit %{max} ületatud pin_errors: direct: Ei saa kinnitada postitusi, mis on nähtavad vaid mainitud kasutajatele limit: Kinnitatud on juba maksimaalne arv postitusi ownership: Kellegi teise postitust ei saa kinnitada reblog: Jagamist ei saa kinnitada + quote_error: + not_available: Postitus pole saadaval + pending_approval: Postitus on ootel + revoked: Autor on postituse eemaldanud quote_policies: followers: Ainult jälgijad nobody: Ainult mina public: Kõik + quote_post_author: Tsiteeris kasutaja %{acct} postitust title: '%{name}: "%{quote}"' visibilities: direct: Privaatne mainimine @@ -2109,7 +2145,7 @@ et: other: "%{people} inimest viimase 2 päeva jooksul" hashtags_subtitle: Avasta, mis viimase 2 päeva jooksul on toimunud hashtags_title: Populaarsed märksõnad - hashtags_view_more: Vaata teisi trendikaid märksõnu + hashtags_view_more: Vaata teisi trendikaid teemaviiteid post_action: Postita post_step: Tervita maailma teksti, fotode, videote või küsitlustega. post_title: Tee oma esimene postitus @@ -2151,3 +2187,5 @@ et: not_supported: See veebilehitseja ei toeta turvavõtmeid otp_required: Turvavõtmete kasutamiseks tuleb eelnevalt sisse lülitada kaheastmeline autentimine. registered_on: Registreeritud %{date} + wrapstodon: + title: "%{year}. aasta Mastodoni kokkuvõte: %{name}" diff --git a/config/locales/eu.yml b/config/locales/eu.yml index fb11192599b8e4..f60c4bcce4bb2c 100644 --- a/config/locales/eu.yml +++ b/config/locales/eu.yml @@ -7,6 +7,8 @@ eu: hosted_on: Mastodon %{domain} domeinuan ostatatua title: Honi buruz accounts: + errors: + cannot_be_added_to_collections: Ezin da kontu hau bildumetara gehitu. followers: one: Jarraitzaile other: jarraitzaile @@ -190,6 +192,7 @@ eu: create_relay: Erreleboa sortu create_unavailable_domain: Sortu eskuragarri ez dagoen domeinua create_user_role: Sortu rola + create_username_block: Sortu erabiltzaile-izenaren araua demote_user: Jaitsi erabiltzailearen maila destroy_announcement: Ezabatu iragarpena destroy_canonical_email_block: Ezabatu email blokeoa @@ -203,12 +206,14 @@ eu: destroy_status: Ezabatu bidalketa destroy_unavailable_domain: Ezabatu eskuragarri ez dagoen domeinua destroy_user_role: Ezabatu rola + destroy_username_block: Ezabatu erabiltzaile-izenaren araua disable_2fa_user: Desgaitu 2FA disable_custom_emoji: Desgaitu emoji pertsonalizatua disable_relay: Erreleboa desaktibatu disable_sign_in_token_auth_user: Desgaitu email token autentifikazioa erabiltzailearentzat disable_user: Desgaitu erabiltzailea enable_custom_emoji: Gaitu emoji pertsonalizatua + enable_relay: Gaitu errelea enable_sign_in_token_auth_user: Gaitu email token autentifikazioa erabiltzailearentzat enable_user: Gaitu erabiltzailea memorialize_account: Bihurtu kontua oroigarri @@ -236,6 +241,7 @@ eu: update_report: Txostena eguneratu update_status: Eguneratu bidalketa update_user_role: Eguneratu rola + update_username_block: Eguneratu erabiltzaile-izenaren araua actions: approve_appeal_html: "%{name} erabiltzaileak %{target} erabiltzailearen moderazio erabakiaren apelazioa onartu du" approve_user_html: "%{name} erabiltzaileak %{target} erabiltzailearen izen-ematea onartu du" @@ -310,7 +316,9 @@ eu: create: Sortu iragarpena title: Iragarpen berria preview: + disclaimer: Erabiltzaileek ezin dutenez horiei uko egin, posta elektroniko bidezko jakinarazpenak iragarpen garrantzitsuetara mugatu behar dira; adibidez, datu pertsonalen segurtasun-haustura edo zerbitzariaren itxiera jakinarazpenetara. explanation_html: 'Mezu elektronikoa %{display_count} erabiltzaileei bidaliko zaie. Testu hau gehituko zaio mezu elektronikoari:' + title: Aurreikusi iragarpenaren aurrebista publish: Argitaratu published_msg: Iragarpena ongi argitaratu da! scheduled_for: "%{time}-rako programatuta" @@ -441,6 +449,7 @@ eu: attempts_over_week: one: Izen-emateko saiakera %{count} azken astean other: Izen-emateko %{count} saiakera azken astean + created_msg: Ongi gehitu da e-mail helbidea domeinuen zerrenda beltzera delete: Ezabatu dns: types: @@ -450,7 +459,9 @@ eu: create: Gehitu domeinua resolve: Ebatzi domeinua title: Posta domeinu berria blokeatu + no_email_domain_block_selected: Ez da eposta domeinu blokeorik aldatu ez delako bat ere hautatu not_permitted: Baimendu gabea + resolved_dns_records_hint_html: Domeinu-izena ondorengo MX domeinuetara ebazten da, zeinek eposta onartzeko ardura duten. MX domeinu bat blokeatzeak MX domeinu hori erabiltzen duen edozein helbide elektronikotatik izena-ematea blokeatzen du, baita ikusgai dagoen domeinu-izena beste bat bada ere. Kontuz ibili eposta hornitzaile nagusiak blokeatu gabe. resolved_through_html: "%{domain} domeinuaren bidez ebatzia" title: Email domeinua blokeatuta export_domain_allows: @@ -479,6 +490,7 @@ eu: providers: active: Aktibo base_url: Oinarrizko URL-a + callback: Dei-erantzuna delete: Ezabatu edit: Editatu hornitzailea finish_registration: Izen-ematea bukatu @@ -488,8 +500,11 @@ eu: registration_requested: Izen-ematea eskatuta registrations: confirm: Berretsi + description: Erregistro bat jaso duzu FASP batetik. Ukatu ez baduzu zuk zeuk abiarazi. Zuk abiarazi baduzu, alderatu arretaz izena eta gakoaren hatz-marka erregistroa berretsi aurretik. reject: Ukatu + title: Berretsi FASP erregistroa save: Gorde + select_capabilities: Hautatu ahalmenak sign_in: Hasi saioa status: Egoera title: Fedibertsoko zerbitzu osagarrien hornitzaileak @@ -503,6 +518,9 @@ eu: title: Jarraitzeko gomendioak unsuppress: Berrezarri jarraitzeko gomendioa instances: + audit_log: + title: Oraintsuko ikuskapen-erregistroak + view_all: Ikusi ikuskapen-erregistro osoak availability: description_html: one: Domeinura entregatzeak arrakastarik gabe huts egiten badu egun %{count} igaro ondoren, ez da entregatzeko saiakera gehiago egingo, ez bada domeinu horretatik entregarik jasotzen. @@ -563,6 +581,9 @@ eu: create: Gehitu Moderazio Oharra created_msg: Instantziako moderazio oharra ongi sortu da! description_html: Ikusi eta idatzi oharrak beste moderatzaileentzat eta zuretzat etorkizunerako + destroyed_msg: Instantziako moderazio oharra ongi ezabatu da! + placeholder: Instantzia honi buruzko informazioa, burututako ekintzak edo etorkizunean instantzia hau moderatzen lagunduko dizun beste edozein gauza. + title: Moderazio oharrak private_comment: Iruzkin pribatua public_comment: Iruzkin publikoa purge: Ezabatu betiko @@ -633,7 +654,9 @@ eu: resolve_description_html: Ez da ekintzarik hartuko salatutako kontuaren aurka, ez da neurria gordeko eta salaketa itxiko da. silence_description_html: Kontua soilik honen jarraitzaile edo espresuki bilatzen dutenentzat izango da ikusgarri, kontuaren irisgarritasuna gogorki mugatzen delarik. suspend_description_html: Kontua bera eta honen edukiak eskuraezinak izango dira, eta azkenean, ezabatuak. Kontu honekin erlazionatzea ezinezkoa izango da. Prozesua 30 egunez itzulgarria izango da. Kontu honen aurkako txosten guztiak baztertuko lirateke. + actions_description_html: Erabaki txosten hau konpontzeko ze ekintza hartu. Salatutako kontuaren aurka zigor ekintza bat hartzen baduzu, eposta jakinarazpen bat bidaliko zaie, Spam kategoria hautatzean ezik. actions_description_remote_html: Hautatu txosten honi konponbidea aurkitzeko zein neurri hartu. Hau soilik zure zerbitzaria urruneko kontu honekin nola komunikatu eta bere edukia nola maneiatzeko da. + actions_no_posts: Txosten honek ez du ezabatzeko lotutako argitalpenik add_to_report: Gehitu gehiago txostenera already_suspended_badges: local: Dagoeneko kanporatu da zerbitzari honetatik @@ -674,6 +697,7 @@ eu: report: 'Salaketa #%{id}' reported_account: Salatutako kontua reported_by: Salatzailea + reported_with_application: Aplikazioaren bidez salatua resolved: Konponduta resolved_msg: Salaketa ongi konpondu da! skip_to_actions: Salto ekintzetara @@ -736,6 +760,7 @@ eu: manage_appeals: Kudeatu apelazioak manage_appeals_description: Baimendu erabiltzaileek moderazio ekintzen aurkako apelazioak berrikustea manage_blocks: Kudeatu blokeatzeak + manage_blocks_description: Baimendu erabiltzaileek eposta hornitzaile eta IP helbideak blokeatzea manage_custom_emojis: Kudeatu emoji pertsonalizatuak manage_custom_emojis_description: Baimendu erabiltzaileek zerbitzariko emoji pertsonalizatuak kudeatzea manage_federation: Kudeatu federazioa @@ -753,6 +778,7 @@ eu: manage_taxonomies: Kudeatu taxonomiak manage_taxonomies_description: Baimendu erabiltzaileek joerak berrikustea eta traolen ezarpenak eguneratzea manage_user_access: Kudeatu erabiltzaileen sarbidea + manage_user_access_description: Baimendu erabiltzaileek beste erabiltzaileen bi faktoreko autentifikazioa desaktibatzea, eposta helbideak aldatzea eta pasahitzak berrezartzea manage_users: Kudeatu erabiltzaileak manage_users_description: Baimendu erabiltzaileek beste erabiltzaileen xehetasunak ikusi eta moderazio ekintzak burutzea manage_webhooks: Kudeatu webhook-ak @@ -771,6 +797,8 @@ eu: description_html: Gehienek erabilera baldintzak irakurri eta onartu dituztela baieztatzen badute ere, orokorrean arazoren bat dagoen arte ez dituzte irakurtzen. Zerbitzariaren arauak begirada batean ikustea errazteko buletadun zerrenda batean bildu. Saiatu arauak labur eta sinple idazten, baina elementu askotan banatu gabe. edit: Editatu araua empty: Ez da zerbitzariko araurik definitu oraindik. + move_down: Behera mugitu + move_up: Mugitu gora title: Zerbitzariaren arauak translation: Itzulpena translations: Itzulpenak @@ -805,10 +833,21 @@ eu: publish_statistics: Argitaratu estatistikak title: Aurkitzea trends: Joerak + wrapstodon: Wrapstodon domain_blocks: all: Guztiei disabled: Inori ez users: Saioa hasita duten erabiltzaile lokalei + feed_access: + modes: + authenticated: Saioa hasi duten erabiltzaileentzat soilik + disabled: Erabiltzaile-rol jakin bat behar da + public: Edonork + landing_page: + values: + about: Honi buruz + local_feed: Jario lokala + trends: Joerak registrations: moderation_recommandation: Mesedez, ziurtatu moderazio-talde egokia eta erreaktiboa duzula erregistroak guztiei ireki aurretik! preamble: Kontrolatu nork sortu dezakeen kontua zerbitzarian. @@ -861,9 +900,14 @@ eu: no_status_selected: Ez da bidalketarik aldatu ez delako bat ere hautatu open: Ireki bidalketa original_status: Jatorrizko bidalketa + quotes: Aipuak reblogs: Bultzadak + replied_to_html: "%{acct_link}(r)i erantzuten" status_changed: Bidalketa aldatuta + status_title: "%{name} erabiltzailearen bidalketa" trending: Joera + view_publicly: Publikoki ikusi + view_quoted_post: Aipatutako bidalketa ikusi visibility: Ikusgaitasuna with_media: Multimediarekin strikes: @@ -908,6 +952,7 @@ eu: sidekiq_process_check: message_html: Ez da ari Sidekiq prozesurik exekutatzen %{value} ilad(et)an. Egiaztatu Sidekiq konfigurazioa software_version_check: + action: Ikusi eguneraketa eskuragarriak message_html: Mastodon eguneratze bat eskuragarri dago. software_version_critical_check: action: Ikusi eguneraketa eskuragarriak @@ -926,8 +971,10 @@ eu: not_trendable: Ez dago modan not_usable: Ez erabilgarri pending_review: Berrikusketaren zain + review_requested: Berrikuspena eskatuta reviewed: Berrikusita title: Egoera + trendable: Joera bihur daiteke unreviewed: Berrikusi gabe usable: Erabilgarri name: Izena @@ -953,6 +1000,9 @@ eu: going_live_on_html: Argitaratua, indarrean %{date} history: Historia live: Zuzenean + notify_users: Jakinarazi erabiltzaileak + preview: + send_preview: 'Bidali aurrebista hona: %{email}' publish: Argitaratu published_on_html: "%{date}(e)an argitaratua" save_draft: Gorde zirriborroa @@ -961,11 +1011,14 @@ eu: trends: allow: Onartu approved: Onartua + confirm_allow: Ziur zaude hautatutako etiketak gaitu nahi dituzula? confirm_disallow: Ziur zaude hautatutako etiketak desgaitu nahi dituzula? disallow: Ukatu links: allow: Onartu esteka allow_provider: Onartu argitaratzailea + confirm_allow: Ziur zaude hautatutako estekak gaitu nahi dituzula? + confirm_disallow: Ziur zaude hautatutako estekak desgaitu nahi dituzula? description_html: Esteka hauek zure zerbitzariak ikusten dituen kontuek asko zabaltzen ari diren estekak dira. Zure erabiltzaileei munduan ze berri den jakiteko lagungarriak izan daitezke. Ez da estekarik bistaratzen argitaratzaileak onartu arte. Esteka bakoitza onartu edo baztertu dezakezu. disallow: Ukatu esteka disallow_provider: Ukatu argitaratzailea @@ -1021,7 +1074,23 @@ eu: used_by_over_week: one: Pertsona batek erabilia azken astean other: "%{count} pertsonak erabilia azken astean" + title: Gomendioak eta joerak trending: Joerak + username_blocks: + add_new: Gehitu berria + block_registrations: Blokeatu izen-emateak + comparison: + equals: Berdin + delete: Ezabatu + edit: + title: Editatu erabiltzaile-izena araua + matches_exactly_html: 'Berdin: %{string}' + new: + create: Sortu araua + title: Sortu erabiltzaile-izen araua berria + no_username_block_selected: Ez da erabiltzaile-izen araurik aldatu, ez delako bat ere hautatu + not_permitted: Baimendu gabea + title: Erabiltzaile-izen arauak warning_presets: add_new: Gehitu berria delete: Ezabatu @@ -1093,10 +1162,8 @@ eu: hint_html: Beste kontu batetik hona migratu nahi baduzu, hemen ezizen bat sortu dezakezu, hau beharrezkoa da kontu zaharreko jarraitzaileak hona ekartzeko. Ekintza hau berez kaltegabea eta desegingarria da. Kontuaren migrazioa kontu zaharretik abiatzen da. remove: Deslotu ezizena appearance: - advanced_web_interface: Web interfaze aurreratua - advanced_web_interface_hint: 'Pantaila bere zabalera osoan erabili nahi baduzu, web interfaze aurreratuak hainbat zutabe desberdin konfiguratzea ahalbidetzen dizu, aldi berean nahi beste informazio ikusteko: Hasiera, jakinarazpenak, federatutako denbora-lerroa, edo nahi beste zerrenda eta traola.' + advanced_settings: Ezarpen aurreratuak animations_and_accessibility: Animazioak eta irisgarritasuna - confirmation_dialogs: Berrespen dialogoak discovery: Aurkitzea localization: body: Mastodon boluntarioek itzultzen dute. @@ -1104,6 +1171,7 @@ eu: guide_link_text: Edonork lagundu dezake. sensitive_content: Eduki hunkigarria application_mailer: + notification_preferences: Posta elektronikoaren lehentasunak aldatu salutation: "%{name}," settings: 'Posta elektronikoaren lehentasunak aldatu: %{link}' unsubscribe: Kendu harpidetza @@ -1125,6 +1193,7 @@ eu: hint_html: Azken kontu bat! Gizakia zarela berretsi behar dugu (zabor-kontuak kanpoan mantentzeko baino ez da!) Ebatzi azpiko CAPTCHA eta sakatu "Jarraitu". title: Segurtasun txekeoa confirmations: + awaiting_review: Zure helbide elektronikoa baieztatu da! %{domain} lan taldea zure erregistroa berrikusten ari da. Mezu elektroniko bat jasoko duzu zure kontua onartzen badute! awaiting_review_title: Zure izen-ematea berrikusten ari da clicking_this_link: lotura hau klikatzen login_link: hasi saioa @@ -1132,6 +1201,7 @@ eu: redirect_to_app_html: "%{app_name} aplikaziora berbideratua izan beharko zenuke. Hori gertatu ez bada, saiatu %{clicking_this_link} edo eskuz itzuli." registration_complete: Osatuta dago orain zure izen-ematea %{domain} -en! welcome_title: Ongi etorri, %{name}! + wrong_email_hint: Helbide-elektroniko hori zuzena ez bada, kontuaren ezarpenetan alda dezakezu. delete_account: Ezabatu kontua delete_account_html: Kontua ezabatu nahi baduzu, jarraitu hemen. Berrestea eskatuko zaizu. description: @@ -1182,9 +1252,11 @@ eu: title: "%{domain}-(e)an saioa hasi" sign_up: manual_review: "%{domain}-(e)n eginiko izen-emateak gure moderatzaileek eskuz aztertzen dituzte. Zure izen-ematea prozesatzen lagun gaitzazun, idatz ezazu zertxobait zuri buruz eta azaldu zergatik nahi duzun %{domain}-(e)n kontu bat." + preamble: Mastodon zerbitzari honetako kontu batekin, aukera izango duzu fedibertsoko edozein pertsona jarraitzeko, ez dio axola kontua non ostatatua dagoen. title: "%{domain} zerbitzariko kontua prestatuko dizugu." status: account_status: Kontuaren egoera + confirming: E-mail baieztapena osatu bitartean zain. functional: Zure kontua guztiz erabilgarri dago. pending: Gure taldea zure eskaera berrikusten ari da. Honek denbora pixka bat beharko du. Mezu elektroniko bat jasoko duzu zure eskaera onartzen bada. redirecting_to: Zure kontua ez dago aktibo orain %{acct} kontura birbideratzen duelako. @@ -1192,6 +1264,9 @@ eu: view_strikes: Ikusi zure kontuaren aurkako neurriak too_fast: Formularioa azkarregi bidali duzu, saiatu berriro. use_security_key: Erabili segurtasun gakoa + author_attribution: + example_title: Testu-lagina + more_from_html: "%{name} erabiltzaileaz gehiago jakin" challenge: confirm: Jarraitu hint_html: "Oharra: Ez dizugu pasahitza berriro eskatuko ordu batez." @@ -1228,6 +1303,7 @@ eu: before: 'Jarraitu aurretik, irakurri adi ohar hauek:' caches: Beste zerbitzariek cachean duten edukia mantentzea gerta daiteke data_removal: Zure bidalketak eta beste datuak behin betiko ezabatuko dira + email_reconfirmation_html: Ez baduzu baieztamen e-maila jasotzen, berriro eskatu dezakezu irreversible: Ezin izango duzu kontua berreskuratu edo berraktibatu more_details_html: Xehetasun gehiagorako, ikusi pribatutasun politika. username_available: Zure erabiltzaile-izena berriro eskuragarri egongo da @@ -1266,6 +1342,10 @@ eu: basic_information: Oinarrizko informazioa hint_html: "Pertsonalizatu jendeak zer ikusi dezakeen zure profil publikoan eta zure bidalketen baitan. Segur aski, jende gehiagok jarraituko dizu eta interakzio gehiago izango dituzu profila osatuta baduzu, profil irudia eta guzti." other: Bestelakoak + emoji_styles: + auto: Automatikoa + native: Bertakoa + twemoji: Twemoji errors: '400': Bidali duzun eskaera baliogabea da edo gaizki osatua dago. '403': Ez duzu orri hau ikusteko baimenik. @@ -1437,6 +1517,13 @@ eu: expires_at: Iraungitzea uses: Erabilerak title: Gonbidatu jendea + link_preview: + author_html: 'Egilea: %{name}' + potentially_sensitive_content: + action: Egin klik erakusteko + confirm_visit: Ziur zaude esteka hau ireki nahi duzula? + hide_button: Ezkutatu + label: Eduki sentikorra izan daiteke lists: errors: limit: Gehienezko zerrenda kopurura iritsi zara @@ -1444,6 +1531,7 @@ eu: authentication_methods: otp: bi faktoreko autentifikazio aplikazioa password: pasahitza + sign_in_token: e-posta segurtasun kodea webauthn: segurtasun gakoak description_html: Ezagutzen ez duzun aktibitatea ikusten baduzu, pasahitza aldatu eta bi faktoreko autentifikazioa gaitzea gomendatzen dizugu. empty: Ez dago autentifikazio historiarik eskuragarri @@ -1454,9 +1542,18 @@ eu: unsubscribe: action: Bai, kendu harpidetza complete: Harpidetza kenduta + confirmation_html: |- + Ziur Mastodonen %{domain} zerbitzariko %{type} %{email} helbide elektronikoan jasotzeari utzi nahi diozula? + Beti harpidetu zaitezke berriro eposta jakinarazpenen hobespenetan. emails: notification_emails: + favourite: zure argitalpena gogoko egin dutenaren jakinarazpen e-mailak follow: jarraitu jakinarazpen-mezu elektronikoak + follow_request: jarraipen-eskaeren jakinarazpen e-mailak + mention: aipamenen jakinarazpen e-mailak + reblog: bultzaden jakinarazpen e-mailak + resubscribe_html: Nahi gabe utzi badiozu jakinarazpenak jasotzeari, berriro harpidetu zaitezke e-mail jakinarazpenen hobespenetan. + success_html: Ez duzu Mastodonen %{domain} zerbitzariko %{type} jasoko %{email} helbide elektronikoan. title: Kendu harpidetza media_attachments: validations: @@ -1492,7 +1589,7 @@ eu: disabled_account: Zure uneko kontua ezin izango da gero erabili. Hala ere, datua exporatu ahal izango dituzu, eta berriro aktibatu. followers: Ekintza honek jarraitzaile guztiak eramango ditu uneko kontutik kontu berrira only_redirect_html: Bestela, zure profilean birbideratze bat jar dezakezu. - other_data: Ez da beste daturik migratuko automatikoki + other_data: Ez da beste daturik automatikoki mugituko (zure argitalpenak eta jarraitzen dituzun kontuen zerrenda barne) redirect: Zure uneko kontuaren profila eguneratuko da birbideratze ohar batekin eta bilaketetatik kenduko da moderation: title: Moderazioa @@ -1526,17 +1623,29 @@ eu: body: "%{name}(e)k aipatu zaitu:" subject: "%{name}(e)k aipatu zaitu" title: Aipamen berria + moderation_warning: + subject: Moderazio-abisu bat jaso duzu poll: subject: "%{name} erabiltzailearen inkesta bat amaitu da" + quote: + body: "%{name}(e)k zure bidalketari aipamena egin dio:" + subject: "%{name} erabiltzaileak zure bidalketa aipatu du" + title: Aipamen berria + quoted_update: + subject: "%{name} erabiltzaileak aipatu duzun post bat editatu du" reblog: body: "%{name}(e)k bultzada eman dio zure bidalketari:" subject: "%{name}(e)k bultzada eman dio zure bidalketari" title: Bultzada berria + severed_relationships: + subject: Harremanak galdu dituzu moderazio-erabaki baten ondorioz status: subject: "%{name} erabiltzaileak bidalketa egin berri du" update: subject: "%{name} erabiltzaileak bidalketa bat editatu du" notifications: + administration_emails: Administratzailearen posta elektroniko bidezko jakinarazpenak + email_events: E-mail jakinarazpenentzako gertaerak email_events_hint: 'Hautatu jaso nahi dituzun gertaeren jakinarazpenak:' number: human: @@ -1574,6 +1683,9 @@ eu: self_vote: Ezin duzu zuk sortutako inkestetan bozka eman too_few_options: elementu bat baino gehiago izan behar du too_many_options: ezin ditu %{max} elementu baino gehiago izan + vote: Bozkatu + posting_defaults: + explanation: Konfigurazio hau lehenetsi gisa erabiliko da argitalpen berriak sortzen dituzunean, baina aldatu egin dezakezu argitalpen bat idaztean. preferences: other: Denetarik posting_defaults: Bidalketarako lehenetsitakoak @@ -1630,6 +1742,7 @@ eu: scheduled_statuses: over_daily_limit: 'Egun horretarako programatutako bidalketa kopuruaren muga gainditu duzu: %{limit}' over_total_limit: 'Programatutako bidalketa kopuruaren muga gainditu duzu: %{limit}' + too_soon: datak etorkizunekoa izan behar du self_destruct: lead_html: Zoritxarrez, %{domain} betirako itxiko da. Kontu bat baduzu bertan, ezin izango duzu erabiltzen jarraitu, baina, oraindik zure datuen babeskopia bat eska dezakezu. title: Zerbitzari hau ixtear dago @@ -1728,6 +1841,9 @@ eu: other: "%{count} bideo" boosted_from_html: "%{acct_link}(e)tik bultzatua" content_warning: 'Edukiaren abisua: %{warning}' + content_warnings: + hide: Tuta ezkutatu + show: Erakutsi gehiago default_language: Interfazearen hizkuntzaren berdina disallowed_hashtags: one: 'debekatutako traola bat zuen: %{tags}' @@ -1735,15 +1851,31 @@ eu: edited_at_html: Editatua %{date} errors: in_reply_not_found: Erantzuten saiatu zaren bidalketa antza ez da existitzen. + quoted_status_not_found: Aipua egiten saiatu zaren bidalketa antza ez da existitzen. + quoted_user_not_mentioned: Ezin da aipatu ez den erabiltzaile baten aipamenik egin Aipamen pribatu batean. over_character_limit: "%{max}eko karaktere muga gaindituta" pin_errors: direct: Aipatutako erabiltzaileentzat soilik ikusgai dauden bidalketak ezin dira finkatu limit: Gehienez finkatu daitekeen bidalketa kopurua finkatu duzu jada ownership: Ezin duzu beste norbaiten bidalketa bat finkatu reblog: Bultzada bat ezin da finkatu + quote_error: + not_available: Bidalketa ez dago eskuragarri + pending_approval: Bidalketa zain dago + revoked: Egileak bidalketa kendu du + quote_policies: + followers: Jarraitzaileentzat soilik + nobody: Nik bakarrik + public: Edonork + quote_post_author: "%{acct}-(r)en bidalketan aipatua" title: '%{name}: "%{quote}"' visibilities: + direct: Aipu pribatua + private: Jarraitzaileentzat soilik public: Publikoa + public_long: Mastodonen dagoen edo ez dagoen edonor + unlisted: Ikusgarritasun mugatua + unlisted_long: Ezkutatuta Mastodon bilaketen emaitzetatik, joeretatik, eta denbora-lerro publikoetatik statuses_cleanup: enabled: Ezabatu bidalketa zaharrak automatikoki enabled_hint: Zure bidalketa zaharrak automatikoki ezabatzen ditu zehazturiko denbora mugara iristean, beheko baldintza bat betetzen ez bada @@ -1790,6 +1922,8 @@ eu: title: Erabilera baldintzak terms_of_service_interstitial: future_preamble_html: Gure zerbitzu-baldintzetan aldaketa batzuk egiten ari gara, eta %{date}-tik aurrera jarriko dira indarrean. Eguneratutako baldintzak berrikustea gomendatzen dizugu. + past_preamble_html: Zerbitzu-baldintzak aldatu ditugu zure azken bisitatik. Baldintza eguneratuak berrikustera animatzen zaitugu. + review_link: Berrikusi zerbitzu-baldintzak themes: contrast: Mastodon (Kontraste altua) default: Mastodon (Iluna) @@ -1822,6 +1956,7 @@ eu: webauthn: Segurtasun gakoak user_mailer: announcement_published: + subject: Zerbitzuaren iragarpena title: "%{domain} zerbitzuaren iragarpena" appeal_approved: action: Kontuaren ezarpenak @@ -1852,6 +1987,12 @@ eu: further_actions_html: Ez bazara zu izan, lehenbailehen %{action} gomendatzen dizugu eta bi faktoreko autentifikazioa gaitzea zure kontua seguru mantentzeko. subject: Zure kontura sarbidea egon da IP helbide berri batetik title: Saio hasiera berria + terms_of_service_changed: + changelog: 'Laburbilduz, hau da eguneratze honek zuretzat esan nahi duena:' + sign_off: "%{domain} taldea" + subject: Zerbitzu-baldintzen eguneratzeak + subtitle: "%{domain}(e)ko zerbitzu-baldintzak aldatu egingo dira" + title: Eguneraketa garrantzitsua warning: appeal: Bidali apelazioa appeal_description: Hau errore bat dela uste baduzu, apelazio bat bidali diezaiekezu %{instance} instantziako arduradunei. @@ -1929,6 +2070,7 @@ eu: invalid_otp_token: Bi faktoreetako kode baliogabea otp_lost_help_html: 'Bietara sarbidea galdu baduzu, jarri kontaktuan hemen: %{email}' rate_limited: Autentifikazio saiakera gehiegi, saiatu berriro geroago. + seamless_external_login: Kanpo zerbitzu baten bidez hasi duzu saioa, beraz pasahitza eta e-mail ezarpenak ez daude eskuragarri. signed_in_as: 'Saioa honela hasita:' verification: extra_instructions_html: Aholkua: webguneko esteka ikusezina izan daiteke. Muina rel="me" da, erabiltzaileak sortutako edukia duten webguneetan beste inor zure burutzat aurkeztea eragozten duena. a beharrean esteka motako etiketa bat ere erabil dezakezu orriaren goiburuan, baina HTMLak erabilgarri egon behar du JavaScript exekutatu gabe. @@ -1955,3 +2097,6 @@ eu: not_supported: Nabigatzaile honek ez ditu segurtasun gakoak onartzen otp_required: Segurtasun gakoak erabili aurretik bi faktoreko autentifikazioa gaitu behar duzu. registered_on: "%{date}(e)an erregistratua" + wrapstodon: + description: Begira nola erabili duen Mastodon %{name}(e)k urte honetan! + title: "%{year}(e)ko Wrapstodona, %{name}(e)rentzat" diff --git a/config/locales/fa.yml b/config/locales/fa.yml index 9c24be803516ad..75c5869854b4f9 100644 --- a/config/locales/fa.yml +++ b/config/locales/fa.yml @@ -1,35 +1,35 @@ --- fa: about: - about_mastodon_html: 'شبکه‌ی اجتماعی آینده: بدون تبلیغات، بدون شنود از طرف شرکت‌ها، طراحی اخلاق‌مدار، و معماری غیرمتمرکز! با ماستودون صاحب داده‌های خودتان باشید!' + about_mastodon_html: 'شبکهٔ اجتماعی آینده: بدون تبلیغ، بدون شنود شرکت‌ها، طراحی اخلاق مدار و معماری نامتمرکز! با ماستودون صاحب داده‌های خودتان باشید!' contact_missing: تنظیم نشده contact_unavailable: موجود نیست - hosted_on: ماستودون، میزبانی‌شده روی %{domain} + hosted_on: ماستودون میزبانی شده روی %{domain} title: درباره accounts: followers: - one: پیگیر - other: پیگیر + one: پی‌گیرنده + other: پی‌گیرنده following: پی می‌گیرد - instance_actor_flash: این حساب یک عامل مجازی است که به نمایندگی از خود کارساز استفاده می‌شود و نه هیچ یکی از کاربران. این حساب به منظور اتصال به فدراسیون استفاده می‌شود و نباید معلق شود. - last_active: آخرین فعالیت + instance_actor_flash: این حساب عاملی مجازیست که به نمایندگی از خود کارساز استفاده می‌شود و نه کاربری جداگانه. این حساب به منظور اتصال به فدراسیون استفاده می‌شود و نباید معلق شود. + last_active: آخرین فعّالیت link_verified_on: مالکیت این پیوند در %{date} بررسی شد - nothing_here: این‌جا چیزی نیست! + nothing_here: چیزی این‌جا نیست! pin_errors: following: باید کاربری که می‌خواهید پیشنهاد دهید را دنبال کرده باشید posts: one: فرسته other: فرسته‌ها posts_tab_heading: فرسته‌ها - self_follow_error: دنبال کردن حساب کاربری شما مجاز نیست + self_follow_error: پی‌گیری حساب خودتان مجاز نیست admin: account_actions: action: انجامِ کنش - already_silenced: این جساب از پیش محدود شده. - already_suspended: این جساب از پیش معلّق شده. + already_silenced: این حساب از پیش محدود شده. + already_suspended: این حساب از پیش معلّق شده. title: انجام کنش مدیریتی روی %{acct} account_moderation_notes: - create: افزودن یادداشت + create: گذاشتن یادداشت created_msg: یادداشت مدیر با موفقیت ساخته شد! destroyed_msg: یادداشت نظارتی با موفقیت نابود شد! accounts: @@ -796,6 +796,8 @@ fa: view_dashboard_description: اجازه به کاربران برای دسترسی به داشتبورد و سنجه‌های مختلف view_devops: دواپس view_devops_description: به کاربران امکان دسترسی به داشبورد Sidekiq و pgHero را می دهد + view_feeds: دیدن خوراک‌های موضوع و زنده + view_feeds_description: می‌گذارد کاربران فارغ از تنظیمات کارساز به خوراک‌های موضوع و زنده دسترسی داشته باشند title: نقش‌ها rules: add_new: افزودن قانون @@ -837,7 +839,7 @@ fa: title: درخواست خروج از اندیس‌گذاری پیش‌گزیدهٔ موتور جست‌وجو discovery: follow_recommendations: پیروی از پیشنهادها - preamble: ارائه محتوای جالب در جذب کاربران جدیدی که ممکن است کسی ماستودون را نشناسند، مفید است. نحوه عملکرد ویژگی‌های کشف مختلف روی سرور خود را کنترل کنید. + preamble: ارائه محتوای جذّاب برای آغاز به کار کاربرانی که شاید کسی را روی ماستودون نشناسند ضروری است. واپایش چگونگی کار کردن ویژگی‌های کشف روی کارسازتان. privacy: محرمانگی profile_directory: شاخهٔ نمایه public_timelines: خط زمانی‌های عمومی @@ -848,6 +850,16 @@ fa: all: برای همه disabled: برای هیچ‌کدام users: برای کاربران محلی واردشده + feed_access: + modes: + authenticated: تنها کاربران تأیید شده + disabled: نیازمند نقش کاربری خاص + public: هرکسی + landing_page: + values: + about: درباره + local_feed: خوراک محلی + trends: داغ‌ها registrations: moderation_recommandation: لطفاً قبل از اینکه ثبت نام را برای همه باز کنید، مطمئن شوید که یک تیم نظارتی مناسب و واکنشی دارید! preamble: کنترل کنید چه کسی می تواند در سرور شما یک حساب ایجاد کند. @@ -901,6 +913,7 @@ fa: no_status_selected: هیچ فرسته‌ای تغییری نکرد زیرا هیچ‌کدام از آن‌ها انتخاب نشده بودند open: گشودن فرسته original_status: فرستهٔ اصلی + quotes: نقل‌ها reblogs: تقویت‌ها replied_to_html: به %{acct_link} پاسخ داد status_changed: فرسته تغییر کرد @@ -908,6 +921,7 @@ fa: title: پست‌های حساب - @%{name} trending: پرطرفدار view_publicly: مشاهده عمومی + view_quoted_post: دیدن فرستهٔ نقل شده visibility: نمایانی with_media: دارای عکس یا ویدیو strikes: @@ -1182,10 +1196,10 @@ fa: hint_html: اگر می‌خواهید از حساب دیگری به این حساب منتقل شوید، این‌جا می‌توانید یک نام مستعار بسازید که برای انتقال از حساب قدیمی به این حساب لازم است. این کار به تنهایی بی‌ضرر و قابل بازگشت است. فرایند انتقال حساب از حساب قدیمی آغاز خواهد شد. remove: حذف ارتباط نام مستعار appearance: - advanced_web_interface: رابط کاربری پیشرفته - advanced_web_interface_hint: اگر می‌خواهید از تمامی پهنای صفحه‌تان استفاده کنید، رابط پیش‌رفتهٔ وب می‌گذارد هر چند ستون را که می‌خواهید، برای دیدن اطّلاعات بیش‌تر در یک زمان، پیکربندی کنید:‌خانه، آگاهی‌ها، خط زمانی عمومی،‌ هرتعدادی از سیاهه‌ها و برچسب‌ها. + advanced_settings: تنظیمات پیش‌رفته animations_and_accessibility: پویانمایی‌های و دسترسی‌پذیری - confirmation_dialogs: پیغام‌های تأیید + boosting_preferences: ترجیحات تقویت + boosting_preferences_info_html: "نکته: فارغ از تنظیمات، تبدیل + کلیک روی %{icon} نقشک تقویت بلافاصله تقویت خواهد کرد." discovery: کاوش localization: body: ماستودون توسط داوطلبان ترجمه شده است. @@ -1587,6 +1601,13 @@ fa: expires_at: تاریخ انقضا uses: استفاده‌ها title: دعوت دیگران + link_preview: + author_html: به دست %{name} + potentially_sensitive_content: + action: زدن برای نمایش + confirm_visit: مطمئنید که می‌خواهید این پیوند را بگشایید؟ + hide_button: نهفتن + label: محتوای بالقوه حسّاس lists: errors: limit: به بیشینهٔ تعداد سیاهه‌ها رسیدید @@ -1651,7 +1672,7 @@ fa: disabled_account: حساب فعلی شما پس از این کار دیگر قابل استفاده نخواهد بود. شما فقط خواهید توانست داده‌های خود را بیرون ببرید یا حساب را دوباره فعال کنید. followers: این کار همهٔ پیگیران شما را از حساب فعلی به حساب تازه منتقل خواهد کرد only_redirect_html: شما همچنین می‌توانید حساب خود را به یک حساب دیگر اشاره دهید. - other_data: هیچ دادهٔ دیگری خودبه‌خود منتقل نخواهد شد + other_data: هیچ دادهٔ دیگری به صورت خودکار جابه‌جا نخواهد شد (از حمله فرسته‌هایتان و سیاههٔ حساب‌هایی که پی می‌گیرید) redirect: نمایهٔ حساب کنونیتان به حساب تازه اشاره خواهد کرد و از جست‌وجوها حذف خواهد شد moderation: title: مدیریت کاربران @@ -1685,16 +1706,22 @@ fa: body: "%{name} در این‌جا به شما اشاره کرد:" subject: "%{name} به شما اشاره کرد" title: اشارهٔ جدید + moderation_warning: + subject: هشداری مدیریتی گرفته‌اید poll: subject: نظرسنجی‌ای از %{name} پایان یافت quote: body: 'فرسته‌تان توسط %{name} نقل شد:' subject: "%{name} فرسته‌تان را نقل کرد" title: نقل‌قول جدید + quoted_update: + subject: "‏%{name} فرسته‌ای که نقل کردید را ویراست" reblog: body: "%{name} فرستهٔ شما را تقویت کرد:" subject: "%{name} فرستهٔ شما را تقویت کرد" title: تقویت تازه + severed_relationships: + subject: بنا به تصمیمی مدیریتی ارتباطاتی را از دست داده‌اید status: subject: "%{name} چیزی فرستاد" update: @@ -1739,6 +1766,9 @@ fa: self_vote: شما نمی توانید در نظرسنجی خودتان رای دهید too_few_options: حتماً باید بیش از یک گزینه داشته باشد too_many_options: نمی‌تواند بیشتر از %{max} گزینه داشته باشد + vote: رأی + posting_defaults: + explanation: این تنظیمات هنگام ایجاد فرسته به عنوان پیش‌گزیده استفاده خواهند شد؛ ولی می‌توانید در نویسنده برای هر فرسته تغییرش دهید. preferences: other: سایر تنظیمات posting_defaults: تنظیمات پیش‌فرض انتشار @@ -1894,6 +1924,9 @@ fa: other: "%{count} ویدیو" boosted_from_html: تقویت شده از طرف %{acct_link} content_warning: 'هشدا محتوا: %{warning}' + content_warnings: + hide: نهفتن فرسته + show: نمایش بیش‌تر default_language: همانند زبان واسط disallowed_hashtags: one: 'دارای برچسبی غیرمجاز: %{tags}' @@ -1902,16 +1935,30 @@ fa: errors: in_reply_not_found: به نظر نمی‌رسد وضعیتی که می‌خواهید به آن پاسخ دهید، وجود داشته باشد. quoted_status_not_found: به نظر نمی‌رسد فرسته‌ای که می‌خواهید نقلش کنید وجود داشته باشد. + quoted_user_not_mentioned: نمی‌توان کاربری اشاره نشده را در فرستهٔ اشارهٔ خصوصی نقل کرد. over_character_limit: از حد مجاز %{max} حرف فراتر رفتید pin_errors: direct: فرسته‌هایی که فقط برای کاربران اشاره شده نمایانند نمی‌توانند سنجاق شوند limit: از این بیشتر نمی‌شود نوشته‌های ثابت داشت ownership: نوشته‌های دیگران را نمی‌توان ثابت کرد reblog: تقویت نمی‌تواند سنجاق شود + quote_error: + not_available: فرسته ناموجود + pending_approval: فرسته منتظر + revoked: فرسته به دست نگارنده برداشته شد + quote_policies: + followers: تنها پی‌گیران + nobody: فقط من + public: هرکسی + quote_post_author: فرسته‌ای از %{acct} را نقل کرد title: "%{name}: «%{quote}»" visibilities: direct: اشاره خصوصی + private: تنها پی‌گیران public: عمومی + public_long: هرکسی داخل و خارج از ماستودون + unlisted: عمومی ساکت + unlisted_long: نهفته از نتیجه‌های جست‌وجوی ماستودون و خط‌های زمانی داغ و عمومی statuses_cleanup: enabled: حذف خودکار فرسته‌های قدیمی enabled_hint: فرسته‌هایتان را هنگام رسیدن به کرانهٔ سن خاصی به صورت خودکار حذف می‌کند، مگر این که با یکی از استثناهای زیر مطابق باشند @@ -2048,7 +2095,7 @@ fa: silence: همچنان می‌توانید از حساب خود استفاده کنید، اما فقط افرادی که از قبل شما را دنبال می‌کنند، پست‌های شما را در این سرور می‌بینند و ممکن است از ویژگی‌های مختلف کشف مستثنی شوید. با این حال، دیگران ممکن است همچنان به صورت دستی شما را دنبال کنند. suspend: دیگر نمی توانید از حساب خود استفاده کنید و نمایه و سایر داده های شما دیگر در دسترس نیستند. هنوز هم می‌توانید برای درخواست پشتیبان‌گیری از داده‌های خود وارد شوید تا زمانی که داده‌ها در حدود 30 روز به طور کامل حذف شوند، اما ما برخی از داده‌های اولیه را حفظ می‌کنیم تا از تعلیق فرار نکنید. reason: 'دلیل:' - statuses: 'پست های ذکر شده:' + statuses: 'فرسته‌های ارجاع داده:' subject: delete_statuses: فرسته‌هایتان روی %{acct} برداشته شده‌اند disable: حساب %{acct} شما متوقف شده است @@ -2138,3 +2185,5 @@ fa: not_supported: این مرورگر از کلیدهای امنیتی پشتیبانی نمی‌کند otp_required: برای استفاده از کلیدهای امنیتی، لطفاً ابتدا تأیید هویت دو عاملی را به کار بیندازید. registered_on: ثبت‌شده در %{date} + wrapstodon: + title: خلاصهٔ ماستودون %{year} برای %{name} diff --git a/config/locales/fi.yml b/config/locales/fi.yml index ec474090a62f0f..eee902e812ff5c 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -7,6 +7,8 @@ fi: hosted_on: Mastodon palvelimella %{domain} title: Tietoja accounts: + errors: + cannot_be_added_to_collections: Tätä tiliä ei voi lisätä kokoelmiin. followers: one: seuraaja other: seuraajaa @@ -48,7 +50,7 @@ fi: title: Vaihda käyttäjän %{username} sähköposti-osoite change_role: changed_msg: Roolin vaihto onnistui! - edit_roles: Hallinnoi käyttäjien rooleja + edit_roles: Hallitse käyttäjärooleja label: Vaihda rooli no_role: Ei roolia title: Vaihda käyttäjän %{username} rooli @@ -76,7 +78,7 @@ fi: followers: Seuraajat follows: Seurattavat header: Otsakekuva - inbox_url: Postilaatikon osoite + inbox_url: Postilaatikon URL-⁠osoite invite_request_text: Syitä liittymiseen invited_by: Kutsuja ip: IP-osoite @@ -101,7 +103,7 @@ fi: title: Moderointi moderation_notes: Moderointimuistiinpanot most_recent_activity: Viimeisin toiminta - most_recent_ip: Viimeisin IP-osoite + most_recent_ip: Viimeisin IP-⁠osoite no_account_selected: Tilejä ei muutettu, koska yhtään ei ollut valittuna no_limits_imposed: Ei asetettuja rajoituksia no_role_assigned: Roolia ei asetettu @@ -124,7 +126,7 @@ fi: remote_suspension_reversible_hint_html: Tili on jäädytetty omalla palvelimellaan, ja kaikki tiedot poistetaan %{date}. Sitä ennen etäpalvelin voi palauttaa tilin ongelmitta. Jos haluat poistaa kaikki tilin tiedot heti, onnistuu se alta. remove_avatar: Poista profiilikuva remove_header: Poista otsakekuva - removed_avatar_msg: Käyttäjän %{username} avatar-kuva poistettiin onnistuneesti + removed_avatar_msg: Käyttäjän %{username} profiilikuva poistettiin onnistuneesti removed_header_msg: Käyttäjän %{username} otsakekuva poistettiin onnistuneesti resend_confirmation: already_confirmed: Tämä käyttäjä on jo vahvistettu @@ -136,14 +138,14 @@ fi: role: Rooli search: Hae search_same_email_domain: Muut käyttäjät, joilla on sama sähköpostiverkkotunnus - search_same_ip: Muut käyttäjät, joilla on sama IP-osoite + search_same_ip: Muut käyttäjät, joilla on sama IP-⁠osoite security: Turvallisuus security_measures: only_password: Vain salasana password_and_2fa: Salasana ja kaksivaiheinen todennus sensitive: Pakota arkaluonteiseksi sensitized: Merkitty arkaluonteiseksi - shared_inbox_url: Jaetun postilaatikon osoite + shared_inbox_url: Jaetun postilaatikon URL-⁠osoite show: created_reports: Tämän tilin luomat raportit targeted_reports: Tästä tilistä tehdyt raportit @@ -186,7 +188,7 @@ fi: create_domain_allow: Luo verkkotunnuksen salliminen create_domain_block: Luo verkkotunnuksen esto create_email_domain_block: Luo sähköpostiverkkotunnuksen esto - create_ip_block: Luo IP-sääntö + create_ip_block: Luo IP-⁠sääntö create_relay: Luo välittäjä create_unavailable_domain: Luo ei-saatavilla oleva verkkotunnus create_user_role: Luo rooli @@ -199,7 +201,7 @@ fi: destroy_domain_block: Poista verkkotunnuksen esto destroy_email_domain_block: Poista sähköpostiverkkotunnuksen esto destroy_instance: Tyhjennä verkkotunnus - destroy_ip_block: Poista IP-sääntö + destroy_ip_block: Poista IP-⁠sääntö destroy_relay: Poista välittäjä destroy_status: Poista julkaisu destroy_unavailable_domain: Poista ei-saatavilla oleva verkkotunnus @@ -224,18 +226,18 @@ fi: resend_user: Lähetä vahvistusviesti uudelleen reset_password_user: Palauta salasana resolve_report: Ratkaise raportti - sensitive_account: Pakota arkaluonteiseksi tiliksi + sensitive_account: Pakota tili arkaluonteiseksi silence_account: Rajoita tiliä suspend_account: Jäädytä tili unassigned_report: Poista raportti käsittelystä unblock_email_account: Kumoa sähköpostiosoitteen esto - unsensitive_account: Kumoa pakotus arkaluonteiseksi tiliksi + unsensitive_account: Kumoa tilin pakotus arkaluonteiseksi unsilence_account: Kumoa tilin rajoitus unsuspend_account: Kumoa tilin jäädytys update_announcement: Päivitä tiedote update_custom_emoji: Päivitä mukautettu emoji update_domain_block: Päivitä verkkotunnuksen esto - update_ip_block: Päivitä IP-sääntö + update_ip_block: Päivitä IP-⁠sääntö update_report: Päivitä raportti update_status: Päivitä julkaisu update_user_role: Päivitä rooli @@ -254,7 +256,7 @@ fi: create_domain_allow_html: "%{name} salli federoinnin verkkotunnuksen %{target} kanssa" create_domain_block_html: "%{name} esti verkkotunnuksen %{target}" create_email_domain_block_html: "%{name} esti sähköpostiverkkotunnuksen %{target}" - create_ip_block_html: "%{name} loi säännön IP-osoitteelle %{target}" + create_ip_block_html: "%{name} loi säännön IP-⁠osoitteelle %{target}" create_relay_html: "%{name} loi välittäjän %{target}" create_unavailable_domain_html: "%{name} pysäytti toimituksen verkkotunnukseen %{target}" create_user_role_html: "%{name} loi roolin %{target}" @@ -267,7 +269,7 @@ fi: destroy_domain_block_html: "%{name} kumosi verkkotunnuksen %{target} eston" destroy_email_domain_block_html: "%{name} kumosi sähköpostiverkkotunnuksen %{target} eston" destroy_instance_html: "%{name} tyhjensi verkkotunnuksen %{target}" - destroy_ip_block_html: "%{name} poisti säännön IP-osoitteelta %{target}" + destroy_ip_block_html: "%{name} poisti säännön IP-⁠osoitteelta %{target}" destroy_relay_html: "%{name} poisti välittäjän %{target}" destroy_status_html: "%{name} poisti käyttäjän %{target} julkaisun" destroy_unavailable_domain_html: "%{name} jatkoi toimitusta verkkotunnukseen %{target}" @@ -303,7 +305,7 @@ fi: update_announcement_html: "%{name} päivitti tiedotteen %{target}" update_custom_emoji_html: "%{name} päivitti emojin %{target}" update_domain_block_html: "%{name} päivitti verkkotunnuksen %{target} eston" - update_ip_block_html: "%{name} muutti IP-osoitteen %{target} sääntöä" + update_ip_block_html: "%{name} muutti IP-⁠osoitteen %{target} sääntöä" update_report_html: "%{name} päivitti raportin %{target}" update_status_html: "%{name} päivitti käyttäjän %{target} julkaisun" update_user_role_html: "%{name} muutti roolia %{target}" @@ -330,8 +332,8 @@ fi: title: Esikatsele tiedoteilmoitus publish: Julkaise published_msg: Tiedotteen julkaisu onnistui! - scheduled_for: Ajoitettu %{time} - scheduled_msg: Tiedotteen julkaisu ajoitettu! + scheduled_for: Ajastettu %{time} + scheduled_msg: Tiedotteen julkaisu ajastettu! title: Tiedotteet unpublish: Lopeta julkaisu unpublished_msg: Tiedotteen julkaisun lopetus onnistui! @@ -362,8 +364,8 @@ fi: no_emoji_selected: Emojeita ei muutettu, koska yhtään ei ollut valittuna not_permitted: Sinulla ei ole oikeutta suorittaa tätä toimintoa overwrite: Korvaa - shortcode: Lyhennekoodi - shortcode_hint: Vähintään 2 merkkiä, vain kirjaimia, numeroita ja alaviivoja + shortcode: Lyhytkoodi + shortcode_hint: Vähintään 2 merkkiä; vain kirjaimia, numeroita ja alaviivoja title: Mukautetut emojit uncategorized: Luokittelematon unlist: Poista listasta @@ -569,8 +571,8 @@ fi: all: Kaikki clear: Tyhjennä toimitusvirheet failing: Epäonnistuva - restart: Käynnistä toimitus uudelleen - stop: Lopeta toimitus + restart: Jatka toimitusta + stop: Pysäytä toimitus unavailable: Ei saatavilla delivery_available: Toimitus on saatavilla delivery_error_days: Toimitusvirheen päivät @@ -616,14 +618,14 @@ fi: created_msg: Uusi IP-sääntö lisättiin onnistuneesti delete: Poista expires_in: - '1209600': 2 viikkoa - '15778476': 6 kuukautta - '2629746': 1 kuukausi - '31556952': 1 vuosi - '86400': 1 päivä - '94670856': 3 vuotta + '1209600': 2 viikkoa + '15778476': 6 kuukautta + '2629746': 1 kuukausi + '31556952': 1 vuosi + '86400': 1 päivä + '94670856': 3 vuotta new: - title: Luo uusi IP-sääntö + title: Luo uusi IP-⁠sääntö no_ip_block_selected: IP-sääntöjä ei muutettu, koska yhtään ei ollut valittuna title: IP-säännöt relationships: @@ -637,7 +639,7 @@ fi: enable: Ota käyttöön enable_hint: Kun tämä on otettu käyttöön, palvelimesi tilaa välittäjältä kaikki sen välittämät julkiset julkaisut ja alkaa lähettää omansa sille. enabled: Käytössä - inbox_url: Välittäjän URL + inbox_url: Välittäjän URL-⁠osoite pending: Odotetaan välittäjän hyväksyntää save_and_enable: Tallenna ja ota käyttöön setup: Määritä yhteys välittäjään @@ -748,7 +750,7 @@ fi: moderation: Moderointi special: Erityistä delete: Poista - description_html: "Käyttäjärooleilla voit mukauttaa, mihin Mastodonin toimintoihin ja alueisiin käyttäjäsi on pääsy." + description_html: "Käyttäjärooleilla voit mukauttaa sitä, mihin Mastodonin toimintoihin ja alueisiin käyttäjilläsi on pääsy." edit: Muokkaa roolia ”%{name}” everyone: Oletuskäyttöoikeudet everyone_full_description_html: Tämä on perusrooli, joka vaikuttaa kaikkiin käyttäjiin, jopa ilman asetettua roolia. Kaikki muut roolit perivät sen käyttöoikeudet. @@ -769,7 +771,7 @@ fi: manage_blocks: Hallita estoja manage_blocks_description: Sallii käyttäjien estää sähköpostipalveluntarjoajia ja IP-osoitteita manage_custom_emojis: Hallita mukautettuja emojeita - manage_custom_emojis_description: Sallii käyttäjien hallita mukautettuja emojeita palvelimella + manage_custom_emojis_description: Sallii käyttäjien hallita palvelimen mukautettuja emojeita manage_federation: Hallita federointia manage_federation_description: Sallii käyttäjien estää tai sallia federointi muiden verkkotunnusten kanssa ja hallita toimitusta manage_invites: Hallita kutsuja @@ -784,7 +786,7 @@ fi: manage_settings_description: Sallii käyttäjien muuttaa sivuston asetuksia manage_taxonomies: Hallita luokittelua manage_taxonomies_description: Sallii käyttäjien tarkistaa suositun sisällön ja päivittää aihetunnisteiden asetuksia - manage_user_access: Hallita käyttäjäoikeuksia + manage_user_access: Hallita käyttäjien oikeuksia manage_user_access_description: Sallii käyttäjien poistaa muiden käyttäjien kaksivaiheinen todennus käytöstä, vaihtaa heidän sähköpostiosoitteensa ja palauttaa heidän salasanansa manage_users: Hallita käyttäjiä manage_users_description: Sallii käyttäjien tarkastella muiden käyttäjien tietoja ja suorittaa moderointitoimia heitä kohtaan @@ -796,6 +798,8 @@ fi: view_dashboard_description: Sallii käyttäjille pääsyn koontinäyttöön ja erilaisiin mittareihin view_devops: DevOps view_devops_description: Sallii käyttäjille pääsyn Sidekiq- ja pgHero-hallintapaneeleihin + view_feeds: Näytä live- ja aihesyötteet + view_feeds_description: Sallii käyttäjien tarkastella live- ja aihesyötteitä palvelimen asetuksista riippumatta title: Roolit rules: add_new: Lisää sääntö @@ -837,17 +841,28 @@ fi: title: Jätä käyttäjät oletusarvoisesti hakukoneindeksoinnin ulkopuolelle discovery: follow_recommendations: Seurantasuositukset - preamble: Mielenkiintoisen sisällön esille tuominen auttaa saamaan uusia käyttäjiä, jotka eivät ehkä tunne ketään Mastodonista. Määrittele, kuinka erilaiset löytämisominaisuudet toimivat palvelimellasi. + preamble: Mielenkiintoisen sisällön esille tuominen on keskeistä perehdyttäessä uusia käyttäjiä, jotka eivät ehkä tunne ketään Mastodonissa. Hallitse, miten eri löydettävyysominaisuudet toimivat palvelimellasi. privacy: Yksityisyys profile_directory: Profiilihakemisto public_timelines: Julkiset aikajanat publish_statistics: Julkaise tilastot - title: Löytäminen + title: Löydettävyys trends: Trendit + wrapstodon: Wrapstodon domain_blocks: all: Kaikille disabled: Ei kenellekään users: Kirjautuneille paikallisille käyttäjille + feed_access: + modes: + authenticated: Vain todennetut käyttäjät + disabled: Vaadi tiettyä käyttäjäroolia + public: Kaikki + landing_page: + values: + about: Tietoja + local_feed: Paikallinen syöte + trends: Trendit registrations: moderation_recommandation: Varmista, että sinulla on riittävä ja toimintavalmis joukko moderaattoreita, ennen kuin avaat rekisteröitymisen kaikille! preamble: Määritä, kuka voi luoda tilin palvelimellesi. @@ -882,7 +897,7 @@ fi: statuses: account: Tekijä application: Sovellus - back_to_account: Takaisin tilin sivulle + back_to_account: Takaisin tilisivulle back_to_report: Takaisin raporttisivulle batch: add_to_report: Lisää raporttiin nro %{id} @@ -901,13 +916,15 @@ fi: no_status_selected: Julkaisuja ei muutettu, koska yhtään ei ollut valittuna open: Avaa julkaisu original_status: Alkuperäinen julkaisu + quotes: Lainaukset reblogs: Edelleen jako replied_to_html: Vastaus käyttäjälle %{acct_link} status_changed: Julkaisua muutettu status_title: Julkaisu käyttäjältä @%{name} - title: Tilin julkaisut - @%{name} + title: Tilin julkaisut – @%{name} trending: Suosituttua view_publicly: Näytä julkisesti + view_quoted_post: Näytä lainattu julkaisu visibility: Näkyvyys with_media: Sisältää mediaa strikes: @@ -1182,11 +1199,11 @@ fi: hint_html: Jos haluat muuttaa toisesta tilistä tähän tiliin, voit luoda tässä aliaksen, mitä vaaditaan ennen kuin voit edetä siirtämään seuraajasi vanhalta tililtä tälle tilille. Tänä toiminto on itsessään vaaraton ja kumottavissa. Tilin muuttaminen aloitetaan vanhalta tililtä. remove: Poista aliaksen linkitys appearance: - advanced_web_interface: Edistynyt selainkäyttöliittymä - advanced_web_interface_hint: 'Jos haluat hyödyntää näytön koko leveyttä, edistyneen selainkäyttöliittymän avulla voit määrittää useita erilaisia sarakkeita, niin näet kerralla niin paljon tietoa kuin haluat: kotisyöte, ilmoitukset, yleinen aikajana, mikä tahansa määrä listoja ja aihetunnisteita.' + advanced_settings: Edistyneet asetukset animations_and_accessibility: Animaatiot ja saavutettavuus - confirmation_dialogs: Vahvistusikkunat - discovery: Löytäminen + boosting_preferences: Tehostusasetukset + boosting_preferences_info_html: "Vihje: Asetuksista riippumatta Vaihto + napsautus %{icon} Tehosta-kuvakkeeseen tehostaa välittömästi." + discovery: Löydettävyys localization: body: Mastodonin ovat kääntäneet vapaaehtoiset. guide_link: https://crowdin.com/project/mastodon @@ -1398,7 +1415,7 @@ fi: not_found_multiple: käyttäjänimiä %{usernames} ei löytynyt exports: archive_takeout: - date: Päiväys + date: Päivämäärä download: Lataa arkisto hint_html: Voit pyytää arkistoa omista julkaisuista ja mediasta. Viedyt tiedot ovat ActivityPub-muodossa, ja ne voi lukea millä tahansa yhteensopivalla ohjelmalla. Voit pyytää arkistoa 7 päivän välein. in_progress: Arkistoa kootaan… @@ -1454,7 +1471,7 @@ fi: statuses: back_to_filter: Takaisin suodattimeen batch: - remove: Poista suodattimista + remove: Poista suodattimesta index: hint: Tämä suodatin koskee yksittäisten julkaisujen valintaa muista kriteereistä riippumatta. Voit lisätä lisää julkaisuja tähän suodattimeen selainkäyttöliittymästä. title: Suodatetut julkaisut @@ -1540,7 +1557,7 @@ fi: states: finished: Valmis in_progress: Käynnissä - scheduled: Ajoitettu + scheduled: Ajastettu unconfirmed: Varmistamaton status: Tila success: Tietojen lähettäminen onnistui, ja ne käsitellään aivan pian @@ -1587,6 +1604,13 @@ fi: expires_at: Vanhenee uses: Käyttökertoja title: Kutsu käyttäjiä + link_preview: + author_html: Tehnyt %{name} + potentially_sensitive_content: + action: Näytä napsauttamalla + confirm_visit: Haluatko varmasti avata tämän linkin? + hide_button: Piilota + label: Mahdollisesti arkaluonteista sisältöä lists: errors: limit: Sinulla on enimmäismäärä listoja @@ -1651,7 +1675,7 @@ fi: disabled_account: Nykyinen tilisi ei ole täysin käytettävissä tämän jälkeen. Sinulla on kuitenkin pääsy tietojen vientiin ja tilin uudelleenaktivointiin. followers: Tämä toiminto siirtää kaikki seuraajasi nykyiseltä tililtä uudelle tilille only_redirect_html: Vaihtoehtoisesti voit asettaa vain ohjauksen profiiliisi. - other_data: Muita tietoja ei siirretä automaattisesti + other_data: Muita tietoja ei siirretä automaattisesti (mukaan lukien julkaisujasi ja seurattavien tilien luettoloa) redirect: Nykyisen tilisi profiili päivitetään uudelleenohjaushuomautuksella ja suljetaan pois hauista moderation: title: Moderointi @@ -1685,16 +1709,22 @@ fi: body: "%{name} mainitsi sinut:" subject: "%{name} mainitsi sinut" title: Uusi maininta + moderation_warning: + subject: Olet saanut moderointivaroituksen poll: subject: Äänestys käyttäjältä %{name} on päättynyt quote: body: "%{name} lainasi julkaisuasi:" subject: "%{name} lainasi julkaisuasi" title: Uusi lainaus + quoted_update: + subject: "%{name} muokkasi lainaamaasi julkaisua" reblog: body: "%{name} tehosti julkaisuasi:" subject: "%{name} tehosti julkaisuasi" title: Uusi tehostus + severed_relationships: + subject: Olet menettänyt yhteyksiä moderointipäätöksen vuoksi status: subject: "%{name} julkaisi juuri" update: @@ -1729,7 +1759,7 @@ fi: truncate: "…" polls: errors: - already_voted: Olet jo äänestänyt tässä äänestyksessä + already_voted: Olet jo osallistunut tähän äänestykseen duplicate_options: sisältää kaksoiskappaleita duration_too_long: on liian kaukana tulevaisuudessa duration_too_short: on liian aikainen @@ -1796,8 +1826,8 @@ fi: account: Julkiset julkaisut tililtä @%{acct} tag: 'Julkiset julkaisut aihetunnisteella #%{hashtag}' scheduled_statuses: - over_daily_limit: Olet ylittänyt %{limit} ajoitetun julkaisun rajan tälle päivälle - over_total_limit: Olet ylittänyt %{limit} ajoitetun julkaisun rajan + over_daily_limit: Olet ylittänyt %{limit} ajastetun julkaisun rajan tälle päivälle + over_total_limit: Olet ylittänyt %{limit} ajastetun julkaisun rajan too_soon: päivämäärän on oltava tulevaisuudessa self_destruct: lead_html: Valitettavasti %{domain} sulkeutuu pysyvästi. Jos sinulla on siellä tili, et voi jatkaa sen käyttöä mutta voit yhä pyytää varmuuskopiota tiedoistasi. @@ -1826,7 +1856,7 @@ fi: unknown_browser: tuntematon selain weibo: Weibo current_session: Nykyinen istunto - date: Päiväys + date: Päivämäärä description: "%{browser} %{platform}" explanation: Nämä verkkoselaimet ovat parhaillaan kirjautuneena Mastodon-tilillesi. ip: IP-osoite @@ -1897,6 +1927,9 @@ fi: other: "%{count} videota" boosted_from_html: Tehosti lähteestä %{acct_link} content_warning: 'Sisältövaroitus: %{warning}' + content_warnings: + hide: Piilota julkaisu + show: Näytä lisää default_language: Sama kuin käyttöliittymän kieli disallowed_hashtags: one: 'sisälsi kielletyn aihetunnisteen: %{tags}' @@ -1905,16 +1938,22 @@ fi: errors: in_reply_not_found: Julkaisua, johon yrität vastata, ei näytä olevan olemassa. quoted_status_not_found: Julkaisua, jota yrität lainata, ei näytä olevan olemassa. + quoted_user_not_mentioned: Mainitsematonta käyttäjää ei voi lainata yksityismaininnassa. over_character_limit: merkkimäärän rajoitus %{max} ylitetty pin_errors: direct: Vain mainituille käyttäjille näkyviä julkaisuja ei voi kiinnittää limit: Olet jo kiinnittänyt enimmäismäärän julkaisuja ownership: Muiden julkaisuja ei voi kiinnittää reblog: Tehostusta ei voi kiinnittää + quote_error: + not_available: Julkaisu ei saatavilla + pending_approval: Julkaisu odottaa + revoked: Tekijä on poistanut julkaisun quote_policies: followers: Vain seuraajat nobody: Vain minä public: Kuka tahansa + quote_post_author: Lainasi käyttäjän %{acct} julkaisua title: "%{name}: ”%{quote}”" visibilities: direct: Yksityismaininta @@ -2056,7 +2095,7 @@ fi: disable: Et voi enää käyttää tiliäsi, mutta profiilisi ja muut tiedot pysyvät muuttumattomina. Voit pyytää varmuuskopiota tiedoistasi, vaihtaa tilin asetuksia tai poistaa tilisi. mark_statuses_as_sensitive: Palvelimen %{instance} moderaattorit ovat merkinneet osan julkaisuistasi arkaluonteisiksi. Tämä tarkoittaa sitä, että mediaa täytyy napauttaa ennen kuin sen esikatselu näytetään. Voit merkitä median itse arkaluonteiseksi, kun julkaiset tulevaisuudessa. sensitive: Tästä lähtien kaikki lähetetyt mediatiedostot merkitään arkaluonteisiksi ja piilotetaan napsautusvaroituksen taakse. - silence: Voit edelleen käyttää tiliäsi, mutta vain sinua jo seuraavat käyttäjät näkevät julkaisusi tällä palvelimella ja sinut voidaan sulkea pois eri löytämisominaisuuksista. Toiset voivat kuitenkin edelleen seurata sinua manuaalisesti. + silence: Voit edelleen käyttää tiliäsi, mutta vain sinua jo seuraavat käyttäjät näkevät julkaisusi tällä palvelimella ja sinut voidaan sulkea pois eri löydettävyysominaisuuksista. Toiset voivat kuitenkin edelleen seurata sinua manuaalisesti. suspend: Et voi enää käyttää tiliäsi, eivätkä profiilisi ja muut tiedot ole enää käytettävissä. Voit silti kirjautua sisään pyytääksesi tietojesi varmuuskopiota, kunnes tiedot on poistettu kokonaan noin 30 päivän kuluttua. Säilytämme kuitenkin joitain perustietoja, jotka estävät sinua kiertämästä jäädytystä. reason: 'Syy:' statuses: 'Julkaisuja lainattu:' @@ -2128,7 +2167,7 @@ fi: extra_instructions_html: Vinkki: Verkkosivustollasi oleva linkki voi olla myös näkymätön. Olennainen osuus on rel="me", joka estää toiseksi henkilöksi tekeytymisen verkkosivustoilla, joilla on käyttäjien luomaa sisältöä. Voit käyttää jopa link-elementtiä sivun head-osassa elementin a sijaan, mutta HTML:n pitää olla käytettävissä ilman JavaScript-koodin suorittamista. here_is_how: Näin se onnistuu hint_html: "Henkilöllisyyden vahvistaminen on Mastodonissa jokaisen käyttäjän ulottuvilla. Se perustuu avoimiin standardeihin ja on maksutonta nyt ja aina. Tarvitset vain henkilökohtaisen verkkosivuston, jonka perusteella sinut voidaan tunnistaa. Kun teet linkin tuolle verkkosivulle profiilistasi, tarkistamme, että verkkosivustolla on linkki takaisin profiiliisi, ja näytämme profiilissasi visuaalisen ilmaisimen." - instructions_html: Kopioi ja liitä seuraava koodi verkkosivustosi HTML-lähdekoodiin. Lisää sitten verkkosivustosi osoite johonkin profiilisi lisäkentistä ”Muokkaa profiilia” -välilehdellä ja tallenna muutokset. + instructions_html: Kopioi ja liitä seuraava koodi verkkosivustosi HTML-lähdekoodiin. Lisää sitten verkkosivustosi osoite johonkin profiilisi lisäkentistä ”Muokkaa profiilia” -⁠välilehdellä ja tallenna muutokset. verification: Vahvistus verified_links: Vahvistetut linkkisi website_verification: Verkkosivuston vahvistus @@ -2149,3 +2188,6 @@ fi: not_supported: Tämä selain ei tue suojausavaimia otp_required: Jos haluat käyttää suojausavaimia, ota ensin kaksivaiheinen todennus käyttöön. registered_on: Rekisteröity %{date} + wrapstodon: + description: Katso, kuinka %{name} käytti Mastodonia tänä vuonna! + title: Käyttäjän %{name} Wrapstodon vuodelle %{year} diff --git a/config/locales/fo.yml b/config/locales/fo.yml index f6ba039efc7777..4216367aef5efa 100644 --- a/config/locales/fo.yml +++ b/config/locales/fo.yml @@ -7,6 +7,8 @@ fo: hosted_on: Mastodon hýst á %{domain} title: Um accounts: + errors: + cannot_be_added_to_collections: Hendan kontan kann ikki leggjast afturat søvnum. followers: one: Fylgjari other: Fylgjarar @@ -796,6 +798,8 @@ fo: view_dashboard_description: Gevur brúkarum atgongd til kunningarbrettið og ymisk mát view_devops: DevOps view_devops_description: Gevur brúkarum atgongd til Sidekiq- og pgHero-kunningarbretti + view_feeds: Vís beinleiðis rásir og evnisrásir + view_feeds_description: Loyvir brúkarum atgongd til beinleiðis rásir og evnisrásir, óansæð ambætisstillingar title: Leiklutir rules: add_new: Ger nýggja reglu @@ -848,6 +852,16 @@ fo: all: Til øll disabled: Til ongan users: Fyri lokalum brúkarum, sum eru ritaðir inn + feed_access: + modes: + authenticated: Einans váttaðir brúkarar + disabled: Krev serstakan brúkaraleiklut + public: Øll + landing_page: + values: + about: Um + local_feed: Lokal rás + trends: Rák registrations: moderation_recommandation: Vinarliga tryggja tær, at tú hevur eitt nøktandi og klárt umsjónartoymi, áðreen tú letur upp fyri skrásetingum frá øllum! preamble: Stýr, hvør kann stovna eina kontu á tínum ambætara. @@ -901,6 +915,7 @@ fo: no_status_selected: Eingir postar vóru broyttir, tí eingir vóru valdir open: Lat post upp original_status: Upprunapostur + quotes: Sitatir reblogs: Endurbloggar replied_to_html: Svaraði %{acct_link} status_changed: Postur broyttur @@ -908,6 +923,7 @@ fo: title: Kontupostar - @%{name} trending: Vælumtókt view_publicly: Vís fyri øllum + view_quoted_post: Vís siteraðan post visibility: Sýni with_media: Við miðli strikes: @@ -1182,10 +1198,10 @@ fo: hint_html: Ynskir tú at flyta frá eini aðrari kontu til hesa, so kanst tú stovna eitt tøkuheiti her. Tað er kravt, áðrenn tú kanst fara í gongd við at flyta fylgjarar frá gomlu kontuni til hesa. Hendan atgerðin er í sær sjálvum meinaleys og kann angrast. Flytingin av kontuni verður sett í gongd frá gomlu kontuni. remove: Strika tilknýti til tøkuheiti appearance: - advanced_web_interface: Framkomið vevmarkamót - advanced_web_interface_hint: 'Ynskir tú at brúka alla skermbreiddina, so loyvir framkomna vevmarkamóti tær at uppseta fleiri ymiskar teigar, soleiðis at tú kanst síggja so nógvar upplýsingar, sum tú ynskir, samstundis: Heima, fráboðanir, sameind tíðarlinja og óavmarkað tal av listum og frámerkjum.' + advanced_settings: Framkomnar stillingar animations_and_accessibility: Teknimyndagerð og atkomuligheit - confirmation_dialogs: Váttanarskermmyndir + boosting_preferences: Stimbranarynski + boosting_preferences_info_html: "Góð ráð: Óansæð stillingar, so verður beinan vegin stimbrað, um trýst verður á Shift + klikk á %{icon} stimbranar-ímyndina." discovery: Uppdaging localization: body: Mastodon er umsett av sjálvbodnum. @@ -1587,6 +1603,13 @@ fo: expires_at: Rennir út uses: Brúk title: Bjóða fólki + link_preview: + author_html: Av %{name} + potentially_sensitive_content: + action: Klikka fyri at vísa + confirm_visit: Er tú vís/ur í, at tú vil lata hetta leinkið upp? + hide_button: Fjal + label: Tilfar, sum møguliga er viðkvæmt lists: errors: limit: Tú hevur rokkið mesta talið av listum @@ -1651,7 +1674,7 @@ fo: disabled_account: Tín verandi konta verður ikki fult nýtilig aftaná. Tó so, tú hevur atgongd til at útlesa dátur og tú kanst gera kontuna virkna aftur. followers: Hetta fer at flyta alla fylgjarar frá verandi kontuni til ta nýggju only_redirect_html: Ein annar møguleiki er, at tú setir eina víðaribeining upp á vanga tínum. - other_data: Ongar aðrar dátur verða fluttar sjálvvirkandi + other_data: Ongar aðrar dátur verða fluttar sjálvvirkandi (íroknað postarnir hjá tær og listin av kontum, sum tú fylgir) redirect: Vangin á tíni núverandi kontu verður dagførd við eini viðmerking um víðaribeining og tú verður ikki at finna í leitingum moderation: title: Umsjón @@ -1685,16 +1708,22 @@ fo: body: 'Tú var umrødd/ur av %{name} í:' subject: Tú var umrødd/ur av %{name} title: Nýggj umrøða + moderation_warning: + subject: Tú hevur móttikið eina umsjónarávaring poll: subject: Ein spurnarkanning hjá %{name} er endað quote: body: 'Postur tín var siteraður av %{name}:' subject: "%{name} siteraði postin hjá tær" title: Nýggj sitering + quoted_update: + subject: "%{name} rættaði ein post, sum tú hevur siterað" reblog: body: 'Postur tín var stimbraður av %{name}:' subject: "%{name} stimbraði tín post" title: Nýggj stimbran + severed_relationships: + subject: Tú hevur mist sambond vegna eina umsjónaravgerð status: subject: "%{name} hevur júst postað" update: @@ -1897,6 +1926,9 @@ fo: other: "%{count} sjónfílur" boosted_from_html: Stimbrað frá %{acct_link} content_warning: 'Innihaldsávaring: %{warning}' + content_warnings: + hide: Fjal post + show: Vís meiri default_language: Sama sum markamótsmál disallowed_hashtags: one: 'innihelt eitt vrakað frámerki: %{tags}' @@ -1905,16 +1937,22 @@ fo: errors: in_reply_not_found: Posturin, sum tú roynir at svara, sýnist ikki at finnast. quoted_status_not_found: Posturin, sum tú roynir at sitera, sýnist ikki at finnast. + quoted_user_not_mentioned: Kann ikki sitera ein brúkara, sum ikki er nevndur, í einum posti, sum er ein privat umrøða. over_character_limit: mesta tal av teknum, %{max}, rokkið pin_errors: direct: Postar, sum einans eru sjónligir hjá nevndum brúkarum, kunnu ikki festast limit: Tú hevur longu fest loyvda talið av postum ownership: Postar hjá øðrum kunnu ikki festast reblog: Ein stimbran kann ikki festast + quote_error: + not_available: Postur ikki tøkur + pending_approval: Postur bíðar + revoked: Posturin burturbeindur av høvundinum quote_policies: followers: Einans fylgjarar nobody: Bara eg public: Ein og hvør + quote_post_author: Siteraði ein post hjá %{acct} title: '%{name}: "%{quote}"' visibilities: direct: Privat umrøða @@ -2149,3 +2187,6 @@ fo: not_supported: Hesin kagin stuðlar ikki uppundir trygdarlyklar otp_required: Fyri at brúka trygdarlyklar er neyðugt at gera váttan í tveimum stigum virkna fyrst. registered_on: Skrásett %{date} + wrapstodon: + description: Sí hvussu %{name} brúkti Mastodon í ár! + title: Wrapstodon %{year} fyri %{name} diff --git a/config/locales/fr-CA.yml b/config/locales/fr-CA.yml index 21bc55898377ac..6590c544d9fa4f 100644 --- a/config/locales/fr-CA.yml +++ b/config/locales/fr-CA.yml @@ -7,6 +7,8 @@ fr-CA: hosted_on: Serveur Mastodon hébergé sur %{domain} title: À propos accounts: + errors: + cannot_be_added_to_collections: Ce compte ne peut pas être ajouté aux collections. followers: one: Abonné·e other: Abonné·e·s @@ -190,6 +192,7 @@ fr-CA: create_relay: Créer un relais create_unavailable_domain: Créer un domaine indisponible create_user_role: Créer le rôle + create_username_block: Créer une règle de nom d'utilisateur demote_user: Rétrograder l’utilisateur·ice destroy_announcement: Supprimer l’annonce destroy_canonical_email_block: Supprimer le blocage de courriel @@ -203,6 +206,7 @@ fr-CA: destroy_status: Supprimer le message destroy_unavailable_domain: Supprimer le domaine indisponible destroy_user_role: Détruire le rôle + destroy_username_block: Supprimer la règle de nom d'utilisateur disable_2fa_user: Désactiver l’A2F disable_custom_emoji: Désactiver les émojis personnalisés disable_relay: Désactiver le relais @@ -237,6 +241,7 @@ fr-CA: update_report: Mettre à jour le rapport update_status: Mettre à jour le message update_user_role: Mettre à jour le rôle + update_username_block: Mettre à jour la règle de nom d'utilisateur actions: approve_appeal_html: "%{name} a approuvé l'appel de la décision de modération émis par %{target}" approve_user_html: "%{name} a approuvé l’inscription de %{target}" @@ -255,6 +260,7 @@ fr-CA: create_relay_html: "%{name} a créé un relais %{target}" create_unavailable_domain_html: "%{name} a arrêté la livraison vers le domaine %{target}" create_user_role_html: "%{name} a créé le rôle %{target}" + create_username_block_html: "%{name} a ajouté une règle pour les noms d'utilisateur contenant %{target}" demote_user_html: "%{name} a rétrogradé l'utilisateur·rice %{target}" destroy_announcement_html: "%{name} a supprimé l'annonce %{target}" destroy_canonical_email_block_html: "%{name} a débloqué l'adresse email avec le hachage %{target}" @@ -268,6 +274,7 @@ fr-CA: destroy_status_html: "%{name} a supprimé le message de %{target}" destroy_unavailable_domain_html: "%{name} a repris la livraison au domaine %{target}" destroy_user_role_html: "%{name} a supprimé le rôle %{target}" + destroy_username_block_html: "%{name} a supprimé une règle pour les noms d'utilisateurs contenant %{target}" disable_2fa_user_html: "%{name} a désactivé l'authentification à deux facteurs pour l'utilisateur·rice %{target}" disable_custom_emoji_html: "%{name} a désactivé l'émoji %{target}" disable_relay_html: "%{name} a désactivé le relais %{target}" @@ -302,6 +309,7 @@ fr-CA: update_report_html: "%{name} a mis à jour le rapport de signalement %{target}" update_status_html: "%{name} a mis à jour le message de %{target}" update_user_role_html: "%{name} a changé le rôle %{target}" + update_username_block_html: "%{name} a mis à jour une règle pour les noms d'utilisateurs contenant %{target}" deleted_account: compte supprimé empty: Aucun journal trouvé. filter_by_action: Filtrer par action @@ -489,9 +497,19 @@ fr-CA: created_at: Créé à delete: Supprimer ip: Adresse IP + request_body: Corps de la demande + title: Déboguer les rappels providers: + active: Activé + base_url: URL de base + callback: Rappel + delete: Supprimer + edit: Modifier un fournisseur + finish_registration: Terminer l'inscription name: Nom providers: Fournisseur + public_key_fingerprint: Empreinte de la clé publique + registration_requested: Inscription demandée registrations: confirm: Confirmer description: Vous avez reçu une souscription d'un FSAF. Rejetez-la si vous ne l'avez pas initiée. Si c'est bien votre intention, comparez le nom et l'empreinte de la clé avant de confirmer la souscription. @@ -502,6 +520,7 @@ fr-CA: sign_in: Se connecter status: État title: Fournisseurs de Services Auxiliaire du Fedivers + title: FASP follow_recommendations: description_html: "Les recommandations d'abonnement aident les nouvelles personnes à trouver rapidement du contenu intéressant. Si un·e utilisateur·rice n'a pas assez interagi avec les autres pour avoir des recommandations personnalisées, ces comptes sont alors recommandés. La sélection est mise à jour quotidiennement depuis un mélange de comptes ayant le plus d'interactions récentes et le plus grand nombre d'abonné·e·s locaux pour une langue donnée." language: Pour la langue @@ -571,6 +590,11 @@ fr-CA: limited: Limité title: Modération moderation_notes: + create: Ajouter une note de modération + created_msg: Note de modération de l'instance créée avec succès ! + description_html: Voir et laisser des notes pour les autres modérateurs et pour référence future + destroyed_msg: Note de modération de l'instance supprimée avec succès ! + placeholder: Informations à propos de cette instance, actions entreprises ou de toute autre chose qui vous aidera à modérer cette instance à l'avenir. title: Notes de modération private_comment: Commentaire privé public_comment: Commentaire public @@ -777,6 +801,8 @@ fr-CA: view_dashboard_description: Permet aux utilisateur⋅rice⋅s d'accéder au tableau de bord et à diverses statistiques view_devops: DevOps view_devops_description: Permet aux utilisateur⋅rice⋅s d'accéder aux tableaux de bord Sidekiq et pgHero + view_feeds: Voir les flux en direct et les fils de discussion + view_feeds_description: Permet aux utilisateur·rice·s d'accéder aux flux en direct et de discussion indépendamment des paramètres du serveur title: Rôles rules: add_new: Ajouter une règle @@ -785,15 +811,21 @@ fr-CA: description_html: Bien que la plupart des gens prétende avoir lu les conditions d'utilisation avant de les accepter, généralement les utilisateur·rice·s ne les lisent vraiment que lorsque un problème apparaît. Pour faciliter la visualisation des règles de votre serveur en un seul coup d’œil, présentez-les sous la forme d'une liste à puces ! Essayez de garder chacune des règles simple et concise, mais faites attention à ne pas non plus les diviser en de trop nombreux éléments distincts. edit: Modifier la règle empty: Aucune règle de serveur n'a été définie pour l'instant. + move_down: Déplacer vers le bas + move_up: Déplacer vers le haut title: Règles du serveur translation: Traduction translations: Traductions + translations_explanation: Vous pouvez éventuellement ajouter des traductions pour les règles. La valeur par défaut sera affichée si aucune version traduite n'est disponible. Veuillez toujours vous assurer que toute traduction fournie est synchronisée avec la valeur par défaut. settings: about: manage_rules: Gérer les règles du serveur preamble: Fournissez des informations détaillées sur le fonctionnement, la modération et le financement du serveur. rules_hint: Il y a un espace dédié pour les règles auxquelles vos utilisateurs sont invités à adhérer. title: À propos + allow_referrer_origin: + desc: Quand les utilisateurs cliquent sur un lien externe, leur navigateur peut envoyer l'adresse du serveur Mastodon en tant qu'adresse d'origine. À désactiver si cela permet d'identifier les utilisateurs, par exemple s'il s'agit d'un serveur Mastodon personnel. + title: Autoriser les sites externes à voir votre serveur Mastodon comme une source de trafic appearance: preamble: Personnaliser l'interface web de Mastodon. title: Apparence @@ -812,18 +844,30 @@ fr-CA: title: Par défaut, ne pas indexer les comptes dans les moteurs de recherche discovery: follow_recommendations: Suivre les recommandations - preamble: Faire apparaître un contenu intéressant est essentiel pour interagir avec de nouveaux utilisateurs qui ne connaissent peut-être personne sur Mastodonte. Contrôlez le fonctionnement des différentes fonctionnalités de découverte sur votre serveur. + preamble: Il est essentiel de donner de la visibilité à des contenus intéressants pour attirer des utilisateur⋅rice⋅s néophytes qui ne connaissent peut-être personne sur Mastodon. Contrôlez le fonctionnement des différentes fonctionnalités de découverte sur votre serveur. + privacy: Vie privée profile_directory: Annuaire des profils public_timelines: Fils publics publish_statistics: Publier les statistiques title: Découverte trends: Tendances + wrapstodon: Wrapstodon domain_blocks: all: À tout le monde disabled: À personne users: Aux utilisateur·rice·s connecté·e·s localement + feed_access: + modes: + authenticated: Utilisateurs authentifiés uniquement + disabled: Nécessite un rôle spécifique + public: Tout le monde + landing_page: + values: + about: À propos + local_feed: Fil local + trends: Tendances registrations: - moderation_recommandation: Veuillez vous assurer d'avoir une équipe de modération adéquate et réactive avant d'ouvrir les inscriptions à tout le monde! + moderation_recommandation: Veuillez vous assurer d'avoir une équipe de modération adéquate et réactive avant d'ouvrir les inscriptions à tout le monde ! preamble: Affecte qui peut créer un compte sur votre serveur. title: Inscriptions registrations_mode: @@ -875,6 +919,7 @@ fr-CA: no_status_selected: Aucun message n’a été modifié car aucun n’a été sélectionné open: Ouvrir le message original_status: Message original + quotes: Citations reblogs: Partages replied_to_html: Répondu à %{acct_link} status_changed: Publication modifiée @@ -882,6 +927,7 @@ fr-CA: title: Messages du compte - @%{name} trending: Tendances view_publicly: Afficher de manière publique + view_quoted_post: Voir la publication citée visibility: Visibilité with_media: Avec médias strikes: @@ -899,6 +945,8 @@ fr-CA: system_checks: database_schema_check: message_html: Vous avez des migrations de base de données en attente. Veuillez les exécuter pour vous assurer que l'application se comporte comme prévu + elasticsearch_analysis_index_mismatch: + message_html: Les paramètres de l'analyseur d'index d'Elasticsearch ne sont plus à jour. Veuillez lancer tootctl search deploy --only=%{value} elasticsearch_health_red: message_html: Le cluster Elasticsearch n’est pas sain (statut rouge), les fonctionnalités de recherche ne sont pas disponible elasticsearch_health_yellow: @@ -1063,6 +1111,25 @@ fr-CA: other: Utilisé par %{count} personnes au cours de la dernière semaine title: Recommandations et tendances trending: Tendances + username_blocks: + add_new: Ajouter un nouveau + block_registrations: Bloquer les inscriptions + comparison: + contains: Contient + equals: Est égal à + contains_html: Contient %{string} + created_msg: Règle de nom d'utilisateur créée avec succès + delete: Supprimer + edit: + title: Modifier la règle de nom d'utilisateur + matches_exactly_html: Égal à %{string} + new: + create: Créer une règle + title: Créer une nouvelle règle de nom d'utilisateur + no_username_block_selected: Aucune règle de nom d'utilisateur n'a été modifiée car aucune n'a été sélectionnée + not_permitted: Non autorisé + title: Règles de nom d'utilisateur + updated_msg: Règle de nom d'utilisateur mise à jour warning_presets: add_new: Ajouter un nouveau delete: Supprimer @@ -1135,10 +1202,10 @@ fr-CA: hint_html: Si vous voulez déménager d’un autre compte vers celui-ci, vous pouvez créer ici un alias, qui est nécessaire avant de pouvoir migrer les abonné·e·s de l’ancien compte vers celui-ci. Cette action en soi est inoffensive et réversible. La migration du compte est initiée à partir de l’ancien compte. remove: Détacher l'alias appearance: - advanced_web_interface: Interface web avancée - advanced_web_interface_hint: 'Si vous voulez utiliser toute la largeur de votre écran, l’interface web avancée vous permet de configurer plusieurs colonnes différentes pour voir autant d’informations que vous le souhaitez en même temps : Accueil, notifications, fil public fédéré, un nombre illimité de listes et hashtags.' + advanced_settings: Paramètres avancés animations_and_accessibility: Animations et accessibilité - confirmation_dialogs: Dialogues de confirmation + boosting_preferences: Préférences de partage + boosting_preferences_info_html: "Astuce : indépendamment des paramètres, Maj + Clic sur l'icône %{icon} Boost va immédiatement partager." discovery: Découverte localization: body: Mastodon est traduit par des volontaires. @@ -1327,6 +1394,10 @@ fr-CA: basic_information: Informations de base hint_html: "Personnalisez ce que les gens voient sur votre profil public et à côté de vos messages. Les autres personnes seront plus susceptibles de vous suivre et d’interagir avec vous lorsque vous avez un profil complet et une photo." other: Autre + emoji_styles: + auto: Auto + native: Natif + twemoji: Twemoji errors: '400': La demande que vous avez soumise est invalide ou mal formée. '403': Vous n’avez pas accès à cette page. @@ -1536,6 +1607,13 @@ fr-CA: expires_at: Expire uses: Utilisations title: Inviter des gens + link_preview: + author_html: Par %{name} + potentially_sensitive_content: + action: Cliquer pour afficher + confirm_visit: Voulez-vous vraiment ouvrir ce lien ? + hide_button: Masquer + label: Contenu potentiellement sensible lists: errors: limit: Vous avez atteint le nombre maximum de listes @@ -1600,7 +1678,7 @@ fr-CA: disabled_account: Votre compte actuel ne sera pas entièrement utilisable par la suite. Cependant, vous aurez accès à l'exportation de données et à la réactivation. followers: Cette action va déménager tou·te·s les abonné·e·s du compte actuel vers le nouveau compte only_redirect_html: Alternativement, vous pouvez seulement appliquer une redirection sur votre profil. - other_data: Aucune autre donnée ne sera déplacée automatiquement + other_data: Aucune autre donnée ne sera déplacée automatiquement (y compris vos messages et la liste des comptes suivis) redirect: Le profil de votre compte actuel sera mis à jour avec un avis de redirection et sera exclu des recherches moderation: title: Modération @@ -1634,12 +1712,22 @@ fr-CA: body: "%{name} vous a mentionné⋅e dans :" subject: "%{name} vous a mentionné·e" title: Nouvelle mention + moderation_warning: + subject: Vous avez reçu un avertissement de la modération poll: subject: Un sondage de %{name} est terminé + quote: + body: 'Votre message a été cité par %{name}:' + subject: "%{name} a cité votre message" + title: Nouvelle citation + quoted_update: + subject: "%{name} a modifié un message que vous avez cité" reblog: body: 'Votre message été partagé par %{name} :' subject: "%{name} a partagé votre message" title: Nouveau partage + severed_relationships: + subject: Vous avez perdu des relations à cause d’une décision de modération status: subject: "%{name} vient de publier" update: @@ -1684,6 +1772,9 @@ fr-CA: self_vote: Vous ne pouvez pas voter à vos propres sondages too_few_options: doit avoir plus qu’une proposition too_many_options: ne peut contenir plus de %{max} propositions + vote: Voter + posting_defaults: + explanation: Ces paramètres seront utilisés par défaut lorsque vous créez de nouveaux messages, mais vous pouvez les modifier par message dans le champs de rédaction. preferences: other: Autre posting_defaults: Paramètres de publication par défaut @@ -1839,6 +1930,9 @@ fr-CA: other: "%{count} vidéos" boosted_from_html: Partagé depuis %{acct_link} content_warning: 'Avertissement sur le contenu : %{warning}' + content_warnings: + hide: Masquer le message + show: Afficher plus default_language: Même langue que celle de l’interface disallowed_hashtags: one: 'contient un hashtag désactivé : %{tags}' @@ -1846,15 +1940,31 @@ fr-CA: edited_at_html: Édité le %{date} errors: in_reply_not_found: Le message auquel vous essayez de répondre ne semble pas exister. + quoted_status_not_found: Le message que vous essayez de citer ne semble pas exister. + quoted_user_not_mentioned: Impossible de citer un compte non mentionné dans une mention privée. over_character_limit: limite de %{max} caractères dépassée pin_errors: direct: Les messages qui ne sont visibles que pour les utilisateur·rice·s mentionné·e·s ne peuvent pas être épinglés limit: Vous avez déjà épinglé le nombre maximum de messages ownership: Vous ne pouvez pas épingler un message ne vous appartenant pas reblog: Un partage ne peut pas être épinglé + quote_error: + not_available: Message indisponible + pending_approval: Message en attente + revoked: Message supprimé par l'auteur + quote_policies: + followers: Abonné·e·s seulement + nobody: Moi uniquement + public: Tout le monde + quote_post_author: A cité un message de %{acct} title: "%{name} : « %{quote} »" visibilities: + direct: Mention privée + private: Abonné·e·s seulement public: Publique + public_long: Tout le monde sur et en dehors de Mastodon + unlisted: Public discret + unlisted_long: Caché des résultats de recherche, des tendances et des fils d'actualité publics statuses_cleanup: enabled: Supprimer automatiquement vos anciens messages enabled_hint: Supprime automatiquement vos messages une fois qu'ils ont atteint un seuil d'ancienneté défini, à moins qu'ils ne correspondent à l'une des exceptions ci-dessous @@ -1900,6 +2010,9 @@ fr-CA: terms_of_service: title: Conditions d'utilisation terms_of_service_interstitial: + future_preamble_html: Nous avons modifié nos conditions d'utilisation, ces changements entreront en vigueur le %{date}. Nous vous invitons à prendre connaissance des nouvelles conditions. + past_preamble_html: Nous avons modifié nos conditions d'utilisation depuis votre dernière visite. Nous vous invitons à prendre connaissance des nouvelles conditions. + review_link: Vérifier les conditions d'utilisation title: Les conditions d'utilisation de %{domain} ont changées themes: contrast: Mastodon (Contraste élevé) @@ -2078,3 +2191,6 @@ fr-CA: not_supported: Ce navigateur ne prend pas en charge les clés de sécurité otp_required: Pour utiliser les clés de sécurité, veuillez d'abord activer l'authentification à deux facteurs. registered_on: Inscrit le %{date} + wrapstodon: + description: Voir comment %{name} a utilisé Mastodon cette année ! + title: Wrapstodon %{year} pour %{name} diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 36244422330217..3ce00ca9f97a7e 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -7,6 +7,8 @@ fr: hosted_on: Serveur Mastodon hébergé sur %{domain} title: À propos accounts: + errors: + cannot_be_added_to_collections: Ce compte ne peut pas être ajouté aux collections. followers: one: Abonné·e other: Abonné·e·s @@ -190,6 +192,7 @@ fr: create_relay: Créer un relais create_unavailable_domain: Créer un domaine indisponible create_user_role: Créer le rôle + create_username_block: Créer une règle de nom d'utilisateur demote_user: Rétrograder l’utilisateur·ice destroy_announcement: Supprimer l’annonce destroy_canonical_email_block: Supprimer le blocage de courriel @@ -203,6 +206,7 @@ fr: destroy_status: Supprimer le message destroy_unavailable_domain: Supprimer le domaine indisponible destroy_user_role: Détruire le rôle + destroy_username_block: Supprimer la règle de nom d'utilisateur disable_2fa_user: Désactiver l’A2F disable_custom_emoji: Désactiver les émojis personnalisés disable_relay: Désactiver le relais @@ -237,6 +241,7 @@ fr: update_report: Mettre à jour le rapport update_status: Mettre à jour le message update_user_role: Mettre à jour le rôle + update_username_block: Mettre à jour la règle de nom d'utilisateur actions: approve_appeal_html: "%{name} a approuvé l'appel de la décision de modération émis par %{target}" approve_user_html: "%{name} a approuvé l’inscription de %{target}" @@ -255,6 +260,7 @@ fr: create_relay_html: "%{name} a créé un relais %{target}" create_unavailable_domain_html: "%{name} a arrêté la livraison vers le domaine %{target}" create_user_role_html: "%{name} a créé le rôle %{target}" + create_username_block_html: "%{name} a ajouté une règle pour les noms d'utilisateur contenant %{target}" demote_user_html: "%{name} a rétrogradé l'utilisateur·rice %{target}" destroy_announcement_html: "%{name} a supprimé l'annonce %{target}" destroy_canonical_email_block_html: "%{name} a débloqué l'adresse email avec le hachage %{target}" @@ -268,6 +274,7 @@ fr: destroy_status_html: "%{name} a supprimé le message de %{target}" destroy_unavailable_domain_html: "%{name} a repris la livraison au domaine %{target}" destroy_user_role_html: "%{name} a supprimé le rôle %{target}" + destroy_username_block_html: "%{name} a supprimé une règle pour les noms d'utilisateurs contenant %{target}" disable_2fa_user_html: "%{name} a désactivé l'authentification à deux facteurs pour l'utilisateur·rice %{target}" disable_custom_emoji_html: "%{name} a désactivé l'émoji %{target}" disable_relay_html: "%{name} a désactivé le relais %{target}" @@ -302,6 +309,7 @@ fr: update_report_html: "%{name} a mis à jour le rapport de signalement %{target}" update_status_html: "%{name} a mis à jour le message de %{target}" update_user_role_html: "%{name} a changé le rôle %{target}" + update_username_block_html: "%{name} a mis à jour une règle pour les noms d'utilisateurs contenant %{target}" deleted_account: compte supprimé empty: Aucun journal trouvé. filter_by_action: Filtrer par action @@ -489,9 +497,19 @@ fr: created_at: Créé à delete: Supprimer ip: Adresse IP + request_body: Corps de la demande + title: Déboguer les rappels providers: + active: Activé + base_url: URL de base + callback: Rappel + delete: Supprimer + edit: Modifier un fournisseur + finish_registration: Terminer l'inscription name: Nom providers: Fournisseur + public_key_fingerprint: Empreinte de la clé publique + registration_requested: Inscription demandée registrations: confirm: Confirmer description: Vous avez reçu une souscription d'un FSAF. Rejetez-la si vous ne l'avez pas initiée. Si c'est bien votre intention, comparez le nom et l'empreinte de la clé avant de confirmer la souscription. @@ -502,6 +520,7 @@ fr: sign_in: Se connecter status: État title: Fournisseurs de Services Auxiliaire du Fedivers + title: FASP follow_recommendations: description_html: "Les recommandations d'abonnement aident les nouvelles personnes à trouver rapidement du contenu intéressant. Si un·e utilisateur·rice n'a pas assez interagi avec les autres pour avoir des recommandations personnalisées, ces comptes sont alors recommandés. La sélection est mise à jour quotidiennement depuis un mélange de comptes ayant le plus d'interactions récentes et le plus grand nombre d'abonné·e·s locaux pour une langue donnée." language: Pour la langue @@ -571,6 +590,11 @@ fr: limited: Limité title: Modération moderation_notes: + create: Ajouter une note de modération + created_msg: Note de modération de l'instance créée avec succès ! + description_html: Voir et laisser des notes pour les autres modérateurs et pour référence future + destroyed_msg: Note de modération de l'instance supprimée avec succès ! + placeholder: Informations à propos de cette instance, actions entreprises ou de toute autre chose qui vous aidera à modérer cette instance à l'avenir. title: Notes de modération private_comment: Commentaire privé public_comment: Commentaire public @@ -777,6 +801,8 @@ fr: view_dashboard_description: Permet aux utilisateur⋅rice⋅s d'accéder au tableau de bord et à diverses statistiques view_devops: DevOps view_devops_description: Permet aux utilisateur⋅rice⋅s d'accéder aux tableaux de bord Sidekiq et pgHero + view_feeds: Voir les flux en direct et les fils de discussion + view_feeds_description: Permet aux utilisateur·rice·s d'accéder aux flux en direct et de discussion indépendamment des paramètres du serveur title: Rôles rules: add_new: Ajouter une règle @@ -785,15 +811,21 @@ fr: description_html: Bien que la plupart des gens prétende avoir lu les conditions d'utilisation avant de les accepter, généralement les utilisateur·rice·s ne les lisent vraiment que lorsque un problème apparaît. Pour faciliter la visualisation des règles de votre serveur en un seul coup d’œil, présentez-les sous la forme d'une liste à puces ! Essayez de garder chacune des règles simple et concise, mais faites attention à ne pas non plus les diviser en de trop nombreux éléments distincts. edit: Modifier la règle empty: Aucune règle de serveur n'a été définie pour l'instant. + move_down: Déplacer vers le bas + move_up: Déplacer vers le haut title: Règles du serveur translation: Traduction translations: Traductions + translations_explanation: Vous pouvez éventuellement ajouter des traductions pour les règles. La valeur par défaut sera affichée si aucune version traduite n'est disponible. Veuillez toujours vous assurer que toute traduction fournie est synchronisée avec la valeur par défaut. settings: about: manage_rules: Gérer les règles du serveur preamble: Fournissez des informations détaillées sur le fonctionnement, la modération et le financement du serveur. rules_hint: Il y a un espace dédié pour les règles auxquelles vos utilisateurs sont invités à adhérer. title: À propos + allow_referrer_origin: + desc: Quand les utilisateurs cliquent sur un lien externe, leur navigateur peut envoyer l'adresse du serveur Mastodon en tant qu'adresse d'origine. À désactiver si cela permet d'identifier les utilisateurs, par exemple s'il s'agit d'un serveur Mastodon personnel. + title: Autoriser les sites externes à voir votre serveur Mastodon comme une source de trafic appearance: preamble: Personnaliser l'interface web de Mastodon. title: Apparence @@ -813,17 +845,29 @@ fr: discovery: follow_recommendations: Suivre les recommandations preamble: Il est essentiel de donner de la visibilité à des contenus intéressants pour attirer des utilisateur⋅rice⋅s néophytes qui ne connaissent peut-être personne sur Mastodon. Contrôlez le fonctionnement des différentes fonctionnalités de découverte sur votre serveur. + privacy: Vie privée profile_directory: Annuaire des profils public_timelines: Fils publics publish_statistics: Publier les statistiques title: Découverte trends: Tendances + wrapstodon: Wrapstodon domain_blocks: all: À tout le monde disabled: À personne users: Aux utilisateur·rice·s connecté·e·s localement + feed_access: + modes: + authenticated: Utilisateurs authentifiés uniquement + disabled: Nécessite un rôle spécifique + public: Tout le monde + landing_page: + values: + about: À propos + local_feed: Fil local + trends: Tendances registrations: - moderation_recommandation: Veuillez vous assurer d'avoir une équipe de modération adéquate et réactive avant d'ouvrir les inscriptions à tout le monde! + moderation_recommandation: Veuillez vous assurer d'avoir une équipe de modération adéquate et réactive avant d'ouvrir les inscriptions à tout le monde ! preamble: Affecte qui peut créer un compte sur votre serveur. title: Inscriptions registrations_mode: @@ -875,6 +919,7 @@ fr: no_status_selected: Aucun message n’a été modifié car aucun n’a été sélectionné open: Ouvrir le message original_status: Message original + quotes: Citations reblogs: Partages replied_to_html: Répondu à %{acct_link} status_changed: Message modifié @@ -882,6 +927,7 @@ fr: title: Messages du compte - @%{name} trending: Tendances view_publicly: Afficher de manière publique + view_quoted_post: Voir la publication citée visibility: Visibilité with_media: Avec médias strikes: @@ -899,6 +945,8 @@ fr: system_checks: database_schema_check: message_html: Vous avez des migrations de base de données en attente. Veuillez les exécuter pour vous assurer que l'application se comporte comme prévu + elasticsearch_analysis_index_mismatch: + message_html: Les paramètres de l'analyseur d'index d'Elasticsearch ne sont plus à jour. Veuillez lancer tootctl search deploy --only=%{value} elasticsearch_health_red: message_html: Le cluster Elasticsearch n’est pas sain (statut rouge), les fonctionnalités de recherche ne sont pas disponible elasticsearch_health_yellow: @@ -1063,6 +1111,25 @@ fr: other: Utilisé par %{count} personnes au cours de la dernière semaine title: Recommandations et tendances trending: Tendances + username_blocks: + add_new: Ajouter un nouveau + block_registrations: Bloquer les inscriptions + comparison: + contains: Contient + equals: Est égal à + contains_html: Contient %{string} + created_msg: Règle de nom d'utilisateur créée avec succès + delete: Supprimer + edit: + title: Modifier la règle de nom d'utilisateur + matches_exactly_html: Égal à %{string} + new: + create: Créer une règle + title: Créer une nouvelle règle de nom d'utilisateur + no_username_block_selected: Aucune règle de nom d'utilisateur n'a été modifiée car aucune n'a été sélectionnée + not_permitted: Non autorisé + title: Règles de nom d'utilisateur + updated_msg: Règle de nom d'utilisateur mise à jour warning_presets: add_new: Ajouter un nouveau delete: Supprimer @@ -1135,10 +1202,10 @@ fr: hint_html: Si vous voulez déménager d’un autre compte vers celui-ci, vous pouvez créer ici un alias, qui est nécessaire avant de pouvoir migrer les abonné·e·s de l’ancien compte vers celui-ci. Cette action en soi est inoffensive et réversible. La migration du compte est initiée à partir de l’ancien compte. remove: Détacher l'alias appearance: - advanced_web_interface: Interface web avancée - advanced_web_interface_hint: 'Si vous voulez utiliser toute la largeur de votre écran, l’interface web avancée vous permet de configurer plusieurs colonnes différentes pour voir autant d’informations que vous le souhaitez en même temps : Accueil, notifications, fil fédéré, un nombre illimité de listes et hashtags.' + advanced_settings: Paramètres avancés animations_and_accessibility: Animations et accessibilité - confirmation_dialogs: Dialogues de confirmation + boosting_preferences: Préférences de partage + boosting_preferences_info_html: "Astuce : indépendamment des paramètres, Maj + Clic sur l'icône %{icon} Boost va immédiatement partager." discovery: Découverte localization: body: Mastodon est traduit par des volontaires. @@ -1327,6 +1394,10 @@ fr: basic_information: Informations de base hint_html: "Personnalisez ce que les gens voient sur votre profil public et à côté de vos messages. Les autres personnes seront plus susceptibles de vous suivre et d’interagir avec vous lorsque vous avez un profil complet et une photo." other: Autre + emoji_styles: + auto: Auto + native: Natif + twemoji: Twemoji errors: '400': La demande que vous avez soumise est invalide ou mal formée. '403': Vous n’avez pas accès à cette page. @@ -1536,6 +1607,13 @@ fr: expires_at: Expire uses: Utilisations title: Inviter des gens + link_preview: + author_html: Par %{name} + potentially_sensitive_content: + action: Cliquer pour afficher + confirm_visit: Voulez-vous vraiment ouvrir ce lien ? + hide_button: Masquer + label: Contenu potentiellement sensible lists: errors: limit: Vous avez atteint le nombre maximum de listes @@ -1600,7 +1678,7 @@ fr: disabled_account: Votre compte actuel ne sera pas entièrement utilisable par la suite. Cependant, vous aurez accès à l'exportation de données et à la réactivation. followers: Cette action va déménager tou·te·s les abonné·e·s du compte actuel vers le nouveau compte only_redirect_html: Alternativement, vous pouvez seulement appliquer une redirection sur votre profil. - other_data: Aucune autre donnée ne sera déplacée automatiquement + other_data: Aucune autre donnée ne sera déplacée automatiquement (y compris vos messages et la liste des comptes suivis) redirect: Le profil de votre compte actuel sera mis à jour avec un avis de redirection et sera exclu des recherches moderation: title: Modération @@ -1634,12 +1712,22 @@ fr: body: "%{name} vous a mentionné⋅e dans :" subject: "%{name} vous a mentionné·e" title: Nouvelle mention + moderation_warning: + subject: Vous avez reçu un avertissement de la modération poll: subject: Un sondage de %{name} est terminé + quote: + body: 'Votre message a été cité par %{name}:' + subject: "%{name} a cité votre message" + title: Nouvelle citation + quoted_update: + subject: "%{name} a modifié un message que vous avez cité" reblog: body: 'Votre message été partagé par %{name} :' subject: "%{name} a partagé votre message" title: Nouveau partage + severed_relationships: + subject: Vous avez perdu des relations à cause d’une décision de modération status: subject: "%{name} vient de publier" update: @@ -1684,6 +1772,9 @@ fr: self_vote: Vous ne pouvez pas voter à vos propres sondages too_few_options: doit avoir plus qu’une proposition too_many_options: ne peut contenir plus de %{max} propositions + vote: Voter + posting_defaults: + explanation: Ces paramètres seront utilisés par défaut lorsque vous créez de nouveaux messages, mais vous pouvez les modifier par message dans le champs de rédaction. preferences: other: Autre posting_defaults: Paramètres de publication par défaut @@ -1839,6 +1930,9 @@ fr: other: "%{count} vidéos" boosted_from_html: Partagé depuis %{acct_link} content_warning: 'Avertissement de contenu : %{warning}' + content_warnings: + hide: Masquer le message + show: Afficher plus default_language: Même langue que celle de l’interface disallowed_hashtags: one: 'contient un hashtag désactivé : %{tags}' @@ -1846,15 +1940,31 @@ fr: edited_at_html: Modifié le %{date} errors: in_reply_not_found: Le message auquel vous essayez de répondre ne semble pas exister. + quoted_status_not_found: Le message que vous essayez de citer ne semble pas exister. + quoted_user_not_mentioned: Impossible de citer un compte non mentionné dans une mention privée. over_character_limit: limite de %{max} caractères dépassée pin_errors: direct: Les messages qui ne sont visibles que pour les utilisateur·rice·s mentionné·e·s ne peuvent pas être épinglés limit: Vous avez déjà épinglé le nombre maximum de messages ownership: Vous ne pouvez pas épingler un message ne vous appartenant pas reblog: Un partage ne peut pas être épinglé + quote_error: + not_available: Message indisponible + pending_approval: Message en attente + revoked: Message supprimé par l'auteur + quote_policies: + followers: Abonné·e·s seulement + nobody: Moi uniquement + public: Tout le monde + quote_post_author: A cité un message de %{acct} title: "%{name} : « %{quote} »" visibilities: + direct: Mention privée + private: Abonné·e·s seulement public: Publique + public_long: Tout le monde sur et en dehors de Mastodon + unlisted: Public discret + unlisted_long: Caché des résultats de recherche, des tendances et des fils d'actualité publics statuses_cleanup: enabled: Supprimer automatiquement vos anciens messages enabled_hint: Supprime automatiquement vos messages une fois qu'ils ont atteint un seuil d'ancienneté défini, à moins qu'ils ne correspondent à l'une des exceptions ci-dessous @@ -1900,6 +2010,9 @@ fr: terms_of_service: title: Conditions d'utilisation terms_of_service_interstitial: + future_preamble_html: Nous avons modifié nos conditions d'utilisation, ces changements entreront en vigueur le %{date}. Nous vous invitons à prendre connaissance des nouvelles conditions. + past_preamble_html: Nous avons modifié nos conditions d'utilisation depuis votre dernière visite. Nous vous invitons à prendre connaissance des nouvelles conditions. + review_link: Vérifier les conditions d'utilisation title: Les conditions d'utilisation de %{domain} ont changées themes: contrast: Mastodon (Contraste élevé) @@ -2078,3 +2191,6 @@ fr: not_supported: Ce navigateur ne prend pas en charge les clés de sécurité otp_required: Pour utiliser les clés de sécurité, veuillez d'abord activer l'authentification à deux facteurs. registered_on: Inscrit le %{date} + wrapstodon: + description: Voir comment %{name} a utilisé Mastodon cette année ! + title: Wrapstodon %{year} pour %{name} diff --git a/config/locales/fy.yml b/config/locales/fy.yml index c631241a3192af..f96e0f92ecfc4f 100644 --- a/config/locales/fy.yml +++ b/config/locales/fy.yml @@ -837,7 +837,6 @@ fy: title: Brûkers standert net troch sykmasinen yndeksearje litte discovery: follow_recommendations: Oanrekommandearre accounts - preamble: It toanen fan ynteressante ynhâld is fan essinsjeel belang foar it oan board heljen fan nije brûkers dy’t mooglik net ien fan Mastodon kinne. Bepaal hoe’t ferskate funksjes foar it ûntdekken fan ynhâld en brûkers op jo server wurkje. privacy: Privacy profile_directory: Profylmap public_timelines: Iepenbiere tiidlinen @@ -1182,10 +1181,7 @@ fy: hint_html: Wannear’t jo fan in oare account ôf nei dizze account ferhúzje wolle, kinne jo hjir in alias oanmeitsje. Dit is nedich eardat jo troch gean kinne mei it ferhúzjen fan folgers fan de âlde nei dizze nije account. Dizze aksje is op eins net gefaarlik en omkearber. De accountmigraasje wurdt start fan de âlde account ôf. remove: Alias ûntkeppelje appearance: - advanced_web_interface: Avansearre webomjouwing - advanced_web_interface_hint: 'Wannear’t jo fan de hiele skermbreedte gebrûk meitsje wolle, stelt de avansearre webomjouwing jo yn steat om meardere ferskate kolommen te konfigurearjen. Hjirmei kinne jo safolle mooglik ynformaasje op itselde momint besjen, lykas: Start, meldingen, de globale tiidline, meardere listen en hashtags.' animations_and_accessibility: Animaasjes en tagonklikheid - confirmation_dialogs: Befêstigingen discovery: Untdekke localization: body: Mastodon wurdt troch frijwilligers oerset. @@ -1651,7 +1647,6 @@ fy: disabled_account: Jo aktuele account is hjirnei net mear folslein brûkber. Jo hawwe echter wol tagong ta it eksportearjen fan jo gegevens en ta it opnij aktivearjen fan jo account. followers: Dizze aksje ferhuzet alle folgers fan de aktuele account ôf nei de nije account only_redirect_html: Jo kinne as alternatyf ek allinnich de trochferwizing op jo profyl sette. - other_data: Oare gegevens wurde net automatysk ferhuze redirect: Jo aktuele accountprofyl wurdt bywurke mei in trochferwizingsmelding en wurdt útsluten fan sykresultaten moderation: title: Moderaasje diff --git a/config/locales/ga.yml b/config/locales/ga.yml index adfdacb034be5e..acba6eab84944c 100644 --- a/config/locales/ga.yml +++ b/config/locales/ga.yml @@ -7,6 +7,8 @@ ga: hosted_on: Mastodon arna óstáil ar %{domain} title: Maidir le accounts: + errors: + cannot_be_added_to_collections: Ní féidir an cuntas seo a chur le bailiúcháin. followers: few: Leantóirí many: Leantóirí @@ -838,6 +840,8 @@ ga: view_dashboard_description: Ligeann sé d’úsáideoirí rochtain a fháil ar an deais agus ar mhéadrachtaí éagsúla view_devops: DevOps view_devops_description: Ligeann sé d’úsáideoirí rochtain a fháil ar dheais Sidekiq agus pgHero + view_feeds: Féach ar fhothaí beo agus topaicí + view_feeds_description: Ceadaíonn sé d’úsáideoirí rochtain a fháil ar na fothaí beo agus topaicí beag beann ar shocruithe an fhreastalaí title: Róil rules: add_new: Cruthaigh riail @@ -879,17 +883,28 @@ ga: title: Diúltaigh d'innéacsú inneall cuardaigh mar réamhshocrú d'úsáideoirí discovery: follow_recommendations: Lean na moltaí - preamble: Tá sé ríthábhachtach dromchla a chur ar ábhar suimiúil chun úsáideoirí nua a chur ar bord nach bhfuil aithne acu ar dhuine ar bith Mastodon. Rialú conas a oibríonn gnéithe fionnachtana éagsúla ar do fhreastalaí. + preamble: Tá sé ríthábhachtach ábhar suimiúil a chur chun cinn chun úsáideoirí nua a thabhairt isteach ar Mastodon, úsáideoirí nach mbeadh aithne acu ar aon duine. Rialú conas a oibríonn gnéithe éagsúla fionnachtana ar do fhreastalaí. privacy: Príobháideacht profile_directory: Eolaire próifíle public_timelines: Amlínte poiblí publish_statistics: Staitisticí a fhoilsiú title: Fionnachtain trends: Treochtaí + wrapstodon: Wrapstodon domain_blocks: all: Do chách disabled: Do dhuine ar bith users: Chun úsáideoirí áitiúla logáilte isteach + feed_access: + modes: + authenticated: Úsáideoirí fíordheimhnithe amháin + disabled: Éiligh ról úsáideora sonrach + public: Gach duine + landing_page: + values: + about: Maidir + local_feed: Fotha áitiúil + trends: Treochtaí registrations: moderation_recommandation: Cinntigh le do thoil go bhfuil foireann mhodhnóireachta imoibríoch leordhóthanach agat sula n-osclaíonn tú clárúcháin do gach duine! preamble: Rialú cé atá in ann cuntas a chruthú ar do fhreastalaí. @@ -943,6 +958,7 @@ ga: no_status_selected: Níor athraíodh aon phostáil mar níor roghnaíodh ceann ar bith open: Oscail postáil original_status: Bunphostáil + quotes: Sleachta reblogs: Athbhlaganna replied_to_html: D'fhreagair %{acct_link} status_changed: Athraíodh postáil @@ -950,6 +966,7 @@ ga: title: Postálacha cuntais - @%{name} trending: Ag treochtáil view_publicly: Féach go poiblí + view_quoted_post: Féach ar an bpost atá luaite visibility: Infheictheacht with_media: Le meáin strikes: @@ -1239,10 +1256,12 @@ ga: hint_html: Más mian leat bogadh ó chuntas eile go dtí an ceann seo, anseo is féidir leat ailias a chruthú, a theastaíonn sular féidir leat leanúint ar aghaidh le leantóirí a bhogadh ón seanchuntas go dtí an ceann seo. Tá an gníomh seo ann féin neamhdhíobhálach agus inchúlaithe. Cuirtear tús leis an aistriú cuntais ón seanchuntas. remove: Dícheangail ailias appearance: - advanced_web_interface: Comhéadan gréasáin chun cinn - advanced_web_interface_hint: 'Más mian leat úsáid a bhaint as do leithead scáileáin ar fad, ceadaíonn an comhéadan gréasáin ardleibhéil duit go leor colúin éagsúla a chumrú chun an oiread faisnéise a fheiceáil ag an am céanna agus is mian leat: Baile, fógraí, amlíne chónaidhme, aon líon liostaí agus hashtags.' + advanced_settings: Socruithe ardleibhéil animations_and_accessibility: Beochan agus inrochtaineacht - confirmation_dialogs: Dialóga deimhnithe + boosting_preferences: Roghanna á bhfeabhsú + boosting_preferences_info_html: |- + Leid: Beag beann ar shocruithe, Shift + + Cliceáil ar an deilbhín %{icon} Tabharfaidh an deilbhín borradh láithreach. discovery: Fionnachtain localization: body: Oibrithe deonacha a dhéanann aistriúchán Mastodon. @@ -1704,6 +1723,13 @@ ga: expires_at: In éag uses: Úsáidí title: Tabhair cuireadh do dhaoine + link_preview: + author_html: Le %{name} + potentially_sensitive_content: + action: Cliceáil chun a thaispeáint + confirm_visit: An bhfuil tú cinnte gur mian leat an nasc seo a oscailt? + hide_button: Folaigh + label: Ábhar a d'fhéadfadh a bheith íogair lists: errors: limit: Tá uaslíon na liostaí sroichte agat @@ -1768,7 +1794,7 @@ ga: disabled_account: Ní bheidh do chuntas reatha inúsáidte go hiomlán ina dhiaidh sin. Mar sin féin, beidh rochtain agat ar onnmhairiú sonraí chomh maith le hathghníomhú. followers: Bogfaidh an gníomh seo gach leantóir ón gcuntas reatha go dtí an cuntas nua only_redirect_html: Nó, ní féidir leat ach atreorú a chur suas ar do phróifíl. - other_data: Ní bhogfar aon sonraí eile go huathoibríoch + other_data: Ní bhogfar aon sonraí eile go huathoibríoch (lena n-áirítear do phoist agus an liosta cuntas a leanann tú) redirect: Déanfar próifíl do chuntais reatha a nuashonrú le fógra atreoraithe agus fágfar as an áireamh é ó chuardaigh moderation: title: Measarthacht @@ -1802,16 +1828,22 @@ ga: body: 'Luaigh %{name} thú i:' subject: Luaigh %{name} thú title: Lua nua + moderation_warning: + subject: Tá rabhadh modhnóireachta faighte agat poll: subject: Tháinig deireadh le vótaíocht le %{name} quote: body: 'Luaigh %{name} do phost:' subject: Luaigh %{name} do phost title: Luachan nua + quoted_update: + subject: Chuir %{name} post in eagar a luaigh tú reblog: body: 'Treisíodh do phostáil le %{name}:' subject: Mhol %{name} do phostáil title: Moladh nua + severed_relationships: + subject: Tá naisc caillte agat mar gheall ar chinneadh modhnóireachta status: subject: Tá %{name} díreach postáilte update: @@ -2023,6 +2055,9 @@ ga: two: "%{count} físeáin" boosted_from_html: Molta ó %{acct_link} content_warning: 'Rabhadh ábhair: %{warning}' + content_warnings: + hide: Folaigh an post + show: Taispeáin níos mó default_language: Mar an gcéanna le teanga an chomhéadain disallowed_hashtags: few: 'bhí na Haischlib dícheadaithe: %{tags}' @@ -2034,16 +2069,22 @@ ga: errors: in_reply_not_found: Is cosúil nach ann don phostáil a bhfuil tú ag iarraidh freagra a thabhairt air. quoted_status_not_found: Is cosúil nach bhfuil an post atá tú ag iarraidh a lua ann. + quoted_user_not_mentioned: Ní féidir úsáideoir nach luaitear a lua i bpost Lua Príobháideach. over_character_limit: teorainn carachtar %{max} sáraithe pin_errors: direct: Ní féidir postálacha nach bhfuil le feiceáil ach ag úsáideoirí luaite a phinnáil limit: Tá uaslíon na bpostálacha pinn agat cheana féin ownership: Ní féidir postáil duine éigin eile a phionnáil reblog: Ní féidir treisiú a phinnáil + quote_error: + not_available: Níl an postáil ar fáil + pending_approval: Post ar feitheamh + revoked: Post bainte ag an údar quote_policies: followers: Leantóirí amháin nobody: Mise amháin public: Aon duine + quote_post_author: Luaigh mé post le %{acct} title: '%{name}: "%{quote}"' visibilities: direct: Lua príobháideach @@ -2281,3 +2322,6 @@ ga: not_supported: Ní thacaíonn an brabhsálaí seo le heochracha slándála otp_required: Chun eochracha slándála a úsáid cumasaigh fíordheimhniú dhá fhachtóir ar dtús. registered_on: Cláraithe ar %{date} + wrapstodon: + description: Féach conas a d'úsáid %{name} Mastodon i mbliana! + title: Wrapstodon %{year} do %{name} diff --git a/config/locales/gd.yml b/config/locales/gd.yml index 0720cf964a289a..ce62e5261faf71 100644 --- a/config/locales/gd.yml +++ b/config/locales/gd.yml @@ -824,6 +824,8 @@ gd: view_dashboard_description: Leigidh seo le cleachdaichean an deas-bhòrd agus meatrachdan inntrigeadh view_devops: DevOps view_devops_description: Leigidh seo le cleachdaichean na deas-bhùird aig Sidekiq is pgHero inntrigeadh + view_feeds: Seall loidhnichean-ama beòtha ’s nan cuspairean + view_feeds_description: Leigidh seo le cleachdaichean loidhnichean-ama beòtha ’s nan cuspairean inntrigeadh ge b’ e dè roghainnean an fhrithealaiche title: Dreuchdan rules: add_new: Cuir riaghailt ris @@ -876,6 +878,16 @@ gd: all: Dhan a h-uile duine disabled: Na seall idir users: Dhan luchd-chleachdaidh a clàraich a-steach gu h-ionadail + feed_access: + modes: + authenticated: Cleachdaichean a chlàraich a-steach a-mhàin + disabled: Iarr dreuchd shònraichte a’ chleachdaiche + public: A h-uile duine + landing_page: + values: + about: Mu dhèidhinn + local_feed: Loidhne-ama ionadail + trends: Treandaichean registrations: moderation_recommandation: Dèan cinnteach gu bheil sgioba maoir deiseil is deònach agad mus fhosgail thu an clàradh dhan a h-uile duine! preamble: Stiùirich cò dh’fhaodas cunntas a chruthachadh air an fhrithealaiche agad. @@ -929,6 +941,7 @@ gd: no_status_selected: Cha deach post sam bith atharrachadh o nach deach gin dhiubh a thaghadh open: Fosgail am post original_status: Am post tùsail + quotes: Luaidhean reblogs: Brosnachaidhean replied_to_html: Freagairt do %{acct_link} status_changed: Post air atharrachadh @@ -936,6 +949,7 @@ gd: title: Postaichean a’ chunntais – @%{name} trending: A’ treandadh view_publicly: Seall gu poblach + view_quoted_post: Seall am post ’ga luaidh visibility: Faicsinneachd with_media: Le meadhanan riutha strikes: @@ -1220,10 +1234,10 @@ gd: hint_html: Nam bu mhiann leat imrich o chunntas eile dhan fhear seo, ’s urrainn dhut alias a chruthachadh an-seo agus feumaidh tu sin a dhèanamh mus urrainn dhut tòiseachadh air an luchd-leantainn agad imrich on seann-chunntas dhan fhear seo. Tha an gnìomh seo fhèin neo-chronail is chan eil e buan. Tòisichidh tu air imrich a’ chunntais on t-seann-chunntas. remove: Dì-cheangail an t-alias appearance: - advanced_web_interface: Eadar-aghaidh-lìn adhartach - advanced_web_interface_hint: 'Ma tha thu airson leud na sgrìn agad gu lèir a chleachdadh, leigidh an eadar-aghaidh-lìn adhartach leat mòran cholbhan eadar-dhealaichte a cho-rèiteachadh airson uiread de dh''fhiosrachadh ''s a thogras tu fhaicinn aig an aon àm: Dachaigh, brathan, loidhne-ama cho-naisgte, liostaichean is tagaichean hais a rèir do thoil.' + advanced_settings: Roghainnean adhartach animations_and_accessibility: Beòthachaidhean agus so-ruigsinneachd - confirmation_dialogs: Còmhraidhean dearbhaidh + boosting_preferences: Roghainnean brosnachaidh + boosting_preferences_info_html: "Gliocas: Ge b’ e dè na roghainnean, ma nì thuShift + Briogadh air ìomhaigheag%{icon} a’ bhrosnachaidh, thèid a bhrosnachadh sa bhad." discovery: Rùrachadh localization: body: Tha Mastodon ’ga eadar-theangachadh le saor-thoilich. @@ -1566,10 +1580,10 @@ gd: other: Tha thu an impis suas ri %{count} àrainn o %{filename} a chur an àite liosta nam bacaidhean àrainne agad. two: Tha thu an impis suas ri %{count} àrainn o %{filename} a chur an àite liosta nam bacaidhean àrainne agad. following_html: - few: Tha thu an impis suas ri %{count} cunntasan o %{filename} a leantainn agus sguiridh tu a leantainn duine sam bith eile. - one: Tha thu an impis suas ri %{count} chunntas o %{filename} a leantainn agus sguiridh tu a leantainn duine sam bith eile. - other: Tha thu an impis suas ri %{count} cunntas o %{filename} a leantainn agus sguiridh tu a leantainn duine sam bith eile. - two: Tha thu an impis suas ri %{count} chunntas o %{filename} a leantainn agus sguiridh tu a leantainn duine sam bith eile. + few: Tha thu an impis suas ri %{count} cunntasan o %{filename} a leantainn agus sguiridh tu a leantainn neach sam bith eile. + one: Tha thu an impis suas ri %{count} chunntas o %{filename} a leantainn agus sguiridh tu a leantainn neach sam bith eile. + other: Tha thu an impis suas ri %{count} cunntas o %{filename} a leantainn agus sguiridh tu a leantainn neach sam bith eile. + two: Tha thu an impis suas ri %{count} chunntas o %{filename} a leantainn agus sguiridh tu a leantainn neach sam bith eile. lists_html: few: Tha thu an impis susbaint %{filename} a chur an àite nan liostaichean agad. Thèid suas ri %{count} cunntasan a chur ri liostaichean ùra. one: Tha thu an impis susbaint %{filename} a chur an àite nan liostaichean agad. Thèid suas ri %{count} chunntas a chur ri liostaichean ùra. @@ -1665,6 +1679,13 @@ gd: expires_at: Falbhaidh an ùine air uses: Cleachdadh title: Thoir cuireadh do dhaoine + link_preview: + author_html: Le %{name} + potentially_sensitive_content: + action: Dèan briogadh gus a shealltainn + confirm_visit: A bheil thu cinnteach gu bheil thu airson an ceangal seo fhosgladh? + hide_button: Falaich + label: Susbaint fhrionasach ma dh’fhaoidte lists: errors: limit: Ràinig thu na tha ceadaichte dhut de liostaichean @@ -1729,7 +1750,7 @@ gd: disabled_account: Cha ghabh an cunntas làithreach agad a chleachdadh gu slàn às a dhèidh. Gidheadh, bidh an dà chuid às-phortadh an dàta is ath-ghnìomhachadh ri fhaighinn dhut. followers: Imrichidh an gnìomh seo a h-uile neach-leantainn on chunntas làithreach dhan chunntas ùr only_redirect_html: Mar roghainn eile, ’s urrainn dhut ath-stiùireadh a-mhàin a chur air a’ phròifil agad. - other_data: Cha dèid dàta sam bith eile imrich gu fèin-obrachail + other_data: Cha dèid dàta sam bith eile imrich gu fèin-obrachail (a’ gabhail a-staigh nam postaichean ’s liosta nan cunntasan a leanas tu) redirect: Thèid pròifil a’ chunntais làithrich agad ùrachadh le brath ath-stiùiridh agus às-dhùnadh on lorg moderation: title: Maorsainneachd @@ -1981,6 +2002,9 @@ gd: two: "%{count} video" boosted_from_html: Brosnachadh o %{acct_link} content_warning: 'Rabhadh susbainte: %{warning}' + content_warnings: + hide: Falaich am post + show: Seall barrachd dheth default_language: Co-ionnan ri cànan na h-eadar-aghaidh disallowed_hashtags: few: "– bha na tagaichean hais toirmisgte seo ann: %{tags}" @@ -1991,23 +2015,29 @@ gd: errors: in_reply_not_found: Tha coltas nach eil am post dhan a tha thu airson freagairt ann. quoted_status_not_found: Tha coltas nach eil am post dhan a tha thu airson luaidh ann. + quoted_user_not_mentioned: Chan urrainn dhut neach gun iomradh air a luaidh ann am post a tha ’na iomradh prìobhaideach. over_character_limit: chaidh thu thar crìoch charactaran de %{max} pin_errors: direct: Chan urrainn dhut post a phrìneachadh nach fhaic ach na cleachdaichean le iomradh orra limit: Tha an àireamh as motha de phostaichean prìnichte agad a tha ceadaichte ownership: Chan urrainn dhut post càich a phrìneachadh reblog: Chan urrainn dhut brosnachadh a phrìneachadh + quote_error: + not_available: Chan eil am post ri fhaighinn + pending_approval: Cha deach dèiligeadh ris a’ phost fhathast + revoked: Chaidh am post a thoirt air falbh leis an ùghdar quote_policies: followers: Luchd-leantainn a-mhàin nobody: Mi fhìn a-mhàin - public: Duine sam bith + public: Neach sam bith + quote_post_author: Luaidh air post le %{acct} title: "%{name}: “%{quote}”" visibilities: direct: Iomradh prìobhaideach private: Luchd-leantainn a-mhàin public: Poblach - public_long: Duine sam bith taobh a-staigh no a-muigh Mhastodon - unlisted: Poblach ach sàmhach + public_long: Neach sam bith taobh a-staigh no a-muigh Mhastodon + unlisted: Sàmhach unlisted_long: Falaichte o na toraidhean-luirg, na treandaichean ’s na loichnichean-ama poblach statuses_cleanup: enabled: Sguab às seann-phostaichean gu fèin-obrachail diff --git a/config/locales/gl.yml b/config/locales/gl.yml index 4eee0fe0f981cb..c465f3fd87d2c8 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -7,6 +7,8 @@ gl: hosted_on: Mastodon aloxado en %{domain} title: Sobre accounts: + errors: + cannot_be_added_to_collections: Non se pode engadir esta conta ás coleccións. followers: one: Seguidora other: Seguidoras @@ -796,6 +798,8 @@ gl: view_dashboard_description: Permite acceder ao taboleiro e varias métricas do servidor view_devops: DevOps view_devops_description: Permite acceder aos taboleiros Sidekiq e phHero + view_feeds: Ver as canles do directo e temas + view_feeds_description: Permite ás usuarias acceder ás canles de directo e temas independentemento dos axustes do servidor. title: Roles rules: add_new: Engadir regra @@ -844,10 +848,21 @@ gl: publish_statistics: Publicar estatísticas title: Descubrir trends: Tendencias + wrapstodon: Wrapstodon domain_blocks: all: Para todos disabled: Para ninguén users: Para usuarias locais conectadas + feed_access: + modes: + authenticated: Só para usuarias con sesión iniciada + disabled: Requerir un rol da usuaria específico + public: Para calquera + landing_page: + values: + about: Sobre + local_feed: Cronoloxía Local + trends: Tendencias registrations: moderation_recommandation: Por favor, pon interese en crear un equipo de moderación competente e reactivo antes de permitir que calquera poida crear unha conta! preamble: Xestiona quen pode crear unha conta no teu servidor. @@ -901,6 +916,7 @@ gl: no_status_selected: Non se cambiou ningunha publicación xa que ningunha foi seleccionada open: Abrir publicación original_status: Publicación orixinal + quotes: Citas reblogs: Promocións replied_to_html: Respondeu a %{acct_link} status_changed: Publicación editada @@ -908,6 +924,7 @@ gl: title: Publicacións da conta - @%{name} trending: Popular view_publicly: Ver publicamente + view_quoted_post: Ver publicación citada visibility: Visibilidade with_media: con medios strikes: @@ -1182,10 +1199,10 @@ gl: hint_html: Se queres mudarte desde outra conta a esta nova, aquí podes crear un alcume, que é requerido antes de poder proceder a mover os seguidores da conta antiga a esta nova. Esta acción por si mesma é inocua e reversible. A migración da conta iníciase desde a conta antiga. remove: Desligar alcume appearance: - advanced_web_interface: Interface web avanzada - advanced_web_interface_hint: Se queres empregar todo o ancho da pantalla, a interface web avanzada permíteche configurar diferentes columnas para ver tanta información como queiras. Inicio, notificacións, cronoloxía federada, varias listaxes e cancelos. + advanced_settings: Axustes avanzados animations_and_accessibility: Animacións e accesibilidade - confirmation_dialogs: Diálogos de confirmación + boosting_preferences: Preferencias das promocións + boosting_preferences_info_html: "Info: Independentemente dos axustes, Maiús + Click na icona %{icon} Promover promoverá inmediatamente." discovery: Descubrir localization: body: Mastodon tradúceno persoas voluntarias. @@ -1587,6 +1604,13 @@ gl: expires_at: Caduca uses: Usos title: Convidar a persoas + link_preview: + author_html: De %{name} + potentially_sensitive_content: + action: Preme para ver + confirm_visit: Tes certeza de querer abrir esta ligazón? + hide_button: Ocultar + label: Posible contido sensible lists: errors: limit: Xa acadaches o número máximo de listas @@ -1651,7 +1675,7 @@ gl: disabled_account: Tras o cambio a túa conta actual non será totalmente usable, pero terás acceso a exportar os datos e tamén a reactivación. followers: Esta acción moverá todas as túas seguidoras desde a conta actual a nova conta only_redirect_html: De xeito alternativo, podes simplemente pór unha redirección no perfil. - other_data: Non se moverán outros datos de xeito automático + other_data: Ningún outro dato se moverá de xeito automático (incluíndo as túas publicacións e a lista das contas que segues) redirect: O perfil da túa conta actualizarase cun aviso de redirección e será excluído das buscas moderation: title: Moderación @@ -1685,16 +1709,22 @@ gl: body: "%{name} mencionoute en:" subject: Foches mencionada por %{name} title: Nova mención + moderation_warning: + subject: Recibiches unha advertencia da moderación poll: subject: A enquisa de %{name} rematou quote: body: 'A túa publicación foi citada por %{name}:' subject: "%{name} citou a túa publicación" title: Nova cita + quoted_update: + subject: "%{name} editou unha publicación que citaches" reblog: body: 'A túa publicación promovida por %{name}:' subject: "%{name} promoveu a túa publicación" title: Nova promoción + severed_relationships: + subject: Perdeches algunhas relacións debido a decisións da moderación status: subject: "%{name} publicou" update: @@ -1897,6 +1927,9 @@ gl: other: "%{count} vídeos" boosted_from_html: Promovida desde %{acct_link} content_warning: 'Aviso sobre o contido: %{warning}' + content_warnings: + hide: Ocultar publicación + show: Ver máis default_language: Igual que o idioma da interface disallowed_hashtags: one: 'contiña un cancelo non permitido: %{tags}' @@ -1905,16 +1938,22 @@ gl: errors: in_reply_not_found: A publicación á que tentas responder semella que non existe. quoted_status_not_found: Parece que a publicación que intentas citar non existe. + quoted_user_not_mentioned: Non se pode citar a unha usuaria non mencionada nunha Mención Privada. over_character_limit: Excedeu o límite de caracteres %{max} pin_errors: direct: As publicacións que só son visibles para as usuarias mencionadas non se poden fixar limit: Xa fixaches o número máximo permitido de publicacións ownership: Non podes fixar a publicación doutra usuaria reblog: Non se poden fixar as mensaxes promovidas + quote_error: + not_available: Publicación non dispoñible + pending_approval: Publicación pendente + revoked: Publicación retirada pola autora quote_policies: followers: Só para seguidoras nobody: Só para min public: Calquera + quote_post_author: Citou unha publicación de %{acct} title: '%{name}: "%{quote}"' visibilities: direct: Mención privada @@ -2086,7 +2125,7 @@ gl: edit_profile_action: Personalizar edit_profile_step: Aumenta as túas interaccións grazas a un perfil descriptivo. edit_profile_title: Personaliza o teu perfil - explanation: Aquí tes algunhas endereitas para ir aprendendo + explanation: Aquí tes algúns consellos para ir aprendendo feature_action: Saber máis feature_audience: Mastodon dache a oportunidade de xestionar sen intermediarios as túas relacións. Incluso se usas o teu propio servidor Mastodon poderás seguir e ser seguida desde calquera outro servidor Mastodon conectado á rede e estará baixo o teu control exclusivo. feature_audience_title: Crea a túa audiencia con tranquilidade @@ -2149,3 +2188,6 @@ gl: not_supported: Este navegador non ten soporte para chaves de seguridade otp_required: Para usar chaves de seguridade tes que activar primeiro o segundo factor. registered_on: Rexistrado o %{date} + wrapstodon: + description: Mira como usou %{name} Mastodon este ano! + title: Wrapstodon %{year} de %{name} diff --git a/config/locales/he.yml b/config/locales/he.yml index 9c8d91722ab71e..f22d5d96074430 100644 --- a/config/locales/he.yml +++ b/config/locales/he.yml @@ -7,6 +7,8 @@ he: hosted_on: מסטודון שיושב בכתובת %{domain} title: אודות accounts: + errors: + cannot_be_added_to_collections: לא ניתן להוסיף חשבון זה לאוספים. followers: many: עוקבים one: עוקב @@ -824,6 +826,8 @@ he: view_dashboard_description: אפשר למשתמשים לגשת ללוח המחוונים view_devops: DevOps view_devops_description: מאפשר למשתמשים לגשת ללוחות המחוונים של Sidekiq ושל pgHero + view_feeds: צפיה בפיד החי ונושאים + view_feeds_description: מאפשר למשמתמשיםות גישה לפיד החי ופיד נושאים בלי קשר להגדרות השרת title: תפקידים rules: add_new: הוספת כלל @@ -872,10 +876,21 @@ he: publish_statistics: פרסום הסטטיסטיקות בפומבי title: תגליות trends: נושאים חמים + wrapstodon: סיכומודון domain_blocks: all: לכולם disabled: לאף אחד users: למשתמשים מקומיים מחוברים + feed_access: + modes: + authenticated: משתמשים מאומתים בלבד + disabled: נדרש תפקיד משתמש מסוים + public: כולם + landing_page: + values: + about: אודות + local_feed: פיד מקומי + trends: נושאים חמים registrations: moderation_recommandation: יש לוודא שלאתר יש צוות מנחות ומנחי שיחה מספק ושירותי בטרם תבחרו לפתוח הרשמה לכולם! preamble: שליטה בהרשאות יצירת חשבון בשרת שלך. @@ -929,6 +944,7 @@ he: no_status_selected: לא בוצעו שינויים בהודעות שכן לא נבחרו כאלו open: פתח הודעה original_status: הודעה מקורית + quotes: ציטוטים reblogs: שיתופים replied_to_html: בתגובה לחשבון %{acct_link} status_changed: הודעה שונתה @@ -936,6 +952,7 @@ he: title: פרסומי החשבון - @%{name} trending: נושאים חמים view_publicly: צפיה בפומבי + view_quoted_post: צפיה בהודעה המצוטטת visibility: חשיפה with_media: עם מדיה strikes: @@ -1220,10 +1237,10 @@ he: hint_html: אם ברצונך לעבור מחשבון אחר לחשבון הזה, כאן ניתן ליצור שם נרדף, הנדרש לפני שאפשר יהיה להמשיך עם העברת עוקבים מהחשבון הישן לזה. הפעולה עצמה הפיכה ובלתי מזיקה. הגירת החשבון מופעלת מהחשבון הישן. remove: הסרת שם נרדף appearance: - advanced_web_interface: ממשק ווב מתקדם - advanced_web_interface_hint: 'אם ברצונך לעשות שימוש במלוא רוחב המסך, ממשק הווב המתקדם מאפשר לך להגדיר עמודות רבות ושונות כדי לראות בו זמנית כמה מידע שתרצה/י: פיד הבית, התראות, פרהסיה ומספר כלשהו של רשימות ותגיות.' + advanced_settings: הגדרות מתקדמות animations_and_accessibility: הנפשות ונגישות - confirmation_dialogs: חלונות אישור + boosting_preferences: העדפות הדהודים + boosting_preferences_info_html: "עצה: ללא קשר לבחירה, שיפט + קליק על %{icon} איקון ההדהוד ירשם כהדהוד מיידי." discovery: תגליות localization: body: מסטודון מתורגם על ידי מתנדבים. @@ -1665,6 +1682,13 @@ he: expires_at: פג תוקף ב- uses: שימושים title: הזמנת אנשים + link_preview: + author_html: מאת %{name} + potentially_sensitive_content: + action: לחץ להצגה + confirm_visit: האם להמשיך ליעד הקישור? + hide_button: להסתיר + label: תוכן שעלול להיות רגיש lists: errors: limit: הגעת למספר הרשימות המירבי @@ -1729,7 +1753,7 @@ he: disabled_account: חשבונך הנוכחי לא יהיה שמיש לחלוטין לאחר מכן. עם זאת, ניתן יהיה לגשת ליצוא המידע, וכמו כן להפעילו מחדש. followers: פעולה זו תעביר את כל העוקבים מהחשבון הנוכחי לחשבון החדש only_redirect_html: לחילופין, ניתן להסתפק בהכוונה מחדש בפרופילך. - other_data: שום מידע לא יועבר אוטומטית + other_data: אף מידע נוסף לא יועבר אוטומטית (ובכלל זה הודעותיך ורשימת נעקביך) redirect: פרופיל חשבונך הנוכחי יעודכן עם הודעת הכוונה מחדש ויוחרג מחיפושים moderation: title: ניהול קהילה @@ -1763,16 +1787,22 @@ he: body: 'התקבלה פניה עבורך מאת %{name} ב:' subject: התקבלה פניה עבורך מאת %{name} title: אזכור חדש + moderation_warning: + subject: קיבלת אזהרה מצוות ניהול התוכן poll: subject: סקר מאת %{name} הסתיים quote: body: 'הודעתך צוטטה על ידי %{name}:' subject: "%{name} ציטט.ה את הודעתך" title: ציטוט חדש + quoted_update: + subject: "%{name} ערך הודעה שהשתמשת בה בציטוט" reblog: body: 'הודעתך הודהדה על ידי %{name}:' subject: הודעתך הודהדה על ידי%{name} title: הדהוד חדש + severed_relationships: + subject: איבדת קשרים עם משתמשים אחרים עקב החלטת צוות ניהול התוכן status: subject: "%{name} בדיוק פרסם" update: @@ -1981,6 +2011,9 @@ he: two: "%{count} סרטונים" boosted_from_html: הודהד מ-%{acct_link} content_warning: 'אזהרת תוכן: %{warning}' + content_warnings: + hide: להסתיר הודעה + show: הצג עוד default_language: זהה לשפת ממשק disallowed_hashtags: many: 'מכיל את התגיות האסורות: %{tags}' @@ -1991,16 +2024,22 @@ he: errors: in_reply_not_found: נראה שההודעה שנסית להגיב לה לא קיימת. quoted_status_not_found: נראה שההודעה שנסית לצטט לא קיימת. + quoted_user_not_mentioned: לא ניתן לצטט משתמש שאיננו מאוזכר בהודעה פרטית. over_character_limit: חריגה מגבול התווים של %{max} pin_errors: direct: לא ניתן לקבע הודעות שנראותן מוגבלת למכותבים בלבד limit: הגעת למספר המירבי של ההודעות המוצמדות ownership: הודעות של אחרים לא יכולות להיות מוצמדות reblog: אין אפשרות להצמיד הדהודים + quote_error: + not_available: ההודעה לא זמינה + pending_approval: ההודעה בהמתנה לאישור + revoked: ההודעה הוסרה על ידי המחבר.ת quote_policies: followers: לעוקבים בלבד nobody: רק אני public: כולם + quote_post_author: ההודעה היא ציטוט של %{acct} title: '%{name}: "%{quote}"' visibilities: direct: אזכור פרטי @@ -2237,3 +2276,6 @@ he: not_supported: דפדפן זה לא תומך במפתחות אבטחה otp_required: על מנת להשתמש במפתחות אבטחה אנא אפשר.י אימות דו-שלבי קודם. registered_on: נרשם ב %{date} + wrapstodon: + description: ראו איך %{name} השתמשו במסטודון השנה! + title: סיכומודון %{year} עבור %{name} diff --git a/config/locales/hr.yml b/config/locales/hr.yml index 7d04d7278c83bc..4439460df6c218 100644 --- a/config/locales/hr.yml +++ b/config/locales/hr.yml @@ -79,7 +79,6 @@ hr: aliases: add_new: Stvori alias appearance: - advanced_web_interface: Napredno web sučelje localization: body: Mastodon prevode dobrovoljci. guide_link_text: Svi mogu doprinjeti. diff --git a/config/locales/hu.yml b/config/locales/hu.yml index 123e8a97c9feae..3a5172e05cc2a9 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -7,6 +7,8 @@ hu: hosted_on: "%{domain} Mastodon-kiszolgáló" title: Névjegy accounts: + errors: + cannot_be_added_to_collections: Ez a fiók nem adható hozzá gyűjteményekhez. followers: one: Követő other: Követő @@ -796,6 +798,8 @@ hu: view_dashboard_description: Lehetővé teszi, hogy a felhasználó elérje az irányítópultot és vele számos metrikát view_devops: DevOps view_devops_description: Lehetővé teszi, hogy a felhasználó elérje a Sidekiq és pgHero irányítópultjait + view_feeds: Élő és témahírfolyamok megtekintése + view_feeds_description: A kiszolgálóbeállításoktól függetlenül engedélyezi az élő és témahírfolyamok elérését title: Szerepek rules: add_new: Szabály hozzáadása @@ -844,10 +848,21 @@ hu: publish_statistics: Statisztikák közzététele title: Felfedezés trends: Trendek + wrapstodon: Wrapstodon domain_blocks: all: Mindenkinek disabled: Senkinek users: Bejelentkezett helyi felhasználóknak + feed_access: + modes: + authenticated: Csak hitelesített felhasználók + disabled: Konkrét felhasználói szerepkör megkövetelése + public: Mindenki + landing_page: + values: + about: Névjegy + local_feed: Helyi idővonal + trends: Trendek registrations: moderation_recommandation: Győződj meg arról, hogy megfelelő és gyors reagálású moderátor csapatod van, mielőtt mindenki számára megnyitod a regisztrációt! preamble: Szabályozd, hogy ki hozhat létre fiókot a kiszolgálón. @@ -901,6 +916,7 @@ hu: no_status_selected: Nem változtattunk meg egy bejegyzést sem, mert semmi sem volt kiválasztva open: Bejegyzés megnyitása original_status: Eredeti bejegyzés + quotes: Idézetek reblogs: Megosztások replied_to_html: 'Válasz neki: %{acct_link}' status_changed: A bejegyzés megváltozott @@ -908,6 +924,7 @@ hu: title: Fiók bejegyzései – @%{name} trending: Felkapott view_publicly: Megtekintés nyilvánosan + view_quoted_post: Idézett bejegyzés megtekintése visibility: Láthatóság with_media: Médiával strikes: @@ -1182,10 +1199,10 @@ hu: hint_html: Ha másik fiókról kívánsz átlépni erre a fiókra, itt létrehozhatsz egy aliast, amelyre szükség van, mielőtt folytathatod a követők áthelyezését a régi fiókból erre. Ez az áthelyezés önmagában ártalmatlan és visszafordítható folyamat. A fiók áttelepítése a régi fiókból indul el. remove: Alias szétkapcsolása appearance: - advanced_web_interface: Speciális webes felület - advanced_web_interface_hint: 'Ha szeretnéd, a képernyő teljes szélességét kihasználhatod. A speciális webes felülettel különböző oszlopokat állíthatsz be, hogy egyszerre annyi információt láthass, amennyit csak akarsz: Kezdőoldal, értesítések, föderációs idővonal, bármennyi lista vagy hashtag.' + advanced_settings: Speciális beállítások animations_and_accessibility: Animáció és akadálymentesítés - confirmation_dialogs: Megerősítő párbeszédablakok + boosting_preferences: Megtolási beállítások + boosting_preferences_info_html: "Tipp: A beállításoktól függetlenül, ha Shift + katinttást használsz a %{icon} Megtolás ikonon, akkor azonnal megteszi." discovery: Felfedezés localization: body: A Mastodont önkéntesek fordítják. @@ -1587,6 +1604,13 @@ hu: expires_at: Lejárat uses: Használat title: Meghívások + link_preview: + author_html: 'Szerző: %{name}' + potentially_sensitive_content: + action: Kattints a megjelenítéshez + confirm_visit: Biztos, hogy megnyitod ezt a hivatkozást? + hide_button: Elrejtés + label: Lehet, hogy kényes tartalom lists: errors: limit: Elérted a listák maximális számát @@ -1651,7 +1675,7 @@ hu: disabled_account: A jelenlegi fiókod nem lesz teljesen használható ezután. Viszont elérhető lesz majd az adatexport funkció, valamint a reaktiválás is. followers: Ez a művelet az összes követődet a jelenlegi fiókról az újra fogja költöztetni only_redirect_html: Az is lehetséges, hogy csak átirányítást raksz a profilodra. - other_data: Más adatot nem fogunk automatikusan mozgatni + other_data: Semmilyen más adat (beleértve a bejegyzéseket és a követett fiókokat) nem lesz automatikusan áthelyezve redirect: A jelenlegi fiókod profiljára átirányításról szóló figyelmeztetést rakunk, valamint már nem fogjuk mutatni a keresésekben moderation: title: Moderáció @@ -1685,16 +1709,22 @@ hu: body: "%{name} megemlített téged:" subject: "%{name} megemlített téged" title: Új említés + moderation_warning: + subject: Moderációs figyelmeztetést kaptál poll: subject: "%{name} szavazása véget ért" quote: body: 'A bejegyzésedet %{name} idézte:' subject: "%{name} idézte a bejegyzésedet" title: Új idézet + quoted_update: + subject: "%{name} szerkesztett egy bejegyzést, melyet idéztél" reblog: body: 'A bejegyzésedet %{name} megtolta:' subject: "%{name} megtolta a bejegyzésedet" title: Új megtolás + severed_relationships: + subject: Moderációs döntés miatt kapcsolatokat veszítettél status: subject: "%{name} bejegyzést írt" update: @@ -1897,6 +1927,9 @@ hu: other: "%{count} videó" boosted_from_html: Megtolva innen %{acct_link} content_warning: 'Tartalmi figyelmeztetés: %{warning}' + content_warnings: + hide: Bejegyzés elrejtése + show: Több megjelenítése default_language: Felhasználói felület nyelvével azonos disallowed_hashtags: one: 'tiltott hashtaget tartalmaz: %{tags}' @@ -1905,16 +1938,22 @@ hu: errors: in_reply_not_found: Már nem létezik az a bejegyzés, melyre válaszolni szeretnél. quoted_status_not_found: Már nem létezik az a bejegyzés, amelyből idézni szeretnél. + quoted_user_not_mentioned: Nem idézhet meg nem említett felhasználót egy privát említési bejegyzésben. over_character_limit: túllépted a maximális %{max} karakteres keretet pin_errors: direct: A csak a megemlített felhasználók számára látható bejegyzések nem tűzhetők ki limit: Elérted a kitűzhető bejegyzések maximális számát ownership: Nem tűzheted ki valaki más bejegyzését reblog: Megtolt bejegyzést nem tudsz kitűzni + quote_error: + not_available: A bejegyzés nem érhető el + pending_approval: A bejegyzés függőben van + revoked: A szerző eltávolította a bejegyzést quote_policies: followers: Csak követőknek nobody: Csak én public: Bárki + quote_post_author: Idézte %{acct} bejegyzését title: "%{name}: „%{quote}”" visibilities: direct: Privát említés @@ -2149,3 +2188,6 @@ hu: not_supported: Ez a böngésző nem támogatja a biztonsági kulcsokat otp_required: A biztonsági kulcsok használatához először engedélyezd a kétlépcsős hitelesítést. registered_on: 'Regisztráció ekkor: %{date}' + wrapstodon: + description: Nézd meg, hogy %{name} hogyan használta a Mastodont az éven! + title: Wrapstodon %{year} – %{name} diff --git a/config/locales/hy.yml b/config/locales/hy.yml index 933f8a4fa75e78..3dc7913784fd40 100644 --- a/config/locales/hy.yml +++ b/config/locales/hy.yml @@ -433,10 +433,7 @@ hy: new_trending_statuses: title: Թրենդային գրառումներ appearance: - advanced_web_interface: Սյունակավոր ինտերֆեյս - advanced_web_interface_hint: Եթէ ցանկանում ես օգտագործել էկրանիդ ամբողջ լայնքը, ապա ընդլայնուած վեբ ինտերֆեյսով հնարաւոր է էկրանը բաժանել սիւնակների՝ զուգահեռ տեսնելու տարբեր տիպի ինֆորմացիա՝ տեղական հոսքը, ծանուցումները, ֆեդերացված հոսքը, և ցանկացած թվի ցուցակ ու հեշթեգ։ animations_and_accessibility: Անիմացիաներ եւ հասանելիութիւն - confirmation_dialogs: Հաստատման պատուհաններ discovery: Բացայայտում localization: body: Մաստոդոնը թարգմանուում է կամաւորների կողմից։ @@ -619,7 +616,6 @@ hy: warning: followers: Այս քայլով քո բոլոր հետեւորդներին այս հաշուից կը տեղափոխես դէպի նորը only_redirect_html: Որպէս այլընտրանք, կարող ես ուղղակի վերահասցէաւորել քո հաշիւը - other_data: Մնացեալ տուեալները ինքնուրոյն չեն փոխադրուի moderation: title: Մոդերացիա notification_mailer: diff --git a/config/locales/ia.yml b/config/locales/ia.yml index 019e3f8438dec6..19c938e0f7ac64 100644 --- a/config/locales/ia.yml +++ b/config/locales/ia.yml @@ -320,7 +320,7 @@ ia: edit: title: Modificar annuncio empty: Necun annuncios trovate. - live: In directo + live: In vivo new: create: Crear annuncio title: Nove annuncio @@ -796,6 +796,8 @@ ia: view_dashboard_description: Permitte que usatores accede al tabuliero de instrumentos e a varie statisticas view_devops: DevOps view_devops_description: Permitte que usatores accede al tabulieros de instrumentos de Sidekiq e pgHero + view_feeds: Vider canales thematic e in vivo + view_feeds_description: Permitte que usatores acceder al canales thematic e in vivo independentemente del configuration del servitor title: Rolos rules: add_new: Adder regula @@ -848,6 +850,16 @@ ia: all: A omnes disabled: A necuno users: A usators local in session + feed_access: + modes: + authenticated: Solmente usatores authenticate + disabled: Requirer un rolo de usator specific + public: Omnes + landing_page: + values: + about: A proposito + local_feed: Canal local + trends: Tendentias registrations: moderation_recommandation: Per favor assecura te de haber un equipa de moderation adequate e reactive ante de aperir le inscription a omnes! preamble: Controla qui pote crear un conto sur tu servitor. @@ -901,6 +913,7 @@ ia: no_status_selected: Necun message ha essite cambiate perque necun ha essite seligite open: Aperir message original_status: Message original + quotes: Citationes reblogs: Republicationes replied_to_html: Responsa a %{acct_link} status_changed: Message cambiate @@ -908,6 +921,7 @@ ia: title: Messages del conto – @%{name} trending: Tendentias view_publicly: Vider publicamente + view_quoted_post: Vider message citate visibility: Visibilitate with_media: Con multimedia strikes: @@ -1182,10 +1196,10 @@ ia: hint_html: Si tu vole migrar de un altere conto a iste, tu pote crear un alias ci, que es necessari pro poter transferer le sequitores del conto ancian a iste. Iste action per se es innocue e reversibile. Le migration del conto es initiate desde le conto ancian. remove: Disligar alias appearance: - advanced_web_interface: Interfacie web avantiate - advanced_web_interface_hint: 'Si tu vole utilisar tote le largessa de tu schermo, le interfacie web avantiate te permitte configurar multe columnas differente pro vider al mesme tempore tante informationes como tu vole: pagina principal, notificationes, chronologia federate, un numero illimitate de listas e hashtags.' + advanced_settings: Parametros avantiate animations_and_accessibility: Animationes e accessibilitate - confirmation_dialogs: Dialogos de confirmation + boosting_preferences: Preferentias de impulso + boosting_preferences_info_html: "Consilio: Independentemente del parametros, Shift + clic sur le icone %{icon} “Impulsar” impulsara immediatemente." discovery: Discoperta localization: body: Mastodon es traducite per voluntarios. @@ -1587,6 +1601,13 @@ ia: expires_at: Expira uses: Usos title: Invitar personas + link_preview: + author_html: Per %{name} + potentially_sensitive_content: + action: Clicca pro monstrar + confirm_visit: Es tu secur que tu vole aperir iste message? + hide_button: Celar + label: Contento potentialmente sensibile lists: errors: limit: Tu ha attingite le maxime numero de listas @@ -1651,7 +1672,6 @@ ia: disabled_account: Tu conto actual non essera plenmente usabile postea. Nonobstante, tu habera accesso al exportation de datos e al reactivation. followers: Iste action transferera tote le sequitores del conto actual al conto nove only_redirect_html: Como alternativa, tu pote poner solmente un redirection sur tu profilo. - other_data: Nulle altere datos essera migrate automaticamente redirect: Le profilo de tu conto actual essera actualisate con un aviso de redirection e excludite de recercas moderation: title: Moderation @@ -1897,6 +1917,9 @@ ia: other: "%{count} videos" boosted_from_html: Impulsate desde %{acct_link} content_warning: 'Advertimento de contento: %{warning}' + content_warnings: + hide: Celar message + show: Monstrar plus default_language: Mesme como lingua de interfacie disallowed_hashtags: one: 'contineva un hashtag non autorisate: %{tags}' @@ -1911,10 +1934,15 @@ ia: limit: Tu ha jam appunctate le maxime numero de messages ownership: Le message de alcuno altere non pote esser appunctate reblog: Un impulso non pote esser affixate + quote_error: + not_available: Message indisponibile + pending_approval: Message pendente + revoked: Message removite per le autor quote_policies: followers: Solmente sequitores nobody: Solo io public: Omnes + quote_post_author: Ha citate un message de %{acct} title: "%{name}: “%{quote}”" visibilities: direct: Mention private diff --git a/config/locales/id.yml b/config/locales/id.yml index 3489519f847096..a7f21ee3f51fc8 100644 --- a/config/locales/id.yml +++ b/config/locales/id.yml @@ -670,7 +670,6 @@ id: title: Keluarkan pengguna dari pengindeksan mesin telusur secara bawaan discovery: follow_recommendations: Ikuti rekomendasi - preamble: Menampilkan konten menarik penting dalam memandu pengguna baru yang mungkin tidak tahu siapa pun di Mastodon. Atur bagaimana berbagai fitur penemuan bekerja di server Anda. profile_directory: Direktori profil public_timelines: Linimasa publik title: Penemuan @@ -862,10 +861,7 @@ id: hint_html: Jika Anda ingin pindah dari akun lain ke sini, Anda dapat membuat alias, yang dilakukan sebelum Anda setuju dengan memindah pengikut dari akun lama ke akun sini. Aksi ini tidak berbahaya dan tidak bisa dikembalikan. Pemindahan akun dimulai dari akun lama. remove: Hapus tautan alias appearance: - advanced_web_interface: Antarmuka web tingkat lanjut - advanced_web_interface_hint: 'Jika Anda ingin memanfaatkan seluruh lebar layar Anda, antarmuka web tingkat lanjut memungkinkan Anda mengonfigurasi beragam kolom untuk menampilkan informasi sebanyak yang Anda inginkan: Beranda, notifikasi, linimasa gabungan, daftar, dan tagar.' animations_and_accessibility: Animasi dan aksesibilitas - confirmation_dialogs: Dialog konfirmasi discovery: Jelajah localization: body: Mastodon diterjemahkan oleh sukarelawan. @@ -1171,7 +1167,6 @@ id: disabled_account: Akun Anda tidak akan dapat dipakai secara penuh. Namun, Anda tetap akan memiliki akses ke ekspor data dan aktivasi ulang. followers: Tindakan ini akan memindah semua pengikut Anda dari akun sekarang ke akun baru only_redirect_html: Secara alternatif, Anda hanya dapat menaruh tulisan mengarahkan ke profil Anda. - other_data: Tidak akan ada data lagi yang dipindahkan secara otomatis redirect: Pemberitahuan peralihan akan dimunculkan pada akun profil Anda dan akun akan dikecualikan dari pencarian moderation: title: Moderasi diff --git a/config/locales/ie.yml b/config/locales/ie.yml index d202e96d491e37..31d1ea27f77b71 100644 --- a/config/locales/ie.yml +++ b/config/locales/ie.yml @@ -729,7 +729,6 @@ ie: title: Predefinir que usatores ne apari in índexes de serchatores discovery: follow_recommendations: Seque-recomandationes - preamble: Exposir interessant contenete es importantissim por incorporar nov usatores qui fórsan conosse nequi che Mastodon. Decider qualmen diferent utensiles de decovrition functiona che vor servitor. profile_directory: Profilarium public_timelines: Public témpor-lineas publish_statistics: Publicar statisticas @@ -982,10 +981,7 @@ ie: hint_html: Si tu vole mover de un altri conto a ti-ci, ci tu posse crear un alias, quel es besonat ante que tu posse proceder a mover sequitores del antiqui conto a ti-ci. Ti-ci action, sol, es ínnociv e reversibil. Li conto-migration es initiat del antiqui conto. remove: Desconexer alias appearance: - advanced_web_interface: Avansat web-interfacie - advanced_web_interface_hint: 'Si tu vole usar li tot largore de tui ecran, li avansat web-interfacie permisse que tu mey configurar mult columnes diferent por vider tam mult information simultanmen quam tu vole: Hem, federat témpor-linea, quelcunc númere de listes e hashtags.' animations_and_accessibility: Animationes e accessibilitá - confirmation_dialogs: Dialogs de confirmation discovery: Decovriment localization: body: Mastodon es traductet de voluntarios. @@ -1373,7 +1369,6 @@ ie: disabled_account: Tui actual conto ne va esser completmen usabil pos to. Támen, tu va posser accesser li exportation de data, e anc reactivisation. followers: Ti-ci action va mover omni sequitores del actual conto al nov conto only_redirect_html: Alternativmen, tu posse solmen meter un redirection sur tui profil. - other_data: Necun altri data va esser translocat automaticmen redirect: Li profil de tui actual conto va esser actualisat con un anuncie de redirection e va esser excludet de serchas moderation: title: Moderation diff --git a/config/locales/io.yml b/config/locales/io.yml index 36d4ce251fdf63..3036c363be6e74 100644 --- a/config/locales/io.yml +++ b/config/locales/io.yml @@ -780,7 +780,6 @@ io: title: Despartoprenigez uzanti de serchilo-indexi quale originala stando discovery: follow_recommendations: Sequez rekomendaji - preamble: Montrar interesanta kontenajo esas importanta ye voligar nova uzanti quo forsan ne savas irgu. Dominacez quale ca deskovrotraiti funcionar en ca servilo. profile_directory: Profiluyo public_timelines: Publika tempolinei publish_statistics: Publikar statistiki @@ -1097,10 +1096,7 @@ io: hint_html: Se vu volas transferesar de altra konto a co, hike vu povas krear alternativnomo, quo bezonesas ante vu povas durigar transferar sequanti de la olda konto a co. Ca ago esas nedanjeroza e inversigebla. Kontomigro komencesas de la olda konto. remove: Deligez alternative nomo appearance: - advanced_web_interface: Altnivela retintervizajo - advanced_web_interface_hint: 'Se vu volas uzar tota skrenlongeso, altnivela retintervizajo povigas vu modifikar multa dessama kolumni por vida multa informi en sama tempo quale vu volas: Hemo, savigi, fratara tempolineo, multa listi e gretvorti.' animations_and_accessibility: Animi e adirebleso - confirmation_dialogs: Konfirmdialogi discovery: Deskovro localization: body: Mastodon tradukesas da voluntarii. @@ -1507,7 +1503,6 @@ io: disabled_account: Vua nuna konto ne esos tote uzebla pose. followers: Ca ago transferos omna sequanti de nuna konto a nova konto only_redirect_html: Alternative, vu povas nur pozar ridirekto che vua profilo. - other_data: Altra informi ne transferesos automate redirect: Vua nuna profilo di konto novigesos kun ridirektoavizo e neinkluzesos de trovi moderation: title: Jero diff --git a/config/locales/is.yml b/config/locales/is.yml index 5f783331c067bf..d03b74391f7135 100644 --- a/config/locales/is.yml +++ b/config/locales/is.yml @@ -7,6 +7,8 @@ is: hosted_on: Mastodon hýst á %{domain} title: Um hugbúnaðinn accounts: + errors: + cannot_be_added_to_collections: Þessum aðgangi er ekki hægt að bæta í söfn. followers: one: fylgjandi other: fylgjendur @@ -182,7 +184,7 @@ is: create_account_warning: Útbúa aðvörun create_announcement: Búa til tilkynningu create_canonical_email_block: Búa til útilokunarblokk tölvupósts - create_custom_emoji: Búa til sérsniðið tjáningartákn + create_custom_emoji: Búa til sérsniðið lyndistákn create_domain_allow: Búa til lén leyft create_domain_block: Búa til útilokun léns create_email_domain_block: Búa til útilokun tölvupóstléns @@ -194,7 +196,7 @@ is: demote_user: Lækka notanda í tign destroy_announcement: Eyða tilkynningu destroy_canonical_email_block: Eyða útilokunarblokk tölvupósts - destroy_custom_emoji: Eyða sérsniðnu tjáningartákni + destroy_custom_emoji: Eyða sérsniðnu lyndistákni destroy_domain_allow: Eyða léni leyft destroy_domain_block: Eyða útilokun léns destroy_email_domain_block: Eyða útilokun tölvupóstléns @@ -206,11 +208,11 @@ is: destroy_user_role: Eyða hlutverki destroy_username_block: Eyða notandanafnsreglu disable_2fa_user: Gera tveggja-þátta auðkenningu óvirka - disable_custom_emoji: Gera sérsniðið tjáningartákn óvirkt + disable_custom_emoji: Gera sérsniðið lyndistákn óvirkt disable_relay: Gera endurvarpa óvirkan disable_sign_in_token_auth_user: Gera óvirka auðkenningu með teikni í tölvupósti fyrir notandann disable_user: Gera notanda óvirkan - enable_custom_emoji: Virkja sérsniðið tjáningartákn + enable_custom_emoji: Virkja sérsniðið lyndistákn enable_relay: Virkja endurvarpa enable_sign_in_token_auth_user: Gera virka auðkenningu með teikni í tölvupósti fyrir notandann enable_user: Virkja notanda @@ -233,7 +235,7 @@ is: unsilence_account: Hætta að hylja notandaaðgang unsuspend_account: Taka notandaaðgang úr frysti update_announcement: Uppfæra tilkynningu - update_custom_emoji: Uppfæra sérsniðið tjáningartákn + update_custom_emoji: Uppfæra sérsniðið lyndistákn update_domain_block: Uppfæra útilokun léns update_ip_block: Uppfæra reglu IP-vistfangs update_report: Uppfæra kæru @@ -250,7 +252,7 @@ is: create_account_warning_html: "%{name} sendi aðvörun til %{target}" create_announcement_html: "%{name} útbjó nýja tilkynningu %{target}" create_canonical_email_block_html: "%{name} útilokaði tölvupóst með tætigildið %{target}" - create_custom_emoji_html: "%{name} sendi inn nýtt tjáningartákn %{target}" + create_custom_emoji_html: "%{name} sendi inn nýtt lyndistákn %{target}" create_domain_allow_html: "%{name} leyfði skýjasamband með léninu %{target}" create_domain_block_html: "%{name} útilokaði lénið %{target}" create_email_domain_block_html: "%{name} útilokaði póstlénið %{target}" @@ -274,11 +276,11 @@ is: destroy_user_role_html: "%{name} eyddi hlutverki %{target}" destroy_username_block_html: "%{name} fjarlægði reglu varðandi notendanöfn sem innihalda %{target}" disable_2fa_user_html: "%{name} gerði kröfu um tveggja-þátta innskráningu óvirka fyrir notandann %{target}" - disable_custom_emoji_html: "%{name} gerði tjáningartáknið %{target} óvirkt" + disable_custom_emoji_html: "%{name} gerði lyndistáknið %{target} óvirkt" disable_relay_html: "%{name} gerði endurvarpann %{target} óvirkan" disable_sign_in_token_auth_user_html: "%{name} gerði óvirka auðkenningu með teikni í tölvupósti fyrir %{target}" disable_user_html: "%{name} gerði innskráningu óvirka fyrir notandann %{target}" - enable_custom_emoji_html: "%{name} gerði tjáningartáknið %{target} virkt" + enable_custom_emoji_html: "%{name} gerði lyndistáknið %{target} virkt" enable_relay_html: "%{name} virkjaði endurvarpann %{target}" enable_sign_in_token_auth_user_html: "%{name} gerði virka auðkenningu með teikni í tölvupósti fyrir %{target}" enable_user_html: "%{name} gerði innskráningu virka fyrir notandann %{target}" @@ -301,7 +303,7 @@ is: unsilence_account_html: "%{name} hætti að hylja notandaaðganginn %{target}" unsuspend_account_html: "%{name} tók notandaaðganginn %{target} úr frysti" update_announcement_html: "%{name} uppfærði tilkynningu %{target}" - update_custom_emoji_html: "%{name} uppfærði tjáningartáknið %{target}" + update_custom_emoji_html: "%{name} uppfærði lyndistáknið %{target}" update_domain_block_html: "%{name} uppfærði útilokun lénsins %{target}" update_ip_block_html: "%{name} breytti reglu fyrir IP-vistfangið %{target}" update_report_html: "%{name} uppfærði kæru %{target}" @@ -340,36 +342,36 @@ is: custom_emojis: assign_category: Úthluta flokki by_domain: Lén - copied_msg: Það tókst að búa til afrit af tjáningartákninu + copied_msg: Það tókst að búa til afrit af lyndistákninu copy: Afrita - copy_failed_msg: Ekki tókst að gera staðvært afrit af tjáningartákninu + copy_failed_msg: Ekki tókst að gera staðvært afrit af lyndistákninu create_new_category: Búa til nýjan flokk - created_msg: Tókst að búa til tjáningartákn! + created_msg: Tókst að búa til lyndistákn! delete: Eyða - destroyed_msg: Tókst að eyða tjáningartákni! + destroyed_msg: Tókst að eyða lyndistákni! disable: Gera óvirkt disabled: Óvirkt - disabled_msg: Tókst að gera þetta tjáningartákn óvirkt - emoji: Tjáningartákn + disabled_msg: Tókst að gera þetta lyndistákn óvirkt + emoji: Lyndistákn enable: Virkja enabled: Virkt - enabled_msg: Tókst að gera þetta tjáningartákn virkt + enabled_msg: Tókst að gera þetta lyndistákn virkt image_hint: PNG eða GIF allt að %{size} list: Listi listed: Skráð new: - title: Bæta við nýju sérsniðnu tjáningartákni + title: Bæta við nýju sérsniðnu lyndistákni no_emoji_selected: Engum táknum var breytt þar sem engin voru valin not_permitted: Þú hefur ekki réttindi til að framkvæma þessa aðgerð overwrite: Skrifa yfir shortcode: Stuttkóði shortcode_hint: Að minnsta kosti 2 stafir, einungis tölu- og bókstafir ásamt undirstrikum - title: Sérsniðin tjáningartákn + title: Sérsniðin lyndistákn uncategorized: Óflokkað unlist: Afskrá unlisted: Óskráð - update_failed_msg: Gat ekki uppfært þetta tjáningartákn - updated_msg: Tókst að uppfæra tjáningartákn! + update_failed_msg: Gat ekki uppfært þetta lyndistákn + updated_msg: Tókst að uppfæra lyndistákn! upload: Senda inn dashboard: active_users: virkir notendur @@ -768,8 +770,8 @@ is: manage_appeals_description: Leyfir notendum að yfirfara áfrýjanir vegna aðgerða umsjónarfólks manage_blocks: Sýsla með útilokanir manage_blocks_description: Leyfir notendum að loka á tölvupóstþjónustur og IP-vistföng - manage_custom_emojis: Sýsla með sérsniðin tjáningartákn - manage_custom_emojis_description: Leyfir notendum að sýsla með sérsniðin tjáningartákn á netþjóninum + manage_custom_emojis: Sýsla með sérsniðin lyndistákn + manage_custom_emojis_description: Leyfir notendum að sýsla með sérsniðin lyndistákn á netþjóninum manage_federation: Sýsla með netþjónasambönd manage_federation_description: Leyfir notendum að loka á eða leyfa samþættingu við önnur lén (federation) og stýra afhendingu skilaboða manage_invites: Sýsla með boðsgesti @@ -796,6 +798,8 @@ is: view_dashboard_description: Leyfir notendum að skoða stjórnborðið og sjá ýmsar mælingar view_devops: DevOps view_devops_description: Leyfir notendum að skoða Sidekiq og pgHero stjórnborð + view_feeds: Skoða bein streymi og efnistengd + view_feeds_description: Gefur notendum aðgang að beinum streymum og efnistengdum, burtséð frá stillingum netþjóns title: Hlutverk rules: add_new: Skrá reglu @@ -846,10 +850,21 @@ is: publish_statistics: Birta tölfræði title: Uppgötvun trends: Vinsælt + wrapstodon: Ársuppgjörið domain_blocks: all: Til allra disabled: Til engra users: Til innskráðra staðværra notenda + feed_access: + modes: + authenticated: Einungis auðkenndir notendur + disabled: Krefjast sérstaks hlutverks notanda + public: Allir + landing_page: + values: + about: Um hugbúnaðinn + local_feed: Staðbundið streymi + trends: Vinsælt registrations: moderation_recommandation: Tryggðu að þú hafir hæft og aðgengilegt umsjónarteymi til taks áður en þú opnar á skráningar fyrir alla! preamble: Stýrðu því hverjir geta útbúið notandaaðgang á netþjóninum þínum. @@ -903,6 +918,7 @@ is: no_status_selected: Engum færslum var breytt þar sem engar voru valdar open: Opna færslu original_status: Upprunaleg færsla + quotes: Tilvitnanir reblogs: Endurbirtingar replied_to_html: Svaraði til %{acct_link} status_changed: Færslu breytt @@ -910,6 +926,7 @@ is: title: Færslur notanda - @%{name} trending: Vinsælt view_publicly: Skoða opinberlega + view_quoted_post: Skoða færslu sem vitnað er í visibility: Sýnileiki with_media: Með myndefni strikes: @@ -1184,10 +1201,10 @@ is: hint_html: Ef þú vilt flytjast af öðrum notandaaðgangi yfir á þennan, þá geturðu búið hér til samnefni, sem er nauðsynlegt áður en þú getur haldið áfram við að flytja fylgjendur af gamla notandaaðgangnum yfir á þennan aðgang. Þessi aðgerð er í sjálfu sér skaðlaus og afturkræf. Yfirfærsla notandaaðgangsins er síðan ræst á gamla notandaaðgangnum. remove: Aftengja samnefni appearance: - advanced_web_interface: Ítarlegt vefviðmót - advanced_web_interface_hint: 'Ef þú vilt geta notað alla skjábreiddina gefur ítarlegt vefviðmót þér færi á að stilla marga mismunandi dálka svo hægt sé að sjá eins miklar upplýsingar í einu eins og þér hentar: Heim, tilkynningar, sameiginleg tímalína, ótiltekinn fjöldi lista og myllumerkja.' + advanced_settings: Ítarlegar stillingar animations_and_accessibility: Hreyfingar og algilt aðgengi - confirmation_dialogs: Staðfestingargluggar + boosting_preferences: Kjörstillingar fyrir endurbirtingar + boosting_preferences_info_html: "Ábending: Hverjar svo sem stillingarnar eru, þá mun Shift + smella á %{icon} endurbirtingartáknið alltaf endurbirta strax." discovery: Uppgötvun localization: body: Mastodon er þýtt af sjálfboðaliðum. @@ -1591,6 +1608,13 @@ is: expires_at: Rennur út uses: Afnot title: Bjóða fólki + link_preview: + author_html: Frá %{name} + potentially_sensitive_content: + action: Smelltu til að birta + confirm_visit: Ertu viss um að þú viljir opna þennan tengil? + hide_button: Fela + label: Mögulega viðkvæmt efni lists: errors: limit: Þú hefur náð hámarksfjölda lista @@ -1655,7 +1679,7 @@ is: disabled_account: Núverandi aðgangur þinn verður ekki nothæfur að fullu eftir þetta. Hinsvegar muntu geta flutt út gögn af honum og einnig endurvirkjað hann. followers: Þessi aðgerð mun flytja alla fylgjendur af núverandi aðgangi yfir á nýja aðganginn only_redirect_html: Einnig geturðu einungis sett upp endurbeiningu á notandasniðið þitt. - other_data: Engin önnur gögn munu flytjast sjálfvirkt + other_data: Engin önnur gögn verða flutt sjálfkrafa (m.a. færslurnar þínar og listar yfir þá sem þú fylgist með) redirect: Notandasnið aðgangsins verður uppfært með athugasemd um endurbeininguna og verður undanþegið frá leitum moderation: title: Umsjón @@ -1689,16 +1713,22 @@ is: body: "%{name} minntist á þig í:" subject: "%{name} minntist á þig" title: Ný tilvísun + moderation_warning: + subject: Þú hefur fengið aðvörun frá umsjónarmanni poll: subject: Könnun frá %{name} er lokið quote: body: "%{name} vitnaði í færsluna þína:" subject: "%{name} vitnaði í færsluna þína" title: Ný tilvitnun + quoted_update: + subject: "%{name} breytti færslu sem þú hefur vitnað í" reblog: body: "%{name} endurbirti færsluna þína:" subject: "%{name} endurbirti færsluna þína" title: Ný endurbirting + severed_relationships: + subject: Þú hefur tapað tengingum vegna ákvörðunar umsjónarmanna status: subject: "%{name} sendi inn rétt í þessu" update: @@ -1764,7 +1794,7 @@ is: reactions: errors: limit_reached: Hámarki mismunandi viðbragða náð - unrecognized_emoji: er ekki þekkt tjáningartákn + unrecognized_emoji: er ekki þekkt lyndistákn redirects: prompt: Ef þú treystir þessum tengli, geturðu smellt á hann til að halda áfram. title: Þú ert að yfirgefa %{instance}. @@ -1901,6 +1931,9 @@ is: other: "%{count} myndskeið" boosted_from_html: Endurbirt frá %{acct_link} content_warning: 'Aðvörun vegna efnis (CW): %{warning}' + content_warnings: + hide: Fela færslu + show: Sýna meira default_language: Sama og tungumál viðmóts disallowed_hashtags: one: 'innihélt óleyfilegt myllumerki: %{tags}' @@ -1909,16 +1942,22 @@ is: errors: in_reply_not_found: Færslan sem þú ert að reyna að svara að er líklega ekki til. quoted_status_not_found: Færslan sem þú ert að reyna að vitna í virðist ekki vera til. + quoted_user_not_mentioned: Ekki er hægt að vitna í aðila sem ekki er minnst á í einkaspjalli. over_character_limit: hámarksfjölda stafa (%{max}) náð pin_errors: direct: Ekki er hægt að festa færslur sem einungis eru sýnilegar þeim notendum sem minnst er á limit: Þú hefur þegar fest leyfilegan hámarksfjölda færslna ownership: Færslur frá einhverjum öðrum er ekki hægt að festa reblog: Ekki er hægt að festa endurbirtingu + quote_error: + not_available: Færsla ekki tiltæk + pending_approval: Færsla í bið + revoked: Færsla fjarlægð af höfundi quote_policies: followers: Einungis fylgjendur nobody: Bara ég public: Hver sem er + quote_post_author: Vitnaði í færslu frá %{acct} title: "%{name}: „%{quote}‟" visibilities: direct: Einkaspjall @@ -2096,7 +2135,7 @@ is: feature_audience_title: Byggðu upp orðspor þitt og áheyrendafjölda feature_control: Þú veist best hvað þú vilt sjá í heimastreyminu þínu. Engin reiknirit eða auglýsingar að þvælast fyrir. Fylgstu af einum aðgangi með hverjum sem er á milli Mastodon-netþjóna og fáðu færslurnar þeirra í tímaröð, þannig geturðu útbúið þitt eigið lítið horn á internetinu þar sem hlutirnir eru að þínu skapi. feature_control_title: Hafðu stjórn á þinni eigin tímalínu - feature_creativity: Mastodon styður færslur með hljóði, myndum og myndskeiðum, lýsingum fyrir aukið aðgengi, kannanir, aðvörunum vegna efnis, hreyanlegum auðkennismyndum, sérsniðnum tjáningartáknum, utanskurði smámynda ásamt fleiru; til að hjálpa þér við að tjá þig á netinu. Hvort sem þú sért að gefa út listina þína, tónlist eða hlaðvarp, þá er Mastodon til staðar fyrir þig. + feature_creativity: Mastodon styður færslur með hljóði, myndum og myndskeiðum, lýsingum fyrir aukið aðgengi, kannanir, aðvörunum vegna efnis, hreyanlegum auðkennismyndum, sérsniðnum lyndistáknum, utanskurði smámynda ásamt fleiru; til að hjálpa þér við að tjá þig á netinu. Hvort sem þú sért að gefa út listina þína, tónlist eða hlaðvarp, þá er Mastodon til staðar fyrir þig. feature_creativity_title: Óviðjafnanleg sköpunargleði feature_moderation: Mastodon setur ákvarðanatökur aftur í þínar hendur. Hver netþjónn býr til sínar eigin reglur og venjur, sem gilda fyrir þann netþjón en eru ekki boðaðar með valdi að ofan og niður líkt og á samfélagsnetum stórfyrirtækja. Á þennan hátt svarar samfélagsmiðillinn þörfum mismunandi hópa. Taktu þátt á netþjóni með reglum sem þú samþykkir, eða hýstu þinn eigin. feature_moderation_title: Umsjón með efni eins og slík á að vera @@ -2153,3 +2192,6 @@ is: not_supported: Þessi vafri styður ekki öryggislykla otp_required: Til að nota öryggislykla skaltu fyrst virkja tveggja-þátta auðkenningu. registered_on: Skráði sig %{date} + wrapstodon: + description: Sjáðu hvernig %{name} notaði Mastodon á árinu! + title: Ársuppgjörið %{year} fyrir %{name} diff --git a/config/locales/it.yml b/config/locales/it.yml index 264119d7e374c6..37b131c4b99684 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -7,6 +7,8 @@ it: hosted_on: Mastodon ospitato su %{domain} title: Info accounts: + errors: + cannot_be_added_to_collections: Questo account non può essere aggiunto alle collezioni. followers: one: Seguace other: Seguaci @@ -796,6 +798,8 @@ it: view_dashboard_description: Consente agli utenti di accedere alla dashboard e alle varie metriche view_devops: DevOps view_devops_description: Consente agli utenti di accedere alle dashboard Sidekiq e pgHero + view_feeds: Visualizza feed in diretta e feed di argomenti + view_feeds_description: Consente agli utenti di accedere ai feed in diretta e ai feed di argomenti indipendentemente dalle impostazioni del server title: Ruoli rules: add_new: Aggiungi regola @@ -837,17 +841,28 @@ it: title: Esclude gli utenti dall'indicizzazione dei motori di ricerca per impostazione predefinita discovery: follow_recommendations: Segui le raccomandazioni - preamble: La comparsa di contenuti interessanti è determinante per l'arrivo di nuovi utenti che potrebbero non conoscere nessuno su Mastodon. Controlla in che modo varie funzionalità di scoperta funzionano sul tuo server. + preamble: La scoperta di contenuti interessanti è fondamentale per l'inserimento di nuovi utenti che potrebbero non conoscere nessuno su Mastodon. Controlla l'andamento delle varie funzionalità di scoperta sul tuo server. privacy: Privacy profile_directory: Directory del profilo public_timelines: Timeline pubbliche publish_statistics: Pubblica le statistiche title: Scopri trends: Tendenze + wrapstodon: Wrapstodon domain_blocks: all: A tutti disabled: A nessuno users: Agli utenti locali connessi + feed_access: + modes: + authenticated: Solo utenti autenticati + disabled: Richiedi un ruolo utente specifico + public: Tutti + landing_page: + values: + about: Info + local_feed: Feed locale + trends: Tendenze registrations: moderation_recommandation: Assicurati di avere un team di moderazione adeguato e reattivo prima di aprire le registrazioni a tutti! preamble: Controlla chi può creare un account sul tuo server. @@ -901,6 +916,7 @@ it: no_status_selected: Nessun status è stato modificato perché nessuno era stato selezionato open: Apri il post original_status: Post originale + quotes: Citazioni reblogs: Condivisioni replied_to_html: Risposta a %{acct_link} status_changed: Post modificato @@ -908,6 +924,7 @@ it: title: Post dell'account - @%{name} trending: Di tendenza view_publicly: Visualizza pubblicamente + view_quoted_post: Visualizza il post citato visibility: Visibilità with_media: con media strikes: @@ -1182,12 +1199,10 @@ it: hint_html: Se vuoi trasferirti da un altro account a questo, qui puoi creare un alias, che è necessario prima di poter spostare i seguaci dal vecchio account a questo. Questa azione è innocua e reversibile. La migrazione dell'account è avviata dal vecchio account. remove: Scollega alias appearance: - advanced_web_interface: Interfaccia web avanzata - advanced_web_interface_hint: |- - Se vuoi utilizzare l'intera larghezza dello schermo, l'interfaccia web avanzata ti consente di configurare varie colonne per mostrare più informazioni allo stesso tempo, secondo le tue preferenze: - Home, notifiche, timeline federata, qualsiasi numero di liste e etichette. + advanced_settings: Impostazioni avanzate animations_and_accessibility: Animazioni e accessibilità - confirmation_dialogs: Dialoghi di conferma + boosting_preferences: Preferenze di potenziamento + boosting_preferences_info_html: "Suggerimento: Indipendentemente dalle impostazioni, Maiusc + Fare clic su sull'icona Boost %{icon} aumenterà immediatamente." discovery: Scoperta localization: body: Mastodon è tradotto da volontari. @@ -1589,6 +1604,13 @@ it: expires_at: Scade uses: Utilizzi title: Invita persone + link_preview: + author_html: Di %{name} + potentially_sensitive_content: + action: Clicca per mostrare + confirm_visit: Si è sicuri di voler aprire questo link? + hide_button: Nascondi + label: Contenuto potenzialmente sensibile lists: errors: limit: Hai raggiunto il numero massimo di liste @@ -1653,7 +1675,7 @@ it: disabled_account: Il tuo account attuale non sarà più pienamente utilizzabile. Tuttavia, avrai accesso all'esportazione dei dati e alla riattivazione. followers: Questa azione sposterà tutti i follower dall'account attuale al nuovo account only_redirect_html: In alternativa, puoi solo impostare un reindirizzamento sul tuo profilo. - other_data: Nessun altro dato verrà spostato automaticamente + other_data: Nessun altro dato verrà trasferito automaticamente (compresi i tuoi post e l'elenco degli account che segui) redirect: Il profilo del tuo account corrente sarà aggiornato con un avviso di ridirezione e sarà escluso dalle ricerche moderation: title: Moderazione @@ -1687,16 +1709,22 @@ it: body: 'Sei stato menzionato da %{name} su:' subject: Sei stato menzionato da %{name} title: Nuova menzione + moderation_warning: + subject: Hai ricevuto un avviso di moderazione poll: subject: Un sondaggio da %{name} è terminato quote: body: 'Il tuo post è stato citato da %{name}:' subject: "%{name} ha citato il tuo post" title: Nuova citazione + quoted_update: + subject: "%{name} ha modificato un post che hai citato" reblog: body: 'Il tuo status è stato condiviso da %{name}:' subject: "%{name} ha condiviso il tuo status" title: Nuova condivisione + severed_relationships: + subject: Hai perso le connessioni a causa di una decisione della moderazione status: subject: "%{name} ha appena pubblicato un post" update: @@ -1899,6 +1927,9 @@ it: other: "%{count} video" boosted_from_html: Condiviso da %{acct_link} content_warning: 'Avviso di contenuto: %{warning}' + content_warnings: + hide: Nascondi il post + show: Mostra di più default_language: Come la lingua dell'interfaccia disallowed_hashtags: one: 'contiene un hashtag non permesso: %{tags}' @@ -1907,16 +1938,22 @@ it: errors: in_reply_not_found: Il post a cui stai tentando di rispondere non sembra esistere. quoted_status_not_found: Il post che stai cercando di citare non sembra esistere. + quoted_user_not_mentioned: Non è possibile citare un utente non menzionato in un post di menzione privata. over_character_limit: Limite caratteri superato di %{max} pin_errors: direct: I messaggi visibili solo agli utenti citati non possono essere fissati in cima limit: Hai già fissato in cima il massimo numero di post ownership: Non puoi fissare in cima un post di qualcun altro reblog: Un toot condiviso non può essere fissato in cima + quote_error: + not_available: Post non disponibile + pending_approval: Post in attesa + revoked: Post rimosso dall'autore quote_policies: followers: Solo i seguaci nobody: Solo io public: Chiunque + quote_post_author: Citato un post di %{acct} title: '%{name}: "%{quote}"' visibilities: direct: Menzione privata @@ -2151,3 +2188,6 @@ it: not_supported: Questo browser non supporta le chiavi di sicurezza otp_required: Per utilizzare le chiavi di sicurezza, prima abilita l'autenticazione a due fattori. registered_on: Registrato il %{date} + wrapstodon: + description: Guarda come %{name} ha utilizzato Mastodon quest'anno! + title: Wrapstodon %{year} per %{name} diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 184ab506d64a4a..c9d4642d5ad6b5 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -572,6 +572,8 @@ ja: title: モデレーション moderation_notes: create: モデレーションノートを追加 + description_html: 他のモデレーターと将来の自分にメモを残してください + destroyed_msg: インスタンス・モデレーションノートを正常に削除しました! title: モデレーションメモ private_comment: コメント (非公開) public_comment: コメント (公開) @@ -816,7 +818,6 @@ ja: title: デフォルトで検索エンジンによるインデックスを拒否する discovery: follow_recommendations: おすすめフォロー - preamble: Mastodon を知らないユーザーを取り込むには、興味深いコンテンツを浮上させることが重要です。サーバー上で様々なディスカバリー機能がどのように機能するかを制御します。 privacy: プライバシー profile_directory: ディレクトリ public_timelines: 公開タイムライン @@ -827,6 +828,9 @@ ja: all: 誰にでも許可 disabled: 誰にも許可しない users: ログイン済みローカルユーザーのみ許可 + landing_page: + values: + trends: トレンド registrations: moderation_recommandation: 登録受付を開始する前に、迅速かつ適切にモデレーションを行うチームを編成しましょう! preamble: あなたのサーバー上でアカウントを作成できるユーザーを制御します。 @@ -880,6 +884,7 @@ ja: no_status_selected: 何も選択されていないため、変更されていません open: 投稿を開く original_status: オリジナルの投稿 + quotes: 引用 reblogs: ブースト replied_to_html: "%{acct_link}さんへの返信" status_changed: 投稿を変更しました @@ -887,6 +892,7 @@ ja: title: 投稿一覧 - @%{name} trending: トレンド view_publicly: 元の投稿を開く + view_quoted_post: 引用されている投稿を見る visibility: 公開範囲 with_media: メディアあり strikes: @@ -1079,6 +1085,7 @@ ja: matches_exactly_html: "%{string}に一致" new: create: ルールを作成 + title: ユーザー名ルールを作成 title: ユーザー名ルール warning_presets: add_new: 追加 @@ -1151,10 +1158,10 @@ ja: hint_html: 他のアカウントからこのアカウントにフォロワーを引き継いで引っ越したい場合、ここでエイリアスを作成しておく必要があります。エイリアス自体は無害で、取り消すことができます。引っ越しは以前のアカウント側から開始する必要があります。 remove: エイリアスを削除 appearance: - advanced_web_interface: 上級者向けUI - advanced_web_interface_hint: ディスプレイを幅いっぱいまで活用したい場合、上級者向け UI をおすすめします。ホーム、通知、連合タイムライン、更にはリストやハッシュタグなど、様々な異なるカラムから望む限りの情報を一度に受け取れるような設定が可能になります。 + advanced_settings: 詳細設定 animations_and_accessibility: アニメーションとアクセシビリティー - confirmation_dialogs: 確認ダイアログ + boosting_preferences: ブースト設定 + boosting_preferences_info_html: "Tip: 設定に関係なく、Shift を押しながら %{icon} Boostアイコンをクリックすると、すぐにブーストされます。" discovery: 見つける localization: body: Mastodonは有志によって翻訳されています。 @@ -1536,6 +1543,9 @@ ja: expires_at: 有効期限 uses: 使用 title: 新規ユーザーの招待 + link_preview: + potentially_sensitive_content: + hide_button: 非表示 lists: errors: limit: リストの上限数に達しています @@ -1600,7 +1610,6 @@ ja: disabled_account: 引っ越した後はデータのエクスポートと再有効化を除きほとんどの機能が利用できなくなります followers: すべてのフォロワーを現在のアカウントから新しいアカウントに引き継ぎます only_redirect_html: または、フォロワーを残したまま引っ越すこともできます。 - other_data: その他のデータは自動的に引き継がれません redirect: プロフィールに引っ越し済みの通知が表示され、検索結果から除外されます moderation: title: モデレーション @@ -1685,6 +1694,8 @@ ja: too_few_options: は複数必要です too_many_options: は%{max}個までです vote: 投票 + posting_defaults: + explanation: これらの設定は投稿を新規作成するときにデフォルトとして使用されますが、作成画面で投稿ごとに変更できます。 preferences: other: その他 posting_defaults: デフォルトの投稿設定 @@ -1855,6 +1866,7 @@ ja: public: 誰でも title: '%{name}: "%{quote}"' visibilities: + direct: 非公開の返信 private: フォロワーのみ public: 公開 statuses_cleanup: diff --git a/config/locales/kab.yml b/config/locales/kab.yml index 9ec00b897aa910..62bc46143dcb00 100644 --- a/config/locales/kab.yml +++ b/config/locales/kab.yml @@ -33,6 +33,11 @@ kab: new_email: Imayl amaynut submit: Beddel imayl title: Beddel imayl-ik s %{username} + change_role: + edit_roles: Sefrek timlilin n usqdac + label: Snifel tamlilt + no_role: War tamlilt + title: Snifel tamlilt n %{username} confirm: Sentem confirmed: Yettwasentem confirming: Asentem @@ -95,6 +100,7 @@ kab: reset: Wennez reset_password: Beddel awal uffir resubscribe: Ales ajerred + role: Tamlilt search: Nadi search_same_ip: Imseqdacen-nniḍen s tansa IP am tinn-ik security: Taɣellist @@ -121,6 +127,7 @@ kab: whitelisted: Deg tebdert tamellalt action_logs: action_types: + change_role_user: Snifel tamlilt n useqdac confirm_user: Sentem aseqdac create_announcement: Rnu-d ulɣu create_custom_emoji: Rnu imujit udmawan @@ -128,6 +135,7 @@ kab: create_domain_block: Rnu-d asewḥel n taɣult create_ip_block: Rnu alugen n IP create_unavailable_domain: Rnu-d taɣult ur nelli ara + create_user_role: Snulfu-d tamlilt destroy_announcement: Kkes ulɣu destroy_custom_emoji: Kkes imujit udmawan destroy_domain_allow: Kkes taɣult yettusirgen @@ -135,6 +143,7 @@ kab: destroy_ip_block: Kkes alugen n IP destroy_status: Kkes tasufeɣt destroy_unavailable_domain: Kkes taɣult ur nelli ara + destroy_user_role: Senger tamlilt disable_2fa_user: Gdel 2FA disable_custom_emoji: Sens imujit udmawan disable_user: Sens aseqdac @@ -150,6 +159,7 @@ kab: update_custom_emoji: Leqqem imuji udmawan update_domain_block: Leqqem iḥder n taɣult update_status: Leqqem tasufeɣt + update_user_role: Leqqem tamlilt actions: assigned_to_self_report_html: "%{name} imudd aneqqis %{target} i yiman-nsen" create_account_warning_html: "%{name} yuzen alɣu i %{target}" @@ -275,6 +285,7 @@ kab: ip: Tansa IP providers: active: D urmid + base_url: URL n taffa delete: Kkes edit: Ẓreg asaǧǧaw finish_registration: Fakk ajerred @@ -383,6 +394,7 @@ kab: resolved: Fran status: Addad title: Ineqqisen + unknown_action_msg: 'Tigawt tarussint: %{action}' unresolved: Ur yefra ara updated_at: Yettwaleqqem view_profile: Wali amaɣnu @@ -400,7 +412,10 @@ kab: privileges: administrator: Anedbal manage_federation: Sefrek Tafidiralit + manage_roles: Sefrek ilugan + manage_rules: Sefrek ilugan manage_settings: Asefrek n iɣewwaṛen + manage_users: Sefrek iqeddacen view_dashboard: Timẓriwt n tfelwit rules: add_new: Rnu alugen @@ -425,6 +440,10 @@ kab: all: I medden akk disabled: Ɣef ula yiwen users: Ɣef yimseqdacen idiganen i yeqqnen + landing_page: + values: + about: Ɣef + trends: Inezzaɣ registrations: title: Ajerred registrations_mode: @@ -436,6 +455,7 @@ kab: delete: Kkes afaylu yulin software_updates: documentation_link: Issin ugar + title: Llan ileqman type: Anaw version: Lqem statuses: @@ -449,7 +469,9 @@ kab: language: Tutlayt media: title: Amidya + metadata: Iɣefisefka open: Ldi tasuffeɣt + quotes: Tinebdurin status_title: Tasuffeɣt sɣur @%{name} trending: Inezzaɣ visibility: Abani @@ -476,12 +498,16 @@ kab: terms_of_service: changelog: Amaynut create: Sqedcet ayla-nwen + current: Amiran draft: Arewway generates: action: Sirew + title: Tawila n tewtilin n useqdec history: Amazray live: Srid + notify_users: Selɣu iseqdacen publish: Asuffeɣ + published_on_html: Teffeɣ-d ass n %{date} save_draft: Sekles arewway title: Tiwtilin n useqdec title: Tadbelt @@ -491,6 +517,7 @@ kab: allow: Sireg aseɣwen title: Iseɣwan inezzaɣ statuses: + allow_account: Sireg ameskar title: Tisuffaɣ tinezzaɣ tags: dashboard: @@ -506,11 +533,13 @@ kab: new: create: Rnu alugen title: Rnu alugen n useqdac amaynut + title: Ilugan n yisem n useqdac warning_presets: add_new: Rnu amaynut delete: Kkes webhooks: delete: Kkes + disable: Kkes armad enable: Rmed enabled: D urmid admin_mailer: @@ -525,7 +554,7 @@ kab: new_trending_tags: title: Ihacṭagen inezzaɣ appearance: - advanced_web_interface: Agrudem n web leqqayen + advanced_settings: Iɣewwaren leqqayen discovery: Asnirem localization: body: Mastodon suqqlen-t-id yiwiziwen. @@ -533,7 +562,9 @@ kab: guide_link_text: Yal yiwen·t y·tezmer a ttekki. sensitive_content: Agbur amḥulfu application_mailer: + notification_preferences: Snifel imenyafen n imayl salutation: "%{name}," + settings: 'Snifel imenyafen n imayl: %{link}' view: 'Ẓaṛ:' view_profile: Ssken-d amaɣnu view_status: Ssken-d tasuffiɣt @@ -566,10 +597,10 @@ kab: migrate_account: Gujj ɣer umiḍan nniḍen or_log_in_with: Neɣ eqqen s progress: - confirm: Sentem imayl - details: Isalli-inek + confirm: Asentem n imayl + details: Isalli-inek·inem review: Tamuɣli-nneɣ - rules: Qbel ilugan + rules: Abal n ilugan providers: cas: CAS saml: SAML @@ -599,6 +630,8 @@ kab: account_status: Addad n umiḍan functional: Amiḍan-inek·inem yetteddu s lekmal-is. use_security_key: Seqdec tasarut n teɣlist + user_agreement_html: Ɣriɣ yerna qebleɣ " target="_blank">tiwtilin ne useqdec akked tsertit n tbaḍnit + user_privacy_agreement_html: Ɣriɣ yerna qebleɣ tasertit n tbaḍnit author_attribution: example_title: Amedya n weḍris more_from_html: Ugar s ɣur %{name} @@ -669,6 +702,7 @@ kab: filters: contexts: account: Imeɣna + home: Agejdan akked tebdarin notifications: Ilɣa thread: Idiwenniyen edit: @@ -708,6 +742,9 @@ kab: modes: merge: Smezdi overwrite: Semselsi + states: + finished: Immed + status: Addad types: blocking: Tabdart n yimiḍanen iweḥlen bookmarks: Ticraḍ @@ -737,6 +774,12 @@ kab: table: expires_at: Ad ifat di title: Ɛreḍ-d kra n yimdanen + link_preview: + author_html: S ɣur %{name} + potentially_sensitive_content: + action: Sit i uskan + confirm_visit: Tebɣiḍ s tidet ad teldiḍ aseɣwen-a? + hide_button: Ffer-it login_activities: authentication_methods: password: awal uffir @@ -810,6 +853,8 @@ kab: status: Addad n umiḍan rss: content_warning: 'Alɣu n ugbur :' + self_destruct: + title: Aqeddac-a ad yemdel sessions: activity: Armud aneggaru browser: Iminig @@ -848,6 +893,7 @@ kab: kai_os: KaiOS linux: Linux mac: macOS + unknown_platform: Tiɣeṛɣeṛt tarussint windows: Windows windows_mobile: Windows Mobile windows_phone: Tiliɣri Windows Phone @@ -886,7 +932,13 @@ kab: video: one: "%{count} n tbidyutt" other: "%{count} n tbidyutin" + content_warnings: + hide: Ffer tasuffeɣt + show: Sken-d ugar + default_language: Kif kif am tutlayt n wegrudem edited_at_html: Tettwaẓreg ass n %{date} + pin_errors: + reblog: Azuzer ur yezmir ara ad yili d unṭiḍ quote_policies: followers: Imeḍfaṛen kan nobody: Nekki kan @@ -900,6 +952,7 @@ kab: unlisted: Azayez asusam statuses_cleanup: enabled: Tukksa n tsuffaɣ tiqburin s wudem awurman + keep_pinned: Eǧǧ tisuffaɣ tunṭiḍin min_age: '1209600': 2 n yimalasen '15778476': 6 n wayyuren diff --git a/config/locales/kk.yml b/config/locales/kk.yml index b877cb09c79f83..628dcbc3f82cd2 100644 --- a/config/locales/kk.yml +++ b/config/locales/kk.yml @@ -308,10 +308,7 @@ kk: deleted_msg: Алиасты сәтті алып тастаңыз. Осы есептік жазбадан екіншіге ауысу мүмкін болмайды. remove: Алиас сілтемесін алып тастау appearance: - advanced_web_interface: Кеңейтілген веб-интерфейс - advanced_web_interface_hint: 'Егер сіз бүкіл экранның енін пайдаланғыңыз келсе, кеңейтілген веб-интерфейс сізге көптеген ақпаратты бір уақытта қалағанша көру үшін әр түрлі бағандарды конфигурациялауға мүмкіндік береді: негізгі бет, ескертпелер, жаһандық желі, тізім мен хэштегтерді.' animations_and_accessibility: Анимациялар және қолжетімділік - confirmation_dialogs: Пікірталас диалогтары discovery: Пікірталас sensitive_content: Нәзік контент application_mailer: diff --git a/config/locales/ko.yml b/config/locales/ko.yml index 5c158223c13ca2..36dabb67cf5b09 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -7,6 +7,8 @@ ko: hosted_on: "%{domain}에서 호스팅 되는 마스토돈" title: 정보 accounts: + errors: + cannot_be_added_to_collections: 이 계정은 컬렉션에 추가할 수 없습니다. followers: other: 팔로워 following: 팔로잉 @@ -784,6 +786,8 @@ ko: view_dashboard_description: 사용자가 여러 통계정보를 볼 수 있는 대시보드에 접근할 수 있도록 허용 view_devops: 데브옵스 view_devops_description: Sidekiq과 pgHero 대시보드에 접근할 수 있도록 허용 + view_feeds: 실시간, 해시태그 피드 보기 + view_feeds_description: 서버 설정에 관계 없이 실시간과 해시태그 피드에 접근할 수 있도록 허용 title: 역할 rules: add_new: 규칙 추가 @@ -825,7 +829,6 @@ ko: title: 사용자들이 기본적으로 검색엔진에 인덱싱되지 않도록 합니다 discovery: follow_recommendations: 팔로우 추천 - preamble: 흥미로운 콘텐츠를 노출하는 것은 마스토돈을 알지 못할 수도 있는 신규 사용자를 유입시키는 데 중요합니다. 이 서버에서 작동하는 다양한 발견하기 기능을 제어합니다. privacy: 개인정보 profile_directory: 프로필 책자 public_timelines: 공개 타임라인 @@ -836,6 +839,16 @@ ko: all: 모두에게 disabled: 아무에게도 안 함 users: 로그인 한 사용자에게 + feed_access: + modes: + authenticated: 로그인한 사용자들만 + disabled: 특정한 사용자 역할 필요 + public: 모두 + landing_page: + values: + about: 정보 + local_feed: 로컬 피드 + trends: 유행 registrations: moderation_recommandation: 모두에게 가입을 열기 전에 적절하고 반응이 빠른 중재 팀을 데리고 있는지 확인해 주세요! preamble: 누가 이 서버에 계정을 만들 수 있는지 제어합니다. @@ -889,6 +902,7 @@ ko: no_status_selected: 아무 것도 선택 되지 않아 어떤 게시물도 바뀌지 않았습니다 open: 게시물 열기 original_status: 원본 게시물 + quotes: 인용 reblogs: 리블로그 replied_to_html: "%{acct_link} 님에게 답장" status_changed: 게시물 변경됨 @@ -896,6 +910,7 @@ ko: title: 계정 게시물 - @%{name} trending: 유행 중 view_publicly: 공개시점으로 보기 + view_quoted_post: 인용된 게시물 보기 visibility: 공개 설정 with_media: 미디어 있음 strikes: @@ -1165,10 +1180,10 @@ ko: hint_html: 다른 계정에서 이 계정으로 옮기길 원하는 경우, 여기에서 별칭을 만들 수 있습니다, 기존 계정의 팔로워를 이쪽으로 옮기고 싶은 경우 필요한 과정입니다. 이 행동 자체는 해롭지 않고 되돌리기가 가능합니다.계정 이주는 이전 계정에서 착수하게 됩니다 remove: 별칭 연결 끊기 appearance: - advanced_web_interface: 고급 웹 인터페이스 - advanced_web_interface_hint: '화면의 가로폭을 가득 채우고 싶다면, 고급 웹 인터페이스는 한 번에 여러 정보를 볼 수 있도록 여러 컬럼을 설정할 수 있도록 합니다: 홈, 알림, 연합타임라인, 리스트, 해시태그 등' + advanced_settings: 고급 설정 animations_and_accessibility: 애니메이션과 접근성 - confirmation_dialogs: 확인 대화상자 + boosting_preferences: 부스팅 설정 + boosting_preferences_info_html: "팁: 설정에 관계 없이 %{icon}을 시프트+클릭하여 곧바로 부스트할 수 있습니다." discovery: 발견하기 localization: body: 마스토돈은 자원봉사자들에 의해 번역되었습니다. @@ -1550,6 +1565,13 @@ ko: expires_at: 만료 uses: 이용 수 title: 초대하기 + link_preview: + author_html: "%{name} 작성" + potentially_sensitive_content: + action: 클릭하여 보기 + confirm_visit: 정말로 이 링크를 여시겠습니까? + hide_button: 숨기기 + label: 민감한 컨텐츠일 수 있음 lists: errors: limit: 리스트 최대 개수를 초과합니다 @@ -1614,7 +1636,7 @@ ko: disabled_account: 이 계정은 완전한 사용이 불가능하게 됩니다. 하지만, 데이터 내보내기나 재활성화를 위해 접근할 수 있습니다. followers: 이 행동은 현재 계정의 모든 팔로워를 새 계정으로 이동시킵니다 only_redirect_html: 대신, 프로필에 리디렉션만 표시할 수도 있습니다. - other_data: 다른 어떤 데이터도 자동적으로 옮겨지지 않을 것입니다 + other_data: 다른 데이터는 자동으로 이전되지 않을 것입니다 (게시물, 팔로우 목록 등) redirect: 현재 계정 프로필은 리다이렉트 알림과 함께 업데이트 되며 검색에서 제외 됩니다 moderation: title: 중재 @@ -1703,6 +1725,8 @@ ko: too_few_options: 둘 이상의 항목이 있어야 함 too_many_options: 항목은 %{max}개를 넘을 수 없습니다 vote: 투표 + posting_defaults: + explanation: 이 설정은 새 게시물을 작성할 때 기본값으로 쓰이지만, 작성기 내에서 게시물별로 편집할 수 있습니다. preferences: other: 기타 posting_defaults: 게시물 기본설정 @@ -1855,6 +1879,9 @@ ko: other: "%{count} 개의 영상" boosted_from_html: "%{acct_link}의 글을 부스트" content_warning: '열람 주의: %{warning}' + content_warnings: + hide: 게시물 숨기기 + show: 더 보기 default_language: 화면 표시 언어와 동일하게 disallowed_hashtags: other: '허용되지 않은 해시태그를 포함하고 있습니다: %{tags}' @@ -1862,19 +1889,26 @@ ko: errors: in_reply_not_found: 답장하려는 게시물이 존재하지 않습니다. quoted_status_not_found: 인용하려는 게시물이 존재하지 않습니다. + quoted_user_not_mentioned: 개인 멘션에서 다른 사람의 게시물을 인용할 수 없습니다. over_character_limit: 최대 %{max}자까지 입력할 수 있습니다 pin_errors: direct: 멘션된 사용자들에게만 보이는 게시물은 고정될 수 없습니다 limit: 이미 너무 많은 게시물을 고정했습니다 ownership: 다른 사람의 게시물은 고정될 수 없습니다 reblog: 부스트는 고정될 수 없습니다 + quote_error: + not_available: 게시물 사용 불가 + pending_approval: 계류 중인 게시물 + revoked: 원작성자에 의해 게시물 삭제됨 quote_policies: followers: 팔로워만 nobody: 나만 public: 누구나 + quote_post_author: 인용된 %{acct} 님의 게시물 title: '%{name}: "%{quote}"' visibilities: direct: 개인 멘션 + private: 팔로워만 public: 공개 public_long: 마스토돈 내외 모두 unlisted: 조용한 공개 @@ -2104,3 +2138,5 @@ ko: not_supported: 이 브라우저는 보안 키를 지원하지 않습니다 otp_required: 보안 키를 사용하기 위해서는 2단계 인증을 먼저 활성화 해 주세요 registered_on: "%{date}에 등록됨" + wrapstodon: + title: "%{name} 님의 %{year} 랩스토돈" diff --git a/config/locales/ku.yml b/config/locales/ku.yml index b05f49cd471fc3..3c3da2ad102523 100644 --- a/config/locales/ku.yml +++ b/config/locales/ku.yml @@ -679,7 +679,6 @@ ku: title: Pêlrêçkirna bikarhêneran ji motorê lêgerînê dûr bixe discovery: follow_recommendations: Pêşniyarên şopandinê - preamble: Rûbirûbûna naveroka balkêş ji bo bikarhênerên nû yên ku li ser Mastodon kesek nas nakin pir bi bandor e. Kontrol bike ka çend taybetmendiyên vekolînê li ser rajekarê te çawa dixebite. profile_directory: Rêgeha profîlê public_timelines: Demnameya gelemperî title: Vekolîne @@ -875,10 +874,7 @@ ku: hint_html: Ku tu dixwazî ji ajimêreke din bar bike bo yekî din, li vir tu dikarî bernavekê biafirîne, ku pêdivî ye berî ku tu bi şopandina şopînerên xwe ji ajimêra kevn ber bi vê yekê biçe. Ev çalakî bi serê xwe bê ziyan û vegere.Koçberiya ajimêr ji ajimêreke kevin dest pê dike. remove: Girêdana nûçikê rake appearance: - advanced_web_interface: Navrûya tevnê yê pêşketî - advanced_web_interface_hint: 'Ku tu bixwazî tevahiya ferehiya dîmendera xwe bi kar bînî, navrûya pêşketî ya tevnê dihêle ku tu gelek stûnên cihêreng saz bikî da ku di heman demê de bi qasî ku tu dixwazî zanyariyan bibînî: Serrûpel, agahdarî, demnameya giştî, her hejmarek ji rêzik û hashtagan.' animations_and_accessibility: Anîmasyon û gihînî - confirmation_dialogs: Gotûbêjên piştrastkirî discovery: Vedîtin localization: body: Mastodon ji aliyê xêrxwazan tê wergerandin. @@ -1193,7 +1189,6 @@ ku: disabled_account: Ajimêra te ya heyî dê paşê bi tevahî neyê bikaranîn. Lê belê, tu dikarî bigihîje derxistinê daneyan û hem jî ji nû ve çalakkirinê. followers: Ev çalakî dê hemî şopînerên ji ajimêra heyî bar bike ajimêra nû only_redirect_html: Wekî din, tu dikarî tenê beralîkirinekê li ser profîla xwe bicîh bike . - other_data: Daneyên din dê bi xweberî neyên livandin redirect: Profîla ajimêra te ya heyî dê bi nîşeyeke beralîkirinê were nûve kirin û ji lêgerînan were bi dûrxistin moderation: title: Çavdêrî diff --git a/config/locales/lad.yml b/config/locales/lad.yml index 06bc932ea4d4b1..7747a18f5d7474 100644 --- a/config/locales/lad.yml +++ b/config/locales/lad.yml @@ -189,6 +189,7 @@ lad: create_relay: Kriya relevo create_unavailable_domain: Kriya domeno no desponivle create_user_role: Kriya rolo + create_username_block: Kriya regla de nombre de utilizador demote_user: Degrada utilizador destroy_announcement: Efasa pregon destroy_canonical_email_block: Efasa bloko de posta elektronika @@ -202,6 +203,7 @@ lad: destroy_status: Efasa publikasyon destroy_unavailable_domain: Efasa domeno no desponivle destroy_user_role: Efasa rolo + destroy_username_block: Suprime regla de nombre de utilizador disable_2fa_user: Inkapasita autentifikasyon en dos pasos disable_custom_emoji: Inkapasita emoji personalizados disable_relay: Dezaktiva relevo @@ -236,6 +238,7 @@ lad: update_report: Aktualiza raporto update_status: Aktualiza publikasyon update_user_role: Aktualiza rolo + update_username_block: Aktualiza regla de nombre de utilizador actions: approve_appeal_html: "%{name} acheto la solisitasyon de moderasyon de %{target}" approve_user_html: "%{name} acheto el enrejistramiento de %{target}" @@ -474,11 +477,16 @@ lad: created_at: Kriyado en delete: Efasa ip: Adreso IP + request_body: Kuerpo de la solisitud providers: active: Aktivo + base_url: URL baza delete: Efasa + edit: Edita prokurador finish_registration: Finaliza enrejistrasyon name: Nombre + providers: Prokuradores + registration_requested: Enrejistrasyon rekerida registrations: confirm: Konfirma reject: Refuza @@ -692,6 +700,7 @@ lad: delete_data_html: Efasa el profil i kontenido de @%{acct} en 30 dias si no sea desuspendido en akel tiempo preview_preamble_html: "@%{acct} resivira una avertensya komo esta:" record_strike_html: Enrejistra un amonestamiento kontra @%{acct} para ke te ayude eskalar las violasyones de reglas de este kuento en el avenir + send_email_html: Embia un mesaj de avertensia a la posta elektronika de @%{acct} warning_placeholder: Adisionalas, opsionalas razones la aksyon de moderasyon. target_origin: Orijin del kuento raportado title: Raportos @@ -768,6 +777,8 @@ lad: description_html: Aunke la majorita afirma aver meldado i estar de akodro kon los terminos de servisyo, la djente normalmente no los melda asta dempues de ke surja algun problema. Az ke sea mas kolay ver las normas de tu sirvidor de un vistazo estipulándolas en una lista de puntos. Aprova ke kada norma sea corta i kolay, ama sin estar divididas en munchos puntos. edit: Edita regla empty: Dinguna regla del sirvidor tiene sido definida. + move_down: Mueve verso abasho + move_up: Mueve verso arriva title: Reglas del sirvidor translation: Traduksyon translations: Traduksyones @@ -795,7 +806,7 @@ lad: title: Ekskluye utilizadores de la indeksasyon de los bushkadores komo preferensya predeterminada discovery: follow_recommendations: Rekomendasyones de kuentos - preamble: Ekspone kontenido enteresante a la superfisie es fundamental para inkorporar muevos utilizadores ke pueden no koneser a dinguno Mastodon. Kontrola komo fonksionan varias opsiones de diskuvrimiento en tu sirvidor. + preamble: Ekspone kontenido enteresante a la superfisie es fundamental para inkorporar muevos utilizadores ke pueden no koneser a dinguno en Mastodon. Kontrola komo fonksionan varias opsiones de diskuvrimiento en tu sirvidor. privacy: Privasita profile_directory: Katalogo de profiles public_timelines: Linyas de tiempo publikas @@ -806,6 +817,13 @@ lad: all: A todos disabled: A dinguno users: Para los utilizadores lokales ke entrado en su kuento + feed_access: + modes: + public: Todos + landing_page: + values: + about: Sovre esto + trends: Trendes registrations: moderation_recommandation: Por favor, asigurate ke tyenes una taifa de moderasyon adekuada i reaktiva antes de avrir los enrejistramyentos a todos! preamble: Kontrola ken puede kriyar un kuento en tu sirvidor. @@ -843,6 +861,7 @@ lad: back_to_account: Retorna al kuento back_to_report: Retorna a la pajina del raporto batch: + add_to_report: 'Adjusta al raporto #%{id}' remove_from_report: Kita del raporto report: Raporto contents: Kontenidos @@ -857,9 +876,12 @@ lad: no_status_selected: No se troko dinguna publikasyon al no eskojer dinguna open: Avre publikasyon original_status: Publikasyon orijinala + quotes: Sitas reblogs: Repartajasyones status_changed: Publikasyon trokada trending: Trendes + view_publicly: Ve puvlikamente + view_quoted_post: Ve puvlikasyon sitada visibility: Vizivilita with_media: Kon multimedia strikes: @@ -937,12 +959,17 @@ lad: updated_msg: Konfigurasyon de etiketas aktualizada kon sukseso terms_of_service: changelog: Ke troko + create: Uza los tuyos current: Aktual + generate: Uza modelo generates: action: Djenera + title: Konfigurasyon de terminos de servisyo history: Istorya live: En bivo + notify_users: Aviza a los utilizadores publish: Publika + published_on_html: Puvlikado el %{date} title: Terminos de servisyo title: Administrasyon trends: @@ -1011,9 +1038,22 @@ lad: trending: En trend username_blocks: add_new: Adjusta muevo + block_registrations: Bloka enrejistrasyones + comparison: + contains: Kontyene + equals: Es egual a + contains_html: Kontyene %{string} + created_msg: Regla de nombre de utilizador kriyada kon reusho delete: Efasa + edit: + title: Edita regla de nombre de utilizador + matches_exactly_html: Es egual a %{string} new: create: Kriya regla + title: Kriya mueva regla de nombre de utilizador + not_permitted: Sin permiso + title: Reglas de nombre de utilizador + updated_msg: Regla de nombre de utilizador aktualizada kon reusho warning_presets: add_new: Adjusta muevo delete: Efasa @@ -1085,10 +1125,9 @@ lad: hint_html: Si keres migrar de otro kuento a este, aki puedes kriyar un alias, kale proseder antes de ampesar a mover suivantes del kuento anterior a este. Esta aksion por si mezma es inofensiva i reversivle. La migrasyon del kuento se inisya dizde el kuento viejo. remove: Dezata alias appearance: - advanced_web_interface: Enterfaz web avanzada - advanced_web_interface_hint: 'Si keres utilizar todo el ancho de ekran, la enterfaz web avanzada te permete konfigurar varias kolumnas desferentes para ver tanta enformasyon al mezmo tiempo komo keras: Linya prinsipala, avizos, linya de tiempo federada, kualkier numero de listas i etiketas.' + advanced_settings: Konfigurasyon avansada animations_and_accessibility: Animasyones i aksesivilita - confirmation_dialogs: Dialogos de konfirmasyon + boosting_preferences: Preferensias de repartajar discovery: Diskuvrimiento localization: body: Mastodon es trezladado por volontarios. @@ -1193,6 +1232,7 @@ lad: example_title: Teksto de enshemplo more_from_html: Mas de %{name} s_blog: Blog de %{name} + title: Atribusyon del otor challenge: confirm: Kontinua hint_html: "Konsejo: No retornaremos a demandarte por el kod durante la sigiente ora." @@ -1445,6 +1485,12 @@ lad: expires_at: Kaduka uses: Uzos title: Envita a djente + link_preview: + author_html: Publikasyon de %{name} + potentially_sensitive_content: + action: Klika para amostrar + confirm_visit: Estas siguro ke keres avrir este atadijo? + hide_button: Eskonde lists: errors: limit: Tienes alkansado el karar maksimo de listas @@ -1508,7 +1554,6 @@ lad: disabled_account: Tu kuento aktual no sera kompletamente utilizable dempues. Portanto, tendras akseso a la eksportasyon de datos ansi komo a la reaktivasyon. followers: Esta aksion migrara a todos los suivantes del kuento aktual al muevo kuento only_redirect_html: Alternativamente, solo puedes poner un readreso en tu profil. - other_data: No se moveran otros datos otomatikamente redirect: El profil de tu kuento aktual se aktualizara kon un avizo de readreso i sera eskluido de las bushkedas moderation: title: Moderasyon @@ -1544,6 +1589,10 @@ lad: title: Mueva enmentadura poll: subject: Una anketa de %{name} eskapo + quote: + body: 'Tu publikasyon fue sitada por %{name}:' + subject: "%{name} sito tu publikasyon" + title: Mueva sita reblog: body: 'Tu publikasyon fue repartajada por %{name}:' subject: "%{name} repartajo tu publikasyon" @@ -1649,6 +1698,7 @@ lad: scheduled_statuses: over_daily_limit: Tienes superado el limito de %{limit} publikasyones programadas para akel diya over_total_limit: Tienes superado el limito de %{limit} publikasyones programadas + too_soon: data tiene ke ser en el avenir self_destruct: lead_html: Malorozamente, %{domain} va serrar permanentemente. Si teniyas un kuento ayi, ya no podras utilizarlo, ama ainda puedes solisitar una kopya de tus datos. title: Este sirvidor esta serrando @@ -1717,6 +1767,7 @@ lad: preferences: Preferensyas profile: Profil publiko relationships: Segidos i suivantes + severed_relationships: Relasyones kortadas statuses_cleanup: Efasasyon otomatika de publikasyones strikes: Amonestamientos de moderasyon two_factor_authentication: Autentifikasyon en dos pasos @@ -1724,6 +1775,8 @@ lad: severed_relationships: download: Abasha (%{count}) event_type: + account_suspension: Suspensyon de kuento (%{target_name}) + domain_block: Suspensyon de sirvidor (%{target_name}) user_domain_block: Blokates a %{target_name} lost_followers: Suivantes pedridos lost_follows: Segimyentos pedridos @@ -1742,6 +1795,9 @@ lad: other: "%{count} videos" boosted_from_html: Repartajado dizde %{acct_link} content_warning: 'Avertensya de kontenido: %{warning}' + content_warnings: + hide: Eskonde puvlikasyon + show: Amostra mas default_language: La mezma ke la lingua de la enterfaz disallowed_hashtags: one: 'kontenia una etiketa no permetida: %{tags}' @@ -1749,14 +1805,22 @@ lad: edited_at_html: Editado %{date} errors: in_reply_not_found: La publikasion a la ke aprovas arispondir no egziste. + quoted_status_not_found: La publikasion a la ke aprovas sitar no egziste. over_character_limit: limito de karakteres de %{max} superado pin_errors: direct: Las publikasyones ke son vizivles solo para los utilizadores enmentados no pueden fiksarse limit: Ya tienes fiksado el numero maksimo de publikasyones ownership: La publikasyon de otra persona no puede fiksarse reblog: No se puede fixar una repartajasyon + quote_error: + not_available: Puvlikasyon no desponivle + pending_approval: Puvlikasyon esta asperando + revoked: Puvlikasyon kitada por el otor quote_policies: + followers: Solo suivantes nobody: Solo yo + public: Todos + quote_post_author: Sito una puvlikasyon de %{acct} title: '%{name}: "%{quote}"' visibilities: direct: Enmentadura privada @@ -1838,6 +1902,8 @@ lad: recovery_instructions_html: Si piedres akseso a tu telefon, puedes uzar uno de los sigientes kodiches de rekuperasyon para obtener akseso a tu kuento. Mantenlos a salvo. Por enshemplo, puedes imprimirlos i guadrarlos kon otros dokumentos emportantes. webauthn: Yaves de sigurita user_mailer: + announcement_published: + subject: Pregon de servisyo appeal_approved: action: Preferensyas de kuento explanation: La apelasyon del amonestamiento kontra tu kuento del %{strike_date} ke mandates el %{appeal_date} fue achetada. Tu kuento se topa de muevo en dobro estado. @@ -1868,6 +1934,8 @@ lad: subject: Tu kuento fue aksedido dizde un muevo adreso IP title: Una mueva koneksyon kon tu kuento terms_of_service_changed: + sign_off: La taifa de %{domain} + subject: Aktualizasyones de muestros terminos de sirvisyo title: Aktualizasyon emportante warning: appeal: Embia una apelasyon diff --git a/config/locales/lt.yml b/config/locales/lt.yml index eb69f2b06d518f..031ca2428d1015 100644 --- a/config/locales/lt.yml +++ b/config/locales/lt.yml @@ -1,7 +1,7 @@ --- lt: about: - about_mastodon_html: 'Ateities socialinis tinklas: jokių reklamų ir įmonių sekimo, etiškas dizainas bei decentralizacija! Turėkite savo duomenis su „Mastodon“.' + about_mastodon_html: 'Ateities socialinis tinklas: jokių reklamų ir įmonių sekimo, etiškas dizainas bei decentralizacija! Turėkite savo duomenis su „Mastodon“!' contact_missing: Nenustatyta contact_unavailable: Nėra hosted_on: "„Mastodon“ talpinamas domene %{domain}" @@ -25,6 +25,7 @@ lt: one: Įrašas other: Įrašų posts_tab_heading: Įrašai + self_follow_error: Neleidžiama sekti savo pačių paskyros admin: account_actions: action: Atlikti veiksmą @@ -36,8 +37,9 @@ lt: created_msg: Prižiūrėjimo pastaba sėkmingai sukurta! destroyed_msg: Prižiūrėjimo pastaba sėkmingai sunaikinta! accounts: + add_email_domain_block: Blokuoti el. pašto domeną approve: Patvirtinti - approved_msg: Sėkmingai patvirtinta %{username} registracijos paraiška. + approved_msg: Sėkmingai patvirtinta %{username} registracijos paraiška are_you_sure: Ar esi įsitikinęs (-usi)? avatar: Avataras by_domain: Domenas @@ -47,13 +49,13 @@ lt: label: Keisti el. paštą new_email: Naujas el. paštas submit: Keisti el. paštą - title: Keisti el. paštą %{username} + title: Keisti el. paštą vartotjui %{username} change_role: changed_msg: Vaidmuo sėkmingai pakeistas. edit_roles: Tvarkyti naudotojų vaidmenis label: Keisti vaidmenį no_role: Jokios vaidmenį - title: Keisti vaidmenį %{username} + title: Keisti teises vartotojui %{username} confirm: Patvirtinti confirmed: Patvirtinta confirming: Patvirtinama @@ -61,8 +63,9 @@ lt: delete: Ištrinti duomenis deleted: Ištrinta demote: Pažeminti - destroyed_msg: "%{username} duomenys dabar laukia eilėje, kad būtų netrukus ištrinti." + destroyed_msg: "%{username} duomenys dabar laukia eilėje, kad būtų netrukus ištrinti" disable: Sustabdyti + disable_sign_in_token_auth: Išjungti el. pašto prieigos rakto tapatybės nustatymą disable_two_factor_authentication: Išjungti 2FA disabled: Pristabdyta display_name: Rodomas vardas @@ -71,8 +74,9 @@ lt: email: El. paštas email_status: El. pašto būsena enable: Panaikinti sustabdymą + enable_sign_in_token_auth: Įjungti el. pašto prieigos rakto tapatybės nustatymą enabled: Įjungta - enabled_msg: Sėkmingai panaikintas %{username} paskyros sustabdymas. + enabled_msg: Sėkmingai %{username} paskyros sustabdymas panaikintas followers: Sekėjai follows: Seka header: Antraštė @@ -90,7 +94,7 @@ lt: media_attachments: Medijos priedai memorialize: Paversti į memorialinį memorialized: Memorializuota - memorialized_msg: Sėkmingai paversta %{username} į memorialinę paskyrą. + memorialized_msg: Sėkmingai %{username} paskyra paversta į memorialinę moderation: active: Aktyvus all: Visi @@ -119,15 +123,15 @@ lt: public: Viešas push_subscription_expires: PuSH prenumerata baigiasi redownload: Atnaujinti profilį - redownloaded_msg: Sėkmingai atnaujintas %{username} profilis iš kilmės šaltinio. + redownloaded_msg: Sėkmingai atnaujintas %{username} profilis iš pradinio šaltinio reject: Atmesti - rejected_msg: Sėkmingai atmesta %{username} registracijos paraiška. + rejected_msg: Sėkmingai atmesta %{username} registracijos paraiška remote_suspension_irreversible: Šios paskyros duomenys negrįžtamai ištrinti. remote_suspension_reversible_hint_html: Paskyra jų serveryje buvo pristabdyta, o duomenys bus visiškai pašalinti %{date}. Iki to laiko nuotolinis serveris gali atkurti šią paskyrą be jokių neigiamų pasekmių. Jei nori iš karto pašalinti visus paskyros duomenis, gali tai padaryti toliau. remove_avatar: Pašalinti avatarą remove_header: Pašalinti antraštę - removed_avatar_msg: Sėkmingai pašalintas %{username} avataro vaizdas. - removed_header_msg: Sėkmingai pašalintas %{username} antraštės vaizdas. + removed_avatar_msg: Sėkmingai pašalintas %{username} avataro vaizdas + removed_header_msg: Sėkmingai pašalintas %{username} antraštės paveikslėlis resend_confirmation: already_confirmed: Šis naudotojas jau patvirtintas. send: Iš naujo siųsti patvirtinimo nuorodą @@ -137,13 +141,14 @@ lt: resubscribe: Prenumeruoti iš naujo role: Vaidmuo search: Ieškoti + search_same_email_domain: Kiti naudotojai su tuo pačiu el. pašto domenu search_same_ip: Kiti naudotojai su tuo pačiu IP security: Saugumas security_measures: only_password: Tik slaptažodis password_and_2fa: Slaptažodis ir 2FA - sensitive: Priversti žymėti kaip jautrią - sensitized: Pažymėta kaip jautri + sensitive: Priversti žymėti kaip su jautria informacija + sensitized: Pažymėti kaip jautrią informaciją shared_inbox_url: Bendras gautiejų URL show: created_reports: Sukurtos ataskaitos @@ -161,7 +166,7 @@ lt: unblock_email: Atblokuoti el. pašto adresą unblocked_email_msg: Sėkmingai atblokuotas %{username} el. pašto adresas. unconfirmed_email: Nepatvirtintas el. pašto adresas. - undo_sensitized: Atšaukti privertimą žymėti kaip jautrią + undo_sensitized: Atšaukti privertimą žymėti kaip jautrią informaciją undo_silenced: Atšaukti ribojimą undo_suspension: Atšaukti pristabdymą unsilenced_msg: Sėkmingai atšauktas %{username} paskyros ribojimas. @@ -177,13 +182,16 @@ lt: approve_appeal: Patvirtinti apeliaciją approve_user: Patvirtinti naudotoją assigned_to_self_report: Priskirti ataskaitą + change_email_user: Keisti el. paštą vartotjui change_role_user: Keisti naudotojo vaidmenį confirm_user: Patvirtinti naudotoją create_account_warning: Kurti įspėjimą create_announcement: Kurti skelbimą + create_canonical_email_block: Sukurti el. pašto blokavimą create_custom_emoji: Kurti pasirinktinį jaustuką create_domain_allow: Kurti domeno leidimą create_domain_block: Kurti domeno bloką + create_email_domain_block: Sukurti el. pašto domeno blokavimą create_ip_block: Kurti IP taisyklę create_relay: Kurti perdavimą create_unavailable_domain: Kurti nepasiekiamą domeną @@ -191,9 +199,11 @@ lt: create_username_block: Kurti naudotojo vardo taisyklę demote_user: Pažeminti naudotoją destroy_announcement: Ištrinti skelbimą + destroy_canonical_email_block: Ištrinti el. pašto blokavimą destroy_custom_emoji: Ištrinti pasirinktinį jaustuką destroy_domain_allow: Ištrinti domeno leidimą destroy_domain_block: Ištrinti domeno bloką + destroy_email_domain_block: Ištrinti el. pašto domeno blokavimą destroy_instance: Išvalyti domeną destroy_ip_block: Ištrinti IP taisyklę destroy_relay: Ištrinti perdavimą @@ -204,6 +214,7 @@ lt: disable_2fa_user: Išjungti 2FA disable_custom_emoji: Išjungti pasirinktinį jaustuką disable_relay: Išjungti perdavimą + disable_sign_in_token_auth_user: Išjungti el. pašto prieigos rakto tapatybės nustatymą naudotojui disable_user: Išjungti naudotoją enable_custom_emoji: Įjungti pasirinktinį jaustuką enable_relay: Įjungti perdavimą @@ -218,12 +229,12 @@ lt: resend_user: Iš naujo siųsti patvirtinimo laišką reset_password_user: Nustatyti iš naujo slaptažodį resolve_report: Išspręsti ataskaitą - sensitive_account: Priversti žymėti kaip jautrią paskyrai + sensitive_account: Nustatyti kaip jautrios informacijos paskyrą silence_account: Riboti paskyrą suspend_account: Pristabdyti paskyrą unassigned_report: Panaikinti priskyrimą ataskaitą unblock_email_account: Atblokuoti el. pašto adresą - unsensitive_account: Atšaukti privertimą žymėti kaip jautrią paskyrai + unsensitive_account: Atšaukti privertimą žymėti kaip jautrios informacijos paskyrą unsilence_account: Atšaukti riboti paskyrą unsuspend_account: Atšaukti paskyros pristabdymą update_announcement: Atnaujinti skelbimą @@ -457,9 +468,19 @@ lt: new: title: Importuoti domeno blokavimus no_file: Nėra pasirinkto failo + fasp: + providers: + sign_in: Prisijungti + title: Fediverse Auxiliary Service tiekėjai + title: FAST follow_recommendations: + description_html: "Rekomendacijos padeda naujiems vartotojams greitai rasti įdomų turinį. Kai vartotojas nėra pakankamai bendravęs su kitais, kad būtų galima suformuoti asmeninį turinį, vietoj to rekomenduojami šios paskyros. Jos perskaičiuojamos kasdien, remiantis kas pastaruoju metu sulaukė didžiausio susidomėjimo ir turi daugiausiai vietinių sekėjų tam tikra kalba." language: Kalbui status: Būsena + suppress: Slėpti rekomendacijas + suppressed: Paslėpta + title: Sekti rekomendacijas + unsuppress: Atkurti rekomendaciją instances: audit_log: title: Naujausi audito žurnalai @@ -500,6 +521,8 @@ lt: expired: Nebegaliojantis title: Filtras title: Kvietimai + ip_blocks: + delete: Ištrinti relays: add_new: Pridėti naują pamainą delete: Ištrinti @@ -562,7 +585,7 @@ lt: invite_users_description: Leidžia naudotojams pakviesti naujus žmones į serverį. manage_invites: Tvarkyti kvietimus manage_invites_description: Leidžia naudotojams naršyti ir deaktyvuoti kvietimų nuorodas. - manage_taxonomies_description: Leidžia naudotojams peržiūrėti tendencingą turinį ir atnaujinti saitažodžių nustatymus + manage_taxonomies_description: Leidžia naudotojams peržiūrėti tendencingą turinį ir atnaujinti grotažymių nustatymus settings: branding: title: Firminio ženklo kūrimas @@ -617,11 +640,13 @@ lt: no_status_selected: Jokie įrašai nebuvo pakeisti, nes nė vienas buvo pasirinktas open: Atidaryti įrašą original_status: Originalus įrašas + quotes: Paminėjimai replied_to_html: Atsakyta į %{acct_link} status_title: Paskelbė @%{name} title: Paskyros įrašai – @%{name} trending: Tendencinga view_publicly: Peržiūrėti viešai + view_quoted_post: Peržiūrėti paminėtą įrašą with_media: Su medija system_checks: database_schema_check: @@ -657,7 +682,8 @@ lt: open: Peržiūrėti viešai reset: Atkurti search: Paieška - title: Saitažodžiai + title: Grotažymė + updated_msg: Grotažymės nustatymai sėkmingai atnaujinti terms_of_service: back: Atgal į paslaugų sąlygas changelog: Kas pasikeitė @@ -745,9 +771,10 @@ lt: tag_servers_dimension: Populiariausi serveriai tag_servers_measure: skirtingi serveriai tag_uses_measure: bendri naudojimai + description_html: Tai yra grotažymės, kurios šiuo metu dažnai pasirodo daugelyje jūsų serverio matomų įrašų. Tai gali padėti jūsų vartotojams sužinoti, apie ką žmonės šiuo metu kalba daugiausiai. Grotažymės nėra rodomos viešai, kol jūs jų nepatvirtinate. listable: Gali būti siūloma not_trendable: Nepasirodys tendencijose - title: Tendencingos saitažodžiai + title: Populiarios grotažymės trendable: Gali pasirodyti tendencijose trending_rank: 'Tendencinga #%{rank}' title: Rekomendacijos ir tendencijos @@ -802,13 +829,12 @@ lt: new_trending_statuses: title: Tendencingi įrašai new_trending_tags: - title: Tendencingos saitažodžiai + title: Populiarios grotažymės subject: Naujos tendencijos peržiūrimos %{instance} appearance: - advanced_web_interface: Išplėstinė žiniatinklio sąsaja - advanced_web_interface_hint: 'Jei nori išnaudoti visą ekrano plotį, išplėstinė žiniatinklio sąsaja leidžia sukonfigūruoti daug skirtingų stulpelių, kad vienu metu matytum tiek informacijos, kiek tik nori: Pagrindinis, pranešimai, federacinė laiko skalė, bet kokie sąrašai ir saitažodžiai.' animations_and_accessibility: Animacijos ir pritaikymas neįgaliesiems - confirmation_dialogs: Patvirtinimo dialogai + boosting_preferences: Pasidalinimo nustatymai + boosting_preferences_info_html: "Patarimas: Nepriklausomai nuo nustatymų, Shift + Click ant %{icon} Dalintis ikonos iš karto pasidalins įrašu." discovery: Atradimas localization: body: Mastodon verčia savanoriai. @@ -879,6 +905,7 @@ lt: security: Apsauga set_new_password: Nustatyti naują slaptažodį setup: + email_below_hint_html: Patikrinkite savo šlamšto aplanką arba paprašykite naujo el. laiško. Jei el. pašto adresas neteisingas, galite jį pataisyti. email_settings_hint_html: Spustelėkite nuorodą, kurią atsiuntėme adresu %{email}, kad pradėtumėte naudoti „Mastodon“. Lauksime čia. link_not_received: Negavai nuorodos? title: Patikrinti pašto dėžutę @@ -933,7 +960,9 @@ lt: your_appeal_pending: Pateikei apeliaciją your_appeal_rejected: Tavo apeliacija buvo atmesta edit_profile: + basic_information: Pagrindinė informacija hint_html: "Tinkink tai, ką žmonės mato tavo viešame profilyje ir šalia įrašų. Kiti žmonės labiau linkę sekti atgal ir bendrauti su tavimi, jei tavo profilis yra užpildytas ir turi profilio nuotrauką." + other: Kita emoji_styles: auto: Automatinis native: Vietiniai @@ -967,7 +996,7 @@ lt: storage: Medijos saugykla featured_tags: add_new: Pridėti naują - hint_html: "Savo profilyje parodyk svarbiausius saitažodžius. Tai puikus įrankis kūrybiniams darbams ir ilgalaikiams projektams sekti, todėl svarbiausios saitažodžiai rodomi matomoje vietoje profilyje ir leidžia greitai pasiekti tavo paties įrašus." + hint_html: "Savo profilyje parodyk svarbiausias grotažymes. Tai puikus įrankis kūrybiniams darbams ir ilgalaikiams projektams sekti, todėl svarbiausios grotažymės rodomos matomoje vietoje profilyje ir leidžia greitai pasiekti tavo paties įrašus." filters: contexts: account: Profiliai @@ -992,7 +1021,7 @@ lt: changes_saved_msg: Pakeitimai sėkmingai išsaugoti! copy: Kopijuoti delete: Ištrinti - deselect: Panaikinti visus žymėjimus + deselect: Atžymėti visus order_by: Tvarkyti pagal save_changes: Išsaugoti pakeitimus today: šiandien @@ -1046,6 +1075,9 @@ lt: title: Tapatybės nustatymo istorija mail_subscriptions: unsubscribe: + emails: + notification_emails: + reblog: dalintis pranešimų el. pašto laiškais success_html: Daugiau negausi %{type} „Mastodon“ domene %{domain} į savo el. paštą %{email}. media_attachments: validations: @@ -1078,10 +1110,14 @@ lt: body: 'Tave %{name} paminėjo:' subject: Tave paminėjo %{name} title: Naujas paminėjimas + quote: + body: 'Tavo įrašą paminėjo %{name}:' + subject: "%{name} paminėjo jūsų įrašą" + title: Naujas paminėjimas reblog: - body: 'Tavo įrašą pakėlė %{name}:' - subject: "%{name} pakėlė tavo įrašą" - title: Naujas pakėlimas + body: 'Tavo įrašą dalinosi %{name}:' + subject: "%{name} dalinosi tavo įrašu" + title: Naujas dalinimasis notifications: administration_emails: Administratoriaus el. laiško pranešimai email_events: Įvykiai, skirti el. laiško pranešimams @@ -1136,6 +1172,9 @@ lt: status: Paskyros būsena remote_follow: missing_resource: Jūsų paskyros nukreipimo URL nerasta + rss: + descriptions: + tag: 'Vieši įrašai su grotažymėmis #%{hashtag}' scheduled_statuses: over_daily_limit: Jūs pasieketė limitą (%{limit}) galimų toot'ų per dieną over_total_limit: Jūs pasieketė %{limit} limitą galimų toot'ų @@ -1179,7 +1218,7 @@ lt: development: Kūrimas edit_profile: Redaguoti profilį export: Eksportuoti - featured_tags: Rodomi saitažodžiai + featured_tags: Išskirtos grotažymės import: Importuoti import_and_export: Importas ir eksportas migrate: Paskyros migracija @@ -1207,33 +1246,47 @@ lt: many: "%{count} vaizdo" one: "%{count} vaizdas" other: "%{count} vaizdų" - boosted_from_html: Pakelta iš %{acct_link} + boosted_from_html: Dalintasi iš %{acct_link} content_warning: 'Turinio įspėjimas: %{warning}' errors: quoted_status_not_found: Įrašas, kurį bandote cituoti, atrodo, neegzistuoja. + quoted_user_not_mentioned: Privačiame paminėjime negalima cituoti citatoje nepaminėto vartotojo. over_character_limit: pasiektas %{max} simbolių limitas pin_errors: limit: Jūs jau prisegėte maksimalų toot'ų skaičų ownership: Kitų vartotojų toot'ai negali būti prisegti - reblog: Pakeltos žinutės negali būti prisegtos + reblog: Žinutės, kuriomis pasidalinta, negali būti papildomai prisegtos + quote_error: + not_available: Įrašas nepasiekiamas + pending_approval: Įrašas peržiūrimas + revoked: Autorius pašalino įrašą + quote_policies: + followers: Tik sekėjai + nobody: Tik aš + public: Visi + quote_post_author: Paminėjo %{acct} įrašą + title: '%{name}: "%{quote}"' visibilities: public: Vieša statuses_cleanup: enabled_hint: Automatiškai ištrina įrašus, kai jie pasiekia nustatytą amžiaus ribą, nebent jie atitinka vieną iš toliau nurodytų išimčių + ignore_reblogs: Ignoruoti pasidalinimus interaction_exceptions_explanation: Atkreipk dėmesį, kad negarantuojama, jog įrašai nebus ištrinti, jei jų mėgstamumo ar pasidalinimo riba bus žemesnė, nors vieną kartą ji jau buvo viršyta. keep_polls_hint: Neištrina jokių tavo apklausų - keep_self_bookmark: Laikyti įrašus, kuriuos pažymėjai + keep_self_bookmark: Laikyti įrašus, kuriuos pažymėjai su žyma keep_self_bookmark_hint: Neištrina tavo pačių įrašų, jei esi juos pažymėjęs (-usi) keep_self_fav_hint: Neištrina tavo pačių įrašų, jei esi juos pamėgęs (-usi) min_age_label: Amžiaus riba + min_reblogs: Pasidalintus įrašus laikyti bent + min_reblogs_hint: Neištrina jokių jūsų įrašų, kuriais buvo dalintasi bent tiek kartų. Palikite tuščią laukelį, jei norite ištrinti pasidalitus įrašus, nepriklausomai nuo jų paskelbimų skaičiaus stream_entries: sensitive_content: Jautrus turinys terms_of_service: title: Paslaugų sąlygos themes: contrast: Mastodon (didelis kontrastas) - default: Mastodon (tamsi) - mastodon-light: Mastodon (šviesi) + default: Mastodon (Tamsus) + mastodon-light: Mastodon (Šviesus) system: Automatinis (naudoti sistemos temą) two_factor_authentication: add: Pridėti @@ -1318,8 +1371,8 @@ lt: follows_title: Ką sekti follows_view_more: Peržiūrėti daugiau sekamų žmonių hashtags_subtitle: Naršyk, kas tendencinga per pastarąsias 2 dienas - hashtags_title: Tendencingos saitažodžiai - hashtags_view_more: Peržiūrėti daugiau tendencingų saitažodžių + hashtags_title: Populiarios grotažymės + hashtags_view_more: Peržiūrėti daugiau populiarių grotažymių post_action: Sukurti post_step: Sakyk labas pasauliui tekstu, nuotraukomis, vaizdo įrašais arba apklausomis. post_title: Sukūrk savo pirmąjį įrašą diff --git a/config/locales/lv.yml b/config/locales/lv.yml index b57d2b3ef02c99..3ad4eb5e4ea862 100644 --- a/config/locales/lv.yml +++ b/config/locales/lv.yml @@ -160,7 +160,7 @@ lv: suspension_irreversible: Šī konta dati ir neatgriezeniski izdzēsti. Tu vari atcelt konta darbības apturēšanu, lai tas būtu izmantojams, taču tas neatjaunos iepriekšējos datus. suspension_reversible_hint_html: Konta darbība ir apturēta, un dati tiks pilnībā noņemti %{date}. Līdz tam kontu var atjaunot bez jebkādām nelabvēlīgām sekām. Ja vēlies nekavējoties noņemt visus konta datus, to vari izdarīt zemāk. title: Konti - unblock_email: Atbloķēt e-pasta adresi + unblock_email: Atcelt e-pasta adreses liegumu unblocked_email_msg: "%{username} e-pasta adreses liegšana sekmīgi atcelta" unconfirmed_email: Neapstiprināts e-pasts undo_sensitized: Atcelt uzspiestu atzīmēšanu kā jūtīgu @@ -229,7 +229,7 @@ lv: silence_account: Ierobežot Kontu suspend_account: Apturēt Kontu unassigned_report: Atcelt Pārskata Piešķiršanu - unblock_email_account: Atbloķēt e-pasta adresi + unblock_email_account: Atcelt e-pasta adreses liegumu unsensitive_account: Atsaukt uzspiestu konta atzīmēšanu kā jūtīgu unsilence_account: Atcelt Konta Ierobežošanu unsuspend_account: Atcelt konta apturēšanu @@ -252,7 +252,7 @@ lv: create_canonical_email_block_html: "%{name} liedza e-pasta adresi ar jaucējkodu %{target}" create_custom_emoji_html: "%{name} augšupielādēja jaunu emocijzīmi %{target}" create_domain_allow_html: "%{name} atļāva federāciju ar domēnu %{target}" - create_domain_block_html: "%{name} bloķēja domēnu %{target}" + create_domain_block_html: "%{name} liedza domēnu %{target}" create_email_domain_block_html: "%{name} liedza e-pasta domēnu %{target}" create_ip_block_html: "%{name} izveidoja nosacījumu priekš IP %{target}" create_unavailable_domain_html: "%{name} apturēja piegādi uz domēnu %{target}" @@ -262,7 +262,7 @@ lv: destroy_canonical_email_block_html: "%{name} atcēla e-pasta adreses liegumu ar jaucējvērtību %{target}" destroy_custom_emoji_html: "%{name} izdzēsa emocijzīmi %{target}" destroy_domain_allow_html: "%{name} neatļāva federāciju ar domēnu %{target}" - destroy_domain_block_html: "%{name} atbloķēja domēnu %{target}" + destroy_domain_block_html: "%{name} atcēla domēna %{target} liegšanu" destroy_email_domain_block_html: "%{name} atcēla e-pasta domēna %{target} liegumu" destroy_instance_html: "%{name} attīrija domēnu %{target}" destroy_ip_block_html: "%{name} izdzēsa nosacījumu priekš IP %{target}" @@ -288,7 +288,7 @@ lv: silence_account_html: "%{name} ierobežoja %{target} kontu" suspend_account_html: "%{name} apturēja %{target} kontu" unassigned_report_html: "%{name} nepiešķīra ziņojumu %{target}" - unblock_email_account_html: "%{name} atbloķēja %{target} e-pasta adresi" + unblock_email_account_html: "%{name} atcēla %{target} e-pasta adreses liegšanu" unsensitive_account_html: "%{name} atcēla %{target} informācijas nesēja atzīmēšanu kā jūtīgu" unsilence_account_html: "%{name} atcēla ierobežojumu %{target} kontam" unsuspend_account_html: "%{name} neapturēja %{target} kontu" @@ -365,7 +365,7 @@ lv: dashboard: active_users: aktīvie lietotāji interactions: mijiedarbības - media_storage: Multividesu krātuve + media_storage: Multivides uzglabāšana new_users: jauni lietotāji opened_reports: atvērtie ziņojumi pending_appeals_html: @@ -412,12 +412,12 @@ lv: preamble_html: Tu gatavojies apturēt domēna %{domain} un tā apakšdomēnu darbību. remove_all_data: Tādējādi no tava servera tiks noņemts viss šī domēna kontu saturs, multivide un profila dati. stop_communication: Tavs serveris pārtrauks sazināties ar šiem serveriem. - title: Apstiprināt domēna %{domain} bloķēšanu + title: Apstiprināt domēna %{domain} liegšanu undo_relationships: Tādējādi tiks atsauktas jebkuras sekošanas attiecības starp šo un tavu serveru kontiem. - created_msg: Domēna bloķēšana tagad tiek apstrādāta - destroyed_msg: Domēna bloķēšana ir atsaukta + created_msg: Domēna liegšana tagad tiek apstrādāta + destroyed_msg: Domēna liegšana tika atsaukta domain: Domēns - edit: Labot domēna aizturēšanu + edit: Labot domēna liegšanu existing_domain_block: Tu jau esi noteicis stingrākus ierobežojumus %{name}. existing_domain_block_html: Tu jau esi noteicis stingrākus ierobežojumus %{name}, vispirms tev jāatbloķē. export: Eksportēt @@ -832,7 +832,6 @@ lv: title: Pēc noklusējuma lietotāji būs atteikušies no meklētājprogrammu indeksēšanas discovery: follow_recommendations: Sekotšanas rekomendācijas - preamble: Interesanta satura parādīšana palīdz piesaistīt jaunus lietotājus, kuri, iespējams, nepazīst nevienu Mastodon. Kontrolē, kā tavā serverī darbojas dažādi atklāšanas līdzekļi. privacy: Konfidencialitāte profile_directory: Profila direktorija public_timelines: Publiskās ziņu lentas @@ -903,6 +902,7 @@ lv: title: Konta ieraksti - @%{name} trending: Aktuāli view_publicly: Skatīt publiski + view_quoted_post: Skatīt citēto ierakstu visibility: Redzamība with_media: Ar multividi strikes: @@ -1153,7 +1153,7 @@ lv: new_trending_statuses: title: Populārākās ziņas new_trending_tags: - title: Izplatīti tēmturi + title: Populārākie tēmturi subject: Tiek pārskatītas jaunas tendences %{instance} aliases: add_new: Izveidot aizstājvārdu @@ -1163,10 +1163,7 @@ lv: hint_html: Ja vēlies pāriet no cita konta uz šo, šeit vari izveidot aizstājvārdu, kas ir nepieciešams, lai varētu turpināt sekotāju pārvietošanu no vecā konta uz šo. Šī darbība pati par sevi ir nekaitīga un atgriezeniska. Konta migrācija tiek sākta no vecā konta. remove: Atsaistīt aizstājvārdu appearance: - advanced_web_interface: Paplašinātā tīmekļa saskarne - advanced_web_interface_hint: 'Ja vēlies izmantot visu ekrāna platumu, uzlabotā tīmekļa saskarne ļauj konfigurēt daudzas dažādas kolonnas, lai vienlaikus redzētu tik daudz informācijas, cik vēlies: Sākums, paziņojumi, apvienotā ziņu lenta, neierobežots skaits sarakstu un tēmturu.' animations_and_accessibility: Animācijas un pieejamība - confirmation_dialogs: Apstiprināšanas dialogi discovery: Atklāšana localization: body: Mastodon ir tulkojuši brīvprātīgie. @@ -1248,7 +1245,7 @@ lv: setup: email_below_hint_html: Jāpārbauda sava surogātpasta mape vai jāpieprasa vēl vienu! Savu e-pasta adresi var labot, ja tā ir nepareiza. email_settings_hint_html: Jāatver saite, kuru mēs nosūtījām uz %{email}, lai sāktu izmantot Mastodon. Mēs gaidīsim šeit pat. - link_not_received: Vai nesaņēmi sati? + link_not_received: Vai nesaņēmi saiti? new_confirmation_instructions_sent: Pēc dažām minūtēm saņemsi jaunu e-pasta ziņojumu ar apstiprinājuma saiti. title: Pārbaudi savu iesūtni sign_in: @@ -1648,7 +1645,6 @@ lv: disabled_account: Tavs pašreizējais konts pēc tam nebūs pilnībā lietojams. Tomēr tev būs piekļuve datu eksportēšanai, kā arī atkārtotai aktivizēšanai. followers: Veicot šo darbību, visi sekotāji tiks pārvietoti no pašreizējā konta uz jauno kontu only_redirect_html: Citādāk tu vari arī savā profilā ievietot tikai novirzīšanu. - other_data: Nekādi citi dati netiks automātiski pārvietoti redirect: Tava pašreizējā konta profils tiks atjaunināts ar novirzīšanas paziņojumu un tiks izslēgts no meklēšanas moderation: title: Satura pārraudzība @@ -1684,6 +1680,8 @@ lv: title: Jauna pieminēšana poll: subject: "%{name} aptauja ir beigusies" + quote: + body: 'Tavu ierakstu citēja %{name}:' reblog: body: 'Tavu ziņu izcēla %{name}:' subject: "%{name} izcēla tavu ziņu" @@ -1728,7 +1726,7 @@ lv: duration_too_short: ir par agru expired: Aptauja jau ir beigusies invalid_choice: Izvēlētā balsošanas iespēja nepastāv - over_character_limit: katrs nedrīkst būt garāks par %{max} rakstzīmēm + over_character_limit: nedrīkst būt garākas par %{max} rakstzīmēm self_vote: Tu nevari balsot pats savā aptaujā too_few_options: jābūt vairāk nekā vienam vienumam too_many_options: nevar saturēt vairāk par %{max} vienumiem @@ -1898,15 +1896,24 @@ lv: edited_at_html: Labots %{date} errors: in_reply_not_found: Šķiet, ka ziņa, uz kuru tu mēģini atbildēt, nepastāv. + quoted_status_not_found: Šķiet, ka ieraksts, kuru tu mēģini citēt, nepastāv. over_character_limit: pārsniegts %{max} rakstzīmju ierobežojums pin_errors: direct: Ierakstus, kas ir redzami tikai pieminētajiem lietotājiem, nevar piespraust limit: Jau ir piesprausts lielākais iespējamais ierakstu skaits ownership: Kāda cita ierakstu nevar piespraust reblog: Pastiprinātu ierakstu nevar piespraust + quote_error: + not_available: Ieraksts nav pieejams + quote_policies: + followers: Tikai sekotāji + nobody: Tikai es + public: Jebkurš title: "%{name}: “%{quote}”" visibilities: public: Publisks + unlisted: Klusi publisks + unlisted_long: Netiks rādīts Mastodon meklēšanas rezultātos, populārākajos, un publiskajās laikjoslās statuses_cleanup: enabled: Automātiski dzēst vecās ziņas enabled_hint: Automātiski izdzēš Tavus ierakstus, tiklīdz tie sasniedz noteiktu vecuma slieksni, ja vien tie neatbilst kādam no zemāk norādītajiem izņēmumiem @@ -2085,7 +2092,7 @@ lv: other: "%{people} cilvēki pēdējās 2 dienās" zero: "%{people} cilvēku pēdējās divās dienās" hashtags_subtitle: Izpēti, kas pēdējās divās dienās ir piesasitījis cilvēku uzmanību - hashtags_title: Izplatīti tēmturi + hashtags_title: Populārākie tēmturi hashtags_view_more: Skatīt vairāk izplatītu tēmturu post_action: Rakstīt post_step: Pasveicini pasauli ar tekstu, fotoattēliem, video vai aptaujām! diff --git a/config/locales/ms.yml b/config/locales/ms.yml index 2df1e0c2b8fdd8..a89ae26283488e 100644 --- a/config/locales/ms.yml +++ b/config/locales/ms.yml @@ -714,7 +714,6 @@ ms: title: Tarik pengguna keluar daripada pengindeksan enjin carian secara lalai discovery: follow_recommendations: Ikut cadangan - preamble: Memaparkan kandungan yang menarik adalah penting dalam memasukkan pengguna baharu yang mungkin tidak mengenali sesiapa Mastodon. Kawal cara pelbagai ciri penemuan berfungsi pada server anda. profile_directory: Direktori profil public_timelines: Garis masa awam publish_statistics: Terbitkan statistik @@ -957,10 +956,7 @@ ms: hint_html: Jika anda ingin beralih dari akaun lain ke akaun ini, di sini anda boleh membuat alias, yang diperlukan sebelum anda boleh meneruskan dengan memindahkan pengikut dari akaun lama ke akaun ini. Tindakan ini dengan sendirinya tidak berbahaya dan boleh diterbalikkan. Penghijrahan akaun dimulakan daripada akaun lama. remove: Nyahpaut alias appearance: - advanced_web_interface: Antara muka web lanjutan - advanced_web_interface_hint: 'Jika anda ingin menggunakan keseluruhan lebar skrin anda, antara muka web lanjutan membolehkan anda mengkonfigurasi banyak lajur berbeza untuk melihat seberapa banyak maklumat pada masa yang sama seperti yang anda mahu: Laman Utama, pemberitahuan, garis masa bersekutu, sebarang bilangan senarai dan hashteg.' animations_and_accessibility: Animasi dan kebolehaksesan - confirmation_dialogs: Dialog pengesahan discovery: Penemuan localization: body: Mastodon diterjemahkan oleh sukarelawan. @@ -1325,7 +1321,6 @@ ms: disabled_account: Akaun semasa anda tidak akan dapat digunakan sepenuhnya selepas itu. Walau bagaimanapun, anda akan mempunyai akses kepada eksport data serta pengaktifan semula. followers: Tindakan ini akan mengalihkan semua pengikut daripada akaun semasa ke akaun baharu only_redirect_html: Sebagai alternatif, anda boleh hanya meletakkan ubah hala pada profil anda. - other_data: Tiada data lain akan dialihkan secara automatik redirect: Profil akaun semasa anda akan dikemas kini dengan notis ubah hala dan dikecualikan daripada carian moderation: title: Penyederhanaan diff --git a/config/locales/my.yml b/config/locales/my.yml index 644e648ac54f93..90b0ca0a6d626d 100644 --- a/config/locales/my.yml +++ b/config/locales/my.yml @@ -709,7 +709,6 @@ my: title: ပုံမှန်အားဖြင့် ရှာဖွေမှုအညွှန်းကိန်းမှ သုံးစွဲသူများကို ဖယ်ထုတ်ပါ discovery: follow_recommendations: အကြံပြုချက်များကို စောင့်ကြည့်ပါ - preamble: စိတ်ဝင်စားစရာကောင်းသော အကြောင်းအရာများပြထားခြင်းမှာ Mastodon ကို မသိသေးသော သုံးစွဲသူအသစ်များအတွက် အရေးပါပါသည်။ သင့်ဆာဗာတွင် မည်သည့်ရှာဖွေတွေ့ရှိမှုအကြောင်းအရာများ ပြထားမည်ကို ထိန်းချုပ်ပါ။ profile_directory: ပရိုဖိုင်လမ်းညွှန် public_timelines: အများမြင်စာမျက်နှာ publish_statistics: စာရင်းဇယားထုတ်ပြန်မည် @@ -938,10 +937,7 @@ my: hint_html: အခြားအကောင့်မှ ဤအကောင့်သို့ ပြောင်းရွှေ့လိုပါက ဤနေရာတွင် အကောင့်ဟောင်းမှ စောင့်ကြည့်သူများကို ဤအကောင့်သို့ မရွှေ့မီ လိုအပ်သော အမည်တစ်ခု ဖန်တီးနိုင်ပါသည်။ ဤလုပ်ဆောင်ချက်မှာ အန္တရာယ်ကင်းပြီး ပြန်ပြောင်းလုပ်ဆောင်နိုင်ပါသည်အကောင့်ပြောင်းရွှေ့ခြင်းကို အကောင့်ဟောင်းမှ စတင်လုပ်ဆောင်ပါသည်။ remove: နာမည်တူများကို လင့်ခ်ဖြုတ်ပါ appearance: - advanced_web_interface: အဆင့်မြင့်ဝဘ်ပုံစံ - advanced_web_interface_hint: အဆင့်မြင့်ဝဘ်အင်တာဖေ့စ်သည် မျက်နှာပြင်အကျယ်တစ်ခုလုံးကို သင် အသုံးပြုလိုပါက သင်အလိုရှိသည့်အတိုင်း အချက်အလက်များကို တစ်ပြိုင်နက်ကြည့်ရှုရန် ကော်လံများစွာဖြင့် ပြသနိုင်သည် - ပင်မစာမျက်နှာ၊ အကြောင်းကြားချက်များ၊ ဖက်ဒီစာမျက်နှာ၊ စာရင်းအရေအတွက်နှင့် hashtags မှန်သမျှကို ချိန်ညှိဖော်ပြနိုင်သည်။ animations_and_accessibility: လှုပ်ရှားမှုဆိုင်ရာများ - confirmation_dialogs: အတည်ပြုချက် ဒိုင်ယာလော့ခ်များ discovery: ရှာဖွေတွေ့ရှိမှု localization: body: Mastodon ကို စေတနာ့ဝန်ထမ်းများမှ ဘာသာပြန်ထားပါသည်။ @@ -1320,7 +1316,6 @@ my: disabled_account: နောက်ပိုင်းတွင် သင့်လက်ရှိအကောင့်အား အပြည့်အဝအသုံးပြုနိုင်တော့မည်မဟုတ်သော်လည်း အချက်အလက်ထုတ်ယူခြင်းနှင့် ပြန်လည်အတည်ပြုခြင်းတို့ကို ဆောင်ရွက်နိုင်မည်ဖြစ်သည်။ followers: ဤလုပ်ဆောင်ချက်မှာ စောင့်ကြည့်သူအားလုံးကို လက်ရှိအကောင့်မှ အကောင့်အသစ်သို့ ရွှေ့ပြောင်းခြင်းဖြစ်သည် only_redirect_html: တနည်းအားဖြင့် သင်သည် သင့်ပရိုဖိုင်ပေါ်တွင် ပြန်ညွှန်းခြင်းကိုသာ ပြုလုပ်နိုင်သည်။ - other_data: အခြားအချက်အလက်များကို အလိုအလျောက်ရွှေ့မည်မဟုတ်ပါ redirect: သင့်လက်ရှိအကောင့်၏ပရိုဖိုင်ကို ပြန်လည်ညွှန်းပေးသည့်အသိပေးချက်ဖြင့် ပြင်ဆင်ပေးမည်ဖြစ်ပြီး ရှာဖွေမှုများမှ ဖယ်ထုတ်ပေးမည်ဖြစ်သည် moderation: title: စိစစ်ခြင်း diff --git a/config/locales/nan.yml b/config/locales/nan.yml index 21474c6d70dcfd..e51b8f506b4d63 100644 --- a/config/locales/nan.yml +++ b/config/locales/nan.yml @@ -7,6 +7,8 @@ nan: hosted_on: 佇 %{domain} 運作 ê Mastodon站 title: 關係本站 accounts: + errors: + cannot_be_added_to_collections: Tsit ê口座袂當加入kàu集合。 followers: other: 跟tuè ê following: Leh跟tuè @@ -31,7 +33,7 @@ nan: created_msg: 管理記錄成功建立! destroyed_msg: 管理記錄成功thâi掉! accounts: - add_email_domain_block: 封鎖電子phue ê網域 + add_email_domain_block: 封鎖電子phue ê域名 approve: 允准 approved_msg: 成功審核 %{username} ê註冊申請ah are_you_sure: Lí kám確定? @@ -213,7 +215,7 @@ nan: enable_user: 啟用口座 memorialize_account: 設做故人ê口座 promote_user: Kā用者升級 - publish_terms_of_service: 公佈服務ê使用規則 + publish_terms_of_service: 公佈服務規定 reject_appeal: 拒絕申訴 reject_user: 拒絕用者 remove_avatar_user: Thâi掉標頭 @@ -281,7 +283,7 @@ nan: enable_user_html: "%{name} kā 用者 %{target} 設做允准登入" memorialize_account_html: "%{name} kā %{target} 設做故人口座" promote_user_html: "%{name} kā 用者 %{target} 升級" - publish_terms_of_service_html: "%{name} 公佈服務規則ê更新" + publish_terms_of_service_html: "%{name} 公佈服務規定ê更新" reject_appeal_html: "%{name} 拒絕 %{target} 所寫ê tuì管理決定ê投訴" reject_user_html: "%{name} 拒絕 %{target} ê 註冊" remove_avatar_user_html: "%{name} thâi掉 %{target} ê標頭" @@ -412,7 +414,7 @@ nan: stop_communication: Lí ê服侍器ē停止kap hia ê服侍器聯絡。 title: 確認封鎖域名 %{domain} undo_relationships: Tse ē取消任何ê佇in ê服侍器ê口座kap lí ê之間ê跟tuè關係。 - created_msg: 當leh封鎖網域 + created_msg: 當leh封鎖域名 destroyed_msg: 已經取消封鎖域名 domain: 域名 edit: 編輯域名封鎖 @@ -457,12 +459,12 @@ nan: new: create: 加添域名 resolve: 解析域名 - title: 封鎖新ê電子phue網域 + title: 封鎖新ê電子phue ê域名 no_email_domain_block_selected: 因為無揀任何電子phue域名封鎖,所以lóng無改變 not_permitted: 無允准 resolved_dns_records_hint_html: 域名解析做下kha ê MX域名,tsiah ê域名上後負責收電子phue。封鎖MX域名ē封任何有siâng款MX域名ê電子郵件ê註冊,就算通看見ê域名無kâng,mā án-ne。Tio̍h細膩,m̄通封鎖主要ê電子phue提供者。 resolved_through_html: 通過 %{domain} 解析 - title: 封鎖ê電子phue網域 + title: 封鎖ê電子phue域名 export_domain_allows: new: title: 輸入允准ê域名 @@ -659,7 +661,7 @@ nan: are_you_sure: Lí kám確定? assign_to_self: 分配hōo家kī assigned: 分配管理者 - by_target_domain: 受檢舉ê口座ê網域 + by_target_domain: 受檢舉ê口座ê域名 cancel: 取消 category: 類別 category_description_html: Tsit ê 受檢舉ê口座kap/á是內容,ē佇kap tsit ê口座ê聯絡內底引用。 @@ -782,12 +784,14 @@ nan: view_dashboard_description: 允准用者接近使用tsit ê la-jí-báng kap tsē-tsē指標 view_devops: DevOps view_devops_description: 允准用者接近使用Sidekiq kap pgHero ê la-jí-báng + view_feeds: 看即時內容kap主題ê feed + view_feeds_description: 允准用者接近使用即時kap主題feed,無論服侍器ê設定。 title: 角色 rules: add_new: 加添規則 add_translation: 加添翻譯 delete: Thâi掉 - description_html: 雖bóng大部份ê lóng講有讀kap同意使用規則,m̄-koh攏無讀了,直到發生問題ê時。提供in列單ē當hōo tsi̍t kái看服侍器ê規則khah快。請試kā個別ê規則寫kah短koh簡單,m̄-kú m̄通kā in拆做tsē-tsē分開ê項目。 + description_html: 雖bóng大部份ê lóng講有讀kap同意服務規定,m̄-koh攏無讀了,直到發生問題ê時。提供in列單ē當hōo tsi̍t kái看服侍器ê規則khah快。請試kā個別ê規則寫kah短koh簡單,m̄-kú m̄通kā in拆做tsē-tsē分開ê項目。 edit: 編輯規則 empty: Iáu bē定義服侍器ê規則。 move_down: Suá khah落來 @@ -834,6 +838,16 @@ nan: all: Kàu ta̍k ê lâng disabled: 無kàu tó tsi̍t ê users: Kàu ta̍k位登入ê用者 + feed_access: + modes: + authenticated: Kan-ta hōo登入ê用者 + disabled: 愛特別ê用者角色 + public: Ta̍k lâng + landing_page: + values: + about: 關係本站 + local_feed: 本地ê動態 + trends: 趨勢 registrations: moderation_recommandation: 佇開放hōo ta̍k ê lâng註冊進前,請確認lí有夠額koh主動反應ê管理團隊! preamble: 控制ē當佇lí ê服侍器註冊ê人。 @@ -887,6 +901,7 @@ nan: no_status_selected: 因為無揀任何PO文,所以lóng無改變 open: 公開PO文 original_status: 原底ê PO文 + quotes: 引用 reblogs: 轉送 replied_to_html: 回應 %{acct_link} status_changed: PO文有改ah @@ -894,6 +909,7 @@ nan: title: Tsit ê口座ê PO文 - @%{name} trending: 趨勢 view_publicly: 公開看 + view_quoted_post: 看引用ê PO文 visibility: 通看ê程度 with_media: 有媒體 strikes: @@ -972,14 +988,542 @@ nan: search: Tshiau-tshuē title: Hashtag updated_msg: Hashtag設定更新成功ah + terms_of_service: + back: 轉去服務規定 + changelog: Siánn物有改 + create: 用lí家tī ê + current: 目前ê + draft: 草稿 + generate: 用枋模 + generates: + action: 生成 + chance_to_review_html: "所生成ê服務規定bē自動發布。Lí ē有機會來看結果。請添必要ê詳細來繼續。" + explanation_html: 提供ê服務規定kan-ta做參考用,bē當成做任何法律建議。請照lí ê情形kap有ê特別ê法律問題諮詢lí ê法律顧問。 + title: 設定服務規定 + going_live_on_html: 目前規定,tuì %{date} 施行 + history: 歷史 + live: 目前ê + no_history: Iáu無半項服務規定ê改變記錄。 + no_terms_of_service_html: Lí目前iáu無設定任何服務規定。服務規定是beh提供明確性,兼保護lí佇kap用者ê爭議中毋免承受可能ê責任。 + notified_on_html: 佇 %{date} 通知ê用者 + notify_users: 通知用者 + preview: + explanation_html: Tsit封email ē送hōo tī %{date} 進前註冊 ê %{display_count} ê用者。Email ē包括下跤ê文字: + send_preview: Kā tāi先看ê內容寄kàu %{email} + send_to_all: + other: 寄出 %{display_count} 張電子phue + title: 先kā服務規定ê通知看māi + publish: 公開 + published_on_html: 公開tī %{date} + save_draft: 儲存草稿 + title: 服務規定 + title: 管理 trends: + allow: 允准 + approved: 允准ah + confirm_allow: Lí kám確定beh允准所揀ê標簽? + confirm_disallow: Lí kám確定無愛允准所揀ê標簽? + disallow: 無愛允准 + links: + allow: 允准連結 + allow_provider: 允准提供者 + confirm_allow: Lí kám確定beh允准所揀ê連結? + confirm_allow_provider: Lí kám確定beh允准所揀ê提供者? + confirm_disallow: Lí kám確定無愛允准所揀ê連結? + confirm_disallow_provider: Lí kám確定無愛允准所揀ê提供者? + description_html: Tsia是連結,現tsú時lí ê服侍器hōo通見ê用者大量分享ê。tse通幫tsān lí ê用者tshuē著tsit-má世間有siánn tāi誌。直kàu lí允准公開者,連結bē公開展示。lí通允准á是拒絕單ê連結。 + disallow: 無愛允准連結 + disallow_provider: 無愛允准提供者 + no_link_selected: 因為無揀任何連結,所以lóng無改變 + publishers: + no_publisher_selected: 因為無揀任何提供者,所以lóng無改變 + shared_by_over_week: + other: 頂禮拜hōo %{count} 位用者分享 + title: 趨勢ê連結 + usage_comparison: Tī kin-á日hōo %{today} ê lâng分享,比較tsa-hng有 %{yesterday} ê + not_allowed_to_trend: 無允准刊tī趨勢 + only_allowed: Kan-ta允准 + pending_review: Teh等審核 + preview_card_providers: + allowed: Tsit ê提供者ê連結通刊tī趨勢 + description_html: Tsiah ê域名來自定定受lí ê服侍器分享ê連結。連結bē變做公開趨勢,除非連結ê域名受允准。Lí ê允准(á是拒絕)擴展kàu kiánn域名。 + rejected: Tsit ê提供者ê連結bē刊tī趨勢 + title: 發布者 + rejected: 拒絕ê + statuses: + allow: 允准PO文 + allow_account: 允准作者 + confirm_allow: Lí kám確定beh允准所揀ê狀態? + confirm_allow_account: Lí kám確定beh允准所揀ê口座? + confirm_disallow: Lí kám確定無愛允准所揀ê狀態? + confirm_disallow_account: Lí kám確定無愛允准所揀ê口座? + description_html: Tsiah ê是lí ê服侍器所知ê,tsit-má teh受tsē-tsē分享kap收藏ê PO文。PO文bē公開顯示,除非lí允准作者,而且作者允准in ê口座hőng推薦hōo別lâng。Lí通允准á是拒絕個別PO文。 + disallow: 無允准PO文 + disallow_account: 無允准作者 + no_status_selected: 因為無揀任何趨勢PO文,所以lóng無改變 + not_discoverable: 作者iáu bē揀通hōo lâng發現 + shared_by: + other: Hōo lâng分享á是收藏 %{friendly_count} kái + title: 趨勢ê PO文 tags: + current_score: 目前ê分數:%{score} dashboard: + tag_accounts_measure: 無重複用 tag_languages_dimension: Tsia̍p用ê語言 + tag_servers_dimension: 人氣服侍器 + tag_servers_measure: 無kâng ê服侍器 + tag_uses_measure: Lóng總使用 + description_html: Tsiah ê是hashtag,tsit-má佇lí ê服侍器看見ê tsē-tsē PO文中出現。Tse通tsān lí ê用者tshuē著ta̍k家上tsē討論ê內容。除非lí核准,hashtag bē公開顯示。 + listable: 通受建議 + no_tag_selected: 因為無揀任何標簽,所以lóng無改變 + not_listable: Bē當受建議 + not_trendable: Bē刊tī趨勢 + not_usable: Bē當用 + peaked_on_and_decaying: 佇 %{date} 日上烘,tsit-má teh退火 + title: 趨勢ê hashtag + trendable: 通刊tī趨勢 + trending_rank: '趨勢 #%{rank}' + usable: Ē當用 + usage_comparison: Tī kin-á日hōo %{today} ê lâng用,比較tsa-hng有 %{yesterday} ê + used_by_over_week: + other: 頂禮拜hōo %{count} 位用者用 + title: 推薦kap趨勢 + trending: 趨勢 + username_blocks: + add_new: 加新ê + block_registrations: 封鎖註冊 + comparison: + contains: 包含 + equals: 等於 + contains_html: 包含 %{string} + created_msg: 成功加添用者名規則 + delete: Thâi掉 + edit: + title: 編輯使用者號名規則 + matches_exactly_html: 等於 %{string} + new: + create: 建立規則 + title: 創造使用者號名規則 + no_username_block_selected: 因為無揀任何使用者號名規則,所以lóng無改變 + not_permitted: 無允准 + title: 用者號名規則 + updated_msg: 用者號名規則更新成功ah + warning_presets: + add_new: 加新ê + delete: Thâi掉 + edit_preset: 編輯預設ê警告 + empty: Lí iáu bē定義任何預設ê警告。 + title: 預設ê警告 + webhooks: + add_new: 加添端點 + delete: Thâi掉 + description_html: "Webhook hōo Mastodon 通kā關係所揀ê事件ê即時通知sak kàu lí 家己ê應用程式,就án-ne lí ê應用程式ē當自動啟動反應。" + disable: 停止使用 + disabled: 停用ah + edit: 編輯端點 + empty: Lí iáu bē設定任何webhook端點。 + enable: 啟用 + enabled: 有效ê + enabled_events: + other: "%{count} ê啟用ê端點" + events: 事件 + new: 新ê Webhook + rotate_secret: 換秘密鎖匙 + secret: 簽秘密鎖匙 + status: 狀態 + title: Webhooks + webhook: Webhook + admin_mailer: + auto_close_registrations: + body: 因為欠最近ê管理員活動,佇 %{instance} ê註冊已經自動切kàu需要手動審查,以免 %{instance} hōo 可能行ê pháinn 行為ê當做平臺。Lí不管時lóng通切轉去,開放註冊。 + subject: "%{instance} ê註冊已經自動切kàu需要允准" + new_appeal: + actions: + delete_statuses: thâi掉in ê PO文 + disable: 冷凍in ê口座 + mark_statuses_as_sensitive: 標in ê PO文做敏感ê + none: 警告 + sensitive: 標in ê 口座做敏感ê + silence: 限制in ê口座 + suspend: 停止使用in ê口座 + body: "%{target} teh tuì %{action_taken_by} tī %{date} 所做ê管理決定送申訴,hit ê決定是 %{type}。In有寫:" + next_steps: Lí通允准申訴來取消管理決定,á是kā忽略。 + subject: "%{username} teh tuì tī %{instance} 頂ê管理決定送申訴" + new_critical_software_updates: + body: Mastodon 推出新ê重大版本,請liōng早升級! + subject: "%{instance} 有通the̍h ê Mastodon ê重大更新!" + new_pending_account: + body: 下kha是新口座ê詳細。Lí通允准á是拒絕tsit ê申請。 + subject: "%{instance} 頂有新ê口座 (%{username}) 愛審查" + new_report: + body: "%{reporter} 有檢舉用者 %{target}" + body_remote: Tuì %{domain} 來ê有檢舉 %{target} + subject: "%{instance} ê 新檢舉(#%{id})" + new_software_updates: + body: Mastodon推出新ê版本ah,lí huân-sè想beh升級! + subject: "%{instance} 有通the̍h ê Mastodon ê新版本!" + new_trends: + body: 下kha ê項目愛審查,tsiah ē當公開顯示: + new_trending_links: + title: 趨勢ê連結 + new_trending_statuses: + title: 趨勢ê PO文 + new_trending_tags: + title: 趨勢ê hashtag + subject: "%{instance} 頂有新ê趨勢愛審查" + aliases: + add_new: 加添別名 + created_msg: 別名成功加添ah。Lí ē當開始tuì舊ê口座轉。 + deleted_msg: Thâi掉捌名成功。Bē當tuì hit ê口座轉kàu tsit ê ah。 + empty: Lí bô半个別名。 + hint_html: Nā lí beh tuì別ê口座轉kah tsit ê,lí通佇tsia加別名,tse是必要ê,了後lí ē當kā跟tuè lí ê tuì舊口座suá到tsit ê。Tsit ê動作是無害koh通還原著tī舊口座suá口座。 + remove: 取消連結別名 + appearance: + advanced_settings: 進一步ê設定 + animations_and_accessibility: 動畫kap無障礙設定 + boosting_preferences: 轉送ê偏好設定 + boosting_preferences_info_html: "撇步:無論án-nuá設定,Shift + Click 佇%{icon} 轉送標á ē liâm-mī轉送。" + discovery: 發現 + localization: + body: Mastodon是由志工翻譯。 + guide_link: https://crowdin.com/project/mastodon + guide_link_text: Ta̍k家lóng ē當貢獻。 + sensitive_content: 敏感ê內容 + application_mailer: + notification_preferences: 改電子phue ê偏好 + salutation: "%{name}、" + settings: 改電子phue ê偏好:%{link} + unsubscribe: 取消訂 + view: 檢視: + view_profile: 看個人資料 + view_status: 看PO文 + applications: + created: 應用程式成功加添 + destroyed: 應用程式成功thâi掉 + logout: 登出 + regenerate_token: 重頭產生接近使用ê token + token_regenerated: 接近使用ê token 成功重頭產生 + warning: 注意!毋通kā分享hōo別lâng! + your_token: Lí ê接近使用token + auth: + apply_for_account: 申請口座 + captcha_confirmation: + help_html: Nā lí完成CAPTCHA ê時陣有問題,lí通用 %{email} kap guán 聯絡,guán ē幫tsān lí。 + hint_html: 上尾步ah!Guán愛確認lí是人類(以免pùn-sò訊息入侵!)解決下kha êCAPTCHA了後,ji̍h「繼續」。 + title: 安全檢查 + confirmations: + awaiting_review: Lí ê電子phue地址有確認ah!%{domain} ê人員leh審查lí ê註冊。Nā in允准lí ê口座,Lí ē收著電子phue! + awaiting_review_title: Lí ê註冊當leh審查 + clicking_this_link: Ji̍h tsit ê連結 + login_link: 登入 + proceed_to_login_html: Lí tsit-má通繼續去 %{login_link}。 + redirect_to_app_html: Lí應該受重轉kàu %{app_name}應用程式,若是iáu-buē,試 %{clicking_this_link} á是手動轉去tsit ê應用程式。 + registration_complete: Lí佇 %{domain} ê註冊完成ah! + welcome_title: 歡迎 %{name}! + wrong_email_hint: Nā是電子phue ê地址無正確,lí ē當tī口座設定kā改。 + delete_account: Thâi掉口座 + delete_account_html: Nā lí behthâi掉lí ê口座,lí ē當ji̍h tsia繼續。Lí著確認動作。 + description: + prefix_invited_by_user: "@%{name} 邀請lí加入tsit ê Mastodon 服侍器!" + prefix_sign_up: Tsit-má註冊Mastodon ê口座! + suffix: 有口座,lí當tuì ta̍k ê Mastodon 服侍器跟tuè lâng、PO更新kap交換訊息等等! + didnt_get_confirmation: Kám無收著確認ê連結? + dont_have_your_security_key: Lí iáu無安全鎖(security key)? + forgot_password: 密碼be記得? + invalid_reset_password_token: 重新設密碼ê token無效á是過期ah。請重頭the̍h新ê。 + link_to_otp: 請tuì lí ê手機á輸入雙因素認證(2FA)ê碼,á是恢復碼。 + link_to_webauth: 用lí ê安全鎖ê裝置 + log_in_with: 登入用 + login: 登入 + logout: 登出 + migrate_account: 轉kàu無kâng ê口座 + migrate_account_html: Nā lí ǹg望引tshuā別人tuè無kâng ê口座,請 kàutsia設定。 + or_log_in_with: Á是登入用 + progress: + confirm: 確認電子phue + details: Lí ê詳細 + review: Lán ê審查 + rules: 接受規則 + providers: + cas: CAS + saml: SAML + register: 註冊 + registration_closed: "%{instance} 無接受新ê成員" + resend_confirmation: 重送確認ê連結 + reset_password: 重設密碼 + rules: + accept: 接受 + back: Tńg去 + invited_by: Lí通用有tuì hia收著ê邀請加入 %{domain}: + preamble: Tsiah-ê hōo %{domain} ê管理員設定kap實施。 + preamble_invited: 佇lí繼續進前,請思考 %{domain} ê管理員設立ê基本規則。 + title: Tsi̍t-kuá基本規定。 + title_invited: Lí受邀請ah。 + security: 安全 + set_new_password: 設新ê密碼 + setup: + email_below_hint_html: 請檢查lí ê pùn-sò phue資料giap-á,á是請求koh送tsi̍t改。Lí通改電子phue地址,nā是有出入。 + email_settings_hint_html: 請tshi̍h guán送kàu %{email} ê連結,來開始用 Mastodon。Guán ē佇tsia等。 + link_not_received: Kám iáu bē收著連結? + new_confirmation_instructions_sent: Lí ē tī幾分鐘後收著新ê電子phue,有確認連結! + title: 請檢查lí ê收件giap-á + sign_in: + preamble_html: 請用lí佇 %{domain} ê口座kap密碼登入。若是lí ê口座tī別ê服侍器,lí bē當tī tsia登入。 + title: 登入 %{domain} + sign_up: + manual_review: 佇 %{domain} ê註冊ē經過guán ê管理員人工審查。為著tsān guán 進行lí ê註冊,寫tsi̍t點á關係lí家己kap想beh tī %{domain}註冊ê理由。 + preamble: Nā是lí tī tsit ê服侍器有口座,lí ē當tuè別ê佇聯邦宇宙ê lâng,無論伊ê口座tī tó-tsi̍t站。 + title: Lán做伙設定 %{domain}。 + status: + account_status: 口座ê狀態 + confirming: Teh等電子phue確認完成。 + functional: Lí ê口座完全ē當用。 + pending: Lán ê人員teh處理lí ê申請。Tse可能愛一段時間。若是申請允准,lí ē收著e-mail。 + redirecting_to: Lí ê口座無活動,因為tann轉kàu %{acct}。 + self_destruct: 因為 %{domain} teh-beh關掉,lí kan-ta ē當有限接近使用lí ê口座。 + view_strikes: 看早前tuì lí ê口座ê警告 + too_fast: 添表ê速度傷緊,請koh試。 + use_security_key: 用安全鎖 + user_agreement_html: 我有讀而且同意 服務規定 kap 隱私權政策 + user_privacy_agreement_html: 我有讀而且同意 隱私權政策 + author_attribution: + example_title: 見本文字 + hint_html: Lí kám佇Mastodon外口寫新聞á是blog文章?控制當in佇Mastodon分享ê時陣,án-tsuánn表示作者資訊。 + instructions: 請確認lí ê文章HTML內底有tsit ê code: + more_from_html: 看 %{name} ê其他內容 + s_blog: "%{name} ê Blog" + then_instructions: 了後,kā公開ê所在ê域名加kàu下kha ê格á: + title: 作者ê落名 + challenge: + confirm: 繼續 + hint_html: "提醒:Guán佇一點鐘內底,bē koh問密碼。" + invalid_password: 無效ê密碼 + prompt: 確認密碼來繼續 + crypto: + errors: + invalid_key: m̄是有效ê Ed25519 á是 Curve25519 鎖 + date: + formats: + default: "%Y年%b月%d日" + with_month_name: "%Y年%B月%d日" + datetime: + distance_in_words: + about_x_hours: "%{count}點鐘前" + about_x_months: "%{count}kò月前" + about_x_years: "%{count}年前" + almost_x_years: 接近%{count}年前 + half_a_minute: 頭tú-á + less_than_x_minutes: "%{count}分鐘前以內" + less_than_x_seconds: 頭tú-á + over_x_years: "%{count}年" + x_days: "%{count}kang" + x_minutes: "%{count}分" + x_months: "%{count}個月" + x_seconds: "%{count}秒" + deletes: + challenge_not_passed: Lí輸入ê資訊毋著 + confirm_password: 輸入lí tsit-má ê密碼,驗證lí ê身份 + confirm_username: 輸入lí ê口座ê名,來確認程序 + proceed: Thâi掉口座 + success_msg: Lí ê口座thâi掉成功 + warning: + before: 佇繼續進前,請斟酌讀下kha ê說明: + caches: 已經hōo其他ê站the̍h著cache ê內容,可能iáu ē有資料 + data_removal: Lí ê PO文kap其他ê資料ē永永thâi掉。 + email_change_html: Lí ē當改lí ê電子phue地址,suah毋免thâi掉lí ê口座。 + email_contact_html: 若是iáu buē收著phue,lí通寄kàu %{email} tshuē幫tsān + email_reconfirmation_html: Nā是lí iáu bē收著驗證phue,lí通koh請求 + irreversible: Lí bē當復原á是重頭啟用lí ê口座 + more_details_html: 其他資訊,請看隱私政策。 + username_available: Lí ê用者名別lâng ē當申請 + username_unavailable: Lí ê口座ē保留,而且別lâng bē當用 + disputes: + strikes: + action_taken: 行ê行動 + appeal: 投訴 + appeal_approved: Tsit ê警告已經投訴成功,bē koh有效。 + appeal_rejected: Tsit ê投訴已經受拒絕 + appeal_submitted_at: 投訴送出去ah + appealed_msg: Lí ê投訴送出去ah。Nā受允准,ē通知lí。 + appeals: + submit: 送投訴 + approve_appeal: 允准投訴 + associated_report: 關聯ê報告 + created_at: 過時ê + description_html: Tsiah ê是 %{instance} ê人員送hōo lí ê,tuì lí ê口座行ê行動kap警告。 + recipient: 送kàu + reject_appeal: 拒絕投訴 + status: 'PO文 #%{id}' + status_removed: 嘟文已經tuì系統thâi掉 + title: "%{action} tuì %{date}" + title_actions: + delete_statuses: Thâi掉PO文 + disable: 冷凍口座 + mark_statuses_as_sensitive: Kā PO文標做敏感ê + none: 警告 + sensitive: Kā 口座文標做敏感ê + silence: 口座制限 + suspend: 停止口座權限 + your_appeal_approved: Lí ê投訴受允准 + your_appeal_pending: Lí有送投訴 + your_appeal_rejected: Lí ê投訴已經受拒絕 + edit_profile: + basic_information: 基本ê資訊 + hint_html: "自訂lâng佇lí ê個人資料kap lí ê Po文邊仔所通看ê。Nā是lí有添好個人資料kap標á,別lâng較有可能kā lí跟tuè轉去,kap lí互動。" + other: 其他 + emoji_styles: + auto: 自動 + native: 原底ê + twemoji: Twemoji + errors: + '400': Lí所送ê請求無效,á是格式毋著。 + '403': Lí bô權限來看tsit頁。 + '404': Lí teh tshuē ê頁無佇leh。 + '406': Tsit頁bē當照lí所請求ê格式提供。 + '410': Lí所tshuē ê頁,無佇tsia ah。 + '422': + content: 安全驗證出tshê。Lí kám有封鎖cookie? + title: 安全驗證失敗 + '429': 請求siunn tsē + '500': + content: 歹勢,guán ê後臺出問題ah。 + title: Tsit頁無正確 + '503': Tsit頁bē當提供,因為服侍器暫時出tshê。 + noscript_html: Beh用Mastodon web app,請拍開JavaScript。另外,請試Mastodon hōo lí ê平臺用ê app。 + existing_username_validator: + not_found: bē當用tsit ê名tshuē著本地ê用者 + not_found_multiple: tshuē無 %{usernames} + exports: + archive_takeout: + date: 日期 + download: Kā lí ê檔案載落 + hint_html: Lí ē當請求lí ê PO文kap所傳ê媒體ê檔案。輸出ê資料ē用ActivityPub格式,通hōo適用ê軟體讀。Lí ē當每7 kang請求tsi̍t kái。 + in_progress: Teh備辦lí ê檔案…… + request: 請求lí ê檔案 + size: Sài-suh + blocks: Lí封鎖ê + bookmarks: 冊籤 + csv: CSV + domain_blocks: 域名封鎖 + lists: 列單 + mutes: Lí消音ê + storage: 媒體儲存 + featured_tags: + add_new: 加新ê + errors: + limit: Lí已經kā hashtag推薦kàu盡磅ah。 + hint_html: "佇個人資料推薦lí上重要ê hashtag。Tse是誠好ê家私,通the̍h來追蹤lí ê創意作品kap長期計畫。推薦ê hashtag ē佇lí ê個人資料顯目展示,koh允准緊緊接近使用lí家tī ê PO文。" + filters: + contexts: + account: 個人資料 + home: Tshù kap列單 + notifications: 通知 + public: 公共ê時間線 + thread: 會話 + edit: + add_keyword: 加關鍵字 + keywords: 關鍵字 + statuses: 個別ê PO文 + statuses_hint_html: Tsit ê過濾器應用佇所揀ê個別ê PO文,毋管in敢有符合下kha ê關鍵字重頭看,á是kā PO文tuì tsit ê過濾器suá掉。 + title: 編輯過濾器 + errors: + deprecated_api_multiple_keywords: Tsiah ê參數bē當tuì tsit ê應用程式改,因為in應用超過tsi̍t个過濾關鍵字。請用khah新ê應用程式,á是網頁界面。 + invalid_context: 無提供內文á是內文無效 + index: + contexts: "%{contexts} 內底ê過濾器" + delete: Thâi掉 + empty: Lí bô半个過濾器。 + expires_in: 佇 %{distance} kàu期 + expires_on: 佇 %{date} kàu期 + keywords: + other: "%{count} ê關鍵字" + statuses: + other: "%{count} 篇PO文" + statuses_long: + other: "%{count} 篇khàm掉ê個別PO文" + title: 過濾器 + new: + save: 儲存新ê過濾器 + title: 加新ê過濾器 + statuses: + back_to_filter: 轉去過濾器 + batch: + remove: Tuì過濾器內suá掉 + index: + hint: Tsit ê過濾器應用kàu所揀ê個別PO文,無論敢有其他ê條件。Lí ē當tuì網頁界面加koh khah tsē PO文kàu tsit ê過濾器。 + title: 過濾ê PO文 + generic: + all: 全部 + all_items_on_page_selected_html: + other: Tsit頁ê %{count} ê項目有揀ah。 + all_matching_items_selected_html: + other: 符合lí ê tshiau-tshuē ê %{count} ê項目有揀ah。 + cancel: 取消 + changes_saved_msg: 改變儲存成功! + confirm: 確認 + copy: Khóo-pih + delete: Thâi掉 + deselect: 取消lóng揀 + none: 無 + order_by: 排列照 + save_changes: 儲存改變 + select_all_matching_items: + other: 揀 %{count} ê符合lí所tshiau-tshuē ê項目。 + today: 今á日 + validation_errors: + other: 干焦iáu有狀況!請看下kha ê %{count} 項錯誤。 + imports: + errors: + empty: 空ê CSV檔案 + incompatible_type: Kap所揀ê輸入類型無配合 + invalid_csv_file: 無效ê CSV檔案。錯誤:%{error} + over_rows_processing_limit: 包含超過 %{count} tsuā + too_large: 檔案siunn大 + failures: 失敗 + imported: 輸入ah + mismatched_types_warning: Lí ká-ná kā輸入類型揀毋著,請koh確認。 + modes: + merge: 合併 + merge_long: 保存已經有ê記錄,koh加新ê + overwrite: Khàm掉 + overwrite_long: 用新ê記錄khàm掉tann ê + overwrite_preambles: + blocking_html: + other: Lí teh-beh用 %{filename} ê %{count} ê口座替換lí ê封鎖列單。 + bookmarks_html: + other: Lí teh-beh用 %{filename} ê %{count} ê PO文替換lí ê冊籤。 + domain_blocking_html: + other: Lí teh-beh用 %{filename} ê %{count} ê域名替換lí ê域名封鎖列單。 + following_html: + other: Lí當beh跟tuè%{filename} 內底ê %{count} ê口座,而且停止跟tuè別lâng。 + lists_html: + other: Lí當beh用 %{filename} ê內容取代lí ê列單%{count} ê口座會加添kàu新列單。 + muting_html: + other: Lí teh-beh用 %{filename} ê %{count} ê口座替換lí ê消音口座ê列單。 + preambles: + blocking_html: + other: Lí teh-beh kā %{filename}內底ê%{count} ê口座封鎖。 + bookmarks_html: + other: Lí當beh對 %{filename} 加添 %{count} 篇PO文kàu lí ê 冊籤。 + domain_blocking_html: + other: Lí teh-beh kā %{filename}內底ê%{count} ê域名封鎖。 + following_html: + other: Lí teh-beh kā %{filename}內底ê%{count} ê口座跟tuè。 + lists_html: + other: Lí當beh對 %{filename}%{count} ê口座 kàu lí ê列單。若無列單通加添,新ê列單ē建立。 + muting_html: + other: Lí teh-beh kā %{filename}內底ê%{count} ê口座消音。 + preface: Lí ē當輸入lí對別ê服侍器輸出ê資料,比如lí所跟tuè ê á是封鎖ê ê列單。 + recent_imports: 最近輸入ê + login_activities: + authentication_methods: + webauthn: 安全檢查 scheduled_statuses: too_soon: Tio̍h用未來ê日期。 statuses: default_language: Kap界面ê語言sio kâng + terms_of_service: + title: 服務規定 two_factor_authentication: disable: 停止用雙因素認證 user_mailer: diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 0c557cd4421a53..bacad16d67abdf 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -7,6 +7,8 @@ nl: hosted_on: Mastodon op %{domain} title: Over accounts: + errors: + cannot_be_added_to_collections: Dit account kan niet aan collecties worden toegevoegd. followers: one: Volger other: Volgers @@ -796,6 +798,8 @@ nl: view_dashboard_description: Geeft gebruikers toegang tot het dashboard en verschillende statistieken view_devops: DevOps view_devops_description: Geeft gebruikers toegang tot de dashboards van Sidekiq en pgHero + view_feeds: Openbare en hashtagtijdlijnen bekijken + view_feeds_description: Hiermee kunnen gebruikers toegang krijgen tot de openbare en hashtagtijdlijnen, ongeacht de serverinstellingen title: Rollen rules: add_new: Regel toevoegen @@ -837,7 +841,7 @@ nl: title: Gebruikers standaard niet door zoekmachines laten indexeren discovery: follow_recommendations: Aanbevolen accounts - preamble: Het tonen van interessante inhoud is van essentieel belang voor het aan boord halen van nieuwe gebruikers, die mogelijk niemand van Mastodon kennen. Bepaal hoe verschillende functies voor het ontdekken van inhoud en gebruikers op jouw server werken. + preamble: Interessante inhoud uitlichten is van essentieel belang voor onboarding van nieuwe gebruikers, die mogelijk niemand op Mastodon kennen. Bepaal hoe verschillende functies voor het ontdekken van inhoud en gebruikers op jouw server werken. privacy: Privacy profile_directory: Gebruikersgids public_timelines: Openbare tijdlijnen @@ -848,6 +852,16 @@ nl: all: Aan iedereen disabled: Aan niemand users: Aan ingelogde lokale gebruikers + feed_access: + modes: + authenticated: Alleen ingelogde gebruikers + disabled: Specifieke gebruikersrol vereisen + public: Iedereen + landing_page: + values: + about: Over + local_feed: Lokale tijdlijn + trends: Trends registrations: moderation_recommandation: Zorg ervoor dat je een adequaat en responsief moderatieteam hebt voordat je registraties voor iedereen openstelt! preamble: Toezicht houden op wie een account op deze server kan registreren. @@ -901,13 +915,15 @@ nl: no_status_selected: Er werden geen berichten gewijzigd, omdat er geen enkele werd geselecteerd open: Bericht tonen original_status: Oorspronkelijk bericht + quotes: Citaten reblogs: Boosts replied_to_html: Reageerde op %{acct_link} status_changed: Bericht veranderd status_title: Bericht van @%{name} title: Accountberichten - @%{name} trending: Trending - view_publicly: In het openbaar bekijken + view_publicly: Openbaar bericht bekijken + view_quoted_post: Geciteerde berichten tonen visibility: Zichtbaarheid with_media: Met media strikes: @@ -980,7 +996,7 @@ nl: name: Naam newest: Nieuwste oldest: Oudste - open: In het openbaar bekijken + open: Oorspronkelijk bericht bekijken reset: Opnieuw review: Status beoordelen search: Zoeken @@ -1182,10 +1198,10 @@ nl: hint_html: Wanneer je vanaf een ander account naar dit account wilt verhuizen, kun je hier een alias aanmaken. Dit is nodig voordat je verder kunt gaan met het verhuizen van volgers van het oude naar dit nieuwe account. Deze actie is op zich ongevaarlijk en omkeerbaar. De accountmigratie wordt gestart vanaf het oude account. remove: Alias ontkoppelen appearance: - advanced_web_interface: Geavanceerde webomgeving - advanced_web_interface_hint: 'Wanneer je van de hele schermbreedte gebruik wilt maken, stelt de geavanceerde webomgeving je in staat om meerdere verschillende kolommen te configureren. Hiermee kun je zoveel mogelijk informatie op hetzelfde moment bekijken, zoals: Start, meldingen, de globale tijdlijn, meerdere lijsten en hashtags.' + advanced_settings: Geavanceerde instellingen animations_and_accessibility: Animaties en toegankelijkheid - confirmation_dialogs: Bevestigingen + boosting_preferences: Boosten + boosting_preferences_info_html: "Tip: Ongeacht je instellingen, kun je met Shift + Klik op het %{icon} boostpictogram onmiddelijk boosten." discovery: Ontdekken localization: body: Mastodon wordt door vrijwilligers vertaald. @@ -1587,6 +1603,13 @@ nl: expires_at: Verloopt op uses: Aantal keer te gebruiken title: Mensen uitnodigen + link_preview: + author_html: Door %{name} + potentially_sensitive_content: + action: Klik om te tonen + confirm_visit: Ben je zeker dat je deze link wilt openen? + hide_button: Verberg + label: Mogelijk gevoelige inhoud lists: errors: limit: Je hebt het maximum aantal lijsten bereikt @@ -1651,7 +1674,7 @@ nl: disabled_account: Jouw huidige account is hierna niet meer volledig bruikbaar. Je hebt echter wel toegang tot het exporteren van je gegevens en tot het opnieuw activeren van je account. followers: Deze actie verhuist alle volgers vanaf het huidige account naar het nieuwe account only_redirect_html: Je kunt als alternatief ook alleen de doorverwijzing op je profiel zetten. - other_data: Geen andere gegevens worden automatisch verhuisd + other_data: Geen andere gegevens worden automatisch verhuisd (inclusief jouw berichten en de lijst met accounts die je volgt) redirect: Jouw huidige accountprofiel wordt bijgewerkt met een doorverwijzingsmelding en wordt uitgesloten van zoekresultaten moderation: title: Moderatie @@ -1685,16 +1708,22 @@ nl: body: 'Je bent door %{name} vermeld in:' subject: Je bent vermeld door %{name} title: Nieuwe vermelding + moderation_warning: + subject: Je hebt een moderatie-waarschuwing ontvangen poll: subject: Een peiling van %{name} is beëindigd quote: body: 'Jouw bericht werd door %{name} geciteerd:' subject: "%{name} heeft jouw bericht geciteerd" title: Nieuw citaat + quoted_update: + subject: "%{name} bewerkte een door jou geciteerd bericht" reblog: body: 'Jouw bericht werd door %{name} geboost:' subject: "%{name} boostte jouw bericht" title: Nieuwe boost + severed_relationships: + subject: Door een moderatie-beslissing ben je volgers en/of door jou gevolgde accounts verloren status: subject: "%{name} heeft zojuist een bericht geplaatst" update: @@ -1744,7 +1773,7 @@ nl: explanation: Deze instellingen worden als standaard gebruikt wanneer je nieuwe berichten aanmaakt, maar je kunt ze per bericht aanpassen. preferences: other: Overig - posting_defaults: Instellingen voor berichten + posting_defaults: Jouw nieuwe berichten public_timelines: Openbare tijdlijnen privacy: hint_html: "Hoe wil je dat jouw profiel en berichten kunnen worden gevonden? Een verscheidenheid aan functies in Mastodon kunnen je helpen om een groter publiek te bereiken als ze zijn ingeschakeld. Neem rustig de tijd om deze instellingen te bekijken, om er zo zeker van te zijn dat ze aan jouw wensen voldoen." @@ -1897,6 +1926,9 @@ nl: other: "%{count} video's" boosted_from_html: Geboost van %{acct_link} content_warning: 'Inhoudswaarschuwing: %{warning}' + content_warnings: + hide: Bericht verbergen + show: Meer tonen default_language: Hetzelfde als de taal van de gebruikersomgeving disallowed_hashtags: one: 'bevatte een niet toegestane hashtag: %{tags}' @@ -1905,16 +1937,22 @@ nl: errors: in_reply_not_found: Het bericht waarop je probeert te reageren lijkt niet te bestaan. quoted_status_not_found: Het bericht die je probeert te citeren lijkt niet te bestaan. + quoted_user_not_mentioned: Een niet-vermelde gebruiker kan niet in een privébericht worden geciteerd. over_character_limit: Limiet van %{max} tekens overschreden pin_errors: direct: Berichten die alleen zichtbaar zijn voor vermelde gebruikers, kunnen niet worden vastgezet limit: Je hebt het maximaal aantal bericht al vastgemaakt ownership: Een bericht van iemand anders kan niet worden vastgemaakt reblog: Een boost kan niet worden vastgezet + quote_error: + not_available: Bericht is niet beschikbaar + pending_approval: Bericht in afwachting van goedkeuring + revoked: Bericht verwijderd door auteur quote_policies: followers: Alleen volgers nobody: Alleen ik public: Iedereen + quote_post_author: Citeerde een bericht van %{acct} title: '%{name}: "%{quote}"' visibilities: direct: Privébericht @@ -2149,3 +2187,6 @@ nl: not_supported: Deze browser ondersteunt geen beveiligingssleutels otp_required: Om beveiligingssleutels te kunnen gebruiken, moet je eerst tweestapsverificatie inschakelen. registered_on: Geregistreerd op %{date} + wrapstodon: + description: Bekijk hoe %{name} dit jaar Mastodon heeft gebruikt! + title: Wrapstodon %{year} voor %{name} diff --git a/config/locales/nn.yml b/config/locales/nn.yml index bd59f5a1a671a7..efcd226f319b5b 100644 --- a/config/locales/nn.yml +++ b/config/locales/nn.yml @@ -796,6 +796,8 @@ nn: view_dashboard_description: Gir brukere tilgang til dashbordet og ulike metrikker view_devops: DevOps view_devops_description: Gir brukere tilgang til Sidekiq og pgHero-dashbord + view_feeds: Sjå direktestraumar og emnestraumar + view_feeds_description: La brukarar sjå direkte- og emnestraumane uansett innstillingar på tenaren title: Roller rules: add_new: Legg til regel @@ -837,17 +839,27 @@ nn: title: Ikkje la brukarar indekserast av søkjemotorar som standard discovery: follow_recommendations: Fylgjeforslag - preamble: Å framheva interessant innhald er vitalt i mottakinga av nye brukarar som ikkje nødvendigvis kjenner nokon på Mastodon. Kontroller korleis oppdagingsfunksjonane på tenaren din fungerar. + preamble: Å framheva interessant innhald er viktig for å ta imot nye brukarar som ikkje nødvendigvis kjenner nokon på Mastodon. Kontroller korleis oppdagingsfunksjonane på tenaren din fungerer. privacy: Personvern profile_directory: Profilkatalog public_timelines: Offentlege tidsliner publish_statistics: Publiser statistikk title: Oppdaging - trends: Trender + trends: Populært domain_blocks: all: Til alle disabled: Til ingen users: Til lokale brukarar som er logga inn + feed_access: + modes: + authenticated: Berre godkjende brukarar + disabled: Krev ei spesifikk brukarrolle + public: Alle + landing_page: + values: + about: Om + local_feed: Lokal tidsline + trends: Populært registrations: moderation_recommandation: Pass på at du har mange og kjappe redaktørar og moderatorar på laget ditt før du opnar for allmenn registrering! preamble: Kontroller kven som kan oppretta konto på tenaren din. @@ -901,6 +913,7 @@ nn: no_status_selected: Ingen statusar vart endra sidan ingen vart valde open: Opne innlegg original_status: Opprinnelig innlegg + quotes: Sitat reblogs: Framhevingar replied_to_html: Svarte %{acct_link} status_changed: Innlegg endret @@ -908,6 +921,7 @@ nn: title: Kontoinnlegg - @%{name} trending: Populært view_publicly: Vis offentleg + view_quoted_post: Vis det siterte innlegget visibility: Synlighet with_media: Med media strikes: @@ -1076,7 +1090,7 @@ nn: tag_uses_measure: brukarar totalt description_html: Dette er emneknagger som for øyeblikket vises i mange innlegg som serveren din ser. Det kan hjelpe dine brukere med å finne ut hva folk snakker mest om i øyeblikket. Ingen emneknagger vises offentlig før du godkjenner dem. listable: Kan bli foreslått - no_tag_selected: Ingen merkelappar vart endra fordi ingen var valde + no_tag_selected: Ingen emneknaggar vart endra fordi ingen var valde not_listable: Vil ikke bli foreslått not_trendable: Kjem ikkje til å syna under trendar not_usable: Kan ikke brukes @@ -1182,10 +1196,10 @@ nn: hint_html: Viss du vil flytta frå ein annan konto til denne, kan du laga eit alias her. Det treng du før du kan halda fram med å flytta fylgjarar frå den gamle kontoen til dnene. Denne handlinga er i seg sjølv harmlaus og kan angrast. Du har starta overføringa frå den gamle kontoen. remove: Fjern aliaslenking appearance: - advanced_web_interface: Avansert nettgrensesnitt - advanced_web_interface_hint: 'Om du vil bruke heile skjermbreidda di, let det avanserte nettgrensesnittet deg setje opp mange ulike kolonnar for å sjå så mykje informasjon du vil på ein gong: Heim, varsel, samla tidslinje, og kva som helst antall lister og emneknaggar.' + advanced_settings: Avanserte innstillingar animations_and_accessibility: Animasjonar og tilgjengelegheit - confirmation_dialogs: Bekreftelsesdialoger + boosting_preferences: Innstillingar for framheving + boosting_preferences_info_html: "Tips: Å trykkja Shift + klikk på %{icon} framhev-ikonet vil framheva innlegget uansett innstillingar." discovery: Oppdaging localization: body: Mastodon er omsett av friviljuge. @@ -1587,6 +1601,13 @@ nn: expires_at: Vert ugyldig uses: Bruk title: By folk inn + link_preview: + author_html: Av %{name} + potentially_sensitive_content: + action: Klikk for å visa + confirm_visit: Er du sikker på at du vil opna denne lenka? + hide_button: Gøym + label: Mogleg sensitivt innhald lists: errors: limit: Du har nådd grensa for kor mange lister du kan ha @@ -1651,7 +1672,7 @@ nn: disabled_account: Din nåværende konto vil ikke være fullt brukbar etterpå. Men du vil ha tilgang til dataeksportering såvel som reaktivering. followers: Denne handlinga flyttar alle fylgjarar frå denne kontoen til den nye only_redirect_html: Alternativt kan du velge å bare legge ut en omdirigering på profilen din. - other_data: Inkje anna data flyttast av seg sjølve + other_data: Ingen andre data blir flytte automatisk (inkludert innlegga dine og lista over folk du fylgjer) redirect: Profilen til din nåværende konto vil bli oppdatert med en omdirigeringsnotis og bli fjernet fra søk moderation: title: Moderasjon @@ -1685,16 +1706,22 @@ nn: body: 'Du vart nemnd av %{name} i:' subject: Du vart nemnd av %{name} title: Ny omtale + moderation_warning: + subject: Du har fått ei moderasjonsåtvaring poll: subject: Meiningsmålinga frå %{name} er avslutta quote: body: 'Innlegget ditt vart sitert av %{name}:' subject: "%{name} siterte innlegget ditt" title: Nytt sitat + quoted_update: + subject: "%{name} redigerte eit innlegg du har sitert" reblog: body: 'Statusen din vart framheva av %{name}:' subject: "%{name} framheva statusen din" title: Ny framheving + severed_relationships: + subject: Du har tapte tilkoplingar på grunn av ei avgjerd frå ein moderator status: subject: "%{name} postet nettopp" update: @@ -1897,6 +1924,9 @@ nn: other: "%{count} videoar" boosted_from_html: Framheva av %{acct_link} content_warning: 'Innhaldsåtvaring: %{warning}' + content_warnings: + hide: Gøym inlegget + show: Vis meir default_language: Samme språk som brukergrensesnittet disallowed_hashtags: one: 'inneheldt ein emneknagg som ikkje var tillaten: %{tags}' @@ -1905,16 +1935,22 @@ nn: errors: in_reply_not_found: Det ser ut til at tutet du freistar å svara ikkje finst. quoted_status_not_found: Innlegget du prøver å sitera ser ikkje ut til å finnast. + quoted_user_not_mentioned: Kan ikkje sitera ein person som ikkje er nemnd i direktemeldingar. over_character_limit: øvregrensa for teikn, %{max}, er nådd pin_errors: direct: Innlegg som bare er synlige for nevnte brukere kan ikke festes limit: Du har allereie festa så mange tut som det går an å festa ownership: Du kan ikkje festa andre sine tut reblog: Ei framheving kan ikkje festast + quote_error: + not_available: Innlegget er ikkje tilgjengeleg + pending_approval: Innlegget ventar + revoked: Innlegget er sletta av skribenten quote_policies: followers: Berre fylgjarar nobody: Berre eg public: Allle + quote_post_author: Siterte eit innlegg av %{acct} title: "%{name}: «%{quote}»" visibilities: direct: Privat omtale diff --git a/config/locales/no.yml b/config/locales/no.yml index a10f4c6a6aa592..81bdb2467ca282 100644 --- a/config/locales/no.yml +++ b/config/locales/no.yml @@ -729,7 +729,6 @@ title: Ikke la brukere indekseres av søkemotorer som standard discovery: follow_recommendations: Følg anbefalinger - preamble: Å fremheve interessant innhold er viktig i ombordstigning av nye brukere som kanskje ikke kjenner noen Mastodon. Kontroller hvordan ulike oppdagelsesfunksjoner fungerer på serveren. profile_directory: Profilkatalog public_timelines: Offentlige tidslinjer publish_statistics: Publiser statistikk @@ -997,10 +996,7 @@ hint_html: Dersom du vil flytte fra en annen konto til den, kan du lage et alias her, som er påkrevd før du kan gå videre med å flytte følgere fra den gamle kontoen til den nye. Handlingen i seg selv er harmløs og reversibel. Kontoflyttingen har blitt satt i gang fra den gamle kontoen. remove: Fjern aliaslenking appearance: - advanced_web_interface: Avansert nettgrensesnitt - advanced_web_interface_hint: 'Hvis du ønsker å bruke hele skjermbredden din, lar det avanserte nettgrensesnittet deg sette opp mange forskjellige kolonner for å se så mye informasjon på én gang som du vil: Hjem, varslinger, fellestidslinjen, og ethvert antall lister og emneknagger.' animations_and_accessibility: Animasjoner og tilgjengelighet - confirmation_dialogs: Bekreftelsesdialoger discovery: Oppdagelse localization: body: Mastodon er oversatt av frivillige. @@ -1389,7 +1385,6 @@ disabled_account: Din nåværende konto vil ikke være fullt brukbar etterpå. Men du vil ha tilgang til dataeksportering såvel som reaktivering. followers: Denne handlingen vil flytte alle følgere fra den nåværende kontoen til den nye kontoen only_redirect_html: Alternativt kan du velge å bare legge ut en omdirigering på profilen din. - other_data: Ingen andre data vil bli flyttet automatisk redirect: Profilen til din nåværende konto vil bli oppdatert med en omdirigeringsnotis og bli fjernet fra søk moderation: title: Moderasjon diff --git a/config/locales/oc.yml b/config/locales/oc.yml index 51f8d8c7b4d004..3d9fbd77453513 100644 --- a/config/locales/oc.yml +++ b/config/locales/oc.yml @@ -442,10 +442,7 @@ oc: add_new: Crear un alias remove: Desligar l’alias appearance: - advanced_web_interface: Interfàcia web avançada - advanced_web_interface_hint: 'Se volètz utilizar la nautor complèta de l’ecran, l’interfàcia web avançada vos permet de configurar diferentas colomnas per mostrar tan d’informacions que volètz : Acuèlh, notificacions, flux d’actualitat, e d’autras listas e etiquetas.' animations_and_accessibility: Animacion e accessibilitat - confirmation_dialogs: Fenèstras de confirmacion discovery: Descobèrta localization: body: Mastodon es traduch per de benevòls. @@ -671,7 +668,6 @@ oc: warning: before: 'Abans de contunhar, volgatz legir aquestas nòtas amb atencion :' only_redirect_html: Autrament, podètz solament definir una redireccion sus vòstre perfil. - other_data: Cap d’autra donada serà desplaçada automaticament moderation: title: Moderacion notification_mailer: diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 20b391c8675c8c..5084ccc4ceeaa9 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -331,6 +331,7 @@ pl: create: Utwórz ogłoszenie title: Nowe ogłoszenie preview: + disclaimer: Ponieważ użytkownicy nie mogą zrezygnować z otrzymywania powiadomień email, powinny one ograniczać się do ważnych ogłoszeń, takich jak powiadomienia o naruszeniu bezpieczeństwa danych osobowych lub zamknięciu serwera. explanation_html: 'Wiadomość e-mail zostanie wysłana do %{display_count} użytkowników. Otrzymają oni wiadomość o następującej treści:' title: Podgląd powiadomienia publish: Opublikuj @@ -507,6 +508,7 @@ pl: created_at: 'Utworzono:' delete: Usuń ip: Adres IP + request_body: Treść zgłoszenia providers: active: Aktywne base_url: Podstawowy adres URL @@ -515,10 +517,12 @@ pl: finish_registration: Zakończ rejestrację name: Nazwa providers: Dostawca + public_key_fingerprint: Odcisk klucza publicznego registration_requested: Wymagana rejestracja registrations: confirm: Zatwierdź reject: Odrzuć + title: Potwierdź rejestrację FASP save: Zapisz sign_in: Zaloguj się status: Status @@ -600,6 +604,10 @@ pl: title: Moderacja moderation_notes: create: Dodaj notatkę moderacyjną + created_msg: Notatka dotycząca moderacji instancji została pomyślnie utworzona! + description_html: Wyświetlaj i zostawiaj notatki dla innych moderatorów oraz dla siebie samego w przyszłości + destroyed_msg: Notatka dotycząca moderacji instancji została pomyślnie usunięta! + placeholder: Informacje o tej instancji, podjętych działaniach lub wszelkie inne informacje, które pomogą ci moderować tę instancję w przyszłości. title: Notatki moderacyjne private_comment: Prywatny komentarz public_comment: Publiczny komentarz @@ -812,6 +820,8 @@ pl: view_dashboard_description: Pozwala użytkownikom na dostęp do panelu i różnych metryk view_devops: DevOps view_devops_description: Pozwala użytkownikom na dostęp do paneli Sidekiq i pgHero + view_feeds: Wyświetlaj aktualności i kanały tematyczne + view_feeds_description: Umożliwia użytkownikom dostęp do aktualności i kanałów tematycznych niezależnie od ustawień serwera title: Role rules: add_new: Dodaj zasadę @@ -825,6 +835,7 @@ pl: title: Regulamin serwera translation: Tłumaczenie translations: Tłumaczenia + translations_explanation: Możesz opcjonalnie dodać tłumaczenia reguł. Wyświetlany będzie tekst domyślny, jeśli nie ma dostępnej wersji przetłumaczonej. Zawsze upewnij się, że podane tłumaczenie jest zgodne z tekstem domyślnym. settings: about: manage_rules: Zarządzaj regułami serwera @@ -849,7 +860,6 @@ pl: title: Domyślnie żądaj nieindeksowania użytkowników przez wyszukiwarki discovery: follow_recommendations: Polecane konta - preamble: Prezentowanie interesujących treści ma kluczowe znaczenie dla nowych użytkowników, którzy mogą nie znać nikogo z Mastodona. Kontroluj, jak różne funkcje odkrywania działają na Twoim serwerze. privacy: Prywatność profile_directory: Katalog profilów public_timelines: Publiczne osie czasu @@ -1199,10 +1209,7 @@ pl: hint_html: Jeżeli chcesz przenieść się z innego konta na to, możesz utworzyć alias, który jest wymagany zanim zaczniesz przenoszenie obserwacji z poprzedniego konta na to. To działanie nie wyrządzi szkód i jest odwracalne. Migracja konta jest inicjowana ze starego konta. remove: Odłącz alias appearance: - advanced_web_interface: Zaawansowany interfejs użytkownika - advanced_web_interface_hint: Jeśli chcesz użyć pełną szerokość swojego ekranu, zaawansowany interfejs użytkownika pozwala Ci skonfigurować wiele różnych kolumn, by zobaczyć jak najwięcej informacji kiedy tylko chcesz. Strona główna, Powiadomienia, Globalna oś czasu, dowolna ilość list i hasztagów. animations_and_accessibility: Animacje i dostępność - confirmation_dialogs: Dialogi potwierdzenia discovery: Odkrywanie localization: body: Mastodon jest tłumaczony przez wolontariuszy. @@ -1708,7 +1715,6 @@ pl: disabled_account: Twoje obecne konto nie będzie później całkowicie użyteczne. Możesz jednak uzyskać dostęp do eksportu danych i ponownie aktywować je. followers: To działanie przeniesie wszystkich Twoich obserwujących z obecnego konta na nowe only_redirect_html: Możesz też po prostu skonfigurować przekierowanie na swój profil. - other_data: Żadne inne dane nie zostaną automatycznie przeniesione redirect: Twoje obecne konto zostanie uaktualnione o informację o przeniesieniu i wyłączone z wyszukiwania moderation: title: Moderacja @@ -1980,6 +1986,8 @@ pl: title: '%{name}: "%{quote}"' visibilities: public: Publiczne + unlisted: Niewidoczny + unlisted_long: Ukryte w wynikach wyszukiwania Mastodona, trendach i publicznych osiach czasu statuses_cleanup: enabled: Automatycznie usuwaj stare wiadomości enabled_hint: Automatycznie usuwa Twoje posty, gdy osiągną określony próg wiekowy, chyba że spełniają jeden z poniższych wyjątków @@ -2025,6 +2033,8 @@ pl: terms_of_service: title: Regulamin terms_of_service_interstitial: + future_preamble_html: Wprowadzamy pewne zmiany w naszych warunkach świadczenia usług, które zaczną obowiązywać od %{date}. Zachęcamy do zapoznania się z aktualnymi warunkami. + past_preamble_html: Od czasu twojej ostatniej wizyty zmieniliśmy warunki korzystania z usługi. Zachęcamy do zapoznania się z aktualnymi warunkami. review_link: Przeglądnij Warunki Korzystania title: Warunki korzystania z %{domain} zmieniają się themes: @@ -2059,6 +2069,7 @@ pl: webauthn: Klucze bezpieczeństwa user_mailer: announcement_published: + description: 'Administratorzy %{domain} publikują ogłoszenie:' subject: Ogłoszenie serwisu title: Ogłoszenie serwisu %{domain} appeal_approved: @@ -2093,6 +2104,8 @@ pl: terms_of_service_changed: agreement: Kontynuując używanie %{domain}, zgadzasz się na te warunki. Jeśli nie zgadzasz się ze zaktualizowanymi warunkami, możesz wypowiedzieć umowę z %{domain} w dowolnym momencie, usuwając swoje konto. changelog: 'W skrócie oto co oznacza dla Ciebie ta aktualizacja:' + description: 'Otrzymujesz tę wiadomość email, ponieważ wprowadzamy pewne zmiany w naszych warunkach świadczenia usług w domenie %{domain}. Aktualizacje te zaczną obowiązywać od dnia %{date}. Zachęcamy do zapoznania się z pełną treścią zaktualizowanych warunków tutaj:' + description_html: Otrzymujesz tę wiadomość email, ponieważ wprowadzamy pewne zmiany w naszych warunkach świadczenia usług w domenie %{domain}. Aktualizacje te zaczną obowiązywać od dnia %{date}. Zachęcamy do zapoznania się z pełną treścią zaktualizowanych warunków tutaj. sign_off: Zespół %{domain} subject: Aktualizacja warunków korzystania z usług subtitle: Warunki korzystania z %{domain} zmieniają się diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 4686305e6470d2..7e3344efff4a86 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -7,6 +7,8 @@ pt-BR: hosted_on: Mastodon hospedado em %{domain} title: Sobre accounts: + errors: + cannot_be_added_to_collections: Esta conta não pode ser adicionada a coleções. followers: one: Seguidor other: Seguidores @@ -796,6 +798,8 @@ pt-BR: view_dashboard_description: Permite que os usuários acessem o painel e várias métricas view_devops: DevOps view_devops_description: Permite aos usuários acessar os painéis da Sidekiq e pgHero + view_feeds: Veja transmissões ao vivo e por tópico + view_feeds_description: Permite que os usuários acessem os feeds ao vivo e por tópico, independentemente das configurações do servidor title: Funções rules: add_new: Adicionar regra @@ -837,7 +841,7 @@ pt-BR: title: Optar por excluir usuários da indexação de mecanismos de pesquisa por padrão discovery: follow_recommendations: Seguir recomendações - preamble: Navegar por um conteúdo interessante é fundamental para integrar novos usuários que podem não conhecer ninguém no Mastodon. Controle como várias características de descoberta funcionam no seu servidor. + preamble: A exibição de conteúdo interessante é fundamental para a integração de novos usuários que podem não conhecer ninguém no Mastodon. Controle o funcionamento de vários recursos de descoberta no seu servidor. privacy: Privacidade profile_directory: Diretório de perfis public_timelines: Timelines públicas @@ -848,6 +852,16 @@ pt-BR: all: Para todos disabled: Para ninguém users: Para usuários locais logados + feed_access: + modes: + authenticated: Apenas usuários autenticados + disabled: Exige função específica de usuário + public: Todos + landing_page: + values: + about: Sobre + local_feed: Feed local + trends: Em alta registrations: moderation_recommandation: Por favor, certifique-se de ter uma equipe de moderação adequada e reativa antes de abrir as inscrições para todos! preamble: Controle quem pode criar uma conta no seu servidor. @@ -901,6 +915,7 @@ pt-BR: no_status_selected: Nenhuma publicação foi modificada porque nenhuma estava selecionada open: Publicação aberta original_status: Publicação original + quotes: Citações reblogs: Reblogs replied_to_html: Respondeu à %{acct_link} status_changed: Publicação alterada @@ -908,6 +923,7 @@ pt-BR: title: Publicações da conta - @%{name} trending: Em alta view_publicly: Ver publicamente + view_quoted_post: Visualizar citação publicada visibility: Visibilidade with_media: Com mídia strikes: @@ -1182,10 +1198,10 @@ pt-BR: hint_html: Se você quiser migrar de uma outra conta para esta, você pode criar um atalho aqui, o que é necessário antes que você possa migrar os seguidores da conta antiga para esta. Esta ação por si só é inofensiva e reversível. A migração da conta é iniciada pela conta antiga. remove: Desvincular alias appearance: - advanced_web_interface: Interface avançada de colunas - advanced_web_interface_hint: 'Se você deseja usar toda a sua largura de tela, a interface avançada permite que você configure muitas colunas diferentes para ver tantas informações ao mesmo tempo quanto você deseja: Página inicial, notificações, linha local, linha global, qualquer número de listas e hashtags.' + advanced_settings: Configurações avançadas animations_and_accessibility: Animações e acessibilidade - confirmation_dialogs: Diálogos de confirmação + boosting_preferences: Adicionar preferências + boosting_preferences_info_html: "Dica: Independentemente das configurações, Shift + Clique no ícone %{icon} Boost irá impulsionar imediatamente." discovery: Descobrir localization: body: Mastodon é traduzido por voluntários. @@ -1587,6 +1603,13 @@ pt-BR: expires_at: Expira em uses: Usos title: Convidar pessoas + link_preview: + author_html: Por %{name} + potentially_sensitive_content: + action: Clique para mostrar + confirm_visit: Tem certeza que deseja abrir esse link? + hide_button: Ocultar + label: Conteúdo potencialmente sensível lists: errors: limit: Você atingiu o número máximo de listas @@ -1651,7 +1674,7 @@ pt-BR: disabled_account: Sua conta não estará totalmente funcional ao término deste processo. Entretanto, você terá acesso à exportação de dados bem como à reativação. followers: Esta ação moverá todos os seguidores da conta atual para a nova conta only_redirect_html: Alternativamente, você pode apenas colocar um redirecionamento no seu perfil. - other_data: Nenhum outro dado será movido automaticamente + other_data: Nenhum outro dado será movido automaticamente (isto inclui suas postagens e a lista de conta que segue) redirect: O perfil atual da sua conta será atualizado com um aviso de redirecionamento e também será excluído das pesquisas moderation: title: Moderação @@ -1685,16 +1708,22 @@ pt-BR: body: "%{name} te mencionou em:" subject: "%{name} te mencionou" title: Nova menção + moderation_warning: + subject: Você recebeu um aviso de moderação poll: subject: Uma enquete por %{name} terminou quote: body: 'Sua publicação foi Citada por %{name}:' subject: "%{name} citou sua publicação" title: Nova citação + quoted_update: + subject: "%{name} editou uma publicação que você citou" reblog: body: "%{name} impulsionou a sua publicação:" subject: "%{name} impulsionou a sua publicação" title: Novo impulso + severed_relationships: + subject: Você perdeu conexões devido a uma decisão da moderação status: subject: "%{name} acabou de publicar" update: @@ -1713,6 +1742,7 @@ pt-BR: quadrillion: QUA thousand: MIL trillion: TRI + unit: '' otp_authentication: code_hint: Digite o código gerado pelo seu aplicativo autenticador para confirmar description_html: Se você ativar a autenticação de dois fatores usando um aplicativo autenticador, ao se conectar será exigido que você esteja com o seu telefone, que gerará tokens para você entrar. @@ -1897,6 +1927,9 @@ pt-BR: other: "%{count} vídeos" boosted_from_html: Impulso de %{acct_link} content_warning: 'Aviso de conteúdo: %{warning}' + content_warnings: + hide: Ocultar publicação + show: Exibir mais default_language: Igual ao idioma da interface disallowed_hashtags: one: 'continha hashtag não permitida: %{tags}' @@ -1905,21 +1938,29 @@ pt-BR: errors: in_reply_not_found: A publicação que você quer responder parece não existir. quoted_status_not_found: A publicação que você quer responder parece não existir. + quoted_user_not_mentioned: Não é possível citar um usuário não mencionado em uma postagem de Menção Privada. over_character_limit: limite de caracteres de %{max} excedido pin_errors: direct: Publicações visíveis apenas para usuários mencionados não podem ser fixadas limit: Você alcançou o número limite de publicações fixadas ownership: As publicações dos outros não podem ser fixadas reblog: Um impulso não pode ser fixado + quote_error: + not_available: Publicação indisponível + pending_approval: Publicação pendente + revoked: Publicação removida pelo autor quote_policies: followers: Apenas seguidores nobody: Apenas eu public: Qualquer um + quote_post_author: Publicação citada por %{acct} title: '%{name}: "%{quote}"' visibilities: direct: Citação privada + private: Apenas seguidores public: Público public_long: Qualquer um dentro ou fora do Mástodon + unlisted: Publicação silenciada unlisted_long: Oculto aos resultados de pesquisa em Mástodon statuses_cleanup: enabled: Excluir publicações antigas automaticamente @@ -2147,3 +2188,6 @@ pt-BR: not_supported: Este navegador não tem suporte a chaves de segurança otp_required: Para usar chaves de segurança, ative a autenticação de dois fatores. registered_on: Registrado em %{date} + wrapstodon: + description: Veja como %{name} usou o Mastodon este ano! + title: Wrapstodon de %{year} para %{name} diff --git a/config/locales/pt-PT.yml b/config/locales/pt-PT.yml index 3ee797564854aa..b9175395f0ccda 100644 --- a/config/locales/pt-PT.yml +++ b/config/locales/pt-PT.yml @@ -7,6 +7,8 @@ pt-PT: hosted_on: Mastodon alojado em %{domain} title: Sobre accounts: + errors: + cannot_be_added_to_collections: Esta conta não pode ser adicionada às coleções. followers: one: Seguidor other: Seguidores @@ -796,6 +798,8 @@ pt-PT: view_dashboard_description: Permite aos utilizadores acederem ao painel de controlo e a várias estatísticas view_devops: DevOps view_devops_description: Permite aos utilizadores aceder aos painéis de controlo do Sidekiq e pgHero + view_feeds: Ver cronologia em tempo real e de etiquetas + view_feeds_description: Permitir aos utilizadores aceder às cronologias em tempo real e de etiquetas independentemente das definições do servidor title: Funções rules: add_new: Adicionar regra @@ -817,7 +821,7 @@ pt-PT: rules_hint: Existe uma área dedicada às regras a que os teus utilizadores devem aderir. title: Sobre allow_referrer_origin: - desc: Quando os seus utilizadores clicam em links para sites externos, o navegador deles pode enviar o endereço do seu servidor Mastodon como referenciador. Desative esta opção se isso identificar inequivocamente os seus utilizadores, por exemplo, se este for um servidor Mastodon pessoal. + desc: Quando os seus utilizadores clicam em hiperligações para sites externos, o navegador destes pode enviar o endereço do seu servidor Mastodon como referenciador. Desative esta opção se isso identificar inequivocamente os seus utilizadores, por exemplo, se este for um servidor Mastodon pessoal. title: Permitir que sites externos vejam o seu servidor Mastodon como uma fonte de tráfego appearance: preamble: Personaliza a interface web do Mastodon. @@ -837,17 +841,28 @@ pt-PT: title: Desativar, por omissão, a indexação de utilizadores por parte dos motores de pesquisa discovery: follow_recommendations: Recomendações de contas - preamble: Revelar conteúdos interessantes é fundamental para a entrada de novos utilizadores que podem não conhecer ninguém no Mastodon. Controla como os vários recursos de descoberta funcionam no teu servidor. + preamble: Apresentar conteúdos interessantes é fundamental para atrair novos utilizadores que talvez não conheçam ninguém no Mastodon. Controle como várias funcionalidades de descoberta funcionam no seu servidor. privacy: Privacidade profile_directory: Diretório de perfis public_timelines: Cronologias públicas publish_statistics: Publicar estatísticas title: Descobrir trends: Tendências + wrapstodon: Wrapstodon domain_blocks: all: Para toda a gente disabled: Para ninguém users: Para utilizadores locais que se encontrem autenticados + feed_access: + modes: + authenticated: Apesar utilizadores autenticados + disabled: Requerer função de utilizador especifica + public: Todos + landing_page: + values: + about: Sobre + local_feed: Cronologia local + trends: Tendências registrations: moderation_recommandation: Certifique-se de que dispõe de uma equipa de moderação adequada e reativa antes de abrir as inscrições a todos! preamble: Controle quem pode criar uma conta no seu servidor. @@ -901,6 +916,7 @@ pt-PT: no_status_selected: Nenhum estado foi alterado porque nenhum foi selecionado open: Abrir publicação original_status: Publicação original + quotes: Citações reblogs: Impulsos replied_to_html: Respondeu a %{acct_link} status_changed: Publicação alterada @@ -908,6 +924,7 @@ pt-PT: title: Publicações da conta - @%{name} trending: Em destaque view_publicly: Visualizar publicamente + view_quoted_post: Ver publicação citada visibility: Visibilidade with_media: Com multimédia strikes: @@ -1182,10 +1199,10 @@ pt-PT: hint_html: Se quiseres mudar de outra conta para esta, podes criar aqui um pseudónimo, que é necessário antes de poderes prosseguir com a migração de seguidores da conta antiga para esta. Esta ação por si só é inofensiva e reversível. A migração da conta é iniciada a partir da conta antiga. remove: Desvincular pseudónimo appearance: - advanced_web_interface: Interface web avançada - advanced_web_interface_hint: 'Se quiseres utilizar toda a largura do teu ecrã, a interface web avançada permite configurar várias colunas diferentes para veres tanta informação ao mesmo tempo quanto quiseres: página inicial, notificações, cronologia federada, qualquer número de listas e etiquetas.' + advanced_settings: Definições avançadas animations_and_accessibility: Animações e acessibilidade - confirmation_dialogs: Caixas de confirmação + boosting_preferences: Definições de partilha + boosting_preferences_info_html: "Dica: Independente das definições, Shift + Clique no ícone %{icon} de Partilhar vai partilhar de imediato." discovery: Descobrir localization: body: O Mastodon é traduzido por voluntários. @@ -1220,7 +1237,7 @@ pt-PT: clicking_this_link: clicar nesta hiperligação login_link: iniciar sessão proceed_to_login_html: Podes agora prosseguir para %{login_link}. - redirect_to_app_html: Devias ter sido reencaminhado para a aplicação %{app_name}. Se isso não aconteceu, tenta %{clicking_this_link} ou regressa manualmente para a aplicação. + redirect_to_app_html: Devia ter sido reencaminhado para a aplicação %{app_name}. Se isso não aconteceu, tente %{clicking_this_link} ou regresse manualmente para a aplicação. registration_complete: O teu registo sem %{domain} está agora concluído! welcome_title: Bem-vindo(a), %{name}! wrong_email_hint: Se este endereço de correio eletrónico não estiver correto, podes alterá-lo nas definições de conta. @@ -1405,7 +1422,7 @@ pt-PT: request: Pedir o teu arquivo size: Tamanho blocks: Bloqueaste - bookmarks: Marcadores + bookmarks: Itens Salvos csv: CSV domain_blocks: Bloqueios de domínio lists: Listas @@ -1502,8 +1519,8 @@ pt-PT: one: Estás prestes a substituir a tua lista de bloqueios com até conta%{count} de %{filename}. other: Estás prestes a substituir a tua lista de bloqueios com até %{count} contas de %{filename}. bookmarks_html: - one: Estás prestes a substituir os teus marcadores com até %{count} publicações de %{filename}. - other: Estás prestes a substituir os teus marcadores com até %{count} publicação de %{filename}. + one: Está prestes a substituir os seus itens salvos com até %{count} publicação de %{filename}. + other: Está prestes a substituir os seus itens salvos com até %{count} publicações de %{filename}. domain_blocking_html: one: Estás prestes a substituir a tua lista de bloqueios de domínio com até %{count} domínio de %{filename}. other: Estás prestes a substituir a tua lista de bloqueios de domínio com até %{count} domínios de %{filename}. @@ -1521,8 +1538,8 @@ pt-PT: one: Estás prestes a bloquear até %{count} conta de %{filename}. other: Estás prestes a bloquear até %{count} contas de %{filename}. bookmarks_html: - one: Estás prestes a adicionar até %{count} publicação de %{filename} aos teus marcadores. - other: Estás prestes a adicionar até %{count} publicações de %{filename} aos teus marcadores. + one: Está prestes a adicionar até %{count} publicação de %{filename} aos teus marcadores. + other: Está prestes a adicionar até %{count} publicações de %{filename} aos teus marcadores. domain_blocking_html: one: Estás prestes a bloquear até %{count} domínio de %{filename}. other: Estás prestes a bloquear até %{count} domínios de %{filename}. @@ -1547,18 +1564,18 @@ pt-PT: time_started: Iniciado em titles: blocking: Importar contas bloqueadas - bookmarks: Importar marcadores + bookmarks: Importar Itens Salvos domain_blocking: Importar domínios bloqueados following: Importar contas seguidas lists: Importar listas muting: Importar contas ocultadas type: Tipo de importação type_groups: - constructive: Seguidores e marcadores + constructive: Seguidores e Itens Salvos destructive: Bloqueios e ocultados types: blocking: Lista de bloqueios - bookmarks: Marcadores + bookmarks: Itens Salvos domain_blocking: Lista de domínios bloqueados following: Lista de pessoas que estás a seguir lists: Listas @@ -1587,6 +1604,13 @@ pt-PT: expires_at: Expira uses: Utilizações title: Convidar pessoas + link_preview: + author_html: Por %{name} + potentially_sensitive_content: + action: Clicar para mostrar + confirm_visit: Tem a certeza que prentende abrir esta hiperligação? + hide_button: Esconder + label: Conteúdo potencialmente sensível lists: errors: limit: Atingiste o número máximo de listas permitido @@ -1612,7 +1636,7 @@ pt-PT: follow: e-mails de notificação de seguidor follow_request: e-mails de pedido de seguidor mention: e-mails de notificação de menção - reblog: e-mails de notificação de impulsos + reblog: e-mails de notificação de partilhas resubscribe_html: Se tiveres anulado a subscrição por engano, podes voltar a subscrevê-la nas definições de notificação por e-mail. success_html: Não receberás novamente %{type} do Mastodon em %{domain} para o teu e-mail em %{email}. title: Cancelar subscrição @@ -1651,7 +1675,7 @@ pt-PT: disabled_account: Posteriormente, a tua conta atual não será totalmente utilizável. No entanto, continuarás a ter acesso à exportação de dados, bem como à reativação. followers: Esta ação irá migrar todos os seguidores da conta atual para a nova conta only_redirect_html: Em alternativa, podes apenas colocar um redirecionamento no teu perfil. - other_data: Nenhum outro dado será migrado automaticamente + other_data: Não serão movidos automaticamente mais nenhuns dados (incluindo as suas publicações e a lista de contas que segue) redirect: O perfil da tua conta atual será atualizado com um aviso de redirecionamento e será excluído das pesquisas moderation: title: Moderação @@ -1685,16 +1709,22 @@ pt-PT: body: 'Foste mencionado por %{name}:' subject: "%{name} mencionou-te" title: Nova menção + moderation_warning: + subject: Recebeste um aviso da moderação poll: subject: A sondagem de %{name} terminou quote: body: 'A sua publicação foi citada por %{name}:' subject: "%{name} citou a sua publicação" title: Nova citação + quoted_update: + subject: "%{name} editou uma publicação que citou" reblog: - body: 'A tua publicação foi impulsionada por %{name}:' - subject: "%{name} impulsionou a tua publicação" + body: 'A tua publicação foi partilhada por %{name}:' + subject: "%{name} partilhou a sua publicação" title: Novo impulso + severed_relationships: + subject: Perdeste ligações devido a uma decisão da moderação status: subject: "%{name} acabou de publicar" update: @@ -1895,8 +1925,11 @@ pt-PT: video: one: "%{count} vídeo" other: "%{count} vídeos" - boosted_from_html: Impulsionado por %{acct_link} + boosted_from_html: Partilhado por %{acct_link} content_warning: 'Aviso de conteúdo: %{warning}' + content_warnings: + hide: Esconder publicação + show: Mostrar mais default_language: Igual ao idioma da interface disallowed_hashtags: one: 'continha uma #etiqueta proibida: %{tags}' @@ -1905,16 +1938,22 @@ pt-PT: errors: in_reply_not_found: A publicação a que estás a tentar responder parece não existir. quoted_status_not_found: A publicação que está a tentar citar parece não existir. + quoted_user_not_mentioned: Um utilizador não mencionado não pode ser citado numa publicação privada. over_character_limit: limite de caracteres %{max} excedido pin_errors: direct: As publicações que só são visíveis para os utilizadores mencionados não podem ser fixadas limit: Já fixaste a quantidade máxima de publicações ownership: Não podem ser fixadas publicações de outras pessoas - reblog: Não é possível fixar um impulso + reblog: Não é possível fixar uma partilha + quote_error: + not_available: Publicação indisponível + pending_approval: Publicação pendente + revoked: Publicação removida pelo autor quote_policies: followers: Apenas para seguidores nobody: Apenas eu public: Todos + quote_post_author: Citou uma publicação de %{acct} title: '%{name}: "%{quote}"' visibilities: direct: Menção privada @@ -1929,9 +1968,9 @@ pt-PT: exceptions: Exceções explanation: Como eliminar publicações é uma operação custosa, isto é feito lentamente ao longo do tempo, quando o servidor não está ocupado. Por esta razão, as tuas publicações podem ser eliminadas um pouco depois de atingirem o limite de idade definido. ignore_favs: Ignorar favoritos - ignore_reblogs: Ignorar os impulsos + ignore_reblogs: Ignorar partilhas interaction_exceptions: Exceções baseadas em interações - interaction_exceptions_explanation: Tem em atenção que não há garantia de que as mensagens sejam eliminadas se ficarem abaixo do limite de favoritos ou de impulsionamento depois de os terem ultrapassado. + interaction_exceptions_explanation: Observe que não há garantia que as publicações serão excluídas se ficarem abaixo do limite de favoritos ou partilhas depois de terem ultrapassado esses limites. keep_direct: Manter mensagens diretas keep_direct_hint: Não elimina nenhuma das tuas mensagens diretas keep_media: Manter publicações com anexos de multimédia @@ -1940,8 +1979,8 @@ pt-PT: keep_pinned_hint: Não elimina nenhuma das tuas publicações afixadas keep_polls: Manter sondagens keep_polls_hint: Não elimina nenhuma das tuas sondagens - keep_self_bookmark: Manter as publicações que marquei - keep_self_bookmark_hint: Não elimina as tuas próprias publicações se as tiveres nos marcadores + keep_self_bookmark: Manter as publicações que salvou + keep_self_bookmark_hint: Não eliminar as suas próprias publicações se as tiver salvo keep_self_fav: Manter as publicações que adicionei aos favoritos keep_self_fav_hint: Não elimina as tuas próprias publicações se as tiveres adicionado aos favoritos min_age: @@ -1956,8 +1995,8 @@ pt-PT: min_age_label: Limite de idade min_favs: Manter publicações adicionadas aos favoritos pelos menos min_favs_hint: Não elimina nenhuma das tuas publicações que tenham sido adicionadas aos favoritos este número de vezes. Deixa em branco para eliminar publicações, independentemente do número de vezes que tenham sido adicionadas aos favoritos - min_reblogs: Manter as publicações impulsionadas, pelo menos - min_reblogs_hint: Não elimina nenhuma das tuas mensagens que tenham sido impulsionada pelo menos este número de vezes. Deixa em branco para eliminar as mensagens independentemente do número de impulsionamentos + min_reblogs: Manter, pelo menos, as publicações partilhadas + min_reblogs_hint: Não elimina nenhuma das suas publicações que tenha sido partilhadas pelo menos este número de vezes. Deixe em branco para eliminar publicações independentemente do número de partilhas stream_entries: sensitive_content: Conteúdo sensível strikes: @@ -2084,7 +2123,7 @@ pt-PT: checklist_subtitle: 'Vamos começar nesta nova fronteira social:' checklist_title: Passos de boas-vindas edit_profile_action: Personalizar - edit_profile_step: Aumenta as tuas interações com um perfil completo. + edit_profile_step: Aumente as suas interações ao ter um perfil mais completo. edit_profile_title: Personaliza o teu perfil explanation: Aqui estão algumas dicas para começar feature_action: Mais informações @@ -2125,7 +2164,7 @@ pt-PT: seamless_external_login: A sessão foi iniciada através de um serviço externo, pelo que as definições de palavra-passe e e-mail não estão disponíveis. signed_in_as: 'Registado como:' verification: - extra_instructions_html: Dica: a hiperligação no teu site pode ser invisível. A parte importante é rel="me" que impede a falsificação de identidade em sítios na web com conteúdos gerados pelos utilizadores. Podes até utilizar uma etiqueta link no cabeçalho da página ao invés de a, mas o HTML deve ser acessível sem executar JavaScript. + extra_instructions_html: Dica: a hiperligação no seu site pode ser invisível. A parte importante é rel="me" que impede a falsificação de identidade em sítios na web com conteúdos gerados pelos utilizadores. Pode até utilizar uma etiqueta link no cabeçalho da página ao invés de a, mas o HTML deve ser acessível sem executar JavaScript. here_is_how: Eis o que fazer hint_html: "Verificar a sua identidade no Mastodon é para todos. Baseado em normas públicas da web, agora e para sempre gratuitas. Tudo o que precisas é de um site pessoal pelo qual as pessoas te reconheçam. Quando colocas no teu perfil uma hiperligação para esse site, vamos verificar que o site tem uma hiperligação de volta para o teu perfil e mostrar um indicador visual." instructions_html: Copia e cola o código abaixo no HTML do teu site. Em seguida, adiciona o endereço do teu site num dos campos extras no teu perfil, na aba "Editar perfil" e guarda as alterações. @@ -2149,3 +2188,6 @@ pt-PT: not_supported: Este navegador não funciona com chaves de segurança otp_required: Para utilizares chaves de segurança, ativa primeiro a autenticação de dois fatores. registered_on: Registado em %{date} + wrapstodon: + description: Veja como %{name} utilizou o Mastodon este ano! + title: Wrapstodon %{year} de %{name} diff --git a/config/locales/ru.yml b/config/locales/ru.yml index cdccbb65e11bdb..7441211dd11672 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -29,181 +29,181 @@ ru: admin: account_actions: action: Выполнить действие - already_silenced: Эта учетная запись уже ограничена. - already_suspended: Действие этой учетной записи уже приостановлено. + already_silenced: Эта учётная запись уже ограничена. + already_suspended: Эта учётная запись уже заблокирована. title: Произвести модерацию учётной записи %{acct} account_moderation_notes: - create: Создать - created_msg: Заметка модератора успешно создана! - destroyed_msg: Заметка модератора успешно удалена! + create: Создать заметку + created_msg: Заметка для модераторов добавлена + destroyed_msg: Заметка для модераторов удалена accounts: - add_email_domain_block: Забанить email домен - approve: Подтвердить - approved_msg: Успешно одобрена заявка на регистрацию %{username} + add_email_domain_block: " Добавить блокировку по домену эл. почты" + approve: Одобрить + approved_msg: Заявка на регистрацию %{username} одобрена are_you_sure: Вы уверены? - avatar: Аватар + avatar: Фото профиля by_domain: Домен change_email: - changed_msg: Адрес эл. почты успешно изменен! - current_email: Текущий e-mail - label: Сменить e-mail - new_email: Новый e-mail - submit: Сменить e-mail - title: Сменить e-mail для %{username} + changed_msg: Адрес электронной почты изменён + current_email: Текущий адрес электронной почты + label: Изменить адрес эл. почты + new_email: Новый адрес электронной почты + submit: Изменить адрес электронной почты + title: Изменить адрес электронной почты пользователя %{username} change_role: - changed_msg: Роль успешно изменена! + changed_msg: Роль изменена edit_roles: Управление ролями пользователей label: Изменить роль - no_role: Нет роли - title: Изменить роль %{username} + no_role: Без роли + title: Изменить роль пользователя %{username} confirm: Подтвердить - confirmed: Подтверждено - confirming: Подтверждение + confirmed: Подтверждена + confirming: Ожидает подтверждения custom: Другое delete: Удалить данные - deleted: Удалён + deleted: Удалена demote: Разжаловать - destroyed_msg: Данные %{username} поставлены в очередь на удаление - disable: Заморозка - disable_sign_in_token_auth: Отключить аутентификацию по e-mail кодам + destroyed_msg: Данные %{username} будут удалены в ближайшее время + disable: Отключить + disable_sign_in_token_auth: Отключить аутентификацию с помощью одноразового пароля по эл. почте disable_two_factor_authentication: Отключить 2FA - disabled: Отключено + disabled: Отключена display_name: Отображаемое имя domain: Домен - edit: Изменить - email: E-mail - email_status: Статус e-mail + edit: Редактировать + email: Адрес электронной почты + email_status: Подтверждение учётной записи enable: Включить - enable_sign_in_token_auth: Включить аутентификацию по e-mail кодам - enabled: Включен - enabled_msg: Учётная запись %{username} успешно разморожена + enable_sign_in_token_auth: Включить аутентификацию с помощью одноразового пароля по эл. почте + enabled: Включена + enabled_msg: Отменено отключение учётной записи %{username} followers: Подписчики follows: Подписки - header: Шапка + header: Обложка профиля inbox_url: URL входящих invite_request_text: Причины для присоединения invited_by: Приглашение выдал(а) - ip: IP + ip: IP-адрес joined: Дата регистрации location: all: Все local: Локальные - remote: Удаленные - title: Размещение - login_status: Статус учётной записи - media_attachments: Медиафайлы - memorialize: Сделать мемориалом - memorialized: Превращён в памятник - memorialized_msg: "%{username} успешно превращён в памятник" + remote: С других серверов + title: По расположению + login_status: Состояние учётной записи + media_attachments: Медиавложения + memorialize: Почтить память + memorialized: Мемориальная + memorialized_msg: Учётная запись %{username} превращена в мемориальную moderation: active: Действующие all: Все - disabled: Отключено - pending: В ожидании + disabled: Отключённые + pending: Ожидают одобрения silenced: Ограниченные suspended: Заблокированные - title: Модерация + title: По состоянию moderation_notes: Заметки модератора most_recent_activity: Последняя активность - most_recent_ip: Последний IP + most_recent_ip: Недавние IP-адреса no_account_selected: Ничего не изменилось, так как ни одна учётная запись не была выделена - no_limits_imposed: Без ограничений + no_limits_imposed: Ограничения отсутствуют no_role_assigned: Роль не присвоена - not_subscribed: Не подписаны - pending: Ожидает рассмотрения - perform_full_suspension: Блокировка - previous_strikes: Предыдущие замечания + not_subscribed: Подписка отсутствует + pending: Ожидает одобрения + perform_full_suspension: Заблокировать + previous_strikes: Зафиксированные нарушения previous_strikes_description_html: - few: У этой учётной записи %{count} замечания. - many: У этой учётной записи %{count} замечаний. - one: У этой учётной записи одно замечание. - other: У этой учетной записи %{count} замечание. - promote: Повысить + few: За этой учётной записью числится %{count} нарушения. + many: За этой учётной записью числится %{count} нарушений. + one: За этой учётной записью числится %{count} нарушение. + other: За этой учётной записью числится %{count} нарушений. + promote: Сделать администратором protocol: Протокол - public: Публичный + public: Профиль push_subscription_expires: Подписка PuSH истекает redownload: Обновить аватар redownloaded_msg: Профиль %{username} успешно обновлен из оригинала reject: Отклонить - rejected_msg: Успешно отклонено приложение для регистрации %{username} + rejected_msg: Заявка на регистрацию %{username} отклонена remote_suspension_irreversible: Данные этого аккаунта были необратимо удалены. remote_suspension_reversible_hint_html: Учётная запись была заблокирована, и данные будут полностью удалены %{date}. До этого момента её можно восстановить без каких-либо неприятных последствий. Если вы хотите немедленно удалить все данные учётной записи, вы можете сделать это ниже. - remove_avatar: Удалить аватар - remove_header: Убрать шапку - removed_avatar_msg: Аватар %{username} успешно удален - removed_header_msg: Успешно удалено изображение заголовка %{username} + remove_avatar: Удалить фото профиля + remove_header: Удалить обложку профиля + removed_avatar_msg: Фото профиля %{username} удалено + removed_header_msg: Обложка профиля %{username} удалена resend_confirmation: already_confirmed: Этот пользователь уже подтвержден send: Повторно отправить ссылку подтверждения success: Ссылка для подтверждения успешно отправлена! reset: Сбросить reset_password: Сбросить пароль - resubscribe: Переподписаться + resubscribe: Пересоздать подписку role: Роль search: Поиск - search_same_email_domain: Другие пользователи с тем же почтовым доменом - search_same_ip: Другие пользователи с таким же IP + search_same_email_domain: Другие пользователи с тем же доменом эл. почты + search_same_ip: Другие пользователи с тем же IP-адресом security: Безопасность security_measures: only_password: Только пароль password_and_2fa: Пароль и 2FA - sensitive: Деликатный + sensitive: Скрыть медиа sensitized: отмечено как деликатный контент shared_inbox_url: URL общих входящих show: created_reports: Жалобы, отправленные с этой учётной записи targeted_reports: Жалобы на эту учётную запись - silence: Скрытие - silenced: Заглушен + silence: Ограничить + silenced: Ограничена statuses: Посты - strikes: Предыдущие замечания + strikes: Зафиксированные нарушения subscribe: Подписаться suspend: Заблокировать - suspended: Заморожен + suspended: Заблокирована suspension_irreversible: Данные этой учётной записи были необратимо удалены. Вы можете разблокировать учетную запись, чтобы сделать её доступной, но это не восстановит ранее имевшиеся в ней данные. suspension_reversible_hint_html: Учётная запись была заблокирована, и данные будут полностью удалены %{date}. До этого момента её можно восстановить без каких-либо неприятных последствий. Если вы хотите немедленно удалить все данные учётной записи, вы можете сделать это ниже. title: Учётные записи unblock_email: Разблокировать e-mail адрес unblocked_email_msg: E-mail адрес %{username} разблокирован unconfirmed_email: Неподтверждённый e-mail - undo_sensitized: Снять отметку "деликатный" - undo_silenced: Отменить скрытие - undo_suspension: Снять блокировку - unsilenced_msg: Ограничения с учётной записи %{username} сняты успешно + undo_sensitized: Не скрывать медиа + undo_silenced: Не ограничивать + undo_suspension: Разблокировать + unsilenced_msg: Отменено ограничение учётной записи %{username} unsubscribe: Отписаться - unsuspended_msg: Учётная запись %{username} успешно разморожена + unsuspended_msg: Учётная запись %{username} разблокирована username: Имя пользователя view_domain: Просмотр сводки по домену - warn: Предупреждение + warn: Предупредить web: Веб whitelisted: В белом списке action_logs: action_types: - approve_appeal: Одобрение обжалований - approve_user: Утверждение регистраций + approve_appeal: Удовлетворение апелляции + approve_user: Одобрение регистрации assigned_to_self_report: Присвоение жалоб - change_email_user: Смена e-mail пользователей - change_role_user: Смена ролей пользователей - confirm_user: Подтверждение пользователей - create_account_warning: Выдача предупреждения - create_announcement: Создание объявлений - create_canonical_email_block: Создание блокировок e-mail + change_email_user: Смена адреса эл. почты пользователя + change_role_user: Изменение роли пользователя + confirm_user: Подтверждение пользователя + create_account_warning: Вынесение предупреждения + create_announcement: Создание объявления + create_canonical_email_block: Добавление блокировки по адресу эл. почты create_custom_emoji: Добавление эмодзи create_domain_allow: Разрешение доменов create_domain_block: Блокировка доменов - create_email_domain_block: Создание доменных блокировок e-mail + create_email_domain_block: Добавление блокировки по домену эл. почты create_ip_block: Создание правил для IP-адресов create_relay: Создание ретранслятора create_unavailable_domain: Добавление домена в список недоступных - create_user_role: Создание ролей + create_user_role: Создание роли create_username_block: Создать правило имени пользователя demote_user: Разжалование пользователей - destroy_announcement: Удаление объявлений - destroy_canonical_email_block: Удаление блокировок e-mail + destroy_announcement: Удаление объявления + destroy_canonical_email_block: Удаление блокировки по адресу эл. почты destroy_custom_emoji: Удаление эмодзи destroy_domain_allow: Отзыв разрешений для доменов destroy_domain_block: Разблокировка доменов - destroy_email_domain_block: Удаление доменных блокировок e-mail + destroy_email_domain_block: Удаление блокировки по домену эл. почты destroy_instance: Очистить домен destroy_ip_block: Удаление правил для IP-адресов destroy_relay: Удаление ретранслятора @@ -214,15 +214,15 @@ ru: disable_2fa_user: Отключение 2FA disable_custom_emoji: Отключение эмодзи disable_relay: Отключение ретранслятора - disable_sign_in_token_auth_user: Отключение аутентификации по e-mail кодам + disable_sign_in_token_auth_user: Отключение аутентификации с помощью одноразового пароля по эл. почте disable_user: Заморозка пользователей enable_custom_emoji: Включение эмодзи enable_relay: Включение ретранслятора - enable_sign_in_token_auth_user: Включение аутентификации по e-mail кодам + enable_sign_in_token_auth_user: Включение аутентификации с помощью одноразового пароля по эл. почте enable_user: Разморозка пользователей memorialize_account: Присвоение пользователям статуса «мемориала» promote_user: Повышение пользователей - publish_terms_of_service: Опубликование пользовательского соглашения + publish_terms_of_service: Публикация пользовательского соглашения reject_appeal: Отклонение обжалований reject_user: Отклонение регистраций remove_avatar_user: Удаление аватаров @@ -247,8 +247,8 @@ ru: update_user_role: Изменение ролей update_username_block: Обновить правило имени пользователя actions: - approve_appeal_html: "%{name} одобрил(а) обжалование действий модерации от %{target}" - approve_user_html: "%{name} утвердил(а) регистрацию %{target}" + approve_appeal_html: "%{name} удовлетворил(а) апелляцию %{target}" + approve_user_html: "%{name} одобрил(а) заявку на регистрацию %{target}" assigned_to_self_report_html: "%{name} назначил(а) себя для решения жалобы %{target}" change_email_user_html: "%{name} cменил(а) e-mail адрес пользователя %{target}" change_role_user_html: "%{name} изменил(а) роль %{target}" @@ -282,11 +282,11 @@ ru: disable_2fa_user_html: "%{name} отключил(а) требование двухэтапной авторизации для пользователя %{target}" disable_custom_emoji_html: "%{name} отключил(а) эмодзи %{target}" disable_relay_html: "%{name} отключил(а) ретранслятор %{target}" - disable_sign_in_token_auth_user_html: "%{name} отключил(а) аутентификацию по e-mail кодам для %{target}" + disable_sign_in_token_auth_user_html: "%{name} отключил(а) аутентификацию с помощью одноразового пароля по эл. почте для %{target}" disable_user_html: "%{name} заморозил(а) пользователя %{target}" enable_custom_emoji_html: "%{name} включил(а) эмодзи %{target}" enable_relay_html: "%{name} включил(а) ретранслятор %{target}" - enable_sign_in_token_auth_user_html: "%{name} включил(а) аутентификацию по e-mail кодам для %{target}" + enable_sign_in_token_auth_user_html: "%{name} включил(а) аутентификацию с помощью одноразового пароля по эл. почте для %{target}" enable_user_html: "%{name} разморозил(а) пользователя %{target}" memorialize_account_html: "%{name} перевел(а) учётную запись пользователя %{target} в статус памятника" promote_user_html: "%{name} повысил(а) пользователя %{target}" @@ -322,27 +322,27 @@ ru: unavailable_instance: "(доменное имя недоступно)" announcements: back: Вернуться к объявлениям - destroyed_msg: Объявление удалено. + destroyed_msg: Объявление удалено edit: title: Редактировать объявление - empty: Объявления не найдены. - live: В эфире + empty: Объявлений не найдено. + live: Опубликованные new: - create: Создать объявление - title: Новое объявление + create: Опубликовать объявление + title: Создать объявление preview: disclaimer: Так как пользователи не могут отказаться от получения уведомлений по электронной почте, их следует использовать только для действительно важных объявлений, например, чтобы сообщить об утечке персональных данных или о закрытии сервера. - explanation_html: 'Сообщение будет отравлено %{display_count} пользователям. В теле письма будет указан следующий текст:' - title: Предпросмотр объявления по электронной почте + explanation_html: 'Сообщение будет отравлено %{display_count} пользователям. Письмо будет содержать следующий текст:' + title: Предпросмотр уведомления о новом объявлении publish: Опубликовать - published_msg: Объявление опубликовано. + published_msg: Объявление опубликовано scheduled_for: Запланировано на %{time} scheduled_msg: Объявление добавлено в очередь публикации. title: Объявления - unpublish: Отменить публикацию - unpublished_msg: Объявление скрыто. - updated_msg: Объявление обновлено. - critical_update_pending: Ожидается обновление критического уровня + unpublish: Скрыть + unpublished_msg: Объявление скрыто + updated_msg: Объявление отредактировано + critical_update_pending: Доступно критическое обновление custom_emojis: assign_category: Задать категорию by_domain: Домен @@ -365,7 +365,7 @@ ru: listed: В списке new: title: Добавить новый эмодзи - no_emoji_selected: Не было изменено ни одного эмодзи + no_emoji_selected: Ничего не изменилось, так как ни один эмодзи не был выделен not_permitted: У вас недостаточно прав для выполнения этого действия overwrite: Заменить shortcode: Краткий код @@ -384,25 +384,25 @@ ru: new_users: новые пользователи opened_reports: новые жалобы pending_appeals_html: - few: "%{count} ожидают аппеляции" - many: "%{count} ожидают апелляции" - one: "%{count} ожидает апелляции" - other: 'Ожидают апелляции: %{count}' + few: "%{count} открытые апелляции" + many: "%{count} открытых апелляций" + one: "%{count} открытая апелляция" + other: "%{count} открытых апелляций" pending_reports_html: - few: "%{count} ожидающих отчета" - many: "%{count} ожидающих отчетов" - one: "%{count} ожидающий отчет" - other: "%{count} ожидающих отчетов" + few: "%{count} открытые жалобы" + many: "%{count} открытых жалоб" + one: "%{count} открытая жалоба" + other: "%{count} открытых жалоб" pending_tags_html: - few: "%{count} ожидающих хэштега" - many: "%{count} ожидающих хэштегов" - one: "%{count} ожидающий хэштег" - other: "%{count} ожидающих хэштегов" + few: "%{count} нерассмотренных хештега" + many: "%{count} нерассмотренных хештегов" + one: "%{count} нерассмотренный хештег" + other: "%{count} нерассмотренных хештегов" pending_users_html: - few: "%{count} ожидающих пользователя" - many: "%{count} ожидающих пользователей" - one: "%{count} ожидающий пользователь" - other: "%{count} ожидающих пользователей" + few: "%{count} заявки на регистрацию" + many: "%{count} заявок на регистрацию" + one: "%{count} заявка на регистрацию" + other: "%{count} заявок на регистрацию" resolved_reports: жалоб решено software: Программное обеспечение sources: Источники регистрации @@ -413,15 +413,15 @@ ru: website: Веб-сайт disputes: appeals: - empty: Обжалования не найдены. - title: Обжалования + empty: Апелляций не найдено. + title: Апелляции domain_allows: - add_new: Внести в белый список - created_msg: Домен добавлен в белый список - destroyed_msg: Домен убран из белого списка + add_new: Внести домен в белый список + created_msg: Домен внесён в белый список, федерация с ним теперь разрешена + destroyed_msg: Домен исключён из белого списка, федерация с ним больше не разрешена export: Экспорт import: Импорт - undo: Убрать из белого списка + undo: Исключить из белого списка domain_blocks: add_new: Заблокировать домен confirm_suspension: @@ -465,31 +465,31 @@ ru: undo: Отменить блокировку домена view: Посмотреть доменные блокировки email_domain_blocks: - add_new: Добавить новую + add_new: Добавить allow_registrations_with_approval: Разрешить регистрацию с одобрением attempts_over_week: - few: "%{count} попытки за последнюю неделю" - many: "%{count} попыток за последнюю неделю" - one: "%{count} попытка за последнюю неделю" + few: "%{count} попытки регистрации за последнюю неделю" + many: "%{count} попыток регистрации за последнюю неделю" + one: "%{count} попытка регистрации за последнюю неделю" other: "%{count} попыток регистрации за последнюю неделю" - created_msg: Домен email забанен, ура - delete: Удалить + created_msg: Домен электронной почты заблокирован + delete: Разблокировать dns: types: mx: Запись MX domain: Домен new: - create: Создать блокировку - resolve: Проверить домен - title: Блокировка нового почтового домена - no_email_domain_block_selected: Блокировки почтовых доменов не были изменены, так как ни один из них не был выбран - not_permitted: Не разрешено - resolved_dns_records_hint_html: Доменное имя указывает на следующие MX-домены, которые в конечном итоге отвечают за прием электронной почты. Блокировка MX-домена будет блокировать регистрации с любого адреса электронной почты, который использует тот же MX-домен, даже если видимое доменное имя отличается от него. Будьте осторожны, чтобы не заблокировать основных провайдеров электронной почты - resolved_through_html: Разрешено через %{domain} - title: Заблокированные e-mail домены + create: Заблокировать домен + resolve: Проверить DNS-записи + title: Заблокировать домен электронной почты + no_email_domain_block_selected: Ничего не изменилось, так как ни один почтовый домен не был выделен + not_permitted: Недостаточно прав + resolved_dns_records_hint_html: Доменное имя указывает на следующие MX-домены, которые в конечном итоге отвечают за приём электронной почты. Блокировка MX-домена приведёт к блокировке регистраций со всех адресов электронной почты, которые используют тот же MX-домен, даже если видимое доменное имя отличается от него. Будьте осторожны, чтобы не заблокировать основных провайдеров электронной почты + resolved_through_html: Из DNS-записей домена %{domain} + title: Блокировки по домену эл. почты export_domain_allows: new: - title: Импорт домена разрешён + title: Импорт белого списка доменов no_file: Файл не выбран export_domain_blocks: import: @@ -521,18 +521,20 @@ ru: registrations: confirm: Подтвердить reject: Отклонить + title: Подтвердить регистрацию FASP save: Сохранить select_capabilities: Выберите возможности sign_in: status: Пост + title: Fediverse Auxiliary Service Providers title: FASP follow_recommendations: - description_html: "Следуйте рекомендациям, чтобы помочь новым пользователям быстро находить интересный контент. Если пользователь не взаимодействовал с другими в достаточной степени, чтобы сформировать персонализированные рекомендации, вместо этого рекомендуется использовать эти учетные записи. Они пересчитываются на ежедневной основе на основе комбинации аккаунтов с наибольшим количеством недавних взаимодействий и наибольшим количеством местных подписчиков для данного языка." - language: Для языка - status: Статус + description_html: "Рекомендации профилей помогают новым пользователям быстрее найти что-нибудь интересное. Если пользователь мало взаимодействовал с другими и составить персонализированные рекомендации не получается, будут предложены указанные здесь профили. Эти рекомендации обновляются ежедневно из совокупности учётных записей с наибольшим количеством недавних взаимодействий и наибольшим количеством подписчиков с этого сервера для данного языка." + language: Фильтр по языку + status: Фильтр по состоянию suppress: Скрыть рекомендацию suppressed: Скрыта - title: Рекомендации подписок + title: Рекомендации профилей unsuppress: Восстановить рекомендацию instances: audit_log: @@ -758,16 +760,16 @@ ru: one: "%{count} пользователь" other: "%{count} пользователей" categories: - administration: Администрация + administration: Администрирование devops: DevOps invites: Приглашения - moderation: Модерация - special: Особые + moderation: Модерирование + special: Особые разрешения delete: Удалить - description_html: С помощью ролей пользователей вы можете настроить, к каким функциям и областям Mastodon у ваших пользователей будет доступ. - edit: Изменить роль '%{name}' ' + description_html: С помощью пользовательских ролей вы можете настроить, к каким функциям и разделам Mastodon у ваших пользователей будет доступ. + edit: Редактировать роль '%{name}' everyone: Разрешения по умолчанию - everyone_full_description_html: Это базовая роль, касающаяся всех пользователей, даже тех, кто не имеет назначенной роли. Все другие роли наследуют разрешения от нее. + everyone_full_description_html: Это базовая роль, которая распространяется на всех пользователей, включая тех, кому не присвоена никакая роль. Все другие роли наследуют разрешения от неё. permissions_count: few: "%{count} разрешения" many: "%{count} разрешений" @@ -775,108 +777,121 @@ ru: other: "%{count} разрешений" privileges: administrator: Администратор - administrator_description: Пользователи с этим разрешением будут обходить все права - delete_user_data: Удалить пользовательские данные - delete_user_data_description: Позволяет пользователям удалять данные других пользователей без задержки - invite_users: Пригласить пользователей - invite_users_description: Позволяет пользователям приглашать новых людей на сервер + administrator_description: Пользователи с этим разрешением смогут обойти любую проверку разрешений + delete_user_data: Удаление пользовательских данных + delete_user_data_description: Разрешить пользователям моментально удалять данные других пользователей + invite_users: Приглашение пользователей + invite_users_description: Разрешить пользователям приглашать на этот сервер новых людей manage_announcements: Управление объявлениями - manage_announcements_description: Позволяет пользователям управлять объявлениями на сервере + manage_announcements_description: Разрешить пользователям управлять объявлениями этого сервера manage_appeals: Управление апелляциями - manage_appeals_description: Позволяет пользователям просматривать апелляции на действия модерации - manage_blocks: Управление блоками + manage_appeals_description: Разрешить пользователям рассматривать апелляции на действия модераторов + manage_blocks: Управление блокировками manage_blocks_description: Позволить пользователям блокировать провайдеров электронной почты и IP-адреса - manage_custom_emojis: Управление смайлами - manage_custom_emojis_description: Позволяет пользователям управлять пользовательскими эмодзи на сервере - manage_federation: Управление Федерацией + manage_custom_emojis: Управление эмодзи + manage_custom_emojis_description: Разрешить пользователям управлять эмодзи этого сервера + manage_federation: Управление федерацией с другими серверами manage_federation_description: Позволяет пользователям блокировать или разрешить объединение с другими доменами и контролировать возможность доставки manage_invites: Управление приглашениями manage_invites_description: Позволяет пользователям просматривать и деактивировать пригласительные ссылки manage_reports: Управление жалобами manage_reports_description: Позволяет пользователям просматривать жалобы и осуществлять модерацию по ним manage_roles: Управление ролями - manage_roles_description: Разрешает пользователям управлять ролями и назначать их + manage_roles_description: Разрешить пользователям управлять нижестоящими ролями, а также присваивать эти роли другим пользователям manage_rules: Управление правилами - manage_rules_description: Разрешает пользователям изменять правила на сервере + manage_rules_description: Разрешить пользователям менять правила сервера manage_settings: Управление параметрами manage_settings_description: Разрешает пользователям управлять параметрами сайта manage_taxonomies: Управление трендами manage_taxonomies_description: Позволяет пользователям модерировать список популярного контента и менять настройки хэштегов - manage_user_access: Управление правами пользователей - manage_user_access_description: Разрешает пользователям отключать двухэтапную аутентификацию другим пользователям, менять их e-mail и сбрасывать их пароль + manage_user_access: Управление доступом в учётную запись + manage_user_access_description: Разрешить пользователям отключать двухфакторную аутентификацию другим пользователям, менять адреса электронной почты и сбрасывать пароли manage_users: Управление пользователями manage_users_description: Разрешает пользователям просматривать информацию о других пользователях и применять против них модерацию - manage_webhooks: Управление веб-хуками + manage_webhooks: Управление вебхуками manage_webhooks_description: Разрешает пользователям настраивать веб-хуки для административных событий - view_audit_log: Посмотреть журнал аудита - view_audit_log_description: Позволяет пользователям просматривать историю административных действий на сервере - view_dashboard: Открыть панель управления - view_dashboard_description: Разрешает пользователям просматривать статистику сервера + view_audit_log: Просмотр журнала аудита + view_audit_log_description: Разрешить пользователям просмотр истории административных действий на этом сервере + view_dashboard: Просмотр панели мониторинга + view_dashboard_description: Разрешить пользователям доступ к панели мониторинга и различным метрикам view_devops: DevOps - view_devops_description: Разрешить пользователям доступ к панелям Sidekiq и pgHero + view_devops_description: Разрешить пользователям доступ к панелям управления Sidekiq и pgHero + view_feeds: Просмотр живых и тематических лент + view_feeds_description: Разрешить пользователям доступ к живым и тематическим лентам вне зависимости от настроек сервера title: Роли rules: add_new: Добавить правило add_translation: Добавить перевод delete: Удалить - description_html: Хотя большинство утверждает, что прочитали и согласны с условиями обслуживания, обычно люди не читают их до тех пор, пока не возникнет проблема. Упростите просмотр правил вашего сервера с первого взгляда, предоставив их в виде простого маркированного списка. Старайтесь, чтобы отдельные правила были краткими и простыми, но старайтесь не разбивать их на множество отдельных элементов. + description_html: Хотя большинство людей утверждают, что они прочитали условия и согласились с ними, обычно никто не читает текст соглашений до тех пор, пока не возникнет проблема. Составьте правила вашего сервера в виде одноуровневого маркированного списка, чтобы было легче увидеть их сразу. Старайтесь, чтобы отдельные правила были простыми и лаконичными, но старайтесь не разбивать их на множество отдельных элементов. edit: Редактировать правило - empty: Правила сервера еще не определены. - move_down: Переместить вниз - move_up: Переместить вверх + empty: Правила сервера еще не добавлены. + move_down: Опустить вниз + move_up: Поднять вверх title: Правила сервера translation: Перевод - translations: Переводы + translations: Перевод + translations_explanation: При желании можно перевести правила на другие языки. Если перевод отсутствует, будет показан текст по умолчанию. Всегда необходимо следить за тем, чтобы перевод не расходится с текстом по умолчанию. settings: about: - manage_rules: Управление правилами на сервере - preamble: Предоставьте подробную информацию как сервер работает, модерируется, финансируется. - rules_hint: Это отдельное место для правил, которыми должны руководствоваться пользователи. - title: О нас + manage_rules: Перейти к правилам сервера + preamble: Предоставьте подробную информацию о том, как сервер функционирует, модерируется, финансируется. + rules_hint: Для правил, которых должны придерживаться пользователи, есть отдельный раздел. + title: О сервере + allow_referrer_origin: + desc: Когда ваши пользователи переходят по ссылкам на внешние сайты, их браузеры могут отправлять адрес вашего сервера Mastodon в заголовке Referer. Снимите этот флажок, если такое поведение позволит отследить уникальных пользователей, например если это ваш личный сервер Mastodon. + title: Позволить внешним сайтам видеть ваш сервер Mastodon как источник трафика appearance: - preamble: Настройте веб-интерфейс Мастодона. + preamble: Настройте веб-интерфейс Mastodon. title: Внешний вид branding: - preamble: Брендинг вашего сервера отличает его от других серверов сети. Эта информация может отображаться в различных средах, таких как веб-интерфейс Mastodon, нативные приложения, в виде предпросмотра ссылок на других веб-сайтах, в почтовых приложениях и так далее. По этой причине лучше держать эту информацию ясной, короткой и краткой. + preamble: 'Брендинг вашего сервера отличает его от других серверов сети. Эти сведения могут отображаться в различных средах: веб-интерфейсе Mastodon, нативных приложениях, предпросмотре ссылки на других веб-сайтах и в мессенджерах и так далее. По этой причине лучше формулировать информацию в ясной, лаконичной и понятной форме.' title: Брендинг - captcha_enabled: - desc_html: Это зависит от внешних скриптов из hCaptcha, которые могут представлять интерес для безопасности и конфиденциальности. Кроме того, это может сделать процесс регистрации значительно менее доступным для некоторых (особенно отключенных) людей. По этим причинам просьба рассмотреть альтернативные меры, такие, как регистрация, основанная на официальном утверждении или на приглашении. - title: Запрашивать новых пользователей для решения CAPTCHA для подтверждения учетной записи content_retention: - danger_zone: Осторожно! - preamble: Управление сохранением пользовательского контента в Mastodon. - title: Хранение контента + danger_zone: Небезопасные настройки + preamble: Управляйте тем, как Mastodon сохраняет пользовательские данные. + title: Хранение данных default_noindex: - desc_html: Влияет на всех пользователей, которые не изменили эту настройку сами - title: Исключить пользователей из индексации поисковиками по умолчанию + desc_html: Применяется к тем пользователям, которые не изменили эту настройку сами + title: Не индексировать профили пользователей по умолчанию discovery: - follow_recommendations: Рекомендации подписок - preamble: Наблюдение интересного контента играет важную роль при открытии новых пользователей, которые могут не знать ни одного Mastodon. Контролируйте как работают различные функции обнаружения на вашем сервере. - privacy: Конфиденциальность + follow_recommendations: Рекомендации профилей + preamble: Возможность найти интересный контент или получить его в рекомендациях играет важную роль в вовлечении новых пользователей, которые могут ещё не знать никого в Mastodon. Управляйте тем, как различные функции обзора работают на вашем сервере. + privacy: Приватность profile_directory: Каталог профилей public_timelines: Публичные ленты - publish_statistics: Опубликовать стаитстику + publish_statistics: Публикация статистики title: Обзор - trends: Популярное + trends: Актуальное domain_blocks: - all: Всем + all: Кому угодно disabled: Никому - users: Залогиненным локальным пользователям + users: Авторизованным пользователям этого сервера + feed_access: + modes: + authenticated: Только авторизованные пользователи + disabled: Только пользователи с особой ролью + public: Кто угодно + landing_page: + values: + about: О сервере + local_feed: Локальная лента + trends: Актуальное registrations: - moderation_recommandation: Убедитесь, что у вас есть адекватная и оперативная команда модераторов, прежде чем открывать регистрацию для всех желающих! - preamble: Контролируйте, кто может создать учетную запись на вашем сервере. + moderation_recommandation: Прежде чем открывать регистрацию для всех желающих, убедитесь, что у вас есть компетентная команда модераторов, способная на быструю реакцию! + preamble: Контролируйте, кто может создать учётную запись на вашем сервере. title: Регистрации registrations_mode: modes: - approved: Для регистрации требуется подтверждение - none: Никто не может регистрироваться - open: Все могут регистрироваться - warning_hint: Мы рекомендуем использовать "Требуется одобрение для регистрации", если вы не уверены, что ваша команда модераторов сможет своевременно справиться со спамом и злоумышленными регистрациями. + approved: Регистрация требует подтверждения + none: Никто не может зарегистрироваться + open: Кто угодно может зарегистрироваться + warning_hint: Мы рекомендуем использовать опцию «Регистрация требует подтверждения», если вы не уверены, что ваша команда модераторов сможет своевременно справиться со спамом и злоумышленными регистрациями. security: - authorized_fetch: Требовать аутентификацию от федеративных серверов - authorized_fetch_hint: Требование аутентификации от федеративных серверов позволяет более строго соблюдать блокировки как на уровне пользователя, так и на уровне сервера. Однако при этом снижается производительность, уменьшается охват ваших ответов и могут возникнуть проблемы совместимости с некоторыми федеративными сервисами. Кроме того, это не помешает специальным исполнителям получать ваши публичные сообщения и учётные записи. - authorized_fetch_overridden_hint: В настоящее время вы не можете изменить этот параметр, поскольку он переопределяется переменной среды. - federation_authentication: Принудительная аутентификация федерации + authorized_fetch: Требовать аутентификацию для межсерверного взаимодействия + authorized_fetch_hint: Требование аутентификации для сервер-серверного взаимодействия позволяет более строго соблюдать блокировки как на уровне пользователя, так и на уровне сервера. Однако при этом снижается производительность, уменьшается охват ваших ответов, и могут возникнуть проблемы совместимости с некоторыми федеративными сервисами. Кроме того, это не остановит заинтересованных лиц от получения публичных постов и учётных записей с вашего сервера. + authorized_fetch_overridden_hint: В данный момент вы не можете изменить этот параметр, поскольку он переопределён переменной среды. + federation_authentication: Обязательная аутентификация межсерверного взаимодействия title: Настройки сервера site_uploads: delete: Удалить загруженный файл @@ -915,6 +930,7 @@ ru: no_status_selected: Ничего не изменилось, так как ни один пост не был выделен open: Открыть запись original_status: Оригинальный пост + quotes: Цитаты reblogs: Продвинули replied_to_html: Ответ пользователю %{acct_link} status_changed: Пост изменен @@ -922,6 +938,7 @@ ru: title: Посты пользователя - @%{name} trending: Популярное view_publicly: Открыть по публичной ссылке + view_quoted_post: Просмотр цитируемого сообщения visibility: Видимость with_media: С файлами strikes: @@ -1006,9 +1023,9 @@ ru: draft: Черновик generate: Использовать шаблон generates: - action: Генерировать + action: Сгенерировать chance_to_review_html: "Сгенерированное пользовательское соглашение не будет опубликовано автоматически. У вас будет возможность просмотреть результат. Введите все необходимые сведения, чтобы продолжить." - explanation_html: Шаблон пользовательского соглашения приводится исключительно в ознакомительных целях, и не может рассматриваться как юридическая консультация по тому или иному вопросу. Обратитесь к своему юрисконсульту насчёт вашей ситуации и имеющихся правовых вопросов. + explanation_html: Шаблон пользовательского соглашения приводится исключительно в ознакомительных целях и не может рассматриваться как юридическая консультация по тому или иному вопросу. Обратитесь к своему юрисконсульту насчёт вашей ситуации и имеющихся правовых вопросов. title: Создание пользовательского соглашения going_live_on_html: Вступило в силу с %{date} history: История @@ -1018,8 +1035,8 @@ ru: notified_on_html: 'Дата уведомления пользователей: %{date}' notify_users: Уведомить пользователей preview: - explanation_html: 'Сообщение будет отравлено %{display_count} пользователям, которые зарегистрировались до %{date}. В теле письма будет указан следующий текст:' - send_preview: Отправить предпросмотр на %{email} + explanation_html: 'Сообщение будет отравлено %{display_count} пользователям, которые зарегистрировались до %{date}. Письмо будет содержать следующий текст:' + send_preview: Отправить тестовое уведомление на %{email} send_to_all: few: Отправить %{display_count} сообщения many: Отправить %{display_count} сообщений @@ -1131,17 +1148,17 @@ ru: warning_presets: add_new: Добавить delete: Удалить - edit_preset: Удалить шаблон предупреждения - empty: Вы еще не определили пресеты предупреждений. + edit_preset: Редактировать шаблон предупреждения + empty: Вы ещё не создали ни одного шаблона предупреждения. title: Шаблоны предупреждений webhooks: - add_new: Добавить конечную точку + add_new: Добавить вебхук delete: Удалить description_html: "Вебхуки позволяют Mastodon отправлять вашим приложениям уведомления в реальном времени о выбранных происходящих событиях, а они могут обрабатывать их в автоматическом режиме." disable: Отключить - disabled: Отключено + disabled: Деактивирован edit: Редактировать вебхук - empty: У вас пока нет настроенных конечных точек вебхуков. + empty: Вы ещё не настроили ни одного вебхука. enable: Включить enabled: Активен enabled_events: @@ -1150,9 +1167,9 @@ ru: one: "%{count} событие включено" other: "%{count} событий включено" events: События - new: Новый вебхук + new: Создать вебхук rotate_secret: Сгенерировать новый - secret: Ключ подписи + secret: Секретный ключ status: Состояние title: Вебхуки webhook: Вебхук @@ -1202,10 +1219,7 @@ ru: hint_html: Если вы собираетесь переехать с другой учётной записи на эту, то, прежде чем вы сможете перенести подписчиков со старой учётной записи, вы должны связать учётные записи здесь. Это действие само по себе безвредно и обратимо. Начать переезд можно только со старой учётной записи. remove: Отвязать учётную запись appearance: - advanced_web_interface: Многоколоночный интерфейс - advanced_web_interface_hint: 'Многоколоночный интерфейс даёт возможность использовать всю ширину экрана, позволяя вам обозревать столько информации, сколько вы захотите. Вы можете добавить множество различных столбцов: главную ленту, уведомления, глобальную ленту, неограниченное количество списков и хештегов.' animations_and_accessibility: Анимации и доступность - confirmation_dialogs: Диалоговые окна подтверждений discovery: Актуальное localization: body: Mastodon переводится добровольцами. @@ -1304,7 +1318,7 @@ ru: pending: Ваша заявка ожидает одобрения администраторами, это может занять немного времени. Вы получите письмо, как только заявку одобрят. redirecting_to: Ваша учётная запись деактивированна, потому что вы настроили перенаправление на %{acct}. self_destruct: Поскольку %{domain} закрывается, вы получите ограниченный доступ к вашей учетной записи. - view_strikes: Просмотр предупреждений, которые модераторы выносили вам ранее + view_strikes: Просмотр нарушений, зафиксированных ранее при модерации вашей учётной записи too_fast: Форма отправлена слишком быстро, попробуйте еще раз. use_security_key: Использовать электронный ключ user_agreement_html: Мной прочитаны и приняты пользовательское соглашение и политика конфиденциальности @@ -1369,7 +1383,7 @@ ru: appeal_submitted_at: Апелляция отправлена appealed_msg: Ваша апелляция была отправлена. Если она будет одобрена, вы будете уведомлены. appeals: - submit: Подать обжалование + submit: Подать апелляцию approve_appeal: Одобрить обжалование associated_report: Связанные обращения created_at: Дата @@ -1378,15 +1392,15 @@ ru: reject_appeal: Отклонить обжалование status: 'Пост #%{id}' status_removed: Пост уже удален из системы - title: "%{action} от %{date}" + title: "%{action} (%{date})" title_actions: delete_statuses: Удаление поста - disable: Заморозка аккаунта + disable: Отключение учётной записи mark_statuses_as_sensitive: Помечать посты как деликатные - none: Требующие внимания + none: Предупреждение sensitive: Отметить учетную запись как деликатную - silence: Ограничение учетной записи - suspend: Приостановка Аккаунта + silence: Ограничение учётной записи + suspend: Блокировка учётной записи your_appeal_approved: Ваша апелляция одобрена your_appeal_pending: Вы подали апелляцию your_appeal_rejected: Ваша апелляция отклонена @@ -1654,6 +1668,7 @@ ru: authentication_methods: otp: приложения для генерации кодов password: пароля + sign_in_token: одноразового пароля по эл. почте webauthn: электронного ключа description_html: Если вы заметили действия, которых не совершали, вам следует сменить пароль и включить двухфакторную аутентификацию. empty: История входов отсутствует @@ -1710,7 +1725,6 @@ ru: disabled_account: Переезд приведёт к тому, что вашу текущую учётную запись нельзя будет полноценно использовать. Тем не менее у вас останется доступ к экспорту данных и повторной активации учётной записи. followers: В результате переезда все ваши подписчики будут перенесены с текущей учётной записи на новую only_redirect_html: Также вы можете настроить перенаправление без переноса подписчиков. - other_data: Никакие другие данные не будут автоматически перенесены redirect: Профиль текущей учётной записи будет исключён из поиска, а в нём появится объявление о переезде moderation: title: Модерация @@ -2025,7 +2039,7 @@ ru: terms_of_service_interstitial: past_preamble_html: Мы изменили наше пользовательское соглашение с момента вашего последнего посещения. Мы рекомендуем вам ознакомиться с обновленным соглашением. review_link: Посмотреть пользовательское соглашение - title: Изменяется пользовательское соглашение %{domain} + title: Изменяется пользовательское соглашение на сервере %{domain} themes: contrast: Mastodon (высококонтрастная) default: Mastodon (тёмная) @@ -2087,17 +2101,17 @@ ru: change_password: сменить пароль details: 'Подробности о новом входе:' explanation: Мы заметили вход в вашу учётную запись с нового IP-адреса. - further_actions_html: Если это были не вы, рекомендуем вам немедленно %{action} и включить двухфакторную авторизацию, чтобы обезопасить свою учётную запись. + further_actions_html: Если это были не вы, рекомендуем немедленно %{action} и включить двухфакторную аутентификацию, чтобы обезопасить свою учётную запись. subject: В вашу учётную запись был выполнен вход с нового IP-адреса title: Выполнен вход terms_of_service_changed: agreement: Продолжая использовать %{domain}, вы соглашаетесь с этими условиями. Если вы не согласны с новыми условиями, вы в любой момент можете удалить вашу учётную запись на %{domain}. - changelog: 'Вот что обновление условий будет значит для вас в общих чертах:' - description: 'Вы получили это сообщение, потому что мы внесли некоторые изменения в пользовательское соглашение %{domain}. Эти изменения вступят в силу %{date}. Рекомендуем вам ознакомиться с обновлёнными условиями по ссылке:' - description_html: Вы получили это сообщение, потому что мы внесли некоторые изменения в пользовательское соглашение %{domain}. Эти изменения вступят в силу %{date}. Рекомендуем вам ознакомиться с обновлёнными условиями. + changelog: 'Вот что обновление условий будет значить для вас в общих чертах:' + description: 'Вы получили это сообщение, потому что мы внесли некоторые изменения в пользовательское соглашение сервера %{domain}. Эти изменения вступят в силу %{date}. Рекомендуем вам ознакомиться с обновлёнными условиями по ссылке:' + description_html: Вы получили это сообщение, потому что мы внесли некоторые изменения в пользовательское соглашение сервера %{domain}. Эти изменения вступят в силу %{date}. Рекомендуем вам ознакомиться с обновлёнными условиями. sign_off: Ваш %{domain} - subject: Обновления наших условий использования - subtitle: На %{domain} изменилось пользовательское соглашение + subject: Мы обновляем наше пользовательское соглашение + subtitle: Изменяется пользовательское соглашение на сервере %{domain} title: Важное обновление warning: appeal: Обжаловать diff --git a/config/locales/sc.yml b/config/locales/sc.yml index 606b47cc40d19f..55bb21608d42ee 100644 --- a/config/locales/sc.yml +++ b/config/locales/sc.yml @@ -655,10 +655,7 @@ sc: hint_html: Si boles mudare dae un'àteru contu a custu, inoghe as a pòdere creare unu nomìngiu, chi est rechestu in antis de sighire cun sa tràmuda de is persones chi ti sighint dae su contu betzu a custu. Custa atzione est innòcua e reversìbile. Tràmuda de su contu betzu cumintzada. remove: Disconnete su nomìngiu appearance: - advanced_web_interface: Interfache web avantzada - advanced_web_interface_hint: 'Si boles impreare totu sa largària de s''ischermu, s''interfache web avantzada ti permitit de cunfigurare diversas colunnas pro bìdere meda prus informatzione in contemporànea: printzipale, notìficas, lìnia de tempus federada e cale si siat nùmeru de listas e etichetas.' animations_and_accessibility: Animatziones e atzessibilidade - confirmation_dialogs: Diàlogos de cunfirmatzione discovery: Iscoberta localization: body: Mastodon est bortadu in manera voluntària. @@ -954,7 +951,6 @@ sc: disabled_account: A pustis, su contu atuale tuo no at a èssere prus operativu in manera cumpleta. Sende gasi, as a tènnere atzessu a s'esportatzione de datos e a sa re-ativatzione. followers: Custa atzione at a tramudare totu sa gente chi ti sighit dae su contu atuale a su contu nou only_redirect_html: In alternativa, podes isceti cunfigurare un'indiritzamentu in su profilu tuo. - other_data: Perunu àteru datu at a èssere tramudadu in automàticu redirect: Su profilu de su contu atuale tuo at a èssere atualizadu cun un'avisu de indiritzamentu e at a èssere esclùidu dae is chircas moderation: title: Moderatzione diff --git a/config/locales/sco.yml b/config/locales/sco.yml index 5c1e451446b47f..8aa4454f0b29dc 100644 --- a/config/locales/sco.yml +++ b/config/locales/sco.yml @@ -672,7 +672,6 @@ sco: title: Content retention discovery: follow_recommendations: Follae recommendations - preamble: Bringin forret interestin content helps ye tae bring in new uisers thit mibbie wullnae ken oniebody on Mastodon yit. Control hou various discovery features wirk on yer server. profile_directory: Profile directory public_timelines: Public timelines title: Discovery @@ -868,10 +867,7 @@ sco: hint_html: If ye'r wantin fir tae flit fae anither accoont tae this ane, ye kin mak a alias here, this is requirt afore ye kin gae forret flittin follaers fae the auld accoont tae this ane. This action bi an o itsel is hermless an reversible. The accoont migration is initiatie fae the auld accoont. remove: Unlink alias appearance: - advanced_web_interface: Advanced wab interface - advanced_web_interface_hint: 'Gin ye''r wantin fir tae mak uise o the ful width o yer screen, the advanced wab interface lets ye configure a wheen o different columns sae''s ye kin see as muckle information at the same time as ye want: Hame, notes, federatit timeline, onie nummer o lists an hashtags.' animations_and_accessibility: Animations an accessibility - confirmation_dialogs: Confirmation dialogs discovery: Discovery localization: body: Mastodon is translatit bi volunteers. @@ -1183,7 +1179,6 @@ sco: disabled_account: Yer current accoont wullnae be fully uisable efterwart. Hou an iver, ye wull hae access tae data export as weil as re-activation. followers: This action wull flit aw follaers fae the current accoont tae the new accoont only_redirect_html: Alternatively, ye kin ainly pit up a redirect on yer profile. - other_data: Nae ither data wull get flittit automatically redirect: Yer current accoont's profile wull get updatit wi a redirect note an be excludit fae seirches moderation: title: Moderation diff --git a/config/locales/si.yml b/config/locales/si.yml index e6b561b2ba1212..9c3ff9c904d132 100644 --- a/config/locales/si.yml +++ b/config/locales/si.yml @@ -754,10 +754,7 @@ si: hint_html: ඔබට වෙනත් ගිණුමකින් මෙය වෙත මාරු වීමට අවශ්‍ය නම්, මෙහිදී ඔබට අන්වර්ථ නාමයක් සෑදිය හැක, එය පැරණි ගිණුමෙන් අනුගාමිකයින් මෙම ගිණුමට ගෙන යාමට පෙර අවශ්‍ය වේ. මෙම ක්‍රියාවම හානිකර නොවන සහ ආපසු හැරවිය හැකිවේ. ගිණුම් සංක්‍රමණය පැරණි ගිණුමෙන් ආරම්භ වේ. remove: අන්වර්ථය විසන්ධි කරන්න appearance: - advanced_web_interface: සංකීර්ණ අතුරු මුහුණත - advanced_web_interface_hint: 'ඔබට ඔබේ සම්පූර්ණ තිරයේ පළල භාවිතා කිරීමට අවශ්‍ය නම්, උසස් වෙබ් අතුරු මුහුණත ඔබට අවශ්‍ය පරිදි එකම වේලාවක බොහෝ තොරතුරු බැලීමට විවිධ තීරු වින්‍යාස කිරීමට ඉඩ දෙයි: නිවස, දැනුම්දීම්, ෆෙඩරේටඩ් කාලරාමුව, ඕනෑම ලැයිස්තු සහ හැෂ් ටැග්.' animations_and_accessibility: සජීවිකරණ සහ ප්‍රවේශ්‍යතාව - confirmation_dialogs: තහවුරු කිරීමේ සංවාද discovery: සොයාගැනීම localization: body: මාස්ටඩන් ස්වේච්ඡාවෙන් පරිවර්තනය කර ඇත. @@ -1070,7 +1067,6 @@ si: disabled_account: ඔබගේ ජංගම ගිණුම පසුව සම්පූර්ණයෙන්ම භාවිතා කළ නොහැක. කෙසේ වෙතත්, ඔබට දත්ත අපනයනයට මෙන්ම නැවත සක්‍රිය කිරීමට ප්‍රවේශය ඇත. followers: මෙම ක්‍රියාව සියළුම අනුගාමිකයින් ජංගම ගිණුමේ සිට නව ගිණුමට ගෙන යනු ඇත only_redirect_html: විකල්පයක් ලෙස, ඔබට ඔබගේ පැතිකඩහි යළි-යොමුවීමක් පමණක් තැබිය හැකිය. - other_data: වෙනත් දත්ත කිසිවක් ස්වයංක්‍රීයව ගෙන නොයනු ඇත redirect: ඔබගේ ජංගම ගිණුමේ පැතිකඩ යළි-යොමු කිරීමේ දැන්වීමක් සමඟ යාවත්කාලීන කෙරෙන අතර සෙවුම් වලින් බැහැර කරනු ලැබේ moderation: title: මැදිහත්කරණය diff --git a/config/locales/simple_form.an.yml b/config/locales/simple_form.an.yml index ad17b19ba058aa..b720cc176001d4 100644 --- a/config/locales/simple_form.an.yml +++ b/config/locales/simple_form.an.yml @@ -60,13 +60,11 @@ an: featured_tag: name: 'Aquí son belunas d''as etiquetas que mas has utilizau recientment:' filters: - action: Triar qué acción realizar quan una publicación coincide con o filtro actions: hide: Amagar completament lo conteniu filtrau, comportando-se como si no existise warn: Amagar lo conteniu filtrau dezaga d'una alvertencia mencionando lo titol d'o filtro form_admin_settings: activity_api_enabled: Conteyo de publicacions locals, usuarios activos y nuevos rechistros en periodos semanals - bootstrap_timeline_accounts: Estas cuentas amaneixerán en a parte superior d'as recomendacions d'os nuevos usuarios. closed_registrations_message: Amostrau quan los rechistros son zarraus custom_css: Puetz aplicar estilos personalizaus a la versión web de Mastodon. mascot: Reemplaza la ilustración en a interficie web abanzada. @@ -81,7 +79,6 @@ an: site_title: Cómo puede referir-se la chent a lo tuyo servidor amás de per lo nombre de dominio. theme: Lo tema que los visitantes no rechistraus y los nuevos usuarios veyen. thumbnail: Una imachen d'aproximadament 2:1 s'amuestra chunto a la información d'o tuyo servidor. - timeline_preview: Los visitantes no rechistraus podrán navegar per los mensaches publicos mas recients disponibles en o servidor. trendable_by_default: Omitir la revisión manual d'o conteniu en tendencia. Los elementos individuals encara podrán eliminar-se d'as tendencias. trends: Las tendencias amuestran qué mensaches, etiquetas y noticias son ganando tracción en o tuyo servidor. form_challenge: @@ -178,10 +175,8 @@ an: setting_aggregate_reblogs: Agrupar retutz en as linias de tiempo setting_always_send_emails: Ninviar siempre notificacions per correu setting_auto_play_gif: Reproducir automaticament los GIFs animaus - setting_boost_modal: Amostrar finestra de confirmación antes de retutar setting_default_language: Idioma de publicación setting_default_sensitive: Marcar siempre imachens como sensibles - setting_delete_modal: Amostrar dialogo de confirmación antes de borrar una publicación setting_disable_swiping: Deshabilitar movimientos d'eslizamiento setting_display_media: Visualización multimedia setting_display_media_default: Per defecto @@ -233,7 +228,6 @@ an: site_title: Nombre d'o servidor theme: Tema per defecto thumbnail: Miniatura d'o servidor - timeline_preview: Permitir l'acceso no autenticau a las linias de tiempo publicas trendable_by_default: Permitir tendencias sin revisión previa trends: Habilitar tendencias interactions: diff --git a/config/locales/simple_form.ar.yml b/config/locales/simple_form.ar.yml index 4e2c1cf67ee938..27d16dcff6413d 100644 --- a/config/locales/simple_form.ar.yml +++ b/config/locales/simple_form.ar.yml @@ -73,7 +73,6 @@ ar: featured_tag: name: 'فيما يلي بعض الوسوم التي استخدمتها مؤخراً:' filters: - action: اختر الإجراء الذي سينفذ عند تطابق المشاركة فلتر التصفية actions: blur: إخفاء الوسائط وراء تحذير، دون إخفاء النص نفسه hide: إخفاء المحتويات التي تم تصفيتها، والتصرف كما لو أنها غير موجودة @@ -82,7 +81,6 @@ ar: activity_api_enabled: عدد المنشورات المحلية و المستخدمين الناشطين و التسجيلات الأسبوعية الجديدة app_icon: WEBP أو PNG أو GIF أو JPG. يتجاوز أيقونة التطبيق الافتراضية على الجوالات مع أيقونة مخصصة. backups_retention_period: للمستخدمين القدرة على إنشاء أرشيفات لمنشوراتهم لتحميلها في وقت لاحق. عند التعيين إلى قيمة موجبة، سيتم حذف هذه الأرشيف تلقائياً من وحدة تخزينك بعد عدد الأيام المحدد. - bootstrap_timeline_accounts: سيتم تثبيت هذه الحسابات على قمة التوصيات للمستخدمين الجدد. closed_registrations_message: ما سيعرض عند إغلاق التسجيلات content_cache_retention_period: سيتم حذف جميع المنشورات من الخوادم الأخرى (بما في ذلك التعزيزات والردود) بعد عدد الأيام المحدد، دون أي تفاعل محلي للمستخدم مع هذه المنشورات. وهذا يشمل المنشورات التي قام المستخدم المحلي بوضع علامة عليها كإشارات مرجعية أو المفضلة. وسوف تختفي أيضا الإشارات الخاصة بين المستخدمين من المثيلات المختلفة ويستحيل استعادتها. والغرض من استخدام هذا الإعداد هو مثيلات الغرض الخاص ويفسد الكثير من توقعات المستخدمين عند تنفيذها للاستخدام لأغراض عامة. custom_css: يمكنك تطبيق أساليب مخصصة على نسخة الويب من ماستدون. @@ -102,10 +100,8 @@ ar: status_page_url: العنوان التشعبي حيث يمكن للناس رؤية صفحة حالة هذا الخادم عند حدوث عطب ما theme: الشكل الذي يشاهده الزوار الجدد و الغير مسجلين الدخول. thumbnail: عرض حوالي 2:1 صورة إلى جانب معلومات الخادم الخاص بك. - timeline_preview: الزوار الذين سجلوا خروجهم سيكونون قادرين على تصفح أحدث المشاركات العامة المتاحة على الخادم. trendable_by_default: تخطي مراجعة المحتوى التريند اليدوي. لا يزال من الممكن الإزالة اللاحقة للعناصر الفردية من التريندات. trends: تُظهِر المتداولة أي من المنشورات والوسوم وقصص الأخبار التي تجذب الانتباه على خادمك. - trends_as_landing_page: إظهار المحتوى المتداوَل للمستخدمين والزوار غير المسجلين بدلاً من وصف هذا الخادم. يتطلب هذا تفعيل المتداولة. form_challenge: current_password: إنك بصدد الدخول إلى منطقة آمنة imports: @@ -225,12 +221,10 @@ ar: setting_aggregate_reblogs: جمّع المنشورات المعاد نشرها في الخيوط الزمنية setting_always_send_emails: ارسل إشعارات البريد الإلكتروني دائماً setting_auto_play_gif: تشغيل تلقائي لِوَسائط جيف المتحركة - setting_boost_modal: إظهار مربع حوار التأكيد قبل إعادة نشر أي منشور setting_default_language: لغة النشر setting_default_privacy: مدى ظهور المنشور setting_default_quote_policy: من يستطيع الاقتباس setting_default_sensitive: اعتبر الوسائط دائما كمحتوى حساس - setting_delete_modal: إظهار مربع حوار التأكيد قبل حذف أي منشور setting_disable_hover_cards: تعطيل معاينة الملف الشخصي عند التمرير setting_disable_swiping: تعطيل حركات التمرير setting_display_media: عرض الوسائط @@ -240,7 +234,6 @@ ar: setting_emoji_style: نمط الوجوه التعبيرية setting_expand_spoilers: توسيع المنشورات التي تحتوي على تحذيرات عن المحتوى دائما setting_hide_network: إخفِ شبكتك - setting_missing_alt_text_modal: إظهار حوار التأكيد قبل نشر وسائط دون نص بديل setting_reduce_motion: تخفيض عدد الصور في الوسائط المتحركة setting_system_font_ui: استخدم الخطوط الافتراضية للنظام setting_system_scrollbars_ui: استخدام شريط التمرير الافتراضي للنظام @@ -292,10 +285,8 @@ ar: status_page_url: الرابط التشعبي لصفحة حالة الخادم theme: الحُلَّة الإفتراضية thumbnail: الصورة المصغرة للخادم - timeline_preview: السماح بالوصول غير الموثق إلى الخيوط الزمنية العامة trendable_by_default: السماح للوسوم بالظهور على المتداوَلة دون مراجعة مسبقة trends: تمكين المتداوَلة - trends_as_landing_page: استخدام المُتداوَلة كصفحة ترحيب interactions: must_be_follower: حظر الإشعارات القادمة من حسابات لا تتبعك must_be_following: حظر الإشعارات القادمة من الحسابات التي لا تتابعها @@ -356,9 +347,7 @@ ar: jurisdiction: الاختصاص القانوني min_age: الحد الإدنى للعمر user: - date_of_birth_1i: يوم date_of_birth_2i: شهر - date_of_birth_3i: سنة role: الدور time_zone: النطاق الزمني user_role: diff --git a/config/locales/simple_form.ast.yml b/config/locales/simple_form.ast.yml index c042d3ced436cf..830835993e1c92 100644 --- a/config/locales/simple_form.ast.yml +++ b/config/locales/simple_form.ast.yml @@ -104,10 +104,8 @@ ast: setting_aggregate_reblogs: Agrupar los artículos compartíos nes llinies de tiempu setting_always_send_emails: Unviar siempre los avisos per corréu electrónicu setting_auto_play_gif: Reproducir automáticamente los GIFs - setting_boost_modal: Amosar el diálogu de confirmación enantes de compartir una publicación setting_default_language: Llingua de los artículos setting_default_sensitive: Marcar siempre tol conteníu como sensible - setting_delete_modal: Amosar el diálogu de confirmación enantes de desaniciar una publicación setting_disable_hover_cards: Desactivar la previsualización de perfiles al pasar el mur penriba setting_disable_swiping: Desactivar el movimientu de desplazamientu setting_display_media: Conteníu multimedia @@ -142,7 +140,6 @@ ast: site_title: Nome del sirvidor theme: Estilu predetermináu thumbnail: Miniatura del sirvidor - timeline_preview: Permitir l'accesu ensin autenticar a les llinies de tiempu públiques trendable_by_default: Permitir tendencies ensin revisión previa trends: Activar les tendencies interactions: diff --git a/config/locales/simple_form.az.yml b/config/locales/simple_form.az.yml index fd290627cab308..66f424d3e6379c 100644 --- a/config/locales/simple_form.az.yml +++ b/config/locales/simple_form.az.yml @@ -50,14 +50,11 @@ az: setting_aggregate_reblogs: Zaman xəttindəki TP-ları qruplaşdır setting_always_send_emails: E-poçt göndərişlərini həmişə göndər setting_auto_play_gif: Animasiyalı GIF-ləri avto-oxut - setting_boost_modal: Bir təkrar paylaşımı silməzdən əvvəl təsdiq dialoq pəncərəsini göstər setting_default_sensitive: Medianı həmişə həssas olaraq işarələ - setting_delete_modal: Bir göndərişi silməzdən əvvəl təsdiq dialoq pəncərəsini göstər setting_disable_hover_cards: Üzərinə gəldikdə profil önizləməsini sıradan çıxart setting_disable_swiping: Sürüşdürmə hərəkətlərini sıradan çıxart setting_display_media: Medianın nümayişi setting_expand_spoilers: Məzmun xəbərdarlığı ilə işarələnmiş göndərişləri həmişə genişləndir - setting_missing_alt_text_modal: Alternativ mətni olmayan medianı göndərməzdən əvvəl təsdiq dialoq pəncərəsini göstər setting_reduce_motion: Animasiyalarda hərəkəti azalt setting_system_font_ui: Sistemin ilkin şriftini istifadə et setting_system_scrollbars_ui: Sistemin ilkin sürüşdürmə çubuğunu istifadə et @@ -66,7 +63,6 @@ az: setting_use_pending_items: Yavaş rejim form_admin_settings: show_domain_blocks: Əngəllənən domenləri göstər - timeline_preview: Ümumi zaman xətlərinə səlahiyyətsiz erişimə icazə ver ip_block: severities: no_access: Erişimi əngəllə diff --git a/config/locales/simple_form.be.yml b/config/locales/simple_form.be.yml index 811dd80dae14f1..7f899b5139b481 100644 --- a/config/locales/simple_form.be.yml +++ b/config/locales/simple_form.be.yml @@ -54,8 +54,10 @@ be: password: Не менш за 8 сімвалаў phrase: Параўнанне адбудзецца нягледзячы на рэгістр тэксту і папярэджанні аб змесціве допісу scopes: Абярыце, якімі API праграма зможа карыстацца. Выбар дазволу найвышэйшага ўзроўню ўключае ў сябе дазволу астатніх узроўняў. + setting_advanced_layout: Адлюстроўваць Mastodon некалькімі калонкамі, дазваляючы Вам глядзець стужку, апавяшчэнні і трэцюю калонку на Ваш выбар. Не рэкамендуецца для малых экранаў. setting_aggregate_reblogs: Не паказваць новыя пашырэнні для допісаў, якія пашырылі нядаўна (закранае толькі нядаўнія пашырэнні) setting_always_send_emails: Звычайна лісты з апавяшчэннямі не будуць дасылацца, калі вы актыўна карыстаецеся Mastodon + setting_boost_modal: Калі ўключаны, пашырэнне спачатку будзе адкрываць акно пацвярджэння, у якім Вы зможаце змяняць бачнасць свайго пашырэння. setting_default_quote_policy_private: Допісы для падпісчыкаў, створаныя на Mastodon, нельга цытаваць іншым людзям. setting_default_quote_policy_unlisted: Калі людзі працытуюць Вас, іх допіс таксама будзе схаваны ад стужкі трэндаў. setting_default_sensitive: Далікатныя медыя прадвызначана схаваныя. Іх можна адкрыць адзіным клікам @@ -63,6 +65,7 @@ be: setting_display_media_hide_all: Заўсёды хаваць медыя setting_display_media_show_all: Заўсёды паказваць медыя setting_emoji_style: Як паказваць эмодзі. "Аўтаматычны" будзе намагацца выкарыстоўваць мясцовыя эмодзі, але для састарэлых браўзераў — Twemoji. + setting_quick_boosting_html: Калі ўключана, націсканне на %{boost_icon} значок пашырэння адразу пашырыць допіс замест адкрыцця меню пашырэння/цытавання. Перасоўвае дзеянне цытавання ў меню %{options_icon} (выбару). setting_system_scrollbars_ui: Працуе толькі ў камп'ютарных браўзерах на аснове Safari і Chrome setting_use_blurhash: Градыенты заснаваны на колерах схаваных выяў, але размываюць дэталі setting_use_pending_items: Схаваць абнаўленні стужкі за клікам замест аўтаматычнага пракручвання стужкі @@ -76,7 +79,7 @@ be: featured_tag: name: 'Вось некаторыя з хэштэгаў, якімі вы нядаўна карысталіся:' filters: - action: Абярыце, што зрабіць, калі допіс падпадае пад умовы фільтру + action: Выберыце, што трэба рабіць, калі допіс супадзе з фільтрам actions: blur: Схавайце медыя за знакам папярэджання, не хаваючы пры гэтым тэкст hide: Поўнасцю схаваць адфільтраванае змесціва, дзейнічаць, нібы яго не існуе @@ -85,13 +88,14 @@ be: activity_api_enabled: Падлік лакальна апублікаваных пастоў, актыўных карыстальнікаў і новых рэгістрацый у тыдзень app_icon: WEBP, PNG, GIF ці JPG. Заменіце прадвызначаны значок праграмы на мабільных прыладах карыстальніцкім значком. backups_retention_period: Карыстальнікі могуць ствараць архівы сваіх допісаў для наступнай запампоўкі. Пры станоўчай колькасці дзён гэтыя архівы будуць аўтаматычна выдаляцца са сховішча пасля заканчэння названай колькасці дзён. - bootstrap_timeline_accounts: Гэтыя ўліковыя запісы будуць замацаваны ў топе рэкамендацый для новых карыстальнікаў. + bootstrap_timeline_accounts: Гэтыя ўліковыя запісы будуць прымацаваныя наверсе рэкамендацый для новых карыстальнікаў. Дайце спіс уліковых запісаў, выкарыстоўваючы коску, каб раздзяліць іх. closed_registrations_message: Паказваецца, калі рэгістрацыя закрытая content_cache_retention_period: Усе допісы з іншых сервераў (разам з пашырэннямі і адказамі) будуць выдалены праз паказаную колькасць дзён, незалежна ад таго, як лакальны карыстальнік узаемадзейнічаў з гэтымі допісамі. Гэта датычыцца і тых допісаў, якія лакальны карыстальнік пазначыў у закладкі або ўпадабанае. Прыватныя згадванні паміж карыстальнікамі з розных экзэмпляраў сервераў таксама будуць страчаны і іх нельга будзе аднавіць. Выкарыстанне гэтай налады прызначана для экзэмпляраў сервераў спецыяльнага прызначэння і парушае многія чаканні карыстальнікаў пры выкарыстанні ў агульных мэтах. custom_css: Вы можаце прымяняць карыстальніцкія стылі ў вэб-версіі Mastodon. favicon: WEBP, PNG, GIF ці JPG. Замяняе прадвызначаны favicon Mastodon на ўласны значок. + landing_page: Выбірае, якую старонку бачаць новыя наведвальнікі, калі прыходзяць на Ваш сервер. Калі выбераце "Трэнды", тады неабходна іх уключыць у наладах Выяўленне. Калі выбераце "Тутэйшая стужка", тады ў наладах Выяўленне ў налады "Доступ да жывых стужак з лакальнымі допісамі" мусіць стаяць варыянт "Усе". mascot: Замяняе ілюстрацыю ў пашыраным вэб-інтэрфейсе. - media_cache_retention_period: Медыяфайлы з допісаў, зробленых выдаленымі карыстальнікамі, кэшыруюцца на вашым серверы. Пры станоўчым значэнні медыяфайлы будуць выдалены праз пазначаную колькасць дзён. Калі медыяданыя будуць запытаны пасля выдалення, яны будуць спампаваны зноў, калі зыходнае змесціва усё яшчэ даступнае. У сувязі з абмежаваннямі на частату абнаўлення картак перадпрагляду іншых сайтаў, рэкамендуецца ўсталяваць значэнне не менш за 14 дзён, інакш гэтыя карткі не будуць абнаўляцца па запыце раней за гэты тэрмін. + media_cache_retention_period: Медыяфайлы з допісаў, зробленых карыстальнікамі з іншых сервераў, кэшыруюцца на вашым серверы. Пры станоўчым значэнні медыяфайлы будуць выдалены праз пазначаную колькасць дзён. Калі медыяданыя будуць запытаныя пасля выдалення, яны будуць спампаваныя зноў, калі зыходнае змесціва усё яшчэ даступнае. У сувязі з абмежаваннямі на частату абнаўлення картак перадпрагляду іншых сайтаў, рэкамендуецца ўсталяваць значэнне не менш за 14 дзён, інакш гэтыя карткі не будуць абнаўляцца па запыце раней за гэты тэрмін. min_age: Карыстальнікі будуць атрымліваць запыт на пацвярджэнне даты нараджэння падчас рэгістрацыі peers_api_enabled: Спіс даменных імён, з якімі сутыкнуўся гэты сервер у федэральным сусвеце. Даныя пра тое, ці знаходзіцеся вы з дадзеным серверам у федэрацыі, не ўключаны. Уключаны толькі даныя пра тое, што ваш сервер ведае пра іншыя серверы. Гэта выкарыстоўваецца сэрвісамі, якія збіраюць статыстыку па федэрацыі ў агульным сэнсе. profile_directory: Дырэкторыя профіляў змяшчае спіс усіх карыстальнікаў, якія вырашылі быць бачнымі. @@ -105,10 +109,9 @@ be: status_page_url: URL старонкі, дзе людзі могуць бачыць стан гэтага сервера падчас збою theme: Тэма, што бачаць новыя карыстальнікі ды наведвальнікі, якія выйшлі. thumbnail: Выява памерамі прыкладна 2:1, якая паказваецца побач з інфармацыяй пра ваш сервер. - timeline_preview: Наведвальнікі, якія выйшлі, змогуць праглядаць апошнія публічныя допісы на серверы. trendable_by_default: Прапусціць ручны агляд трэндавага змесціва. Асобныя элементы ўсё яшчэ можна будзе выдаліць з трэндаў пастфактум. trends: Трэнды паказваюць, якія допісы, хэштэгі і навіны набываюць папулярнасць на вашым серверы. - trends_as_landing_page: Паказваць папулярнае змесціва карыстальнікам, якія выйшлі з сістэмы, і наведвальнікам, замест апісання гэтага сервера. Патрабуецца ўключэнне трэндаў. + wrapstodon: Прапанаваць мясцовым карыстальнікам інтэрактыўную зводку іх выкарыстання Mastodon на працягу года. Гэта функцыя даступная паміж 10-ым і 31-ым снежня кожнага года і прапаноўваецца карыстальнікам, якія зрабілі хаця б адзін публічны або ціхі публічны допіс, а таксама выкарысталі хаця б адзін хэштэг на працягу года. form_challenge: current_password: Вы ўваходзіце ў бяспечную зону imports: @@ -237,12 +240,12 @@ be: setting_aggregate_reblogs: Групаваць прасоўванні ў стужках setting_always_send_emails: Заўжды дасылаць для апавяшчэнні эл. пошты setting_auto_play_gif: Аўтапрайграванне анімаваных GIF - setting_boost_modal: Паказваць акно пацвярджэння перад пашырэннем + setting_boost_modal: Кантроль бачнасці пашырэння setting_default_language: Мова допісаў setting_default_privacy: Бачнасць допісаў setting_default_quote_policy: Хто можа цытаваць setting_default_sensitive: Заўсёды пазначаць кантэнт як далікатны - setting_delete_modal: Паказваць акно пацвярджэння перад выдаленнем допісу + setting_delete_modal: Папярэджваць перад выдаленнем допісу setting_disable_hover_cards: Адключыць перадпрагляд профілю пры навядзенні setting_disable_swiping: Адключыць прагортванні setting_display_media: Адлюстраванне медыя @@ -252,7 +255,8 @@ be: setting_emoji_style: Стыль эмодзі setting_expand_spoilers: Заўжды разгортваць допісы з папярэджаннем аб змесціве setting_hide_network: Схаваць вашы сувязі - setting_missing_alt_text_modal: Паказваць акно пацвярджэння перад публікацыяй медыя без альтэрнатыўнага тэксту + setting_missing_alt_text_modal: Папярэджваць перад публікацыяй допісу без альтэрнатыўнага тэксту + setting_quick_boosting: Уключыць хуткае пашырэнне setting_reduce_motion: Памяншэнне руху ў анімацыях setting_system_font_ui: Выкарыстоўваць прадвызначаны сістэмны шрыфт setting_system_scrollbars_ui: Паказваць паласу пракручвання па змаўчанні @@ -283,15 +287,20 @@ be: backups_retention_period: Працягласць захавання архіву карыстальніка bootstrap_timeline_accounts: Заўсёды раіць гэтыя ўліковыя запісы новым карыстальнікам closed_registrations_message: Уласнае паведамленне, калі рэгістрацыя немагчымая - content_cache_retention_period: Перыяд захоўвання выдаленага змесціва + content_cache_retention_period: Перыяд захоўвання змесціва з іншых сервераў custom_css: CSS карыстальніка favicon: Значок сайта + landing_page: Старонка прыбыцця для новых наведвальнікаў + local_live_feed_access: Доступ да жывых стужак з лакальнымі допісамі + local_topic_feed_access: Доступ да хэштэгавых і спасылачных стужак з лакальнымі допісамі mascot: Уласны маскот(спадчына) media_cache_retention_period: Працягласць захавання кэшу для медыя min_age: Патрабаванне мінімальнага ўзросту peers_api_enabled: Апублікаваць спіс знойдзеных сервераў у API profile_directory: Уключыць каталог профіляў registrations_mode: Хто можа зарэгістравацца + remote_live_feed_access: Доступ да жывых стужак з допісамі з іншых сервераў + remote_topic_feed_access: Доступ да хэштэгавых і спасылачных стужак з допісамі з іншых сервераў require_invite_text: Каб далучыцца, патрэбна прычына show_domain_blocks: Паказаць заблакіраваныя дамены show_domain_blocks_rationale: Паказваць прычыну блакавання даменаў @@ -304,10 +313,9 @@ be: status_page_url: URL старонкі статусу theme: Тэма па змаўчанні thumbnail: Мініяцюра сервера - timeline_preview: Дазволіць неаўтэнтыфікаваны доступ да публічных стужак trendable_by_default: Дазваляць трэнды без папярэдняй праверкі trends: Уключыць трэнды - trends_as_landing_page: Выкарыстоўваць трэнды ў якасці лэндзінга + wrapstodon: Уключыць Вынікадон interactions: must_be_follower: Заблакіраваць апавяшчэнні ад непадпісаных людзей must_be_following: Заблакіраваць апавяшчэнні ад людзей на якіх вы не падпісаны @@ -368,9 +376,9 @@ be: jurisdiction: Юрысдыкцыя min_age: Мінімальны ўзрост user: - date_of_birth_1i: Дзень + date_of_birth_1i: Год date_of_birth_2i: Месяц - date_of_birth_3i: Год + date_of_birth_3i: Дзень role: Роля time_zone: Часавы пояс user_role: diff --git a/config/locales/simple_form.bg.yml b/config/locales/simple_form.bg.yml index d1673b9e1fc32f..dc6baffb154038 100644 --- a/config/locales/simple_form.bg.yml +++ b/config/locales/simple_form.bg.yml @@ -74,7 +74,6 @@ bg: featured_tag: name: 'Ето няколко хаштага, които последно сте използвали:' filters: - action: Изберете кое действие да се извърши, прецеждайки съвпаденията на публикацията actions: blur: Скриване на мултимедия зад предупреждение, но без скриване на самия текст hide: Напълно скриване на филтрираното съдържание, сякаш не съществува @@ -83,7 +82,6 @@ bg: activity_api_enabled: Броят на местните публикувани публикации, дейни потребители и нови регистрации в седмични кофи app_icon: WEBP, PNG, GIF или JPG. Заменя подразбиращата се икона на приложението в мобилни устройства с произволна икона. backups_retention_period: Потребителите имат способността да пораждат архиви от публикациите си за по-късно изтегляне. Задавайки положителна стойност, тези архиви самодейно ще се изтрият от хранилището ви след определения брой дни. - bootstrap_timeline_accounts: Тези акаунти ще се закачат в горния край на препоръките за следване на нови потребители. closed_registrations_message: Показва се, когато е затворено за регистрации content_cache_retention_period: Всички публикации от други сървъри, включително подсилвания и отговори, ще се изтрият след посочения брой дни, без да се взема предвид каквото и да е взаимодействие на местния потребител с тези публикации. Това включва публикации, които местния потребител е означил като отметки или любими. Личните споменавания между потребители от различни инстанции също ще се загубят и невъзможно да се възстановят. Употребата на тази настройка е предназначена за случаи със специално предназначение и разбива очакванията на много потребители, когато се изпълнява за употреба с общо предназначение. custom_css: Може да прилагате собствени стилове в уебверсията на Mastodon. @@ -103,10 +101,8 @@ bg: status_page_url: Адресът на страницата, където хората могат да видят състоянието на този сървър по време на прекъсване theme: Темата, която излизащи от системата посетители и нови потребители виждат. thumbnail: Образ в съотношение около 2:1, показвано до информацията за сървъра ви. - timeline_preview: Излизащите от системата посетители ще може да разглеждат най-новите публични публикации, налични на сървъра. trendable_by_default: Прескачане на ръчния преглед на изгряващо съдържание. Отделни елементи още могат да се премахват от изгряващи постфактум. trends: В раздел „Налагащо се“ се показват публикации, хаштагове и новини, набрали популярност на сървъра ви. - trends_as_landing_page: Показване на налагащото се съдържание за излизащите потребители и посетители вместо на описа на този сървър. Изисква налагащото се да бъде включено. form_challenge: current_password: Влизате в зона за сигурност imports: @@ -232,11 +228,10 @@ bg: setting_aggregate_reblogs: Групиране на подсилванията в часовите оси setting_always_send_emails: Все да се пращат известия по имейла setting_auto_play_gif: Самопускащи се анимирани гифчета - setting_boost_modal: Показване на прозорец за потвърждение преди подсилване setting_default_language: Език на публикуване setting_default_quote_policy: Кой може да цитира setting_default_sensitive: Все да се бележи мултимедията като деликатна - setting_delete_modal: Показване на прозорче за потвърждение преди изтриване на публикация + setting_delete_modal: Предупреждение преди изтриване на публикация setting_disable_hover_cards: Изключване на прегледа на профила, премествайки показалеца отгоре setting_disable_swiping: Деактивиране на бързо плъзгащи движения setting_display_media: Показване на мултимедия @@ -246,7 +241,8 @@ bg: setting_emoji_style: Стил на емоджито setting_expand_spoilers: Винаги разширяване на публикации, отбелязани с предупреждения за съдържание setting_hide_network: Скриване на социалния ви свързан граф - setting_missing_alt_text_modal: Показване на диалогов прозорец потвърждение преди публикуване на мултимедия без алт. текст + setting_missing_alt_text_modal: Предупреждение преди публикуване на мултимедия без алтернативен текст + setting_quick_boosting: Включване на бързо подсилване setting_reduce_motion: Обездвижване на анимациите setting_system_font_ui: Употреба на стандартния шрифт на системата setting_system_scrollbars_ui: Употреба на системната подразбираща се лента за превъртане @@ -280,6 +276,7 @@ bg: content_cache_retention_period: Период на запазване на отдалечено съдържание custom_css: Персонализиран CSS favicon: Сайтоикона + landing_page: Целева страница за нови посетители mascot: Плашило талисман по избор (остаряло) media_cache_retention_period: Период на запазване на мултимедийния кеш min_age: Минимално възрастово изискване @@ -298,10 +295,8 @@ bg: status_page_url: URL адрес на страница със състоянието theme: Стандартна тема thumbnail: Образче на сървъра - timeline_preview: Позволяване на неудостоверен достъп до публични инфопотоци trendable_by_default: Без преглед на налагащото се trends: Включване на налагащи се - trends_as_landing_page: Употреба на налагащото се като целева страница interactions: must_be_follower: Блокирай известия от не-последователи must_be_following: Блокиране на известия от неследваните @@ -362,9 +357,9 @@ bg: jurisdiction: Законова юрисдикция min_age: Минимална възраст user: - date_of_birth_1i: Ден + date_of_birth_1i: Година date_of_birth_2i: Месец - date_of_birth_3i: Година + date_of_birth_3i: Ден role: Роля time_zone: Часова зона user_role: diff --git a/config/locales/simple_form.br.yml b/config/locales/simple_form.br.yml index 87065f9a0b6e54..6be3f3d178cea4 100644 --- a/config/locales/simple_form.br.yml +++ b/config/locales/simple_form.br.yml @@ -109,9 +109,7 @@ br: domain: Domani jurisdiction: Barnadurezh user: - date_of_birth_1i: Devezh date_of_birth_2i: Mizvezh - date_of_birth_3i: Bloavezh role: Roll time_zone: Gwerzhid eur user_role: diff --git a/config/locales/simple_form.ca.yml b/config/locales/simple_form.ca.yml index 0c12fbb476dec1..d7ced9c6f58d0c 100644 --- a/config/locales/simple_form.ca.yml +++ b/config/locales/simple_form.ca.yml @@ -56,6 +56,8 @@ ca: scopes: API permeses per a accedir a l'aplicació. Si selecciones un àmbit de nivell superior, no cal que en seleccionis un d'individual. setting_aggregate_reblogs: No mostra els nous impulsos dels tuts que ja s'han impulsat recentment (només afecta als impulsos nous rebuts) setting_always_send_emails: Normalment, no s'enviarà cap notificació per correu electrònic mentre facis servir Mastodon + setting_default_quote_policy_private: Altres no poden citar publicacions fetes a Mastodon només per a seguidors. + setting_default_quote_policy_unlisted: Quan la gent et citi la seva publicació estarà amagada de les línies de temps de tendències. setting_default_sensitive: El contingut sensible està ocult per defecte i es pot mostrar fent-hi clic setting_display_media_default: Amaga el contingut gràfic marcat com a sensible setting_display_media_hide_all: Oculta sempre tot el contingut multimèdia @@ -74,7 +76,7 @@ ca: featured_tag: name: 'Aquí estan algunes de les etiquetes que més has usat recentment:' filters: - action: Tria quina acció cal executar quan un apunt coincideixi amb el filtre + action: Trieu quina acció executar quan una publicació coincideixi amb el filtre actions: blur: Amaga el contingut multimèdia rere un avís, sense amagar el text en si hide: Ocultar completament el contingut filtrat, comportant-se com si no existís @@ -83,7 +85,6 @@ ca: activity_api_enabled: Contador de tuts publicats localment, usuaris actius i registres nous en períodes setmanals app_icon: WEBP, PNG, GIF o JPG. Canvia la icona per defecte de l'app en dispositius mòbils per una de personalitzada. backups_retention_period: Els usuaris poden generar arxius de les seves publicacions per a baixar-los més endavant. Quan tingui un valor positiu, els arxius s'esborraran del vostre emmagatzematge després del nombre donat de dies. - bootstrap_timeline_accounts: Aquests comptes es fixaran en la part superior de les recomanacions de seguiment dels nous usuaris. closed_registrations_message: Es mostra quan el registres estan tancats content_cache_retention_period: S'esborraran totes les publicacions d'altres servidors (impulsos i respostes inclosos) passats els dies indicats, sense tenir en consideració les interaccions d'usuaris locals amb aquestes publicacions. Això inclou les publicacions que un usuari local hagi marcat com a favorites. També es perdran, i no es podran recuperar, les mencions privades entre usuaris d'instàncies diferents. Aquest paràmetre està pensat per a instàncies amb un propòsit especial i trencarà les expectatives dels usuaris si s'utilitza en una instància convencional. custom_css: Pots aplicar estils personalitzats en la versió web de Mastodon. @@ -103,10 +104,8 @@ ca: status_page_url: Enllaç de la pàgina on els usuaris poden veure l'estat d'aquest servidor durant una interrupció del servei theme: El tema que els visitants i els nous usuaris veuen. thumbnail: Una imatge d'aproximadament 2:1 que es mostra al costat la informació del teu servidor. - timeline_preview: Els visitants amb sessió no iniciada seran capaços de navegar per els tuts més recents en el teu servidor. trendable_by_default: Omet la revisió manual del contingut en tendència. Els articles individuals poden encara ser eliminats després del fet. trends: Les tendències mostren quins tuts, etiquetes i notícies estan guanyant força en el teu servidor. - trends_as_landing_page: Mostra el contingut en tendència als usuaris i visitants no autenticats enlloc de la descripció d'aquest servidor. Requereix que les tendències estiguin activades. form_challenge: current_password: Estàs entrant en una àrea segura imports: @@ -228,12 +227,10 @@ ca: setting_aggregate_reblogs: Agrupar impulsos en les línies de temps setting_always_send_emails: Envia'm sempre notificacions per correu electrònic setting_auto_play_gif: Reprodueix automàticament els GIF animats - setting_boost_modal: Mostra la finestra de confirmació abans d'impulsar setting_default_language: Llengua dels tuts setting_default_privacy: Visibilitat de la publicació setting_default_quote_policy: Qui pot citar setting_default_sensitive: Marcar sempre el contingut gràfic com a sensible - setting_delete_modal: Mostra la finestra de confirmació abans d'esborrar un tut setting_disable_hover_cards: Deshabilita la vista prèvia del perfil en passar-hi per sobre setting_disable_swiping: Desactiva les animacions setting_display_media: Visualització multimèdia @@ -243,7 +240,6 @@ ca: setting_emoji_style: Estil d'emojis setting_expand_spoilers: Desplega sempre els tuts marcats amb advertències de contingut setting_hide_network: Amaga la teva xarxa - setting_missing_alt_text_modal: Mostra un diàleg de confirmació abans de publicar contingut sense text alternatiu setting_reduce_motion: Redueix el moviment de les animacions setting_system_font_ui: Usa la lletra predeterminada del sistema setting_system_scrollbars_ui: Usa la barra de desplaçament predeterminada del sistema @@ -295,10 +291,8 @@ ca: status_page_url: Enllaç de la pàgina d'estat theme: Tema per defecte thumbnail: Miniatura del servidor - timeline_preview: Permet l'accés no autenticat a les línies de temps públiques trendable_by_default: Permet tendències sense revisió prèvia trends: Activa les tendències - trends_as_landing_page: Fer servir les tendències com a pàgina inicial interactions: must_be_follower: Bloqueja les notificacions de persones que no em segueixen must_be_following: Bloqueja les notificacions de persones no seguides @@ -359,9 +353,9 @@ ca: jurisdiction: Jurisdicció min_age: Edat mínima user: - date_of_birth_1i: Dia + date_of_birth_1i: Any date_of_birth_2i: Mes - date_of_birth_3i: Any + date_of_birth_3i: Dia role: Rol time_zone: Zona horària user_role: @@ -370,6 +364,8 @@ ca: name: Nom permissions_as_keys: Permisos position: Prioritat + username_block: + comparison: Mètode de comparació webhook: events: Esdeveniments activats template: Plantilla de càrrega diff --git a/config/locales/simple_form.ckb.yml b/config/locales/simple_form.ckb.yml index 0956460a6b199a..bf503df1390079 100644 --- a/config/locales/simple_form.ckb.yml +++ b/config/locales/simple_form.ckb.yml @@ -127,10 +127,8 @@ ckb: setting_advanced_layout: چالاککردنی ڕووکاری وێبی پێشکەوتوو setting_aggregate_reblogs: گرووپی توتەکان یەکبخە setting_auto_play_gif: خۆکاربەخشکردنی GIFــەکان - setting_boost_modal: پیشاندانی دیالۆگی دووپاتکردنەوە پێش دوبارە توتاندن setting_default_language: زمانی نووسراوەکانتان setting_default_sensitive: هەمیشە نیشانکردنی میدیا وەک هەستیار - setting_delete_modal: نیساندانی پەیامی پەسەند کردن پاش سڕینەوە setting_disable_swiping: جوڵەی سڕینەوە لە کاربخە setting_display_media: پیشاندانی میدیا setting_display_media_default: بنەڕەت diff --git a/config/locales/simple_form.co.yml b/config/locales/simple_form.co.yml index b4d15c8a1abf0b..80a22c02b1f935 100644 --- a/config/locales/simple_form.co.yml +++ b/config/locales/simple_form.co.yml @@ -128,10 +128,8 @@ co: setting_advanced_layout: Attivà l'interfaccia web avanzata setting_aggregate_reblogs: Gruppà e spartere indè e linee setting_auto_play_gif: Lettura autumatica di i GIF animati - setting_boost_modal: Mustrà una cunfirmazione per sparte un statutu setting_default_language: Lingua di pubblicazione setting_default_sensitive: Sempre cunsiderà media cum’è sensibili - setting_delete_modal: Mustrà une cunfirmazione per toglie un statutu setting_disable_swiping: Disattivà e sculiscere setting_display_media: Affissera di i media setting_display_media_default: Predefinitu diff --git a/config/locales/simple_form.cs.yml b/config/locales/simple_form.cs.yml index f2ef4f672cc7e7..ff3f56a0d463f4 100644 --- a/config/locales/simple_form.cs.yml +++ b/config/locales/simple_form.cs.yml @@ -54,8 +54,10 @@ cs: password: Použijte alespoň 8 znaků phrase: Shoda bude nalezena bez ohledu na velikost písmen v textu příspěvku či varování o obsahu scopes: Která API bude aplikace moct používat. Pokud vyberete rozsah nejvyššího stupně, nebudete je muset vybírat jednotlivě. + setting_advanced_layout: Zobrazit Mastodon ve vícesloupovém rozvržení, umožňující zobrazení časoé osy, oznámení a třetího sloupce vašeho výběru. Není doporučeno pro menší obrazovky. setting_aggregate_reblogs: Nezobrazovat nové boosty pro příspěvky, které byly nedávno boostnuty (ovlivňuje pouze nově přijaté boosty) setting_always_send_emails: Jinak nebudou e-mailové notifikace posílány, když Mastodon aktivně používáte + setting_boost_modal: Pokud je povoleno, boostnutí nejprve otevře dialogové okno pro potvrzení, ve kterém můžete změnit viditelnost svého boostu. setting_default_quote_policy_private: Příspěvky pouze pro sledující, které jsou vytvořeny na Mastodonu, nemohou být citovány ostatními. setting_default_quote_policy_unlisted: Když vás lidé citují, jejich příspěvek bude v časové ose populárních příspěvků také skryt. setting_default_sensitive: Citlivá média jsou ve výchozím stavu skryta a mohou být zobrazena kliknutím @@ -63,6 +65,7 @@ cs: setting_display_media_hide_all: Vždy skrývat média setting_display_media_show_all: Vždy zobrazovat média setting_emoji_style: Jak se budou zobrazovat emoji. "Auto" zkusí použít výchozí emoji, ale pro starší prohlížeče použije Twemoji. + setting_quick_boosting_html: Pokud je povoleno, kliknutím na %{boost_icon} Boost ikonu okamžitě boostnete místo otevření rozbalovací nabídky boost/citace. Přemístí citaci do nabídky %{options_icon} (Možnosti). setting_system_scrollbars_ui: Platí pouze pro desktopové prohlížeče založené na Safari nebo Chrome setting_use_blurhash: Gradienty jsou vytvořeny na základě barvev skrytých médií, ale zakrývají veškeré detaily setting_use_pending_items: Aktualizovat časovou osu až po kliknutí namísto automatického rolování kanálu @@ -76,7 +79,7 @@ cs: featured_tag: name: 'Zde jsou některé z hashtagů, které jste nedávno použili:' filters: - action: Vyberte, jakou akci provést, když příspěvek odpovídá filtru + action: Vyberte, která akce má být provedena, když příspěvek odpovídá filtru actions: blur: Skrýt média za varováním, bez skrytí samotného textu hide: Úplně schovat filtrovaný obsah tak, jako by neexistoval @@ -85,11 +88,12 @@ cs: activity_api_enabled: Počty lokálně zveřejnělých příspěvků, aktivních uživatelů a nových registrací v týdenních intervalech app_icon: WEBP, PNG, GIF nebo JPG. Nahradí výchozí ikonu aplikace v mobilních zařízeních vlastní ikonou. backups_retention_period: Uživatelé mají možnost vytvářet archivy svých příspěvků, které si mohou stáhnout později. Pokud je nastaveno na kladnou hodnotu, budou tyto archivy po zadaném počtu dní automaticky odstraněny z úložiště. - bootstrap_timeline_accounts: Tyto účty budou připnuty na vrchol nových uživatelů podle doporučení. + bootstrap_timeline_accounts: Tyto účty budou připnuty na vrcholu doporučení pro nové uživatele. Napište čárkou oddělený seznam účtů. closed_registrations_message: Zobrazeno při zavření registrace content_cache_retention_period: Všechny příspěvky z jiných serverů (včetně boostů a odpovědí) budou po uplynutí stanoveného počtu dní smazány bez ohledu na interakci místního uživatele s těmito příspěvky. To se týká i příspěvků, které místní uživatel přidal do záložek nebo oblíbených. Soukromé zmínky mezi uživateli z různých instancí budou rovněž ztraceny a nebude možné je obnovit. Použití tohoto nastavení je určeno pro instance pro speciální účely a při implementaci pro obecné použití porušuje mnohá očekávání uživatelů. custom_css: Můžete použít vlastní styly ve verzi Mastodonu. favicon: WEBP, PNG, GIF nebo JPG. Nahradí výchozí favicon Mastodonu vlastní ikonou. + landing_page: Vybere stránku, kterou návštěvníci uvidí, když prvně přijdou na tvůj server. Pokud zvolíte "Trendy", je třeba povolit trendy v nastavení objevování. Pokud zvolíte "Místní kanál", je třeba v nastavení Objevování nastavit "Přístup k živým kanálům s lokálními příspěvky" na "Všichni". mascot: Přepíše ilustraci v pokročilém webovém rozhraní. media_cache_retention_period: Mediální soubory z příspěvků vzdálených uživatelů se ukládají do mezipaměti na vašem serveru. Pokud je nastaveno na kladnou hodnotu, budou média po zadaném počtu dní odstraněna. Pokud jsou mediální data vyžádána po jejich odstranění, budou znovu stažena, pokud je zdrojový obsah stále k dispozici. Vzhledem k omezením týkajícím se četnosti dotazů karet náhledů odkazů na weby třetích stran se doporučuje nastavit tuto hodnotu alespoň na 14 dní, jinak nebudou karty náhledů odkazů na vyžádání aktualizovány dříve. min_age: Uživatelé budou požádáni, aby při registraci potvrdili datum svého narození @@ -105,10 +109,8 @@ cs: status_page_url: URL stránky, kde mohou lidé vidět stav tohoto serveru během výpadku theme: Vzhled stránky, který vidí noví a odhlášení uživatelé. thumbnail: Přibližně 2:1 obrázek zobrazený vedle informací o vašem serveru. - timeline_preview: Odhlášení uživatelé budou moci procházet nejnovější veřejné příspěvky na serveru. trendable_by_default: Přeskočit manuální kontrolu populárního obsahu. Jednotlivé položky mohou být odstraněny z trendů později. trends: Trendy zobrazují, které příspěvky, hashtagy a zprávy získávají na serveru pozornost. - trends_as_landing_page: Zobrazit populární obsah odhlášeným uživatelům a návštěvníkům místo popisu tohoto serveru. Vyžaduje povolení trendů. form_challenge: current_password: Vstupujete do zabezpečeného prostoru imports: @@ -237,12 +239,12 @@ cs: setting_aggregate_reblogs: Seskupovat boosty v časových osách setting_always_send_emails: Vždy posílat e-mailová oznámení setting_auto_play_gif: Automaticky přehrávat animace GIF - setting_boost_modal: Před boostnutím zobrazovat potvrzovací okno + setting_boost_modal: Ovládání viditelnosti boostování setting_default_language: Jazyk příspěvků setting_default_privacy: Viditelnost příspěvků setting_default_quote_policy: Kdo může citovat setting_default_sensitive: Vždy označovat média jako citlivá - setting_delete_modal: Před smazáním příspěvku zobrazovat potvrzovací dialog + setting_delete_modal: Upozornit před odstraněním příspěvku setting_disable_hover_cards: Zakázat náhled profilu při přejetí myší setting_disable_swiping: Vypnout gesta přejetí prsty setting_display_media: Zobrazování médií @@ -252,7 +254,8 @@ cs: setting_emoji_style: Styl emoji setting_expand_spoilers: Vždy rozbalit příspěvky označené varováními o obsahu setting_hide_network: Skrýt mou síť - setting_missing_alt_text_modal: Zobrazit potvrzovací dialog před odesláním médií bez alt textu + setting_missing_alt_text_modal: Upozornit před odesláním médií bez popisného textu + setting_quick_boosting: Povolit rychlé boostnutí setting_reduce_motion: Omezit pohyb v animacích setting_system_font_ui: Použít výchozí písmo systému setting_system_scrollbars_ui: Použít výchozí posuvník systému @@ -286,12 +289,17 @@ cs: content_cache_retention_period: Doba uchovávání vzdáleného obsahu custom_css: Vlastní CSS favicon: Favicon + landing_page: Úvodní stránka pro nové návštěvníky + local_live_feed_access: Přístup k živým kanálům s lokálními příspěvky + local_topic_feed_access: Přístup ke kanálům s hashtagy a odkazy s lokálními příspěvky mascot: Vlastní maskot (zastaralé) media_cache_retention_period: Doba uchovávání mezipaměti médií min_age: Minimální věková hranice peers_api_enabled: Zveřejnit seznam nalezených serverů v API profile_directory: Povolit adresář profilů registrations_mode: Kdo se může přihlásit + remote_live_feed_access: Přístup k live kanálům s vzdálenými příspěvky + remote_topic_feed_access: Přístup ke kanálům s hashtagy a odkazy se vzdálenými příspěvky require_invite_text: Požadovat důvod pro připojení show_domain_blocks: Zobrazit blokace domén show_domain_blocks_rationale: Zobrazit proč byly blokovány domény @@ -304,10 +312,8 @@ cs: status_page_url: URL stránky se stavem theme: Výchozí motiv thumbnail: Miniatura serveru - timeline_preview: Povolit neověřený přístup k veřejným časovým osám trendable_by_default: Povolit trendy bez předchozí revize trends: Povolit trendy - trends_as_landing_page: Použít trendy jako vstupní stránku interactions: must_be_follower: Blokovat oznámení od lidí, kteří vás nesledují must_be_following: Blokovat oznámení od lidí, které nesledujete @@ -368,9 +374,9 @@ cs: jurisdiction: Právní příslušnost min_age: Věková hranice user: - date_of_birth_1i: Den + date_of_birth_1i: Rok date_of_birth_2i: Měsíc - date_of_birth_3i: Rok + date_of_birth_3i: Den role: Role time_zone: Časové pásmo user_role: diff --git a/config/locales/simple_form.cy.yml b/config/locales/simple_form.cy.yml index 0faa978c4d04c0..5a723d20f7e8e9 100644 --- a/config/locales/simple_form.cy.yml +++ b/config/locales/simple_form.cy.yml @@ -54,8 +54,10 @@ cy: password: Defnyddiwch o leiaf 8 nod phrase: Caiff ei gyfateb heb ystyriaeth o briflythrennu mewn testun neu rhybudd ynghylch cynnwys postiad scopes: Pa APIs y bydd y rhaglen yn cael mynediad iddynt. Os dewiswch gwmpas lefel uchaf, nid oes angen i chi ddewis rhai unigol. + setting_advanced_layout: Dangos Mastodon fel cynllun aml-golofn, sy'n eich galluogi i weld y llinell amser, hysbysiadau, a thrydedd golofn o'ch dewis chi. Nid gyfer sgriniau llai. setting_aggregate_reblogs: Peidiwch â dangos hybiau newydd ar bostiadau sydd wedi cael eu hybu'n ddiweddar (dim ond yn effeithio ar hybiau newydd ei dderbyn) setting_always_send_emails: Fel arfer ni fydd hysbysiadau e-bost yn cael eu hanfon pan fyddwch chi wrthi'n defnyddio Mastodon + setting_boost_modal: Pan fydd wedi'i alluogi, bydd hybu'n agor deialog cadarnhau yn gyntaf lle gallwch newid gwelededd eich hwb. setting_default_quote_policy_private: Does dim modd dyfynnu postiadau sydd wedi'u hysgrifennu ar Mastodon ar gyfer dim ond dilynwyr. setting_default_quote_policy_unlisted: Pan fydd pobl yn eich dyfynnu, bydd eu postiad hefyd yn cael ei guddio rhag llinellau amser sy'n trendio. setting_default_sensitive: Mae cyfryngau sensitif wedi'u cuddio yn rhagosodedig a gellir eu datgelu trwy glicio @@ -63,6 +65,7 @@ cy: setting_display_media_hide_all: Cuddio cyfryngau bob tro setting_display_media_show_all: Dangos cyfryngau bob tro setting_emoji_style: Sut i arddangos emojis. Bydd "Awto" yn ceisio defnyddio emoji cynhenid, ond mae'n disgyn yn ôl i Twemoji ar gyfer porwyr traddodiadol. + setting_quick_boosting_html: Pan fydd wedi'i alluogi, bydd clicio ar yr eicon Hwb %{boost_icon} yn rhoi hwb ar unwaith yn lle agor y gwymplen hwb/dyfynnu. Mae'n symud y weithred dyfynnu i'r ddewislen %{options_icon} (Dewisiadau). setting_system_scrollbars_ui: Yn berthnasol i borwyr bwrdd gwaith yn seiliedig ar Safari a Chrome yn unig setting_use_blurhash: Mae graddiannau wedi'u seilio ar liwiau'r delweddau cudd ond maen nhw'n cuddio unrhyw fanylion setting_use_pending_items: Cuddio diweddariadau llinell amser y tu ôl i glic yn lle sgrolio'n awtomatig @@ -76,7 +79,7 @@ cy: featured_tag: name: 'Dyma rai o’r hashnodau a ddefnyddioch chi''n ddiweddar:' filters: - action: Dewiswch pa weithred i'w chyflawni pan fydd postiad yn cyfateb i'r hidlydd + action: Dewiswch pa weithred i'w gyflawni pan fydd postiad yn cyd-fynd â'r hidlydd actions: blur: Cuddio cyfryngau tu ôl i rybudd, heb guddio'r testun ei hun hide: Cuddiwch y cynnwys wedi'i hidlo'n llwyr, gan ymddwyn fel pe na bai'n bodoli @@ -85,11 +88,12 @@ cy: activity_api_enabled: Cyfrif o bostiadau a gyhoeddir yn lleol, defnyddwyr gweithredol, a chofrestriadau newydd mewn bwcedi wythnosol app_icon: WEBP, PNG, GIF neu JPG. Yn diystyru'r eicon ap rhagosodedig ar ddyfeisiau symudol gydag eicon cyfaddas. backups_retention_period: Mae gan ddefnyddwyr y gallu i gynhyrchu archifau o'u postiadau i'w llwytho i lawr yn ddiweddarach. Pan gânt eu gosod i werth positif, bydd yr archifau hyn yn cael eu dileu'n awtomatig o'ch storfa ar ôl y nifer penodedig o ddyddiau. - bootstrap_timeline_accounts: Bydd y cyfrifon hyn yn cael eu pinio i frig argymhellion dilynol defnyddwyr newydd. + bootstrap_timeline_accounts: Bydd y cyfrifon yma'n cael eu pinio i frig argymhellion dilyn defnyddwyr newydd. Darparwch restr cyfrifon wedi'u gwahanu gan gollnod. closed_registrations_message: Yn cael eu dangos pan fydd cofrestriadau wedi cau content_cache_retention_period: Bydd yr holl bostiadau gan weinyddion eraill (gan gynnwys hwb ac atebion) yn cael eu dileu ar ôl y nifer penodedig o ddyddiau, heb ystyried unrhyw ryngweithio defnyddiwr lleol â'r postiadau hynny. Mae hyn yn cynnwys postiadau lle mae defnyddiwr lleol wedi ei farcio fel nodau tudalen neu ffefrynnau. Bydd cyfeiriadau preifat rhwng defnyddwyr o wahanol achosion hefyd yn cael eu colli ac yn amhosibl eu hadfer. Mae'r defnydd o'r gosodiad hwn wedi'i fwriadu ar gyfer achosion pwrpas arbennig ac mae'n torri llawer o ddisgwyliadau defnyddwyr pan gaiff ei weithredu at ddibenion cyffredinol. custom_css: Gallwch gymhwyso arddulliau cyfaddas ar fersiwn gwe Mastodon. favicon: WEBP, PNG, GIF neu JPG. Yn diystyru'r favicon Mastodon rhagosodedig gydag eicon cyfaddas. + landing_page: Yn dewis pa dudalen y mae ymwelwyr newydd yn ei gweld pan fyddan nhw'n cyrraedd eich gweinydd am y tro cyntaf. Os dewiswch "Trendio", yna mae angen galluogi tueddiadau yn y Gosodiadau Darganfod. Os dewiswch "Ffrydiau lleol", yna mae angen gosod "Mynediad i ffrydiau byw sy'n cynnwys postiadau lleol" i "Pawb" yn y Gosodiadau Darganfod. mascot: Yn diystyru'r darlun yn y rhyngwyneb gwe uwch. media_cache_retention_period: Mae ffeiliau cyfryngau o bostiadau a wneir gan ddefnyddwyr o bell yn cael eu storio ar eich gweinydd. Pan gaiff ei osod i werth positif, bydd y cyfryngau yn cael eu dileu ar ôl y nifer penodedig o ddyddiau. Os gofynnir am y data cyfryngau ar ôl iddo gael ei ddileu, caiff ei ail-lwytho i lawr, os yw'r cynnwys ffynhonnell yn dal i fod ar gael. Oherwydd cyfyngiadau ar ba mor aml y mae cardiau rhagolwg cyswllt yn pleidleisio i wefannau trydydd parti, argymhellir gosod y gwerth hwn i o leiaf 14 diwrnod, neu ni fydd cardiau rhagolwg cyswllt yn cael eu diweddaru ar alw cyn yr amser hwnnw. min_age: Mae gofyn i ddefnyddwyr gadarnhau eu dyddiad geni wrth gofrestru @@ -105,10 +109,8 @@ cy: status_page_url: URL tudalen lle gall pobl weld statws y gweinydd hwn yn ystod cyfnod o doriad gwasanaeth theme: Thema sy'n allgofnodi ymwelwyr a defnyddwyr newydd yn gweld. thumbnail: Delwedd tua 2:1 yn cael ei dangos ochr yn ochr â manylion eich gweinydd. - timeline_preview: Bydd ymwelwyr sydd wedi allgofnodi yn gallu pori drwy'r postiadau cyhoeddus diweddaraf sydd ar gael ar y gweinydd. trendable_by_default: Hepgor adolygiad llaw o gynnwys sy'n tueddu. Gall eitemau unigol gael eu tynnu o dueddiadau o hyd ar ôl y ffaith. trends: Mae pynciau llosg yn dangos y postiadau, hashnodau, a newyddion sy'n denu sylw ar eich gweinydd. - trends_as_landing_page: Dangos cynnwys tueddiadol i ddefnyddwyr ac ymwelwyr sydd wedi allgofnodi yn lle disgrifiad o'r gweinydd hwn. Mae angen galluogi tueddiadau. form_challenge: current_password: Rydych chi'n mynd i mewn i ardal ddiogel imports: @@ -239,12 +241,12 @@ cy: setting_aggregate_reblogs: Grwpio hybiau mewn ffrydiau setting_always_send_emails: Anfonwch hysbysiadau e-bost bob amser setting_auto_play_gif: Chwarae GIFs wedi'u hanimeiddio yn awtomatig - setting_boost_modal: Dangos deialog cadarnhau cyn rhoi hwb + setting_boost_modal: Rheoli hybu gwelededd setting_default_language: Iaith postio setting_default_privacy: Gwelededd postio setting_default_quote_policy: Pwy sy'n gallu dyfynnu setting_default_sensitive: Marcio cyfryngau fel eu bod yn sensitif bob tro - setting_delete_modal: Dangos deialog cadarnhau cyn dileu postiad + setting_delete_modal: Rhybuddio fi cyn dileu postiad setting_disable_hover_cards: Analluogi rhagolwg proffil ar lusgo setting_disable_swiping: Analluogi cynigion llusgo setting_display_media: Dangos cyfryngau @@ -254,7 +256,8 @@ cy: setting_emoji_style: Arddull Emojis setting_expand_spoilers: Dangos postiadau wedi'u marcio â rhybudd cynnwys bob tro setting_hide_network: Cuddio eich graff cymdeithasol - setting_missing_alt_text_modal: Dangos deialog cadarnhau cyn postio cyfrwng heb destun amgen + setting_missing_alt_text_modal: Rhybuddio fi cyn postio cyfryngau heb destun amgen + setting_quick_boosting: Galluogi hybu cyflym setting_reduce_motion: Lleihau mudiant mewn animeiddiadau setting_system_font_ui: Defnyddio ffont rhagosodedig y system setting_system_scrollbars_ui: Defnyddiwch far sgrolio rhagosodedig y system @@ -283,17 +286,22 @@ cy: activity_api_enabled: Cyhoeddi ystadegau cyfanredol am weithgarwch defnyddwyr yn yr API app_icon: Eicon ap backups_retention_period: Cyfnod cadw archif defnyddwyr - bootstrap_timeline_accounts: Argymhellwch y cyfrifon hyn i ddefnyddwyr newydd bob amser + bootstrap_timeline_accounts: Argymhellwch y cyfrifon hyn i ddefnyddwyr newydd bob tro closed_registrations_message: Neges bersonol pan nad yw cofrestriadau ar gael content_cache_retention_period: Cyfnod cadw cynnwys o bell custom_css: CSS cyfaddas favicon: Favicon + landing_page: Tudalen cychwyn ar gyfer ymwelwyr newydd + local_live_feed_access: Mynediad i ffrydiau byw sy'n cynnwys postiadau lleol + local_topic_feed_access: Mynediad i ffrydiau hashnod a dolenni sy'n cynnwys postiadau lleol mascot: Mascot cyfaddas (hen) media_cache_retention_period: Cyfnod cadw storfa cyfryngau min_age: Gofyniad oed ieuengaf peers_api_enabled: Cyhoeddi rhestr o weinyddion a ddarganfuwyd yn yr API profile_directory: Galluogi cyfeiriadur proffil registrations_mode: Pwy all gofrestru + remote_live_feed_access: Mynediad i ffrydiau byw sy'n cynnwys postiadau pell + remote_topic_feed_access: Mynediad i ffrydiau hashnod a dolenni sy'n cynnwys postiadau o bell require_invite_text: Gofyn am reswm i ymuno show_domain_blocks: Dangos blociau parth show_domain_blocks_rationale: Dangos pam y cafodd parthau eu rhwystro @@ -306,10 +314,8 @@ cy: status_page_url: URL tudalen statws theme: Thema ragosodedig thumbnail: Bawdlun y gweinydd - timeline_preview: Caniatáu mynediad heb ei ddilysu i linellau amser cyhoeddus trendable_by_default: Caniatáu pynciau llosg heb adolygiad trends: Galluogi pynciau llosg - trends_as_landing_page: Defnyddio tueddiadau fel y dudalen gartref interactions: must_be_follower: Blocio hysbysiadau o bobl nad ydynt yn eich dilyn must_be_following: Blocio hysbysiadau o bobl nad ydych yn eu dilyn @@ -370,9 +376,9 @@ cy: jurisdiction: Awdurdodaeth gyfreithiol min_age: Isafswm oedran user: - date_of_birth_1i: Dydd + date_of_birth_1i: Blwyddyn date_of_birth_2i: Mis - date_of_birth_3i: Blwyddyn + date_of_birth_3i: Diwrnod role: Rôl time_zone: Cylchfa amser user_role: diff --git a/config/locales/simple_form.da.yml b/config/locales/simple_form.da.yml index 05fa2acc9c0aa6..4df4e34eedee1f 100644 --- a/config/locales/simple_form.da.yml +++ b/config/locales/simple_form.da.yml @@ -5,7 +5,7 @@ da: account: attribution_domains: Ét pr. linje. Beskytter mod falske tilskrivninger. discoverable: Dine offentlige indlæg og profil kan blive fremhævet eller anbefalet i forskellige områder af Mastodon, og profilen kan blive foreslået til andre brugere. - display_name: Dit fulde navn eller dit sjove navn. + display_name: Dit fulde navn eller et kaldenavn. fields: Din hjemmeside, dine pronominer, din alder, eller hvad du har lyst til. indexable: Dine offentlige indlæg vil kunne vises i Mastodon-søgeresultater. Folk, som har interageret med dem, vil kunne finde dem uanset. note: 'Du kan @omtale andre personer eller #hashtags.' @@ -54,8 +54,10 @@ da: password: Brug mindst 8 tegn phrase: Matches uanset uanset brug af store/små bogstaver i teksten eller indholdsadvarsel for et indlæg scopes: De API'er, som applikationen vil kunne tilgå. Vælges en topniveaudstrækning, vil detailvalg være unødvendige. + setting_advanced_layout: Vis Mastodon som et layout med flere kolonner, så du kan se tidslinjen, notifikationer og en tredje kolonne efter eget valg. Anbefales ikke til mindre skærme. setting_aggregate_reblogs: Vis ikke nye fremhævelser for nyligt fremhævede indlæg (påvirker kun nyligt modtagne fremhævelser) setting_always_send_emails: Normalt sendes ingen e-mailnotifikationer under aktivt brug af Mastodon + setting_boost_modal: Når aktiveret, åbnes der ved fremhævelse først en bekræftelsesdialog, hvor du kan ændre synligheden af din fremhævelse. setting_default_quote_policy_private: Kun-følgere indlæg forfattet på Mastodon kan ikke citeres af andre. setting_default_quote_policy_unlisted: Når folk citerer dig, vil deres indlæg også blive skjult fra trendtidslinjer. setting_default_sensitive: Sensitive medier er som standard skjult og kan vises med et klik @@ -63,6 +65,7 @@ da: setting_display_media_hide_all: Skjul altid medier setting_display_media_show_all: Vis altid medier setting_emoji_style: Hvordan emojis skal vises. "Auto" vil forsøge at bruge indbyggede emojis, men skifter tilbage til Twemoji i ældre webbrowsere. + setting_quick_boosting_html: Når aktiveret, vil klik på %{boost_icon} fremhæv-ikonet straks fremhæve i stedet for at åbne fremhæv/citér-foldudmenuen. Flytter citeringshandlingen til %{options_icon} menuen (Indstillinger). setting_system_scrollbars_ui: Gælder kun for desktop-browsere baseret på Safari og Chrome setting_use_blurhash: Gradienter er baseret på de skjulte grafikelementers farver, men slører alle detaljer setting_use_pending_items: Skjul tidslinjeopdateringer bag et klik i stedet for brug af auto-feedrulning @@ -85,11 +88,12 @@ da: activity_api_enabled: Antal lokalt opslåede indlæg, aktive brugere samt nye tilmeldinger i ugentlige opdelinger app_icon: WEBP, PNG, GIF eller JPG. Tilsidesætter standard app-ikonet på mobilenheder med et tilpasset ikon. backups_retention_period: Brugere har mulighed for at generere arkiver af deres indlæg til senere downloade. Når sat til positiv værdi, vil disse arkiver automatisk blive slettet fra lagerpladsen efter det angivne antal dage. - bootstrap_timeline_accounts: Disse konti fastgøres øverst på nye brugeres følg-anbefalinger. + bootstrap_timeline_accounts: Disse konti vil blive fastgjort til toppen af nye brugeres følg-anbefalinger. Angiv en kommasepareret liste over konti. closed_registrations_message: Vises, når tilmeldinger er lukket content_cache_retention_period: Alle indlæg fra andre servere (herunder fremhævelser og besvarelser) slettes efter det angivne antal dage uden hensyn til lokal brugerinteraktion med disse indlæg. Dette omfatter indlæg, hvor en lokal bruger har markeret dem som bogmærker eller favoritter. Private omtaler mellem brugere fra forskellige instanser vil også være tabt og umulige at gendanne. Brugen af denne indstilling er beregnet til særlige formål instanser og bryder mange brugerforventninger ved implementering til almindelig brug. custom_css: Man kan anvende tilpassede stilarter på Mastodon-webversionen. favicon: WEBP, PNG, GIF eller JPG. Tilsidesætter standard Mastodon favikonet på mobilenheder med et tilpasset ikon. + landing_page: Vælger, hvilken side nye besøgende ser, når de først ankommer til din server. Hvis du vælger "Trender", skal trends være aktiveret i Opdagelse-indstillingerne. Hvis du vælger "Lokalt feed", skal "Adgang til live feeds med lokale indlæg" være indstillet til "Alle" i Opdagelse-indstillingerne. mascot: Tilsidesætter illustrationen i den avancerede webgrænseflade. media_cache_retention_period: Mediefiler fra indlæg oprettet af eksterne brugere er cachet på din server. Når sat til positiv værdi, slettes medier efter det angivne antal dage. Anmodes om mediedata efter de er slettet, gendownloades de, hvis kildeindholdet stadig er tilgængeligt. Grundet begrænsninger på, hvor ofte linkforhåndsvisningskort forespørger tredjeparts websteder, anbefales det at sætte denne værdi til mindst 14 dage, ellers opdateres linkforhåndsvisningskort ikke efter behov før det tidspunkt. min_age: Brugere anmodes om at bekræfte deres fødselsdato under tilmelding @@ -105,10 +109,9 @@ da: status_page_url: URL'en til en side, hvor status for denne server kan ses under en afbrydelse theme: Tema, som udloggede besøgende og nye brugere ser. thumbnail: Et ca. 2:1 billede vist sammen med serveroplysningerne. - timeline_preview: Udloggede besøgende kan gennemse serverens seneste offentlige indlæg. trendable_by_default: Spring manuel gennemgang af trendindhold over. Individuelle elementer kan stadig fjernes fra trends efter kendsgerningen. trends: Tendenser viser, hvilke indlæg, hashtags og nyheder opnår momentum på serveren. - trends_as_landing_page: Vis tendensindhold til udloggede brugere og besøgende i stedet for en beskrivelse af denne server. Kræver, at tendenser er aktiveret. + wrapstodon: Tilbyd lokale brugere at generere en sjov oversigt over deres brug af Mastodon i løbet af året. Denne funktion er tilgængelig mellem den 10. og 31. december hvert år og tilbydes til brugere, der har lavet mindst ét offentligt eller stille offentligt indlæg og brugt mindst ét hashtag i løbet af året. form_challenge: current_password: Du bevæger dig ind på et sikkert område imports: @@ -177,7 +180,7 @@ da: name: Etiket value: Indhold indexable: Medtag offentlige indlæg i søgeresultater - show_collections: Vis følger og følgere på profil + show_collections: Vis fulgte og følgere på profil unlocked: Acceptér automatisk nye følgere account_alias: acct: Brugernavn på den gamle konto @@ -225,7 +228,7 @@ da: inbox_url: URL til videreformidlingsindbakken irreversible: Fjern istedet for skjul locale: Grænsefladesprog - max_uses: Maks. antal afbenyttelser + max_uses: Maks. antal anvendelser new_password: Ny adgangskode note: Biografi otp_attempt: Tofaktorkode @@ -235,12 +238,12 @@ da: setting_aggregate_reblogs: Gruppér fremhævelser på tidslinjer setting_always_send_emails: Send altid e-mailnotifikationer setting_auto_play_gif: Autoafspil animerede GIF'er - setting_boost_modal: Vis bekræftelsesdialog inden fremhævelse + setting_boost_modal: Kontrollér synlighed af fremhævelse setting_default_language: Sprog for indlæg setting_default_privacy: Indlægssynlighed setting_default_quote_policy: Hvem kan citere setting_default_sensitive: Markér altid medier som sensitive - setting_delete_modal: Vis bekræftelsesdialog før et indlæg slettes + setting_delete_modal: Advar mig før sletning af et indlæg setting_disable_hover_cards: Deaktivér profilforhåndsvisning ved svæv (hover) setting_disable_swiping: Deaktivér strygebevægelser setting_display_media: Medievisning @@ -250,7 +253,8 @@ da: setting_emoji_style: Emoji-stil setting_expand_spoilers: Udvid altid indlæg markeret med indholdsadvarsler setting_hide_network: Skjul din sociale graf - setting_missing_alt_text_modal: Vis bekræftelsesdialog inden medier uden alt-tekst lægges op + setting_missing_alt_text_modal: Advar mig, før medier uden alternativ tekst lægges op + setting_quick_boosting: Aktivér hurtig fremhævelse setting_reduce_motion: Reducér animationsbevægelse setting_system_font_ui: Brug systemets standardskrifttype setting_system_scrollbars_ui: Brug standard systemrullebjælke @@ -284,12 +288,17 @@ da: content_cache_retention_period: Opbevaringsperiode for eksternt indhold custom_css: Tilpasset CSS favicon: Favikon + landing_page: Landingside for nye besøgende + local_live_feed_access: Adgang til live feeds med lokale indlæg + local_topic_feed_access: Adgang til hashtag- og link-feeds med lokale indlæg mascot: Tilpasset maskot (ældre funktion) media_cache_retention_period: Media-cache opbevaringsperiode min_age: Minimums alderskrav peers_api_enabled: Udgiv liste over fundne server i API'en profile_directory: Aktivér profiloversigt registrations_mode: Hvem, der kan tilmelde sig + remote_live_feed_access: Adgang til live feeds med eksterne indlæg + remote_topic_feed_access: Adgang til hashtag- og link-feeds med eksterne indlæg require_invite_text: Kræv tilmeldingsbegrundelse show_domain_blocks: Vis domæneblokeringer show_domain_blocks_rationale: Vis, hvorfor domæner blev blokeret @@ -302,10 +311,9 @@ da: status_page_url: Statusside-URL theme: Standardtema thumbnail: Serverminiaturebillede - timeline_preview: Tillad ikke-godkendt adgang til offentlige tidslinjer trendable_by_default: Tillad ikke-reviderede trends trends: Aktivér trends - trends_as_landing_page: Brug tendenser som destinationssiden + wrapstodon: Aktivér Wrapstodon interactions: must_be_follower: Blokér notifikationer fra bruger, der ikke følger dig must_be_following: Blokér notifikationer fra brugere, du ikke følger @@ -366,9 +374,9 @@ da: jurisdiction: Juridisk jurisdiktion min_age: Minimumsalder user: - date_of_birth_1i: Dag + date_of_birth_1i: År date_of_birth_2i: Måned - date_of_birth_3i: År + date_of_birth_3i: Dag role: Rolle time_zone: Tidszone user_role: diff --git a/config/locales/simple_form.de.yml b/config/locales/simple_form.de.yml index 0eed719cf71f9b..a8755d69010e37 100644 --- a/config/locales/simple_form.de.yml +++ b/config/locales/simple_form.de.yml @@ -40,22 +40,24 @@ de: text: Du kannst nur einmal Einspruch gegen eine Maßnahme einlegen defaults: autofollow: Personen, die sich über deine Einladung registrieren, folgen automatisch deinem Profil - avatar: WEBP, PNG, GIF oder JPG. Höchstens %{size} groß. Wird auf %{dimensions} px verkleinert + avatar: WebP, PNG, GIF oder JPG. Höchstens %{size} groß. Wird auf %{dimensions} px verkleinert bot: Signalisiert, dass dieses Konto hauptsächlich automatisierte Aktionen durchführt und möglicherweise nicht persönlich betreut wird context: Orte, an denen der Filter aktiv sein soll current_password: Gib aus Sicherheitsgründen bitte das Passwort des aktuellen Kontos ein current_username: Um das zu bestätigen, gib den Profilnamen des aktuellen Kontos ein digest: Wenn du eine längere Zeit inaktiv bist oder du während deiner Abwesenheit in einer privaten Nachricht erwähnt worden bist email: Du wirst eine E-Mail zur Verifizierung dieser E-Mail-Adresse erhalten - header: WEBP, PNG, GIF oder JPG. Höchstens %{size} groß. Wird auf %{dimensions} px verkleinert + header: WebP, PNG, GIF oder JPG. Höchstens %{size} groß. Wird auf %{dimensions} px verkleinert inbox_url: Kopiere die URL von der Startseite des gewünschten Relais irreversible: Bereinigte Beiträge verschwinden unwiderruflich für dich, auch dann, wenn dieser Filter zu einem späteren wieder entfernt wird locale: Die Sprache der Bedienoberfläche, E-Mails und Push-Benachrichtigungen password: Verwende mindestens 8 Zeichen phrase: Wird unabhängig von der Groß- und Kleinschreibung im Text oder der Inhaltswarnung eines Beitrags abgeglichen scopes: Welche Schnittstellen der Applikation erlaubt sind. Wenn du einen Top-Level-Scope auswählst, dann musst du nicht jeden einzelnen darunter auswählen. + setting_advanced_layout: Dadurch wird Mastodon in mehrere Spalten aufgeteilt, womit du deine Timeline, Benachrichtigungen und eine dritte Spalte deiner Wahl nebeneinander siehst. Nicht bei einem kleinen Bildschirm empfohlen. setting_aggregate_reblogs: Beiträge, die erst kürzlich geteilt wurden, werden nicht noch einmal angezeigt (betrifft nur zukünftig geteilte Beiträge) setting_always_send_emails: Normalerweise werden Benachrichtigungen nicht per E-Mail versendet, wenn du gerade auf Mastodon aktiv bist + setting_boost_modal: Dadurch wird beim Teilen ein Bestätigungsdialog angezeigt, um die Sichtbarkeit anzupassen. setting_default_quote_policy_private: Beiträge, die nur für deine Follower bestimmt sind und auf Mastodon verfasst wurden, können nicht von anderen zitiert werden. setting_default_quote_policy_unlisted: Sollten dich andere zitieren, werden ihre zitierten Beiträge ebenfalls nicht in den Trends und öffentlichen Timelines angezeigt. setting_default_sensitive: Medien, die mit einer Inhaltswarnung versehen worden sind, werden – je nach Einstellung – erst nach einem zusätzlichen Klick angezeigt @@ -63,6 +65,7 @@ de: setting_display_media_hide_all: Medien immer ausblenden setting_display_media_show_all: Medien mit Inhaltswarnung immer anzeigen setting_emoji_style: 'Wie Emojis dargestellt werden: „Automatisch“ verwendet native Emojis, für veraltete Browser wird jedoch Twemoji verwendet.' + setting_quick_boosting_html: Dadurch wird der Beitrag beim Anklicken des %{boost_icon} Teilen-Symbols sofort geteilt, anstatt das Drop-down-Menü zu öffnen. Die Möglichkeit zum Zitieren wird dabei in %{options_icon} Mehr verschoben. setting_system_scrollbars_ui: Betrifft nur Desktop-Browser, die auf Chrome oder Safari basieren setting_use_blurhash: Der Farbverlauf basiert auf den Farben der ausgeblendeten Medien, verschleiert aber jegliche Details setting_use_pending_items: Neue Beiträge hinter einem Klick verstecken, anstatt automatisch zu scrollen @@ -76,39 +79,39 @@ de: featured_tag: name: 'Hier sind ein paar Hashtags, die du in letzter Zeit am häufigsten verwendet hast:' filters: - action: Gib an, welche Aktion ausgeführt werden soll, wenn ein Beitrag dem Filter entspricht + action: Auswählen, welche Aktion ausgeführt werden soll, wenn ein Beitrag dem Filter entspricht actions: blur: Medien mit einer Warnung ausblenden, ohne den Text selbst auszublenden hide: Den gefilterten Beitrag vollständig ausblenden, als hätte er nie existiert warn: Den gefilterten Beitrag hinter einer Warnung, die den Filtertitel beinhaltet, ausblenden form_admin_settings: activity_api_enabled: Anzahl der wöchentlichen Beiträge, aktiven Profile und Registrierungen auf diesem Server - app_icon: WEBP, PNG, GIF oder JPG. Überschreibt das Standard-App-Symbol auf mobilen Geräten mit einem eigenen Symbol. + app_icon: WebP, PNG, GIF oder JPG. Überschreibt das Standard-App-Symbol auf mobilen Geräten mit einem eigenen Symbol. backups_retention_period: Nutzer*innen haben die Möglichkeit, Archive ihrer Beiträge zu erstellen, die sie später herunterladen können. Wenn ein positiver Wert gesetzt ist, werden diese Archive nach der festgelegten Anzahl von Tagen automatisch aus deinem Speicher gelöscht. - bootstrap_timeline_accounts: Diese Konten werden bei den Follower-Empfehlungen für neu registrierte Nutzer*innen oben angeheftet. + bootstrap_timeline_accounts: Diese Konten werden an den Anfang der Follow-Empfehlungen für neue Nutzer angeheftet. Gib eine durch Kommata getrennte Liste von Konten an. closed_registrations_message: Wird angezeigt, wenn Registrierungen deaktiviert sind content_cache_retention_period: Sämtliche Beiträge von anderen Servern (einschließlich geteilte Beiträge und Antworten) werden, unabhängig von der Interaktion der lokalen Nutzer*innen mit diesen Beiträgen, nach der festgelegten Anzahl von Tagen gelöscht. Das betrifft auch Beiträge, die von lokalen Nutzer*innen favorisiert oder als Lesezeichen gespeichert wurden. Private Erwähnungen zwischen Nutzer*innen von verschiedenen Servern werden ebenfalls verloren gehen und können nicht wiederhergestellt werden. Diese Option richtet sich ausschließlich an Server mit speziellen Zwecken und wird die allgemeine Nutzungserfahrung beeinträchtigen, wenn sie für den allgemeinen Gebrauch aktiviert ist. - custom_css: Du kannst benutzerdefinierte Stile auf die Web-Version von Mastodon anwenden. - favicon: WEBP, PNG, GIF oder JPG. Überschreibt das Standard-Mastodon-Favicon mit einem eigenen Symbol. - mascot: Überschreibt die Abbildung in der erweiterten Weboberfläche. + custom_css: Du kannst eigene Stylesheets für das Webinterface von Mastodon verwenden. + favicon: WebP, PNG, GIF oder JPG. Überschreibt das Standard-Mastodon-Favicon mit einem eigenen Favicon. + landing_page: Legt fest, welche Seite neue Besucher*innen sehen, wenn sie zum ersten Mal auf deinem Server ankommen. Für „Trends“ müssen die Trends in den Entdecken-Einstellungen aktiviert sein. Für „Lokaler Feed“ muss „Zugriff auf Live-Feeds, die lokale Beiträge beinhalten“ in den Entdecken-Einstellungen auf „Alle“ gesetzt werden. + mascot: Überschreibt die Abbildung im erweiterten Webinterface. media_cache_retention_period: Mediendateien aus Beiträgen von externen Nutzer*innen werden auf deinem Server zwischengespeichert. Wenn ein positiver Wert gesetzt ist, werden die Medien nach der festgelegten Anzahl von Tagen gelöscht. Sollten die Medien nach dem Löschvorgang wieder angefragt werden, werden sie erneut heruntergeladen, sofern der ursprüngliche Inhalt noch vorhanden ist. Es wird empfohlen, diesen Wert auf mindestens 14 Tage festzulegen, da die Häufigkeit der Abfrage von Linkvorschaukarten für Websites von Dritten begrenzt ist und die Linkvorschaukarten sonst nicht vor Ablauf dieser Zeit aktualisiert werden. min_age: Nutzer*innen werden bei der Registrierung aufgefordert, ihr Geburtsdatum zu bestätigen peers_api_enabled: Eine Liste von Domains, die diesem Server im Fediverse begegnet sind. Hierbei werden keine Angaben darüber gemacht, ob du mit einem bestimmten Server föderierst, sondern nur, dass dein Server davon weiß. Dies wird von Diensten verwendet, die allgemein Statistiken übers Ferdiverse sammeln. profile_directory: Dieses Verzeichnis zeigt alle Profile an, die sich dafür entschieden haben, entdeckt zu werden. - require_invite_text: Wenn Registrierungen eine manuelle Genehmigung erfordern, dann werden Nutzer einen Grund für ihre Registrierung angeben müssen + require_invite_text: Wenn Registrierungen eine manuelle Genehmigung erfordern, müssen Benutzer*innen ihren Beitrittswunsch begründen site_contact_email: Wie man dich bei rechtlichen oder Support-Anfragen erreichen kann. site_contact_username: Wie man dich auf Mastodon erreichen kann. site_extended_description: Alle zusätzlichen Informationen, die für Besucher*innen und deine Benutzer*innen nützlich sein könnten. Kann mit der Markdown-Syntax formatiert werden. site_short_description: Eine kurze Beschreibung zur eindeutigen Identifizierung des Servers. Wer betreibt ihn, für wen ist er bestimmt? - site_terms: Verwende eine eigene Datenschutzerklärung oder lasse das Feld leer, um die allgemeine Vorlage zu verwenden. Kann mit der Markdown-Syntax formatiert werden. + site_terms: Verwende eine eigene Datenschutzerklärung oder lass das Feld leer, um die allgemeine Vorlage zu verwenden. Kann mit der Markdown-Syntax formatiert werden. site_title: Wie Personen neben dem Domainnamen auf deinen Server verweisen können. status_page_url: Link zu einer Internetseite, auf der der Serverstatus während eines Ausfalls angezeigt wird - theme: Das Design, das abgemeldete Besucher und neue Benutzer sehen. + theme: Das Design, das nicht angemeldete Personen sehen. thumbnail: Ein Bild ungefähr im 2:1-Format, das neben den Server-Informationen angezeigt wird. - timeline_preview: Nicht angemeldete Personen können die neuesten öffentlichen Beiträge dieses Servers aufrufen. trendable_by_default: Manuelles Überprüfen angesagter Inhalte überspringen. Einzelne Elemente können später noch aus den Trends entfernt werden. trends: Trends zeigen, welche Beiträge, Hashtags und Nachrichten auf deinem Server immer beliebter werden. - trends_as_landing_page: Dies zeigt nicht angemeldeten Personen Trendinhalte anstelle einer Beschreibung des Servers an. Erfordert, dass Trends aktiviert sind. + wrapstodon: Ermöglicht Nutzer*innen dieses Servers einen spielerischen Jahresrückblick ihrer Mastodon-Aktivität zu erstellen. Diese Funktion ist jedes Jahr zwischen dem 10. und 31. Dezember verfügbar und wird Nutzer*innen angeboten, die innerhalb des Jahres mindestens einen öffentlichen oder stillen Beitrag verfasst und mindestens einen Hashtag verwendet haben. form_challenge: current_password: Du betrittst einen sicheren Bereich imports: @@ -159,7 +162,7 @@ de: color: Farbe, die für diese Rolle in der gesamten Benutzerschnittstelle verwendet wird, als RGB im Hexadezimalsystem highlighted: Dies macht die Rolle öffentlich im Profil sichtbar name: Name der Rolle, der auch öffentlich als Badge angezeigt wird, sofern dies unten aktiviert ist - permissions_as_keys: Benutzer*innen mit dieser Rolle haben Zugriff auf... + permissions_as_keys: Nutzer*innen mit dieser Rolle haben Zugriff auf … position: Höhere Rollen entscheiden über Konfliktlösungen zu gewissen Situationen. Bestimmte Aktionen können nur mit geringfügigeren Rollen durchgeführt werden username_block: allow_with_approval: Anstatt Registrierungen komplett zu verhindern, benötigen übereinstimmende Treffer eine Genehmigung @@ -227,7 +230,7 @@ de: locale: Sprache des Webinterface max_uses: Maximale Anzahl von Verwendungen new_password: Neues Passwort - note: Biografie + note: Über mich otp_attempt: Zwei-Faktor-Authentisierung password: Passwort phrase: Wort oder Formulierung @@ -235,12 +238,12 @@ de: setting_aggregate_reblogs: Geteilte Beiträge in den Timelines gruppieren setting_always_send_emails: Benachrichtigungen immer senden setting_auto_play_gif: Animierte GIFs automatisch abspielen - setting_boost_modal: Bestätigungsdialog beim Teilen eines Beitrags anzeigen + setting_boost_modal: Sichtbarkeit für geteilte Beiträge anpassen setting_default_language: Beitragssprache setting_default_privacy: Beitragssichtbarkeit - setting_default_quote_policy: Wer zitieren darf + setting_default_quote_policy: Wer darf mich zitieren? setting_default_sensitive: Medien immer mit einer Inhaltswarnung versehen - setting_delete_modal: Bestätigungsdialog beim Löschen eines Beitrags anzeigen + setting_delete_modal: Vor dem Löschen bestätigen setting_disable_hover_cards: Profilvorschau deaktivieren, wenn die Maus über das Profil bewegt wird setting_disable_swiping: Wischgesten deaktivieren setting_display_media: Darstellung von Medien @@ -250,7 +253,8 @@ de: setting_emoji_style: Emoji-Stil setting_expand_spoilers: Beiträge mit Inhaltswarnung immer ausklappen setting_hide_network: Follower und „Folge ich“ nicht anzeigen - setting_missing_alt_text_modal: Bestätigungsdialog anzeigen, bevor Medien ohne Bildbeschreibung veröffentlicht werden + setting_missing_alt_text_modal: Erinnerung für Bildbeschreibung anzeigen + setting_quick_boosting: Schnelles Teilen aktivieren setting_reduce_motion: Bewegung in Animationen verringern setting_system_font_ui: Standardschriftart des Browsers verwenden setting_system_scrollbars_ui: Bildlaufleiste des Betriebssystems verwenden @@ -279,17 +283,22 @@ de: activity_api_enabled: Aggregierte Nutzungsdaten über die API veröffentlichen app_icon: App-Symbol backups_retention_period: Aufbewahrungsfrist für Archive - bootstrap_timeline_accounts: Neuen Nutzern immer diese Konten empfehlen + bootstrap_timeline_accounts: Neuen Nutzer*innen immer diese Konten empfehlen closed_registrations_message: Nachricht, falls Registrierungen deaktiviert sind content_cache_retention_period: Aufbewahrungsfrist für externe Inhalte custom_css: Eigenes CSS favicon: Favicon - mascot: Benutzerdefiniertes Maskottchen (Legacy) + landing_page: Landingpage für neue Besucher*innen + local_live_feed_access: Zugriff auf Live-Feeds, die lokale Beiträge beinhalten + local_topic_feed_access: Zugriff auf Hashtags und Links, die lokale Beiträge beinhalten + mascot: Eigenes Maskottchen (Veraltet) media_cache_retention_period: Aufbewahrungsfrist für Medien im Cache min_age: Erforderliches Mindestalter peers_api_enabled: Die entdeckten Server im Fediverse über die API veröffentlichen profile_directory: Profilverzeichnis aktivieren registrations_mode: Wer darf ein neues Konto registrieren? + remote_live_feed_access: Zugriff auf Live-Feeds, die Beiträge externer Server beinhalten + remote_topic_feed_access: Zugriff auf Hashtags und Links, die Beiträge externer Server beinhalten require_invite_text: Begründung für Beitritt verlangen show_domain_blocks: Anzeigen, welche Domains gesperrt wurden show_domain_blocks_rationale: Anzeigen, weshalb Domains gesperrt wurden @@ -302,10 +311,9 @@ de: status_page_url: Statusseite (URL) theme: Standard-Design thumbnail: Vorschaubild des Servers - timeline_preview: Nicht-authentisierten Zugriff auf öffentliche Timelines gestatten trendable_by_default: Trends ohne vorherige Überprüfung erlauben trends: Trends aktivieren - trends_as_landing_page: Trends als Landingpage verwenden + wrapstodon: Wrapstodon aktivieren interactions: must_be_follower: Benachrichtigungen von Profilen, die mir nicht folgen, ausblenden must_be_following: Benachrichtigungen von Profilen, denen ich nicht folge, ausblenden @@ -347,10 +355,10 @@ de: indexable: Profilseite in Suchmaschinen einbeziehen show_application: App anzeigen, über die ich einen Beitrag veröffentlicht habe tag: - listable: Erlaube, dass dieser Hashtag in Suchen und Empfehlungen erscheint + listable: Dieser Hashtag darf in Suchen und Empfehlungen erscheinen name: Hashtag - trendable: Erlaube, dass dieser Hashtag in den Trends erscheint - usable: Beiträge dürfen diesen Hashtag lokal verwenden + trendable: Dieser Hashtag darf in den Trends erscheinen + usable: Dieser Hashtag darf lokal in Beiträgen verwendet werden terms_of_service: changelog: Was hat sich geändert? effective_date: Datum des Inkrafttretens @@ -366,9 +374,9 @@ de: jurisdiction: Gerichtsstand min_age: Mindestalter user: - date_of_birth_1i: Tag + date_of_birth_1i: Jahr date_of_birth_2i: Monat - date_of_birth_3i: Jahr + date_of_birth_3i: Tag role: Rolle time_zone: Zeitzone user_role: diff --git a/config/locales/simple_form.el.yml b/config/locales/simple_form.el.yml index f43399b5e6993d..a579cafbceb9bc 100644 --- a/config/locales/simple_form.el.yml +++ b/config/locales/simple_form.el.yml @@ -49,13 +49,15 @@ el: email: Θα σου σταλεί email επιβεβαίωσης header: WEBP, PNG, GIF ή JPG. Το πολύ %{size}. Θα υποβαθμιστεί σε %{dimensions}px inbox_url: Αντέγραψε το URL της αρχικής σελίδας του ανταποκριτή που θέλεις να χρησιμοποιήσεις - irreversible: Οι φιλτραρισμένες αναρτήσεις θα εξαφανιστούν αμετάκλητα, ακόμα και αν το φίλτρο αργότερα αφαιρεθεί + irreversible: Οι φιλτραρισμένες αναρτήσεις θα εξαφανιστούν αμετάκλητα, ακόμη και αν το φίλτρο αργότερα αφαιρεθεί locale: Η γλώσσα χρήσης, των email και των ειδοποιήσεων push password: Χρησιμοποίησε τουλάχιστον 8 χαρακτήρες phrase: Θα ταιριάζει ανεξαρτήτως πεζών/κεφαλαίων ή προειδοποίησης περιεχομένου μιας ανάρτησης scopes: Ποια API θα επιτρέπεται στην εφαρμογή να χρησιμοποιήσεις. Αν επιλέξεις κάποιο υψηλό εύρος εφαρμογής, δε χρειάζεται να επιλέξεις και το καθένα ξεχωριστά. + setting_advanced_layout: Εμφάνιση του Mastodon ως διάταξη πολλαπλών στηλών, επιτρέποντάς σας να δείτε το χρονοδιάγραμμα, τις ειδοποιήσεις και μια τρίτη στήλη της επιλογής σας. Δεν συνιστάται για μικρότερες οθόνες. setting_aggregate_reblogs: Απόκρυψη των νέων αναρτήσεων για τις αναρτήσεις που έχουν ενισχυθεί πρόσφατα (επηρεάζει μόνο τις νέες ενισχύσεις) setting_always_send_emails: Κανονικά οι ειδοποιήσεις μέσω ηλεκτρονικού ταχυδρομείου δεν θα αποστέλλονται όταν χρησιμοποιείτε ενεργά το Mastodon + setting_boost_modal: Όταν ενεργοποιηθεί, η ενίσχυση θα ανοίξει πρώτα ένα διάλογο επιβεβαίωσης στο οποίο μπορείτε να αλλάξετε την ορατότητα της ενίσχυσής σας. setting_default_quote_policy_private: Αναρτήσεις για ακολούθους μόνο που έχουν συνταχθεί στο Mastodon, δεν μπορούν να γίνουν παράθεση από άλλους. setting_default_quote_policy_unlisted: Όταν οι άνθρωποι σας παραθέτουν, η ανάρτησή τους θα είναι επίσης κρυμμένη από τις δημοφιλείς ροές. setting_default_sensitive: Τα ευαίσθητα πολυμέσα είναι κρυμμένα και εμφανίζονται με ένα κλικ @@ -63,6 +65,7 @@ el: setting_display_media_hide_all: Μόνιμη απόκρυψη όλων των πολυμέσων setting_display_media_show_all: Πάντα εμφάνιση πολυμέσων setting_emoji_style: Πώς να εμφανίσετε emojis. Το "Αυτόματο" θα προσπαθήσει να χρησιμοποιήσει εγγενή emoji, αλλά πέφτει πίσω στο Twemoji για προγράμματα περιήγησης παλαιού τύπου. + setting_quick_boosting_html: Όταν ενεργοποιηθεί, κάνοντας κλικ στο εικονίδιο %{boost_icon} Ενίσχυση θα ενισχύσει αμέσως αντί να ανοίξει το αναπτυσσόμενο μενού ενίσχυσης/παράθεσης. Μετακινεί την ενέργεια παράθεσης στο μενού %{options_icon} (Επιλογές). setting_system_scrollbars_ui: Ισχύει μόνο για προγράμματα περιήγησης υπολογιστή με βάση το Safari και το Chrome setting_use_blurhash: Οι χρωματισμοί βασίζονται στα χρώματα του κρυμμένου πολυμέσου αλλά θολώνουν τις λεπτομέρειες setting_use_pending_items: Εμφάνιση ενημερώσεων ροής μετά από κλικ αντί για αυτόματη κύλισή τους @@ -74,7 +77,7 @@ el: domain: Αυτό μπορεί να είναι το όνομα τομέα που εμφανίζεται στη διεύθυνση email ή η εγγραφή MX που χρησιμοποιεί. Θα ελέγχονται κατά την εγγραφή. with_dns_records: Θα γίνει απόπειρα ανάλυσης των εγγραφών DNS του τομέα και τα αποτελέσματα θα μπουν και αυτά σε μαύρη λίστα featured_tag: - name: 'Εδώ είναι μερικά από τα hashtags που χρησιμοποιήσατε περισσότερο πρόσφατα:' + name: 'Εδώ είναι μερικές από τις ετικέτες που χρησιμοποιήσατε περισσότερο πρόσφατα:' filters: action: Επιλέξτε ποια ενέργεια θα εκτελεστεί όταν μια ανάρτηση ταιριάζει με το φίλτρο actions: @@ -85,13 +88,14 @@ el: activity_api_enabled: Καταμέτρηση τοπικά δημοσιευμένων δημοσιεύσεων, ενεργών χρηστών και νέων εγγραφών σε εβδομαδιαία πακέτα app_icon: WEBP, PNG, GIF ή JPG. Παρακάμπτει το προεπιλεγμένο εικονίδιο εφαρμογής σε κινητές συσκευές με προσαρμοσμένο εικονίδιο. backups_retention_period: Οι χρήστες έχουν τη δυνατότητα να δημιουργήσουν αρχεία των αναρτήσεων τους για να κατεβάσουν αργότερα. Όταν οριστεί μια θετική τιμή, αυτά τα αρχεία θα διαγράφονται αυτόματα από τον αποθηκευτικό σου χώρο μετά τον καθορισμένο αριθμό ημερών. - bootstrap_timeline_accounts: Αυτοί οι λογαριασμοί θα καρφιτσωθούν στην κορυφή των νέων χρηστών που ακολουθούν τις συστάσεις. + bootstrap_timeline_accounts: Αυτοί οι λογαριασμοί θα καρφιτσωθούν στην κορυφή των προτεινόμενων ακολουθήσεων για νέους χρήστες. Παρέχετε μια λίστα λογαριασμών χωρισμένη με κόμμα. closed_registrations_message: Εμφανίζεται όταν κλείνουν οι εγγραφές content_cache_retention_period: Όλες οι αναρτήσεις από άλλους διακομιστές (συμπεριλαμβανομένων των ενισχύσεων και απαντήσεων) θα διαγραφούν μετά τον καθορισμένο αριθμό ημερών, χωρίς να λαμβάνεται υπόψη οποιαδήποτε αλληλεπίδραση τοπικού χρήστη με αυτές τις αναρτήσεις. Αυτό περιλαμβάνει αναρτήσεις όπου ένας τοπικός χρήστης την έχει χαρακτηρίσει ως σελιδοδείκτη ή αγαπημένη. Θα χαθούν επίσης ιδιωτικές αναφορές μεταξύ χρηστών από διαφορετικές οντότητες και θα είναι αδύνατο να αποκατασταθούν. Η χρήση αυτής της ρύθμισης προορίζεται για οντότητες ειδικού σκοπού και χαλάει πολλές προσδοκίες του χρήστη όταν εφαρμόζεται για χρήση γενική σκοπού. custom_css: Μπορείς να εφαρμόσεις προσαρμοσμένα στυλ στην έκδοση ιστοσελίδας του Mastodon. favicon: WEBP, PNG, GIF ή JPG. Παρακάμπτει το προεπιλεγμένο favicon του Mastodon με ένα προσαρμοσμένο εικονίδιο. + landing_page: Επιλέγει ποια σελίδα βλέπουν οι νέοι επισκέπτες όταν φτάνουν για πρώτη φορά στο διακομιστή σας. Αν επιλέξετε "Τάσεις", τότε οι τάσεις πρέπει να είναι ενεργοποιημένες στις Ρυθμίσεις Ανακάλυψης. Αν επιλέξετε "Τοπική ροή", τότε το "Πρόσβαση σε ζωντανές ροές με τοπικές αναρτήσεις" πρέπει να οριστεί σε "Όλοι" στις Ρυθμίσεις Ανακάλυψης. mascot: Παρακάμπτει την εικονογραφία στην προηγμένη διεπαφή ιστού. - media_cache_retention_period: Τα αρχεία πολυμέσων από αναρτήσεις που γίνονται από απομακρυσμένους χρήστες αποθηκεύονται προσωρινά στο διακομιστή σου. Όταν οριστεί μια θετική τιμή, τα μέσα θα διαγραφούν μετά τον καθορισμένο αριθμό ημερών. Αν τα δεδομένα πολυμέσων ζητηθούν μετά τη διαγραφή τους, θα γίνει ε, αν το πηγαίο περιεχόμενο είναι ακόμα διαθέσιμο. Λόγω περιορισμών σχετικά με το πόσο συχνά οι κάρτες προεπισκόπησης συνδέσμων συνδέονται σε ιστοσελίδες τρίτων, συνιστάται να ορίσεις αυτή την τιμή σε τουλάχιστον 14 ημέρες ή οι κάρτες προεπισκόπησης συνδέσμων δεν θα ενημερώνονται κατ' απάιτηση πριν από εκείνη την ώρα. + media_cache_retention_period: Τα αρχεία πολυμέσων από αναρτήσεις που γίνονται από απομακρυσμένους χρήστες αποθηκεύονται προσωρινά στο διακομιστή σου. Όταν οριστεί μια θετική τιμή, τα μέσα θα διαγραφούν μετά τον καθορισμένο αριθμό ημερών. Αν τα δεδομένα πολυμέσων ζητηθούν μετά τη διαγραφή τους, θα γίνει λήψη τους ξανά, αν το πηγαίο περιεχόμενο είναι ακόμη διαθέσιμο. Λόγω περιορισμών σχετικά με το πόσο συχνά οι κάρτες προεπισκόπησης συνδέσμων συνδέονται σε ιστοσελίδες τρίτων, συνιστάται να ορίσεις αυτή την τιμή σε τουλάχιστον 14 ημέρες ή οι κάρτες προεπισκόπησης συνδέσμων δεν θα ενημερώνονται κατ' απάιτηση πριν από εκείνη την ώρα. min_age: Οι χρήστες θα κληθούν να επιβεβαιώσουν την ημερομηνία γέννησής τους κατά την εγγραφή peers_api_enabled: Μια λίστα με ονόματα τομέα που συνάντησε αυτός ο διακομιστής στο fediverse. Δεν περιλαμβάνονται δεδομένα εδώ για το αν συναλλάσσετε με ένα συγκεκριμένο διακομιστή, μόνο ότι ο διακομιστής σας το ξέρει. Χρησιμοποιείται από υπηρεσίες που συλλέγουν στατιστικά στοιχεία για την συναλλαγή με γενική έννοια. profile_directory: Ο κατάλογος προφίλ παραθέτει όλους τους χρήστες που έχουν επιλέξει να είναι ανακαλύψιμοι. @@ -105,14 +109,13 @@ el: status_page_url: Το URL μιας σελίδας όπου κάποιος μπορεί να δει την κατάσταση αυτού του διακομιστή κατά τη διάρκεια μιας διακοπής λειτουργίας theme: Θέμα που βλέπουν αποσυνδεδεμένοι επισκέπτες ή νέοι χρήστες. thumbnail: Μια εικόνα περίπου 2:1 που εμφανίζεται παράλληλα με τις πληροφορίες του διακομιστή σου. - timeline_preview: Αποσυνδεδεμένοι επισκέπτες θα μπορούν να περιηγηθούν στις πιο πρόσφατες δημόσιες δημοσιεύσεις που είναι διαθέσιμες στο διακομιστή. - trendable_by_default: Παράλειψη χειροκίνητης αξιολόγησης του περιεχομένου σε τάση. Μεμονωμένα στοιχεία μπορούν ακόμα να αφαιρεθούν από τις τάσεις μετέπειτα. + trendable_by_default: Παράλειψη χειροκίνητης αξιολόγησης του περιεχομένου σε τάση. Μεμονωμένα στοιχεία μπορούν ακόμη να αφαιρεθούν από τις τάσεις μετέπειτα. trends: Τάσεις δείχνουν ποιες δημοσιεύσεις, ετικέτες και ειδήσεις προκαλούν έλξη στο διακομιστή σας. - trends_as_landing_page: Εμφάνιση περιεχομένου σε τάση σε αποσυνδεδεμένους χρήστες και επισκέπτες αντί για μια περιγραφή αυτού του διακομιστή. Απαιτεί οι τάσεις να έχουν ενεργοποιηθεί. + wrapstodon: Πρόσφερε στους τοπικούς χρήστες τη δυνατότητα να δημιουργήσουν μια παιχνιδιάρικη σύνοψη της χρήσης τους Mastodon κατά τη διάρκεια του έτους. Αυτό το χαρακτηριστικό είναι διαθέσιμο μεταξύ της 10ης και της 31ης Δεκεμβρίου κάθε έτους, και προσφέρεται σε χρήστες που έκαναν τουλάχιστον μία δημόσια ή ήσυχη δημόσια θέση και χρησιμοποίησαν τουλάχιστον μία ετικέτα εντός του έτους. form_challenge: current_password: Μπαίνεις σε ασφαλή περιοχή imports: - data: Αρχείο CSV που έχει εξαχθεί από διαφορετικό κόμβο Mastodon + data: Αρχείο CSV που έχει εξαχθεί από διαφορετικό διακομιστή Mastodon invite_request: text: Αυτό θα μας βοηθήσει να επιθεωρήσουμε την αίτησή σου ip_block: @@ -171,7 +174,7 @@ el: url: Πού θα σταλούν τα γεγονότα labels: account: - attribution_domains: Ιστοσελίδες επιτρέπεται να σας αναφέρουν + attribution_domains: Ιστοσελίδες που επιτρέπεται να σας αναφέρουν discoverable: Παροχή προφίλ και αναρτήσεων σε αλγορίθμους ανακάλυψης fields: name: Περιγραφή @@ -208,7 +211,7 @@ el: text: Εξηγήστε γιατί αυτή η απόφαση πρέπει να αντιστραφεί defaults: autofollow: Προσκάλεσε για να ακολουθήσουν το λογαριασμό σου - avatar: Αβατάρ + avatar: Άβαταρ bot: Αυτός είναι ένας αυτοματοποιημένος λογαριασμός (bot) chosen_languages: Φιλτράρισμα γλωσσών confirm_new_password: Επιβεβαίωσε νέο συνθηματικό @@ -235,12 +238,12 @@ el: setting_aggregate_reblogs: Ομαδοποίηση προωθήσεων στις ροές setting_always_send_emails: Πάντα να αποστέλλονται ειδοποίησεις μέσω email setting_auto_play_gif: Αυτόματη αναπαραγωγή των GIF - setting_boost_modal: Επιβεβαίωση πριν την προώθηση + setting_boost_modal: Έλεγχος ορατότητας της ενίσχυσης setting_default_language: Γλώσσα κατά την ανάρτηση setting_default_privacy: Ορατότητα αναρτήσεων setting_default_quote_policy: Ποιος μπορεί να παραθέσει setting_default_sensitive: Σημείωση όλων των πολυμέσων ως ευαίσθητου περιεχομένου - setting_delete_modal: Επιβεβαίωση πριν τη διαγραφή ενός τουτ + setting_delete_modal: Προειδοποίηση πριν από τη διαγραφή μιας ανάρτησης setting_disable_hover_cards: Απενεργοποίηση προεπισκόπησης προφίλ κατά την αιώρηση setting_disable_swiping: Απενεργοποίηση κινήσεων συρσίματος setting_display_media: Εμφάνιση πολυμέσων @@ -250,7 +253,8 @@ el: setting_emoji_style: Στυλ Emoji setting_expand_spoilers: Μόνιμη ανάπτυξη των τουτ με προειδοποίηση περιεχομένου setting_hide_network: Κρύψε τις διασυνδέσεις σου - setting_missing_alt_text_modal: Εμφάνιση διαλόγου επιβεβαίωσης πριν από τη δημοσίευση πολυμέσων χωρίς εναλλακτικό κείμενο + setting_missing_alt_text_modal: Προειδοποίηση πριν από την ανάρτηση πολυμέσων χωρίς εναλλακτικό κείμενο + setting_quick_boosting: Ενεργοποίηση γρήγορης ενίσχυσης setting_reduce_motion: Μείωση κίνησης κινουμένων στοιχείων setting_system_font_ui: Χρήση της προεπιλεγμένης γραμματοσειράς του συστήματος setting_system_scrollbars_ui: Χρήση προκαθορισμένης γραμμής κύλισης του συστήματος @@ -284,12 +288,17 @@ el: content_cache_retention_period: Περίοδος διατήρησης απομακρυσμένου περιεχομένου custom_css: Προσαρμοσμένο CSS favicon: Favicon + landing_page: Σελίδα προσγείωσης για νέους επισκέπτες + local_live_feed_access: Πρόσβαση σε ζωντανές ροές με τοπικές αναρτήσεις + local_topic_feed_access: Πρόσβαση σε ροές ετικετών και συνδέσμων με τοπικές αναρτήσεις mascot: Προσαρμοσμένη μασκότ (απαρχαιωμένο) media_cache_retention_period: Περίοδος διατήρησης προσωρινής μνήμης πολυμέσων min_age: Ελάχιστη απαιτούμενη ηλικία peers_api_enabled: Δημοσίευση λίστας των εντοπισμένων διακομιστών στο API profile_directory: Ενεργοποίηση καταλόγου προφίλ registrations_mode: Ποιος μπορεί να εγγραφεί + remote_live_feed_access: Πρόσβαση σε ζωντανές ροές με απομακρυσμένες αναρτήσεις + remote_topic_feed_access: Πρόσβαση σε ροές ετικετών και συνδέσμων με απομακρυσμένες αναρτήσεις require_invite_text: Απαίτησε έναν λόγο για να γίνει κάποιος μέλος show_domain_blocks: Εμφάνιση αποκλεισμένων τομέων show_domain_blocks_rationale: Εμφάνιση γιατί αποκλείστηκαν οι τομείς @@ -302,10 +311,9 @@ el: status_page_url: URL σελίδας κατάστασης theme: Προεπιλεγμένο θέμα thumbnail: Μικρογραφία διακομιστή - timeline_preview: Να επιτρέπεται μη πιστοποιημένη πρόσβαση σε δημόσιες ροές trendable_by_default: Επίτρεψε τις τάσεις χωρίς προηγούμενη αξιολόγηση trends: Ενεργοποίηση τάσεων - trends_as_landing_page: Χρήση των τάσεων ως σελίδα προορισμού + wrapstodon: Ενεργοποίηση Wrapstodon interactions: must_be_follower: Μπλόκαρε τις ειδοποιήσεις από όσους δεν σε ακολουθούν must_be_following: Μπλόκαρε τις ειδοποιήσεις από όσους δεν ακολουθείς @@ -366,9 +374,9 @@ el: jurisdiction: Νομική δικαιοδοσία min_age: Ελάχιστη ηλικία user: - date_of_birth_1i: Ημέρα + date_of_birth_1i: Έτος date_of_birth_2i: Μήνας - date_of_birth_3i: Έτος + date_of_birth_3i: Ημέρα role: Ρόλος time_zone: Ζώνη ώρας user_role: diff --git a/config/locales/simple_form.en-GB.yml b/config/locales/simple_form.en-GB.yml index e813ecc0c34b51..db4080c11bbfb2 100644 --- a/config/locales/simple_form.en-GB.yml +++ b/config/locales/simple_form.en-GB.yml @@ -10,13 +10,13 @@ en-GB: indexable: Your public posts may appear in search results on Mastodon. People who have interacted with your posts may be able to search them regardless. note: 'You can @mention other people or #hashtags.' show_collections: People will be able to browse through your follows and followers. People that you follow will see that you follow them regardless. - unlocked: People will be able to follow you without requesting approval. Uncheck if you want to review follow requests and choose whether to accept or reject new followers. + unlocked: People will be able to follow you without requesting approval. Untick if you want to review follow requests and choose whether to accept or reject new followers. account_alias: acct: Specify the username@domain of the account you want to move from account_migration: acct: Specify the username@domain of the account you want to move to account_warning_preset: - text: You can use post syntax, such as URLs, hashtags and mentions + text: You can use post syntax, such as URLs, hashtags, and mentions title: Optional. Not visible to the recipient admin_account_action: include_statuses: The user will see which posts have caused the moderation action or warning @@ -29,9 +29,9 @@ en-GB: sensitive: Force all this user's media attachments to be flagged as sensitive. silence: Prevent the user from being able to post with public visibility, hide their posts and notifications from people not following them. Closes all reports against this account. suspend: Prevent any interaction from or to this account and delete its contents. Revertible within 30 days. Closes all reports against this account. - warning_preset_id: Optional. You can still add custom text to end of the preset + warning_preset_id: Optional. You can still add custom text to the end of the preset announcement: - all_day: When checked, only the dates of the time range will be displayed + all_day: When ticked, only the dates of the time range will be displayed ends_at: Optional. Announcement will be automatically unpublished at this time scheduled_at: Leave blank to publish the announcement immediately starts_at: Optional. In case your announcement is bound to a specific time range @@ -43,38 +43,43 @@ en-GB: avatar: WEBP, PNG, GIF or JPG. At most %{size}. Will be downscaled to %{dimensions}px bot: Signal to others that the account mainly performs automated actions and might not be monitored context: One or multiple contexts where the filter should apply - current_password: For security purposes please enter the password of the current account + current_password: For security purposes, please enter the password of the current account current_username: To confirm, please enter the username of the current account digest: Only sent after a long period of inactivity and only if you have received any personal messages in your absence - email: You will be sent a confirmation e-mail + email: You will be sent a confirmation email header: WEBP, PNG, GIF or JPG. At most %{size}. Will be downscaled to %{dimensions}px - inbox_url: Copy the URL from the frontpage of the relay you want to use - irreversible: Filtered posts will disappear irreversibly, even if filter is later removed - locale: The language of the user interface, e-mails and push notifications - password: Use at least 8 characters + inbox_url: Copy the URL from the front page of the relay you want to use + irreversible: Filtered posts will disappear irreversibly, even if the filter is later removed + locale: The language of the user interface, emails, and push notifications + password: Use at least eight characters phrase: Will be matched regardless of casing in text or content warning of a post scopes: Which APIs the application will be allowed to access. If you select a top-level scope, you don't need to select individual ones. + setting_advanced_layout: Display Mastodon as a multi-column layout, allowing you to view the timeline, notifications, and a third column of your choosing. Not recommended for smaller screens. setting_aggregate_reblogs: Do not show new boosts for posts that have been recently boosted (only affects newly-received boosts) - setting_always_send_emails: Normally e-mail notifications won't be sent when you are actively using Mastodon + setting_always_send_emails: Normally email notifications won't be sent when you are actively using Mastodon + setting_boost_modal: When enabled, boosting will first open a confirmation dialogue in which you can change the visibility of your boost. + setting_default_quote_policy_private: Followers-only posts authored on Mastodon can't be quoted by others. + setting_default_quote_policy_unlisted: When people quote you, their post will also be hidden from trending timelines. setting_default_sensitive: Sensitive media is hidden by default and can be revealed with a click setting_display_media_default: Hide media marked as sensitive setting_display_media_hide_all: Always hide media setting_display_media_show_all: Always show media setting_emoji_style: How to display emojis. "Auto" will try using native emoji, but falls back to Twemoji for legacy browsers. + setting_quick_boosting_html: When enabled, clicking on the %{boost_icon} Boost icon will immediately boost instead of opening the boost/quote dropdown menu. Relocates the quoting action to the %{options_icon} (Options) menu. setting_system_scrollbars_ui: Applies only to desktop browsers based on Safari and Chrome - setting_use_blurhash: Gradients are based on the colors of the hidden visuals but obfuscate any details + setting_use_blurhash: Gradients are based on the colours of the hidden visuals but obfuscate any details setting_use_pending_items: Hide timeline updates behind a click instead of automatically scrolling the feed username: You can use letters, numbers, and underscores whole_word: When the keyword or phrase is alphanumeric only, it will only be applied if it matches the whole word domain_allow: domain: This domain will be able to fetch data from this server and incoming data from it will be processed and stored email_domain_block: - domain: This can be the domain name that shows up in the e-mail address or the MX record it uses. They will be checked upon sign-up. + domain: This can be the domain name that shows up in the email address or the MX record it uses. They will be checked upon sign-up. with_dns_records: An attempt to resolve the given domain's DNS records will be made and the results will also be blocked featured_tag: name: 'Here are some of the hashtags you used the most recently:' filters: - action: Chose which action to perform when a post matches the filter + action: Choose which action to perform when a post matches the filter actions: blur: Hide media behind a warning, without hiding the text itself hide: Completely hide the filtered content, behaving as if it did not exist @@ -83,15 +88,16 @@ en-GB: activity_api_enabled: Counts of locally published posts, active users, and new registrations in weekly buckets app_icon: WEBP, PNG, GIF or JPG. Overrides the default app icon on mobile devices with a custom icon. backups_retention_period: Users have the ability to generate archives of their posts to download later. When set to a positive value, these archives will be automatically deleted from your storage after the specified number of days. - bootstrap_timeline_accounts: These accounts will be pinned to the top of new users' follow recommendations. + bootstrap_timeline_accounts: These accounts will be pinned to the top of new users' follow recommendations. Provide a comma-separated list of accounts. closed_registrations_message: Displayed when sign-ups are closed - content_cache_retention_period: All posts from other servers (including boosts and replies) will be deleted after the specified number of days, without regard to any local user interaction with those posts. This includes posts where a local user has marked it as bookmarks or favorites. Private mentions between users from different instances will also be lost and impossible to restore. Use of this setting is intended for special purpose instances and breaks many user expectations when implemented for general purpose use. + content_cache_retention_period: All posts from other servers (including boosts and replies) will be deleted after the specified number of days, without regard to any local user interaction with those posts. This includes posts where a local user has marked it as bookmarks or favourites. Private mentions between users from different instances will also be lost and impossible to restore. Use of this setting is intended for special purpose instances and breaks many user expectations when implemented for general purpose use. custom_css: You can apply custom styles on the web version of Mastodon. favicon: WEBP, PNG, GIF or JPG. Overrides the default Mastodon favicon with a custom icon. + landing_page: Selects what page new visitors see when they first arrive on your server. If you select "Trends", then Trends needs to be enabled in the Discovery Settings. If you select "Local feed", then "Access to live feeds featuring local posts" needs to be set to "Everyone" in the Discovery Settings. mascot: Overrides the illustration in the advanced web interface. media_cache_retention_period: Media files from posts made by remote users are cached on your server. When set to a positive value, media will be deleted after the specified number of days. If the media data is requested after it is deleted, it will be re-downloaded, if the source content is still available. Due to restrictions on how often link preview cards poll third-party sites, it is recommended to set this value to at least 14 days, or link preview cards will not be updated on demand before that time. min_age: Users will be asked to confirm their date of birth during sign-up - peers_api_enabled: A list of domain names this server has encountered in the fediverse. No data is included here about whether you federate with a given server, just that your server knows about it. This is used by services that collect statistics on federation in a general sense. + peers_api_enabled: A list of domain names this server has encountered in the Fediverse. No data is included here about whether you federate with a given server, just that your server knows about it. This is used by services that collect statistics on federation in a general sense. profile_directory: The profile directory lists all users who have opted-in to be discoverable. require_invite_text: When sign-ups require manual approval, make the “Why do you want to join?” text input mandatory rather than optional site_contact_email: How people can reach you for legal or support inquiries. @@ -103,10 +109,9 @@ en-GB: status_page_url: URL of a page where people can see the status of this server during an outage theme: Theme that logged out visitors and new users see. thumbnail: A roughly 2:1 image displayed alongside your server information. - timeline_preview: Logged out visitors will be able to browse the most recent public posts available on the server. trendable_by_default: Skip manual review of trending content. Individual items can still be removed from trends after the fact. - trends: Trends show which posts, hashtags and news stories are gaining traction on your server. - trends_as_landing_page: Show trending content to logged-out users and visitors instead of a description of this server. Requires trends to be enabled. + trends: Trends show which posts, hashtags, and news stories are gaining traction on your server. + wrapstodon: Offer local users to generate a playful summary of their Mastodon use during the year. This feature is available between the 10th and 31st of December of each year, and is offered to users who made at least one Public or Quiet Public post and used at least one hashtag within the year. form_challenge: current_password: You are entering a secure area imports: @@ -127,7 +132,7 @@ en-GB: text: Describe a rule or requirement for users on this server. Try to keep it short and simple sessions: otp: 'Enter the two-factor code generated by your phone app or use one of your recovery codes:' - webauthn: If it's an USB key be sure to insert it and, if necessary, tap it. + webauthn: If it's a USB key be sure to insert it and, if necessary, tap it. settings: indexable: Your profile page may appear in search results on Google, Bing, and others. show_application: You will always be able to see which app published your post regardless. @@ -141,25 +146,32 @@ en-GB: admin_email: Legal notices include counternotices, court orders, takedown requests, and law enforcement requests. arbitration_address: Can be the same as Physical address above, or “N/A” if using email. arbitration_website: Can be a web form, or “N/A” if using email. - choice_of_law: City, region, territory or state the internal substantive laws of which shall govern any and all claims. + choice_of_law: City, region, territory, or state, the internal substantive laws of which shall govern any and all claims. dmca_address: For US operators, use the address registered in the DMCA Designated Agent Directory. A P.O. Box listing is available upon direct request, use the DMCA Designated Agent Post Office Box Waiver Request to email the Copyright Office and describe that you are a home-based content moderator who fears revenge or retribution for your actions and need to use a P.O. Box to remove your home address from public view. dmca_email: Can be the same email used for “Email address for legal notices” above. domain: Unique identification of the online service you are providing. - jurisdiction: List the country where whoever pays the bills lives. If it’s a company or other entity, list the country where it’s incorporated, and the city, region, territory or state as appropriate. + jurisdiction: List the country where whoever pays the bills lives. If it’s a company or other entity, list the country where it’s incorporated, and the city, region, territory, or state as appropriate. min_age: Should not be below the minimum age required by the laws of your jurisdiction. user: chosen_languages: When checked, only posts in selected languages will be displayed in public timelines + date_of_birth: + one: We have to make sure you're at least %{count} to use %{domain}. We won't store this. + other: We have to make sure you're at least %{count} to use %{domain}. We won't store this. role: The role controls which permissions the user has. user_role: - color: Color to be used for the role throughout the UI, as RGB in hex format + color: Colour to be used for the role throughout the UI, as RGB in hex format highlighted: This makes the role publicly visible name: Public name of the role, if role is set to be displayed as a badge permissions_as_keys: Users with this role will have access to... position: Higher role decides conflict resolution in certain situations. Certain actions can only be performed on roles with a lower priority + username_block: + allow_with_approval: Instead of preventing sign-up outright, matching sign-ups will require your approval + comparison: Please be mindful of the Scunthorpe Problem when blocking partial matches + username: Will be matched regardless of casing and common homoglyphs like "4" for "a" or "3" for "e" webhook: events: Select events to send template: Compose your own JSON payload using variable interpolation. Leave blank for default JSON. - url: Where events will be sent to + url: Where events will be sent labels: account: attribution_domains: Websites allowed to credit you @@ -178,8 +190,8 @@ en-GB: text: Preset text title: Title admin_account_action: - include_statuses: Include reported posts in the e-mail - send_email_notification: Notify the user per e-mail + include_statuses: Include reported posts in the email + send_email_notification: Notify the user per email text: Custom warning type: Action types: @@ -208,7 +220,7 @@ en-GB: current_password: Current password data: Data display_name: Display name - email: E-mail address + email: Email address expires_in: Expire after fields: Profile metadata header: Header @@ -216,7 +228,7 @@ en-GB: inbox_url: URL of the relay inbox irreversible: Drop instead of hide locale: Interface language - max_uses: Max number of uses + max_uses: Maximum number of uses new_password: New password note: Bio otp_attempt: Two-factor code @@ -224,13 +236,14 @@ en-GB: phrase: Keyword or phrase setting_advanced_layout: Enable advanced web interface setting_aggregate_reblogs: Group boosts in timelines - setting_always_send_emails: Always send e-mail notifications + setting_always_send_emails: Always send email notifications setting_auto_play_gif: Auto-play animated GIFs - setting_boost_modal: Show confirmation dialogue before boosting + setting_boost_modal: Control boosting visibility setting_default_language: Posting language + setting_default_privacy: Posting visibility setting_default_quote_policy: Who can quote setting_default_sensitive: Always mark media as sensitive - setting_delete_modal: Show confirmation dialogue before deleting a post + setting_delete_modal: Warn me before deleting a post setting_disable_hover_cards: Disable profile preview on hover setting_disable_swiping: Disable swiping motions setting_display_media: Media display @@ -240,13 +253,14 @@ en-GB: setting_emoji_style: Emoji style setting_expand_spoilers: Always expand posts marked with content warnings setting_hide_network: Hide your social graph - setting_missing_alt_text_modal: Show confirmation dialogue before posting media without alt text + setting_missing_alt_text_modal: Warn me before posting media without alt text + setting_quick_boosting: Enable quick boosting setting_reduce_motion: Reduce motion in animations setting_system_font_ui: Use system's default font setting_system_scrollbars_ui: Use system's default scrollbar setting_theme: Site theme setting_trends: Show today's trends - setting_unfollow_modal: Show confirmation dialog before unfollowing someone + setting_unfollow_modal: Show confirmation dialogue before unfollowing someone setting_use_blurhash: Show colourful gradients for hidden media setting_use_pending_items: Slow mode severity: Severity @@ -274,16 +288,21 @@ en-GB: content_cache_retention_period: Remote content retention period custom_css: Custom CSS favicon: Favicon + landing_page: Landing page for new visitors + local_live_feed_access: Access to live feeds featuring local posts + local_topic_feed_access: Access to hashtag and link feeds featuring local posts mascot: Custom mascot (legacy) media_cache_retention_period: Media cache retention period min_age: Minimum age requirement peers_api_enabled: Publish list of discovered servers in the API profile_directory: Enable profile directory - registrations_mode: Who can sign-up + registrations_mode: Who can sign up + remote_live_feed_access: Access to live feeds featuring remote posts + remote_topic_feed_access: Access to hashtag and link feeds featuring remote posts require_invite_text: Require a reason to join show_domain_blocks: Show domain blocks show_domain_blocks_rationale: Show why domains were blocked - site_contact_email: Contact e-mail + site_contact_email: Contact email site_contact_username: Contact username site_extended_description: Extended description site_short_description: Server description @@ -292,10 +311,9 @@ en-GB: status_page_url: Status page URL theme: Default theme thumbnail: Server thumbnail - timeline_preview: Allow unauthenticated access to public timelines trendable_by_default: Allow trends without prior review trends: Enable trends - trends_as_landing_page: Use trends as the landing page + wrapstodon: Enable Wrapstodon interactions: must_be_follower: Block notifications from non-followers must_be_following: Block notifications from people you don't follow @@ -320,6 +338,7 @@ en-GB: follow_request: Someone requested to follow you mention: Someone mentioned you pending_account: New account needs review + quote: Someone quoted you reblog: Someone boosted your post report: New report is submitted software_updates: @@ -327,7 +346,7 @@ en-GB: critical: Notify on critical updates only label: A new Mastodon version is available none: Never notify of updates (not recommended) - patch: Notify on bugfix updates + patch: Notify on bug fix updates trending_tag: New trend requires review rule: hint: Additional information @@ -355,9 +374,9 @@ en-GB: jurisdiction: Legal jurisdiction min_age: Minimum age user: - date_of_birth_1i: Day + date_of_birth_1i: Year date_of_birth_2i: Month - date_of_birth_3i: Year + date_of_birth_3i: Day role: Role time_zone: Time Zone user_role: @@ -366,6 +385,10 @@ en-GB: name: Name permissions_as_keys: Permissions position: Priority + username_block: + allow_with_approval: Allow registrations with approval + comparison: Method of comparison + username: Word to match webhook: events: Enabled events template: Payload template diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index 0ab94b5a753467..74550782036e82 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -54,8 +54,10 @@ en: password: Use at least 8 characters phrase: Will be matched regardless of casing in text or content warning of a post scopes: Which APIs the application will be allowed to access. If you select a top-level scope, you don't need to select individual ones. + setting_advanced_layout: Display Mastodon as a multi-column layout, allowing you to view the timeline, notifications, and a third column of your choosing. Not recommended for smaller screens. setting_aggregate_reblogs: Do not show new boosts for posts that have been recently boosted (only affects newly-received boosts) setting_always_send_emails: Normally e-mail notifications won't be sent when you are actively using Mastodon + setting_boost_modal: When enabled, boosting will first open a confirmation dialog in which you can change the visibility of your boost. setting_default_quote_policy_private: Followers-only posts authored on Mastodon can't be quoted by others. setting_default_quote_policy_unlisted: When people quote you, their post will also be hidden from trending timelines. setting_default_sensitive: Sensitive media is hidden by default and can be revealed with a click @@ -63,6 +65,7 @@ en: setting_display_media_hide_all: Always hide media setting_display_media_show_all: Always show media setting_emoji_style: How to display emojis. "Auto" will try using native emoji, but falls back to Twemoji for legacy browsers. + setting_quick_boosting_html: When enabled, clicking on the %{boost_icon} Boost icon will immediately boost instead of opening the boost/quote dropdown menu. Relocates the quoting action to the %{options_icon} (Options) menu. setting_system_scrollbars_ui: Applies only to desktop browsers based on Safari and Chrome setting_use_blurhash: Gradients are based on the colors of the hidden visuals but obfuscate any details setting_use_pending_items: Hide timeline updates behind a click instead of automatically scrolling the feed @@ -76,7 +79,7 @@ en: featured_tag: name: 'Here are some of the hashtags you used the most recently:' filters: - action: Chose which action to perform when a post matches the filter + action: Choose which action to perform when a post matches the filter actions: blur: Hide media behind a warning, without hiding the text itself hide: Completely hide the filtered content, behaving as if it did not exist @@ -85,11 +88,12 @@ en: activity_api_enabled: Counts of locally published posts, active users, and new registrations in weekly buckets app_icon: WEBP, PNG, GIF or JPG. Overrides the default app icon on mobile devices with a custom icon. backups_retention_period: Users have the ability to generate archives of their posts to download later. When set to a positive value, these archives will be automatically deleted from your storage after the specified number of days. - bootstrap_timeline_accounts: These accounts will be pinned to the top of new users' follow recommendations. + bootstrap_timeline_accounts: These accounts will be pinned to the top of new users' follow recommendations. Provide a comma-separated list of accounts. closed_registrations_message: Displayed when sign-ups are closed content_cache_retention_period: All posts from other servers (including boosts and replies) will be deleted after the specified number of days, without regard to any local user interaction with those posts. This includes posts where a local user has marked it as bookmarks or favorites. Private mentions between users from different instances will also be lost and impossible to restore. Use of this setting is intended for special purpose instances and breaks many user expectations when implemented for general purpose use. custom_css: You can apply custom styles on the web version of Mastodon. favicon: WEBP, PNG, GIF or JPG. Overrides the default Mastodon favicon with a custom icon. + landing_page: Selects what page new visitors see when they first arrive on your server. If you select "Trends", then trends needs to be enabled in the Discovery Settings. If you select "Local feed", then "Access to live feeds featuring local posts" needs to be set to "Everyone" in the Discovery Settings. mascot: Overrides the illustration in the advanced web interface. media_cache_retention_period: Media files from posts made by remote users are cached on your server. When set to a positive value, media will be deleted after the specified number of days. If the media data is requested after it is deleted, it will be re-downloaded, if the source content is still available. Due to restrictions on how often link preview cards poll third-party sites, it is recommended to set this value to at least 14 days, or link preview cards will not be updated on demand before that time. min_age: Users will be asked to confirm their date of birth during sign-up @@ -105,10 +109,9 @@ en: status_page_url: URL of a page where people can see the status of this server during an outage theme: Theme that logged out visitors and new users see. thumbnail: A roughly 2:1 image displayed alongside your server information. - timeline_preview: Logged out visitors will be able to browse the most recent public posts available on the server. trendable_by_default: Skip manual review of trending content. Individual items can still be removed from trends after the fact. trends: Trends show which posts, hashtags and news stories are gaining traction on your server. - trends_as_landing_page: Show trending content to logged-out users and visitors instead of a description of this server. Requires trends to be enabled. + wrapstodon: Offer local users to generate a playful summary of their Mastodon use during the year. This feature is available between the 10th and 31st of December of each year, and is offered to users who made at least one Public or Quiet Public post and used at least one hashtag within the year. form_challenge: current_password: You are entering a secure area imports: @@ -235,12 +238,12 @@ en: setting_aggregate_reblogs: Group boosts in timelines setting_always_send_emails: Always send e-mail notifications setting_auto_play_gif: Auto-play animated GIFs - setting_boost_modal: Show confirmation dialog before boosting + setting_boost_modal: Control boosting visibility setting_default_language: Posting language setting_default_privacy: Posting visibility setting_default_quote_policy: Who can quote setting_default_sensitive: Always mark media as sensitive - setting_delete_modal: Show confirmation dialog before deleting a post + setting_delete_modal: Warn me before deleting a post setting_disable_hover_cards: Disable profile preview on hover setting_disable_swiping: Disable swiping motions setting_display_media: Media display @@ -250,7 +253,8 @@ en: setting_emoji_style: Emoji style setting_expand_spoilers: Always expand posts marked with content warnings setting_hide_network: Hide your social graph - setting_missing_alt_text_modal: Show confirmation dialog before posting media without alt text + setting_missing_alt_text_modal: Warn me before posting media without alt text + setting_quick_boosting: Enable quick boosting setting_reduce_motion: Reduce motion in animations setting_system_font_ui: Use system's default font setting_system_scrollbars_ui: Use system's default scrollbar @@ -284,12 +288,17 @@ en: content_cache_retention_period: Remote content retention period custom_css: Custom CSS favicon: Favicon + landing_page: Landing page for new visitors + local_live_feed_access: Access to live feeds featuring local posts + local_topic_feed_access: Access to hashtag and link feeds featuring local posts mascot: Custom mascot (legacy) media_cache_retention_period: Media cache retention period min_age: Minimum age requirement peers_api_enabled: Publish list of discovered servers in the API profile_directory: Enable profile directory registrations_mode: Who can sign-up + remote_live_feed_access: Access to live feeds featuring remote posts + remote_topic_feed_access: Access to hashtag and link feeds featuring remote posts require_invite_text: Require a reason to join show_domain_blocks: Show domain blocks show_domain_blocks_rationale: Show why domains were blocked @@ -302,10 +311,9 @@ en: status_page_url: Status page URL theme: Default theme thumbnail: Server thumbnail - timeline_preview: Allow unauthenticated access to public timelines trendable_by_default: Allow trends without prior review trends: Enable trends - trends_as_landing_page: Use trends as the landing page + wrapstodon: Enable Wrapstodon interactions: must_be_follower: Block notifications from non-followers must_be_following: Block notifications from people you don't follow @@ -366,9 +374,9 @@ en: jurisdiction: Legal jurisdiction min_age: Minimum age user: - date_of_birth_1i: Day + date_of_birth_1i: Year date_of_birth_2i: Month - date_of_birth_3i: Year + date_of_birth_3i: Day role: Role time_zone: Time zone user_role: diff --git a/config/locales/simple_form.eo.yml b/config/locales/simple_form.eo.yml index 4e2ff0bf0af9f1..aad88989241524 100644 --- a/config/locales/simple_form.eo.yml +++ b/config/locales/simple_form.eo.yml @@ -49,7 +49,7 @@ eo: email: Vi ricevos konfirman retpoŝton header: WEBP, PNG, GIF aŭ JPG. Maksimume %{size}. Malgrandiĝos al %{dimensions}px inbox_url: Kopiu la URL de la ĉefpaĝo de la ripetilo, kiun vi volas uzi - irreversible: La filtritaj mesaĝoj malaperos por eterne, eĉ se la filtrilo poste estas forigita + irreversible: La filtritaj mesaĝoj malaperos por ĉiam, eĉ se la filtrilo poste estas forigita locale: La lingvo de la fasado, retpoŝtaĵoj, kaj sciigoj password: Uzu almenaŭ 8 signojn phrase: Estos provita senzorge pri la uskleco de teksto aŭ averto pri enhavo de mesaĝo @@ -76,7 +76,6 @@ eo: featured_tag: name: 'Jen kelkaj el la kradvortoj, kiujn vi uzis lastatempe:' filters: - action: Elekti ago kiam mesaĝo kongruas la filtrilon actions: blur: Kaŝi amaskomunikilaron malantaŭ averto, sen kaŝi la tekston mem hide: Tute kaŝigi la filtritajn enhavojn, kvazau ĝi ne ekzistis @@ -85,7 +84,6 @@ eo: activity_api_enabled: Nombroj de loke publikigitaj afiŝoj, aktivaj uzantoj kaj novaj registradoj en semajnaj siteloj app_icon: WEBP, PNG, GIF aŭ JPG. Anstataŭigas la defaŭltan aplikaĵan bildsimbolon sur porteblaj aparatoj kun propra bildsimbolo. backups_retention_period: Uzantoj havas la kapablon generi arkivojn de siaj afiŝoj por elŝuti poste. Kiam estas agordita al pozitiva valoro, ĉi tiuj arkivoj estos aŭtomate forigitaj de via stokado post la specifita nombro da tagoj. - bootstrap_timeline_accounts: Ĉi tiuj kontoj pinglitas al la supro de sekvorekomendoj de novaj uzantoj. closed_registrations_message: Montrita kiam registroj fermitas content_cache_retention_period: Ĉiuj afiŝoj de aliaj serviloj (inkluzive de diskonigoj kaj respondoj) estos forigitaj post la specifita nombro da tagoj, sen konsidero al iu ajn loka uzantinterago kun tiuj afiŝoj. Ĉi tio inkluzivas afiŝojn, kie loka uzanto markis ĝin kiel legosignojn aŭ ŝatatajn. Privataj mencioj inter uzantoj de malsamaj nodoj ankaŭ estos perditaj kaj neeble restaŭreblaj. Uzo de ĉi tiu agordo estas celita por specialcelaj okazoj kaj rompas multajn uzantajn atendojn kiam efektivigita por ĝenerala uzo. custom_css: Vi povas meti propajn stilojn en la retversio de Mastodon. @@ -105,10 +103,8 @@ eo: status_page_url: URL de paĝo kie homoj povas vidi la staton de ĉi tiu servilo dum malfunkcio theme: Etoso kiun elsalutitaj vizitantoj kaj novaj uzantoj vidas. thumbnail: Ĉirkaua 2:1 bildo montritas kun via servilinformo. - timeline_preview: Elsalutitaj vizitantoj povos vidi la plej lastajn publikajn mesaĝojn disponeblaj en la servilo. trendable_by_default: Ignori permanan kontrolon de tendenca enhavo. trends: Tendencoj montras kiu mesaĝoj, kradvortoj kaj novaĵoj populariĝas en via servilo. - trends_as_landing_page: Montru tendencan enhavon al elsalutitaj uzantoj kaj vizitantoj anstataŭ priskribo de ĉi tiu servilo. Necesas ke tendencoj estu ebligitaj. form_challenge: current_password: Vi eniras sekuran areon imports: @@ -231,12 +227,10 @@ eo: setting_aggregate_reblogs: Grupigi diskonigojn en templinioj setting_always_send_emails: Ĉiam sendi la sciigojn per retpoŝto setting_auto_play_gif: Aŭtomate ekigi GIF-ojn - setting_boost_modal: Montri konfirman fenestron antaŭ ol diskonigi mesaĝon setting_default_language: Publikada lingvo setting_default_privacy: Afiŝa videblo setting_default_quote_policy: Kiu povas citi setting_default_sensitive: Ĉiam marki la vidaŭdaĵojn kiel tiklaj - setting_delete_modal: Montri konfirman fenestron antaŭ ol forigi mesaĝon setting_disable_hover_cards: Malebligi profilan antaŭmontron kiam oni musumas setting_disable_swiping: Malebligi svingajn movojn setting_display_media: Vidigo de vidaŭdaĵoj @@ -246,7 +240,6 @@ eo: setting_emoji_style: Emoĝio-stilo setting_expand_spoilers: Ĉiam malfoldas mesaĝojn markitajn per averto pri enhavo setting_hide_network: Kaŝi viajn sekvantojn kaj sekvatojn - setting_missing_alt_text_modal: Montru konfirman dialogon antaŭ afiŝado de aŭdvidaĵoj sen altteksto setting_reduce_motion: Redukti la movecojn de la animacioj setting_system_font_ui: Uzi la dekomencan tiparon de la sistemo setting_system_scrollbars_ui: Uzu la defaŭltan rulumilon de la sistemo @@ -298,10 +291,8 @@ eo: status_page_url: URL de la paĝo de stato theme: Implicita etoso thumbnail: Bildeto de servilo - timeline_preview: Permesi la neaŭtentigitan aliron al la publikaj templinioj trendable_by_default: Permesi tendencojn sen deviga kontrolo trends: Ŝalti furorojn - trends_as_landing_page: Uzu tendencojn kiel la landpaĝon interactions: must_be_follower: Bloki sciigojn de nesekvantoj must_be_following: Bloki sciigojn de homoj, kiujn vi ne sekvas @@ -362,9 +353,9 @@ eo: jurisdiction: Laŭleĝa jurisdikcio min_age: Minimuma aĝo user: - date_of_birth_1i: Tago + date_of_birth_1i: Jaro date_of_birth_2i: Monato - date_of_birth_3i: Jaro + date_of_birth_3i: Tago role: Rolo time_zone: Horzono user_role: diff --git a/config/locales/simple_form.es-AR.yml b/config/locales/simple_form.es-AR.yml index cf697ea64c3cd6..971ff038c48dc9 100644 --- a/config/locales/simple_form.es-AR.yml +++ b/config/locales/simple_form.es-AR.yml @@ -54,8 +54,10 @@ es-AR: password: Usá al menos 8 caracteres phrase: Se aplicará sin importar las mayúsculas o las advertencias de contenido de un mensaje scopes: Qué APIs de la aplicación tendrán acceso. Si seleccionás el alcance de nivel más alto, no necesitás seleccionar las individuales. + setting_advanced_layout: Mostrar Mastodon como una disposición de varias columnas, permitiéndote ver la línea temporal, las notificaciones y una tercera columna de tu elección. No recomendado para pantallas pequeñas. setting_aggregate_reblogs: No mostrar nuevas adhesiones de los mensajes que fueron recientemente adheridos (sólo afecta a las adhesiones recibidas recientemente) setting_always_send_emails: Normalmente las notificaciones por correo electrónico no se enviarán cuando estés usando Mastodon activamente + setting_boost_modal: Al estar activado, la adhesión abrirá primero un diálogo de confirmación en el que podés cambiar su visibilidad. setting_default_quote_policy_private: Los mensajes solo para seguidores redactados en Mastodon no pueden ser citados por otras cuentas. setting_default_quote_policy_unlisted: Cuando otras cuentas te citen, sus publicaciones también se ocultarán de las líneas temporales de tendencias. setting_default_sensitive: El contenido de medios sensibles está oculto predeterminadamente y puede ser mostrado con un clic @@ -63,6 +65,7 @@ es-AR: setting_display_media_hide_all: Siempre ocultar todos los medios setting_display_media_show_all: Siempre mostrar todos los medios setting_emoji_style: Cómo se mostrarán los emojis. "Automático" intentará usar emojis nativos, cambiando a Twemoji en navegadores antiguos. + setting_quick_boosting_html: Al estar habilitado, haciendo clic en el ícono de adhesión %{boost_icon} vas a adherir al mensaje inmediatamente, en lugar de abrir el menú desplegable de adhesión/citas. Esto cambia la acción de citas al menú de opciones %{options_icon}. setting_system_scrollbars_ui: Solo aplica para navegadores web de escritorio basados en Safari y Chrome setting_use_blurhash: Los gradientes se basan en los colores de las imágenes ocultas pero haciendo borrosos los detalles setting_use_pending_items: Ocultar actualizaciones de la línea temporal detrás de un clic en lugar de desplazar automáticamente el flujo @@ -85,11 +88,12 @@ es-AR: activity_api_enabled: Conteos de mensajes publicados localmente, cuentas activas y nuevos registros en tandas semanales app_icon: WEBP, PNG, GIF o JPG. Reemplaza el ícono de aplicación predeterminado en dispositivos móviles con uno personalizado. backups_retention_period: Los usuarios tienen la capacidad de generar archivos historiales de sus mensajes para descargar más adelante. Cuando se establece un valor positivo, estos archivos se eliminarán automáticamente de su almacenamiento después del número especificado de días. - bootstrap_timeline_accounts: Estas cuentas serán fijadas a la parte superior de las recomendaciones de cuentas a seguir para nuevos usuarios. + bootstrap_timeline_accounts: Estas cuentas se fijarán en la parte superior de las recomendaciones de seguimiento para los nuevos usuarios. Proporcioná una lista de cuentas separadas por comas. closed_registrations_message: Mostrado cuando los registros están cerrados content_cache_retention_period: Todos los mensajes de otros servidores (incluyendo adhesiones y respuestas) se eliminarán después del número de días especificado, sin tener en cuenta la interacción del usuario local con esos mensajes. Esto incluye mensajes que un usuario local haya agregado a marcadores o los haya marcado como favoritos. Las menciones privadas entre usuarios de diferentes servidores también se perderán y también serán imposibles de restaurar. El uso de esta configuración está destinado a servidores de propósito especial y rompe muchas expectativas de los usuarios cuando se implementa para uso general. custom_css: Podés aplicar estilos personalizados a la versión web de Mastodon. favicon: WEBP, PNG, GIF o JPG. Reemplaza el favicón predeterminado de Mastodon con uno personalizado. + landing_page: Selecciona qué página se carga para los nuevos visitantes cuando llegan por primera vez a tu servidor. Si seleccionás «Tendencias», entonces las tendencias tienen que estar habilitadas en la configuración de «Descubrimiento». Si seleccionás «Línea temporal local», entonces «Acceso a líneas temporales en vivo, destacando mensajes locales» tiene que estar establecida a «Todos» en la configuración de «Descubrimiento». mascot: Reemplaza la ilustración en la interface web avanzada. media_cache_retention_period: Los archivos de medios de mensajes publicados por usuarios remotos se almacenan en la memoria caché en tu servidor. Cuando se establece un valor positivo, los medios se eliminarán después del número especificado de días. Si los datos multimedia se solicitan después de eliminarse, se volverán a descargar, si es que el contenido fuente todavía está disponible. Debido a restricciones en la frecuencia con la que las tarjetas de previsualización de enlace consultan a sitios web de terceros, se recomienda establecer este valor a, al menos, 14 días, o las tarjetas de previsualización de enlaces no se actualizarán a pedido antes de ese momento. min_age: Se pedirá a los usuarios que confirmen su fecha de nacimiento durante el registro @@ -105,10 +109,9 @@ es-AR: status_page_url: Dirección web de una página donde la gente puede ver el estado de este servidor durante un apagón theme: El tema que los visitantes no registrados y los nuevos usuarios ven. thumbnail: Una imagen de aproximadamente 2:1 se muestra junto a la información de tu servidor. - timeline_preview: Los visitantes no registrados podrán navegar por los mensajes públicos más recientes disponibles en el servidor. trendable_by_default: Omití la revisión manual del contenido en tendencia. Los elementos individuales aún podrán eliminarse de las tendencias. trends: Las tendencias muestran qué mensajes, etiquetas y noticias están ganando tracción en tu servidor. - trends_as_landing_page: Mostrar contenido en tendencia para usuarios que no iniciaron sesión y visitantes, en lugar de una descripción de este servidor. Requiere que las tendencias estén habilitadas. + wrapstodon: Ofrecer a los usuarios locales un resumen lúdico de su uso en Mastodon durante el año. Esta función está disponible entre los días 10 y 31 de diciembre de cada año, y se ofrece a los usuarios que enviaron a Mastodon, al menos, un mensaje público o un mensaje público pero silencioso, y que utilizaron, al menos, una etiqueta durante el año. form_challenge: current_password: Estás ingresando en un área segura imports: @@ -235,12 +238,12 @@ es-AR: setting_aggregate_reblogs: Agrupar adhesiones en las líneas temporales setting_always_send_emails: Siempre enviar notificaciones por correo electrónico setting_auto_play_gif: Reproducir automáticamente los GIFs animados - setting_boost_modal: Mostrar diálogo de confirmación antes de adherir + setting_boost_modal: Control de visibilidad de adhesiones setting_default_language: Idioma de tus mensajes setting_default_privacy: Visibilidad del mensaje setting_default_quote_policy: Quién puede citar setting_default_sensitive: Siempre marcar medios como sensibles - setting_delete_modal: Mostrar diálogo de confirmación antes de eliminar un mensaje + setting_delete_modal: Advertirme antes de eliminar un mensaje setting_disable_hover_cards: Deshabilitar previsualización del perfil al pasar el cursor setting_disable_swiping: Deshabilitar movimientos de deslizamiento setting_display_media: Visualización de medios @@ -250,7 +253,8 @@ es-AR: setting_emoji_style: Estilo de emoji setting_expand_spoilers: Siempre expandir los mensajes marcados con advertencias de contenido setting_hide_network: Ocultá tu gráfica social - setting_missing_alt_text_modal: Mostrar diálogo de confirmación antes de enviar medios sin texto alternativo + setting_missing_alt_text_modal: Advertirme antes de enviar medios sin texto alternativo + setting_quick_boosting: Habilitar adhesión rápida setting_reduce_motion: Reducir el movimiento de las animaciones setting_system_font_ui: Utilizar la tipografía predeterminada del sistema setting_system_scrollbars_ui: Usar la barra de desplazamiento predeterminada del sistema operativo @@ -284,12 +288,17 @@ es-AR: content_cache_retention_period: Período de retención de contenido remoto custom_css: CSS personalizado favicon: Favicón + landing_page: Página de carga para nuevos visitantes + local_live_feed_access: Acceso a líneas temporales en vivo, destacando mensajes locales + local_topic_feed_access: Acceso a líneas temporales de etiquetas y enlaces, destacando mensajes locales mascot: Mascota personalizada (legado) media_cache_retention_period: Período de retención de la caché de medios min_age: Edad mínima requerida peers_api_enabled: Publicar lista de servidores descubiertos en la API profile_directory: Habilitar directorio de perfiles registrations_mode: Quién puede registrarse + remote_live_feed_access: Acceso a líneas temporales en vivo, destacando mensajes remotos + remote_topic_feed_access: Acceso a líneas temporales de etiquetas y enlaces, destacando mensajes remotos require_invite_text: Requerir un motivo para unirse show_domain_blocks: Mostrar dominios bloqueados show_domain_blocks_rationale: Mostrar por qué se bloquearon los dominios @@ -302,10 +311,9 @@ es-AR: status_page_url: Dirección web de la página de estado theme: Tema predeterminado thumbnail: Miniatura del servidor - timeline_preview: Permitir el acceso no autenticado a las líneas temporales públicas trendable_by_default: Permitir tendencias sin revisión previa trends: Habilitar tendencias - trends_as_landing_page: Usar las tendencias como la página de destino + wrapstodon: Habilitar MastodonAnual interactions: must_be_follower: Bloquear notificaciones de cuentas que no te siguen must_be_following: Bloquear notificaciones de cuentas que no seguís @@ -366,9 +374,9 @@ es-AR: jurisdiction: Jurisdicción legal min_age: Edad mínima user: - date_of_birth_1i: Día + date_of_birth_1i: Año date_of_birth_2i: Mes - date_of_birth_3i: Año + date_of_birth_3i: Día role: Rol time_zone: Zona horaria user_role: diff --git a/config/locales/simple_form.es-MX.yml b/config/locales/simple_form.es-MX.yml index ceaa3fcf9feb5f..cfcbc77f3c4938 100644 --- a/config/locales/simple_form.es-MX.yml +++ b/config/locales/simple_form.es-MX.yml @@ -54,8 +54,10 @@ es-MX: password: Usa al menos 8 caracteres phrase: Se aplicará sin importar las mayúsculas o los avisos de contenido de una publicación scopes: Qué APIs de la aplicación tendrán acceso. Si seleccionas el alcance de nivel mas alto, no necesitas seleccionar las individuales. + setting_advanced_layout: Mostrar Mastodon en un diseño de varias columnas, lo que te permite ver la cronología, las notificaciones y una tercera columna de tu elección. No se recomienda para pantallas pequeñas. setting_aggregate_reblogs: No mostrar nuevos impulsos para las publicaciones que han sido recientemente impulsadas (sólo afecta a las publicaciones recibidas recientemente) setting_always_send_emails: Normalmente las notificaciones por correo electrónico no se enviarán cuando estés usando Mastodon activamente + setting_boost_modal: Cuando está habilitado, impulsar abrirá primero un cuadro de confirmación en el que podrás cambiar la visibilidad de tu impulso. setting_default_quote_policy_private: Las publicaciones solo para seguidores hechas en Mastodon no pueden ser citadas por otros usuarios. setting_default_quote_policy_unlisted: Cuando las personas te citen, su publicación también se ocultará en las cronologías públicas. setting_default_sensitive: El contenido multimedia sensible está oculto por defecto y puede ser mostrado con un clic @@ -63,6 +65,7 @@ es-MX: setting_display_media_hide_all: Siempre ocultar todo el contenido multimedia setting_display_media_show_all: Mostrar siempre contenido multimedia marcado como sensible setting_emoji_style: Cómo se muestran los emojis. «Automático» intentará usar emojis nativos, pero vuelve a Twemoji para los navegadores antiguos. + setting_quick_boosting_html: Cuando está activado, pulsar en el icono %{boost_icon} Impulsar impulsará inmediatamente en lugar de abrir el menú desplegable Impulsar/Citas. Mueve la acción de citar al menú %{options_icon} (Opciones). setting_system_scrollbars_ui: Solo se aplica a los navegadores de escritorio basados en Safari y Chrome setting_use_blurhash: Los degradados se basan en los colores de los elementos visuales ocultos, pero ocultan cualquier detalle setting_use_pending_items: Ocultar las publicaciones de la línea de tiempo tras un clic en lugar de desplazar automáticamente el feed @@ -76,7 +79,7 @@ es-MX: featured_tag: name: 'Aquí están algunas de las etiquetas que más has usado recientemente:' filters: - action: Elegir qué acción realizar cuando una publicación coincide con el filtro + action: Elige qué acción realizar cuando una publicación coincida con el filtro actions: blur: Ocultar contenido multimedia detrás de una advertencia, sin ocultar el propio texto hide: Ocultar completamente el contenido filtrado, comportándose como si no existiera @@ -85,11 +88,12 @@ es-MX: activity_api_enabled: Conteo de publicaciones publicadas localmente, usuarios activos, y nuevos registros en periodos semanales app_icon: WEBP, PNG, GIF o JPG. Reemplaza el icono de aplicación predeterminado en dispositivos móviles con un icono personalizado. backups_retention_period: Los usuarios tienen la posibilidad de generar archivos de sus mensajes para descargarlos más adelante. Cuando se establece en un valor positivo, estos archivos se eliminarán automáticamente del almacenamiento después del número especificado de días. - bootstrap_timeline_accounts: Estas cuentas aparecerán en la parte superior de las recomendaciones de los nuevos usuarios. + bootstrap_timeline_accounts: Estas cuentas aparecerán en la parte superior de las recomendaciones de seguimiento de los nuevos usuarios. Proporciona una lista de cuentas separadas por comas. closed_registrations_message: Mostrado cuando los registros están cerrados content_cache_retention_period: Todas las publicaciones de otros servidores (incluyendo impuestos y respuestas) serán borrados después del número de días especificado, sin tener en cuenta cualquier interacción del usuario local con esas publicaciones. Esto incluye los mensajes que un usuario local haya marcado como favoritos. Las menciones privadas entre usuarios de diferentes instancias también se perderán y será imposible restaurarlas. El uso de esta configuración está pensado para instancias de propósito especial y rompe muchas expectativas de los usuarios cuando se implementa para uso general. custom_css: Puedes aplicar estilos personalizados a la versión web de Mastodon. favicon: WEBP, PNG, GIF o JPG. Reemplaza el icono predeterminado de Mastodon con un icono personalizado. + landing_page: Selecciona qué página ven los nuevos visitantes cuando llegan por primera vez a tu servidor. Si seleccionas "Tendencias", entonces las tendencias deben estar habilitadas en la Configuración de Descubrimiento. Si selecciona "Cronología local", entonces "Acceso a las cronologías que destacan publicaciones locales" debe configurarse a "Todos" en la Configuración de Descubrimiento. mascot: Reemplaza la ilustración en la interfaz web avanzada. media_cache_retention_period: Los archivos multimedia de las publicaciones realizadas por usuarios remotos se almacenan en caché en su servidor. Si se establece en un valor positivo, los archivos multimedia se eliminarán tras el número de días especificado. Si los datos multimedia se solicitan después de haber sido eliminados, se volverán a descargar, si el contenido de origen sigue estando disponible. Debido a las restricciones sobre la frecuencia con la que las tarjetas de previsualización de enlaces sondean sitios de terceros, se recomienda establecer este valor en al menos 14 días, o las tarjetas de previsualización de enlaces no se actualizarán bajo demanda antes de ese tiempo. min_age: Se pedirá a los usuarios que confirmen su fecha de nacimiento al registrarse @@ -105,10 +109,9 @@ es-MX: status_page_url: URL de una página donde las personas pueden ver el estado de este servidor durante una interrupción theme: El tema que los visitantes no registrados y los nuevos usuarios ven. thumbnail: Una imagen de aproximadamente 2:1 se muestra junto a la información de tu servidor. - timeline_preview: Los visitantes no registrados podrán navegar por los mensajes públicos más recientes disponibles en el servidor. trendable_by_default: Omitir la revisión manual del contenido en tendencia. Los elementos individuales aún podrán eliminarse de las tendencias. trends: Las tendencias muestran qué mensajes, etiquetas y noticias están ganando tracción en tu servidor. - trends_as_landing_page: Mostrar contenido en tendencia para usuarios y visitantes desconectados en lugar de una descripción de este servidor. Requiere tendencias para ser habilitado. + wrapstodon: Ofrece a los usuarios locales la posibilidad de generar un resumen divertido de su uso de Mastodon durante el año. Esta función está disponible entre el 10 y el 31 de diciembre de cada año, y se ofrece a los usuarios que hayan publicado al menos una publicación pública o pública silenciosa y hayan utilizado al menos una etiqueta durante el año. form_challenge: current_password: Estás entrando en un área segura imports: @@ -235,12 +238,12 @@ es-MX: setting_aggregate_reblogs: Agrupar impulsos en las líneas de tiempo setting_always_send_emails: Enviar siempre notificaciones por correo setting_auto_play_gif: Reproducir automáticamente los GIFs animados - setting_boost_modal: Mostrar ventana de confirmación antes de impulsar + setting_boost_modal: Control de visibilidad de impulsos setting_default_language: Idioma de publicación setting_default_privacy: Visibilidad de publicación setting_default_quote_policy: Quién puede citar setting_default_sensitive: Marcar siempre imágenes como sensibles - setting_delete_modal: Mostrar diálogo de confirmación antes de borrar una publicación + setting_delete_modal: Avisarme antes de eliminar una publicación setting_disable_hover_cards: Desactivar vista previa del perfil al pasar el cursor setting_disable_swiping: Deshabilitar movimientos de deslizamiento setting_display_media: Visualización multimedia @@ -250,7 +253,8 @@ es-MX: setting_emoji_style: Estilo de emoji setting_expand_spoilers: Siempre expandir las publicaciones marcadas con advertencias de contenido setting_hide_network: Ocultar tu red - setting_missing_alt_text_modal: Mostrar cuadro de diálogo de confirmación antes de publicar contenido multimedia sin texto alternativo + setting_missing_alt_text_modal: Avisarme antes de publicar contenido multimedia sin descripción de texto + setting_quick_boosting: Habilitar impulso rápido setting_reduce_motion: Reducir el movimiento de las animaciones setting_system_font_ui: Usar la fuente por defecto del sistema setting_system_scrollbars_ui: Usar la barra de desplazamiento por defecto del sistema @@ -284,12 +288,17 @@ es-MX: content_cache_retention_period: Periodo de conservación de contenidos remotos custom_css: CSS personalizado favicon: Favicon + landing_page: Página de inicio para nuevos visitantes + local_live_feed_access: Acceso a las cronologías que destacan publicaciones locales + local_topic_feed_access: Acceso a las etiquetas y enlaces en tendencia que destacan publicaciones locales mascot: Mascota personalizada (legado) media_cache_retention_period: Período de retención de caché multimedia min_age: Edad mínima requerida peers_api_enabled: Publicar lista de servidores descubiertos en la API profile_directory: Habilitar directorio de perfiles registrations_mode: Quién puede registrarse + remote_live_feed_access: Acceso a las cronologías que destacan publicaciones remotas + remote_topic_feed_access: Acceso a las etiquetas y enlaces en tendencia que destacan publicaciones remotas require_invite_text: Requerir una razón para unirse show_domain_blocks: Mostrar dominios bloqueados show_domain_blocks_rationale: Mostrar por qué se bloquearon los dominios @@ -302,10 +311,9 @@ es-MX: status_page_url: URL de página de estado theme: Tema por defecto thumbnail: Miniatura del servidor - timeline_preview: Permitir el acceso no autenticado a las líneas de tiempo públicas trendable_by_default: Permitir tendencias sin revisión previa trends: Habilitar tendencias - trends_as_landing_page: Usar tendencias como página de destino + wrapstodon: Habilitar Wrapstodon interactions: must_be_follower: Bloquear notificaciones de personas que no te siguen must_be_following: Bloquear notificaciones de personas que no sigues @@ -366,9 +374,9 @@ es-MX: jurisdiction: Jurisdicción legal min_age: Edad mínima user: - date_of_birth_1i: Día + date_of_birth_1i: Año date_of_birth_2i: Mes - date_of_birth_3i: Año + date_of_birth_3i: Día role: Rol time_zone: Zona horaria user_role: diff --git a/config/locales/simple_form.es.yml b/config/locales/simple_form.es.yml index ec7ba3e30c37be..b56fbff243ae13 100644 --- a/config/locales/simple_form.es.yml +++ b/config/locales/simple_form.es.yml @@ -54,8 +54,10 @@ es: password: Utiliza al menos 8 caracteres phrase: Se aplicará sin importar las mayúsculas o los avisos de contenido de una publicación scopes: Qué APIs de la aplicación tendrán acceso. Si seleccionas el alcance de nivel mas alto, no necesitas seleccionar las individuales. + setting_advanced_layout: Mostrar Mastodon con vista de varias columnas, permitiéndote ver tu cronología, notificaciones y una tercera columna que tú elijas. No recomendado para pantallas pequeñas. setting_aggregate_reblogs: No mostrar nuevos impulsos para las publicaciones que han sido recientemente impulsadas (sólo afecta a los impulsos recibidos recientemente) setting_always_send_emails: Normalmente las notificaciones por correo electrónico no se enviarán cuando estés usando Mastodon activamente + setting_boost_modal: Si está activado, impulsar una publicación abrirá una ventana donde podrás cambiar la visibilidad de tu impulso. setting_default_quote_policy_private: Las publicaciones solo para seguidores hechas en Mastodon no pueden ser citadas por otros usuarios. setting_default_quote_policy_unlisted: Cuando las personas te citen, su publicación también se ocultará en las cronologías públicas. setting_default_sensitive: El contenido multimedia sensible está oculto por defecto y puede ser mostrado con un click @@ -63,6 +65,7 @@ es: setting_display_media_hide_all: Siempre ocultar todo el contenido multimedia setting_display_media_show_all: Mostrar siempre contenido multimedia marcado como sensible setting_emoji_style: Cómo se mostrarán los emojis. "Auto" intentará usar emojis nativos, cambiando a Twemoji en navegadores antiguos. + setting_quick_boosting_html: Cuando está activado, pulsar en el icono %{boost_icon} Impulsar impulsará inmediatamente en lugar de abrir el menú desplegable Impulsar/Citas. Mueve la acción de citar al menú %{options_icon} (Opciones). setting_system_scrollbars_ui: Solo aplica para navegadores de escritorio basados en Safari y Chrome setting_use_blurhash: Los gradientes se basan en los colores de las imágenes ocultas pero haciendo borrosos los detalles setting_use_pending_items: Ocultar nuevas publicaciones detrás de un clic en lugar de desplazar automáticamente el feed @@ -76,7 +79,7 @@ es: featured_tag: name: 'Aquí están algunas de las etiquetas que más has utilizado recientemente:' filters: - action: Elegir qué acción realizar cuando una publicación coincide con el filtro + action: Elige qué acción realizar cuando una publicación coincide con el filtro actions: blur: Ocultar contenido multimedia detrás de una advertencia, sin ocultar el texto en sí hide: Ocultar completamente el contenido filtrado, comportándose como si no existiera @@ -85,11 +88,12 @@ es: activity_api_enabled: Conteo de publicaciones publicadas localmente, usuarios activos y registros nuevos cada semana app_icon: WEBP, PNG, GIF o JPG. Reemplaza el icono de aplicación predeterminado en dispositivos móviles con un icono personalizado. backups_retention_period: Los usuarios tienen la capacidad de generar archivos de sus mensajes para descargar más adelante. Cuando se establece un valor positivo, estos archivos se eliminarán automáticamente del almacenamiento después del número de días especificado. - bootstrap_timeline_accounts: Estas cuentas aparecerán en la parte superior de las recomendaciones de los nuevos usuarios. + bootstrap_timeline_accounts: Estas cuentas se colocarán en la parte superior de las recomendaciones de cuentas a las que seguir para los nuevos usuarios. Proporciona una lista de cuentas separadas por comas. closed_registrations_message: Mostrado cuando los registros están cerrados content_cache_retention_period: Todas las publicaciones de otros servidores (incluso impulsos y respuestas) se eliminarán después del número de días especificado, sin tener en cuenta la interacción del usuario local con esos mensajes. Esto incluye mensajes donde un usuario local los ha marcado como marcadores o favoritos. Las menciones privadas entre usuarios de diferentes instancias también se perderán sin posibilidad de recuperación. El uso de esta configuración está destinado a instancias de propósito especial, y rompe muchas expectativas de los usuarios cuando se implementa para un uso de propósito general. custom_css: Puedes aplicar estilos personalizados a la versión web de Mastodon. favicon: WEBP, PNG, GIF o JPG. Reemplaza el favicon predeterminado de Mastodon con un icono personalizado. + landing_page: Selecciona qué página ven los nuevos visitantes cuando llegan por primera vez a tu servidor. Si seleccionas "Tendencias", entonces las tendencias deben estar habilitadas en la Configuración de Descubrimiento. Si selecciona "Cronología local", entonces "Acceso a las cronologías que destacan publicaciones locales" debe configurarse a "Todos" en la Configuración de Descubrimiento. mascot: Reemplaza la ilustración en la interfaz web avanzada. media_cache_retention_period: Los archivos multimedia de las publicaciones creadas por usuarios remotos se almacenan en caché en tu servidor. Cuando se establece un valor positivo, estos archivos se eliminarán después del número especificado de días. Si los datos multimedia se solicitan después de eliminarse, se volverán a descargar, si el contenido fuente todavía está disponible. Debido a restricciones en la frecuencia con la que las tarjetas de previsualización de enlaces realizan peticiones a sitios de terceros, se recomienda establecer este valor a al menos 14 días, o las tarjetas de previsualización de enlaces no se actualizarán bajo demanda antes de ese momento. min_age: Se pedirá a los usuarios que confirmen su fecha de nacimiento durante el registro @@ -105,10 +109,8 @@ es: status_page_url: URL de una página donde se pueda ver el estado de este servidor durante una incidencia theme: El tema que los visitantes no registrados y los nuevos usuarios ven. thumbnail: Una imagen de aproximadamente 2:1 se muestra junto a la información de tu servidor. - timeline_preview: Los visitantes no registrados podrán navegar por los mensajes públicos más recientes disponibles en el servidor. trendable_by_default: Omitir la revisión manual del contenido en tendencia. Los elementos individuales aún podrán eliminarse de las tendencias. trends: Las tendencias muestran qué publicaciones, etiquetas y noticias están ganando tracción en tu servidor. - trends_as_landing_page: Mostrar contenido en tendencia para usuarios y visitantes en lugar de una descripción de este servidor. Requiere que las tendencias estén habilitadas. form_challenge: current_password: Estás entrando en un área segura imports: @@ -235,12 +237,12 @@ es: setting_aggregate_reblogs: Agrupar impulsos en las líneas de tiempo setting_always_send_emails: Enviar siempre notificaciones por correo setting_auto_play_gif: Reproducir automáticamente los GIFs animados - setting_boost_modal: Mostrar diálogo de confirmación antes de impulsar una publicación + setting_boost_modal: Control de visibilidad de impulsos setting_default_language: Idioma de publicación setting_default_privacy: Visibilidad de publicación setting_default_quote_policy: Quién puede citar setting_default_sensitive: Marcar siempre imágenes como sensibles - setting_delete_modal: Mostrar diálogo de confirmación antes de borrar una publicación + setting_delete_modal: Avisarme antes de borrar una publicación setting_disable_hover_cards: Desactivar vista previa del perfil al pasar el cursor setting_disable_swiping: Deshabilitar movimientos de deslizamiento setting_display_media: Visualización multimedia @@ -250,7 +252,8 @@ es: setting_emoji_style: Estilo de emoji setting_expand_spoilers: Siempre expandir las publicaciones marcadas con advertencias de contenido setting_hide_network: Ocultar tu red - setting_missing_alt_text_modal: Mostrar diálogo de confirmación antes de publicar medios sin texto alternativo + setting_missing_alt_text_modal: Avisarme antes de publicar multimedia sin descripción de texto + setting_quick_boosting: Habilitar impulso rápido setting_reduce_motion: Reducir el movimiento de las animaciones setting_system_font_ui: Utilizar la tipografía por defecto del sistema setting_system_scrollbars_ui: Utilizar barra de desplazamiento predeterminada del sistema @@ -284,12 +287,17 @@ es: content_cache_retention_period: Período de retención de contenido remoto custom_css: CSS personalizado favicon: Favicon + landing_page: Página de inicio para nuevos visitantes + local_live_feed_access: Acceso a las cronologías que destacan publicaciones locales + local_topic_feed_access: Acceso a las etiquetas y enlaces en tendencia que destacan publicaciones locales mascot: Mascota personalizada (legado) media_cache_retention_period: Período de retención de caché multimedia min_age: Edad mínima requerida peers_api_enabled: Publicar lista de servidores descubiertos en la API profile_directory: Habilitar directorio de perfiles registrations_mode: Quién puede registrarse + remote_live_feed_access: Acceso a las cronologías que destacan publicaciones remotas + remote_topic_feed_access: Acceso a las etiquetas y enlaces en tendencia que destacan publicaciones remotas require_invite_text: Requerir una razón para unirse show_domain_blocks: Mostrar dominios bloqueados show_domain_blocks_rationale: Mostrar por qué se bloquearon los dominios @@ -302,10 +310,8 @@ es: status_page_url: URL de página de estado theme: Tema por defecto thumbnail: Miniatura del servidor - timeline_preview: Permitir el acceso no autenticado a las líneas de tiempo públicas trendable_by_default: Permitir tendencias sin revisión previa trends: Habilitar tendencias - trends_as_landing_page: Usar tendencias como la página de inicio interactions: must_be_follower: Bloquear notificaciones de personas que no te siguen must_be_following: Bloquear notificaciones de personas que no sigues @@ -366,9 +372,9 @@ es: jurisdiction: Jurisdicción legal min_age: Edad mínima user: - date_of_birth_1i: Día + date_of_birth_1i: Año date_of_birth_2i: Mes - date_of_birth_3i: Año + date_of_birth_3i: Día role: Rol time_zone: Zona horaria user_role: diff --git a/config/locales/simple_form.et.yml b/config/locales/simple_form.et.yml index 1f9bf778fad6c9..92efc6dbadf70a 100644 --- a/config/locales/simple_form.et.yml +++ b/config/locales/simple_form.et.yml @@ -8,7 +8,7 @@ et: display_name: Su täisnimi või naljanimi. fields: Su koduleht, sugu, vanus. Mistahes, mida soovid. indexable: Sinu avalikud postitused võivad ilmuda Mastodoni otsingutulemustes. Inimesed, kes on sinu postitustele reageerinud, saavad neid otsida nii või naa. - note: 'Saad @mainida teisi inimesi või #silte.' + note: 'Saad @mainida teisi inimesi või #teemaviiteid.' show_collections: Inimesed saavad sirvida su jälgijaid ja jälgitavaid. Inimesed, keda sa jälgid, näevad seda sõltumata häälestuse valikust. unlocked: Teised kasutajad saavad sind jälgima hakata nõusolekut küsimata. Eemalda märge, kui soovid jälgimistaotlusi üle vaadata ja valida, kas nõustuda või keelduda uute jälgijatega. account_alias: @@ -16,7 +16,7 @@ et: account_migration: acct: Sisesta kasutajanimi@domeen, kuhu soovid konto siit kolida account_warning_preset: - text: Saab kasutada postituse süntaksi, näiteks URLe, silte ja mainimisi + text: Saad kasutada postituse süntaksi, näiteks võrguaadresse, teemaviiteid ja mainimisi title: Valikuline. Ei ole nähtav saajale admin_account_action: include_statuses: Kasutaja näeb, millised postitused on põhjustanud moderaatori otsuse või hoiatuse @@ -54,8 +54,10 @@ et: password: Vajalik on vähemalt 8 märki phrase: Kattub olenemata postituse teksti suurtähtedest või sisuhoiatusest scopes: Milliseid API-sid see rakendus tohib kasutada. Kui valid kõrgeima taseme, ei pea üksikuid eraldi valima. + setting_advanced_layout: Näita Mastodoni mitme veeruga paigutuses, mispuhul näed korraga nii ajajoont, teavitusi, kui sinu valitud kolmandat veergu. Ei sobi kasutamiseks väikeste ekraanide puhul. setting_aggregate_reblogs: Ära kuva uusi postituste jagamisi, mis on hiljuti jagatud (kehtib vaid uutele jagamistele) setting_always_send_emails: Mastodoni aktiivsel kasutamisel sulle tavaliselt meilile teavitusi ei saadeta + setting_boost_modal: Selle eelistuse kasutamisel hooandmise avatakse esmalt kinnitusvaade, kus saad muuta hooandmise nähtavust. setting_default_quote_policy_private: Ainult jälgijatele mõeldud Mastodoni postitusi ei saa teiste poolt tsiteerida. setting_default_quote_policy_unlisted: Kui teised kasutajad sind tsiteerivad, siis nende postitused peidetakse ajajoonelt, mis näitavad populaarsust koguvaid postitusi. setting_default_sensitive: Tundlik meedia on vaikimisi peidetud ning seda saab avada sellele klikkides @@ -63,6 +65,7 @@ et: setting_display_media_hide_all: Alati peida kõik meedia setting_display_media_show_all: Alati näita tundlikuks märgistatud meedia setting_emoji_style: See määrab emojide kuvamise viisi. Automaatse valiku puhul üritatakse kasutada platvormi või klientrakenduse oma emojisid, kuid varuvariandina jääb toimima Twemoji (näiteks vanade veebibrauserite puhul). + setting_quick_boosting_html: Selle eelistuse kasutamisel, Hooandmise ikooni %{boost_icon} teeb toimingu kohe ilma avamata Hooandmise/Tsiteerimise menüüvalikut. Sel puhul tsiteerimise link leidub %{options_icon} (Valikud) menüüs. setting_system_scrollbars_ui: Kehtib vaid Safaril ja Chrome'il põhinevatel tavaarvuti veebibrauserite puhul setting_use_blurhash: Värvid põhinevad peidetud visuaalidel, kuid hägustavad igasuguseid detaile setting_use_pending_items: Voo automaatse kerimise asemel peida ajajoone uuendused kliki taha @@ -74,9 +77,9 @@ et: domain: See võib olla e-postiaadressis näha olev domeen või MX-kirje, mida aadress kasutab. Kontroll toimub liitumise käigus. with_dns_records: Püütakse lahendada selle domeeni DNS-kirjed ja ühtlasi blokeerida ka selle tulemused featured_tag: - name: 'Siin on mõned nendest siltidest, mida oled viimati kasutanud:' + name: 'Siin on mõned nendest teemaviiteid, mida oled viimati kasutanud:' filters: - action: Tegevuse valik, kui postitus vastab filtrile + action: Vali tegevus, kui postitus vastab filtrile actions: blur: Peida meediumifailid hoiatusega, kuid jäta tekst peitmata hide: Filtreeritud sisu täielik peitmine, nagu seda polekski üldse olemas @@ -85,11 +88,12 @@ et: activity_api_enabled: Kohalike postituste, aktiivsete kasutajate ja uute registreerumistr arv nädala kaupa grupeeritult app_icon: WEBP, PNG, GIF või JPG. Asendab mobiilsel seadmel äpi vaikeikooni kohandatud ikooniga. backups_retention_period: Kasutajatel on võimalus genereerida oma postitustest hiljem allalaaditav arhiiv. Kui määrad positiivse arvu, siis kustutatakse need arhiivid serveri andmeruumist määratud arvu päevade järel automaatselt. - bootstrap_timeline_accounts: Need kasutajad kinnitatakse uute kasutajate jälgimissoovituste esiritta. + bootstrap_timeline_accounts: Järgnevad kasutajakontod kinnitatakse vaate ülaossa ning on mõeldud jälgitavate kontode soovituseks uutele kasutajatele. Sisend peab olema komadega eraldatud kasutajakontode loend. closed_registrations_message: Kuvatakse, kui liitumised pole võimalikud content_cache_retention_period: Kõik teiste serverite postitused (sealhulgas jagamised ja vastused) kustutatakse pärast määratud arvu päevade möödumist, sõltumata, kuidas kohalik kasutaja on nende postitustega interakteerunud. Hõlmatud on ka postitused, mille kohalik kasutaja on märkinud järjehoidjaks või lemmikuks. Ka eri instantside kasutajate vahelised privaatsed mainimised kaovad ja neid on võimatu taastada. See seadistus on mõeldud eriotstarbeliste instantside jaoks ja rikub paljude kasutajate ootusi, kui seda rakendatakse üldotstarbelise kasutuse puhul. custom_css: Mastodoni veebiliideses on võimalik kasutada kohandatud stiile. favicon: WEBP, PNG, GIF või JPG. Asendab Mastodoni vaike- favicon ikooni kohandatud ikooniga. + landing_page: Sellega valid lehe, mida uued külastajad sinu serverisse tulles näevad. Kui sa valid „Trendid“, siis peavad nad olema lubatud. Kui sa valid „Kohalike postituste voog“, siis seadistuse „Ligipääs kohalike postituste voole“ väärtus peab olema „Kõik“. mascot: Asendab kohandatud veebiliidese illustratsiooni. media_cache_retention_period: Kaugkasutajate tehtud postituste meediafailid salvestatakse sinu koduserveri vahemällu. Kui see seadistus on seatud positiivsele väärtusele, kustutatakse meediumifailid määratud päevade möödumisel. Kui meediaandmeid küsitakse pärast nende kustutamist, laaditakse need uuesti alla, kui lähtesisu on veel saadaval. Kuna on olemas piirangud, kui tihti tohivad lingikaardid kolmandatelt saitidelt andmeid pärida, on soovitatav määrata väärtuseks vähemalt 14. Vastasel juhul ei uuendata linkide eelvaatekaarte nõudmise korral enne seda aega. min_age: Kasutajad peavad liitumisel kinnitama oma sünnikuupäeva @@ -105,10 +109,8 @@ et: status_page_url: Lehe URL, kus saab serveri maas oleku ajal näha serveri olekut theme: Teema, mida näevad sisenemata ning uued kasutajad. thumbnail: Umbes 2:1 mõõdus pilt serveri informatsiooni kõrval. - timeline_preview: Sisenemata külastajatel on võimalik sirvida viimaseid avalikke postitusi serveril. trendable_by_default: Populaarse sisu ülevaatuse vahele jätmine. Pärast seda on siiski võimalik üksikuid üksusi trendidest eemaldada. - trends: Trendid näitavad, millised postitused, sildid ja uudislood koguvad sinu serveris tähelepanu. - trends_as_landing_page: Näitab välja logitud kasutajatele ja külalistele serveri kirjelduse asemel populaarset sisu. Populaarne sisu (trendid) peab selleks olema sisse lülitatud. + trends: Trendid näitavad, millised postitused, teemaviited ja uudislood koguvad sinu serveris tähelepanu. form_challenge: current_password: Turvalisse alasse sisenemine imports: @@ -235,12 +237,12 @@ et: setting_aggregate_reblogs: Grupeeri jagamised ajajoontel setting_always_send_emails: Edasta kõik teavitused meilile setting_auto_play_gif: Esita GIF-e automaatselt - setting_boost_modal: Näita enne jagamist kinnitusdialoogi + setting_boost_modal: Kontrolli hooandmise nähtavust setting_default_language: Postituse keel setting_default_privacy: Postituse nähtavus setting_default_quote_policy: Kes võivad tsiteerida setting_default_sensitive: Alati märgista meedia tundlikuks - setting_delete_modal: Näita kinnitusdialoogi enne postituse kustutamist + setting_delete_modal: Enne postituse kustutamist hoiata mind setting_disable_hover_cards: Keela profiili eelvaade kui hõljutada setting_disable_swiping: Keela pühkimisliigutused setting_display_media: Meedia kuvarežiim @@ -250,7 +252,8 @@ et: setting_emoji_style: Emojide stiil setting_expand_spoilers: Alati näita tundlikuks märgitud postituste sisu setting_hide_network: Peida oma võrk - setting_missing_alt_text_modal: Näita kinnitusvaadet enne ilma selgitustekstita meediumit sisaldava postutuse avaldamist + setting_missing_alt_text_modal: Enne postitamist hoiata mind meediumi puuduva selgitusteksti puhul + setting_quick_boosting: Luba kiire hooandmine setting_reduce_motion: Vähenda animatsioonides liikumist setting_system_font_ui: Kasuta süsteemi vaikefonti setting_system_scrollbars_ui: Kasuta süsteemi vaikimisi kerimisriba @@ -269,7 +272,7 @@ et: email_domain_block: with_dns_records: Kaasa domeeni MX-kirjed ning IP-aadressid featured_tag: - name: Silt + name: Teemaviide filters: actions: blur: Peida hoiatusega meedia @@ -284,12 +287,17 @@ et: content_cache_retention_period: Kaugsisu säilitamise aeg custom_css: Kohandatud CSS favicon: Favicon + landing_page: Avaleht uute külastajate jaoks + local_live_feed_access: Ligipääs kohalike postituste voole + local_topic_feed_access: Ligipääs kohalike postitustele viitavale teemaviidete ja linkide voole mascot: Kohandatud maskott (kunagine) media_cache_retention_period: Meediapuhvri talletusperiood min_age: Vanuse alampiiri nõue peers_api_enabled: Avalda avastatud serverite loetelu API kaudu profile_directory: Luba kasutajate kataloog registrations_mode: Kes saab liituda + remote_live_feed_access: Ligipääs muude serverite postituste voole + remote_topic_feed_access: Ligipääs muude serverite postitustele viitavale teemaviidete ja linkide voole require_invite_text: Nõua liitumiseks põhjendust show_domain_blocks: Näita domeenikeelde show_domain_blocks_rationale: Näite domeenikeeldude põhjuseid @@ -302,10 +310,8 @@ et: status_page_url: Oleku lehe URL theme: Vaikmisi teema thumbnail: Serveri pisipilt - timeline_preview: Luba autentimata ligipääs avalikele ajajoontele trendable_by_default: Luba trendid eelneva ülevaatuseta trends: Luba trendid - trends_as_landing_page: Kasuta maabumislehena lehte Populaarne interactions: must_be_follower: Keela teavitused mittejälgijatelt must_be_following: Keela teavitused kasutajatelt, keda sa ei jälgi @@ -347,10 +353,10 @@ et: indexable: Kaasa profiilileht otsimootoritesse show_application: Näita, millisest äpist postituse saatsid tag: - listable: Luba sellel sildil ilmuda profiilide kataloogis - name: Silt - trendable: Luba sellel sildil trendida - usable: Luba seda märksõna postitustes kasutada lokaalselt + listable: Luba sellel teemaviitel ilmuda profiilide kataloogis + name: Teemaviide + trendable: Luba sellel teemaviitel olla nähtav viimaste trendide all + usable: Luba seda teemaviidet postitustes kasutada lokaalselt terms_of_service: changelog: Mis on muutunud? effective_date: Jõustumise kuupäev @@ -366,9 +372,9 @@ et: jurisdiction: Jurisdiktsioon min_age: Vanuse alampiir user: - date_of_birth_1i: Päev + date_of_birth_1i: Aasta date_of_birth_2i: Kuu - date_of_birth_3i: Aasta + date_of_birth_3i: Päev role: Roll time_zone: Ajavöönd user_role: diff --git a/config/locales/simple_form.eu.yml b/config/locales/simple_form.eu.yml index c1879e9f87ccb8..0e36f83c341dc3 100644 --- a/config/locales/simple_form.eu.yml +++ b/config/locales/simple_form.eu.yml @@ -54,8 +54,12 @@ eu: password: Erabili 8 karaktere gutxienez phrase: Bat egingo du Maiuskula/minuskula kontuan hartu gabe eta edukiaren abisua kontuan hartu gabe scopes: Zeintzuk API atzitu ditzakeen aplikazioak. Goi mailako arloa aukeratzen baduzu, ez dituzu azpikoak aukeratu behar. + setting_advanced_layout: Erakutsi Mastodon hainbat zutaberen ikuspegiarekin, zure kronologia, jakinarazpenak eta zuk aukeratutako hirugarren zutabe bat ikusteko aukera emanez. Ez da gomendagarria pantaila txikietarako. setting_aggregate_reblogs: Ez erakutsi bultzada berriak berriki bultzada jaso duten tootentzat (berriki jasotako bultzadei eragiten die bakarrik) setting_always_send_emails: Normalean eposta jakinarazpenak ez dira bidaliko Mastodon aktiboki erabiltzen ari zaren bitartean + setting_boost_modal: Gaituta dagoenean, bultzada emateak berrespen-leiho bat irekiko du lehenik; bertan, bultzadaren ikusgaitasuna aldatu ahal izango duzu. + setting_default_quote_policy_private: Jarraitzaileentzat soilik sortutako bidalketak Mastodonen ezin dituzte beste batzuek aipatu. + setting_default_quote_policy_unlisted: Jendeak aipatzen zaituenean, bere bidalketa ere joeren denbora-lerro publikoetatik ezkutatuko da. setting_default_sensitive: Multimedia hunkigarria lehenetsita ezkutatzen da, eta sakatuz ikusi daiteke setting_display_media_default: Ezkutatu hunkigarri gisa markatutako multimedia setting_display_media_hide_all: Ezkutatu multimedia guztia beti @@ -83,7 +87,7 @@ eu: activity_api_enabled: Lokalki argitaratutako bidalketak, erabiltzaile aktiboak, eta izen-emateen kopuruak astero zenbatzen ditu app_icon: WEBP, PNG, GIF edo JPG. Aplikazioaren ikono lehenetsia gainidazten du ikono pertsonalizatu batekin gailu mugikorretan. backups_retention_period: Erabiltzaileek geroago deskarga dezaketen beren argitalpenen artxiboak sor ditzakete. Balio positibo bat ezartzean, artxibo hauek biltegiratzetik automatikoki ezabatuko dira zehazturiko egunen buruan. - bootstrap_timeline_accounts: Kontu hauek erabiltzaile berrien jarraitzeko gomendioen goiko aldean ainguratuko dira. + bootstrap_timeline_accounts: Erabiltzaile berrien jarraipenerako gomendioen goiko aldean agertuko dira kontu horiek. Eman komaz bereizitako kontuen zerrenda. closed_registrations_message: Izen-ematea itxia dagoenean bistaratua content_cache_retention_period: Beste zerbitzarietako argitalpen guztiak (bultzadak eta erantzunak barne) ezabatuko dira zehazturiko egunen buruan, argitalpen horiek izan ditzaketen erabiltzaile lokalaren interakzioa kontuan izanik gabe. Instantzia desberdinetako erabiltzaileen arteko aipamen pribatuak ere galdu egingo dira eta ezin izango dira berreskuratu. Ezarpen honen erabilera xede berezia duten instantziei zuzendua dago eta erabiltzaileen itxaropena hausten da orotariko erabilerarako inplementatzean. custom_css: Estilo pertsonalizatuak aplikatu ditzakezu Mastodonen web bertsioan. @@ -103,10 +107,9 @@ eu: status_page_url: Kanporatua dagoen jendeak zerbitzari honen egoera ikus dezaten gune baten URL theme: Saioa hasi gabeko erabiltzaileek eta berriek ikusiko duten gaia. thumbnail: Zerbitzariaren informazioaren ondoan erakusten den 2:1 inguruko irudia. - timeline_preview: Saioa hasi gabeko erabiltzaileek ezingo dituzte arakatu zerbitzariko bidalketa publiko berrienak. trendable_by_default: Saltatu joeretako edukiaren eskuzko berrikuspena. Ondoren elementuak banan-bana kendu daitezke joeretatik. trends: Joeretan zure zerbitzarian bogan dauden bidalketa, traola eta albisteak erakusten dira. - trends_as_landing_page: Erakutsi pil-pilean dagoen edukia saioa hasita ez duten erabiltzaileei eta bisitariei, zerbitzari honen deskribapena erakutsi ordez. Joerak aktibatuak edukitzea beharrezkoa da. + wrapstodon: Bertako erabiltzaileei Mastodonen urteko erabileraren laburpen ludiko bat sortzea eskaini. Ezaugarri hori urte bakoitzeko abenduaren 10etik 31ra bitartean dago eskuragarri, eta urtean gutxienez hashtag bat erabili duten erabiltzaileei eskaintzen zaie. form_challenge: current_password: Zonalde seguruan sartzen ari zara imports: @@ -141,14 +144,23 @@ eu: admin_email: Legezko abisuak, kontraindikazioak, agindu judizialak, erretiratzeko eskaerak eta legea betetzeko eskaerak barne. arbitration_address: Goiko helbide fisikoa edo "N/A" bera izan daiteke posta elektronikoa erabiliz gero. arbitration_website: Web formularioa izan daiteke, edo "N/A" posta elektronikoa erabiliz gero. + choice_of_law: Edozein gatazka juridiko gobernatuko duten epaitegien hiria, eskualdea, lurraldea edo estatua. + dmca_email: Goiko "Lege-oharretarako helbide elektronikoa" atalean erabilitako helbide elektroniko bera izan daiteke. + domain: Ematen ari zaren lineako zerbitzuaren identifikazio bakarra. + jurisdiction: Zerrendatu fakturak ordaintzen dituen pertsona bizi den herrialdea. Enpresa edo bestelako erakunde bat bada, adierazi egoitza duen herrialdea eta, kasuan kasu, hiria, eskualdea, lurraldea edo estatua. + min_age: Ez luke izan behar zure jurisdikzioko legeek eskatzen duten gutxieneko adinetik beherakoa. user: chosen_languages: Markatzean, hautatutako hizkuntzetan dauden tutak besterik ez dira erakutsiko. + role: Rolak erabiltzaileak dituen baimenak zeintzuk diren kontrolatzen du. user_role: color: Rolarentzat erabiltzaile interfazean erabiliko den kolorea, formatu hamaseitarreko RGB bezala highlighted: Honek rola publikoki ikusgai jartzen du name: Rolaren izen publikoa, rola bereizgarri bezala bistaratzeko ezarrita badago permissions_as_keys: Rol hau duten erabiltzaileek sarbidea izango dute... position: Maila goreneko rolak erabakitzen du gatazkaren konponbidea kasu batzuetan. Ekintza batzuk maila baxuagoko rolen gain bakarrik gauzatu daitezke + username_block: + allow_with_approval: Izena ematea zuzenean eragotzi beharrean, bat datozen erregistroek zure onarpena beharko dute + comparison: Kontuan izan 'Scunthorpe arazoa' hitz-zatiak blokeatzean webhook: events: Hautatu gertaerak bidaltzeko template: Osatu zure JSON karga interpolazio aldakorra erabiliz. Utzi hutsik JSON lehenetsiarentzat. @@ -219,19 +231,26 @@ eu: setting_aggregate_reblogs: Taldekatu bultzadak denbora-lerroetan setting_always_send_emails: Bidali beti eposta jakinarazpenak setting_auto_play_gif: Erreproduzitu GIF animatuak automatikoki - setting_boost_modal: Erakutsi baieztapen elkarrizketa-koadroa bultzada eman aurretik + setting_boost_modal: Bultzaden ikusgaitasun-kontrola setting_default_language: Argitalpenen hizkuntza + setting_default_privacy: Bidalketarako ikusgarritasuna + setting_default_quote_policy: Nork aipa dezake setting_default_sensitive: Beti markatu edukiak hunkigarri gisa - setting_delete_modal: Erakutsi baieztapen elkarrizketa-koadroa toot bat ezabatu aurretik + setting_delete_modal: Eman abisua argitalpen bat ezabatu aurretik + setting_disable_hover_cards: Desgaitu profilaren aurrebista gainetik igarotzean setting_disable_swiping: Desgaitu hatza pasatzeko mugimenduak setting_display_media: Multimedia bistaratzea setting_display_media_default: Lehenetsia setting_display_media_hide_all: Ezkutatu guztia setting_display_media_show_all: Erakutsi guztia + setting_emoji_style: Emojien estiloa setting_expand_spoilers: Zabaldu beti edukiaren abisuak dituzten argitalpenak setting_hide_network: Ezkutatu zure sarea + setting_missing_alt_text_modal: Eman abisua testu alternatiborik gabeko multimedia-edukia argitaratu aurretik + setting_quick_boosting: Gaitu bultzada azkarra setting_reduce_motion: Murriztu animazioen mugimenduak setting_system_font_ui: Erabili sistemako tipografia lehenetsia + setting_system_scrollbars_ui: Erabili sistemako desplazamendu-barra lehenetsia setting_theme: Gunearen azala setting_trends: Erakutsi gaurko joerak setting_unfollow_modal: Erakutsi baieztapen elkarrizketa-koadroa inor jarraitzeari utzi aurretik @@ -250,6 +269,7 @@ eu: name: Traola filters: actions: + blur: Ezkutatu multimedia-edukia ohar batekin hide: Ezkutatu guztiz warn: Ezkutatu ohar batekin form_admin_settings: @@ -261,12 +281,17 @@ eu: content_cache_retention_period: Urruneko edukiaren atxikipen-aldia custom_css: CSS pertsonalizatua favicon: Gune-ikurra + landing_page: Bisitari berrientzako harrera-orria + local_live_feed_access: Tokiko argitalpenak nabarmentzen dituzten zuzeneko jarioetara sarbidea + local_topic_feed_access: Tokiko argitalpenak nabarmentzen dituzten traola eta esteketara sarbidea mascot: Maskota pertsonalizatua (zaharkitua) media_cache_retention_period: Multimediaren cachea atxikitzeko epea min_age: Gutxieneko adin-eskakizuna peers_api_enabled: Argitaratu aurkitutako zerbitzarien zerrenda APIan profile_directory: Gaitu profil-direktorioa registrations_mode: Nork eman dezake izena + remote_live_feed_access: Urruneko argitalpenak nabarmentzen dituzten zuzeneko jarioetara sarbidea + remote_topic_feed_access: Urruneko argitalpenak nabarmentzen dituzten traola eta esteketara sarbidea require_invite_text: Eskatu arrazoi bat batzeko show_domain_blocks: Erakutsi domeinu-blokeoak show_domain_blocks_rationale: Erakutsi domeinuak zergatik blokeatu ziren @@ -279,10 +304,9 @@ eu: status_page_url: Egoera-orriaren URLa theme: Lehenetsitako gaia thumbnail: Zerbitzariaren koadro txikia - timeline_preview: Onartu autentifikatu gabeko sarbidea denbora lerro publikoetara trendable_by_default: Onartu joerak aurrez berrikusi gabe trends: Gaitu joerak - trends_as_landing_page: Erabili joerak hasierako orri gisa + wrapstodon: Gaitu Wrapstodon interactions: must_be_follower: Blokeatu jarraitzaile ez direnen jakinarazpenak must_be_following: Blokeatu zuk jarraitzen ez dituzu horien jakinarazpenak @@ -307,6 +331,7 @@ eu: follow_request: Bidali e-mail bat norbaitek zu jarraitzea eskatzen duenean mention: Bidali e-mail bat norbaitek zu aipatzean pending_account: Bidali e-mail bat kontu bat berrikusi behar denean + quote: Norbaitek aipatu zaitu reblog: Bidali e-mail bat norbaitek zure mezuari bultzada ematen badio report: Salaketa berria bidali da software_updates: @@ -329,13 +354,22 @@ eu: usable: Baimendu bidalketek traola lokal hau erabiltzea terms_of_service: changelog: Zer aldatu da? + effective_date: Indarrean sartzeko data text: Zerbitzuaren baldintzak terms_of_service_generator: + admin_email: Lege-oharretarako helbide elektronikoa + arbitration_address: Arbitraje-jakinarazpenak bidaltzeko helbide fisikoa + arbitration_website: Arbitraje-jakinarazpenak bidaltzeko webgunea + choice_of_law: Aplikatu beharreko legedia + dmca_address: DMCA/egile-eskubideen jakinarazpenetarako helbide fisikoa + dmca_email: DMCA/egile-eskubideen jakinarazpenetarako helbide elektronikoa domain: Domeinua + jurisdiction: Jurisdikzio legala + min_age: Gutxieneko adina user: - date_of_birth_1i: Eguna + date_of_birth_1i: Urtea date_of_birth_2i: Hilabetea - date_of_birth_3i: Urtea + date_of_birth_3i: Eguna role: Rola time_zone: Ordu zona user_role: @@ -344,6 +378,10 @@ eu: name: Izena permissions_as_keys: Baimenak position: Lehentasuna + username_block: + allow_with_approval: Baimendu izen-emateak onarpen bidez + comparison: Konparazio-metodoa + username: Bat etorri beharreko hitza webhook: events: Gertaerak gaituta template: Karga txantiloia diff --git a/config/locales/simple_form.fa.yml b/config/locales/simple_form.fa.yml index 9d75b9e5db4ae5..6515f50b44e1b1 100644 --- a/config/locales/simple_form.fa.yml +++ b/config/locales/simple_form.fa.yml @@ -54,13 +54,18 @@ fa: password: دست‌کم باید ۸ نویسه داشته باشد phrase: مستقل از کوچکی و بزرگی حروف، با متن اصلی یا هشدار محتوای فرسته‌ها مقایسه می‌شود scopes: واسط‌های برنامه‌نویسی که این برنامه به آن دسترسی دارد. اگر بالاترین سطح دسترسی را انتخاب کنید، دیگر نیازی به انتخاب سطح‌های پایینی ندارید. + setting_advanced_layout: نمایش ماستودون به شکل چندستونه که می‌گذارد خط زمانی، آگاهی‌ها رو ستون سومی را به دلخواه ببینید. برای صفحه‌های کوچک‌تر توصیه نمی‌شود. setting_aggregate_reblogs: برای تقویت‌هایی که به تازگی برایتان نمایش داده شده‌اند، تقویت‌های بیشتر را نمایش نده (فقط روی تقویت‌های اخیر تأثیر می‌گذارد) setting_always_send_emails: در حالت عادی آگاهی‌های رایانامه‌ای هنگامی که فعّالانه از ماستودون استفاده می‌کنید فرستاده نمی‌شوند + setting_boost_modal: هنگام به کار افتادن تقویت نخست گفت‌وگوی تأییدی خواهد گشود که می‌توانید نمایانی تقویتتان را تغییر دهید. + setting_default_quote_policy_private: فرسته‌های فقط پی‌گیران روی ماستودون نمی‌توانند به دست دیگران نقل شوند. + setting_default_quote_policy_unlisted: هنگامی که کسی نقلتان می‌کند هم فرسته‌اش از خط زمانی‌های داغ پنهان خواهد بود. setting_default_sensitive: تصاویر حساس به طور پیش‌فرض پنهان هستند و می‌توانند با یک کلیک آشکار شوند setting_display_media_default: تصویرهایی را که به عنوان حساس علامت زده شده‌اند پنهان کن setting_display_media_hide_all: همیشه همهٔ عکس‌ها و ویدیوها را پنهان کن setting_display_media_show_all: همیشه تصویرهایی را که به عنوان حساس علامت زده شده‌اند را نشان بده setting_emoji_style: چگونگی نمایش شکلک‌ها. «خودکار» تلاش خواهد کرد از شکلک‌های بومی استفاده کند؛ ولی برای مرورگرهای قدیمی به توییموجی برخواهد گشت. + setting_quick_boosting_html: هنگام به کار افتادن، زدن روی %{boost_icon} نقشک تقویت به جای گشودنِ فهرست پایین افتادنی تقویت و نقل، بلافاصله تقویت خواهد کرد. کنشِ نقل قول را به فهرست %{options_icon} (گزینه‌ها) منتقل می‌کند. setting_system_scrollbars_ui: فقط برای مرورگرهای دسکتاپ مبتنی بر سافاری و کروم اعمال می شود setting_use_blurhash: سایه‌ها بر اساس رنگ‌های به‌کاررفته در تصویر پنهان‌شده ساخته می‌شوند ولی جزئیات تصویر در آن‌ها آشکار نیست setting_use_pending_items: به جای پیش‌رفتن خودکار در فهرست، به‌روزرسانی فهرست نوشته‌ها را پشت یک کلیک پنهان کن @@ -83,11 +88,12 @@ fa: activity_api_enabled: تعداد بوق‌های منتشرهٔ محلی، کاربران فعال، و کاربران تازه در هر هفته app_icon: WEBP، PNG، GIF یا JPG. با یک نماد سفارشی، نماد برنامه پیش‌فرض را در دستگاه‌های تلفن همراه لغو می‌کند. backups_retention_period: کاربران می توانند بایگانی هایی از پست های خود ایجاد کنند تا بعدا دانلود کنند. وقتی روی مقدار مثبت تنظیم شود، این بایگانی‌ها پس از تعداد روزهای مشخص شده به‌طور خودکار از فضای ذخیره‌سازی شما حذف می‌شوند. - bootstrap_timeline_accounts: سنجاق کردنThese accounts will be pinned to the top of new users' follow recommendations. + bootstrap_timeline_accounts: این حساب‌ها به بالای توصیه‌های پی‌گیری کاربران جدید سنجاق خواهند شد. سیاهه‌ای از حساب‌ها جدا شده با کاما فراهم کنید. closed_registrations_message: نمایش داده هنگام بسته بودن ثبت‌نام‌ها - content_cache_retention_period: همه پست‌های سرورهای دیگر (از جمله تقویت‌کننده‌ها و پاسخ‌ها) پس از چند روز مشخص شده، بدون توجه به هرگونه تعامل کاربر محلی با آن پست‌ها، حذف خواهند شد. این شامل پست هایی می شود که یک کاربر محلی آن را به عنوان نشانک یا موارد دلخواه علامت گذاری کرده است. ذکر خصوصی بین کاربران از نمونه های مختلف نیز از بین خواهد رفت و بازیابی آنها غیرممکن است. استفاده از این تنظیم برای موارد با هدف خاص در نظر گرفته شده است و بسیاری از انتظارات کاربر را هنگامی که برای استفاده عمومی اجرا می شود، از بین می برد. + content_cache_retention_period: پس از تعداد روزهای مشخّص شده تمامی فرسته‌ها از دیگر کارسازها (از جمله تقویت‌ها و پاسخ‌ها) بدون توجّه به هر تعامل کاربری محلی با آن‌ها حذف خواهند شد. این موضوع شامل حال فرسته‌هایی که کاربران محلی به عنوان نشانک یا برگزیده علامت زده‌اند نیز می‌شود. اشاره‌های خصوصی بین کاربران نمونه‌های مختلف نیزی از دست رفته و قابل بازگردانی نخواهند بود. استفاده از این تنظیم برای نمونه‌هایی با هدف خاص در نظر گرفته شده و برای استفادهٔ عمومی بسیاری از انتظارات کاربر را زیر پا می‌گذارد. custom_css: می‌توانیدروی نگارش وب ماستودون سبک‌های سفارشی اعمال کنید. favicon: WEBP، PNG، GIF یا JPG. فاویکون پیش‌فرض ماستودون را با یک نماد سفارشی لغو می‌کند. + landing_page: گزینش صفحه‌ای که بینندگان هنگام نخستین بازدید از کارسازتان می‌بینند. اگر «داغ‌ها» را بگزینید باید داغ‌ها در تنظمیات کشف به کار افتاده باشند. اگر «خوراک محلی» را بگزینید باید «دسترسی به خوراک‌های محلی» در تنظیمات کشف روی «هرکسی» تنظیم شده باشد. mascot: نقش میانای وب پیش‌رفته را پایمال می‌کند. media_cache_retention_period: فایل های رسانه ای از پست های ارسال شده توسط کاربران راه دور در سرور شما ذخیره می شوند. وقتی روی مقدار مثبت تنظیم شود، رسانه پس از تعداد روزهای مشخص حذف می شود. اگر داده‌های رسانه پس از حذف درخواست شود، در صورتی که محتوای منبع هنوز در دسترس باشد، مجدداً بارگیری می‌شود. با توجه به محدودیت‌هایی که در مورد تعداد دفعات نظرسنجی کارت‌های پیش‌نمایش پیوند از سایت‌های شخص ثالث وجود دارد، توصیه می‌شود این مقدار را حداقل 14 روز تنظیم کنید، در غیر این صورت کارت‌های پیش‌نمایش پیوند قبل از آن زمان به‌روزرسانی نمی‌شوند. min_age: در طول ثبت‌نام از کاربران خواسته خواهد شد که تاریخ تولَدشان را تأیید کنند @@ -103,10 +109,8 @@ fa: status_page_url: نشانی وب صفحه‌ای که در آن افراد می‌توانند وضعیت این سرور را در هنگام قطع شدن ببینند theme: زمینه‌ای که بینندگان خارج شده و کاربران جدید می‌بینند. thumbnail: یک تصویر تقریباً 2:1 در کنار اطلاعات سرور شما نمایش داده می شود. - timeline_preview: بازدیدکنندگانی که از سیستم خارج شده اند می توانند جدیدترین پست های عمومی موجود در سرور را مرور کنند. trendable_by_default: از بررسی دستی محتوای پرطرفدار صرف نظر کنید. آیتم های فردی هنوز هم می توانند پس از واقعیت از روند حذف شوند. trends: روندها نشان می‌دهند که کدام پست‌ها، هشتگ‌ها و داستان‌های خبری در سرور شما مورد توجه قرار گرفته‌اند. - trends_as_landing_page: به جای توضیح این سرور، محتوای پرطرفدار را به کاربران و بازدیدکنندگان از سیستم خارج شده نشان دهید. نیاز به فعال شدن روندها دارد. form_challenge: current_password: شما در حال ورود به یک منطقهٔ‌ حفاظت‌شده هستید imports: @@ -233,11 +237,12 @@ fa: setting_aggregate_reblogs: تقویت‌ها را در خط‌زمانی گروه‌بندی کن setting_always_send_emails: فرستادن همیشگی آگاهی‌های رایانامه‌ای setting_auto_play_gif: پخش خودکار تصویرهای متحرک - setting_boost_modal: نمایش پیغام تأیید پیش از تقویت کردن + setting_boost_modal: واپایش نمایانی تقویت setting_default_language: زبان نوشته‌های شما + setting_default_privacy: نمایانی فرسته setting_default_quote_policy: افراد مجاز به نقل setting_default_sensitive: همیشه تصاویر را به عنوان حساس علامت بزن - setting_delete_modal: نمایش پیغام تأیید پیش از پاک کردن یک نوشته + setting_delete_modal: هشدار پیش از حذف کردن فرسته setting_disable_hover_cards: از کار انداختن پیش‌نمایش نمایه هنگام رفتن رویش setting_disable_swiping: از کار انداختن حرکت‌های کشیدنی setting_display_media: نمایش عکس و ویدیو @@ -247,7 +252,8 @@ fa: setting_emoji_style: سبک شکلک setting_expand_spoilers: همیشه فرسته‌هایی را که هشدار محتوا دارند کامل نشان بده setting_hide_network: نهفتن شبکهٔ ارتباطی - setting_missing_alt_text_modal: نمایش گفتگوی تایید قبل از ارسال رسانه بدون متن جایگزین + setting_missing_alt_text_modal: هشدار پیش از فرستادن رسانه بدون متن جایگزین + setting_quick_boosting: به کار انداختن تقویت سریع setting_reduce_motion: کاستن از حرکت در پویانمایی‌ها setting_system_font_ui: به‌کاربردن قلم پیش‌فرض سیستم setting_system_scrollbars_ui: از نوار اسکرول پیش فرض سیستم استفاده کنید @@ -281,12 +287,17 @@ fa: content_cache_retention_period: دوره نگهداری محتوا از راه دور custom_css: سبک CSS سفارشی favicon: نمادک + landing_page: صفحهٔ فرود برای بینندگان جدید + local_live_feed_access: دسترسی به خوراک‌های زندهٔ نمایانگر فرسته‌های محلی + local_topic_feed_access: دسترسی به خوراک‌های پیوند و برچسب‌های نمایانگر فرسته‌های محلی mascot: نشان سفارشی (قدیمی) media_cache_retention_period: دورهٔ نگه‌داری انبارهٔ رسانه min_age: کمینهٔ سن لازم peers_api_enabled: انتشار سیاههٔ کارسازهای کشف شده در API profile_directory: به کار انداختن شاخهٔ نمایه registrations_mode: چه کسانی می‌توانند ثبت‌نام کنند + remote_live_feed_access: دسترسی به خوراک‌های زندهٔ نمایانگر فرسته‌های دوردست + remote_topic_feed_access: دسترسی به خوراک‌های پیوند و برچسب‌های نمایانگر فرسته‌های دوردست require_invite_text: نیازمند دلیلی برای پیوستن show_domain_blocks: نمایش مسدودیت‌های دامنه show_domain_blocks_rationale: نمایش چرایی مسدودیت دامنه‌ها @@ -299,10 +310,8 @@ fa: status_page_url: نشانی صفحهٔ وضعیت theme: زمینهٔ پیش‌گزیده thumbnail: بندانگشتی کارساز - timeline_preview: اجازهٔ دسترسی بدون تأیید هویت به خط‌زمانی‌های عمومی trendable_by_default: اجازهٔ پرطرفدار شدن بدون بازبینی پیشین trends: به کار انداختن پرطرفدارها - trends_as_landing_page: استفاده از داغ‌ها به عنوان صفحهٔ فرود interactions: must_be_follower: انسداد آگاهی‌ها از ناپی‌گیران must_be_following: انسداد آگاهی‌ها از افرادی که پی نمی‌گیرید @@ -363,9 +372,9 @@ fa: jurisdiction: صلاحیت قانونی min_age: کمینهٔ زمان user: - date_of_birth_1i: روز + date_of_birth_1i: سال date_of_birth_2i: ماه - date_of_birth_3i: سال + date_of_birth_3i: روز role: نقش time_zone: منطقهٔ زمانی user_role: diff --git a/config/locales/simple_form.fi.yml b/config/locales/simple_form.fi.yml index b31c8e1ef35d82..a57a5305d88b7d 100644 --- a/config/locales/simple_form.fi.yml +++ b/config/locales/simple_form.fi.yml @@ -19,7 +19,7 @@ fi: text: Voit käyttää julkaisun syntaksia, kuten URL-osoitteita, aihetunnisteita ja mainintoja title: Valinnainen. Ei näy vastaanottajalle admin_account_action: - include_statuses: Käyttäjä näkee, mitkä julkaisut johtivat moderointitoimeen tai -varoitukseen + include_statuses: Käyttäjä näkee, mitkä julkaisut johtivat moderointitoimeen tai -⁠varoitukseen send_email_notification: Käyttäjä saa selvityksen siitä, mitä hänen tililleen tapahtui text_html: Valinnainen. Voit käyttää julkaisun syntaksia. Voit lisätä varoitusasetuksia säästääksesi aikaa type_html: Valitse mitä teet käyttäjälle %{acct} @@ -28,7 +28,7 @@ fi: none: Käytä tätä lähettääksesi varoitus käyttäjälle käynnistämättä mitään muita toimia. sensitive: Pakota kaikki tämän käyttäjän mediatiedostot arkaluonteisiksi. silence: Estä käyttäjää lähettämästä julkaisuja, joiden näkyvyys on julkinen, sekä piilota hänen julkaisunsa ja häneen liittyvät ilmoitukset niiltä, jotka eivät seuraa häntä. Sulkee kaikki tiliin kohdistuvat raportit. - suspend: Estä kaikki vuorovaikutus tältä tililtä ja tälle tilille sekä poista kaikki sen sisältö. Peruttavissa 30 päivän ajan. Sulkee kaikki tiliin kohdistuvat raportit. + suspend: Estä kaikki vuorovaikutus tältä tililtä ja tälle tilille sekä poista kaikki sen sisältö. Peruttavissa 30 päivän ajan. Sulkee kaikki tiliin kohdistuvat raportit. warning_preset_id: Valinnainen. Voit silti lisätä mukautetun tekstin esiasetuksen loppuun announcement: all_day: Kun valittuna, vain aikavälin päivät näytetään @@ -48,14 +48,16 @@ fi: digest: Lähetetään vain pitkän poissaolon jälkeen ja vain, jos olet saanut suoria viestejä poissaolosi aikana email: Sinulle lähetetään vahvistusviesti header: WEBP, PNG, GIF tai JPG. Enintään %{size}. Skaalataan kokoon %{dimensions} px - inbox_url: Kopioi URL-osoite haluamasi välittäjän etusivulta + inbox_url: Kopioi URL-⁠osoite haluamasi välittäjän etusivulta irreversible: Suodatetut julkaisut katoavat peruuttamattomasti, vaikka suodatin poistettaisiin myöhemmin locale: Käyttöliittymän, sähköpostiviestien ja puskuilmoitusten kieli password: Käytä vähintään 8:aa merkkiä phrase: Täsmää riippumatta tekstin aakkoslajista tai julkaisun sisältövaroituksesta scopes: Mihin ohjelmointirajapintoihin sovelluksella on pääsy. Jos valitset ylätason käyttöoikeuden, sinun ei tarvitse valita yksittäisiä. + setting_advanced_layout: Näytä Mastodon usean sarakkeen asetteluna, jossa voit tarkastella aikajanaa, ilmoituksia ja valitsemaasi kolmatta saraketta. Ei suositella pienemmille näytöille. setting_aggregate_reblogs: Älä näytä uusia tehostuksia julkaisuille, joita on äskettäin tehostettu (koskee vain juuri vastaanotettuja tehostuksia) setting_always_send_emails: Yleensä sähköposti-ilmoituksia ei lähetetä, kun käytät Mastodonia aktiivisesti + setting_boost_modal: Kun käytössä, tehostaminen avaa ensin vahvistusikkunan, josta voit vaihtaa tehostuksesi näkyvyyttä. setting_default_quote_policy_private: Muut eivät voi lainata vain seuraajille tarkoitettuja, Mastodonissa kirjoitettuja julkaisuja. setting_default_quote_policy_unlisted: Kun ihmiset lainaavat sinua, heidän julkaisunsa piilotetaan suosittujen julkaisujen aikajanoilta. setting_default_sensitive: Arkaluonteinen media piilotetaan oletusarvoisesti, ja se voidaan näyttää yhdellä napsautuksella @@ -63,11 +65,12 @@ fi: setting_display_media_hide_all: Piilota mediasisältö aina setting_display_media_show_all: Näytä mediasisältö aina setting_emoji_style: Miten emojit näkyvät. ”Automaattinen” pyrkii käyttämään natiiveja emojeita, mutta Twemoji-emojeita käytetään varavaihtoehtoina vanhoissa selaimissa. + setting_quick_boosting_html: Kun käytössä, %{boost_icon} Tehosta-kuvakkeen painaminen tehostaa välittömästi sen sijaan, että Tehosta/Lainaa-pudotusvalikko avautuisi. Siirtää lainaustoiminnon %{options_icon} (Valinnat) -⁠valikkoon. setting_system_scrollbars_ui: Koskee vain Safari- ja Chrome-pohjaisia työpöytäselaimia setting_use_blurhash: Liukuvärit perustuvat piilotettujen kuvien väreihin mutta sumentavat yksityiskohdat setting_use_pending_items: Piilota aikajanan päivitykset napsautuksen taakse syötteen automaattisen vierityksen sijaan username: Voit käyttää kirjaimia, numeroita ja alaviivoja - whole_word: Kun avainsana tai -fraasi on täysin aakkosnumeerinen, suodatin aktivoituu vain sen täysvastineille + whole_word: Kun avainsana tai -⁠fraasi on kokonaan aakkosnumeerinen, suodatin tulee voimaan vain sen täysvastineille domain_allow: domain: Tämä verkkotunnus voi noutaa tietoja tältä palvelimelta, ja sieltä saapuvat tiedot käsitellään ja tallennetaan email_domain_block: @@ -76,7 +79,7 @@ fi: featured_tag: name: 'Tässä muutamia hiljattain käyttämiäsi aihetunnisteita:' filters: - action: Valitse, mikä toiminto suoritetaan, kun julkaisu vastaa suodatinta + action: Valitse, miten toimitaan, kun julkaisu vastaa suodatinta actions: blur: Piilota media varoituksen taakse piilottamatta itse tekstiä hide: Piilota suodatettu sisältö kokonaan, ikään kuin sitä ei olisi olemassa @@ -85,17 +88,18 @@ fi: activity_api_enabled: Paikallisesti julkaistujen julkaisujen, aktiivisten käyttäjien ja rekisteröitymisten viikoittainen määrä app_icon: WEBP, PNG, GIF tai JPG. Korvaa oletusarvoisen mobiililaitteiden sovelluskuvakkeen haluamallasi kuvakkeella. backups_retention_period: Käyttäjillä on mahdollisuus arkistoida julkaisujaan myöhemmin ladattaviksi. Kun kentän arvo on positiivinen, nämä arkistot poistuvat automaattisesti, kun määritetty määrä päiviä on kulunut. - bootstrap_timeline_accounts: Nämä tilit kiinnitetään uusien käyttäjien seurantasuositusten alkuun. + bootstrap_timeline_accounts: Nämä tilit kiinnittyvät uusien käyttäjien seurantasuositusten alkuun. Syötä pilkuin eroteltu luettelo tilejä. closed_registrations_message: Näkyy, kun rekisteröityminen on suljettu content_cache_retention_period: Kaikki muiden palvelinten julkaisut (mukaan lukien tehostukset ja vastaukset) poistuvat, kun määritetty määrä päiviä on kulunut, lukuun ottamatta paikallisen käyttäjän vuorovaikutusta näiden julkaisujen kanssa. Tämä sisältää julkaisut, jotka paikallinen käyttäjä on merkinnyt kirjanmerkiksi tai suosikiksi. Myös yksityismaininnat eri palvelinten käyttäjien välillä menetetään, eikä niitä voi palauttaa. Tämä asetus on tarkoitettu käytettäväksi erityistapauksissa ja rikkoo monia käyttäjien odotuksia, kun sitä sovelletaan yleiskäyttöön. custom_css: Voit käyttää mukautettuja tyylejä Mastodonin selainversiossa. favicon: WEBP, PNG, GIF tai JPG. Korvaa oletusarvoisen Mastodonin sivustokuvakkeen haluamallasi kuvakkeella. + landing_page: Valitsee, minkä sivun uudet kävijät näkevät saapuessaan palvelimellesi. Jos valitset ”Trendit”, trendien tulee olla käytössä Löydettävyys-asetuksissa. Jos valitset ”Paikallinen syöte”, kohta ”Pääsy paikallisia julkaisuja esitteleviin livesyötteisiin” tulee asettaa arvoon ”Kaikki” Löydettävyys-asetuksissa. mascot: Korvaa kuvituksen edistyneessä selainkäyttöliittymässä. media_cache_retention_period: Etäkäyttäjien tekemien julkaisujen mediatiedostot ovat välimuistissa palvelimellasi. Kun kentän arvo on positiivinen, media poistuu, kun määritetty määrä päiviä on kulunut. Jos mediaa pyydetään sen poistamisen jälkeen, se ladataan uudelleen, jos lähdesisältö on vielä saatavilla. Koska linkkien esikatselun kyselyitä kolmansien osapuolien sivustoille on rajoitettu, on suositeltavaa asettaa tämä arvo vähintään 14 päivään, tai linkkien kortteja ei päivitetä pyynnöstä ennen tätä ajankohtaa. min_age: Käyttäjiä pyydetään rekisteröitymisen aikana vahvistamaan syntymäpäivänsä peers_api_enabled: Luettelo verkkotunnuksista, jotka tämä palvelin on kohdannut fediversumissa. Se ei kerro, federoitko tietyn palvelimen kanssa, vaan että palvelimesi on ylipäätään tietoinen siitä. Tätä tietoa käytetään palveluissa, jotka keräävät tilastoja federoinnista yleisellä tasolla. profile_directory: Profiilihakemisto luetteloi kaikki käyttäjät, jotka ovat valinneet olla löydettävissä. - require_invite_text: Kun rekisteröityminen vaatii manuaalisen hyväksynnän, tee ”Miksi haluat liittyä?” -tekstikentästä pakollinen vapaaehtoisen sijaan + require_invite_text: Kun rekisteröityminen vaatii manuaalisen hyväksynnän, tee ”Miksi haluat liittyä?” -⁠tekstikentästä pakollinen vapaaehtoisen sijaan site_contact_email: Miten sinut voi tavoittaa oikeus- tai tukikysymyksissä. site_contact_username: Miten sinut voi tavoittaa Mastodonissa. site_extended_description: Kaikki lisätiedot, jotka voivat olla hyödyllisiä kävijöille ja käyttäjille. Voidaan jäsentää Markdown-syntaksilla. @@ -105,10 +109,8 @@ fi: status_page_url: URL-osoite sivulle, josta tämän palvelimen tilan voi ongelmatilanteissa tarkistaa theme: Teema, jonka uloskirjautuneet vierailijat ja uudet käyttäjät näkevät. thumbnail: Noin 2:1 kuva näkyy palvelimen tietojen ohessa. - timeline_preview: Uloskirjautuneet vierailijat voivat selata uusimpia julkisia julkaisuja, jotka ovat saatavilla palvelimella. trendable_by_default: Ohita suositun sisällön manuaalinen tarkastus. Yksittäisiä kohteita voidaan edelleen poistaa jälkikäteen. trends: Trendit osoittavat, mitkä julkaisut, aihetunnisteet ja uutiset keräävät huomiota palvelimellasi. - trends_as_landing_page: Näytä vierailijoille ja uloskirjautuneille käyttäjille suosittua sisältöä palvelimen kuvauksen sijaan. Edellyttää, että trendit on otettu käyttöön. form_challenge: current_password: Olet menossa suojatulle alueelle imports: @@ -143,7 +145,8 @@ fi: admin_email: Oikeudellisiin ilmoituksiin kuuluvat vastailmoitukset, oikeuden määräykset, poistopyynnöt ja lainvalvontaviranomaisten pyynnöt. arbitration_address: Voi olla sama kuin edellä mainittu Fyysinen osoite tai ”N/A”, jos käytät sähköpostia. arbitration_website: Voi olla verkkolomake tai ”N/A”, jos käytät sähköpostia. - dmca_address: Yhdysvaltalaisten operaattoreiden on käytettävä DMCA Designated Agent Directory -luetteloon rekisteröityä osoitetta. Postilokeroluettelo on saatavissa suoralla pyynnöllä, joten käytä DMCA Designated Agent Post Office Box Waiver Request -lomaketta lähettääksesi sähköpostia tekijänoikeusvirastolle ja kuvaile, että olet kotona toimiva sisältömoderaattori, joka pelkää kostoa tai rangaistusta toimistaan ja tarvitsee postilokeroa pitääkseen kotiosoitteensa poissa julkisuudesta. + choice_of_law: Kaupunki, alue, territorio tai valtio, jonka sisäinen aineellinen oikeus säätelee kaikkia vaatimuksia. + dmca_address: Yhdysvaltalaisten operaattoreiden on käytettävä DMCA Designated Agent Directory -⁠luetteloon rekisteröityä osoitetta. Postilokeroluettelo on saatavissa suoralla pyynnöllä, joten käytä DMCA Designated Agent Post Office Box Waiver Request -⁠lomaketta lähettääksesi sähköpostia tekijänoikeusvirastolle ja kuvaile, että olet kotona toimiva sisältömoderaattori, joka pelkää kostoa tai rangaistusta toimistaan ja tarvitsee postilokeroa pitääkseen kotiosoitteensa poissa julkisuudesta. dmca_email: Voi olla sama kuin edellä mainittu ”Sähköpostiosoite oikeudellisille ilmoituksille”. domain: Tarjoamasi verkkopalvelun yksilöllinen tunniste. jurisdiction: Mainitse valtio, jossa laskujen maksaja asuu. Jos kyseessä on yritys tai muu yhteisö, mainitse valtio, johon se on rekisteröity, ja tarvittaessa kaupunki, alue, territorio tai osavaltio. @@ -171,7 +174,7 @@ fi: labels: account: attribution_domains: Verkkosivustot, jotka voivat antaa sinulle tunnustusta - discoverable: Pidä profiiliasi ja julkaisujasi esillä löytämisalgoritmeissa + discoverable: Pidä profiiliasi ja julkaisujasi esillä löydettävyysalgoritmeissa fields: name: Nimike value: Sisältö @@ -200,7 +203,7 @@ fi: announcement: all_day: Koko päivän tapahtuma ends_at: Tapahtuman loppu - scheduled_at: Ajoita julkaisu + scheduled_at: Ajasta julkaisu starts_at: Tapahtuman alku text: Tiedote appeal: @@ -221,7 +224,7 @@ fi: fields: Lisäkentät header: Otsakekuva honeypot: "%{label} (älä täytä)" - inbox_url: Välittäjän postilaatikon URL-osoite + inbox_url: Välittäjän postilaatikon URL-⁠osoite irreversible: Pudota piilottamisen sijaan locale: Käyttöliittymän kieli max_uses: Käyttökertoja enintään @@ -234,12 +237,12 @@ fi: setting_aggregate_reblogs: Ryhmitä tehostukset aikajanoilla setting_always_send_emails: Lähetä sähköposti-ilmoitukset aina setting_auto_play_gif: Toista GIF-animaatiot automaattisesti - setting_boost_modal: Kysy vahvistusta ennen tehostusta + setting_boost_modal: Hallitse tehostuksen näkyvyyttä setting_default_language: Julkaisun kieli setting_default_privacy: Julkaisun näkyvyys setting_default_quote_policy: Kuka voi lainata setting_default_sensitive: Merkitse media aina arkaluonteiseksi - setting_delete_modal: Kysy vahvistusta ennen julkaisun poistamista + setting_delete_modal: Varoita ennen kuin poistan julkaisun setting_disable_hover_cards: Poista käytöstä profiilin esikatselu osoitettaessa setting_disable_swiping: Poista pyyhkäisyeleet käytöstä setting_display_media: Median näkyminen @@ -249,7 +252,8 @@ fi: setting_emoji_style: Emojityyli setting_expand_spoilers: Laajenna aina sisältövaroituksilla merkityt julkaisut setting_hide_network: Piilota verkostotietosi - setting_missing_alt_text_modal: Näytä vahvistusikkuna ennen kuin julkaistaan mediaa ilman vaihtoehtoista tekstiä + setting_missing_alt_text_modal: Varoita ennen kuin julkaisen mediaa ilman vaihtoehtoista tekstiä + setting_quick_boosting: Ota nopea tehostus käyttöön setting_reduce_motion: Vähennä animaatioiden liikettä setting_system_font_ui: Käytä järjestelmän oletusfonttia setting_system_scrollbars_ui: Käytä järjestelmän oletusarvoista vierityspalkkia @@ -283,12 +287,17 @@ fi: content_cache_retention_period: Etäsisällön säilytysaika custom_css: Mukautettu CSS favicon: Sivustokuvake + landing_page: Uusien kävijöiden aloitussivu + local_live_feed_access: Pääsy paikallisia julkaisuja esitteleviin livesyötteisiin + local_topic_feed_access: Pääsy paikallisia julkaisuja esitteleviin aihetunniste- ja linkkisyötteisiin mascot: Mukautettu maskotti (vanhentunut) media_cache_retention_period: Mediasisällön välimuistin säilytysaika min_age: Vähimmäisikävaatimus peers_api_enabled: Julkaise löydettyjen palvelinten luettelo ohjelmointirajapinnassa profile_directory: Ota profiilihakemisto käyttöön registrations_mode: Kuka voi rekisteröityä + remote_live_feed_access: Pääsy etäjulkaisuja esitteleviin livesyötteisiin + remote_topic_feed_access: Pääsy etäjulkaisuja esitteleviin aihetunniste- ja linkkisyötteisiin require_invite_text: Vaadi liittymissyy show_domain_blocks: Näytä verkkotunnusten estot show_domain_blocks_rationale: Näytä, miksi verkkotunnukset on estetty @@ -301,10 +310,9 @@ fi: status_page_url: Tilasivun URL-osoite theme: Oletusteema thumbnail: Palvelimen pienoiskuva - timeline_preview: Salli todentamaton pääsy julkisille aikajanoille trendable_by_default: Salli trendit ilman ennakkotarkastusta trends: Ota trendit käyttöön - trends_as_landing_page: Käytä trendejä aloitussivuna + wrapstodon: Ota Wrapstodon käyttöön interactions: must_be_follower: Estä ilmoitukset käyttäjiltä, jotka eivät seuraa sinua must_be_following: Estä ilmoitukset käyttäjiltä, joita et seuraa @@ -365,9 +373,9 @@ fi: jurisdiction: Lainkäyttöalue min_age: Vähimmäisikä user: - date_of_birth_1i: Päivä + date_of_birth_1i: Vuosi date_of_birth_2i: Kuukausi - date_of_birth_3i: Vuosi + date_of_birth_3i: Päivä role: Rooli time_zone: Aikavyöhyke user_role: diff --git a/config/locales/simple_form.fo.yml b/config/locales/simple_form.fo.yml index 95368bcf7ab071..15465fff9ce18f 100644 --- a/config/locales/simple_form.fo.yml +++ b/config/locales/simple_form.fo.yml @@ -54,8 +54,10 @@ fo: password: Skriva minst 8 tekin phrase: Fer at samsvara óanæsð um tað er skrivað við lítlum ella stórum ella um postar hava innihaldsávaringar scopes: Hvørji API nýtsluskipanin fær atgongd til. Velur tú eitt vav á hægsta stigi, so er ikki neyðugt at velja tey einstøku. + setting_advanced_layout: Vís Mastodon í fleiri teigum, soleiðis at tú sær tíðarlinju, fráboðanir og ein triðja teig, sum tú velur. Ikki viðmælt fyri smærri skermar. setting_aggregate_reblogs: Vís ikki nýggjar stimbranir fyri postar, sum nýliga eru stimbraðir (ávirkar einans stimbranir, ið eru móttiknar fyri kortum) setting_always_send_emails: Vanliga vera teldupostfráboðanir ikki sendar, tá tú virkin brúkar Mastodon + setting_boost_modal: Tá hetta er virkið opna stimbranir fyrst eitt váttanarvindeyga, har tú kanst broyta hvussu tín stimbran sæst. setting_default_quote_policy_private: Postar, sum einans eru fyri fylgjarar á Mastodon, kunnu ikki siterast av øðrum. setting_default_quote_policy_unlisted: Tá fólk sitera teg, so vera teirra postar eisini fjaldir frá tíðarlinjum við ráki. setting_default_sensitive: Viðkvæmar miðlafílur eru fjaldar og kunnu avdúkast við einum klikki @@ -63,6 +65,7 @@ fo: setting_display_media_hide_all: Fjal altíð miðlafílur setting_display_media_show_all: Vís altíð miðlafílur setting_emoji_style: Hvussu kenslutekn vera víst. "Sjálvvirkandi" roynir at brúka upprunalig kenslutekn, men fellir aftur á Twitter kenslutekn í eldri kagum. + setting_quick_boosting_html: Tá hetta er virkið, hendir stimbranin beinanvegin tá trýst verður á %{boost_icon} Stimbranar-ímyndin, í staðin fyri at stimbranar/siterings-valmyndin verður latin um. Flytir siteringsmøguleikan til %{options_icon} (Valmøguleikar) valmyndina. setting_system_scrollbars_ui: Er einans viðkomandi fyri skriviborðskagar grundaðir á Safari og Chrome setting_use_blurhash: Gradientar eru grundaðir á litirnar av fjaldu myndunum, men grugga allar smálutir setting_use_pending_items: Fjal tíðarlinjudagføringar aftan fyri eitt klikk heldur enn at skrulla tilføringina sjálvvirkandi @@ -85,11 +88,12 @@ fo: activity_api_enabled: Tal av lokalt útgivnum postum, virknum brúkarum og nýggjum skrásetingum býtt vikuliga app_icon: WEBP, PNG, GIF ella JPG. Býtir vanligu ikonina á fartelefoneindum um við eina ser-ikon. backups_retention_period: Brúkarar hava møguleika at gera trygdaravrit av teirra postum, sum tey kunnu taka niður seinni. Tá hetta er sett til eitt virði størri enn 0, so verða hesi trygdaravrit strikaði av sær sjálvum frá tínar goymslu eftir ásetta talið av døgum. - bootstrap_timeline_accounts: Hesar kontur verða festar ovast á listanum yvir brúkarar, sum tey nýggju verða mælt til at fylgja. + bootstrap_timeline_accounts: Hesar konturnar verða festar ovast í fylgjaratilmælunum hjá nýggjum brúkarum. Kom við einum komma-skildum lista av kontum. closed_registrations_message: Víst tá stongt er fyri tilmeldingum content_cache_retention_period: Allir postar frá øðrum ambætarum (íroknað stimbranir og svar) verða strikaði eftir ásetta talið av døgum, óansæð hvussu lokalir brúkarar hava samvirkað við hesar postar. Hetta fevnir eisini um postar, sum lokalir brúkarar hava bókamerkt ella yndismerkt. Privatar umrøður millum brúkarar frá ymiskum ambætarum verða eisini burturmistar og ómøguligar at endurskapa. Brúk av hesi stillingini er einans hugsað til serligar støður og oyðileggur nógv, sum brúkarar vænta av einum vanligum ambætara. custom_css: Tú kanst seta títt egna snið upp í net-útgávuni av Mastodon. favicon: WEBP, PNG, GIF ella JPG. Býtir vanligu Mastodon fav-ikonina um við eina ser-ikon. + landing_page: Velur hvørja síðu nýggj vitandi síggja tá tey koma á ambætaran hjá tær. Neyðugt er at rák eru gjørd virkin í Uppdagingarstillingum, um tú velur "Rák". Velur tú "Lokal rás" má "Atgongd til beinleiðis rásir við lokalum postum" vera sett til "Øll" í Uppdagingarstillingum. mascot: Skúgvar til viks myndprýðingina í framkomna vev-markamótinum. media_cache_retention_period: Miðlafílur frá postum, sum fjarbrúkarar hava gjørt, verða goymdir á tínum ambætara. Tá hetta er sett til eitt virði størri enn 0, so verða miðlafílurnar strikaðar eftir ásetta talið av døgum. Um miðladátur verða umbidnar eftir at tær eru strikaðar, verða tær tiknar innaftur á ambætaran, um keldutilfarið enn er tøkt. Vegna avmarkingar á hvussu ofta undanvísingarkort til leinki spyrja triðjapartsstøð, so verður mælt til at seta hetta virðið til í minsta lagi 14 dagar. Annars verða umbønir um dagføringar av undanvísingarkortum til leinki ikki gjørdar áðrenn hetta. min_age: Brúkarar verða spurdir um at vátta teirra føðingardag, tá tey skráseta seg @@ -105,10 +109,8 @@ fo: status_page_url: Slóð til eina síðu, har ið fólk kunnu síggja støðuna á hesum ambætaranum í sambandi við streymslit theme: Uppsetingareyðkenni, sum vitjandi, ið ikki eru ritaði inn, og nýggir brúkarar síggja. thumbnail: Ein mynd í lutfallinum 2:1, sum verður víst saman við ambætaraupplýsingunum hjá tær. - timeline_preview: Vitjandi, sum eru ritaði út, fara at kunna blaða ígjøgnum nýggjastu almennu postarnar, sum eru tøkir á ambætaranum. trendable_by_default: Loyp uppum serskilda eftirkannan av tilfari, sum er vælumtókt. Einstakir lutir kunnu framvegis strikast frá listum við vælumtóktum tilfari seinni. trends: Listar við vælumtóktum tilfari vísa, hvørjir postar, frámerki og tíðindasøgur hava framburð á tínum ambætara. - trends_as_landing_page: Vís vitjandi og brúkarum, sum ikki eru innritaðir, rák í staðin fyri eina lýsing av ambætaranum. Krevur at rák eru virkin. form_challenge: current_password: Tú ert á veg til eitt trygt øki imports: @@ -235,12 +237,12 @@ fo: setting_aggregate_reblogs: Bólka stimbranir í tíðarlinjum setting_always_send_emails: Send altíð fráboðanir við telduposti setting_auto_play_gif: Spæl teknimyndagjørdar GIFar sjálvvirkandi - setting_boost_modal: Vís váttanarmynd, áðrenn tú stimbrar postar + setting_boost_modal: Stýr hvussu stimbranir síggjast setting_default_language: Mál, sum verður brúkt til postar setting_default_privacy: Postar sýni setting_default_quote_policy: Hvør kann sitera setting_default_sensitive: Merk altíð miðlafílur sum viðkvæmar - setting_delete_modal: Vís váttanarmynd, áðrenn postar verða strikaðir + setting_delete_modal: Ávara meg áðrenn ein postur er strikaður setting_disable_hover_cards: Ger undanvísing, tá músin verður flutt yvir vangan, óvirkna setting_disable_swiping: Ger sveipurørslur óvirknar setting_display_media: Vístir miðlar @@ -250,7 +252,8 @@ fo: setting_emoji_style: Kensluteknsstílur setting_expand_spoilers: Víðka altíð postar, sum eru merktir við innihaldsávaringum setting_hide_network: Fjal sosiala grafin hjá tær - setting_missing_alt_text_modal: Spyr um góðkenning áðrenn miðlar uttan alternativan tekst verða postaðir + setting_missing_alt_text_modal: Ávara meg áðrenn miðlar uttan alt tekst verða postaðir + setting_quick_boosting: Ger skjóta stimbran virkna setting_reduce_motion: Minka um rørslu í teknimyndum setting_system_font_ui: Brúka vanliga skriftaslagið hjá skipanini setting_system_scrollbars_ui: Brúka vanliga skrullibjálkan hjá skipanini @@ -284,12 +287,17 @@ fo: content_cache_retention_period: Tíðarskeið fyri varðveiðslu av fjartilfari custom_css: Serskilt CSS favicon: Favikon + landing_page: Heimasíða til nýggj vitjandi + local_live_feed_access: Atgongd til beinleiðis rásir við lokalum postum + local_topic_feed_access: Atgongd til frámerki og rásir við leinkjum við lokalum postum mascot: Serskildur maskottur (arvur) media_cache_retention_period: Tíðarskeið, har miðlagoymslur verða varðveittar min_age: Aldursmark peers_api_enabled: Kunnger lista við uppdagaðum ambætarum í API'num profile_directory: Ger vangaskrá virkna registrations_mode: Hvør kann tilmelda seg + remote_live_feed_access: Atgongd til beinleiðis rásir við fjarum postum + remote_topic_feed_access: Atgongd til frámerki og rásir við leinkjum við fjarum postum require_invite_text: Krev eina orsøk at luttaka show_domain_blocks: Vís navnaøkisblokeringar show_domain_blocks_rationale: Vís hví navnaøki vóru blokeraði @@ -302,10 +310,8 @@ fo: status_page_url: Slóð til støðusíðu theme: Sjálvvalt uppsetingareyðkenni thumbnail: Ambætarasmámynd - timeline_preview: Loyv teimum, sum ikki eru ritaði inn, atgongd til almennar tíðarlinjur trendable_by_default: Loyv vælumtóktum tilfari uttan at viðgera tað fyrst trends: Loyv ráki - trends_as_landing_page: Brúka rák sum lendingarsíðu interactions: must_be_follower: Blokera fráboðanum frá teimum, sum ikki fylgja tær must_be_following: Blokera fráboðanum frá teimum, tú ikki fylgir @@ -366,9 +372,9 @@ fo: jurisdiction: Løgdømi min_age: Lægsti aldur user: - date_of_birth_1i: Dagur + date_of_birth_1i: Ár date_of_birth_2i: Mánaði - date_of_birth_3i: Ár + date_of_birth_3i: Dagur role: Leiklutur time_zone: Tíðarsona user_role: diff --git a/config/locales/simple_form.fr-CA.yml b/config/locales/simple_form.fr-CA.yml index 4a38b57118060f..aec2a254a1ff6a 100644 --- a/config/locales/simple_form.fr-CA.yml +++ b/config/locales/simple_form.fr-CA.yml @@ -54,12 +54,18 @@ fr-CA: password: Utilisez au moins 8 caractères phrase: Sera filtré peu importe la casse ou l’avertissement de contenu du message scopes: À quelles APIs l’application sera autorisée à accéder. Si vous sélectionnez une permission générale, vous n’avez pas besoin de sélectionner les permissions plus précises. + setting_advanced_layout: Afficher Mastodon avec une mise en page multicolonnes, vous permettant de visualiser le flux, les notifications et une troisième colonne de votre choix. Non recommandé pour les écrans plus petits. setting_aggregate_reblogs: Ne pas afficher les nouveaux partages pour les messages déjà récemment partagés (n’affecte que les partages futurs) setting_always_send_emails: Normalement, les notifications par courriel ne seront pas envoyées lorsque vous utilisez Mastodon activement + setting_boost_modal: Lorsque cette option est activée, avant de partager un message un écran de confirmation vous permettra de changer la visibilité du partage. + setting_default_quote_policy_private: Les messages limités aux personnes qui vous suivent publiés depuis Mastodon ne peuvent pas être cités. + setting_default_quote_policy_unlisted: Lorsque des personnes vous citent, leur message sera également masqué des fils des tendances. setting_default_sensitive: Les médias sensibles sont cachés par défaut et peuvent être révélés d’un simple clic setting_display_media_default: Masquer les médias marqués comme sensibles setting_display_media_hide_all: Toujours masquer les médias setting_display_media_show_all: Toujours afficher les médias + setting_emoji_style: Manière d'afficher les émojis. Utiliser « Auto » pour essayer d'utiliser les émojis natifs, mais Twemoji sera utilisé pour les anciens navigateurs. + setting_quick_boosting_html: Lorsque cette option est activée, cliquer sur l'icône de partage %{boost_icon} va immédiatement partager le message au lieu d'ouvrir le menu déroulant Partage/Citation. L'action de citation est déplacée dans le menu %{options_icon} (options). setting_system_scrollbars_ui: S'applique uniquement aux navigateurs basés sur Safari et Chrome setting_use_blurhash: Les dégradés sont basés sur les couleurs des images cachées mais n’en montrent pas les détails setting_use_pending_items: Cacher les mises à jour des fils d’actualités derrière un clic, au lieu de les afficher automatiquement @@ -82,11 +88,12 @@ fr-CA: activity_api_enabled: Nombre de messages publiés localement, de comptes actifs et de nouvelles inscriptions par tranche hebdomadaire app_icon: WEBP, PNG, GIF ou JPG. Remplace la favicon Mastodon par défaut avec une icône personnalisée. backups_retention_period: Les utilisateur·rice·s ont la possibilité de générer des archives de leurs messages pour les télécharger plus tard. Lorsqu'elles sont définies à une valeur positive, ces archives seront automatiquement supprimées de votre stockage après le nombre de jours spécifié. - bootstrap_timeline_accounts: Ces comptes seront épinglés en tête de liste des recommandations pour les nouveaux utilisateurs. + bootstrap_timeline_accounts: Ces comptes seront épinglés en haut des recommandations pour les nouveaux utilisateurs. Accepte une liste de comptes séparés par des virgules. closed_registrations_message: Affiché lorsque les inscriptions sont fermées content_cache_retention_period: Tous les messages provenant d'autres serveurs (y compris les partages et les réponses) seront supprimés passé le nombre de jours spécifié, sans tenir compte de l'interaction de l'utilisateur·rice local·e avec ces messages. Cela inclut les messages qu'un·e utilisateur·rice aurait marqué comme signets ou comme favoris. Les mentions privées entre utilisateur·rice·s de différentes instances seront également perdues et impossibles à restaurer. L'utilisation de ce paramètre est destinée à des instances spécifiques et contrevient à de nombreuses attentes des utilisateurs lorsqu'elle est appliquée à des fins d'utilisation ordinaires. custom_css: Vous pouvez appliquer des styles personnalisés sur la version Web de Mastodon. favicon: WEBP, PNG, GIF ou JPG. Remplace la favicon Mastodon par défaut avec une icône personnalisée. + landing_page: Sélectionner la page à afficher aux nouveaux visiteurs quand ils arrivent sur votre serveur. Pour utiliser « Tendances » les tendances doivent être activées dans les paramètres de découverte. Pour utiliser « Fil local » le paramètre « Accès au flux en direct de ce serveur » doit être défini sur « Tout le monde » dans les paramètres de découverte. mascot: Remplace l'illustration dans l'interface Web avancée. media_cache_retention_period: Les fichiers médias des messages publiés par des utilisateurs distants sont mis en cache sur votre serveur. Lorsque cette valeur est positive, les médias sont supprimés au terme du nombre de jours spécifié. Si les données des médias sont demandées après leur suppression, elles seront téléchargées à nouveau, dans la mesure où le contenu source est toujours disponible. En raison des restrictions concernant la fréquence à laquelle les cartes de prévisualisation des liens interrogent des sites tiers, il est recommandé de fixer cette valeur à au moins 14 jours, faute de quoi les cartes de prévisualisation des liens ne seront pas mises à jour à la demande avant cette échéance. min_age: Les utilisateurs seront invités à confirmer leur date de naissance lors de l'inscription @@ -102,10 +109,9 @@ fr-CA: status_page_url: URL d'une page où les gens peuvent voir l'état de ce serveur en cas de panne theme: Thème que verront les utilisateur·rice·s déconnecté·e·s ainsi que les nouveaux·elles utilisateur·rice·s. thumbnail: Une image d'environ 2:1 affichée à côté des informations de votre serveur. - timeline_preview: Les visiteurs déconnectés pourront parcourir les derniers messages publics disponibles sur le serveur. trendable_by_default: Ignorer l'examen manuel du contenu tendance. Des éléments individuels peuvent toujours être supprimés des tendances après coup. trends: Les tendances montrent quelles publications, hashtags et actualités sont en train de gagner en traction sur votre serveur. - trends_as_landing_page: Afficher le contenu tendance au lieu d'une description de ce serveur pour les comptes déconnectés et les non-inscrit⋅e⋅s. Nécessite que les tendances soient activées. + wrapstodon: Offrez aux comptes locaux de générer un récapitulatif annuel de leur utilisation de Mastodon. Cette fonctionnalité est disponible chaque année du 10 au 31 décembre, et est accessible pour les comptes ayant publié au moins un message Public ou Public discret et utilisé au moins un hashtag dans l'année. form_challenge: current_password: Vous entrez une zone sécurisée imports: @@ -148,6 +154,9 @@ fr-CA: min_age: Ne doit pas être en dessous de l’âge minimum requis par les lois de votre juridiction. user: chosen_languages: Lorsque coché, seuls les messages dans les langues sélectionnées seront affichés sur les fils publics + date_of_birth: + one: Nous devons vérifier que vous avez au moins %{count} an pour utiliser %{domain}. Cette information ne sera pas conservée. + other: Nous devons vérifier que vous avez au moins %{count} ans pour utiliser %{domain}. Cette information ne sera pas conservée. role: Le rôle définit quelles autorisations a l'utilisateur⋅rice. user_role: color: Couleur à attribuer au rôle dans l'interface, au format hexadécimal RVB @@ -155,6 +164,10 @@ fr-CA: name: Nom public du rôle, si le rôle est configuré pour être affiché avec un badge permissions_as_keys: Les utilisateur·rice·s ayant ce rôle auront accès à … position: Dans certaines situations, un rôle supérieur peut trancher la résolution d'un conflit. Mais certaines opérations ne peuvent être effectuées que sur des rôles ayant une priorité inférieure + username_block: + allow_with_approval: Au lieu de bloquer l'inscription, les inscriptions correspondantes nécessiteront votre approbation + comparison: Veuillez garder à l'esprit le problème de Scunthorpe lors du blocage des correspondances partielles + username: Correspondra peu importe la casse utilisée et pour les homoglyphes courants – par exemple « 4 » pour « a » ou « 3 » pour « e » webhook: events: Sélectionnez les événements à envoyer template: Écrivez votre propre bloc JSON avec la possibilité d’utiliser de l’interpolation de variables. Laissez vide pour le bloc JSON par défaut. @@ -225,20 +238,23 @@ fr-CA: setting_aggregate_reblogs: Grouper les partages dans les fils d’actualités setting_always_send_emails: Toujours envoyer les notifications par courriel setting_auto_play_gif: Lire automatiquement les GIFs animés - setting_boost_modal: Demander confirmation avant de partager un message + setting_boost_modal: Configurer la visibilité du partage setting_default_language: Langue de publication + setting_default_privacy: Visibilité de la publication setting_default_quote_policy: Autoriser les citations pour setting_default_sensitive: Toujours marquer les médias comme sensibles - setting_delete_modal: Demander confirmation avant de supprimer un message + setting_delete_modal: M'avertir avant de supprimer un message setting_disable_hover_cards: Désactiver l'aperçu du profil au survol setting_disable_swiping: Désactiver les actions par glissement setting_display_media: Affichage des médias setting_display_media_default: Défaut setting_display_media_hide_all: Masquer tout setting_display_media_show_all: Montrer tout + setting_emoji_style: Style des émojis setting_expand_spoilers: Toujours déplier les messages marqués d’un avertissement de contenu setting_hide_network: Cacher votre réseau - setting_missing_alt_text_modal: Afficher une fenêtre de confirmation avant de poster un média sans texte alternatif + setting_missing_alt_text_modal: M'avertir avant de publier des médias sans texte alternatif + setting_quick_boosting: Activer le partage rapide setting_reduce_motion: Réduire la vitesse des animations setting_system_font_ui: Utiliser la police par défaut du système setting_system_scrollbars_ui: Utiliser la barre de défilement par défaut du système @@ -272,12 +288,17 @@ fr-CA: content_cache_retention_period: Durée de rétention du contenu distant custom_css: CSS personnalisé favicon: Favicon + landing_page: Page d'accueil pour les nouveaux visiteurs + local_live_feed_access: Accès au flux en direct de ce serveur + local_topic_feed_access: Accès aux flux hashtag et lien de ce serveur mascot: Mascotte personnalisée (héritée) media_cache_retention_period: Durée de rétention des médias dans le cache min_age: Âge minimum requis peers_api_enabled: Publie la liste des serveurs découverts dans l'API profile_directory: Activer l’annuaire des profils registrations_mode: Qui peut s’inscrire + remote_live_feed_access: Accès au flux en direct des autres serveurs + remote_topic_feed_access: Accès aux flux hashtag et lien des autres serveurs require_invite_text: Exiger une raison pour s’inscrire show_domain_blocks: Afficher les blocages de domaines show_domain_blocks_rationale: Montrer pourquoi les domaines ont été bloqués @@ -290,10 +311,9 @@ fr-CA: status_page_url: URL de la page de l'état du serveur theme: Thème par défaut thumbnail: Miniature du serveur - timeline_preview: Autoriser l’accès non authentifié aux fils publics trendable_by_default: Autoriser les tendances sans révision préalable trends: Activer les tendances - trends_as_landing_page: Utiliser les tendances comme page d'accueil + wrapstodon: Activer Wrapstodon interactions: must_be_follower: Bloquer les notifications des personnes qui ne vous suivent pas must_be_following: Bloquer les notifications des personnes que vous ne suivez pas @@ -318,6 +338,7 @@ fr-CA: follow_request: Quelqu’un demande à me suivre mention: Quelqu’un me mentionne pending_account: Nouveau compte en attente d’approbation + quote: Quelqu'un vous a cité reblog: Quelqu’un a partagé mon message report: Nouveau signalement soumis software_updates: @@ -353,9 +374,9 @@ fr-CA: jurisdiction: Juridiction min_age: Âge minimum user: - date_of_birth_1i: Jour + date_of_birth_1i: Année date_of_birth_2i: Mois - date_of_birth_3i: Année + date_of_birth_3i: Jour role: Rôle time_zone: Fuseau horaire user_role: @@ -364,6 +385,10 @@ fr-CA: name: Nom permissions_as_keys: Autorisations position: Priorité + username_block: + allow_with_approval: Autoriser les inscriptions avec approbation + comparison: Méthode de comparaison + username: Mot à faire correspondre webhook: events: Événements activés template: Modèle de charge utile diff --git a/config/locales/simple_form.fr.yml b/config/locales/simple_form.fr.yml index 83eef109363918..77f0917fd8ec4e 100644 --- a/config/locales/simple_form.fr.yml +++ b/config/locales/simple_form.fr.yml @@ -31,7 +31,7 @@ fr: suspend: Empêcher toute interaction depuis ou vers ce compte et supprimer son contenu. Réversible dans les 30 jours. Cloture tous les signalements concernant ce compte. warning_preset_id: Facultatif. Vous pouvez toujours ajouter un texte personnalisé à la fin de la présélection announcement: - all_day: Coché, seules les dates de l’intervalle de temps seront affichées + all_day: Lorsque coché, seules les dates de l’intervalle de temps seront affichées ends_at: Facultatif. La fin de l'annonce surviendra automatiquement à ce moment scheduled_at: Laisser vide pour publier l’annonce immédiatement starts_at: Facultatif. Si votre annonce est liée à une période spécifique @@ -54,12 +54,18 @@ fr: password: Utilisez au moins 8 caractères phrase: Sera filtré peu importe la casse ou l’avertissement de contenu du message scopes: À quelles APIs l’application sera autorisée à accéder. Si vous sélectionnez une permission générale, vous n’avez pas besoin de sélectionner les permissions plus précises. + setting_advanced_layout: Afficher Mastodon avec une mise en page multicolonnes, vous permettant de visualiser le flux, les notifications et une troisième colonne de votre choix. Non recommandé pour les écrans plus petits. setting_aggregate_reblogs: Ne pas afficher les nouveaux partages pour les messages déjà récemment partagés (n’affecte que les partages futurs) setting_always_send_emails: Normalement, les notifications par courriel ne seront pas envoyées lorsque vous utilisez Mastodon activement + setting_boost_modal: Lorsque cette option est activée, avant de partager un message un écran de confirmation vous permettra de changer la visibilité du partage. + setting_default_quote_policy_private: Les messages limités aux personnes qui vous suivent publiés depuis Mastodon ne peuvent pas être cités. + setting_default_quote_policy_unlisted: Lorsque des personnes vous citent, leur message sera également masqué des fils des tendances. setting_default_sensitive: Les médias sensibles sont cachés par défaut et peuvent être révélés d’un simple clic setting_display_media_default: Masquer les médias marqués comme sensibles setting_display_media_hide_all: Toujours masquer les médias setting_display_media_show_all: Toujours afficher les médias + setting_emoji_style: Manière d'afficher les émojis. Utiliser « Auto » pour essayer d'utiliser les émojis natifs, mais Twemoji sera utilisé pour les anciens navigateurs. + setting_quick_boosting_html: Lorsque cette option est activée, cliquer sur l'icône de partage %{boost_icon} va immédiatement partager le message au lieu d'ouvrir le menu déroulant Partage/Citation. L'action de citation est déplacée dans le menu %{options_icon} (options). setting_system_scrollbars_ui: S'applique uniquement aux navigateurs basés sur Safari et Chrome setting_use_blurhash: Les dégradés sont basés sur les couleurs des images cachées mais n’en montrent pas les détails setting_use_pending_items: Cacher les mises à jour des fils d’actualités derrière un clic, au lieu de les afficher automatiquement @@ -82,11 +88,12 @@ fr: activity_api_enabled: Nombre de messages publiés localement, de comptes actifs et de nouvelles inscriptions par tranche hebdomadaire app_icon: WEBP, PNG, GIF ou JPG. Remplace la favicon Mastodon par défaut avec une icône personnalisée. backups_retention_period: Les utilisateur·rice·s ont la possibilité de générer des archives de leurs messages pour les télécharger plus tard. Lorsqu'elles sont définies à une valeur positive, ces archives seront automatiquement supprimées de votre stockage après le nombre de jours spécifié. - bootstrap_timeline_accounts: Ces comptes seront épinglés en tête de liste des recommandations pour les nouveaux utilisateurs. + bootstrap_timeline_accounts: Ces comptes seront épinglés en haut des recommandations pour les nouveaux utilisateurs. Accepte une liste de comptes séparés par des virgules. closed_registrations_message: Affiché lorsque les inscriptions sont fermées content_cache_retention_period: Tous les messages provenant d'autres serveurs (y compris les partages et les réponses) seront supprimés passé le nombre de jours spécifié, sans tenir compte de l'interaction de l'utilisateur·rice local·e avec ces messages. Cela inclut les messages qu'un·e utilisateur·rice aurait marqué comme signets ou comme favoris. Les mentions privées entre utilisateur·rice·s de différentes instances seront également perdues et impossibles à restaurer. L'utilisation de ce paramètre est destinée à des instances spécifiques et contrevient à de nombreuses attentes des utilisateurs lorsqu'elle est appliquée à des fins d'utilisation ordinaires. custom_css: Vous pouvez appliquer des styles personnalisés sur la version Web de Mastodon. favicon: WEBP, PNG, GIF ou JPG. Remplace la favicon Mastodon par défaut avec une icône personnalisée. + landing_page: Sélectionner la page à afficher aux nouveaux visiteurs quand ils arrivent sur votre serveur. Pour utiliser « Tendances » les tendances doivent être activées dans les paramètres de découverte. Pour utiliser « Fil local » le paramètre « Accès au flux en direct de ce serveur » doit être défini sur « Tout le monde » dans les paramètres de découverte. mascot: Remplace l'illustration dans l'interface Web avancée. media_cache_retention_period: Les fichiers médias des messages publiés par des utilisateurs distants sont mis en cache sur votre serveur. Lorsque cette valeur est positive, les médias sont supprimés au terme du nombre de jours spécifié. Si les données des médias sont demandées après leur suppression, elles seront téléchargées à nouveau, dans la mesure où le contenu source est toujours disponible. En raison des restrictions concernant la fréquence à laquelle les cartes de prévisualisation des liens interrogent des sites tiers, il est recommandé de fixer cette valeur à au moins 14 jours, faute de quoi les cartes de prévisualisation des liens ne seront pas mises à jour à la demande avant cette échéance. min_age: Les utilisateurs seront invités à confirmer leur date de naissance lors de l'inscription @@ -102,10 +109,9 @@ fr: status_page_url: URL d'une page où les gens peuvent voir l'état de ce serveur en cas de panne theme: Thème que verront les utilisateur·rice·s déconnecté·e·s ainsi que les nouveaux·elles utilisateur·rice·s. thumbnail: Une image d'environ 2:1 affichée à côté des informations de votre serveur. - timeline_preview: Les utilisateur⋅rice⋅s déconnecté⋅e⋅s pourront parcourir les derniers messages publics disponibles sur le serveur. trendable_by_default: Ignorer l'examen manuel du contenu tendance. Des éléments individuels peuvent toujours être supprimés des tendances après coup. trends: Les tendances montrent quels messages, hashtags et actualités gagnent en popularité sur votre serveur. - trends_as_landing_page: Afficher le contenu tendance au lieu d'une description de ce serveur pour les comptes déconnectés et les non-inscrit⋅e⋅s. Nécessite que les tendances soient activées. + wrapstodon: Offrez aux comptes locaux de générer un récapitulatif annuel de leur utilisation de Mastodon. Cette fonctionnalité est disponible chaque année du 10 au 31 décembre, et est accessible pour les comptes ayant publié au moins un message Public ou Public discret et utilisé au moins un hashtag dans l'année. form_challenge: current_password: Vous entrez une zone sécurisée imports: @@ -148,6 +154,9 @@ fr: min_age: Ne doit pas être en dessous de l’âge minimum requis par les lois de votre juridiction. user: chosen_languages: Lorsque coché, seuls les messages dans les langues sélectionnées seront affichés sur les fils publics + date_of_birth: + one: Nous devons vérifier que vous avez au moins %{count} an pour utiliser %{domain}. Cette information ne sera pas conservée. + other: Nous devons vérifier que vous avez au moins %{count} ans pour utiliser %{domain}. Cette information ne sera pas conservée. role: Le rôle définit quelles autorisations a l'utilisateur⋅rice. user_role: color: Couleur à attribuer au rôle dans l'interface, au format hexadécimal RVB @@ -155,6 +164,10 @@ fr: name: Nom public du rôle, si le rôle est configuré pour être affiché avec un badge permissions_as_keys: Les utilisateur·rice·s ayant ce rôle auront accès à … position: Dans certaines situations, un rôle supérieur peut trancher la résolution d'un conflit. Mais certaines opérations ne peuvent être effectuées que sur des rôles ayant une priorité inférieure + username_block: + allow_with_approval: Au lieu de bloquer l'inscription, les inscriptions correspondantes nécessiteront votre approbation + comparison: Veuillez garder à l'esprit le problème de Scunthorpe lors du blocage des correspondances partielles + username: Correspondra peu importe la casse utilisée et pour les homoglyphes courants – par exemple « 4 » pour « a » ou « 3 » pour « e » webhook: events: Sélectionnez les événements à envoyer template: Écrivez votre propre bloc JSON avec la possibilité d’utiliser de l’interpolation de variables. Laissez vider pour utiliser le bloc JSON par défaut. @@ -225,20 +238,23 @@ fr: setting_aggregate_reblogs: Grouper les partages dans les fils d’actualités setting_always_send_emails: Toujours envoyer les notifications par courriel setting_auto_play_gif: Lire automatiquement les GIFs animés - setting_boost_modal: Demander confirmation avant de partager un message + setting_boost_modal: Configurer la visibilité du partage setting_default_language: Langue de publication + setting_default_privacy: Visibilité de la publication setting_default_quote_policy: Autoriser les citations pour setting_default_sensitive: Toujours marquer les médias comme sensibles - setting_delete_modal: Demander confirmation avant de supprimer un message + setting_delete_modal: M'avertir avant de supprimer un message setting_disable_hover_cards: Désactiver l'aperçu du profil au survol setting_disable_swiping: Désactiver les actions par glissement setting_display_media: Affichage des médias setting_display_media_default: Défaut setting_display_media_hide_all: Masquer tout setting_display_media_show_all: Montrer tout + setting_emoji_style: Style des émojis setting_expand_spoilers: Toujours déplier les messages marqués d’un avertissement de contenu setting_hide_network: Cacher votre réseau - setting_missing_alt_text_modal: Afficher une fenêtre de confirmation avant de poster un média sans texte alternatif + setting_missing_alt_text_modal: M'avertir avant de publier des médias sans texte alternatif + setting_quick_boosting: Activer le partage rapide setting_reduce_motion: Réduire la vitesse des animations setting_system_font_ui: Utiliser la police par défaut du système setting_system_scrollbars_ui: Utiliser la barre de défilement par défaut du système @@ -272,12 +288,17 @@ fr: content_cache_retention_period: Durée de rétention du contenu distant custom_css: CSS personnalisé favicon: Favicon + landing_page: Page d'accueil pour les nouveaux visiteurs + local_live_feed_access: Accès au flux en direct de ce serveur + local_topic_feed_access: Accès aux flux hashtag et lien de ce serveur mascot: Mascotte personnalisée (héritée) media_cache_retention_period: Durée de rétention des médias dans le cache min_age: Âge minimum requis peers_api_enabled: Publie la liste des serveurs découverts dans l'API profile_directory: Activer l’annuaire des profils registrations_mode: Qui peut s’inscrire + remote_live_feed_access: Accès au flux en direct des autres serveurs + remote_topic_feed_access: Accès aux flux hashtag et lien des autres serveurs require_invite_text: Exiger une raison pour s’inscrire show_domain_blocks: Afficher les blocages de domaines show_domain_blocks_rationale: Montrer pourquoi les domaines ont été bloqués @@ -290,10 +311,9 @@ fr: status_page_url: URL de la page de l'état du serveur theme: Thème par défaut thumbnail: Miniature du serveur - timeline_preview: Autoriser l’accès non authentifié aux fils publics trendable_by_default: Autoriser les tendances sans révision préalable trends: Activer les tendances - trends_as_landing_page: Utiliser les tendances comme page d'accueil + wrapstodon: Activer Wrapstodon interactions: must_be_follower: Bloquer les notifications des personnes qui ne vous suivent pas must_be_following: Bloquer les notifications des personnes que vous ne suivez pas @@ -318,6 +338,7 @@ fr: follow_request: Quelqu’un demande à vous suivre mention: Quelqu’un vous a mentionné⋅e pending_account: Nouveau compte en attente d’approbation + quote: Quelqu'un vous a cité reblog: Quelqu’un a partagé votre message report: Nouveau signalement soumis software_updates: @@ -353,9 +374,9 @@ fr: jurisdiction: Juridiction min_age: Âge minimum user: - date_of_birth_1i: Jour + date_of_birth_1i: Année date_of_birth_2i: Mois - date_of_birth_3i: Année + date_of_birth_3i: Jour role: Rôle time_zone: Fuseau horaire user_role: @@ -364,6 +385,10 @@ fr: name: Nom permissions_as_keys: Autorisations position: Priorité + username_block: + allow_with_approval: Autoriser les inscriptions avec approbation + comparison: Méthode de comparaison + username: Mot à faire correspondre webhook: events: Événements activés template: Modèle de payload diff --git a/config/locales/simple_form.fy.yml b/config/locales/simple_form.fy.yml index 4345f2f6a70a8b..7b88eee24f6693 100644 --- a/config/locales/simple_form.fy.yml +++ b/config/locales/simple_form.fy.yml @@ -74,7 +74,6 @@ fy: featured_tag: name: 'Hjir binne inkelde fan de hashtags dy’t jo koartlyn brûkt hawwe:' filters: - action: Kies hokker aksjes útfierd wurde moatte, wannear’t in berjocht oerienkomt mei it filter actions: blur: Media ferstopje efter in warskôging, sûnder de tekst sels te ferstopjen hide: Ferstopje de filtere ynhâld folslein, as oft it net bestiet @@ -83,7 +82,6 @@ fy: activity_api_enabled: Tal lokaal publisearre artikelen, aktive brûkers en nije registraasjes yn wyklikse werjefte app_icon: WEBP, PNG, GIF of JPG. Ferfangt op mobile apparaten it standert app-pictogram mei in oanpast piktogram. backups_retention_period: Brûkers hawwe de mooglikheid om argiven fan harren berjochten te generearjen om letter te downloaden. Wannear ynsteld op in positive wearde, wurde dizze argiven automatysk fuortsmiten út jo ûnthâld nei it opjûne oantal dagen. - bootstrap_timeline_accounts: Dizze accounts wurde boppe oan de oanrekommandaasjes oan nije brûkers toand. Meardere brûkersnammen troch komma’s skiede. closed_registrations_message: Werjûn wannear’t registraasje fan nije accounts útskeakele is content_cache_retention_period: Alle berjochten fan oare servers (ynklusyf boosts en reaksjes) wurde fuortsmiten nei it opjûne oantal dagen, nettsjinsteande iennige lokale brûkersynteraksje mei dy berjochten. Dit oanbelanget ek berjochten dy’t in lokale brûker oan harren blêdwizers tafoege hat of as favoryt markearre hat. Priveeberjochten tusken brûkers fan ferskate servers gean ek ferlern en binne ûnmooglik te werstellen. It gebrûk fan dizze ynstelling is bedoeld foar servers dy’t in spesjaal doel tsjinje en oertrêdet in protte brûkersferwachtingen wannear’t dizze foar algemien gebrûk ymplemintearre wurdt. custom_css: Jo kinne oanpaste CSS tapasse op de webferzje fan dizze Mastodon-server. @@ -103,10 +101,8 @@ fy: status_page_url: URL fan in side dêr’t minsken de steat fan dizze server sjen kinne wylst in steuring theme: Tema dy’t ôfmelde besikers en nije brûkers sjen. thumbnail: In ôfbylding fan ûngefear in ferhâlding fan 2:1 dy’t njonken jo serverynformaasje toand wurdt. - timeline_preview: Net oanmelde besikers kinne de meast resinte, op de server oanwêzige iepenbiere berjochten besjen. trendable_by_default: Hânmjittige beoardieling fan trends oerslaan. Yndividuele items kinne letter dochs noch ôfkard wurde. trends: Trends toane hokker berjochten, hashtags en nijsberjochten op jo server oan populariteit winne. - trends_as_landing_page: Toan trending ynhâld oan ôfmelde brûkers en besikers yn stee fan in beskriuwing fan dizze server. Fereasket dat trends ynskeakele binne. form_challenge: current_password: Jo betrêdzje in feilige omjouwing imports: @@ -229,11 +225,9 @@ fy: setting_aggregate_reblogs: Boosts yn tiidlinen groepearje setting_always_send_emails: Altyd e-mailmeldingen ferstjoere setting_auto_play_gif: Spylje animearre GIF’s automatysk ôf - setting_boost_modal: Freegje foar it boosten fan in berjocht in befêstiging setting_default_language: Taal fan jo berjochten setting_default_quote_policy: Wa kin sitearje setting_default_sensitive: Media altyd as gefoelich markearje - setting_delete_modal: Freegje foar it fuortsmiten fan in berjocht in befêstiging setting_disable_hover_cards: Profylfoarbylden troch der oerhinne te sweven útskeakelje setting_disable_swiping: Feibewegingen útskeakelje setting_display_media: Mediawerjefte @@ -243,7 +237,6 @@ fy: setting_emoji_style: Emojistyl setting_expand_spoilers: Berjochten mei ynhâldswarskôgingen altyd útklappe setting_hide_network: Jo folgers en wa’t jo folget ferstopje - setting_missing_alt_text_modal: Befêstigingsfinster toane foar it pleatsen fan media sûnder alt-tekst setting_reduce_motion: Stadigere animaasjes setting_system_font_ui: Standertlettertype fan jo systeem brûke setting_system_scrollbars_ui: Standert skowbalke fan jo systeem brûke @@ -295,10 +288,8 @@ fy: status_page_url: URL fan steatside theme: Standerttema thumbnail: Serverthumbnail - timeline_preview: Tagong ta de iepenbiere tiidlinen sûnder oan te melden tastean trendable_by_default: Trends goedkarre sûnder yn it foar geande beoardieling trends: Trends ynskeakelje - trends_as_landing_page: Lit trends op de startside sjen interactions: must_be_follower: Meldingen fan minsken dy’t jo net folgje blokkearje must_be_following: Meldingen fan minsken dy’t jo net folgje blokkearje @@ -358,9 +349,7 @@ fy: jurisdiction: Rjochtsgebiet min_age: Minimum leeftiid user: - date_of_birth_1i: Dei date_of_birth_2i: Moanne - date_of_birth_3i: Jier role: Rol time_zone: Tiidsône user_role: diff --git a/config/locales/simple_form.ga.yml b/config/locales/simple_form.ga.yml index c7e143743d99aa..f8f8b09000c944 100644 --- a/config/locales/simple_form.ga.yml +++ b/config/locales/simple_form.ga.yml @@ -54,8 +54,10 @@ ga: password: Úsáid ar a laghad 8 gcarachtar phrase: Déanfar é a mheaitseáil beag beann ar chásáil an téacs nó ar an ábhar atá ag tabhairt foláireamh do phostáil scopes: Cé na APIanna a mbeidh cead ag an bhfeidhmchlár rochtain a fháil orthu. Má roghnaíonn tú raon feidhme barrleibhéil, ní gá duit cinn aonair a roghnú. + setting_advanced_layout: Taispeáin Mastodon mar leagan amach ilcholún, rud a ligeann duit an t-amlíne, fógraí, agus tríú colún de do rogha féin a fheiceáil. Ní mholtar é seo do scáileáin níos lú. setting_aggregate_reblogs: Ná taispeáin treisithe nua do phoist a treisíodh le déanaí (ní dhéanann difear ach do threisithe nuafhaighte) setting_always_send_emails: Go hiondúil ní sheolfar fógraí ríomhphoist agus tú ag úsáid Mastodon go gníomhach + setting_boost_modal: Nuair a bheidh sé cumasaithe, osclóidh borradh dialóg deimhnithe ar dtús inar féidir leat infheictheacht do bhorrtha a athrú. setting_default_quote_policy_private: Ní féidir le daoine eile poist atá scríofa ar Mastodon agus atá dírithe ar leanúna amháin a lua. setting_default_quote_policy_unlisted: Nuair a luann daoine thú, beidh a bpost i bhfolach ó amlínte treochta freisin. setting_default_sensitive: Tá meáin íogair i bhfolach de réir réamhshocraithe agus is féidir iad a nochtadh le cliceáil @@ -63,6 +65,7 @@ ga: setting_display_media_hide_all: Folaigh meáin i gcónaí setting_display_media_show_all: Taispeáin meáin i gcónaí setting_emoji_style: Conas emojis a thaispeáint. Déanfaidh "Auto" iarracht emoji dúchasacha a úsáid, ach titeann sé ar ais go Twemoji le haghaidh seanbhrabhsálaithe. + setting_quick_boosting_html: Nuair a bhíonn sé cumasaithe, má chliceálann tú ar an deilbhín Treisithe %{boost_icon}, treiseofar láithreach é in ionad an roghchlár anuas treisithe/lua a oscailt. Bogann sé seo an gníomh lua go dtí an roghchlár %{options_icon} (Roghanna). setting_system_scrollbars_ui: Ní bhaineann sé ach le brabhsálaithe deisce bunaithe ar Safari agus Chrome setting_use_blurhash: Tá grádáin bunaithe ar dhathanna na n-amharcanna ceilte ach cuireann siad salach ar aon mhionsonraí setting_use_pending_items: Folaigh nuashonruithe amlíne taobh thiar de chlic seachas an fotha a scrollú go huathoibríoch @@ -76,7 +79,7 @@ ga: featured_tag: name: 'Seo cuid de na hashtags a d’úsáid tú le déanaí:' filters: - action: Roghnaigh an gníomh ba cheart a dhéanamh nuair a mheaitseálann postáil an scagaire + action: Roghnaigh cén gníomh atá le déanamh nuair a mheaitseálann post an scagaire actions: blur: Folaigh na meáin taobh thiar de rabhadh, gan an téacs féin a cheilt hide: Cuir an t-ábhar scagtha i bhfolach go hiomlán, ag iompar amhail is nach raibh sé ann @@ -85,11 +88,12 @@ ga: activity_api_enabled: Áireamh na bpost a foilsíodh go háitiúil, úsáideoirí gníomhacha, agus clárúcháin nua i buicéid seachtainiúla app_icon: WEBP, PNG, GIF nó JPG. Sáraíonn sé an deilbhín réamhshocraithe aipe ar ghléasanna soghluaiste le deilbhín saincheaptha. backups_retention_period: Tá an cumas ag úsáideoirí cartlanna dá gcuid post a ghiniúint le híoslódáil níos déanaí. Nuair a bheidh luach dearfach socraithe, scriosfar na cartlanna seo go huathoibríoch ó do stór tar éis an líon sonraithe laethanta. - bootstrap_timeline_accounts: Cuirfear na cuntais seo ar bharr na moltaí a leanann úsáideoirí nua. + bootstrap_timeline_accounts: Beidh na cuntais seo bioráilte ag barr mholtaí leantacha úsáideoirí nua. Cuir liosta cuntas ar fáil atá scartha le camóga. closed_registrations_message: Ar taispeáint nuair a dhúntar clárúcháin content_cache_retention_period: Scriosfar gach postáil ó fhreastalaithe eile (lena n-áirítear treisithe agus freagraí) tar éis an líon sonraithe laethanta, gan aird ar aon idirghníomhaíocht úsáideora áitiúil leis na postálacha sin. Áirítear leis seo postálacha ina bhfuil úsáideoir áitiúil tar éis é a mharcáil mar leabharmharcanna nó mar cheanáin. Caillfear tagairtí príobháideacha idir úsáideoirí ó chásanna éagsúla freisin agus ní féidir iad a athchóiriú. Tá úsáid an tsocraithe seo beartaithe le haghaidh cásanna sainchuspóra agus sáraítear go leor ionchais úsáideoirí nuair a chuirtear i bhfeidhm é le haghaidh úsáid ghinearálta. custom_css: Is féidir leat stíleanna saincheaptha a chur i bhfeidhm ar an leagan gréasáin de Mastodon. favicon: WEBP, PNG, GIF nó JPG. Sáraíonn sé an favicon Mastodon réamhshocraithe le deilbhín saincheaptha. + landing_page: Roghnaíonn sé seo an leathanach a fheiceann cuairteoirí nua nuair a shroicheann siad do fhreastalaí den chéad uair. Má roghnaíonn tú "Treochtaí", ní mór treochtaí a chumasú sna Socruithe Fionnachtana. Má roghnaíonn tú "Fotha Áitiúil", ní mór "Rochtain ar fhothaí beo ina bhfuil poist áitiúla" a shocrú go "Gach Duine" sna Socruithe Fionnachtana. mascot: Sáraíonn sé an léaráid san ardchomhéadan gréasáin. media_cache_retention_period: Déantar comhaid meán ó phoist a dhéanann cianúsáideoirí a thaisceadh ar do fhreastalaí. Nuair a bheidh luach dearfach socraithe, scriosfar na meáin tar éis an líon sonraithe laethanta. Má iarrtar na sonraí meán tar éis é a scriosadh, déanfar é a ath-íoslódáil, má tá an t-ábhar foinse fós ar fáil. Mar gheall ar shrianta ar cé chomh minic is atá cártaí réamhamhairc ag vótaíocht do shuíomhanna tríú páirtí, moltar an luach seo a shocrú go 14 lá ar a laghad, nó ní dhéanfar cártaí réamhamhairc naisc a nuashonrú ar éileamh roimh an am sin. min_age: Iarrfar ar úsáideoirí a ndáta breithe a dhearbhú le linn clárúcháin @@ -105,10 +109,9 @@ ga: status_page_url: URL leathanach inar féidir le daoine stádas an fhreastalaí seo a fheiceáil le linn briseadh amach theme: Téama a fheiceann cuairteoirí logáilte amach agus úsáideoirí nua. thumbnail: Íomhá thart ar 2:1 ar taispeáint taobh le faisnéis do fhreastalaí. - timeline_preview: Beidh cuairteoirí logáilte amach in ann na postálacha poiblí is déanaí atá ar fáil ar an bhfreastalaí a bhrabhsáil. trendable_by_default: Léim ar athbhreithniú láimhe ar ábhar treochta. Is féidir míreanna aonair a bhaint as treochtaí fós tar éis an fhíric. trends: Léiríonn treochtaí cé na postálacha, hashtags agus scéalta nuachta atá ag tarraingt ar do fhreastalaí. - trends_as_landing_page: Taispeáin inneachar treochta d'úsáideoirí agus do chuairteoirí atá logáilte amach in ionad cur síos ar an bhfreastalaí seo. Éilíonn treochtaí a chumasú. + wrapstodon: Iarr ar úsáideoirí áitiúla achoimre spraíúil a ghiniúint ar a n-úsáid Mastodon i rith na bliana. Bíonn an ghné seo ar fáil idir an 10ú agus an 31ú Nollaig gach bliain, agus tairgtear é d’úsáideoirí a rinne post Poiblí nó Ciúin Poiblí amháin ar a laghad agus a d’úsáid hais clib amháin ar a laghad laistigh den bhliain. form_challenge: current_password: Tá tú ag dul isteach i limistéar slán imports: @@ -238,12 +241,12 @@ ga: setting_aggregate_reblogs: Treisithe grúpa i línte ama setting_always_send_emails: Seol fógraí ríomhphoist i gcónaí setting_auto_play_gif: Gifs beoite go huathoibríoch a imirt - setting_boost_modal: Taispeáin dialóg deimhnithe roimh threisiú + setting_boost_modal: Rialú a fheabhsaíonn infheictheacht setting_default_language: Teanga postála setting_default_privacy: Infheictheacht postála setting_default_quote_policy: Cé a fhéadfaidh lua setting_default_sensitive: Marcáil na meáin mar íogair i gcónaí - setting_delete_modal: Taispeáin dialóg deimhnithe sula scriostar postáil + setting_delete_modal: Tabhair rabhadh dom sula scriostar post setting_disable_hover_cards: Díchumasaigh réamhamharc próifíle ar ainlíon setting_disable_swiping: Díchumasaigh gluaiseachtaí swiping setting_display_media: Taispeáint meáin @@ -253,7 +256,8 @@ ga: setting_emoji_style: Stíl Emoji setting_expand_spoilers: Méadaigh postálacha atá marcáilte le rabhaidh inneachair i gcónaí setting_hide_network: Folaigh do ghraf sóisialta - setting_missing_alt_text_modal: Taispeáin dialóg deimhnithe sula bpostálann tú meán gan alt téacs + setting_missing_alt_text_modal: Tabhair rabhadh dom sula bpostálann tú meáin gan téacs malartach + setting_quick_boosting: Cumasaigh borradh tapa setting_reduce_motion: Laghdú ar an tairiscint i beochan setting_system_font_ui: Úsáid cló réamhshocraithe an chórais setting_system_scrollbars_ui: Bain úsáid as scrollbharra réamhshocraithe an chórais @@ -287,12 +291,17 @@ ga: content_cache_retention_period: Tréimhse choinneála inneachair cianda custom_css: CSS saincheaptha favicon: Favicon + landing_page: Leathanach tuirlingthe do chuairteoirí nua + local_live_feed_access: Rochtain ar bheatha bheo ina bhfuil poist áitiúla + local_topic_feed_access: Rochtain ar fhothaí hashtag agus nasc ina bhfuil poist áitiúla mascot: Mascóg saincheaptha (oidhreacht) media_cache_retention_period: Tréimhse choinneála taisce meán min_age: Riachtanas aoise íosta peers_api_enabled: Foilsigh liosta de na freastalaithe aimsithe san API profile_directory: Cumasaigh eolaire próifíle registrations_mode: Cé atá in ann clárú + remote_live_feed_access: Rochtain ar bheatha bheo ina bhfuil poist iargúlta + remote_topic_feed_access: Rochtain ar fhothaí hashtag agus nasc ina bhfuil poist iargúlta require_invite_text: A cheangal ar chúis a bheith páirteach show_domain_blocks: Taispeáin bloic fearainn show_domain_blocks_rationale: Taispeáin cén fáth ar cuireadh bac ar fhearann @@ -305,10 +314,9 @@ ga: status_page_url: URL an leathanaigh stádais theme: Téama réamhshocraithe thumbnail: Mionsamhail freastalaí - timeline_preview: Ceadaigh rochtain neamhdheimhnithe ar amlínte poiblí trendable_by_default: Ceadaigh treochtaí gan athbhreithniú roimh ré trends: Cumasaigh treochtaí - trends_as_landing_page: Úsáid treochtaí mar an leathanach tuirlingthe + wrapstodon: Cumasaigh Wrapstodon interactions: must_be_follower: Cuir bac ar fhógraí ó dhaoine nach leantóirí iad must_be_following: Cuir bac ar fhógraí ó dhaoine nach leanann tú @@ -369,9 +377,9 @@ ga: jurisdiction: Dlínse dhlíthiúil min_age: Aois íosta user: - date_of_birth_1i: Lá + date_of_birth_1i: Bliain date_of_birth_2i: Mí - date_of_birth_3i: Bliain + date_of_birth_3i: Lá role: Ról time_zone: Crios ama user_role: diff --git a/config/locales/simple_form.gd.yml b/config/locales/simple_form.gd.yml index 2464955a43e5be..e11b300b33a316 100644 --- a/config/locales/simple_form.gd.yml +++ b/config/locales/simple_form.gd.yml @@ -54,8 +54,10 @@ gd: password: Cleachd co-dhiù 8 caractaran phrase: Thèid a mhaidseadh gun aire air litrichean mòra ’s beaga no air rabhadh susbainte puist scopes: Na APIan a dh’fhaodas an aplacaid inntrigeadh. Ma thaghas tu sgòp air ìre as àirde, cha leig thu leas sgòpaichean fa leth a thaghadh. + setting_advanced_layout: Seall Mastodon ’na ioma-cholbh ach am faic thu an loidhne-ama, na brathan agus treas colbh a thaghas tu fhèin. Cha mholamaid seo air sgrìnichean beaga. setting_aggregate_reblogs: Na seall brosnachaidhean ùra do phostaichean a chaidh a bhrosnachadh o chionn goirid (cha doir seo buaidh ach air brosnachaidhean ùra o seo a-mach) setting_always_send_emails: Mar as àbhaist, cha dèid brathan puist-d a chur nuair a a bhios tu ri Mastodon gu cunbhalach + setting_boost_modal: Ma tha seo an comas, thèid còmhradh dearbhaidh a shealltainn far an urrainn dhut faicsinneachd a’ phuist agad atharrachadh nuair a bhios tu ri brosnachadh. setting_default_quote_policy_private: Chan urrainn do chàch postaichean dhan luchd-leantainn a-mhàin a chaidh a sgrìobhadh le Mastodon a luaidh. setting_default_quote_policy_unlisted: Nuair a luaidheas daoine thu, thèid am post aca-san fhalach o loidhnichean-ama nan treandaichean. setting_default_sensitive: Thèid meadhanan frionasach fhalach a ghnàth is gabhaidh an nochdadh le briogadh orra @@ -63,6 +65,7 @@ gd: setting_display_media_hide_all: Falaich na meadhanan an-còmhnaidh setting_display_media_show_all: Seall na meadhanan an-còmhnaidh setting_emoji_style: An dòigh air an dèid emojis a shealltainn. Feuchaidh “Fèin-obrachail” ris na h-emojis tùsail a chleachdadh ach thèid Twemoji a chleachdadh ’nan àite air seann-bhrabhsairean. + setting_quick_boosting_html: Ma tha seo an comas, ma nì thu briogadh air ìomhaigheag %{boost_icon} a’ bhrosnachaidh, thèid a bhrosnachadh sa bhad seach a bhith a’ fosgladh clàr-taice teàrnach a’ bhrosnachaidh/luaidh. Thèid gnìomh an luaidh a ghluasad gu clàr-taice %{options_icon} nan roghainnean. setting_system_scrollbars_ui: Chan obraich seo ach air brabhsairean desktop stèidhichte air Safari ’s Chrome setting_use_blurhash: Tha caiseadan stèidhichte air dathan nan nithean lèirsinneach a chaidh fhalach ach chan fhaicear am mion-fhiosrachadh setting_use_pending_items: Falaich ùrachaidhean na loidhne-ama air cùlaibh briogaidh seach a bhith a’ sgroladh nam postaichean gu fèin-obrachail @@ -85,11 +88,11 @@ gd: activity_api_enabled: Cunntasan nam postaichean a chaidh fhoillseachadh gu h-ionadail, nan cleachdaichean gnìomhach ’s nan clàraidhean ùra an am bucaidean seachdaineil app_icon: WEBP, PNG, GIF no JPG. Tar-àithnidh seo ìomhaigheag bhunaiteach na h-aplacaid air uidheaman mobile le ìomhaigheag ghnàthaichte. backups_retention_period: "’S urrainn do chleachdaichean tasg-lannan dhe na postaichean aca a gintinn airson an luchdadh a-nuas an uairsin. Nuair a bhios luach dearbh air, thèid na tasg-lannan a sguabadh às on stòras agad gu fèin-obrachail às dèidh an àireamh de làithean a shònraich thu." - bootstrap_timeline_accounts: Thèid na cunntasan seo a phrìneachadh air bàrr nam molaidhean leantainn dhan luchd-cleachdaidh ùr. closed_registrations_message: Thèid seo a shealltainn nuair a bhios an clàradh dùinte content_cache_retention_period: Thèid a h-uile post o fhrithealaiche sam bith eile (a’ gabhail a-staigh brosnachaidhean is freagairtean) a sguabadh às às dèidh na h-àireimh de làithean a shònraich thu ’s gun diù a chon air eadar-ghabhail ionadail air na postaichean ud. Gabhaidh seo a-steach na postaichean a chuir cleachdaiche ionadail ris na h-annsachdan aca no comharran-lìn riutha. Thèid iomraidhean prìobhaideach eadar cleachdaichean o ionstansan diofraichte air chall cuideachd agus cha ghabh an aiseag idir. Tha an roghainn seo do dh’ionstansan sònraichte a-mhàin agus briseadh e dùilean an luchd-cleachdaidh nuair a rachadh a chleachdadh gu coitcheann. custom_css: "’S urrainn dhut stoidhlean gnàthaichte a chur an sàs air an tionndadh-lìn de Mhastodon." favicon: WEBP, PNG, GIF no JPG. Tar-àithnidh seo favicon bunaiteach Mhastodon le ìomhaigheag ghnàthaichte. + landing_page: Taghaidh seo an duilleag a chì aoighean ùra nuair a thadhlas air an fhrithealaiche agad a’ chiad turas. ma thaghas tu “Treandaichean”, feumaidh tu na treandaichean a chur an comas ann ann roghainnean an rùrachaidh. Ma thaghas tu “Loidhne-ama ionadail”, feumaidh tu “Inntrigeadh dhan t-saoghal bheò sa bheil postaichean ionadail” a shuidheachadh air “A h-uile duine” ann an roghainnean an rùrachaidh. mascot: Tar-àithnidh seo an sgead-dhealbh san eadar-aghaidh-lìn adhartach. media_cache_retention_period: Thèid faidhlichean meadhain o phostaichean a chruthaich cleachdaichean cèine a chur ri tasgadan an fhrithealaiche agad. Nuair a shuidhicheas tu seo air luach dearbh, thèid na meadhanan a sguabadh às às dèidh na h-àireimh de làithean a shònraich thu. Ma tha dàta meadhain ga iarraidh às dèidh a sguabaidh às, thèid a luchdadh a-nuas a-rithist ma tha susbaint an tùis ri fhaighinn fhathast. Ri linn cuingeachaidhean air mar a cheasnaicheas cairtean ro-sheallaidh làraichean threas-phàrtaidhean, mholamaid gun suidhich thu an luach seo air 14 làithean ar a char as giorra no cha dèid an ùrachadh nuair a thèid an iarraidh ron àm sin. min_age: Thèid iarraidh air an luchd-cleachdaidh gun dearbh iad an là-breith rè a’ chlàraidh @@ -105,10 +108,8 @@ gd: status_page_url: URL duilleige far am faicear staid an fhrithealaiche seo nuair a bhios e sìos theme: An t-ùrlar a chì na h-aoighean gun chlàradh a-staigh agus an luchd-cleachdaidh ùr. thumbnail: Dealbh mu 2:1 a thèid a shealltainn ri taobh fiosrachadh an fhrithealaiche agad. - timeline_preview: "’S urrainn dha na h-aoighean gun chlàradh a-staigh na postaichean poblach as ùire a tha ri fhaighinn air an fhrithealaiche a bhrabhsadh." trendable_by_default: Geàrr leum thar lèirmheas a làimh na susbainte a’ treandadh. Gabhaidh nithean fa leth a thoirt far nan treandaichean fhathast an uairsin. trends: Seallaidh na treandaichean na postaichean, tagaichean hais is naidheachdan a tha fèill mhòr orra air an fhrithealaiche agad. - trends_as_landing_page: Seall susbaint a’ treandadh dhan fheadhainn nach do chlàraich a-steach is do dh’aoighean seach tuairisgeul an fhrithealaiche seo. Feumaidh treandaichean a bhith an comas airson sin. form_challenge: current_password: Tha thu a’ tighinn a-steach gu raon tèarainte imports: @@ -166,6 +167,7 @@ gd: username_block: allow_with_approval: An àite bacadh clàraidh gu tur, bidh clàraidhean a mhaidsicheas feumach air d’ aonta comparison: Thoir an aire air an Scunthorpe Problem nuair a bhacas tu maidsichean pàirteach + username: Bidh am maidseadh coma mu litrichean mòra ’s beaga agus co-ghlifichean cumanta a leithid “4” an àite “a” no “3” an àite “e” webhook: events: Tagh na tachartasan a thèid a chur template: Cruthaich an JSON payload agad fhèin le eadar-phòlachadh chaochladairean. Fàg seo bàn airson JSON bunaiteach fhaighinn. @@ -236,12 +238,12 @@ gd: setting_aggregate_reblogs: Buidhnich na brosnachaidhean air an loidhne-ama setting_always_send_emails: Cuir brathan puist-d an-còmhnaidh setting_auto_play_gif: Cluich GIFs beòthaichte gu fèin-obrachail - setting_boost_modal: Seall còmhradh dearbhaidh mus dèan thu brosnachadh + setting_boost_modal: Smachd air faicsinneachd nam brosnachaidhean setting_default_language: Cànan postaidh setting_default_privacy: Faicsinneachd nam post setting_default_quote_policy: Cò dh’fhaodas luaidh setting_default_sensitive: Cuir comharra ri meadhanan an-còmhnaidh gu bheil iad frionasach - setting_delete_modal: Seall còmhradh dearbhaidh mus sguab thu às post + setting_delete_modal: Thoir rabhadh dhomh mus dèid post a sguabadh às setting_disable_hover_cards: Na ro-sheall pròifil nuair a dh’fhanas mi os a cionn setting_disable_swiping: Cuir gluasadan grad-shlaighdidh à comas setting_display_media: Sealltainn nam meadhanan @@ -251,7 +253,8 @@ gd: setting_emoji_style: Stoidhle nan Emojis setting_expand_spoilers: Leudaich postaichean ris a bheil rabhadh susbainte an-còmhnaidh setting_hide_network: Falaich an graf sòisealta agad - setting_missing_alt_text_modal: Faic còmhradh dearbhaidh mus postaich thu meadhan às aonais roghainn teacsa + setting_missing_alt_text_modal: Thoir rabhadh dhomh mus postaich mi meadhan às aonais roghainn teacsa + setting_quick_boosting: Cuir am brosnachadh luath an comas setting_reduce_motion: Ìslich an gluasad sna beòthachaidhean setting_system_font_ui: Cleachd cruth-clò bunaiteach an t-siostaim setting_system_scrollbars_ui: Seall bàr-sgrolaidh bunaiteach an t-siostaim @@ -285,12 +288,17 @@ gd: content_cache_retention_period: Ùine glèidhidh aig susbaint chèin custom_css: CSS gnàthaichte favicon: Favicon + landing_page: An duilleag-laighe do dh’aoighean ùra + local_live_feed_access: Inntrigeadh dhan t-saoghal bheò sa bheil postaichean ionadail + local_topic_feed_access: Inntrigeadh dha loidhnichean-ama nan tagaichean hais is ceanglaichean sa bheil postaichean ionadail mascot: Suaichnean gnàthaichte (dìleabach) media_cache_retention_period: Ùine glèidhidh aig tasgadan nam meadhanan min_age: Riatanas aoise as lugha peers_api_enabled: Foillsich liosta nam frithealaichean a chaidh a rùrachadh san API profile_directory: Cuir eòlaire nam pròifil an comas registrations_mode: Cò dh’fhaodas clàradh + remote_live_feed_access: Inntrigeadh dhan t-saoghal bheò sa bheil postaichean cèine + remote_topic_feed_access: Inntrigeadh dha loidhnichean-ama nan tagaichean hais is ceanglaichean sa bheil postaichean cèine require_invite_text: Iarr adhbhar clàraidh show_domain_blocks: Seall bacaidhean àrainne show_domain_blocks_rationale: Seall carson a chaidh àrainnean a bacadh @@ -303,10 +311,8 @@ gd: status_page_url: URL duilleag na staide theme: An t-ùrlar bunaiteach thumbnail: Dealbhag an fhrithealaiche - timeline_preview: Ceadaich inntrigeadh gun ùghdarrachadh air na loidhnichean-ama phoblach trendable_by_default: Ceadaich treandaichean gun lèirmheas ro làimh trends: Cuir na treandaichean an comas - trends_as_landing_page: Cleachd na treandaichean ’nan duilleag-laighe interactions: must_be_follower: Bac na brathan nach eil o luchd-leantainn must_be_following: Bac na brathan o dhaoine nach lean thu @@ -367,9 +373,7 @@ gd: jurisdiction: Uachdranas laghail min_age: An aois as lugha user: - date_of_birth_1i: Latha date_of_birth_2i: Mìos - date_of_birth_3i: Bliadhna role: Dreuchd time_zone: Roinn-tìde user_role: diff --git a/config/locales/simple_form.gl.yml b/config/locales/simple_form.gl.yml index 3b4f72b1be4926..f9d896a2fdca96 100644 --- a/config/locales/simple_form.gl.yml +++ b/config/locales/simple_form.gl.yml @@ -54,8 +54,10 @@ gl: password: Utiliza 8 caracteres ao menos phrase: Concordará independentemente das maiúsculas ou avisos de contido na publicación scopes: A que APIs terá acceso a aplicación. Se escolles un ámbito de alto nivel, non precisas seleccionar elementos individuais. + setting_advanced_layout: Mostrar Mastodon coa disposición en varias columnas, para poder ver a cronoloxía, notificacións e unha terceira columna da túa elección. Non se recomenda con pantallas pequenas. setting_aggregate_reblogs: Non mostrar novas promocións de publicacións que foron promovidas recentemente (só afecta a promocións recén recibidas) setting_always_send_emails: Como norma xeral non che enviamos correos electrónicos se usas activamente Mastodon + setting_boost_modal: Ao activar a opción as promocións primeiro abrirán un cadro de confirmación no que poderás cambiar a visibilidade da túa promoción. setting_default_quote_policy_private: As publicacións só para seguidoras creadas con Mastodon non poden ser citadas. setting_default_quote_policy_unlisted: Cando alguén te cite, a súa publicación non aparecerá nas cronoloxías de popularidade. setting_default_sensitive: Medios sensibles marcados como ocultos por defecto e móstranse cun click @@ -63,6 +65,7 @@ gl: setting_display_media_hide_all: Ocultar sempre os medios setting_display_media_show_all: Mostrar sempre os medios marcados como sensibles setting_emoji_style: Forma de mostrar emojis. «Auto» intentará usar os emojis nativos, e se falla recurrirase a Twemoji en navegadores antigos. + setting_quick_boosting_html: Se está activo, ao premer na icona %{boost_icon} Promover farase automáticamente a promoción no lugar de abrir o menú despregable promover/citar. Sitúa a acción de citar no menú %{options_icon} (Opcións). setting_system_scrollbars_ui: Aplícase só en navegadores de escritorio baseados en Safari e Chrome setting_use_blurhash: Os gradientes toman as cores da imaxe oculta pero esvaecendo tódolos detalles setting_use_pending_items: Agochar actualizacións da cronoloxía tras un click no lugar de desprazar automáticamente os comentarios @@ -85,11 +88,12 @@ gl: activity_api_enabled: Conta do número de publicacións locais, usuarias activas, e novos rexistros en acumulados semanais app_icon: WEBP, PNG, GIF ou JPG. Sobrescribe a icona por defecto da aplicación nos dispositivos móbiles cunha icona personalizada. backups_retention_period: As usuarias poden crear arquivos das súas publicacións para descargalos. Cando se establece un valor positivo, estes arquivos serán borrados automáticamente da túa almacenaxe despois do número de días establecido. - bootstrap_timeline_accounts: Estas contas aparecerán fixas na parte superior das recomendacións para as usuarias. + bootstrap_timeline_accounts: Vas fixar estas contas na parte superior das recomendacións de seguimento. Escribe unha lista de contas separadas por vírgulas. closed_registrations_message: Móstrase cando non se admiten novas usuarias content_cache_retention_period: Todas as publicacións procedentes de outros servidores (incluído promocións e respostas) van ser eliminadas despois do número de días indicado, sen importar as interaccións das usuarias locais con esas publicacións. Esto inclúe publicacións que a usuaria local marcou como favoritas ou incluíu nos marcadores. As mencións privadas entre usuarias de diferentes instancias tamén se eliminarán e non se poderán restablecer. O uso desta ferramenta esta orientado a situacións especiais e estraga moitas das expectativas das usuarias ao implementala cun propósito de uso xeral. custom_css: Podes aplicar deseños personalizados na versión web de Mastodon. favicon: WEBP, PNG, GIF ou JPG. Sobrescribe a icona de favoritos de Mastodon por defecto cunha icona personalizada. + landing_page: Elixe a páxina que verán as persoas que se acheguen por primeira vez ao teu servidor. Se elixes «Tendencias» entón debes activar as tendencias nos Axustes de Descubrimento. Se elixes «Cronoloxía local», hai que configurar nos Axustes de Descubrimento a opción «Acceso das cronoloxías en directo ás publicacións locais» como «Calquera». mascot: Sobrescribe a ilustración na interface web avanzada. media_cache_retention_period: Os ficheiros multimedia de publicacións de usuarias remotas están almacenados no teu servidor. Ao establecer un valor positivo, o multimedia vaise eliminar despois do número de días establecido. Se o multimedia fose requerido após ser eliminado entón descargaríase outra vez, se aínda está dispoñible na orixe. Debido a restricións sobre a frecuencia en que o servizo de vista previa trae recursos de terceiras partes, é recomendable establecer este valor polo menos en 14 días, ou as tarxetas de vista previa non se actualizarán baixo demanda para casos anteriores a ese prazo. min_age: Váiselle pedir ás usuarias que confirmen a súa data de nacemento cando creen a conta @@ -105,10 +109,9 @@ gl: status_page_url: URL dunha páxina onde se pode ver o estado deste servidor cando non está a funcionar theme: Decorado que verán visitantes e novas usuarias. thumbnail: Imaxe con proporcións 2:1 mostrada xunto á información sobre o servidor. - timeline_preview: Visitantes e usuarias non conectadas poderán ver as publicacións públicas máis recentes do servidor. trendable_by_default: Omitir a revisión manual dos contidos populares. Poderás igualmente eliminar manualmente os elementos que vaian aparecendo. trends: As tendencias mostran publicacións, cancelos e novas historias que teñen popularidade no teu servidor. - trends_as_landing_page: Mostrar contidos en voga para as persoas sen sesión iniciada e visitantes no lugar dunha descrición deste servidor. Require ter activado Popularidade. + wrapstodon: Ofrecerlle ás usuarias locais crear un divertido resumo do seu uso de Mastodon durante o ano. Esta ferramenta está dispoñible entre o 10 e o 31 de Decembro de cada ano, e ofréceselle ás usuarias que publicaron polo menos unha mensaxe Pública ou Pública Limitada e utilizaron polo menos un cancelo durante o ano. form_challenge: current_password: Estás entrando nun área segura imports: @@ -235,12 +238,12 @@ gl: setting_aggregate_reblogs: Agrupar promocións nas cronoloxías setting_always_send_emails: Enviar sempre notificacións por correo electrónico setting_auto_play_gif: Reprodución automática de GIFs animados - setting_boost_modal: Solicitar confirmación antes de promover + setting_boost_modal: Controlar a visibilidade das promocións setting_default_language: Idioma de publicación setting_default_privacy: Visibilidade da publicación setting_default_quote_policy: Quen pode citar setting_default_sensitive: Marcar sempre multimedia como sensible - setting_delete_modal: Solicitar confirmación antes de eliminar unha publicación + setting_delete_modal: Avisarme antes de eliminar unha publicación setting_disable_hover_cards: Desactivar vista previa do perfil ao poñerse enriba setting_disable_swiping: Desactivar opcións de desprazamento setting_display_media: Mostrar multimedia @@ -250,7 +253,8 @@ gl: setting_emoji_style: Estilo dos emojis setting_expand_spoilers: Despregar sempre as publicacións marcadas con avisos de contido setting_hide_network: Non mostrar contactos - setting_missing_alt_text_modal: Mostrar mensaxe de confirmación antes de publicar multimedia sen texto descritivo + setting_missing_alt_text_modal: Avisarme antes de publicar multimedia sen descrición alternativa + setting_quick_boosting: Activar promocións rápidas setting_reduce_motion: Reducir o movemento nas animacións setting_system_font_ui: Utilizar a tipografía por defecto do sistema setting_system_scrollbars_ui: Usar barras de desprazamento predeterminadas no sistema @@ -284,12 +288,17 @@ gl: content_cache_retention_period: Período de retención de contido remoto custom_css: CSS personalizado favicon: Favicon + landing_page: Páxina que se mostra ás visitas + local_live_feed_access: Acceso a cronoloxías ao vivo que mostran publicacións locais + local_topic_feed_access: Acceso a cronoloxías de ligazóns e cancelos que mostran publicacións locais mascot: Mascota propia (herdado) media_cache_retention_period: Período de retención da caché multimedia min_age: Idade mínima requerida peers_api_enabled: Publicar na API unha lista dos servidores descubertos profile_directory: Activar o directorio de perfís registrations_mode: Quen se pode rexistrar + remote_live_feed_access: Acceso a cronoloxías ao vivo que mostran publicacións remotas + remote_topic_feed_access: Acceso a cronoloxías de ligazóns e cancelos que mostran publicacións remotas require_invite_text: Pedir unha razón para unirse show_domain_blocks: Amosar dominios bloqueados show_domain_blocks_rationale: Explicar porque están bloqueados os dominios @@ -302,10 +311,9 @@ gl: status_page_url: URL da páxina do estado theme: Decorado predeterminado thumbnail: Icona do servidor - timeline_preview: Permitir acceso á cronoloxía pública sen autenticación trendable_by_default: Permitir tendencias sen aprobación previa trends: Activar tendencias - trends_as_landing_page: Usar as tendencias como páxina de benvida + wrapstodon: Activar Wrapstodon interactions: must_be_follower: Bloquea as notificacións de persoas que non te seguen must_be_following: Bloquea as notificacións de persoas que non segues @@ -366,9 +374,9 @@ gl: jurisdiction: Xurisdición legal min_age: Idade mínima user: - date_of_birth_1i: Día + date_of_birth_1i: Ano date_of_birth_2i: Mes - date_of_birth_3i: Ano + date_of_birth_3i: Día role: Rol time_zone: Fuso horario user_role: diff --git a/config/locales/simple_form.he.yml b/config/locales/simple_form.he.yml index b96d98f60d74dd..5fcaeb4421ad42 100644 --- a/config/locales/simple_form.he.yml +++ b/config/locales/simple_form.he.yml @@ -54,8 +54,10 @@ he: password: נא להשתמש בלפחות 8 תוים phrase: התאמה תמצא ללא תלות באזהרת תוכן בהודעה scopes: לאיזה ממשק יורשה היישום לגשת. בבחירת תחום כללי, אין צורך לבחור ממשקים ספציפיים. + setting_advanced_layout: מסטודון יוצג כמהדורה רבת טורים, ותוכלו לראות את ציר הזמן לצד ההתראות, טורים נוספים לפי בחירתכם. לא מומלץ למסכים קטנים. setting_aggregate_reblogs: לא להראות הדהודים של הודעות שהודהדו לאחרונה (משפיע רק על הדהודים שהתקבלו לא מזמן) setting_always_send_emails: בדרך כלל התראות דוא"ל לא יישלחו בזמן שימוש פעיל במסטודון + setting_boost_modal: כאשר מאופשר, בחירה בהדהוד תפתח דיאלוג אישור שבו ניתן לשנות את הניראות של ההדהוד. setting_default_quote_policy_private: הודעות לעוקבים־בלבד שנוצרו במסטודון חסומות מציטוט על ידי אחרים. setting_default_quote_policy_unlisted: כאשר אחרים מצטטים אותך, ההודעות שלהם יוסתרו גם מ"נושאים חמים". setting_default_sensitive: מדיה רגישה מוסתרת כברירת מחדל וניתן להציגה בקליק @@ -63,6 +65,7 @@ he: setting_display_media_hide_all: הסתר מדיה תמיד setting_display_media_show_all: גלה מדיה תמיד setting_emoji_style: כיצד להציג רגישונים. "אוטומטי" ינסה להציג מסט האימוג'י המקומי, אבל נופל לערכת Twemoji כברירת מחדל עבור דפדפנים ישנים. + setting_quick_boosting_html: כשמאופשר, לחיצה על %{boost_icon} איקון הדהוד ייצור הדהוד מיידי במקום לפתוח את תיבת הבחירה הדהוד/ציטוט. מעביר את פעולת הציטוט אל %{options_icon} תפריט אפשרויות. setting_system_scrollbars_ui: נוגע רק לגבי דפדפני דסקטופ מבוססים ספארי וכרום setting_use_blurhash: הגראדיינטים מבוססים על תוכן התמונה המוסתרת, אבל מסתירים את כל הפרטים setting_use_pending_items: הסתר עדכוני פיד מאחורי קליק במקום לגלול את הפיד אוטומטית @@ -85,11 +88,12 @@ he: activity_api_enabled: מספר ההודעות שפורסמו מקומית, משתמשים פעילים, והרשמות חדשות בדליים שבועיים app_icon: WEBP, PNG, GIF או JPG. גובר על אייקון ברירת המחדל ביישומון על מכשירים ניידים ומחליף אותו באייקון נבחר. backups_retention_period: למשתמשים יש יכולת לבקש ארכיון של הודעותיהם להורדה מאוחר יותר. כאשר נבחר ערך חיובי, הארכיונים הללו ימחקו מאחסון לאחר מספר הימים שצוינו. - bootstrap_timeline_accounts: חשבונות אלו יוצמדו לראש רשימת המלצות המעקב של משתמשים חדשים. + bootstrap_timeline_accounts: חשבונות אלו יוצמדו בראש הפיד של המלצות המעקב למשתמשים חדשים. נא לספק רשימת חשבונות מופרדים בפסיקים. closed_registrations_message: להציג כאשר הרשמות חדשות אינן מאופשרות content_cache_retention_period: כל ההודעות משרתים אחרים (לרבות הדהודים ותגובות) ימחקו אחרי מספר ימים, ללא קשר לאינטראקציה של משתמשים מקומיים איתם. בכלל זה הודעות שהמתשתמשים המקומיים סימנו בסימניה או חיבוב. איזכורים פרטיים ("דיאם") בין משתמשים בין שרתים שונים יאבדו גם הם ולא תהיה אפשרות לשחזרם. השימוש באפשרות הזו מיועד לשרתים עם ייעוד מיוחד ושובר את ציפיותיהם של רב המשתמשים כאשר האפשרות מופעלת בשרת לשימוש כללי. custom_css: ניתן לבחור ערכות סגנון אישיות בגרסת הדפדפן של מסטודון. favicon: WEBP, PNG, GIF או JPG. גובר על "פאבאייקון" ברירת המחדל ומחליף אותו באייקון נבחר בדפדפן. + landing_page: בחירה בעמוד שיוצג ראשון למבקרים חדשים בביקור הראשון בשרת שלך. אם תבחרו "נושאים חמים", אזי הנושאים החמים צריכים להיות מאופשרים בהעדפות "תגליות". אם תבחרו "פיד מקומי", אז "גישה לפידים חיים המציגים הודעות מקומיות" חייב להיות מכוון למצב "כולם" בהעדפות תגליות. mascot: בחירת ציור למנשק הווב המתקדם. media_cache_retention_period: קבצי מדיה מהודעות שהגיעו משרתים רחוקים נשמרות על השרת שלך. כאשר יבחר פה מספר חיובי, המדיה תמחק לאחר מספר ימים כמצוין. אם המידע יבוקש שוב לאחר שנמחק, הוא יורד מחדש, אם המידע עדיין זמין בצד הרחוק. עקב מגבלות על תכיפות שליפת כרטיסי קדימון מאתרים מרוחקים, מומלץ לכוון את הערך ל־14 יום לפחות, או שכרטיסי קדימונים לא יעודכנו לפי דרישה לפני חלוף חלון הזמן הזה. min_age: משתמשיםות יתבקשו לאשר את תאריך הלידה בתהליך ההרשמה @@ -105,10 +109,9 @@ he: status_page_url: כתובת לבדיקת מצב שרת זה בעת תקלה theme: ערכת המראה שיראו משתמשים חדשים ומשתמשים שאינם מחוברים. thumbnail: תמונה ביחס 2:1 בערך שתוצג ליד המידע על השרת שלך. - timeline_preview: משתמשים מנותקים יוכלו לדפדף בהודעות ציר הזמן הציבורי שעל השרת. trendable_by_default: לדלג על בדיקה ידנית של התכנים החמים. פריטים ספציפיים עדיין ניתנים להסרה לאחר מעשה. trends: נושאים חמים יציגו אילו הודעות, תגיות וידיעות חדשות צוברות חשיפה על השרת שלך. - trends_as_landing_page: הצג למבקרים ולמשתמשים שאינם מחוברים את הנושאים החמים במקום את תיאור השרת. מחייב הפעלה של אפשרות הנושאים החמים. + wrapstodon: אפשר למשתמשיך המקומיים.ות ליצור סיכום חביב של פעילותם במסטודון בשנה האחרונה. התכונה מאופשרת בין 10 ועד 31 בדצמבר כל שנה, ומצעת למשתמשים שיצרו לפחות הודעה ציבורית אחת והשתמשו לפחות בתגית אחת במשך השנה. form_challenge: current_password: את.ה נכנס. ת לאזור מאובטח imports: @@ -237,12 +240,12 @@ he: setting_aggregate_reblogs: קבץ הדהודים זהים setting_always_send_emails: תמיד שלח התראות לדוא"ל setting_auto_play_gif: ניגון אוטומטי של גיפים - setting_boost_modal: הצגת דיאלוג אישור לפני הדהוד + setting_boost_modal: שליטה בנראות של הדהודים setting_default_language: שפת ברירת מחדל להודעה setting_default_privacy: חשיפת ההודעה setting_default_quote_policy: למי מותר לצטט setting_default_sensitive: תמיד לתת סימון "רגיש" למדיה - setting_delete_modal: להראות תיבת אישור לפני מחיקת חיצרוץ + setting_delete_modal: הזהר אותי לפני שאמחק הודעה setting_disable_hover_cards: כבה הצצה מקדימה לפרופיל בעת מעבר עכבר מעליו setting_disable_swiping: ביטול החלקת-צד setting_display_media: תצוגת מדיה @@ -252,7 +255,8 @@ he: setting_emoji_style: סגנון רגישונים (אמוג'י) setting_expand_spoilers: להרחיב תמיד הודעות מסומנות באזהרת תוכן setting_hide_network: להחביא את הגרף החברתי שלך - setting_missing_alt_text_modal: הצג כרטיס אישור לפני פרסום קובץ גרפי ללא תיאור מילולי + setting_missing_alt_text_modal: הזהר אותי לפני שאפרסם מדיה ללא תיאור alt text + setting_quick_boosting: לאפשר הדהוד מהיר setting_reduce_motion: הפחתת תנועה בהנפשות setting_system_font_ui: להשתמש בגופן ברירת המחדל של המערכת setting_system_scrollbars_ui: להשתמש בפס הגלילה שהוא ברירת המחדל של המערכת @@ -286,12 +290,17 @@ he: content_cache_retention_period: תקופת השמירה על תוכן חיצוני custom_css: CSS בהתאמה אישית favicon: סמל מועדפים (Favicon) + landing_page: דף נחיתה למבקרים חדשים + local_live_feed_access: גישה לפידים חיים המציגים הודעות מקומיות + local_topic_feed_access: גישה לפידים של תגיות וקישורים המציגים הודעות מקומיות mascot: סמל השרת (ישן) media_cache_retention_period: תקופת שמירת מטמון מדיה min_age: דרישת גיל מינימלי peers_api_enabled: פרסם רשימה של שרתים שנתגלו באמצעות ה-API profile_directory: הפעלת ספריית פרופילים registrations_mode: מי יכולים לפתוח חשבון + remote_live_feed_access: גישה לפידים חיים המציגים הודעות מהעולם + remote_topic_feed_access: גישה לפידים של תגיות וקישורים המציגים הודעות מהעולם require_invite_text: לדרוש סיבה להצטרפות show_domain_blocks: הצגת חסימת דומיינים show_domain_blocks_rationale: הצגת סיבות חסימה למתחמים @@ -304,10 +313,9 @@ he: status_page_url: URL של עמוד סטטוס חיצוני theme: ערכת נושא ברירת מחדל thumbnail: תמונה ממוזערת מהשרת - timeline_preview: הרשאת גישה בלתי מאומתת לפיד הפומבי trendable_by_default: הרשאה לפריטים להופיע בנושאים החמים ללא אישור מוקדם trends: אפשר פריטים חמים (טרנדים) - trends_as_landing_page: דף הנחיתה יהיה "נושאים חמים" + wrapstodon: הפעלת סיכומודון interactions: must_be_follower: חסימת התראות משאינם עוקבים must_be_following: חסימת התראות משאינם נעקבים @@ -368,9 +376,9 @@ he: jurisdiction: איזור השיפוט min_age: גיל מינימלי user: - date_of_birth_1i: יום + date_of_birth_1i: שנה date_of_birth_2i: חודש - date_of_birth_3i: שנה + date_of_birth_3i: יום role: תפקיד time_zone: אזור זמן user_role: diff --git a/config/locales/simple_form.hu.yml b/config/locales/simple_form.hu.yml index c4f0e2b6f68454..6021d66f6506f9 100644 --- a/config/locales/simple_form.hu.yml +++ b/config/locales/simple_form.hu.yml @@ -54,8 +54,10 @@ hu: password: Legalább 8 karakter phrase: Illeszkedni fog kis/nagybetű függetlenül, és tartalmi figyelmeztetések mögött is scopes: Mely API-kat érheti el az alkalmazás. Ha felső szintű hatáskört választasz, nem kell egyesével kiválasztanod az alatta lévőeket. + setting_advanced_layout: A Mastodon megjelenítése többhasábos elrendezéssel, így az idővonal, az értesítések és egy harmadik választott hasáb jelenik meg. Kisebb kijelzők esetén nem javasolt. setting_aggregate_reblogs: Ne mutassunk megtolásokat olyan bejegyzésekhez, melyeket nemrég toltak meg (csak új megtolásokra lép életbe) setting_always_send_emails: Alapesetben nem küldünk e-mail-értesítéseket, ha aktívan használod a Mastodont + setting_boost_modal: Ha engedélyezve van, akkor a megtolás egy megerősítő párbeszédablakot jelenít meg, melyen kiválaszthatod a megtolásod láthatóságát. setting_default_quote_policy_private: A Mastodonon írt, csak követőknek szóló bejegyzéseket mások nem idézhetik. setting_default_quote_policy_unlisted: Amikor idéznek tőled, a bejegyzésük rejtve lesz a felkapott bejegyzések hírfolyamain is. setting_default_sensitive: A kényes médiatartalmat alapesetben elrejtjük, de egyetlen kattintással előhozható @@ -63,6 +65,7 @@ hu: setting_display_media_hide_all: Média elrejtése mindig setting_display_media_show_all: Média megjelenítése mindig setting_emoji_style: Az emodzsik megjelenítési módja. Az „Automatikus” megpróbálja a natív emodzsikat használni, de az örökölt böngészők esetén a Twemojira vált vissza. + setting_quick_boosting_html: Ha engedélyezve van, akkor a %{boost_icon} Megtolás azonnal megtörténik, ahelyett hogy megnyitná a megtolás/idézés legördülő menüje. Az idézési műveletet áthelyezi a %{options_icon} (Beállítások) menübe. setting_system_scrollbars_ui: Csak Chrome és Safari alapú asztali böngészőkre vonatkozik setting_use_blurhash: A kihomályosítás az eredeti képből történik, de minden részletet elrejt setting_use_pending_items: Idővonal frissítése csak kattintásra automatikus görgetés helyett @@ -76,7 +79,7 @@ hu: featured_tag: name: 'Itt vannak azok a hashtagek, melyeket legutóbb használtál:' filters: - action: A végrehajtandó műveletet, ha a bejegyzés megfelel a szűrőnek + action: Válassza ki a végrehajtandó műveletet, ha a bejegyzés megfelel a szűrőnek actions: blur: Média elrejtése figyelmeztetéssel, a szöveg elrejtése nélkül hide: A szűrt tartalom teljes elrejtése, mintha nem is létezne @@ -85,11 +88,12 @@ hu: activity_api_enabled: Helyi bejegyzések, aktív felhasználók és új regisztrációk száma heti bontásban app_icon: WEBP, PNG, GIF vagy JPG. Mobileszközökön az alkalmazás alapértelmezett ikonját felülírja egy egyéni ikonnal. backups_retention_period: A felhasználók archívumokat állíthatnak elő a bejegyzéseikből, hogy később letöltsék azokat. Ha pozitív értékre van állítva, akkor a megadott számú nap után automatikusan törölve lesznek a tárhelyedről. - bootstrap_timeline_accounts: Ezek a fiókok ki lesznek tűzve az új felhasználók követési javaslatainak élére. + bootstrap_timeline_accounts: Ezek a fiókok rögzítve lesznek az új felhasználók követési ajánlásai tetején. Add meg a fiókok vesszővel elválasztott listáját. closed_registrations_message: Akkor jelenik meg, amikor a regisztráció le van zárva content_cache_retention_period: Minden más kiszolgálóról származó bejegyzés (megtolásokkal és válaszokkal együtt) törölve lesz a megadott számú nap elteltével, függetlenül a helyi felhasználók ezekkel a bejegyzésekkel történő interakcióitól. Ebben azok a bejegyzések is benne vannak, melyeket a helyi felhasználó könyvjelzőzött vagy kedvencnek jelölt. A különböző kiszolgálók felhasználói közötti privát üzenetek is el fognak veszni visszaállíthatatlanul. Ennek a beállításnak a használata különleges felhasználási esetekre javasolt, mert számos felhasználói elvárás fog eltörni, ha általános céllal használják. custom_css: A Mastodon webes verziójában használhatsz egyéni stílusokat. favicon: WEBP, PNG, GIF vagy JPG. Az alapértelmezett Mastodon favicont felülírja egy egyéni ikonnal. + landing_page: Kiválasztja, hogy a webhely új látogatói mit látnak, amikor először érkeznek a kiszolgálóra. Ha a „Trendek” lehetőséget választod, akkor engedélyezni kell a trendeket a Felfedezési beállításokban. Ha a „Helyi hírfolyamot” választod, akkor a „Helyi bejegyzéseket tartalmazó helyi élő idővonalak elérése” lehetőséget „Mindenki” értékre kell állítani a Felfedezési beállításokban. mascot: Felülbírálja a speciális webes felületen található illusztrációt. media_cache_retention_period: A távoli felhasználók bejegyzéseinek médiatartalmait a kiszolgálód gyorsítótárazza. Ha pozitív értékre állítják, ezek a médiatartalmak a megadott számú nap után törölve lesznek. Ha a médiát újra lekérik, miután törlődött, újra le fogjuk tölteni, ha az eredeti még elérhető. A hivatkozások előnézeti kártyáinak harmadik fél weboldalai felé történő hivatkozásaira alkalmazott megkötései miatt javasolt, hogy ezt az értéket legalább 14 napra állítsuk, ellenkező esetben a hivatkozások előnézeti kártyái szükség esetén nem fognak tudni frissülni ezen idő előtt. min_age: A felhasználók a regisztráció során arra lesznek kérve, hogy erősítsek meg a születési dátumukat @@ -105,10 +109,9 @@ hu: status_page_url: Annak az oldalnak az URL-je, melyen ennek a kiszolgálónak az állapotát látják az emberek egy leállás során theme: A téma, melyet a kijelentkezett látogatók és az új felhasználók látnak. thumbnail: Egy durván 2:1 arányú kép, amely a kiszolgálóinformációk mellett jelenik meg. - timeline_preview: A kijelentkezett látogatók továbbra is böngészhetik a kiszolgáló legfrissebb nyilvános bejegyzéseit. trendable_by_default: Kézi felülvizsgálat kihagyása a felkapott tartalmaknál. Az egyes elemek utólag távolíthatók el a trendek közül. trends: A trendek azt mondják meg, hogy mely bejegyzések, hashtagek és hírbejegyzések felkapottak a kiszolgálódon. - trends_as_landing_page: Felkapott tartalmak mutatása a kijelentkezett felhasználók és látogatók számára ennek a kiszolgálónak a leírása helyett. Szükséges hozzá a trendek engedélyezése. + wrapstodon: Kínáld fel a lehetőséget a helyi felhasználóknak, hogy egy vidám összefoglalóban összesíthessék a Mastodon használatukat az év folyamán. Ez a lehetőség minden évben december 10. és 31. között érhető el, azoknak, aki létrehoztak legalább egy nyilvános vagy csendes nyilvános bejegyzést és használtak legalább egy hashtaget az év folyamán. form_challenge: current_password: Beléptél egy biztonsági térben imports: @@ -235,12 +238,12 @@ hu: setting_aggregate_reblogs: Megtolások csoportosítása az idővonalakon setting_always_send_emails: E-mail-értesítések küldése mindig setting_auto_play_gif: GIF-ek automatikus lejátszása - setting_boost_modal: Megerősítés kérése megtolás előtt + setting_boost_modal: Megtolás láthatóságának beállítása setting_default_language: Bejegyzések nyelve setting_default_privacy: Közzététel láthatósága setting_default_quote_policy: Ki idézhet setting_default_sensitive: Minden médiafájl megjelölése kényesként - setting_delete_modal: Megerősítés kérése bejegyzés törlése előtt + setting_delete_modal: Figyelmeztetés egy bejegyzés törlése előtt setting_disable_hover_cards: Profilelőnézet letiltása föléhúzás esetén setting_disable_swiping: Elhúzás művelet kikapcsolása setting_display_media: Média megjelenítése @@ -250,7 +253,8 @@ hu: setting_emoji_style: Emodzsistílus setting_expand_spoilers: Tartalmi figyelmeztetéssel ellátott bejegyzések automatikus kinyitása setting_hide_network: Hálózatod elrejtése - setting_missing_alt_text_modal: Megerősítési párbeszédablak megjelenítése a helyettesítő szöveg nélküli média közzététele előtt + setting_missing_alt_text_modal: Figyelmeztetés alternatív szöveg nélküli média közzétetele előtt + setting_quick_boosting: Gyors megtolás engedélyezése setting_reduce_motion: Animációk mozgásának csökkentése setting_system_font_ui: Rendszer betűtípusának használata setting_system_scrollbars_ui: Rendszer alapértelmezett görgetősávjának használata @@ -284,12 +288,17 @@ hu: content_cache_retention_period: Távoli tartalmak megtartási időszaka custom_css: Egyéni CSS favicon: Könyvjelzőikon + landing_page: Kezdőoldal az új látogatóknak + local_live_feed_access: Helyi bejegyzéseket bemutató élő hírfolyamok elérése + local_topic_feed_access: Helyi bejegyzéseket bemutató hashtagek és hivatkozásfolyamok elérése mascot: Egyéni kabala (örökölt) media_cache_retention_period: Média-gyorsítótár megtartási időszaka min_age: Minimális korhatár peers_api_enabled: Felfedezett kiszolgálók listájának közzététele az API-ban profile_directory: Profiladatbázis engedélyezése registrations_mode: Ki regisztrálhat + remote_live_feed_access: Távoli bejegyzéseket bemutató élő hírfolyamok elérése + remote_topic_feed_access: Távoli bejegyzéseket bemutató hashtagek és hivatkozásfolyamok elérése require_invite_text: Indok megkövetelése a csatlakozáshoz show_domain_blocks: Domain tiltások megjelenitése show_domain_blocks_rationale: A domainek letiltási okainak megjelenítése @@ -302,10 +311,9 @@ hu: status_page_url: Állapotoldal URL-je theme: Alapértelmezett téma thumbnail: Kiszolgáló bélyegképe - timeline_preview: A nyilvános idővonalak hitelesítés nélküli elérésének engedélyezése trendable_by_default: Trendek engedélyezése előzetes ellenőrzés nélkül trends: Trendek engedélyezése - trends_as_landing_page: Trendek használata nyitóoldalként + wrapstodon: Wrapstodon engedélyezése interactions: must_be_follower: Nem követőidtől érkező értesítések tiltása must_be_following: Nem követettjeidtől érkező értesítések tiltása @@ -366,9 +374,9 @@ hu: jurisdiction: Joghatóság min_age: Minimális életkor user: - date_of_birth_1i: Nap + date_of_birth_1i: Év date_of_birth_2i: Hónap - date_of_birth_3i: Év + date_of_birth_3i: Nap role: Szerep time_zone: Időzóna user_role: diff --git a/config/locales/simple_form.hy.yml b/config/locales/simple_form.hy.yml index a9c770e5cb2a34..4bf125a5b7e19b 100644 --- a/config/locales/simple_form.hy.yml +++ b/config/locales/simple_form.hy.yml @@ -127,10 +127,8 @@ hy: setting_advanced_layout: Միացնել ընդլայնուած վեբ ինտերֆեյս setting_aggregate_reblogs: Տարծածները խմբաւորել հոսքում setting_auto_play_gif: Աւտոմատ մեկնարկել GIFs անիմացիաները - setting_boost_modal: Ցուցադրել հաստատման պատուհանը տարածելուց առաջ setting_default_language: Հրապարակման լեզու setting_default_sensitive: Միշտ նշել մեդիան որպէս դիւրազգաց - setting_delete_modal: Ցուցադրել հաստատման պատուհանը ջնջելուց առաջ setting_disable_swiping: Կասեցնել սահող շարժումները setting_display_media: Ցուցադրել մեդիա setting_display_media_default: Լռելեայն diff --git a/config/locales/simple_form.ia.yml b/config/locales/simple_form.ia.yml index 499d565f4f6414..39992a569e9f6e 100644 --- a/config/locales/simple_form.ia.yml +++ b/config/locales/simple_form.ia.yml @@ -54,8 +54,10 @@ ia: password: Usa al minus 8 characteres phrase: Se applicara independentemente de majusculas/minusculas in le texto o del aviso de contento de un message scopes: Le APIs al quales le application habera accesso. Si tu selige un ambito de nivello superior, non es necessari seliger ambitos individual. + setting_advanced_layout: Presenta Mastodon in un disposition multicolumnar con le chronologia, le notificationes e un tertie columna de tu preferentia. Non recommendate pro schermos minor. setting_aggregate_reblogs: Non monstrar nove impulsos pro messages que ha essite recentemente impulsate (affecta solmente le impulsos novemente recipite) setting_always_send_emails: Normalmente, le notificationes de e-mail non es inviate quando tu activemente usa Mastodon + setting_boost_modal: Si isto es activate, impulsar un message aperira primo un dialogo de confirmation in le qual tu pote cambiar le visibilitate de tu impulso. setting_default_quote_policy_private: Le messages limitate al sequitores scribite sur Mastodon non pote esser citate per alteres. setting_default_quote_policy_unlisted: Quando le gente te cita, lor message equalmente non apparera in le chronologias de tendentias. setting_default_sensitive: Le medios sensibile es celate de ordinario e pote esser revelate con un clic @@ -63,6 +65,7 @@ ia: setting_display_media_hide_all: Sempre celar contento multimedial setting_display_media_show_all: Sempre monstrar contento multimedial setting_emoji_style: Como monstrar emojis. “Automatic” tentara usar emojis native, ma recurre al Twemojis pro navigatores ancian. + setting_quick_boosting_html: Si isto es activate, un clic sur le icone %{boost_icon} Impulsar impulsara immediatemente le message in loco de aperir le menu disrolante de impulsar/citar. Isto tamben colloca le action de citar in le menu %{options_icon} (Optiones). setting_system_scrollbars_ui: Se applica solmente al navigatores de scriptorio basate sur Safari e Chrome setting_use_blurhash: Le imagines degradate se basa sur le colores del visuales celate, ma illos offusca tote le detalios setting_use_pending_items: Requirer un clic pro monstrar nove messages in vice de rolar automaticamente le fluxo @@ -76,7 +79,6 @@ ia: featured_tag: name: 'Ecce alcun del hashtags que tu usava le plus recentemente:' filters: - action: Selige que action exequer quando un message concorda con le filtro actions: blur: Celar contento multimedial detra un advertimento, sin celar le texto mesme hide: Completemente celar le contento filtrate, comportar se como si illo non existerea @@ -85,11 +87,11 @@ ia: activity_api_enabled: Numeros de messages localmente publicate, usatores active, e nove registrationes in gruppos septimanal app_icon: WEBP, PNG, GIF o JPG. Supplanta le icone predefinite sur apparatos mobile con un icone personalisate. backups_retention_period: Le usatores pote generar archivos de lor messages pro discargar los plus tarde. Si iste option es definite a un valor positive, iste archivos essera automaticamente delite de tu immagazinage post le numero specificate de dies. - bootstrap_timeline_accounts: Iste contos essera fixate al initio del recommendationes de sequimento de nove usatores. closed_registrations_message: Monstrate quando le inscriptiones es claudite content_cache_retention_period: Tote le messages de altere servitores (includite impulsos e responsas) essera delite post le numero de dies specificate, independentemente de tote interaction de usatores local con ille messages. Isto include le messages addite al marcapaginas o marcate como favorite per un usator local. Le mentiones private inter usatores de differente instantias tamben essera irrecuperabilemente perdite. Le uso de iste parametro es intendite pro instantias con scopos specific e viola multe expectationes de usatores si es implementate pro uso general. custom_css: Tu pote applicar stilos personalisate sur le version de web de Mastodon. favicon: WEBP, PNG, GIF o JPG. Supplanta le favicone predefinite de Mastodon con un icone personalisate. + landing_page: Selige le pagina presentate al nove visitatores al prime arrivata sur tu servitor. Si tu selige “Tendentias”, alora le tendentias debe esser activate in le Parametros de discoperta. Si tu selige “Canal local”, alora le option “Accesso a canales in vivo con messages local” debe esser mittite a “Omnes” in le Parametros de discoperta. mascot: Illo substitue le illustration in le interfacie web avantiate. media_cache_retention_period: Le files multimedial de messages producite per usatores distante se immagazina in cache sur tu servitor. Quando iste option es definite a un valor positive, tal files essera delite post le numero specificate de dies. Si alcuno requesta le datos multimedial post lor deletion, illos essera re-discargate si le contento original es ancora disponibile. Debite a limitationes sur le frequentia con que le cartas de previsualisation de ligamines se connecte al sitos de tertios, il es recommendate definir iste valor a al minus 14 dies, alteremente le previsualisationes de ligamines non essera actualisate sur demanda ante ille tempore. min_age: Le usatores debera confirmar lor data de nascentia durante le inscription @@ -105,10 +107,8 @@ ia: status_page_url: URL de un pagina ubi le personas pote vider le stato de iste servitor durante un interruption theme: Thema que le visitatores disconnexe e le nove usatores vide. thumbnail: Un imagine de circa 2:1 monstrate al latere del informationes de tu servitor. - timeline_preview: Le visitatores foras de session potera percurrer le messages public le plus recente disponibile sur le servitor. trendable_by_default: Saltar le revision manual del contento de tendentia. Elementos singule pote ancora esser removite de tendentias post le facto. trends: Tendentias monstra que messages, hashtags e novas gania traction sur tu servitor. - trends_as_landing_page: Monstrar contento de tendentia a usatores disconnexe e visitatores in vice que un description de iste servitor. Require tendentias esser activate. form_challenge: current_password: Tu entra in un area secur imports: @@ -235,12 +235,12 @@ ia: setting_aggregate_reblogs: Gruppar impulsos in chronologias setting_always_send_emails: Sempre inviar notificationes per e-mail setting_auto_play_gif: Auto-reproduce GIFs animate - setting_boost_modal: Monstrar dialogo de confirmation ante de impulsar + setting_boost_modal: Controlar le visibilitate de impulsos setting_default_language: Lingua de publication setting_default_privacy: Visibilitate de publication setting_default_quote_policy: Qui pote citar setting_default_sensitive: Sempre marcar le medios cmo sensbile - setting_delete_modal: Monstrar le dialogo de confirmation ante deler un message + setting_delete_modal: Advertir me ante de deler un message setting_disable_hover_cards: Disactivar le previsualisation de profilos al passar del mus setting_disable_swiping: Disactivar le movimentos per glissamento setting_display_media: Visualisation de medios @@ -250,7 +250,8 @@ ia: setting_emoji_style: Stilo de emojis setting_expand_spoilers: Sempre expander messages marcate con avisos de contento setting_hide_network: Cela tu rete social - setting_missing_alt_text_modal: Monstrar un dialogo de confirmation ante de publicar multimedia sin texto alternative + setting_missing_alt_text_modal: Advertir me ante de publicar multimedia sin texto alternative + setting_quick_boosting: Activar impulso rapide setting_reduce_motion: Reducer movimento in animationes setting_system_font_ui: Usar typo de litteras predefinite del systema setting_system_scrollbars_ui: Usar le barra de rolamento predefinite del systema @@ -284,12 +285,17 @@ ia: content_cache_retention_period: Periodo de retention del contento remote custom_css: CSS personalisate favicon: Favicon + landing_page: Pagina de arrivata pro nove visitatores + local_live_feed_access: Accesso a canales in vivo con messages local + local_topic_feed_access: Accesso a canales de hashtag e ligamines con messages local mascot: Personalisar le mascotte (hereditage) media_cache_retention_period: Periodo de retention del cache multimedial min_age: Etate minime requirite peers_api_enabled: Publicar le lista de servitores discoperite in le API profile_directory: Activar directorio de profilos registrations_mode: Qui pote inscriber se + remote_live_feed_access: Accesso a canales in vivo con messages remote + remote_topic_feed_access: Accesso a canales de hashtag e ligamines con messages remote require_invite_text: Requirer un ration pro junger se show_domain_blocks: Monstrar le blocadas de dominio show_domain_blocks_rationale: Monstrar proque le dominios ha essite blocate @@ -302,10 +308,8 @@ ia: status_page_url: URL del pagina de stato theme: Thema predefinite thumbnail: Miniatura de servitor - timeline_preview: Permitter accesso non authenticate a chronologias public trendable_by_default: Permitter tendentias sin revision previe trends: Activar tendentias - trends_as_landing_page: Usar tendentias como pagina de destination interactions: must_be_follower: Blocar notificationes de personas qui non te seque must_be_following: Blocar notificationes de personas que tu non seque @@ -366,9 +370,7 @@ ia: jurisdiction: Jurisdiction min_age: Etate minime user: - date_of_birth_1i: Die date_of_birth_2i: Mense - date_of_birth_3i: Anno role: Rolo time_zone: Fuso horari user_role: diff --git a/config/locales/simple_form.id.yml b/config/locales/simple_form.id.yml index 0413dafe36abb2..56c9deed9d6200 100644 --- a/config/locales/simple_form.id.yml +++ b/config/locales/simple_form.id.yml @@ -61,12 +61,10 @@ id: featured_tag: name: 'Ini adalah beberapa tagar yang sering Anda gunakan:' filters: - action: Pilih tindakan apa yang dilakukan ketika sebuah kiriman cocok dengan saringan actions: hide: Sembunyikan konten yang disaring, seperti itu tidak ada warn: Sembunyikan konten yang disaring di belakang sebuah peringatan menyebutkan judul saringan form_admin_settings: - bootstrap_timeline_accounts: Akun ini akan disematkan di atas rekomendasi ikut pengguna baru. closed_registrations_message: Ditampilkan ketika pendaftaran ditutup custom_css: Anda dapat menerapkan gaya kustom di versi web Mastodon. mascot: Menimpa ilustrasi di antarmuka web tingkat lanjut. @@ -80,7 +78,6 @@ id: site_title: Bagaimana orang dapat memberitahu tentang server selain nama domain. theme: Tema yang dilihat oleh pengunjung yang keluar dan pengguna baru. thumbnail: Gambar sekitar 2:1 yang ditampilkan di samping informasi server Anda. - timeline_preview: Pengunjung yang keluar akan dapat menjelajahi kiriman publik terkini yang tersedia di server. trendable_by_default: Lewati tinjauan manual dari konten tren. Item individu masih dapat dihapus dari tren setelah faktanya. trends: Tren yang menampilkan kiriman, tagar, dan cerita berita apa yang sedang tren di server Anda. form_challenge: @@ -177,10 +174,8 @@ id: setting_aggregate_reblogs: Boost grup di linimasa setting_always_send_emails: Selalu kirim notifikasi email setting_auto_play_gif: Mainkan otomatis animasi GIF - setting_boost_modal: Tampilkan dialog konfirmasi dialog sebelum boost setting_default_language: Bahasa posting setting_default_sensitive: Selalu tandai media sebagai sensitif - setting_delete_modal: Tampilkan dialog konfirmasi sebelum hapus toot setting_disable_swiping: Nonaktifkan gerak usap setting_display_media: Tampilan media setting_display_media_default: Bawaan @@ -230,7 +225,6 @@ id: site_title: Nama server theme: Tema bawaan thumbnail: Gambar kecil server - timeline_preview: Perbolehkan akses tidak terotentikasi ke linimasa publik trendable_by_default: Perbolehkan tren tanpa tinjauan trends: Aktifkan tren interactions: diff --git a/config/locales/simple_form.ie.yml b/config/locales/simple_form.ie.yml index 24635c7f8320f6..73c3bcfa036477 100644 --- a/config/locales/simple_form.ie.yml +++ b/config/locales/simple_form.ie.yml @@ -70,7 +70,6 @@ ie: featured_tag: name: 'Vi quelc hashtags usat max recentmen de te:' filters: - action: Selecter quel action a far quande un posta egala un filtre actions: hide: Celar completmen li contenete filtrat, quam si it ne existe warn: Celar li contenete filtrat detra un avise mentionant li titul del filtre @@ -78,7 +77,6 @@ ie: activity_api_enabled: Númeres de postas publicat localmen, activ usatores, e nov adhesiones in periodes semanal app_icon: WEBP, PNG, GIF o JPG. Remplazza li predenifit favicon Mastodon sur mobiles con un icon customisat. backups_retention_period: Usatores posse generar archives de lor postas por adcargar plu tard. Si on specifica un valore positiv, li archives va esser automaticmen deletet de tui magazinage pos li specificat quantitá de dies. - bootstrap_timeline_accounts: Ti-ci contos va esser pinglat al parte superiori del recomandationes por nov usatores. closed_registrations_message: Monstrat quande adhesiones es cludet content_cache_retention_period: Omni postas de altri servitores (includente boosts e responses) va esser deletet pos li specificat quantitá de dies, sin egard a local usator-interactiones con les. To vale anc por postas queles un local usator ha marcat o favoritat it. Anc privat mentiones ínter usatores de diferent instanties va esser perdit e ínrestorabil. Talmen, ti-ci parametre es intentet por scopes special pro que it posse ruptes li expectationes de usatores. custom_css: On posse aplicar customisat stiles al web-version de Mastodon. @@ -97,10 +95,8 @@ ie: status_page_url: URL de un págine monstrant li statu de ti-ci servitor durant un ruptura de servicie theme: Li dessine quel ínregistrat visitantes e nov usatores vide. thumbnail: Un image de dimensiones circa 2:1 monstrat along tui servitor-information. - timeline_preview: Ínregistrat visitantes va posser vider li max recent public postas disponibil che li servitor. trendable_by_default: Pretersaltar un manual revision de contenete in tendentie. Mem pos to on posse remover índividual pezzes de tendentie. trends: Tendenties monstra quel postas, hashtags e novas es ganiant atention sur tui servitor. - trends_as_landing_page: Monstrar populari contenete a ínregistrat visitantes vice un description del servitor. Besona que tendenties es activisat. form_challenge: current_password: Tu nu intra un area secur imports: @@ -204,10 +200,8 @@ ie: setting_aggregate_reblogs: Gruppar boosts in témpor-lineas setting_always_send_emails: Sempre misser notificationes de e-posta setting_auto_play_gif: Reproducter automaticmen animat GIFs - setting_boost_modal: Monstrar dialog de confirmation ante boostar setting_default_language: Lingue in quel postar setting_default_sensitive: Sempre marcar medie quam sensitiv - setting_delete_modal: Monstrar dialog de confirmation ante deleter un posta setting_disable_swiping: Desactivar motiones de glissar setting_display_media: Exposition de medie setting_display_media_default: Predefinitiones @@ -261,10 +255,8 @@ ie: status_page_url: URL de statu-págine theme: Predefenit tema thumbnail: Miniatura del servitor - timeline_preview: Permisser accesse ínautenticat al public témpor-lineas trendable_by_default: Possibilisar tendenties sin priori inspection trends: Possibilisar tendenties - trends_as_landing_page: Usar tendenties quam frontispicie interactions: must_be_follower: Bloccar notificationes de tis qui ne seque te must_be_following: Bloccar notificationes de tis quem tu ne seque diff --git a/config/locales/simple_form.io.yml b/config/locales/simple_form.io.yml index b0904353c1a85d..9cd295eb5f9fcc 100644 --- a/config/locales/simple_form.io.yml +++ b/config/locales/simple_form.io.yml @@ -73,7 +73,6 @@ io: featured_tag: name: 'Yen kelka hashtagi quin vu uzis maxim recente:' filters: - action: Selektez ago kande posto parigas filtrilo actions: hide: Komplete celez filtrita kontenajo quale ol ne existas warn: Celez filtrita kontenajo dop avert quo montras titulo di filtrilo @@ -81,7 +80,6 @@ io: activity_api_enabled: Quanto de lokale publikigita posti, aktiva uzanti e nova registri, donita semanope app_icon: WEBP, PNG, GIF o JPG. Ol remplas la originala imajeto di softwaro sur poshaparati kun personaligita imajeto. backups_retention_period: Uzanto povas igar arkivi di sua afishi por deskargar pose. - bootstrap_timeline_accounts: Ca konti adpinglesos ad super sequorekomendi di nova uzanti. closed_registrations_message: Montresas kande registradi klozesas content_cache_retention_period: Omna posti de altra servili efacesos (anke repeti e respondi) pos decidita quanto di dii, sen ye irga lokala uzantointerago kun ti posti. Privata mencioni inter uzanto de dessanta servili anke desganos e neposible riganesos. custom_css: Vu povas pozar kustumizita staili en retverso di Mastodon. @@ -100,10 +98,8 @@ io: status_page_url: URL di pagino ube personi povas vidar la stando di ca servilo kande la servilo ne funcionas theme: Temo quo videsas da ekirita vizitanti e nova uzanti. thumbnail: Cirkum 2:1 imajo montresar kun informo di ca servilo. - timeline_preview: Ekirita vizitanti videsos maxim recenta publika posti quo esas displonebla en la servilo. trendable_by_default: Ignorez manuala kontrolar di populara enhavajo. trends: Populari montras quala afishi, gretvorti e novaji populareskas en vua servilo. - trends_as_landing_page: Montrez populara posti a uzanti neeniriti e vizitanti vice deskriptajo pri ca servilo. Bezonas ke populari es aktivita. form_challenge: current_password: Vu eniras sekura areo imports: @@ -218,10 +214,8 @@ io: setting_aggregate_reblogs: Grupigar repeti en tempolinei setting_always_send_emails: Sempre sendez retpostoavizi setting_auto_play_gif: Autoplear anima GIFi - setting_boost_modal: Montrez konfirmdialogo ante repetar setting_default_language: Postolinguo setting_default_sensitive: Omnatempe markas audvidaji quale trublema - setting_delete_modal: Montrez konfirmdialogo ante efacar posto setting_disable_hover_cards: Desebligar profilprevido dum paso setting_disable_swiping: Desebligar fingromovi setting_display_media: Audvidajmontrajo @@ -230,7 +224,6 @@ io: setting_display_media_show_all: Montrez omno setting_expand_spoilers: Sempre expansigez posti quo markizesis kun kontenajaverti setting_hide_network: Celez vua sociala grafiko - setting_missing_alt_text_modal: Montrar konfirmdialogo ante afishar audvidaji sen alternative texto setting_reduce_motion: Despluigar movo di animi setting_system_font_ui: Uzez originala literfonto di sistemo setting_system_scrollbars_ui: Uzar originala rullangeto di sistemo @@ -280,10 +273,8 @@ io: status_page_url: URL dil stando-pagino theme: Originala temo thumbnail: Servilimajeto - timeline_preview: Permisez neyurizita aceso a publika tempolineo trendable_by_default: Permisez populari sen kontrolo trends: Ebligar populari - trends_as_landing_page: Uzar populari quale la iniciala pagino interactions: must_be_follower: Celar la savigi da homi, qui ne sequas tu must_be_following: Celar la savigi da homi, quin tu ne sequas diff --git a/config/locales/simple_form.is.yml b/config/locales/simple_form.is.yml index bbbba8c8f7aa8e..cffa7449d35731 100644 --- a/config/locales/simple_form.is.yml +++ b/config/locales/simple_form.is.yml @@ -54,15 +54,18 @@ is: password: Notaðu minnst 8 stafi phrase: Verður notað til samsvörunar burtséð frá stafstöðu texta eða viðvörunar vegna efnis í færslu scopes: Að hvaða API-kerfisviðmótum forritið fær aðgang. Ef þú velur efsta-stigs svið, þarftu ekki að gefa einstakar heimildir. + setting_advanced_layout: Birta Mastodon í margra dálka framsetningu, sem gerir kleift að skoða saman tímalínu, tilkynningar og þriðja dálk að eigin vali. Ekki er mælt með þessu fyrir smærri skjái. setting_aggregate_reblogs: Ekki sýna nýjar endurbirtingar á færslum sem hafa nýlega verið endurbirtar (hefur bara áhrif á ný-mótteknar endurbirtingar) setting_always_send_emails: Venjulega eru tilkynningar í tölvupósti ekki sendar þegar þú ert virk/ur í að nota Mastodon + setting_boost_modal: Þegar þetta er virkt, mun endurbirting fyrst opna staðfestingarglugga, þar sem þú getur breytt sýnileika endurbirtingarinnar þinnar. setting_default_quote_policy_private: Aðrir geta ekki vitnað í færslur einungis til fylgjenda sem skrifaðar eru á Mastodon. setting_default_quote_policy_unlisted: Þegar fólk vitnar í þig verða færslurnar þeirr einnig faldar á vinsældatímalínum. setting_default_sensitive: Viðkvæmt myndefni er sjálfgefið falið og er hægt að birta með smelli setting_display_media_default: Fela myndefni sem merkt er viðkvæmt setting_display_media_hide_all: Alltaf fela allt myndefni setting_display_media_show_all: Alltaf birta myndefni sem merkt er viðkvæmt - setting_emoji_style: Hvernig birta skal tjáningartákn (emoji). "Sjálfvirkt" mun reyna að nota innbyggð tjáningartákn, en til vara verða notuð Twemoji-tákn fyrir eldri vafra. + setting_emoji_style: Hvernig birta skal lyndistákn (emoji). "Sjálfvirkt" mun reyna að nota innbyggð lyndistákn, en til vara verða notuð Twemoji-tákn fyrir eldri vafra. + setting_quick_boosting_html: Þegar þetta er virkt, sé smellt á %{boost_icon}-endurbirtingartáknið mun endurbirting eiga sér stað strax í stað þess að opna endurbirta/tilvitnun fellivalmyndina. Tilvitnunaraðgerðin færist þá yfir í %{options_icon} (Options) valmyndina. setting_system_scrollbars_ui: Á einungis við um vafra fyrir vinnutölvur sem byggjast á Safari og Chrome setting_use_blurhash: Litstiglarnir byggja á litunum í földu myndunum, en gera öll smáatriði óskýr setting_use_pending_items: Fela uppfærslur tímalínu þar til smellt er, í stað þess að hún skruni streyminu sjálfvirkt @@ -85,11 +88,12 @@ is: activity_api_enabled: Fjöldi staðværra stöðufærslna, virkra notenda og nýskráninga í vikulegum skömmtum app_icon: WEBP, PNG, GIF eða JPG. Tekur yfir sjálfgefna táknmynd forrits á snjalltækjum með sérsniðinni táknmynd. backups_retention_period: Notendur hafa kost á að útbúa safnskrár með færslunum sínum til að sækja svo síðar. Þegar þetta er stillt á jákvætt gildi, verður þessum safnskrám eytt sjáfkrafa eftir þeim tiltekna fjölda daga. - bootstrap_timeline_accounts: Þessir notendaaðgangar verða festir efst í meðmælum til nýrra notenda um að fylgjast með þeim. + bootstrap_timeline_accounts: Þessi aðgangar verða festir efst á listann yfir þá aðila sem mælt er með að nýir notendur fylgist með. Gefðu upp lista yfir aðganga aðgreindan með kommum. closed_registrations_message: Birtist þegar lokað er á nýskráningar content_cache_retention_period: Öllum færslum af öðrum netþjónum (þar með taldar endurbirtingar og svör) verður eytt eftir uppgefinn fjölda daga, án tillits til gagnvirkni staðværra notenda við þessar færslur. Þetta á einnig við um færslur sem notandinn hefur merkt sem bókamerki eða eftirlæti. Beinar tilvísanir (einkaspjall) milli notenda af mismunandi netþjónum munu einnig tapast og er engin leið til að endurheimta þær. Notkun á þessari stillingu er einungis ætluð sérstilltum netþjónum og mun skemma fyrir notendum ef þetta er sett upp fyrir almenna notkun. custom_css: Þú getur virkjað sérsniðna stíla í vefútgáfu Mastodon. favicon: WEBP, PNG, GIF eða JPG. Tekur yfir sjálfgefna Mastodon favicon-táknmynd með sérsniðinni táknmynd. + landing_page: Þetta ákvarðar hvaða síðu nýir gestir sjá þegar þeir koma fyrst á netþjóninn þinn. Ef þú velur "Vinsælt" þá þurfa vinsælar færslur að vera virkjaðar í stillingum fyrir Uppgötvun. Ef þú velur "Staðbundið streymi" þá þarf "Aðgangur að beinum streymum, þar með töldum staðværum færslum" að vera stillt á "Allir" í stillingum fyrir Uppgötvun. mascot: Þetta tekyr yfir myndskreytinguna í ítarlega vefviðmótinu. media_cache_retention_period: Myndefnisskrár úr færslum sem gerðar eru af fjartengdum notendum eru geymdar á netþjóninum þínum. Þegar þetta er stillt á jákvætt gildi, verður þessum skrám eytt sjáfkrafa eftir þeim tiltekna fjölda daga. Ef beðið er um myndefnið eftir að því er eytt, mun það verða sótt aftur ef frumgögnin eru ennþá aðgengileg. Vegna takmarkana á hversu oft forskoðunarspjöld tengla eru sótt á utanaðkomandi netþjóna, þá er mælt með því að setja þetta gildi á að minnsta kosti 14 daga, annars gæti mistekist að uppfæra forskoðunarspjöld tengla eftir þörfum fyrir þann tíma. min_age: Notendur verða beðnir um að staðfesta fæðingardag sinn við nýskráningu @@ -105,10 +109,9 @@ is: status_page_url: Slóð á síðu þar sem fólk getur séð ástand netþjónsins þegar vandræði koma upp theme: Þema sem útskráðir gestir og nýjir notendur sjá. thumbnail: Mynd um það bil 2:1 sem birtist samhliða upplýsingum um netþjóninn þinn. - timeline_preview: Gestir sem ekki eru skráðir inn munu geta skoðað nýjustu opinberu færslurnar sem tiltækar eru á þjóninum. trendable_by_default: Sleppa handvirkri yfirferð á vinsælu efni. Áfram verður hægt að fjarlægja stök atriði úr vinsældarlistum. trends: Vinsældir sýna hvaða færslur, myllumerki og fréttasögur séu í umræðunni á netþjóninum þínum. - trends_as_landing_page: Sýna vinsælt efni til ekki-innskráðra notenda í stað lýsingar á þessum netþjóni. Krefst þess að vinsældir efnis sé virkjað. + wrapstodon: Býður notendum á netþjóninum upp á skemmtilega samantekt um notkun þeirra á Mastodon á árinu sem er að líða. Þessi eiginleiki er í boði á milli 10. og 31. desember ár hvert og býðst þeim notendum sem hafa gert að minnsta kosti eina opinbera eða hljóðlega opinbera færslu og notað a. m. k. eitt myllumerki á árinu. form_challenge: current_password: Þú ert að fara inn á öryggissvæði imports: @@ -235,22 +238,23 @@ is: setting_aggregate_reblogs: Hópa endurbirtingar í tímalínum setting_always_send_emails: Alltaf senda tilkynningar í tölvupósti setting_auto_play_gif: Spila sjálfkrafa GIF-hreyfimyndir - setting_boost_modal: Sýna staðfestingarglugga fyrir endurbirtingu + setting_boost_modal: Stýrðu sýnileika endurbirtinga setting_default_language: Tungumál sem skrifað er á setting_default_privacy: Sýnileiki færslna setting_default_quote_policy: Hverjir geta gert tilvitnanir setting_default_sensitive: Alltaf merkja myndefni sem viðkvæmt - setting_delete_modal: Birta staðfestingarglugga áður en færslu er eytt + setting_delete_modal: Vara mig við áður en færslum er eytt setting_disable_hover_cards: Gera óvirka forskoðun notandasniðs við yfirsvif setting_disable_swiping: Gera strokuhreyfingar óvirkar setting_display_media: Birting myndefnis setting_display_media_default: Sjálfgefið setting_display_media_hide_all: Fela allt setting_display_media_show_all: Birta allt - setting_emoji_style: Stíll tjáningartákna + setting_emoji_style: Stíll lyndistákna setting_expand_spoilers: Alltaf útfella færslur sem eru með aðvörun vegna efnisins setting_hide_network: Fela félagsnetið þitt - setting_missing_alt_text_modal: Birta staðfestingarglugga áður en myndefni án ALT-hjálpartexta er birt + setting_missing_alt_text_modal: Vara mig við áður en myndefni er birt án ALT-varatexta + setting_quick_boosting: Virkja fljótlega endurbirtingu setting_reduce_motion: Minnka hreyfingu í hreyfimyndum setting_system_font_ui: Nota sjálfgefið letur kerfisins setting_system_scrollbars_ui: Nota sjálfgefna skrunstiku kerfisins @@ -284,12 +288,17 @@ is: content_cache_retention_period: Tímabil sem á að geyma fjartengt efni custom_css: Sérsniðið CSS favicon: Auðkennismynd + landing_page: Kynningarsíða fyrir nýja gesti + local_live_feed_access: Aðgangur að beinum streymum, þar með töldum staðværum færslum + local_topic_feed_access: Aðgangur að myllumerkjum og tengdum streymum, þar með töldum staðværum færslum mascot: Sérsniðið gæludýr (eldra) media_cache_retention_period: Tímalengd sem myndefni haldið min_age: Kröfur um lágmarksaldur peers_api_enabled: Birta lista yfir uppgötvaða netþjóna í API-kerfisviðmótinu profile_directory: Virkja notendamöppu registrations_mode: Hverjir geta nýskráð sig + remote_live_feed_access: Aðgangur að beinum streymum, þar með töldum fjartengdum færslum + remote_topic_feed_access: Aðgangur að myllumerkjum og tengdum streymum, þar með töldum fjartengdum færslum require_invite_text: Krefjast ástæðu fyrir þátttöku show_domain_blocks: Sýna útilokanir léna show_domain_blocks_rationale: Sýna af hverju lokað var á lén @@ -302,10 +311,9 @@ is: status_page_url: Slóð á ástandssíðu theme: Sjálfgefið þema thumbnail: Smámynd vefþjóns - timeline_preview: Leyfa óauðkenndan aðgang að opinberum tímalínum trendable_by_default: Leyfa vinsælt efni án undanfarandi yfirferðar trends: Virkja vinsælt - trends_as_landing_page: Nota vinsælasta sem upphafssíðu + wrapstodon: Virkja Ársuppgjörið interactions: must_be_follower: Loka á tilkynningar frá þeim sem ekki eru fylgjendur must_be_following: Loka á tilkynningar frá þeim sem þú fylgist ekki með @@ -366,9 +374,9 @@ is: jurisdiction: Lögsagnarumdæmi min_age: Lágmarksaldur user: - date_of_birth_1i: Dagur + date_of_birth_1i: Ár date_of_birth_2i: Mánuður - date_of_birth_3i: Ár + date_of_birth_3i: Dagur role: Hlutverk time_zone: Tímabelti user_role: diff --git a/config/locales/simple_form.it.yml b/config/locales/simple_form.it.yml index 39587cd5e5028c..98772991814ba7 100644 --- a/config/locales/simple_form.it.yml +++ b/config/locales/simple_form.it.yml @@ -54,8 +54,10 @@ it: password: Usa almeno 8 caratteri phrase: Il confronto sarà eseguito ignorando minuscole/maiuscole e i content warning scopes: A quali API l'applicazione potrà avere accesso. Se selezionate un ambito di alto livello, non c'è bisogno di selezionare quelle singole. + setting_advanced_layout: Visualizza Mastodon con un layout multicolonna, permettendoti di visualizzare la cronologia, le notifiche e una terza colonna a tua scelta. Non consigliato per schermi di dimensioni più piccole. setting_aggregate_reblogs: Non mostrare nuove condivisioni per toot che sono stati condivisi di recente (ha effetto solo sulle nuove condivisioni) setting_always_send_emails: Normalmente le notifiche e-mail non vengono inviate quando si utilizza attivamente Mastodon + setting_boost_modal: Se abilitata, la funzione Boost aprirà prima una finestra di dialogo di conferma in cui potrai modificare la visibilità del tuo potenziamento. setting_default_quote_policy_private: I post scritti e riservati ai seguaci su Mastodon non possono essere citati da altri. setting_default_quote_policy_unlisted: Quando le persone ti citano, il loro post verrà nascosto anche dalle timeline di tendenza. setting_default_sensitive: Media con contenuti sensibili sono nascosti in modo predefinito e possono essere rivelati con un click @@ -63,6 +65,7 @@ it: setting_display_media_hide_all: Nascondi sempre tutti i media setting_display_media_show_all: Mostra sempre i media segnati come sensibili setting_emoji_style: Come visualizzare gli emoji. "Automatico" proverà a usare gli emoji nativi, ma per i browser più vecchi ricorrerà a Twemoji. + setting_quick_boosting_html: Se abilitato, cliccando sull'icona Boost %{boost_icon}, il potenziamento verrà immediatamente attivato, anziché aprire il menu a discesa potenziamento/citazione. Sposta l'azione della citazione nel menu %{options_icon} (Opzioni). setting_system_scrollbars_ui: Si applica solo ai browser desktop basati su Safari e Chrome setting_use_blurhash: I gradienti sono basati sui colori delle immagini nascoste ma offuscano tutti i dettagli setting_use_pending_items: Fare clic per mostrare i nuovi messaggi invece di aggiornare la timeline automaticamente @@ -85,11 +88,12 @@ it: activity_api_enabled: Conteggi di post pubblicati localmente, utenti attivi e nuove registrazioni in gruppi settimanali app_icon: WEBP, PNG, GIF o JPG. Sostituisce l'icona dell'app predefinita sui dispositivi mobili con un'icona personalizzata. backups_retention_period: Gli utenti hanno la possibilità di generare archivi dei propri post da scaricare successivamente. Se impostati su un valore positivo, questi archivi verranno automaticamente eliminati dallo spazio di archiviazione dopo il numero di giorni specificato. - bootstrap_timeline_accounts: Questi account verranno aggiunti in cima ai consigli da seguire dei nuovi utenti. + bootstrap_timeline_accounts: Questi account verranno aggiunti in cima ai suggerimenti dei nuovi utenti. Fornisci un elenco di account separati da virgole. closed_registrations_message: Visualizzato alla chiusura delle iscrizioni content_cache_retention_period: Tutti i post da altri server (inclusi booster e risposte) verranno eliminati dopo il numero specificato di giorni, senza tener conto di eventuali interazioni con gli utenti locali con tali post. Questo include i post in cui un utente locale ha contrassegnato come segnalibri o preferiti. Anche le menzioni private tra utenti di diverse istanze andranno perse e impossibile da ripristinare. L'uso di questa impostazione è inteso per casi di scopo speciale e rompe molte aspettative dell'utente quando implementato per uso generale. custom_css: È possibile applicare stili personalizzati sulla versione web di Mastodon. favicon: WEBP, PNG, GIF o JPG. Sostituisce la favicon predefinita di Mastodon con un'icona personalizzata. + landing_page: Seleziona quale pagina vedono i nuovi visitatori al loro primo arrivo sul tuo server. Se selezioni "Tendenze", è necessario abilitare le tendenze nelle Impostazioni di scoperta. Se selezioni "Feed locale", allora è necessario impostare "Accesso ai feed in diretta con post locali" su "Tutti" nelle Impostazioni di scoperta. mascot: Sostituisce l'illustrazione nell'interfaccia web avanzata. media_cache_retention_period: I file multimediali da post fatti da utenti remoti sono memorizzati nella cache sul tuo server. Quando impostato a un valore positivo, i media verranno eliminati dopo il numero specificato di giorni. Se i dati multimediali sono richiesti dopo che sono stati eliminati, saranno nuovamente scaricati, se il contenuto sorgente è ancora disponibile. A causa di restrizioni su quanto spesso link anteprima carte sondaggio siti di terze parti, si consiglia di impostare questo valore ad almeno 14 giorni, o le schede di anteprima link non saranno aggiornate su richiesta prima di quel tempo. min_age: Agli utenti verrà chiesto di confermare la propria data di nascita durante l'iscrizione @@ -105,10 +109,9 @@ it: status_page_url: URL di una pagina in cui le persone possono visualizzare lo stato di questo server durante un disservizio theme: Tema visualizzato dai visitatori e dai nuovi utenti disconnessi. thumbnail: Un'immagine approssimativamente 2:1 visualizzata insieme alle informazioni del tuo server. - timeline_preview: I visitatori disconnessi potranno sfogliare i post pubblici più recenti disponibili sul server. trendable_by_default: Salta la revisione manuale dei contenuti di tendenza. I singoli elementi possono ancora essere rimossi dalle tendenze dopo il fatto. trends: Le tendenze mostrano quali post, hashtag e notizie stanno guadagnando popolarità sul tuo server. - trends_as_landing_page: Mostra i contenuti di tendenza agli utenti disconnessi e ai visitatori, invece di una descrizione di questo server. Richiede l'abilitazione delle tendenze. + wrapstodon: Offri agli utenti locali la possibilità di generare un riassunto giocoso del loro utilizzo di Mastodon durante l’anno. Questa funzione è disponibile dal 10 al 31 dicembre di ogni anno ed è offerta agli utenti che hanno pubblicato almeno un post pubblico o pubblico silenzioso e utilizzato almeno un hashtag nell’arco dell’anno. form_challenge: current_password: Stai entrando in un'area sicura imports: @@ -235,12 +238,12 @@ it: setting_aggregate_reblogs: Raggruppa condivisioni in timeline setting_always_send_emails: Manda sempre notifiche via email setting_auto_play_gif: Riproduci automaticamente le GIF animate - setting_boost_modal: Mostra dialogo di conferma prima del boost + setting_boost_modal: Controllo della visibilità del potenziamento setting_default_language: Lingua dei post setting_default_privacy: Visibilità dei post setting_default_quote_policy: Chi può citare setting_default_sensitive: Segna sempre i media come sensibili - setting_delete_modal: Mostra dialogo di conferma prima di eliminare un post + setting_delete_modal: Avvisami prima di eliminare un post setting_disable_hover_cards: Disabilita l'anteprima del profilo al passaggio del mouse setting_disable_swiping: Disabilita i movimenti di scorrimento setting_display_media: Visualizzazione dei media @@ -250,7 +253,8 @@ it: setting_emoji_style: Stile emoji setting_expand_spoilers: Espandi sempre post con content warning setting_hide_network: Nascondi la tua rete - setting_missing_alt_text_modal: Chiedi di confermare prima di pubblicare media senza testo alternativo + setting_missing_alt_text_modal: Avvisami prima di pubblicare contenuti multimediali senza testo alternativo + setting_quick_boosting: Abilita il potenziamento rapido setting_reduce_motion: Riduci movimento nelle animazioni setting_system_font_ui: Usa il carattere predefinito del sistema setting_system_scrollbars_ui: Utilizza la barra di scorrimento predefinita del sistema @@ -284,12 +288,17 @@ it: content_cache_retention_period: Periodo di ritenzione del contenuto remoto custom_css: Personalizza CSS favicon: Favicon + landing_page: Pagina di destinazione per i nuovi visitatori + local_live_feed_access: Accesso ai feed dal vivo con post locali + local_topic_feed_access: Accesso a feed di hashtag e link con post locali mascot: Personalizza mascotte (legacy) media_cache_retention_period: Periodo di conservazione della cache multimediale min_age: Età minima richiesta peers_api_enabled: Pubblica l'elenco dei server scoperti nell'API profile_directory: Abilita directory del profilo registrations_mode: Chi può iscriversi + remote_live_feed_access: Accesso ai feed live con post remoti + remote_topic_feed_access: Accesso a feed di hashtag e link con post remoti require_invite_text: Richiedi un motivo per unirsi show_domain_blocks: Mostra i blocchi di dominio show_domain_blocks_rationale: Mostra perché i domini sono stati bloccati @@ -302,10 +311,9 @@ it: status_page_url: URL della pagina di stato theme: Tema predefinito thumbnail: Miniatura del server - timeline_preview: Consenti l'accesso non autenticato alle timeline pubbliche trendable_by_default: Consenti le tendenze senza revisione preventiva trends: Abilita le tendenze - trends_as_landing_page: Usa le tendenze come pagina di destinazione + wrapstodon: Abilita Wrapstodon interactions: must_be_follower: Blocca notifiche da chi non ti segue must_be_following: Blocca notifiche dalle persone che non segui @@ -366,9 +374,9 @@ it: jurisdiction: Giurisdizione legale min_age: Età minima user: - date_of_birth_1i: Giorno + date_of_birth_1i: Anno date_of_birth_2i: Mese - date_of_birth_3i: Anno + date_of_birth_3i: Giorno role: Ruolo time_zone: Fuso orario user_role: diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml index 15e6439cb72dd7..34c1bcc8e4c414 100644 --- a/config/locales/simple_form.ja.yml +++ b/config/locales/simple_form.ja.yml @@ -54,12 +54,16 @@ ja: password: 少なくとも8文字は入力してください phrase: 投稿内容の大文字小文字や閲覧注意に関係なく一致 scopes: アプリの API に許可するアクセス権を選択してください。最上位のスコープを選択する場合、個々のスコープを選択する必要はありません。 + setting_advanced_layout: Mastodonを複数カラムのレイアウトで表示します。タイムライン、通知、そして好みのカラムを閲覧できます。小さい画面には非推奨です。 setting_aggregate_reblogs: 最近ブーストされた投稿が新たにブーストされても表示しません (設定後受信したものにのみ影響) setting_always_send_emails: 通常、Mastodon からメール通知は行われません。 + setting_boost_modal: 有効にすると、ブーストによって、まず、ブーストの公開範囲を設定できる確認ダイアログが表示されます。 setting_default_sensitive: 閲覧注意状態のメディアはデフォルトでは内容が伏せられ、クリックして初めて閲覧できるようになります setting_display_media_default: 閲覧注意としてマークされたメディアは隠す setting_display_media_hide_all: メディアを常に隠す setting_display_media_show_all: メディアを常に表示する + setting_emoji_style: 絵文字の表示方法。「オート」の場合、可能ならネイティブの絵文字を使用し、レガシーなブラウザではTwemojiで代替します。 + setting_quick_boosting_html: 有効にすると、%{boost_icon} ブーストアイコンのクリックで即座にブーストされます。ブースト/引用ドロップダウンは開きません。引用アクションは %{options_icon} (オプション) メニューに移されます。 setting_system_scrollbars_ui: Safari/Chromeベースのデスクトップブラウザーでのみ有効です setting_use_blurhash: ぼかしはメディアの色を元に生成されますが、細部は見えにくくなっています setting_use_pending_items: 新着があってもタイムラインを自動的にスクロールしないようにします @@ -73,7 +77,6 @@ ja: featured_tag: name: 最近使用したハッシュタグ filters: - action: 投稿がフィルタに一致したときに実行するアクションを選択 actions: blur: メディアは警告して非表示にするが、テキストは表示する hide: フィルタに一致した投稿を完全に非表示にします @@ -82,7 +85,6 @@ ja: activity_api_enabled: 週単位でローカルで公開された投稿数、アクティブユーザー数、新規登録者数を表示します app_icon: モバイル端末で表示されるデフォルトのアプリアイコンを独自のアイコンで上書きします。WEBP、PNG、GIF、JPGが利用可能です。 backups_retention_period: ユーザーには、後でダウンロードするために投稿のアーカイブを生成する機能があります。正の値に設定すると、これらのアーカイブは指定された日数後に自動的にストレージから削除されます。 - bootstrap_timeline_accounts: これらのアカウントは、新しいユーザー向けのおすすめユーザーの一番上にピン留めされます。 closed_registrations_message: アカウント作成を停止している時に表示されます content_cache_retention_period: 他のサーバーからのすべての投稿(ブーストや返信を含む)は、指定された日数が経過すると、ローカルユーザーとのやりとりに関係なく削除されます。これには、ローカルユーザーがブックマークやお気に入りとして登録した投稿も含まれます。異なるサーバーのユーザー間の非公開な返信も失われ、復元することは不可能です。この設定の使用は特別な目的のインスタンスのためのものであり、一般的な目的のサーバーで使用した場合、多くのユーザーの期待を裏切ることになります。 custom_css: ウェブ版のMastodonでカスタムスタイルを適用できます。 @@ -102,10 +104,8 @@ ja: status_page_url: 障害発生時などにユーザーがサーバーの状態を確認できるページのURL theme: ログインしていない人と新規ユーザーに表示されるテーマ。 thumbnail: サーバー情報と共に表示される、アスペクト比が約 2:1 の画像。 - timeline_preview: ログインしていないユーザーがサーバー上の最新の公開投稿を閲覧できるようにします。 trendable_by_default: トレンドの審査を省略します。トレンドは掲載後でも個別に除外できます。 trends: トレンドは、サーバー上で人気を集めている投稿、ハッシュタグ、ニュース記事などが表示されます。 - trends_as_landing_page: ログインしていないユーザーに対して、サーバーの説明の代わりにトレンドコンテンツを表示します。トレンドを有効にする必要があります。 form_challenge: current_password: セキュリティ上重要なエリアにアクセスしています imports: @@ -225,12 +225,12 @@ ja: setting_aggregate_reblogs: ブーストをまとめる setting_always_send_emails: 常にメール通知を送信する setting_auto_play_gif: アニメーションGIFを自動再生する - setting_boost_modal: ブーストする前に確認ダイアログを表示する + setting_boost_modal: ブーストの公開範囲の設定 setting_default_language: 投稿する言語 setting_default_privacy: 投稿の公開範囲 setting_default_quote_policy: 引用できるユーザー setting_default_sensitive: メディアを常に閲覧注意としてマークする - setting_delete_modal: 投稿を削除する前に確認ダイアログを表示する + setting_delete_modal: 投稿を削除する前に警告する setting_disable_hover_cards: マウスオーバーでプロフィールをポップアップしない setting_disable_swiping: スワイプでの切り替えを無効にする setting_display_media: メディアの表示 @@ -240,7 +240,8 @@ ja: setting_emoji_style: 絵文字スタイル setting_expand_spoilers: 閲覧注意としてマークされた投稿を常に展開する setting_hide_network: 繋がりを隠す - setting_missing_alt_text_modal: 代替テキストなしでメディアを投稿する前に確認ダイアログを表示する + setting_missing_alt_text_modal: 代替テキストなしでメディアを投稿する前に警告する + setting_quick_boosting: クイックブーストの有効化 setting_reduce_motion: アニメーションの動きを減らす setting_system_font_ui: システムのデフォルトフォントを使う setting_system_scrollbars_ui: システムのデフォルトのスクロールバーを使う @@ -292,10 +293,8 @@ ja: status_page_url: サーバーの状態ページのURL theme: デフォルトテーマ thumbnail: サーバーのサムネイル - timeline_preview: 公開タイムラインへの未認証のアクセスを許可する trendable_by_default: 審査前のトレンドの掲載を許可する trends: トレンドを有効にする - trends_as_landing_page: 新規登録画面にトレンドを表示する interactions: must_be_follower: フォロワー以外からの通知をブロック must_be_following: フォローしていないユーザーからの通知をブロック @@ -356,9 +355,7 @@ ja: jurisdiction: 裁判管轄 min_age: 登録可能な最低年齢 user: - date_of_birth_1i: 日 date_of_birth_2i: 月 - date_of_birth_3i: 年 role: ロール time_zone: タイムゾーン user_role: diff --git a/config/locales/simple_form.ka.yml b/config/locales/simple_form.ka.yml index e9a07fd97a62a7..d5c65efc1f5e63 100644 --- a/config/locales/simple_form.ka.yml +++ b/config/locales/simple_form.ka.yml @@ -49,10 +49,8 @@ ka: password: პაროლი phrase: სიტყვა ან ფრაზა setting_auto_play_gif: ანიმაციური გიფების ავტო-დაკვრა - setting_boost_modal: ბუსტამე მოხდეს დამოწმება setting_default_language: პოსტინგის ენა setting_default_sensitive: ყოველთვის მოინიშნოს მედია მგრძნობიარედ - setting_delete_modal: ტუტის გაუქმებამდე გამოჩნდეს დადასტურების ფანჯარა setting_hide_network: თქვენი ქსელის დამალვა setting_reduce_motion: მოძრაობის შემცირება ანიმაციებში setting_system_font_ui: მოხდეს სისტემის საწყისი ფონტის მოხმარება diff --git a/config/locales/simple_form.kab.yml b/config/locales/simple_form.kab.yml index 4e06281ece2d64..cf66a05130654a 100644 --- a/config/locales/simple_form.kab.yml +++ b/config/locales/simple_form.kab.yml @@ -29,6 +29,7 @@ kab: name: 'Ha-t-an kra seg ihacṭagen i tesseqdaceḍ ussan-a ineggura maḍi :' form_admin_settings: min_age: Ad ttwasutren yiseqdacen ad sentemen azemz-nsen n tlalit deg ujerred + site_contact_username: Amek i zemren ad ak·em-id-afen medden ɣef Mastodon. form_challenge: current_password: Tkecmeḍ ɣer temnaḍt taɣellsant imports: @@ -152,13 +153,12 @@ kab: tag: name: Ahacṭag terms_of_service: + changelog: Amaynut? text: Tiwtilin n useqdec terms_of_service_generator: domain: Taɣult user: - date_of_birth_1i: Ass date_of_birth_2i: Ayyur - date_of_birth_3i: Aseggas role: Tamlilt time_zone: Tamnaḍt tasragant user_role: diff --git a/config/locales/simple_form.kk.yml b/config/locales/simple_form.kk.yml index 97cfcbd23ba895..ad80ff667cb25d 100644 --- a/config/locales/simple_form.kk.yml +++ b/config/locales/simple_form.kk.yml @@ -43,10 +43,8 @@ kk: phrase: Кілтсөз немесе фраза setting_advanced_layout: Кеңейтілген веб-интерфейс қосу setting_auto_play_gif: GIF анимацияларды бірден қосу - setting_boost_modal: Бөлісу алдында растау диалогын көрсету setting_default_language: Жазба тілі setting_default_sensitive: Медиаларды әрдайым нәзік ретінде белгілеу - setting_delete_modal: Жазбаны өшірместен бұрын растау диалогын көрсету setting_display_media: Медианы көрсету setting_display_media_default: Əдепкі setting_display_media_hide_all: Бәрін жасыру diff --git a/config/locales/simple_form.ko.yml b/config/locales/simple_form.ko.yml index 4cca7676f7aef1..5d96fd07215dfb 100644 --- a/config/locales/simple_form.ko.yml +++ b/config/locales/simple_form.ko.yml @@ -54,13 +54,18 @@ ko: password: 최소 8글자 phrase: 게시물 내용이나 열람주의 내용 안에서 대소문자 구분 없이 매칭 됩니다 scopes: 애플리케이션에 허용할 API들입니다. 최상위 스코프를 선택하면 개별적인 것은 선택하지 않아도 됩니다. + setting_advanced_layout: 마스토돈을 멀티컬럼으로 보여주어 타임라인, 알림, 그리고 내가 원하는 컬럼을 한 번에 볼 수 있도록 합니다. 작은 화면에선 추천하지 않습니다. setting_aggregate_reblogs: 최근에 부스트 됐던 게시물은 새로 부스트 되어도 보여주지 않기 (새로 받은 부스트에만 적용됩니다) setting_always_send_emails: 기본적으로 마스토돈을 활동적으로 사용하고 있을 때에는 이메일 알림이 보내지지 않습니다 + setting_boost_modal: 활성화하면 부스트하기 전에 부스트의 공개설정을 바꿀 수 있는 확인창이 먼저 뜨게 됩니다. + setting_default_quote_policy_private: 마스토돈에서 작성된 팔로워 전용 게시물은 다른 사용자가 인용할 수 없습니다. + setting_default_quote_policy_unlisted: 사람들에게 인용된 경우, 인용한 게시물도 유행 타임라인에서 감추게 됩니다. setting_default_sensitive: 민감한 미디어는 기본적으로 가려져 있으며 클릭해서 볼 수 있습니다 setting_display_media_default: 민감함으로 표시된 미디어 가리기 setting_display_media_hide_all: 모든 미디어를 가리기 setting_display_media_show_all: 모든 미디어를 보이기 setting_emoji_style: 에모지 표현 방식. "자동"은 시스템 기본 에모지를 적용하고 그렇지 못하는 오래된 브라우저의 경우 트웨모지를 사용합니다. + setting_quick_boosting_html: 활성화하면 %{boost_icon}부스트 아이콘을 클릭했을 때 부스트/인용 드롭다운 메뉴가 뜨지 않고 바로 부스트하게 됩니다. 인용은 %{options_icon} (옵션) 메뉴 안으로 이동합니다. setting_system_scrollbars_ui: 사파리와 크롬 기반의 데스크탑 브라우저만 적용됩니다 setting_use_blurhash: 그라디언트는 숨겨진 내용의 색상을 기반으로 하지만 상세 내용은 보이지 않게 합니다 setting_use_pending_items: 타임라인의 새 게시물을 자동으로 보여 주는 대신, 클릭해서 나타내도록 합니다 @@ -83,9 +88,8 @@ ko: activity_api_enabled: 주별 로컬에 게시된 글, 활성 사용자 및 새로운 가입자 수 app_icon: WEBP, PNG, GIF 또는 JPG. 모바일 기기에 쓰이는 기본 아이콘을 대체합니다. backups_retention_period: 사용자들은 나중에 다운로드하기 위해 게시물 아카이브를 생성할 수 있습니다. 양수로 설정된 경우 이 아카이브들은 지정된 일수가 지난 후에 저장소에서 자동으로 삭제될 것입니다. - bootstrap_timeline_accounts: 이 계정들은 팔로우 추천 목록 상단에 고정됩니다. closed_registrations_message: 새 가입을 차단했을 때 표시됩니다 - content_cache_retention_period: 다른 서버의 모든 게시물(부스트 및 답글 포함)은 해당 게시물에 대한 로컬 사용자의 상호 작용과 관계없이 지정된 일수가 지나면 삭제됩니다. 여기에는 로컬 사용자가 북마크 또는 즐겨찾기로 표시한 게시물도 포함됩니다. 다른 인스턴스 사용자와 주고 받은 비공개 멘션도 손실되며 복원할 수 없습니다. 이 설정은 특수 목적의 인스턴스를 위한 것이며 일반적인 용도의 많은 사용자의 예상이 빗나가게 됩니다. + content_cache_retention_period: "(부스트 및 답글 포함) 다른 서버의 모든 게시물은 해당 게시물에 대한 로컬 사용자의 상호 작용과 관계없이 지정된 일수가 지나면 삭제됩니다. 여기에는 로컬 사용자가 북마크 또는 즐겨찾기로 표시한 게시물도 포함됩니다. 다른 인스턴스 사용자와 주고 받은 개인 멘션도 손실되며 복원할 수 없습니다. 이 설정은 특수 목적의 인스턴스를 위한 것이며 일반적인 용도의 많은 사용자의 예상이 빗나가게 됩니다." custom_css: 사용자 지정 스타일을 웹 버전의 마스토돈에 지정할 수 있습니다. favicon: WEBP, PNG, GIF 또는 JPG. 기본 파비콘을 대체합니다. mascot: 고급 웹 인터페이스의 그림을 대체합니다. @@ -103,10 +107,8 @@ ko: status_page_url: 이 서버가 중단된 동안 사람들이 서버의 상태를 볼 수 있는 페이지 URL theme: 로그인 하지 않은 사용자나 새로운 사용자가 보게 될 테마. thumbnail: 대략 2:1 비율의 이미지가 서버 정보 옆에 표시됩니다. - timeline_preview: 로그아웃 한 사용자들이 이 서버에 있는 최신 공개글들을 볼 수 있게 합니다. trendable_by_default: 유행하는 콘텐츠에 대한 수동 승인을 건너뜁니다. 이 설정이 적용된 이후에도 각각의 항목들을 삭제할 수 있습니다. trends: 트렌드는 어떤 게시물, 해시태그 그리고 뉴스 기사가 이 서버에서 인기를 끌고 있는지 보여줍니다. - trends_as_landing_page: 로그아웃한 사용자와 방문자에게 서버 설명 대신 유행하는 내용을 보여줍니다. 유행 기능을 활성화해야 합니다. form_challenge: current_password: 당신은 보안 구역에 진입하고 있습니다 imports: @@ -231,12 +233,12 @@ ko: setting_aggregate_reblogs: 타임라인의 부스트를 그룹화 setting_always_send_emails: 항상 이메일 알림 보내기 setting_auto_play_gif: 애니메이션 GIF를 자동 재생 - setting_boost_modal: 부스트 전 확인창을 띄웁니다 + setting_boost_modal: 부스트 공개범위 제어 setting_default_language: 게시물 언어 setting_default_privacy: 게시물 공개 범위 setting_default_quote_policy: 인용할 수 있는 사람 setting_default_sensitive: 미디어를 언제나 민감한 콘텐츠로 설정 - setting_delete_modal: 게시물 삭제 전 확인창을 띄웁니다 + setting_delete_modal: 게시물을 삭제하기 전 경고하기 setting_disable_hover_cards: 호버시 프로필 미리보기를 비활성화 setting_disable_swiping: 스와이프 모션 비활성화 setting_display_media: 미디어 표시 @@ -246,7 +248,8 @@ ko: setting_emoji_style: 에모지 스타일 setting_expand_spoilers: 내용 경고로 표시된 게시물을 항상 펼치기 setting_hide_network: 내 인맥 숨기기 - setting_missing_alt_text_modal: 대체 텍스트 없이 미디어를 게시하려고 할 때 확인창을 띄웁니다 + setting_missing_alt_text_modal: 대체텍스트가 없는 미디어를 포함하여 게시하기 전 경고 + setting_quick_boosting: 빠른 부스트 활성화 setting_reduce_motion: 애니메이션 줄이기 setting_system_font_ui: 시스템의 기본 글꼴을 사용 setting_system_scrollbars_ui: 시스템 기본 스크롤바 사용 @@ -280,12 +283,17 @@ ko: content_cache_retention_period: 리모트 콘텐츠 보유 기간 custom_css: 사용자 정의 CSS favicon: 파비콘 + landing_page: 새 방문자를 위한 랜딩 페이지 + local_live_feed_access: 로컬 게시물에 대한 실시간 피드 접근 + local_topic_feed_access: 로컬 게시물에 대한 해시태그와 링크 피드 접근 mascot: 사용자 정의 마스코트 (legacy) media_cache_retention_period: 미디어 캐시 유지 기한 min_age: 최소 연령 제한 peers_api_enabled: API에 발견 된 서버들의 목록 발행 profile_directory: 프로필 책자 활성화 registrations_mode: 누가 가입할 수 있는지 + remote_live_feed_access: 리모트 게시물에 대한 실시간 피드 접근 + remote_topic_feed_access: 리모트 게시물에 대한 해시태그와 링크 피드 접근 require_invite_text: 가입 하는 이유를 필수로 입력하게 하기 show_domain_blocks: 도메인 차단 보여주기 show_domain_blocks_rationale: 왜 도메인이 차단되었는지 보여주기 @@ -298,10 +306,8 @@ ko: status_page_url: 상태 페이지 URL theme: 기본 테마 thumbnail: 서버 썸네일 - timeline_preview: 로그인 하지 않고 공개 타임라인에 접근하는 것을 허용 trendable_by_default: 사전 리뷰 없이 트렌드에 오르는 것을 허용 trends: 유행 활성화 - trends_as_landing_page: 유행을 방문 페이지로 쓰기 interactions: must_be_follower: 나를 팔로우 하지 않는 사람에게서 온 알림을 차단 must_be_following: 내가 팔로우 하지 않는 사람에게서 온 알림을 차단 @@ -362,9 +368,9 @@ ko: jurisdiction: 법적 관할권 min_age: 최소 연령 user: - date_of_birth_1i: 일 + date_of_birth_1i: 년 date_of_birth_2i: 월 - date_of_birth_3i: 년 + date_of_birth_3i: 일 role: 역할 time_zone: 시간대 user_role: diff --git a/config/locales/simple_form.ku.yml b/config/locales/simple_form.ku.yml index b9111e3f64d2aa..a2f7b2b9b12a6b 100644 --- a/config/locales/simple_form.ku.yml +++ b/config/locales/simple_form.ku.yml @@ -60,12 +60,10 @@ ku: featured_tag: name: 'Li virê çend haştag hene ku te demên dawî bi kar anîne:' filters: - action: Hilbijêre ku dema şandiyek bi parzûnê re lihevhatî be bila kîjan çalakî were pêkanîn actions: hide: Naveroka parzûnkirî bi tevahî veşêre, mîna ku ew tune be tevbigere warn: Naveroka parzûnkirî li pişt hişyariyek ku sernavê parzûnê qal dike veşêre form_admin_settings: - bootstrap_timeline_accounts: Ev ajimêr wê di pêşnîyarên şopandina bikarhênerên nû de werin derzîkirin. closed_registrations_message: Dema ku tomarkirin girtî bin têne xuyakirin custom_css: Tu dikarî awayên kesane li ser guhertoya malperê ya Mastodon bicîh bikî. mascot: Îlustrasyona navrûyê webê yê pêşketî bêbandor dike. @@ -79,7 +77,6 @@ ku: site_title: Tu çawa dixwazî mirov qale rajekarê te bikin ji bilî navê navperê wî. theme: Rûkara ku mêvanên têneketî û bikarhênerên nû dibînin. thumbnail: Li kêleka zanyariyên rajekarê xwe wêneyeke 2:1 nîşan bide. - timeline_preview: Mêvanên têneketî wê karibin li şandiyên gelemperî yên herî dawî yên ku li ser rajekarê peyda dibin bigerin. trendable_by_default: Nirxandina destan a naveroka rojevê derbas bike. Tiştên kesane dîsa jî dikarin piştî rastiyê ji rojevê werin derxistin. trends: Rojev nîşan dide ka kîjan şandî, hashtag û çîrokê nûçeyan balê dikişîne li ser rajekarê te. form_challenge: @@ -176,10 +173,8 @@ ku: setting_aggregate_reblogs: Di demnameyê de şandiyên bilindkirî kom bike setting_always_send_emails: Her dem agahdariya e-nameyê bişîne setting_auto_play_gif: GIF ên livok bi xweber bilîzine - setting_boost_modal: Gotûbêja pejirandinê nîşan bide berî ku şandî werê bilindkirin setting_default_language: Zimanê weşanê setting_default_sensitive: Her dem medya wek hestyar bide nîşan - setting_delete_modal: Berî ku peyamek were jêbirin, gotûbêja pejirandinê nîşan bide setting_disable_swiping: Tevgerên dişiqite ne çalak bike setting_display_media: Nîşandana medyayê setting_display_media_default: Berdest @@ -229,7 +224,6 @@ ku: site_title: Navê rajekar theme: Rûkara berdest thumbnail: Wêneya piçûk a rajekar - timeline_preview: Mafê bide gihîştina ne naskirî bo demnameya gelemperî trendable_by_default: Mafê bide rojevê bêyî ku were nirxandin trends: Rojevê çalak bike interactions: @@ -266,6 +260,8 @@ ku: name: Hashtag trendable: Bihêle ku ev hashtag werê xuyakirin di bin rojevê de user: + date_of_birth_1i: Sal + date_of_birth_3i: Roj role: Rol user_role: color: Rengê nîşanê diff --git a/config/locales/simple_form.lad.yml b/config/locales/simple_form.lad.yml index 08fefe240f011e..b1d0a3e2562149 100644 --- a/config/locales/simple_form.lad.yml +++ b/config/locales/simple_form.lad.yml @@ -71,13 +71,11 @@ lad: featured_tag: name: 'Aki estan algunas de las etiketas ke mas tienes utilizado resientemente:' filters: - action: Eskoje kuala aksyon realizar kuando una publikasyon koensida kon el filtro actions: hide: Eskonde kompletamente el kontenido filtrado komo si no existiera warn: Eskonde el kontenido filtrado detras de una avertensya enmentando el titolo del filtro form_admin_settings: activity_api_enabled: Numero de publikasyones publikadas lokalmente, utilizadores activos i enrejistrasyones muevas kada semana - bootstrap_timeline_accounts: Estos kuentos apareseran en la parte superior de las rekomendasiones de los muevos utilizadores. closed_registrations_message: Amostrado kuando las enrejistrasyones estan serrados custom_css: Puedes aplikar estilos personalizados a la version web de Mastodon. mascot: Reemplaza la ilustrasyon en la enterfaz web avanzada. @@ -93,10 +91,8 @@ lad: status_page_url: Adreso URL de la pajina ande la djente puede ver el estado de este sirvidor durante una insidensya theme: El tema ke los vizitantes no enrejistrados i los muevos utilizadores ven. thumbnail: Una imaje de aproksimadamente 2:1 se amostra djunto a la enformasyon de tu sirvidor. - timeline_preview: Los vizitantes no konektados podran navigar por los mesajes publikos mas resientes desponivles en el sirvidor. trendable_by_default: Omite la revizyon manuala del kontenido en trend. Los elementos individuales ainda podran supremirse de los trendes. trends: Los trendes amostran ke mesajes, etiketas i haberes estan ganando traksyon en tu sirvidor. - trends_as_landing_page: Amostra kontenido en trend para utilizadores i vizitantes en lugar de una deskripsyon de este sirvidor. Rekiere ke los trendes esten kapasitados. form_challenge: current_password: Estas entrando en un area siguro imports: @@ -201,15 +197,17 @@ lad: setting_aggregate_reblogs: Agrupa repartajasyones en linyas setting_always_send_emails: Siempre embia avizos por posta setting_auto_play_gif: Siempre reproduse los GIFs animados - setting_boost_modal: Amostra ventana de konfirmasyon antes de repartajar setting_default_language: Lingua de publikasyones + setting_default_privacy: Vizibilita de puvlikasyones + setting_default_quote_policy: Ken puede sitar setting_default_sensitive: Syempre marka multimedia komo sensivles - setting_delete_modal: Mostra dialogo de konfirmasyon antes de efasar una publikasyon + setting_disable_hover_cards: Dezaktiva vista previa del profil al pasar el kursor setting_disable_swiping: Inkapasita movimyentos de arresvalamiento setting_display_media: Vizualizasyon de multimedia setting_display_media_default: Predeterminado setting_display_media_hide_all: Eskonde todo setting_display_media_show_all: Amostra todo + setting_emoji_style: Estilo de emoji setting_expand_spoilers: Siempre espande las publikasyones markadas kon avertensyas de kontenido setting_hide_network: Eskonde tu red sosyala setting_reduce_motion: Reduse el movimyento en animasyones @@ -244,6 +242,7 @@ lad: favicon: Ikona de favoritos mascot: Maskota personalizada (legado) media_cache_retention_period: Periodo de retensyon de kashe multimedia + min_age: Edad minima rekerida peers_api_enabled: Publika lista de sirvidores diskuviertos en la API profile_directory: Kapasita katalogo de profiles registrations_mode: Ken puede enrejistrarse @@ -259,10 +258,8 @@ lad: status_page_url: URL de pajina de estado theme: Tema predeterminado thumbnail: Minyatura del sirvidor - timeline_preview: Permite el akseso no autentifikado a las linyas de tiempo publikas trendable_by_default: Permite trendes sin revizyon previa trends: Kapasita trendes - trends_as_landing_page: Kulanea trendes komo la pajina prinsipala interactions: must_be_follower: Bloka avizos de personas ke no te sigen must_be_following: Bloka avizos de personas a las kualas no siges @@ -287,6 +284,7 @@ lad: follow_request: Alguno tiene solisitado segirte mention: Alguno te enmento pending_account: Muevo kuento nesesita revizyon + quote: Alguno te sita reblog: Alguno repartajo tu publikasyon report: Muevo raporto fue embiado software_updates: @@ -311,11 +309,13 @@ lad: changelog: Ke troko? text: Terminos de servisyo terms_of_service_generator: + choice_of_law: Legislasyon aplikavle domain: Domeno + min_age: Edad minima user: - date_of_birth_1i: Diya + date_of_birth_1i: Anyo date_of_birth_2i: Mez - date_of_birth_3i: Anyo + date_of_birth_3i: Diya role: Rolo time_zone: Zona de tiempo user_role: @@ -324,6 +324,8 @@ lad: name: Nombre permissions_as_keys: Permisos position: Priorita + username_block: + allow_with_approval: Permite enrejistrasyones kon aprovasyon webhook: events: Evenimientos kapasitados template: Modelo de kontenido diff --git a/config/locales/simple_form.lt.yml b/config/locales/simple_form.lt.yml index 2b2f2df1365a9c..ade16bfa011f3b 100644 --- a/config/locales/simple_form.lt.yml +++ b/config/locales/simple_form.lt.yml @@ -8,7 +8,7 @@ lt: display_name: Tavo pilnas vardas arba smagus vardas. fields: Tavo pagrindinis puslapis, įvardžiai, amžius, bet kas, ko tik nori. indexable: Tavo vieši įrašai gali būti rodomi Mastodon paieškos rezultatuose. Žmonės, kurie bendravo su tavo įrašais, gali jų ieškoti nepriklausomai nuo to. - note: 'Gali @paminėti kitus žmones arba #saitažodžius.' + note: 'Gali @paminėti kitus žmones arba #grotažymes.' show_collections: Žmonės galės peržiūrėti tavo sekimus ir sekėjus. Žmonės, kuriuos seki, matys, kad juos seki, nepaisant to. unlocked: Žmonės galės jus sekti nepaprašę patvirtinimo. Panaikinkite žymėjimą, jei norite peržiūrėti sekimo prašymus ir pasirinkti, ar priimti, ar atmesti naujus sekėjus. account_alias: @@ -41,7 +41,7 @@ lt: defaults: autofollow: Žmonės, kurie užsiregistruos per kvietimą, automatiškai seks tave avatar: WEBP, PNG, GIF arba JPG. Ne daugiau kaip %{size}. Bus sumažintas iki %{dimensions} tšk. - bot: Signalizuoti kitiems, kad paskyroje daugiausia atliekami automatiniai veiksmai ir kad ji gali būti nestebima. + bot: Pranešti visiems, kad paskyroje daugiausia atliekami automatiniai veiksmai ir kad jos neverta sekti context: Vienas arba keli kontekstai, kuriems turėtų būti taikomas filtras current_password: Saugumo sumetimais įvesk dabartinės paskyros slaptažodį current_username: Kad patvirtintum, įvesk dabartinės paskyros naudotojo vardą @@ -54,24 +54,30 @@ lt: password: Naudok bent 8 simbolius phrase: Bus suderinta, neatsižvelgiant į teksto lygį arba įrašo turinio įspėjimą scopes: Prie kurių API programai bus leidžiama pasiekti. Pasirinkus aukščiausio lygio sritį, atskirų sričių pasirinkti nereikia. - setting_aggregate_reblogs: Nerodyti naujų pakėlimų įrašams, kurie neseniai buvo pakelti (taikoma tik naujai gautiems pakėlimams). + setting_advanced_layout: Rodykite „Mastodon“ kaip kelių stulpelių išdėstymą, leidžiantį peržiūrėti įrašų srautą, pranešimus ir trečiąjį stulpelį pagal savo pasirinkimą. Nerekomenduojama mažesniems ekranams. + setting_aggregate_reblogs: Neberodyti naujų pasidalinimų įrašais, kuriais neseniai buvo pasidalinta (taikoma tik naujai daromiems pasidalinimams) setting_always_send_emails: Paprastai el. laiško pranešimai nebus siunčiami, kai aktyviai naudoji Mastodon. + setting_boost_modal: Kai ši funkcija įjungta, pasidalijant įrašu pirmiausia atsivers patvirtinimo langas, kuriame galėsite pakeisti savo pasidalijimo matomumo nustatymus. + setting_default_quote_policy_private: Tik sekėjams skirti įrašai, paskelbti platformoje „Mastodon“, negali būti cituojami kitų. + setting_default_quote_policy_unlisted: Kai žmonės jus cituos, jų įrašai taip pat bus paslėpti iš populiariausių naujienų srauto. setting_default_sensitive: Jautrioji medija pagal numatytuosius nustatymus yra paslėpta ir gali būti atskleista spustelėjus. setting_display_media_default: Slėpti mediją, pažymėtą kaip jautrią setting_display_media_hide_all: Visada slėpti mediją setting_display_media_show_all: Visada rodyti mediją setting_emoji_style: Kaip rodyti emodžius. „Auto“ bandys naudoti vietinius jaustukus, bet senesnėse naršyklėse grįš prie Tvejaustukų. + setting_quick_boosting_html: Kai ši funkcija įjungta, paspaudus ant %{boost_icon} Paryškinimo piktogramos, įrašas bus iškart paryškintas, o ne atidarytas išskleidžiamasis meniu „paryškinti/cituoti“. Citavimo veiksmas perkeliamas į %{options_icon} meniu. setting_system_scrollbars_ui: Taikoma tik darbalaukio naršyklėms, karkasiniais „Safari“ ir „Chrome“. setting_use_blurhash: Gradientai pagrįsti paslėptų vizualizacijų spalvomis, bet užgožia bet kokias detales. setting_use_pending_items: Slėpti laiko skalės naujienas po paspaudimo, vietoj automatinio srauto slinkimo. username: Gali naudoti raides, skaičius ir pabraukimus whole_word: Kai raktažodis ar frazė yra tik raidinis ir skaitmeninis, jis bus taikomas tik tada, jei atitiks visą žodį + domain_allow: + domain: Nurodytas domenas galės gauti duomenis iš šio serverio ir iš jo gaunami duomenys bus apdorojami ir saugomi email_domain_block: with_dns_records: Bus bandoma išspręsti nurodyto domeno DNS įrašus, o rezultatai taip pat bus blokuojami featured_tag: - name: 'Štai keletas pastaruoju metu dažniausiai saitažodžių, kurių tu naudojai:' + name: 'Štai keletas pastaruoju metu dažniausiai tavo naudotų grotažymių:' filters: - action: Pasirink, kokį veiksmą atlikti, kai įrašas atitinka filtrą actions: blur: Slėpti mediją po įspėjimu, neslepiant paties teksto hide: Visiškai paslėpti filtruotą turinį ir elgtis taip, tarsi jo neegzistuotų @@ -80,6 +86,7 @@ lt: activity_api_enabled: Vietinių paskelbtų įrašų, aktyvių naudotojų ir naujų registracijų skaičiai kas savaitę app_icon: WEBP, PNG, GIF arba JPG. Pakeičia numatytąją programos piktogramą mobiliuosiuose įrenginiuose pasirinktine piktograma. backups_retention_period: Naudotojai gali generuoti savo įrašų archyvus, kuriuos vėliau galės atsisiųsti. Nustačius teigiamą reikšmę, šie archyvai po nurodyto dienų skaičiaus bus automatiškai ištrinti iš saugyklos. + bootstrap_timeline_accounts: Šios paskyros bus rekomenduojamos naujiems naudotojams Rekomenduojami sekimai skyrelyje. Pateikite rekomenduojamas paskyras, atskirtas kableliais. content_cache_retention_period: Visi įrašai iš kitų serverių (įskaitant pakėlimus ir atsakymus) bus ištrinti po nurodyto dienų skaičiaus, neatsižvelgiant į bet kokią vietinio naudotojo sąveiką su tais įrašais. Tai taikoma ir tiems įrašams, kuriuos vietinis naudotojas yra pažymėjęs kaip žymes ar mėgstamus. Privačios paminėjimai tarp naudotojų iš skirtingų instancijų taip pat bus prarastos ir jų bus neįmanoma atkurti. Šis nustatymas skirtas naudoti ypatingos paskirties instancijose, o įgyvendinus jį bendram naudojimui, pažeidžiami daugelio naudotojų lūkesčiai. favicon: WEBP, PNG, GIF arba JPG. Pakeičia numatytąją Mastodon svetaines piktogramą pasirinktine piktograma. mascot: Pakeičia išplėstinės žiniatinklio sąsajos iliustraciją. @@ -90,10 +97,9 @@ lt: site_contact_email: Kaip žmonės gali su tavimi susisiekti teisiniais ar pagalbos užklausimais. site_contact_username: Kaip žmonės gali tave pasiekti Mastodon. site_extended_description: Bet kokia papildoma informacija, kuri gali būti naudinga lankytojams ir naudotojams. Gali būti struktūrizuota naudojant Markdown sintaksę. + theme: Tema, kurią mato atsijungę lankytojai ir nauji vartotojai. thumbnail: Maždaug 2:1 dydžio vaizdas, rodomas šalia tavo serverio informacijos. - timeline_preview: Atsijungę lankytojai galės naršyti naujausius viešus įrašus, esančius serveryje. - trends: Trendai rodo, kurios įrašai, saitažodžiai ir naujienų istorijos tavo serveryje sulaukia didžiausio susidomėjimo. - trends_as_landing_page: Rodyti tendencingą turinį atsijungusiems naudotojams ir lankytojams vietoj šio serverio aprašymo. Reikia, kad tendencijos būtų įjungtos. + trends: Tendencijos rodo, kurie įrašai, grotažymės ir naujienų istorijos tavo serveryje sulaukia didžiausio susidomėjimo. imports: data: CSV failas, eksportuotas iš kito „Mastodon“ serverio. invite_request: @@ -138,6 +144,11 @@ lt: title: Pavadinimas admin_account_action: include_statuses: Įtraukti praneštus įrašus į el. laišką + type: Veiksmas + types: + disable: Sustabdyti + none: Siųsti įspėjimą + sensitive: Jautrus(-i) defaults: autofollow: Kviesti sekti tavo paskyrą avatar: Profilio nuotrauka @@ -147,6 +158,7 @@ lt: email: El. pašto adresas expires_in: Nustoja galioti po fields: Papildomi laukai + header: Antraštės paveikslėlis irreversible: Mesti vietoj slėpti locale: Sąsajos kalba max_uses: Maksimalus naudojimo skaičius @@ -158,10 +170,10 @@ lt: setting_aggregate_reblogs: Grupuoti pakėlimus laiko skalėse setting_always_send_emails: Visada siųsti el. laiško pranešimus setting_auto_play_gif: Automatiškai leisti animuotų GIF - setting_boost_modal: Rodyti patvirtinimo dialogą prieš pakėliant įrašą + setting_boost_modal: Kontroliuoti pasidalijimų matomumo nustatymus setting_default_language: Skelbimo kalba + setting_default_quote_policy: Kas gali cituoti setting_default_sensitive: Visada žymėti mediją kaip jautrią - setting_delete_modal: Rodyti patvirtinimo dialogą prieš ištrinant įrašą setting_disable_hover_cards: Išjungti profilio peržiūrą užvedus setting_disable_swiping: Išjungti perbraukimo judėjimus setting_display_media: Medijos rodymas @@ -170,7 +182,7 @@ lt: setting_emoji_style: Jaustuko stilius setting_expand_spoilers: Visada išplėsti įrašus, pažymėtus turinio įspėjimais setting_hide_network: Slėpti savo socialinę diagramą - setting_missing_alt_text_modal: Rodyti patvirtinimo dialogo langą prieš skelbiant mediją be alternatyvaus teksto. + setting_quick_boosting: Įjunkite greitą pasidalinimą setting_reduce_motion: Sumažinti judėjimą animacijose setting_system_font_ui: Naudoti numatytąjį sistemos šriftą setting_system_scrollbars_ui: Naudoti numatytąją sistemos slankjuostę @@ -186,22 +198,24 @@ lt: email_domain_block: with_dns_records: Įtraukti MX įrašus ir domeno IP adresus featured_tag: - name: Saitažodis + name: Grotažymė filters: actions: blur: Slėpti mediją su įspėjimu hide: Slėpti visiškai warn: Slėpti su įspėjimu form_admin_settings: - activity_api_enabled: Skelbti suvestinį statistiką apie naudotojų veiklą per API + activity_api_enabled: Skelbti suvestinę statistiką apie naudotojų veiklą per API app_icon: Programėlės piktograma bootstrap_timeline_accounts: Visada rekomenduoti šias paskyras naujiems naudotojams content_cache_retention_period: Nuotolinio turinio saugojimo laikotarpis custom_css: Pasirinktinis CSS favicon: Svetainės piktograma + local_topic_feed_access: Prieiga prie grotažymių ir nuorodų sienų srautų, kuriuose pateikiami vietiniai įrašai mascot: Pasirinktinis talismanas (pasenęs) min_age: Mažiausias amžiaus reikalavimas registrations_mode: Kas gali užsiregistruoti + remote_topic_feed_access: Prieiga prie grotažymių ir nuorodų srautų, kuriuose pateikiami išoriniai įrašai require_invite_text: Reikalauti priežasties prisijungti show_domain_blocks_rationale: Rodyti, kodėl domenai buvo užblokuoti site_extended_description: Išplėstas aprašymas @@ -212,7 +226,6 @@ lt: thumbnail: Serverio miniatūra trendable_by_default: Leisti tendencijas be išankstinės peržiūros trends: Įjungti tendencijas - trends_as_landing_page: Naudoti tendencijas kaip nukreipimo puslapį invite: comment: Komentuoti invite_request: @@ -223,7 +236,8 @@ lt: follow_request: Kažkas paprašė sekti tave mention: Kažkas paminėjo tave pending_account: Reikia peržiūros naujam paskyrui - reblog: Kažkas pakėlė tavo įrašą + quote: Kažkas jus paminėjo + reblog: Kažkas dalinosi tavo įrašu software_updates: label: Yra nauja Mastodon versija patch: Pranešti apie klaidų ištaisymo atnaujinimus @@ -235,10 +249,10 @@ lt: indexable: Įtraukti profilio puslapį į paieškos variklius show_application: Rodyti, iš kurios programėles išsiuntei įrašą tag: - listable: Leisti šį saitažodį rodyti paieškose ir pasiūlymuose - name: Saitažodis - trendable: Leisti šį saitažodį rodyti pagal trendus - usable: Leisti įrašams naudoti šį saitažodį vietoje + listable: Leisti šį grotažodį rodyti paieškose ir pasiūlymuose + name: Grotažymė + trendable: Leisti šią grotažymę rodyti tendencijose + usable: Leisti įrašams naudoti šią grotažymę lokaliai terms_of_service: changelog: Kas pasikeitė? text: Paslaugų sąlygos @@ -253,9 +267,7 @@ lt: jurisdiction: Teisinis teismingumas min_age: Mažiausias amžius user: - date_of_birth_1i: Diena date_of_birth_2i: Mėnuo - date_of_birth_3i: Metai role: Vaidmuo time_zone: Laiko juosta user_role: diff --git a/config/locales/simple_form.lv.yml b/config/locales/simple_form.lv.yml index 6e7e57ffabe2e1..8475de9f5b8a20 100644 --- a/config/locales/simple_form.lv.yml +++ b/config/locales/simple_form.lv.yml @@ -54,12 +54,12 @@ lv: password: Izmanto vismaz 8 rakstzīmes phrase: Tiks saskaņots neatkarīgi no ziņas teksta reģistra vai satura brīdinājuma scopes: Kuriem API lietotnei būs ļauts piekļūt. Ja atlasa augstākā līmeņa tvērumu, nav nepieciešamas atlasīt atsevišķus. - setting_aggregate_reblogs: Nerādīt jaunus izcēlumus ziņām, kas nesen tika palielinātas (ietekmē tikai nesen saņemtos palielinājumus) + setting_aggregate_reblogs: Nerādīt jaunus pastiprinājumus ierakstiem, kas nesen tikuši pastiprināti (ietekmēs tikai turpmāk saņemtos pastiprinājumus) setting_always_send_emails: Parasti e-pasta paziņojumi netiek sūtīti, kad aktīvi izmantojat Mastodon setting_default_sensitive: Pēc noklusējuma jūtīgi informācijas nesēji ir paslēpti, un tos var atklāt ar klikšķi - setting_display_media_default: Paslēpt informācijas nesējus, kas atzīmēti kā jūtīgi - setting_display_media_hide_all: Vienmēr slēpt multividi - setting_display_media_show_all: Vienmēr rādīt multividi + setting_display_media_default: Paslēpt attēlus un video, kas atzīmēti kā jūtīgi + setting_display_media_hide_all: Vienmēr slēpt attēlus un video + setting_display_media_show_all: Vienmēr rādīt attēlus un video setting_system_scrollbars_ui: Attiecas tikai uz darbvirsmas pārlūkiem, kuru pamatā ir Safari vai Chrome setting_use_blurhash: Pāreju pamatā ir paslēpto uzskatāmo līdzekļu krāsas, bet saturs tiek padarīts neskaidrs setting_use_pending_items: Paslēpt laika skalas atjauninājumus aiz klikšķa, nevis ar automātisku plūsmas ritināšanu @@ -73,7 +73,6 @@ lv: featured_tag: name: 'Šeit ir daži no pēdējiem lietotajiem tēmturiem:' filters: - action: Izvēlies, kuru darbību veikt, ja ziņa atbilst filtram actions: blur: Paslēpt informācijas nesējus aiz brīdinājuma, nepaslēpjot tekstu hide: Paslēp filtrēto saturu pilnībā, izturoties tā, it kā tas neeksistētu @@ -82,7 +81,6 @@ lv: activity_api_enabled: Vietēji publicēto ziņu, aktīvo lietotāju un jauno reģistrāciju skaits nedēļas kopās app_icon: WEBP, PNG, GIF vai JPG. Mobilajās ierīcēs aizstāj noklusējuma lietotnes ikonu ar pielāgotu. backups_retention_period: Lietotājiem ir iespēja izveidot savu ierakstu arhīvu lejupielādēšanai vēlāk. Kad iestatīta pozitīva vērtība, šie arhīvi tiks automātiski izdzēsti no krātuves pēc norādītā dienu skaita. - bootstrap_timeline_accounts: Šie konti tiks piesprausti jauno lietotāju sekošanas ieteikumu augšdaļā. closed_registrations_message: Tiek rādīts, kad reģistrēšanās ir slēgta content_cache_retention_period: Visi ieraksti no citiem serveriem (tajā skaitā pastiprinājumi un atbildes) tiks izdzēsti pēc norādītā dienu skaita, neņemot vērā vietēja lietotāja mijiedarbību ar šādiem ierakstiem. Tas ietver ierakstus, kurus vietējs lietotājs ir atzīmējis kā grāmatzīmi vai pievienojis izlasē. Tiks zaudēti arī privāti pieminējumi starp lietotājiem no dažādiem serveriem, un tos nebūs iespējams atgūt. Šī iestatījuma izmantošana ir paredzēta īpašam nolūkam paredzētiem serveriem un neatbilst tam, ko sagaida vairums lietotāju, kad pielietots vispārējas izmantošanas serveros. custom_css: Vari lietot pielāgotus stilus Mastodon tīmekļa versijā. @@ -102,10 +100,8 @@ lv: status_page_url: Tās lapas URL, kurā lietotāji var redzēt šī servera statusu pārtraukuma laikā theme: Tēma, kuru redz apmeklētāji, kuri ir atteikušies, un jaunie lietotāji. thumbnail: Aptuveni 2:1 attēls, kas tiek parādīts kopā ar tava servera informāciju. - timeline_preview: Atteikušies apmeklētāji varēs pārlūkot jaunākos serverī pieejamos publiskos ierakstus. trendable_by_default: Izlaist aktuālā satura manuālu pārskatīšanu. Atsevišķas preces joprojām var noņemt no tendencēm pēc fakta. trends: Tendences parāda, kuras ziņas, atsauces un ziņu stāsti gūst panākumus tavā serverī. - trends_as_landing_page: Šī servera apraksta vietā rādīt aktuālo saturu lietotājiem un apmeklētājiem, kuri ir atteikušies. Nepieciešams iespējot tendences. form_challenge: current_password: Tu ieej drošā zonā imports: @@ -216,14 +212,12 @@ lv: password: Parole phrase: Atslēgvārds vai frāze setting_advanced_layout: Iespējot paplašināto tīmekļa saskarni - setting_aggregate_reblogs: Grupēt izcēlumus ierakstu lentās + setting_aggregate_reblogs: Grupēt pastiprinājumus ierakstu lentās setting_always_send_emails: Vienmēr sūtīt e-pasta paziņojumus setting_auto_play_gif: Automātiski atskaņot animētos GIF - setting_boost_modal: Rādīt apstiprinājuma dialogu pirms izcelšanas setting_default_language: Publicēšanas valoda setting_default_quote_policy: Kas var citēt setting_default_sensitive: Vienmēr atzīmēt informācijas nesējus kā jūtīgus - setting_delete_modal: Parādīt apstiprinājuma dialogu pirms ziņas dzēšanas setting_disable_hover_cards: Atspējot profila priekšskatījumu pēc kursora novietošanas setting_disable_swiping: Atspējot vilkšanas kustības setting_display_media: Multivides rādīšana @@ -232,13 +226,12 @@ lv: setting_display_media_show_all: Parādīt visu setting_expand_spoilers: Vienmēr izvērst ziņas, kas apzīmētas ar brīdinājumiem par saturu setting_hide_network: Slēpt savu sociālo grafu - setting_missing_alt_text_modal: Rādīt apstiprināšanas lodziņu pirms informācijas nesēju bez aprakstošā teksta iesūtīšanas setting_reduce_motion: Ierobežot kustību animācijās setting_system_font_ui: Lietot sistēmas noklusējuma fontu setting_system_scrollbars_ui: Lietot sistēmas noklusējuma ritjoslu setting_theme: Vietnes motīvs setting_trends: Parādīt šodienas tendences - setting_unfollow_modal: Parādīt apstiprinājuma dialogu pirms pārtraukt kādam sekot + setting_unfollow_modal: Rādīt apstiprinājuma dialogu pirms pārtraukt kādam sekot setting_use_blurhash: Rādīt krāsainas pārejas paslēptajiem informācijas nesējiem setting_use_pending_items: Lēnais režīms severity: Smagums @@ -254,7 +247,7 @@ lv: name: Tēmturis filters: actions: - blur: Paslēpt informācijas nesējus ar brīdinājumu + blur: Paslēpt attēlus un video ar brīdinājumu hide: Paslēpt pilnībā warn: Paslēpt ar brīdinājumu form_admin_settings: @@ -284,10 +277,8 @@ lv: status_page_url: Statusa lapas URL theme: Noklusētā tēma thumbnail: Servera sīkbilde - timeline_preview: Atļaut neautentificētu piekļuvi publiskajām ziņu lentām trendable_by_default: Atļaut tendences bez iepriekšējas pārskatīšanas trends: Iespējot tendences - trends_as_landing_page: Izmantojiet tendences kā galveno lapu interactions: must_be_follower: Bloķēt paziņojumus no ne-sekotājiem must_be_following: Bloķēt paziņojumus no cilvēkiem, kuriem tu neseko @@ -312,6 +303,7 @@ lv: follow_request: Kāds vēlas Tev sekot mention: Kāds pieminēja tevi pending_account: Jāpārskata jaunu kontu + quote: Kāds tevi citēja reblog: Kāds izcēla tavu ierakstu report: Tika iesniegts jauns ziņojums software_updates: @@ -343,9 +335,7 @@ lv: domain: Domēna vārds min_age: Mazākais pieļaujamais vecums user: - date_of_birth_1i: Diena date_of_birth_2i: Mēnesis - date_of_birth_3i: Gads role: Loma time_zone: Laika josla user_role: diff --git a/config/locales/simple_form.ms.yml b/config/locales/simple_form.ms.yml index 6c45240781421c..c35989fb815649 100644 --- a/config/locales/simple_form.ms.yml +++ b/config/locales/simple_form.ms.yml @@ -68,13 +68,11 @@ ms: featured_tag: name: 'Berikut ialah beberapa hashtag yang paling anda gunakan baru-baru ini:' filters: - action: Pilih tindakan yang hendak dilakukan apabila siaran sepadan dengan penapis actions: hide: Sembunyikan sepenuhnya kandungan yang ditapis, berkelakuan seolah-olah ia tidak wujud warn: Sembunyikan kandungan yang ditapis di sebalik amaran yang menyebut tajuk penapis form_admin_settings: activity_api_enabled: Kiraan siaran tempatan yang diterbitkan, pengguna aktif dan pendaftaran baharu dalam baldi mingguan - bootstrap_timeline_accounts: Akaun ini akan disematkan pada bahagian atas cadangan ikutan pengguna baharu. closed_registrations_message: Dipaparkan semasa pendaftaran ditutup custom_css: Anda boleh menggunakan gaya tersuai pada versi web Mastodon. favicon: WEBP, PNG, GIF, atau JPG. Arca tersuai diutamakan dari arca kegemaran Mastodon lalai. @@ -91,10 +89,8 @@ ms: status_page_url: URL halaman yang membolehkan orang ramai melihat status server ini semasa gangguan theme: Tema yang pelawat log keluar dan pengguna baharu lihat. thumbnail: Imej kira-kira 2:1 dipaparkan bersama maklumat server anda. - timeline_preview: Pelawat yang log keluar akan dapat menyemak imbas siaran awam terkini yang tersedia pada server. trendable_by_default: Langkau semakan manual kandungan sohor kini. Item individu masih boleh dialih keluar daripada trend selepas fakta itu. trends: Aliran menunjukkan pos, hashtag dan cerita berita yang mendapat tarikan pada server anda. - trends_as_landing_page: Tunjukkan kandungan trend kepada pengguna dan pelawat yang log keluar dan bukannya penerangan tentang server ini. Memerlukan trend untuk didayakan. form_challenge: current_password: Anda sedang memasuki kawasan selamat imports: @@ -199,10 +195,8 @@ ms: setting_aggregate_reblogs: Rangsangan kumpulan dalam garis masa setting_always_send_emails: Sentiasa hantar pemberitahuan e-mel setting_auto_play_gif: Automain GIF animasi - setting_boost_modal: Tunjukkan dialog pengesahan sebelum meningkatkan setting_default_language: Bahasa hantaran setting_default_sensitive: Selalu tandakan media sebagai sensitif - setting_delete_modal: Tunjukkan dialog pengesahan sebelum memadamkan pos setting_disable_swiping: Lumpuhkan gerakan meleret setting_display_media: Paparan media setting_display_media_default: Lalai @@ -256,10 +250,8 @@ ms: status_page_url: URL halaman status theme: Tema asal thumbnail: Server thumbnail - timeline_preview: Benarkan akses tidak disahkan kepada garis masa awam trendable_by_default: Benarkan aliran tanpa semakan terlebih dahulu trends: Dayakan trend - trends_as_landing_page: Gunakan trend sebagai halaman pendaratan interactions: must_be_follower: Sekat pemberitahuan daripada bukan pengikut must_be_following: Sekat pemberitahuan daripada orang yang anda tidak ikuti diff --git a/config/locales/simple_form.my.yml b/config/locales/simple_form.my.yml index 283acbd76d65d9..0dd04a92183e27 100644 --- a/config/locales/simple_form.my.yml +++ b/config/locales/simple_form.my.yml @@ -68,13 +68,11 @@ my: featured_tag: name: ဤသည်မှာ သင်မကြာသေးမီက အသုံးပြုခဲ့သော hashtag အချို့ဖြစ်သည် - filters: - action: ပို့စ်တစ်ခုသည် စစ်ထုတ်မှုနှင့် ကိုက်ညီချိန်တွင် မည်သည့်လုပ်ဆောင်ချက် ဆောင်ရွက်မည်ကို ရွေးချယ်ပါ actions: hide: စစ်ထုတ်ထားသော အကြောင်းအရာကို လုံးဝဖျောက်ထားပြီး မရှိသကဲ့သို့ ပြပါ warn: စစ်ထုတ်မှုခေါင်းစဉ်ကိုဖော်ပြသည့်သတိပေးချက်နောက်တွင် စစ်ထုတ်ထားသောအကြောင်းအရာကို ဖျောက်ထားပါ form_admin_settings: activity_api_enabled: အပတ်စဉ် စာရင်းများတွင် ဒေသတွင်းတင်ထားသောပို့စ်များ၊ လက်ရှိအသုံးပြုသူများနှင့် စာရင်းသွင်းမှုအသစ်များ - bootstrap_timeline_accounts: ဤအကောင့်များကို အသုံးပြုသူအသစ်များ၏ စောင့်ကြည့်မှု အကြံပြုချက်များ၏ထိပ်ဆုံးတွင် ပင်ချိတ်ထားပါမည်။ closed_registrations_message: အကောင့်ဖွင့်ခြင်းများကို ပိတ်ထားသည့်အခါတွင် ပြသထားသည် custom_css: Mastodon ဝဘ်ဗားရှင်းတွင် စိတ်ကြိုက်စတိုင်များကို အသုံးပြုနိုင်ပါသည်။ mascot: အဆင့်မြင့် ဝဘ်ပုံစံတွင်တွင် ရုပ်ပုံဖြင့်ဖော်ပြထားသည်။ @@ -90,10 +88,8 @@ my: status_page_url: ပြတ်တောက်နေစဉ်အတွင်း လူများက ဤဆာဗာအခြေအနေကို မြင်နိုင်မည့် စာမျက်နှာ URL theme: အကောင့်မှထွက်ပြီး အသုံးပြုသူအသစ်များနှင့် ဝင်ကြည့်မည့်သူများအတွက် မြင်ရမည့်ပုံစံ။ thumbnail: သင့်ဆာဗာအချက်အလက်နှင့်အတူ အကြမ်းဖျင်းအားဖြင့် ၂:၁ ဖြင့် ပြသထားသောပုံတစ်ပုံ။ - timeline_preview: အကောင့်မှထွက်ထားသူများသည် ဆာဗာပေါ်ရှိ လတ်တလော အများမြင်ပို့စ်များကို ရှာဖွေကြည့်ရှုနိုင်မည်ဖြစ်သည်။ trendable_by_default: ခေတ်စားနေသော အကြောင်းအရာများ၏ ကိုယ်တိုင်သုံးသပ်ချက်ကို ကျော်ပါ။ နောက်ပိုင်းတွင် အချက်အလက်တစ်ခုချင်းစီကို ခေတ်စားနေသောအကြောင်းအရာများကဏ္ဍမှ ဖယ်ရှားနိုင်ပါသေးသည်။ trends: လက်ရှိခေတ်စားနေသာပို့စ်များ၊ hashtag များနှင့် သတင်းဇာတ်လမ်းများကို သင့်ဆာဗာပေါ်တွင် တွေ့မြင်နိုင်ပါမည်။ - trends_as_landing_page: ဤဆာဗာဖော်ပြချက်အစား အကောင့်မှ ထွက်ထားသူများနှင့် ဝင်ရောက်ကြည့်ရှုသူများအတွက် ခေတ်စားနေသော အကြောင်းအရာများကို ပြသပါ။ ခေတ်စားနေသောပို့စ်များကို ဖွင့်ထားရန် လိုအပ်သည်။ form_challenge: current_password: သင်သည် လုံခြုံသောနေရာသို့ ဝင်ရောက်နေပါသည် imports: @@ -196,10 +192,8 @@ my: setting_aggregate_reblogs: စာမျက်နှာများရှိ အဖွဲ့လိုက် Boost များ setting_always_send_emails: အီးမေးလ်သတိပေးချက်များကို အမြဲပို့ပါ setting_auto_play_gif: ကာတွန်း GIF များကို အလိုအလျောက်ဖွင့်ပါ - setting_boost_modal: Boost မလုပ်မီ အတည်ပြုချက်ပြပါ setting_default_language: ပို့စ်တင်မည့်ဘာသာစကား setting_default_sensitive: သတိထားရသောမီဒီယာအဖြစ် အမြဲအမှတ်အသားပြုပါ - setting_delete_modal: ပို့စ်တစ်ခုမဖျက်မီ အတည်ပြုချက်ပြပါ။ setting_disable_swiping: ပွတ်ဆွဲခြင်းများကို ပိတ်ပါ setting_display_media: မီဒီယာဖော်ပြမှု setting_display_media_default: မူလသတ်မှတ်ချက် @@ -252,10 +246,8 @@ my: status_page_url: အခြေအနေပြစာမျက်နှာ URL theme: မူလသတ်မှတ်ထားသည့် အပြင်အဆင် thumbnail: ဆာဗာ ပုံသေး - timeline_preview: အများမြင်စာမျက်နှာများသို့ အထောက်အထားမရှိဘဲ ဝင်ရောက်ခွင့်ပြုပါ trendable_by_default: ကြိုမသုံးသပ်ဘဲ ခေတ်စားနေသောအကြောင်းအရာများကို ခွင့်ပြုပါ trends: လက်ရှိခေတ်စားနေမှုများကိုပြပါ - trends_as_landing_page: ခေတ်စားနေသောပို့စ်များကို landing စာမျက်နှာအဖြစ် အသုံးပြုပါ interactions: must_be_follower: စောင့်ကြည့်မနေသူများထံမှ အသိပေးချက်များကို ပိတ်ပါ must_be_following: သင် စောင့်ကြည့်မထားသူများထံမှ အသိပေးချက်များကို ပိတ်ပါ diff --git a/config/locales/simple_form.nan.yml b/config/locales/simple_form.nan.yml index d9049a784b485e..08ae2a55df5bca 100644 --- a/config/locales/simple_form.nan.yml +++ b/config/locales/simple_form.nan.yml @@ -9,3 +9,18 @@ nan: password: 用 8 ê字元以上 setting_display_media_hide_all: 一直khàm掉媒體 setting_display_media_show_all: 一直展示媒體 + labels: + terms_of_service: + changelog: Siánn物有改? + effective_date: 有效ê日期 + text: 服務規定 + terms_of_service_generator: + admin_email: 法律通知用ê電子phue地址 + arbitration_address: 仲裁通知ê實濟地址 + arbitration_website: 送仲裁通知ê網站 + choice_of_law: 法律ê選擇 + dmca_address: DMCA/版權通知ê實際地址 + dmca_email: DMCA/版權通知ê電子phue地址 + domain: 域名 + jurisdiction: 司法管轄區 + min_age: 最少年紀 diff --git a/config/locales/simple_form.nl.yml b/config/locales/simple_form.nl.yml index 16fb62a11ea29a..902e4d0b23fbdd 100644 --- a/config/locales/simple_form.nl.yml +++ b/config/locales/simple_form.nl.yml @@ -54,8 +54,10 @@ nl: password: Gebruik tenminste 8 tekens phrase: Komt overeen ongeacht hoofd-/kleine letters of een inhoudswaarschuwing scopes: Tot welke API's heeft de toepassing toegang. Wanneer je een toestemming van het bovenste niveau kiest, hoef je geen individuele toestemmingen meer te kiezen. + setting_advanced_layout: Geef Mastodon in meerdere kolommen weer, waarmee je jouw tijdlijn, meldingen en een derde kolom naar keuze in een opslag kunt bekijken. Niet aanbevolen voor kleinere schermen. setting_aggregate_reblogs: Geen nieuwe boosts tonen voor berichten die recentelijk nog zijn geboost (heeft alleen effect op nieuw ontvangen boosts) setting_always_send_emails: Normaliter worden er geen e-mailmeldingen verstuurd wanneer je actief Mastodon gebruikt + setting_boost_modal: Wanneer dit is ingeschakeld, krijg je eerst een bevestigingsvenster te zien waarmee je de zichtbaarheid van je boost kunt wijzigen. setting_default_quote_policy_private: Berichten aan alleen volgers afkomstig van Mastodon kunnen niet door anderen worden geciteerd. setting_default_quote_policy_unlisted: Wanneer mensen jou citeren, verschijnt hun bericht ook niet onder trends. setting_default_sensitive: Gevoelige media wordt standaard verborgen en kan met één klik worden getoond @@ -63,6 +65,7 @@ nl: setting_display_media_hide_all: Media altijd verbergen setting_display_media_show_all: Media altijd tonen setting_emoji_style: Waarmee moeten emojis worden weergegeven. ‘Auto’ probeert de systeemeigen emojis te gebruiken, maar valt terug op Twemoji voor oudere webbrowsers. + setting_quick_boosting_html: Wanneer dit is ingeschakeld, boost je in één keer wanneer je op het %{boost_icon} boostpictogram klikt en verplaatst de citeeroptie zich naar het %{options_icon} optiemenu. Wanneer dit is uitgeschakeld krijg je gelijk de mogelijkheid om te boosten of te citeren. setting_system_scrollbars_ui: Alleen van toepassing op desktopbrowsers gebaseerd op Safari en Chrome setting_use_blurhash: Wazige kleurovergangen zijn gebaseerd op de kleuren van de verborgen media, waarmee elk detail verdwijnt setting_use_pending_items: De tijdlijn wordt bijgewerkt door op het aantal nieuwe items te klikken, in plaats van dat deze automatisch wordt bijgewerkt @@ -76,7 +79,7 @@ nl: featured_tag: name: 'Hier zijn enkele van de hashtags die je onlangs hebt gebruikt:' filters: - action: Kies welke acties uitgevoerd moeten wanneer een bericht overeenkomt met het filter + action: Kies welke acties uitgevoerd moeten wanneer een bericht met het filter overeenkomt actions: blur: Media verbergen achter een waarschuwing, zonder de tekst zelf te verbergen hide: Verberg de gefilterde inhoud volledig, alsof het niet bestaat @@ -90,6 +93,7 @@ nl: content_cache_retention_period: Alle berichten van andere servers (inclusief boosts en reacties) worden verwijderd na het opgegeven aantal dagen, ongeacht enige lokale gebruikersinteractie met die berichten. Dit betreft ook berichten die een lokale gebruiker aan diens bladwijzers heeft toegevoegd of als favoriet heeft gemarkeerd. Privéberichten tussen gebruikers van verschillende servers gaan ook verloren en zijn onmogelijk te herstellen. Het gebruik van deze instelling is bedoeld voor servers die een speciaal doel dienen en overtreedt veel gebruikersverwachtingen wanneer deze voor algemeen gebruik wordt geïmplementeerd. custom_css: Je kunt aangepaste CSS toepassen op de webversie van deze Mastodon-server. favicon: WEBP, PNG, GIF of JPG. Vervangt de standaard Mastodon favicon met een aangepast pictogram. + landing_page: Selecteert welke pagina nieuwe bezoekers te zien krijgen wanneer ze voor het eerst op jouw server terechtkomen. Wanneer je ‘Trends’ selecteert, moeten trends ingeschakeld zijn onder 'Serverinstellingen > Ontdekken'. Als je ‘Lokale tijdlijn’ selecteert, moet ‘Toegang tot openbare lokale berichten’ worden ingesteld op ‘Iedereen’ onder 'Serverinstellingen > Ontdekken'. mascot: Overschrijft de illustratie in de geavanceerde webomgeving. media_cache_retention_period: Mediabestanden van berichten van externe gebruikers worden op jouw server in de cache opgeslagen. Indien ingesteld op een positieve waarde, worden media verwijderd na het opgegeven aantal dagen. Als de mediagegevens worden opgevraagd nadat ze zijn verwijderd, worden ze opnieuw gedownload wanneer de originele inhoud nog steeds beschikbaar is. Vanwege beperkingen op hoe vaak linkvoorbeelden sites van derden raadplegen, wordt aanbevolen om deze waarde in te stellen op ten minste 14 dagen. Anders worden linkvoorbeelden niet op aanvraag bijgewerkt. min_age: Gebruikers krijgen tijdens hun inschrijving de vraag om hun geboortedatum te bevestigen @@ -105,10 +109,8 @@ nl: status_page_url: URL van een pagina waar mensen de status van deze server kunnen zien tijdens een storing theme: Thema die (niet ingelogde) bezoekers en nieuwe gebruikers zien. thumbnail: Een afbeelding van ongeveer een verhouding van 2:1 die naast jouw serverinformatie wordt getoond. - timeline_preview: Bezoekers (die niet zijn ingelogd) kunnen de meest recente, op de server aanwezige openbare berichten bekijken. trendable_by_default: Handmatige beoordeling van trends overslaan. Individuele items kunnen later alsnog worden afgekeurd. trends: Trends laten zien welke berichten, hashtags en nieuwsberichten op jouw server aan populariteit winnen. - trends_as_landing_page: Toon trending inhoud aan uitgelogde gebruikers en bezoekers in plaats van een beschrijving van deze server. Vereist dat trends zijn ingeschakeld. form_challenge: current_password: Je betreedt een veilige omgeving imports: @@ -235,12 +237,12 @@ nl: setting_aggregate_reblogs: Boosts in tijdlijnen groeperen setting_always_send_emails: Altijd e-mailmeldingen verzenden setting_auto_play_gif: Geanimeerde GIF's automatisch afspelen - setting_boost_modal: Vraag voor het boosten van een bericht een bevestiging + setting_boost_modal: Zichtbaarheid van boosts setting_default_language: Taal van berichten setting_default_privacy: Zichtbaarheid van nieuwe berichten setting_default_quote_policy: Wie mag jou citeren setting_default_sensitive: Media altijd als gevoelig markeren - setting_delete_modal: Vraag voor het verwijderen van een bericht een bevestiging + setting_delete_modal: Waarschuw mij wanneer ik een bericht probeer te verwijderen setting_disable_hover_cards: Hover-kaarten met profielvoorbeelden uitschakelen setting_disable_swiping: Swipebewegingen uitschakelen setting_display_media: Mediaweergave @@ -250,7 +252,8 @@ nl: setting_emoji_style: Emoji-stijl setting_expand_spoilers: Berichten met inhoudswaarschuwingen altijd uitklappen setting_hide_network: Jouw volgers en wie je volgt verbergen - setting_missing_alt_text_modal: Bevestigingsvenster tonen voor het plaatsen van media zonder alt-tekst + setting_missing_alt_text_modal: Waarschuw mij wanneer ik een bericht zonder alt-tekst probeer te plaatsen + setting_quick_boosting: Snel boosten inschakelen setting_reduce_motion: Beweging in animaties verminderen setting_system_font_ui: Standaardlettertype van het systeem gebruiken setting_system_scrollbars_ui: Standaard scrollbalk van het systeem gebruiken @@ -284,12 +287,17 @@ nl: content_cache_retention_period: Bewaartermijn voor externe inhoud custom_css: Aangepaste CSS favicon: Favicon + landing_page: Landingspagina voor nieuwe bezoekers + local_live_feed_access: Toegang tot openbare lokale berichten + local_topic_feed_access: Toegang tot overzicht met lokale hashtags en links mascot: Aangepaste mascotte (legacy) media_cache_retention_period: Bewaartermijn mediacache min_age: Vereiste minimumleeftijd peers_api_enabled: Lijst van bekende servers via de API publiceren profile_directory: Gebruikersgids inschakelen registrations_mode: Wie kan zich registreren + remote_live_feed_access: Toegang tot openbare berichten van andere servers + remote_topic_feed_access: Toegang tot overzicht met hashtags en links van andere servers require_invite_text: Opgeven van een reden is verplicht show_domain_blocks: Domeinblokkades tonen show_domain_blocks_rationale: Redenen voor domeinblokkades tonen @@ -302,10 +310,8 @@ nl: status_page_url: URL van statuspagina theme: Standaardthema thumbnail: Server-miniatuur - timeline_preview: Toegang tot de openbare tijdlijnen zonder in te loggen toestaan trendable_by_default: Trends goedkeuren zonder voorafgaande beoordeling trends: Trends inschakelen - trends_as_landing_page: Laat trends op de startpagina zien interactions: must_be_follower: Meldingen van mensen die jou niet volgen blokkeren must_be_following: Meldingen van mensen die jij niet volgt blokkeren @@ -366,9 +372,9 @@ nl: jurisdiction: Jurisdictie min_age: Minimumleeftijd user: - date_of_birth_1i: Dag + date_of_birth_1i: Jaar date_of_birth_2i: Maand - date_of_birth_3i: Jaar + date_of_birth_3i: Dag role: Rol time_zone: Tijdzone user_role: diff --git a/config/locales/simple_form.nn.yml b/config/locales/simple_form.nn.yml index 3e6982f14010e7..640e1b7772c816 100644 --- a/config/locales/simple_form.nn.yml +++ b/config/locales/simple_form.nn.yml @@ -54,8 +54,10 @@ nn: password: Nytt minst 8 teikn phrase: Vil bli samsvart med, uansett bruk av store/små bokstaver eller innholdsadvarselen til en tut scopes: API-ane som programmet vil få tilgjenge til. Ettersom du vel eit toppnivåomfang tarv du ikkje velja einskilde API-ar. + setting_advanced_layout: Vis Mastodon i fleire spalter, slik at du kan sjå tidslina, varsel og ei tredje spalte du vel sjølv. Ikkje tilrådd for mindre skjermar. setting_aggregate_reblogs: Ikkje vis nye framhevingar for tut som nyleg har vorte heva fram (Påverkar berre nylege framhevingar) setting_always_send_emails: Vanlegvis vil ikkje e-postvarsel bli sendt når du brukar Mastodon aktivt + setting_boost_modal: Når du har skrudd på dette, vil framheving fyrst opna ei stadfestingsrute der du vel korleis du vil visa innlegget ditt. setting_default_quote_policy_private: Innlegg som er skrivne på Mastodon og berre for fylgjarar kan ikkje siterast av andre. setting_default_quote_policy_unlisted: Når folk siterer deg, vil innlegget deira ikkje syna på populære tidsliner. setting_default_sensitive: Sensitive media vert gøymde som standard, og du syner dei ved å klikka på dei @@ -63,6 +65,7 @@ nn: setting_display_media_hide_all: Alltid skjul alt media setting_display_media_show_all: Vis alltid media setting_emoji_style: Korleis du skal visa smilefjes. «Auto» prøver å visa innebygde smilefjes, men bruker Twemoji som reserveløysing for eldre nettlesarar. + setting_quick_boosting_html: Når dette er skrudd på og du klikkar på %{boost_icon} framhev-ikonet, vil du framheva innlegget med ein gong i staden for å opna framhev/siter-menyen. Du finn siteringa i %{options_icon} (Val)-menyen. setting_system_scrollbars_ui: Gjeld berre skrivebordsnettlesarar som er bygde på Safari og Chrome setting_use_blurhash: Overgangar er basert på fargane til skjulte grafikkelement, men gjer detaljar utydelege setting_use_pending_items: Gøym tidslineoppdateringar bak eit klikk, i staden for å rulla ned automatisk @@ -85,11 +88,12 @@ nn: activity_api_enabled: Tal på lokale innlegg, aktive brukarar og nyregistreringar kvar veke app_icon: WEBP, PNG, GIF eller JPG. Overstyrer standard-app-ikonet på mobile einingar med eit eigendefinert ikon. backups_retention_period: Brukarar har moglegheit til å generere arkiv av sine innlegg for å laste ned seinare. Når sett til ein positiv verdi, blir desse arkiva automatisk sletta frå lagringa etter eit gitt antal dagar. - bootstrap_timeline_accounts: Desse kontoane vil bli festa øverst på fylgjaranbefalingane til nye brukarar. + bootstrap_timeline_accounts: Desse brukarkontoane vil bli festa på toppen av tilrådingane for nye brukarar. Skriv inn ei kommadelt liste med brukarkontoar. closed_registrations_message: Vist når det er stengt for registrering content_cache_retention_period: Alle innlegg frå andre tenarar (inkludert framhevingar og svar) vil bli sletta etter talet på dagar du skriv inn, uansett om nokon har samhandla med desse innlegga. Dette inkluderer innlegg der ein lokal brukar har merka det som bokmerke eller som favoritt. Private omtalar mellom brukarar frå ulike nettstader vil gå tapt og vera umogleg å gjenskapa. Bruk av denne innstillinga er meint for spesielle nettstader og bryt med det mange forventar av ein vanleg nettstad. custom_css: Du kan bruka eigendefinerte stilar på nettversjonen av Mastodon. favicon: WEBP, PNG, GIF eller JPG. Overstyrer det standarde Mastodon-favikonet med eit eigendefinert ikon. + landing_page: Vel kva side nye lesarar ser når dei kjem til tenaren din. Viss du vel «Populært», må du ha skrudd på dette i innstillingane for oppdaging. Viss du vel «Lokalstraum», må du la alle få sjå lokale straumar i innstillingane for oppdaging. mascot: Overstyrer illustrasjonen i det avanserte webgrensesnittet. media_cache_retention_period: Mediafiler frå innlegg laga av eksterne brukarar blir bufra på serveren din. Når sett til ein positiv verdi, slettast media etter eit gitt antal dagar. Viss mediedata blir førespurt etter det er sletta, vil dei bli lasta ned på nytt viss kjelda sitt innhald framleis er tilgjengeleg. På grunn av restriksjonar på kor ofte lenkeførehandsvisningskort lastar tredjepart-nettstadar, rådast det til å setje denne verdien til minst 14 dagar, eller at førehandsvisningskort ikkje blir oppdatert på førespurnad før det tidspunktet. min_age: Brukarane vil bli bedne om å stadfesta fødselsdatoen sin når dei registrerer seg @@ -105,10 +109,8 @@ nn: status_page_url: Adressa til ei side der folk kan sjå statusen til denne tenaren ved feil og utkoplingar theme: Tema som er synleg for nye brukarar og besøkjande som ikkje er logga inn. thumbnail: Eit omlag 2:1 bilete vist saman med informasjon om tenaren. - timeline_preview: Besøkjande som ikkje er logga inn vil kunne bla gjennom dei siste offentlege innlegga på tenaren. trendable_by_default: Hopp over manuell gjennomgang av populært innhald. Enkeltståande innlegg kan fjernast frå trendar i etterkant. trends: Trendar viser kva for nokre innlegg, emneknaggar og nyheiter som er populære på tenaren. - trends_as_landing_page: Vis populært innhald til utlogga brukarar og folk som kjem innom sida i staden for ei skildring av tenaren. Du må ha skrudd på trendar for å kunna bruka dette. form_challenge: current_password: Du går inn i eit trygt område imports: @@ -235,12 +237,12 @@ nn: setting_aggregate_reblogs: Gruppeframhevingar på tidsliner setting_always_send_emails: Alltid send epostvarsel setting_auto_play_gif: Spel av animerte GIF-ar automatisk - setting_boost_modal: Vis stadfesting før framheving + setting_boost_modal: Kontroller korleis du framhevar innlegg setting_default_language: Språk på innlegg setting_default_privacy: Innleggsvising setting_default_quote_policy: Kven kan sitera setting_default_sensitive: Merk alltid media som nærtakande - setting_delete_modal: Vis stadfesting før du slettar eit tut + setting_delete_modal: Åtvar meg før eg slettar eit innlegg setting_disable_hover_cards: Skru av profilvising når peikaren er over setting_disable_swiping: Skru av sveiperørsler setting_display_media: Medievisning @@ -250,7 +252,8 @@ nn: setting_emoji_style: Stil for smilefjes setting_expand_spoilers: Vid alltid ut tut som er merka med innhaldsåtvaringar setting_hide_network: Gøym nettverket ditt - setting_missing_alt_text_modal: Vis stadfestingsdialog før du legg ut media utan alt-tekst + setting_missing_alt_text_modal: Åtvar meg før eg legg ut media utan alternativ tekst + setting_quick_boosting: Skru på rask framheving setting_reduce_motion: Minsk rørsle i animasjonar setting_system_font_ui: Bruk standardskrifttypen på systemet setting_system_scrollbars_ui: Bruk standardrullefeltet til systemet @@ -284,12 +287,17 @@ nn: content_cache_retention_period: Oppbevaringstid for eksternt innhald custom_css: Egendefinert CSS favicon: Favorittikon + landing_page: Startside for nye lesarar + local_live_feed_access: Tilgang til direktestraumar med lokale innlegg + local_topic_feed_access: Tilgang til emneknagg- og lenkestraumar med lokale innlegg mascot: Eigendefinert maskot (eldre funksjon) media_cache_retention_period: Oppbevaringsperiode for mediebuffer min_age: Minste aldersgrense peers_api_enabled: Legg ut ei liste over oppdaga tenarar i APIet profile_directory: Aktiver profilkatalog registrations_mode: Kven kan registrera seg + remote_live_feed_access: Tilgang til direktestraumar med eksterne innlegg + remote_topic_feed_access: Tilgang til emneknagg- og direktestraumar med eksterne innlegg require_invite_text: Krev ei grunngjeving for å få bli med show_domain_blocks: Vis domeneblokkeringar show_domain_blocks_rationale: Vis grunngjeving for domeneblokkeringar @@ -302,10 +310,8 @@ nn: status_page_url: Adresse til statussida theme: Standardtema thumbnail: Miniatyrbilete for tenaren - timeline_preview: Tillat uautentisert tilgang til offentleg tidsline trendable_by_default: Tillat trendar utan gjennomgang på førehand trends: Aktiver trendar - trends_as_landing_page: Bruk trendar som startside interactions: must_be_follower: Blokker varsel frå folk som ikkje fylgjer deg must_be_following: Blokker varsel frå folk du ikkje fylgjer @@ -366,9 +372,9 @@ nn: jurisdiction: Rettskrins min_age: Minstealder user: - date_of_birth_1i: Dag + date_of_birth_1i: År date_of_birth_2i: Månad - date_of_birth_3i: År + date_of_birth_3i: Dag role: Rolle time_zone: Tidssone user_role: diff --git a/config/locales/simple_form.no.yml b/config/locales/simple_form.no.yml index cd92a269b1a044..94d8cc2c552720 100644 --- a/config/locales/simple_form.no.yml +++ b/config/locales/simple_form.no.yml @@ -70,13 +70,11 @@ featured_tag: name: 'Her er noen av emneknaggene du har brukt i det siste:' filters: - action: Velg hvilken handling som skal utføres når et innlegg samsvarer med filteret actions: hide: Skjul filtrert innhold fullstendig, som om det ikke eksisterte warn: Skjul det filtrerte innholdet bak et varsel som omtaler filterets tittel form_admin_settings: activity_api_enabled: Teller med lokale publiserte innlegg, aktive brukere og nye registreringer i ukentlige bøtter - bootstrap_timeline_accounts: Disse kontoene vil bli festet til toppen av nye brukeres følge-anbefalinger. closed_registrations_message: Vises når det er stengt for registrering custom_css: Du kan bruke egendefinerte stiler på nettversjonen av Mastodon. mascot: Overstyrer illustrasjonen i det avanserte webgrensesnittet. @@ -92,10 +90,8 @@ status_page_url: URL-adressen til en side hvor folk kan se tilstanden til denne tjeneren under et avbrudd theme: Tema som vises for nye brukere og besøkende som ikke er logget inn. thumbnail: Et omtrent 2:1 bilde vist sammen med serverinformasjonen din. - timeline_preview: Logget ut besøkende vil kunne bla gjennom de siste offentlige innlegg tilgjengelig på serveren. trendable_by_default: Hopp over manuell gjennomgang av populære innhold. Individuelle elementer kan fjernes fra populært etter faktaen. trends: Trender viser hvilke innlegg, emneknagger og nyheter som får trekkraft på serveren din. - trends_as_landing_page: Vis populære innhold til innloggede brukere og besøkende i stedet for en beskrivelse av tjeneren. Krever populært for å bli aktivert. form_challenge: current_password: Du går inn i et sikkert område imports: @@ -198,10 +194,8 @@ setting_aggregate_reblogs: Samle fremhevinger i tidslinjer setting_always_send_emails: Alltid send e-postvarslinger setting_auto_play_gif: Autoavspill animert GIF-filer - setting_boost_modal: Vis bekreftelse før fremheving setting_default_language: Innleggsspråk setting_default_sensitive: Merk alltid media som følsomt - setting_delete_modal: Vis bekreftelse før du sletter et innlegg setting_disable_swiping: Skru av sveipebevegelser setting_display_media: Mediavisning setting_display_media_default: Standard @@ -255,10 +249,8 @@ status_page_url: Status side lenke theme: Standard tema thumbnail: Miniatyrbilde til server - timeline_preview: Tillat uautentisert tilgang til offentlige tidslinjer trendable_by_default: Tillat trender uten foregående vurdering trends: Aktiver trender - trends_as_landing_page: Bruk trender som landingsside interactions: must_be_follower: Blokker varslinger fra ikke-følgere must_be_following: Blokker varslinger fra personer du ikke følger diff --git a/config/locales/simple_form.oc.yml b/config/locales/simple_form.oc.yml index 2eb7c58f544c75..bf6ff95c76e5e6 100644 --- a/config/locales/simple_form.oc.yml +++ b/config/locales/simple_form.oc.yml @@ -130,10 +130,8 @@ oc: setting_aggregate_reblogs: Agropar los partatges dins lo flux d’actualitat setting_always_send_emails: Totjorn enviar los corrièls de notificacion setting_auto_play_gif: Lectura automatica dels GIFS animats - setting_boost_modal: Mostrar una fenèstra de confirmacion abans de partejar un estatut setting_default_language: Lenga de publicacion setting_default_sensitive: Totjorn marcar los mèdias coma sensibles - setting_delete_modal: Mostrar una fenèstra de confirmacion abans de suprimir un estatut setting_disable_swiping: Desactivar las accions en lisant setting_display_media: Afichatge dels mèdias setting_display_media_default: Defaut @@ -180,7 +178,6 @@ oc: site_title: Nom del servidor theme: Tèma per defaut thumbnail: Miniatura del servidor - timeline_preview: Permtre l’accès a l’apercebut del flux public sens autentificacion trendable_by_default: Activar las tendéncias sens revision prealabla trends: Activar las tendéncias interactions: diff --git a/config/locales/simple_form.pl.yml b/config/locales/simple_form.pl.yml index da7a8e25e54b16..192a30a53ae7ab 100644 --- a/config/locales/simple_form.pl.yml +++ b/config/locales/simple_form.pl.yml @@ -54,13 +54,18 @@ pl: password: Użyj co najmniej 8 znaków phrase: Zostanie wykryte nawet, gdy znajduje się za ostrzeżeniem o zawartości scopes: Wybór API, do których aplikacja będzie miała dostęp. Jeżeli wybierzesz nadrzędny zakres, nie musisz wybierać jego elementów. + setting_advanced_layout: Wyświetlaj Mastodona w układzie wielokolumnowym, umożliwiającym przeglądanie osi czasu, powiadomień oraz trzeciej kolumny według własnego wyboru. Niezalecane w przypadku mniejszych ekranów. setting_aggregate_reblogs: Nie pokazuj nowych podbić dla wpisów, które zostały niedawno podbite (dotyczy tylko nowo otrzymanych podbić) setting_always_send_emails: Powiadomienia e-mail zwykle nie będą wysyłane, gdy używasz Mastodon + setting_boost_modal: Po włączeniu tej funkcji najpierw otworzy się okno potwierdzenia podbicia, w którym można zmienić jego widoczność. + setting_default_quote_policy_private: Wpisy publikowane na Mastodonie wyłącznie dla obserwujących nie mogą być cytowane przez inne osoby. + setting_default_quote_policy_unlisted: Kiedy ktoś cytuje twoje wpisy, będą one również ukryte na popularnych osiach czasu. setting_default_sensitive: Wrażliwe multimedia są domyślnie schowane i mogą być odkryte kliknięciem setting_display_media_default: Ukrywaj zawartość multimedialną oznaczoną jako wrażliwa setting_display_media_hide_all: Zawsze ukrywaj zawartość multimedialną setting_display_media_show_all: Zawsze pokazuj zawartość multimedialną setting_emoji_style: Jak wyświetlić emotikony. "Auto" spróbuje użyć natywnych emoji, ale wróci do Twemoji dla starszych przeglądarek. + setting_quick_boosting_html: Po włączeniu tej opcji kliknięcie ikonki %{boost_icon} spowoduje natychmiastowe podbicie zamiast otwarcia menu rozwijanego z opcją podbicia lub cytatu. Przenosi to akcję cytowania do menu %{options_icon} (Opcje). setting_system_scrollbars_ui: Stosuje się tylko do przeglądarek komputerowych opartych na Safari i Chrome setting_use_blurhash: Gradienty są oparte na kolorach ukrywanej zawartości, ale uniewidaczniają wszystkie szczegóły setting_use_pending_items: Ukryj aktualizacje osi czasu za kliknięciem, zamiast automatycznego przewijania strumienia @@ -74,7 +79,6 @@ pl: featured_tag: name: 'Oto niektóre hasztagi, których były ostatnio przez ciebie użyte:' filters: - action: Wybierz akcję do wykonania, gdy post pasuje do filtra actions: blur: Ukryj media za ostrzeżeniem, bez ukrywania samego tekstu hide: Całkowicie ukryj przefiltrowaną zawartość, jakby nie istniała @@ -83,7 +87,6 @@ pl: activity_api_enabled: Liczby opublikowanych lokalnych postów, aktywnych użytkowników i nowych rejestracji w tygodniowych przedziałach app_icon: WEBP, PNG, GIF, albo JPEG. Nadpisuje domyślną ikonę aplikacji na urządzeniach mobilnych. backups_retention_period: Użytkownicy mogą generować archiwa wpisów do późniejszego pobrania. Jeżeli ta wartość jest dodatnia, te archiwa zostaną automatycznie usunięte z twojego serwera po danej liczbie dni. - bootstrap_timeline_accounts: Te konta zostaną przypięte na górze rekomendacji obserwacji nowych użytkowników. closed_registrations_message: Wyświetlane po zamknięciu rejestracji content_cache_retention_period: Wszystkie wpisy z innych serwerów (w tym podbicia i odpowiedzi) zostaną usunięte po danej liczbie dni, bez względu na interakcje z nimi twoich użytkowników. Zawierają się w tym wpisy, które twoi użytkownicy dodali do zakładek lub ulubionych. Prywatne wzmianki od innych instancji zostaną utracone i będą nieprzywracalne. To ustawienie jest przeznaczone dla instancji zastosowania specjalnego i jest niezgodne z wieloma oczekiwaniami użytkowników. custom_css: Możesz zastosować niestandardowe style w internetowej wersji Mastodon. @@ -103,10 +106,8 @@ pl: status_page_url: Adres URL strony, na której odwiedzający mogą zobaczyć status tego serwera w trakcie awarii theme: Motyw, który widzą wylogowani i nowi użytkownicy. thumbnail: Obraz o proporcjach mniej więcej 2:1 wyświetlany obok informacji o serwerze. - timeline_preview: Wylogowani użytkownicy będą mogli przeglądać najnowsze publiczne wpisy dostępne na serwerze. trendable_by_default: Pomiń ręczny przegląd treści trendów. Pojedyncze elementy nadal mogą być usuwane z trendów po fakcie. trends: Tendencje pokazują, które posty, hasztagi i newsy zyskują popularność na Twoim serwerze. - trends_as_landing_page: Pokaż najpopularniejsze treści niezalogowanym użytkownikom i odwiedzającym zamiast opisu tego serwera. Wymaga włączenia trendów. form_challenge: current_password: Wchodzisz w strefę bezpieczną imports: @@ -235,11 +236,12 @@ pl: setting_aggregate_reblogs: Grupuj podbicia na osiach czasu setting_always_send_emails: Zawsze wysyłaj powiadomienia e-mail setting_auto_play_gif: Automatycznie odtwarzaj animowane GIFy - setting_boost_modal: Pytaj o potwierdzenie przed podbiciem + setting_boost_modal: Kontroluj widoczność podbić setting_default_language: Język wpisów + setting_default_privacy: Widoczność wpisów setting_default_quote_policy: Kto może cytować setting_default_sensitive: Zawsze oznaczaj zawartość multimedialną jako wrażliwą - setting_delete_modal: Pytaj o potwierdzenie przed usunięciem wpisu + setting_delete_modal: Ostrzegaj mnie przed usunięciem wpisu setting_disable_hover_cards: Wyłącz podgląd profilu po najechaniu setting_disable_swiping: Wyłącz ruchy przesuwania setting_display_media: Wyświetlanie zawartości multimedialnej @@ -249,7 +251,8 @@ pl: setting_emoji_style: Styl emoji setting_expand_spoilers: Zawsze rozwijaj wpisy oznaczone ostrzeżeniem o zawartości setting_hide_network: Ukryj swoją sieć - setting_missing_alt_text_modal: Pokaż okno potwierdzenia przed opublikowaniem materiałów bez pomocniczego opisu obrazów + setting_missing_alt_text_modal: Ostrzegaj mnie przed publikowaniem multimediów bez tekstu alternatywnego + setting_quick_boosting: Włącz szybkie podbijanie setting_reduce_motion: Ogranicz ruch w animacjach setting_system_font_ui: Używaj domyślnej czcionki systemu setting_system_scrollbars_ui: Używaj domyślnego paska przewijania systemu @@ -283,12 +286,16 @@ pl: content_cache_retention_period: Okres zachowywania zdalnych treści custom_css: Niestandardowy CSS favicon: Favicon + local_live_feed_access: Uzyskaj dostęp do kanałów zawierających lokalne wpisy + local_topic_feed_access: Uzyskaj dostęp do hashtagów i linków zawierających lokalne wpisy mascot: Własna ikona media_cache_retention_period: Okres przechowywania pamięci podręcznej min_age: Wymagany minimalny wiek peers_api_enabled: Opublikuj listę odkrytych serwerów w API profile_directory: Włącz katalog profilów registrations_mode: Kto może się zarejestrować + remote_live_feed_access: Uzyskaj dostęp do kanałów zawierających zdalne wpisy + remote_topic_feed_access: Uzyskaj dostęp do hashtagów i linków zawierających zdalne wpisy require_invite_text: Wymagaj powodu, aby dołączyć show_domain_blocks: Pokazuj zablokowane domeny show_domain_blocks_rationale: Pokaż dlaczego domeny zostały zablokowane @@ -301,10 +308,8 @@ pl: status_page_url: Adres URL strony statusu theme: Domyślny motyw thumbnail: Miniaturka serwera - timeline_preview: Zezwalaj na nieuwierzytelniony dostęp do publicznych osi czasu trendable_by_default: Zezwalaj na trendy bez wcześniejszego przeglądu trends: Włącz trendy - trends_as_landing_page: Użyj trendów jako strony początkowej interactions: must_be_follower: Nie wyświetlaj powiadomień od osób, które Cię nie obserwują must_be_following: Nie wyświetlaj powiadomień od osób, których nie obserwujesz @@ -365,9 +370,7 @@ pl: jurisdiction: Jurysdykcja min_age: Wiek minimalny user: - date_of_birth_1i: Dzień date_of_birth_2i: Miesiąc - date_of_birth_3i: Rok role: Rola time_zone: Strefa czasowa user_role: diff --git a/config/locales/simple_form.pt-BR.yml b/config/locales/simple_form.pt-BR.yml index fc8d90a86a9087..c0f14f77734a70 100644 --- a/config/locales/simple_form.pt-BR.yml +++ b/config/locales/simple_form.pt-BR.yml @@ -44,7 +44,7 @@ pt-BR: bot: Sinaliza aos outros de que essa conta executa principalmente ações automatizadas e pode não ser monitorada context: Um ou mais contextos onde o filtro deve atuar current_password: Para fins de segurança, digite a senha da conta atual - current_username: Para confirmar, digite o nome de usuário da conta atual + current_username: Para confirmar, entre com nome de usuário da conta atual digest: Enviado apenas após um longo período de inatividade com um resumo das menções recebidas durante ausência email: Você receberá um e-mail de confirmação header: WEBP, PNG, GIF ou JPG. No máximo %{size}. Será reduzido para %{dimensions}px @@ -54,13 +54,18 @@ pt-BR: password: Use pelo menos 8 caracteres phrase: Corresponderá independente de maiúsculas ou minúsculas, no texto ou no Aviso de Conteúdo de um toot scopes: Quais APIs o aplicativo vai ter permissão de acessar. Se você selecionar uma autorização de alto nível, você não precisa selecionar individualmente os outros. + setting_advanced_layout: Exiba o Mastodon em um layout de várias colunas, permitindo que você visualize a linha do tempo, as notificações e uma terceira coluna de sua escolha. Não recomendado para telas menores. setting_aggregate_reblogs: Não mostrar novos impulsos para publicações que já foram impulsionadas recentemente (afeta somente os impulsos mais recentes) setting_always_send_emails: Normalmente, as notificações por e-mail não serão enviadas enquanto você estiver usando ativamente o Mastodon + setting_boost_modal: Quando ativada, a função de impulsionar primeiro abrirá uma caixa de diálogo de confirmação onde você poderá alterar a visibilidade do seu impulsionamento. + setting_default_quote_policy_private: Publicações exclusivas de seguidores criadas no Mastodon não podem ser citadas por outras pessoas. + setting_default_quote_policy_unlisted: Quando as pessoas citarem você, suas publicações também ficarão ocultas da linha do tempo. setting_default_sensitive: Mídia sensível está oculta por padrão e pode ser revelada com um clique setting_display_media_default: Sempre ocultar mídia sensível setting_display_media_hide_all: Sempre ocultar todas as mídias setting_display_media_show_all: Sempre mostrar mídia sensível setting_emoji_style: Como exibir emojis. "Automáticos" tentará usar emojis nativos, mas voltará para o Twemoji para navegadores legados. + setting_quick_boosting_html: Quando ativado, clicar no ícone de impulsionamento %{boost_icon} impulsionará imediatamente o texto, em vez de abrir o menu suspenso de impulsionamento/citação. Move a ação de citação para o menu %{options_icon} (Opções). setting_system_scrollbars_ui: Se aplica apenas para navegadores de computador baseado no Safari e Chrome setting_use_blurhash: O blur é baseado nas cores da imagem oculta, ofusca a maioria dos detalhes setting_use_pending_items: Ocultar atualizações da linha do tempo atrás de um clique ao invés de rolar automaticamente @@ -74,7 +79,7 @@ pt-BR: featured_tag: name: 'Aqui estão algumas hashtags usadas recentemente:' filters: - action: Escolher qual ação executar quando uma publicação corresponder ao filtro + action: Escolha qual ação performar quando uma postagem coincidir com o filtro actions: blur: Oculte a mídia com um aviso, porém mantenha o texto visível hide: Esconder completamente o conteúdo filtrado, comportando-se como se ele não existisse @@ -83,11 +88,12 @@ pt-BR: activity_api_enabled: Contagem de publicações locais, usuários ativos e novos usuários semanais app_icon: WEBP, PNG, GIF ou JPG. Sobrescrever o ícone padrão do aplicativo em dispositivos móveis com um ícone personalizado. backups_retention_period: Os usuários podem gerar arquivos de suas postagens para baixar mais tarde. Quando definido como um valor positivo, esses arquivos serão automaticamente excluídos do seu armazenamento após o número especificado de dias. - bootstrap_timeline_accounts: Estas contas serão fixadas no topo das recomendações de novos usuários para seguir. + bootstrap_timeline_accounts: Estas contas serão fixadas ao topo das recomendações de novos usuários. Forneça uma lista de contas separada por vírgulas. closed_registrations_message: Exibido quando as inscrições estiverem fechadas content_cache_retention_period: Todas as postagens de outros servidores (incluindo boosts e respostas) serão excluídas após o número especificado de dias, sem levar a qualquer interação do usuário local com esses posts. Isto inclui postagens onde um usuário local o marcou como favorito ou favoritos. Menções privadas entre usuários de diferentes instâncias também serão perdidas e impossíveis de restaurar. O uso desta configuração destina-se a instâncias especiais de propósitos e quebra muitas expectativas dos usuários quando implementadas para uso de propósito geral. custom_css: Você pode aplicar estilos personalizados na versão da web do Mastodon. favicon: WEBP, PNG, GIF ou JPG. Sobrescreve o favicon padrão do Mastodon com um ícone personalizado. + landing_page: Seleciona a página que os novos visitantes veem ao acessar seu servidor pela primeira vez. Se você selecionar "Tendências", as tendências precisam estar ativadas nas Configurações de Descoberta. Se você selecionar "Feed local", o "Acesso a feeds ao vivo com publicações locais" precisa estar definido como "Todos" nas Configurações de Descoberta. mascot: Substitui a ilustração na interface web avançada. media_cache_retention_period: Arquivos de mídia de mensagens de usuários remotos são armazenados em cache no seu servidor. Quando definido como valor positivo, a mídia será excluída após o número especificado de dias. Se os dados da mídia forem solicitados depois de excluídos, eles serão baixados novamente, se o conteúdo fonte ainda estiver disponível. Devido a restrições de quantas vezes os cartões de visualização de links sondam sites de terceiros, é recomendado definir este valor em pelo menos 14 dias, ou pré-visualização de links não serão atualizados a pedido antes desse tempo. min_age: Os usuários precisarão confirmar sua data de nascimento no cadastro @@ -103,10 +109,8 @@ pt-BR: status_page_url: URL de uma página onde as pessoas podem ver o status deste servidor durante uma interrupção theme: Tema que visitantes e novos usuários veem. thumbnail: Uma imagem de aproximadamente 2:1 exibida ao lado da informação de sua instância. - timeline_preview: Visitantes conseguirão navegar pelas postagens públicas mais recentes disponíveis na instância. trendable_by_default: Pular a revisão manual do conteúdo em tendência. Itens individuais ainda poderão ser removidos das tendências após a sua exibição. trends: Tendências mostram quais publicações, hashtags e notícias estão ganhando destaque na sua instância. - trends_as_landing_page: Mostrar conteúdo de tendências para usuários deslogados e visitantes em vez de uma descrição deste servidor. Requer que as tendências sejam ativadas. form_challenge: current_password: Você está entrando em uma área segura imports: @@ -233,12 +237,12 @@ pt-BR: setting_aggregate_reblogs: Agrupar boosts nas linhas setting_always_send_emails: Sempre enviar notificações por e-mail setting_auto_play_gif: Reproduzir GIFs automaticamente - setting_boost_modal: Solicitar confirmação antes de dar boost + setting_boost_modal: Controlar a visibilidade dos impulsos setting_default_language: Idioma dos toots setting_default_privacy: Visibilidade da publicação setting_default_quote_policy: Quem pode citar setting_default_sensitive: Sempre marcar mídia como sensível - setting_delete_modal: Solicitar confirmação antes de excluir toot + setting_delete_modal: Avise-me antes de apagar uma publicação setting_disable_hover_cards: Desativar visualização de perfil ao passar o mouse por cima setting_disable_swiping: Desabilitar movimentos deslizantes setting_display_media: Exibição das mídias @@ -248,7 +252,8 @@ pt-BR: setting_emoji_style: Estilo de emoji setting_expand_spoilers: Sempre expandir toots com Aviso de Conteúdo setting_hide_network: Ocultar suas relações - setting_missing_alt_text_modal: Mostrar caixa de diálogo de confirmação antes de postar mídia sem texto alternativo. + setting_missing_alt_text_modal: Avise-me antes de publicar mídia sem texto alternado + setting_quick_boosting: Ativar impulsionamento rápido setting_reduce_motion: Reduzir animações setting_system_font_ui: Usar fonte padrão do sistema setting_system_scrollbars_ui: Usar barra de rolagem padrão do sistema @@ -282,12 +287,17 @@ pt-BR: content_cache_retention_period: Período de retenção de conteúdo remoto custom_css: CSS personalizável favicon: Favicon + landing_page: Página inicial para novos visitantes + local_live_feed_access: Acessar feeds ao vivo com destaque em publicações locais + local_topic_feed_access: Acessar hasthtag e endereços de feed com destaque em publicações locais mascot: Mascote personalizado (legado) media_cache_retention_period: Período de retenção do cachê de mídia min_age: Requisito de idade mínimia peers_api_enabled: Publicar lista de instâncias de servidor descobertas na API profile_directory: Ativar diretório de perfis registrations_mode: Quem pode se inscrever + remote_live_feed_access: Acessar feeds ao vivo com destaque em publicações antigas + remote_topic_feed_access: Acessar hasthtag e endereços de feed com destaque em publicações antigas require_invite_text: Exigir uma razão para entrar show_domain_blocks: Mostrar domínios bloqueados show_domain_blocks_rationale: Mostrar por que domínios foram bloqueados @@ -300,10 +310,8 @@ pt-BR: status_page_url: Endereço da página de status theme: Tema padrão thumbnail: Miniatura do servidor - timeline_preview: Permitir acesso não autenticado às linhas do tempo públicas trendable_by_default: Permitir tendências sem revisão prévia trends: Habilitar tendências - trends_as_landing_page: Usar tendências como página inicial interactions: must_be_follower: Bloquear notificações de não-seguidores must_be_following: Bloquear notificações de não-seguidos @@ -364,9 +372,9 @@ pt-BR: jurisdiction: Jurisdição legal min_age: Idade mínima user: - date_of_birth_1i: Dia + date_of_birth_1i: Ano date_of_birth_2i: Mês - date_of_birth_3i: Ano + date_of_birth_3i: Dia role: Cargo time_zone: Fuso horário user_role: diff --git a/config/locales/simple_form.pt-PT.yml b/config/locales/simple_form.pt-PT.yml index 4e5fa12c041ed8..339c48edea8294 100644 --- a/config/locales/simple_form.pt-PT.yml +++ b/config/locales/simple_form.pt-PT.yml @@ -54,8 +54,10 @@ pt-PT: password: Use pelo menos 8 caracteres phrase: Será correspondido independentemente da capitalização ou do aviso de conteúdo duma publicação scopes: Quais as API a que a aplicação terá permissão para aceder. Se selecionar um âmbito de nível superior, não precisa de selecionar âmbitos individuais. - setting_aggregate_reblogs: Não mostrar os novos impulsos para publicações que tenham sido recentemente impulsionadas (apenas afeta os impulsos recentemente recebidos) + setting_advanced_layout: Exibe o Mastodon num layout com várias colunas, permitindo-lhe visualizar a cronologia, as notificações e uma terceira coluna à sua escolha. Não recomendado para ecrãs mais pequenos. + setting_aggregate_reblogs: Não mostrar as novas partilhas para publicações que tenham sido partilhadas recentemente (apenas afeta as partilhas recebidas recentemente) setting_always_send_emails: Normalmente as notificações por e-mail não serão enviadas quando estiver a utilizar ativamente o Mastodon + setting_boost_modal: Quando ativado, ao partilhar abrirá primeiro uma caixa de diálogo de confirmação onde poderá alterar a visibilidade da sua partilha. setting_default_quote_policy_private: As publicações exclusivas para seguidores criadas no Mastodon não podem ser citadas por outras pessoas. setting_default_quote_policy_unlisted: Quando as pessoas o citarem, as respetivas publicações também serão ocultadas dos destaques. setting_default_sensitive: Os multimédia sensíveis são ocultados por predefinição e podem ser revelados com um clique/toque @@ -63,6 +65,7 @@ pt-PT: setting_display_media_hide_all: Esconder sempre toda a multimédia setting_display_media_show_all: Mostrar sempre a multimédia setting_emoji_style: Como apresentar emojis. "Auto" tenta usar emojis nativos, mas reverte para Twemoji em navegadores mais antigos. + setting_quick_boosting_html: Quando ativado, clicar no ícone %{boost_icon} Partilhar irá de imediato partilhar ao invés de abrir o menu de Partilhar/Citar. Relocaliza a ação Citar para o menu %{options_icon} (Opções). setting_system_scrollbars_ui: Aplica-se apenas a navegadores de desktop baseados no Safari e Chrome setting_use_blurhash: Os gradientes são baseados nas cores das imagens escondidas, mas ofuscam quaisquer pormenores setting_use_pending_items: Ocultar as atualizações da cronologia após um clique em vez de percorrer automaticamente a cronologia @@ -76,7 +79,7 @@ pt-PT: featured_tag: name: 'Eis algumas das etiquetas que utilizou recentemente:' filters: - action: Escolha qual a ação a executar quando uma publicação corresponde ao filtro + action: Escolha que ação executar quando uma publicação corresponder ao filtro actions: blur: Esconder multimédia com um aviso à frente, sem esconder o texto hide: Ocultar completamente o conteúdo filtrado, comportando-se como se não existisse @@ -85,13 +88,14 @@ pt-PT: activity_api_enabled: Contagem, em blocos semanais, de publicações locais, utilizadores ativos e novos registos app_icon: WEBP, PNG, GIF ou JPG. Substitui o ícone padrão da aplicação em dispositivos móveis por um ícone personalizado. backups_retention_period: Os utilizadores têm a possibilidade de gerar arquivos das suas publicações para descarregar mais tarde. Quando definido para um valor positivo, estes arquivos serão automaticamente eliminados do seu armazenamento após o número de dias especificado. - bootstrap_timeline_accounts: Estas contas serão destacadas no topo das recomendações aos novos utilizadores. + bootstrap_timeline_accounts: Essas contas serão colocadas no topo das recomendações de seguidores para os novos utilizadores. Forneça uma lista de contas separadas por vírgulas. closed_registrations_message: Apresentado quando as inscrições estiverem encerradas - content_cache_retention_period: Todas as mensagens de outros servidores (incluindo impulsos e respostas) serão eliminadas após o número de dias especificado, independentemente de qualquer interação do utilizador local com essas mensagens. Isto inclui mensagens em que um utilizador local as tenha marcado ou adicionado aos favoritos. As menções privadas entre utilizadores de instâncias diferentes também se perderão e serão impossíveis de restaurar. A utilização desta definição destina-se a instâncias para fins especiais e quebra muitas expectativas dos utilizadores quando implementada para utilização geral. + content_cache_retention_period: Todas as publicações de outros servidores (incluindo partilhas e respostas) serão eliminadas após o número de dias especificado, independentemente de qualquer interação do utilizador local com essas publicações. Isto inclui mensagens em que um utilizador local as tenha salvo ou adicionado aos favoritos. As menções privadas entre utilizadores de instâncias diferentes também se perderão e serão impossíveis de recuperar. A utilização desta definição destina-se a instâncias para fins especiais e quebra muitas expectativas dos utilizadores quando implementada para utilização geral. custom_css: Pode aplicar estilos personalizados na versão web do Mastodon. favicon: WEBP, PNG, GIF ou JPG. Substitui o ícone de favorito padrão do Mastodon por um ícone personalizado. + landing_page: Seleciona a página que os novos visitantes veem quando chegam ao seu servidor pela primeira vez. Se selecionar «Tendências», então as tendências precisam estar ativas nas Definições de Descoberta. Se selecionar «Cronologia local», então «Acesso a cronologias com publicações locais em destaque» precisa de estar definido como «Todos» nas Definições de Descoberta. mascot: Sobrepõe-se à ilustração na interface web avançada. - media_cache_retention_period: Os ficheiros multimédia de publicações feitas por utilizadores remotos são armazenados em cache no seu servidor. Quando definido para um valor positivo, os ficheiros multimédia serão eliminados após o número de dias especificado. Se os ficheiros multimédia forem solicitados depois de terem sido eliminados, serão transferidos novamente, se o conteúdo de origem ainda estiver disponível. Devido a restrições sobre a frequência com que os cartões de pré-visualização de links pesquisam sites de terceiros, recomenda-se que este valor seja definido para, pelo menos, 14 dias, ou os cartões de pré-visualização de links não serão atualizados a pedido antes desse período. + media_cache_retention_period: Os ficheiros multimédia de publicações feitas por utilizadores remotos são armazenados em cache no seu servidor. Quando definido para um valor positivo, os ficheiros multimédia serão eliminados após o número de dias especificado. Se os ficheiros multimédia forem solicitados depois de terem sido eliminados, serão transferidos novamente, se o conteúdo de origem ainda estiver disponível. Devido a restrições sobre a frequência com que os cartões de pré-visualização de hiperligação pesquisam sites de terceiros, recomenda-se que este valor seja definido para, pelo menos, 14 dias, ou os cartões de pré-visualização de hiperligação não serão atualizados a pedido antes desse período. min_age: Os utilizadores serão convidados a confirmar a sua data de nascimento durante o processo de inscrição peers_api_enabled: Uma lista de nomes de domínio que este servidor encontrou no fediverso. Nenhum dado é incluído aqui sobre se você federa com um determinado servidor, apenas que o seu servidor o conhece. Este serviço é utilizado por serviços que recolhem estatísticas na federação, em termos gerais. profile_directory: O diretório de perfis lista todos os utilizadores que optaram por ter a sua conta a ser sugerida a outros. @@ -105,10 +109,9 @@ pt-PT: status_page_url: URL de uma página onde as pessoas podem ver o estado deste servidor durante uma interrupção theme: Tema que os visitantes e os novos utilizadores veem. thumbnail: Uma imagem de cerca de 2:1, apresentada ao lado da informação do seu servidor. - timeline_preview: Os visitantes sem sessão iniciada poderão consultar as publicações públicas mais recentes disponíveis no servidor. trendable_by_default: Ignorar a revisão manual do conteúdo em destaque. Os itens individuais poderão ainda assim ser posteriormente removidos das tendências. trends: As tendências mostram quais as publicações, etiquetas e notícias que estão a ganhar destaque no seu servidor. - trends_as_landing_page: Mostrar conteúdo em destaque a utilizadores sem sessão iniciada e visitantes, ao invés de uma descrição deste servidor. Requer que os destaques estejam ativados. + wrapstodon: Ofereça aos utilizadores locais a possibilidade de gerar um resumo divertido da sua utilização do Mastodon durante o ano. Esta funcionalidade está disponível entre 10 e 31 de Dezembro de cada ano e é oferecida aos utilizadores que fizeram pelo menos uma publicação Pública ou Não listada e utilizaram pelo menos uma etiqueta durante o ano. form_challenge: current_password: Está a entrar numa área segura imports: @@ -232,15 +235,15 @@ pt-PT: password: Palavra-passe phrase: Palavra-chave ou frase setting_advanced_layout: Ativar interface web avançada - setting_aggregate_reblogs: Agrupar impulsos em linhas de tempo + setting_aggregate_reblogs: Agrupar partilhas nas cronologias setting_always_send_emails: Enviar sempre notificações por e-mail setting_auto_play_gif: Reproduzir GIF automaticamente - setting_boost_modal: Mostrar caixa de diálogo de confirmação antes de impulsionar + setting_boost_modal: Controlar visibilidade da partilha setting_default_language: Idioma de publicação setting_default_privacy: Visibilidade da publicação setting_default_quote_policy: Quem pode citar setting_default_sensitive: Marcar sempre os multimédia como sensíveis - setting_delete_modal: Solicitar confirmação antes de eliminar uma publicação + setting_delete_modal: Avisar-me antes de eliminar uma publicação setting_disable_hover_cards: Desativar visualização de perfil ao passar o cursor setting_disable_swiping: Desativar os movimentos de deslize setting_display_media: Visualização de multimédia @@ -250,7 +253,8 @@ pt-PT: setting_emoji_style: Estilo de emojis setting_expand_spoilers: Expandir sempre as publicações marcadas com avisos de conteúdo setting_hide_network: Esconder a tua rede - setting_missing_alt_text_modal: Mostrar janela de confirmação antes de publicar multimédia sem texto alternativo + setting_missing_alt_text_modal: Avisar-me antes de publicar media sem texto alternativo + setting_quick_boosting: Ativar partilha rápida setting_reduce_motion: Reduzir movimento em animações setting_system_font_ui: Usar o tipo de letra padrão do sistema setting_system_scrollbars_ui: Utilizar a barra de deslocação predefinida do sistema @@ -284,12 +288,17 @@ pt-PT: content_cache_retention_period: Período de retenção de conteúdos remotos custom_css: CSS personalizado favicon: Ícone de favoritos + landing_page: Página inicial para novos visitantes + local_live_feed_access: Acesso a cronologias com publicações locais em destaque + local_topic_feed_access: Acesso a cronologias de etiquetas e hiperligações de publicações locais em destaque mascot: Mascote personalizada (legado) media_cache_retention_period: Período de retenção de ficheiros multimédia em cache min_age: Idade mínima requerida peers_api_enabled: Publicar lista de servidores descobertos na API profile_directory: Ativar o diretório de perfis registrations_mode: Quem se pode inscrever + remote_live_feed_access: Acesso a cronologias com publicações remotas em destaque + remote_topic_feed_access: Acesso a cronologias de etiquetas e hiperligações de publicações remotas em destaque require_invite_text: Requerer uma razão para entrar show_domain_blocks: Mostrar domínios bloqueados show_domain_blocks_rationale: Mostrar porque os domínios foram bloqueados @@ -302,10 +311,9 @@ pt-PT: status_page_url: URL da página de estado theme: Tema predefinido thumbnail: Miniatura do servidor - timeline_preview: Permitir acesso não autenticado às cronologias públicas trendable_by_default: Permitir tendências sem revisão prévia trends: Ativar destaques - trends_as_landing_page: Usar destaques como página de apresentação + wrapstodon: Ativar Wrapstodon interactions: must_be_follower: Bloquear notificações de não-seguidores must_be_following: Bloquear notificações de pessoas que não segues @@ -331,7 +339,7 @@ pt-PT: mention: Alguém o mencionou pending_account: Uma nova conta aguarda aprovação quote: Alguém o citou - reblog: Alguém impulsionou uma publicação sua + reblog: Alguém partilhou uma publicação sua report: Uma nova denúncia foi submetida software_updates: all: Notificar todas as atualizações @@ -366,9 +374,9 @@ pt-PT: jurisdiction: Jurisdição legal min_age: Idade mínima user: - date_of_birth_1i: Dia + date_of_birth_1i: Ano date_of_birth_2i: Mês - date_of_birth_3i: Ano + date_of_birth_3i: Dia role: Função time_zone: Fuso horário user_role: diff --git a/config/locales/simple_form.ro.yml b/config/locales/simple_form.ro.yml index ae8e2e065c3f17..020285edc9b7ea 100644 --- a/config/locales/simple_form.ro.yml +++ b/config/locales/simple_form.ro.yml @@ -71,7 +71,6 @@ ro: featured_tag: name: 'Iată câteva dintre hashtag-urile pe care le-ai folosit cel mai recent:' filters: - action: Alege ce acţiune va fi efectuată atunci când o postare corespunde filtrului actions: hide: Ascunde complet conținutul filtrat, ca și cum nu ar exista warn: Ascunde conținutul filtrat în spatele unui avertisment care menționează titlul filtrului @@ -79,7 +78,6 @@ ro: activity_api_enabled: Numărul de postări publicate local, utilizatori activi și înregistrări noi în grupe săptămânale app_icon: WEBP, PNG, GIF sau JPG. Înlocuiește pictograma implicită a aplicației pe dispozitivele mobile cu o pictogramă personalizată. backups_retention_period: Utilizatorii au posibilitatea de a genera arhive ale postărilor lor pentru a le descărca mai târziu. Când este setat la o valoare pozitivă, aceste arhive vor fi șterse automat din spațiul dvs. de stocare după numărul de zile specificat. - bootstrap_timeline_accounts: Aceste conturi vor fi fixate în partea de sus a recomandărilor de urmărire ale noilor utilizatori. closed_registrations_message: Afișat când înscrierile sunt închise content_cache_retention_period: Toate postările de pe alte servere (inclusiv amplificarea și răspunsurile) vor fi șterse după numărul specificat de zile, fără a ține cont de interacțiunea utilizatorului local cu acele postări. Aceasta include postările în care un utilizator local le-a marcat ca marcaje sau favorite. Mențiunile private între utilizatorii din diferite instanțe se vor pierde și vor fi imposibil de restaurat. Utilizarea acestei setări este destinată cazurilor cu scop special și încalcă multe așteptări ale utilizatorilor atunci când este implementată pentru uz general. custom_css: Puteți aplica stiluri personalizate pe versiunea web a Mastodon. @@ -98,10 +96,8 @@ ro: status_page_url: URL-ul unei pagini unde oamenii pot vedea starea acestui server în timpul unei întreruperi theme: Tema pe care vizitatorii deconectați și utilizatorii noi o văd. thumbnail: O imagine de aproximativ 2:1 afișată alături de informațiile serverului dvs. - timeline_preview: Vizitatorii deconectați vor putea să răsfoiască cele mai recente postări publice disponibile pe server. trendable_by_default: Omiteți revizuirea manuală a conținutului în tendințe. Elementele individuale pot fi în continuare eliminate din tendințe după fapt. trends: Tendințele arată ce postări, hashtag-uri și știri câștigă teren pe serverul dvs. - trends_as_landing_page: Afișați conținut în tendințe utilizatorilor deconectați și vizitatorilor în loc de o descriere a acestui server. Necesită ca tendințele să fie activate. form_challenge: current_password: Ați intrat într-o zonă securizată imports: @@ -189,10 +185,8 @@ ro: setting_advanced_layout: Activează interfața web avansată setting_aggregate_reblogs: Grupează impulsurile în fluxuri setting_auto_play_gif: Redă automat animațiile GIF - setting_boost_modal: Arată dialogul de confirmare înainte de a impulsiona setting_default_language: În ce limbă postezi setting_default_sensitive: Întotdeauna marchează conținutul media ca fiind sensibil - setting_delete_modal: Arată dialogul de confirmare înainte de a șterge o postare setting_display_media: Afișare media setting_display_media_default: Standard setting_display_media_hide_all: Ascunde toate diff --git a/config/locales/simple_form.ru.yml b/config/locales/simple_form.ru.yml index cf59942e7fb732..568a45f16c9afa 100644 --- a/config/locales/simple_form.ru.yml +++ b/config/locales/simple_form.ru.yml @@ -19,23 +19,23 @@ ru: text: Вы можете использовать всё то же самое, что и в обычных постах, — ссылки, хештеги и упоминания title: Необязательно. Не видно получателю admin_account_action: - include_statuses: Пользователь будет видеть к каким постами применялись модераторские действия и выносились предупреждения + include_statuses: Пользователь увидит, какие посты стали причиной действий модераторов либо вынесения предупреждения send_email_notification: Пользователь получит сообщение о том, что случилось с его/её учётной записью text_html: Необязательно. Можно использовать всё то же самое, что и в обычных постах. Для экономии времени вы можете добавить шаблоны предупреждений - type_html: Выберите, какую санкцию вынести в отношении %{acct} + type_html: Выберите, какое действие применить к %{acct} types: - disable: Запретить пользователю использование своей учётной записи, без удаления или скрытия контента. - none: Отправить пользователю предупреждение, не принимая иных действий. - sensitive: Принудительно отметить опубликованное пользователем содержимое как «деликатного характера». - silence: Запретить пользователю публиковать посты с открытой видимостью, а также скрыть все прошлые посты и уведомления от людей, не читающих этого пользователя. Закрыть все отчеты по этому счету. - suspend: Предотвратить любое взаимодействие с этой учётной записью, удалив всё содержимое опубликованное с неё. Это действие можно отменить в течение 30 дней. Закрывает все отчеты против этого аккаунта. + disable: Запретить пользователю использовать свою учётную запись, при этом не удаляя и не скрывая саму учётную запись. + none: Отправить пользователю предупреждение, не принимая никаких других действий. + sensitive: Принудительно отмечать все медиавложения пользователя как содержимое деликатного характера. + silence: Пользователю будет запрещено создавать посты с публичной видимостью, а увидеть уведомления от этого пользователя и его посты смогут только подписчики. Все жалобы на эту учётную запись будут отмечены как решённые. + suspend: Запретить любое взаимодействие с этой учётной записью и удалить все связанные с ней данные. Это действие можно отменить в течение 30 дней. Все жалобы на эту учётную запись будут отмечены как решённые. warning_preset_id: Необязательно. Вы по-прежнему сможете добавить собственный текст в конец шаблона announcement: all_day: Если выбрано, часы начала и завершения будут скрыты ends_at: Необязательно. Объявление будет автоматически отменено в это время scheduled_at: Оставьте поля незаполненными, чтобы опубликовать объявление сразу starts_at: Необязательно. На случай, если ваше объявление привязано к какому-то временному интервалу - text: Вы можете использовать тот же синтаксис, что и в постах. Будьте предусмотрительны насчёт места, которое займёт объявление на экране пользователей + text: Вы можете использовать всё то же самое, что и в обычных постах. Пожалуйста, не забывайте о пространстве, которое объявление будет занимать на экране пользователя appeal: text: Вы можете обжаловать замечание только один раз defaults: @@ -54,12 +54,18 @@ ru: password: Пароль должен состоять минимум из 8 символов phrase: Поиск соответствия будет выполнен без учёта регистра в тексте поста и предупреждения о содержании scopes: Выберите, какие API приложение сможет использовать. Разрешения верхнего уровня имплицитно включают в себя все разрешения более низких уровней. + setting_advanced_layout: Использовать многоколоночный интерфейс Mastodon, который позволяет одновременно открыть ленту, уведомления и третью колонку по вашему выбору. Не рекомендуется для маленьких экранов. setting_aggregate_reblogs: Не показывать новые продвижения постов, которые уже были недавно продвинуты (применяется только к будущим продвижениям) setting_always_send_emails: По умолчанию уведомления не доставляются по электронной почте, пока вы активно используете Mastodon + setting_boost_modal: Отметьте флажок, чтобы при продвижении поста открывать окно подтверждения, в котором вы сможете изменить видимость вашего продвижения. + setting_default_quote_policy_private: Посты, созданные в Mastodon с видимостью только для подписчиков, не могут быть процитированы другими пользователями. + setting_default_quote_policy_unlisted: Если кто-нибудь процитирует вас, его пост тоже будет скрыт из алгоритмических лент. setting_default_sensitive: Медиа деликатного характера скрыты по умолчанию и могут быть показаны по нажатию на них setting_display_media_default: Скрывать медиа деликатного характера setting_display_media_hide_all: Скрывать все медиа setting_display_media_show_all: Показывать все медиа + setting_emoji_style: Как отображать эмодзи. Если выбран вариант «Автоматически», то будут использованы системные эмодзи, а для устаревших браузеров — Twemoji. + setting_quick_boosting_html: Отметьте флажок, чтобы при нажатии на кнопку %{boost_icon} Продвинуть не выбирать между продвижением и цитированием, а сразу продвигать пост. Цитирование будет доступно из меню поста (%{options_icon}). setting_system_scrollbars_ui: Работает только в браузерах для ПК на основе Safari или Chrome setting_use_blurhash: Градиенты основаны на цветах скрытых медиа, но размывают любые детали setting_use_pending_items: Отметьте флажок, чтобы выключить автоматическую прокрутку, и тогда обновления в лентах будут вам показаны только по нажатию @@ -79,14 +85,15 @@ ru: hide: Полностью скрыть отфильтрованный пост, будто бы его не существует warn: Скрыть отфильтрованный пост за предупреждением с указанием названия фильтра form_admin_settings: - activity_api_enabled: Подсчёт количества локальных постов, активных пользователей и новых регистраций на еженедельной основе - app_icon: WEBP, PNG, GIF или JPG. Замените значок приложения по умолчанию на мобильных устройствах пользовательским значком. - backups_retention_period: Пользователи могут создавать архивы своих постов, чтобы потом их забрать. Если задать положительное значение, эти архивы автоматически удалятся с вашего хранилища через указанное число дней. - bootstrap_timeline_accounts: Эти аккаунты будут рекомендованы для подписки новым пользователям. + activity_api_enabled: Еженедельная выгрузка количества локальных постов, активных пользователей и новых регистраций + app_icon: WEBP, PNG, GIF или JPG. Заменяет значок приложения на мобильных устройствах по умолчанию вашим значком. + backups_retention_period: Пользователи могут запустить создание архива своих постов, чтобы скачать его позже. Если задать положительное значение, эти архивы будут автоматически удалены с вашего хранилища через указанное число дней. + bootstrap_timeline_accounts: Эти учётные записи будут закреплены в начале списка рекомендуемых профилей для новых пользователей. Список учётных записей нужно вводить через запятую. closed_registrations_message: Отображается, когда регистрация закрыта content_cache_retention_period: Все сообщения с других серверов (включая бусты и ответы) будут удалены через указанное количество дней, независимо от того, как локальный пользователь взаимодействовал с этими сообщениями. Это касается и тех сообщений, которые локальный пользователь пометил в закладки или избранное. Приватные упоминания между пользователями из разных инстансов также будут потеряны и не смогут быть восстановлены. Использование этой настройки предназначено для экземпляров специального назначения и нарушает многие ожидания пользователей при использовании в общих целях. custom_css: Вы можете применять пользовательские стили в веб-версии Mastodon. favicon: WEBP, PNG, GIF или JPG. Заменяет стандартный фавикон Mastodon на собственный значок. + landing_page: Определите, какую страницу новые посетители увидят, когда зайдут на ваш сервер впервые. Если вы выберете «Актуальное», то необходимо включить тренды в настройках обзора. Если вы выберете «Локальная лента», то необходимо в настройках обзора установить настройку «Доступ к живым лентам, содержащим локальные посты» в значение «Кто угодно». mascot: Заменяет иллюстрацию в расширенном веб-интерфейсе. media_cache_retention_period: Медиафайлы из сообщений, сделанных удаленными пользователями, кэшируются на вашем сервере. При положительном значении медиафайлы будут удалены через указанное количество дней. Если медиаданные будут запрошены после удаления, они будут загружены повторно, если исходный контент все еще доступен. В связи с ограничениями на частоту опроса карточек предварительного просмотра ссылок на сторонних сайтах рекомендуется устанавливать значение не менее 14 дней, иначе карточки предварительного просмотра ссылок не будут обновляться по запросу до этого времени. min_age: Пользователям при регистрации будет предложено ввести свою дату рождения @@ -102,10 +109,8 @@ ru: status_page_url: URL страницы, на которой люди могут видеть статус этого сервера во время отключения theme: Тема, которую видят вышедшие из системы посетители и новые пользователи. thumbnail: Изображение примерно 2:1, отображаемое рядом с информацией о вашем сервере. - timeline_preview: Посетители, вышедшие из системы, смогут просматривать последние публичные сообщения, имеющиеся на сервере. trendable_by_default: Пропустить ручной просмотр трендового контента. Отдельные элементы могут быть удалены из трендов уже постфактум. trends: Тренды показывают, какие посты, хэштеги и новостные истории набирают обороты на вашем сервере. - trends_as_landing_page: Показывать популярный контент для выходов пользователей и посетителей, а не для описания этого сервера. Требует включения тенденций. form_challenge: current_password: Вы переходите к настройкам безопасности вашей учётной записи imports: @@ -122,8 +127,8 @@ ru: sign_up_requires_approval: Новые регистрации потребуют вашего одобрения severity: Выберите, что будет происходить с запросами с этого IP rule: - hint: Необязательно. Предоставьте дополнительные сведения о правиле - text: Опишите правило или требование для пользователей на этом сервере. Постарайтесь сделать его коротким и простым + hint: Необязательно. Расскажите о правиле подробнее + text: Опишите правило или требование для пользователей на этом сервере. Постарайтесь сформулировать его просто и лаконично sessions: otp: Создайте код двухфакторной аутентификации в приложении на вашем смартфоне и введите его здесь, или же вы можете использовать один из ваших резервных кодов. webauthn: Если вы используете USB-ключ, не забудьте вставить и активировать его. @@ -140,12 +145,12 @@ ru: admin_email: Юридические уведомления включают в себя встречные уведомления, постановления суда, запросы на удаление и запросы правоохранительных органов. arbitration_address: Может совпадать с почтовым адресом, указанным выше, либо «N/A» в случае электронной почты. arbitration_website: Веб-форма или «N/A» в случае электронной почты. - choice_of_law: Город, регион, территория или государство, внутреннее материальное право которого регулирует любые претензии. + choice_of_law: Город, регион, территория или государство, внутреннее материальное право которого будет регулировать любые претензии. dmca_address: Находящиеся в США операторы должны использовать адрес, зарегистрированный в DMCA Designated Agent Directory. Использовать абонентский ящик возможно при обращении в соответствующей просьбой, для чего нужно с помощью DMCA Designated Agent Post Office Box Waiver Request написать сообщение в Copyright Office и объяснить, что вы занимаетесь модерацией контента из дома и опасаетесь мести за свои действия, поэтому должны использовать абонентский ящик, чтобы убрать ваш домашний адрес из общего доступа. dmca_email: Может совпадать с адресом электронной почты для юридических уведомлений, указанным выше. domain: Имя, позволяющее уникально идентифицировать ваш онлайн-ресурс. jurisdiction: Впишите страну, где находится лицо, оплачивающее счета. Если это компания либо организация, впишите страну инкорпорации, включая город, регион, территорию или штат, если это необходимо. - min_age: Не меньше минимального возраста, требуемого по закону в вашей юрисдикции. + min_age: Не меньше минимального возраста, требуемого по закону в вашей стране. user: chosen_languages: Отметьте языки, на которых вы желаете видеть посты в публичных лентах. Оставьте выбор пустым, чтобы не фильтровать посты по языку date_of_birth: @@ -180,18 +185,18 @@ ru: acct: Адрес новой учётной записи account_warning_preset: text: Текст шаблона - title: Заголовок + title: Название admin_account_action: - include_statuses: Включать в письмо жалобы на посты - send_email_notification: Уведомить пользователя по электронной почте + include_statuses: Сообщить пользователю о том, на какие из его постов пожаловались + send_email_notification: Отправить пользователю уведомление по электронной почте text: Текст предупреждения type: Действие types: - disable: Заморозить - none: Ничего не делать - sensitive: Отметить как «деликатного характера» - silence: Скрыть - suspend: Заблокировать и безвозвратно удалить все данные учётной записи + disable: Отключить + none: Вынести предупреждение + sensitive: Скрыть медиа + silence: Ограничить + suspend: Заблокировать warning_preset_id: Использовать шаблон предупреждения announcement: all_day: Весь день @@ -230,8 +235,9 @@ ru: setting_aggregate_reblogs: Группировать продвижения в лентах setting_always_send_emails: Всегда отправлять уведомления по электронной почте setting_auto_play_gif: Включить автовоспроизведение анимированных GIF-файлов - setting_boost_modal: Запрашивать подтверждение при продвижении поста + setting_boost_modal: Настроить видимость перед продвижением setting_default_language: Язык публикуемых постов + setting_default_privacy: Видимость поста setting_default_quote_policy: Кто может цитировать вас setting_default_sensitive: Отмечать все мои медиа как содержимое деликатного характера setting_delete_modal: Запрашивать подтверждение при удалении поста @@ -245,6 +251,7 @@ ru: setting_expand_spoilers: Разворачивать все посты с предупреждением о содержании setting_hide_network: Скрыть мои связи setting_missing_alt_text_modal: Запрашивать подтверждение при публикации медиа без альтернативного текста + setting_quick_boosting: Включить ускоренное продвижение setting_reduce_motion: Уменьшить движение пользовательского интерфейса setting_system_font_ui: Использовать системный шрифт setting_system_scrollbars_ui: Использовать системные полосы прокрутки @@ -271,35 +278,38 @@ ru: warn: Скрыть с предупреждением form_admin_settings: activity_api_enabled: Публикация агрегированной статистики активности пользователей в API - app_icon: Иконка приложения - backups_retention_period: Период хранения архива пользователя - bootstrap_timeline_accounts: Всегда рекомендовать эти учетные записи новым пользователям - closed_registrations_message: Сообщение, когда регистрация недоступна - content_cache_retention_period: Период хранения удаленного содержимого + app_icon: Значок приложения + backups_retention_period: Срок хранения архива пользователя + bootstrap_timeline_accounts: Всегда рекомендовать эти профили новым пользователям + closed_registrations_message: Текст сообщения о том, что регистрация недоступна + content_cache_retention_period: Срок хранения содержимого с других серверов custom_css: Пользовательский CSS favicon: Favicon + landing_page: Целевая страница для новых посетителей + local_live_feed_access: Доступ к живым лентам, содержащим локальные посты + local_topic_feed_access: Доступ к лентам хештегов и ссылок, содержащим локальные посты mascot: Пользовательский маскот (устаревшее) - media_cache_retention_period: Период хранения кэша медиафайлов - min_age: Требование минимального возраста - peers_api_enabled: Публикация списка обнаруженных узлов в API + media_cache_retention_period: Срок хранения кэша медиа + min_age: Минимальный возраст для регистрации + peers_api_enabled: Публикация списка обнаруженных серверов в API profile_directory: Включить каталог профилей registrations_mode: Кто может зарегистрироваться - require_invite_text: Требуется причина для присоединения - show_domain_blocks: Показать блокировки домена - show_domain_blocks_rationale: Показать причину блокировки доменов - site_contact_email: Контактный e-mail + remote_live_feed_access: Доступ к живым лентам, содержащим посты с других серверов + remote_topic_feed_access: Доступ к лентам хештегов и ссылок, содержащим посты с других серверов + require_invite_text: Требовать указывать причину регистрации + show_domain_blocks: Показывать список заблокированных доменов + show_domain_blocks_rationale: Показывать причину блокировки домена + site_contact_email: Контактный адрес электронной почты site_contact_username: Контактное имя пользователя site_extended_description: Подробное описание site_short_description: Описание сервера site_terms: Политика конфиденциальности site_title: Имя сервера - status_page_url: Страница уведомлений + status_page_url: Страница состояния сервера theme: Тема по умолчанию - thumbnail: Изображение сервера - timeline_preview: Разрешить доступ к публичным лентам без авторизации - trendable_by_default: Разрешить треды без предварительной проверки + thumbnail: Обложка сервера + trendable_by_default: Отключить обязательную премодерацию трендов trends: Включить тренды - trends_as_landing_page: Использовать тенденции в качестве целевой страницы interactions: must_be_follower: Блокировать уведомления от людей, которые не подписаны на вас must_be_following: Блокировать уведомления от людей, на которых вы не подписаны @@ -324,7 +334,7 @@ ru: follow_request: Мне пришёл запрос на подписку mention: Меня упомянули в посте pending_account: Новая заявка на создание аккаунта - quote: Кто-то процитировал вас + quote: Мой пост процитировали reblog: Мой пост продвинули report: Новое обращение отправлено software_updates: @@ -335,7 +345,7 @@ ru: patch: Уведомлять об обновлении исправлений trending_tag: Новый тренд требует рассмотрения rule: - hint: Больше информации + hint: Дополнительная информация text: Правило settings: indexable: Разрешить индексацию моего профиля поисковыми системами @@ -352,17 +362,17 @@ ru: terms_of_service_generator: admin_email: Адрес электронной почты для юридических уведомлений arbitration_address: Почтовый адрес для уведомлений об арбитраже - arbitration_website: Вебсайт для подачи уведомления об арбитраже - choice_of_law: Юрисдикция + arbitration_website: Веб-сайт для подачи уведомления об арбитраже + choice_of_law: Выбор права dmca_address: Почтовый адрес для обращений правообладателей dmca_email: Адрес электронной почты для обращений правообладателей domain: Доменное имя jurisdiction: Юрисдикция min_age: Минимальный возраст user: - date_of_birth_1i: День + date_of_birth_1i: Год date_of_birth_2i: Месяц - date_of_birth_3i: Год + date_of_birth_3i: День role: Роль time_zone: Часовой пояс user_role: @@ -375,9 +385,9 @@ ru: allow_with_approval: Разрешить регистрацию с одобрением comparison: Метод сравнения webhook: - events: Включенные события - template: Шаблон полезной нагрузки - url: Endpoint URL + events: Типы событий + template: Шаблон данных + url: Адрес 'no': Нет not_recommended: Не рекомендуется overridden: Переопределено diff --git a/config/locales/simple_form.sc.yml b/config/locales/simple_form.sc.yml index 757bdd28d85d7b..2353766e21dd94 100644 --- a/config/locales/simple_form.sc.yml +++ b/config/locales/simple_form.sc.yml @@ -134,10 +134,8 @@ sc: setting_advanced_layout: Ativa s'interfache web avantzada setting_aggregate_reblogs: Agrupa is cumpartziduras in is lìnias de tempus setting_auto_play_gif: Riprodui is GIF animadas in automàticu - setting_boost_modal: Ammustra unu diàlogu de cunfirma in antis de cumpartzire setting_default_language: Idioma de publicatzione setting_default_sensitive: Marca semper is elementos multimediales comente sensìbiles - setting_delete_modal: Ammustra unu diàlogu de cunfirma in antis de cantzellare unu tut setting_disable_swiping: Disativa animatziones setting_display_media: Visualizatzione de is elementos multimediales setting_display_media_default: Predefinida diff --git a/config/locales/simple_form.sco.yml b/config/locales/simple_form.sco.yml index 1a08061dcc14f8..6b800525c7fd43 100644 --- a/config/locales/simple_form.sco.yml +++ b/config/locales/simple_form.sco.yml @@ -58,12 +58,10 @@ sco: featured_tag: name: 'Here some o the hashtags ye uised the maist o late:' filters: - action: Pick whit action tae dae whan a post matches the filter actions: hide: Totally plank the filtert content, ackin as if it didnae exist warn: Plank the filtert content ahin a warnin menshiein the filter's title form_admin_settings: - bootstrap_timeline_accounts: Thir accoonts wull get preenit tae the tap o new uisers' follae recommendations. closed_registrations_message: Displayit whan sign-ups is shut custom_css: Ye kin pit custom styles on tae the web version o Mastodon. mascot: Owerrides the illustration in the advanced web interface. @@ -77,7 +75,6 @@ sco: site_title: Hou fowk kin refer tae yer server asides its domain nemm. theme: Theme thit logged oot visitors an new uisers see. thumbnail: A rochly 2:1 image displayit alangsides yer server information. - timeline_preview: Logged oot visitors wull be able tae broose the maist recent public posts available on the server. trendable_by_default: Jouk the review bi haun o trendin content. Individual items kin stull get taen aff trends efter the fact. trends: Trends shaw whit posts, hashtags an news stories is gettin traction on yer server. form_challenge: @@ -174,10 +171,8 @@ sco: setting_aggregate_reblogs: Group heezes in timelines setting_always_send_emails: Aye sen email notifications setting_auto_play_gif: Auto-pley animatit GIFs - setting_boost_modal: Shaw confirmation dialog afore heezin setting_default_language: Postin leid setting_default_sensitive: Aye mairk media as sensitive - setting_delete_modal: Shaw confirmation dialog afore deletin a post setting_disable_swiping: Turn aff swipin motions setting_display_media: Media display setting_display_media_default: Defaut @@ -227,7 +222,6 @@ sco: site_title: Server nemm theme: Defaut theme thumbnail: Server thoomnail - timeline_preview: Alloo unauthenticated access tae public timelines trendable_by_default: Alloo trends athoot prior review trends: Turn on trends interactions: diff --git a/config/locales/simple_form.si.yml b/config/locales/simple_form.si.yml index 8aac332bf22f43..a81524f182a053 100644 --- a/config/locales/simple_form.si.yml +++ b/config/locales/simple_form.si.yml @@ -73,7 +73,6 @@ si: featured_tag: name: 'ඔබ මෑතකදී භාවිතා කළ හැෂ් ටැග් කිහිපයක් මෙන්න:' filters: - action: ලිපියක් පෙරහනට ගැළපෙන විට ඉටු විය යුතු ක්‍රියාමාර්ගය තෝරන්න actions: blur: පෙළම සඟවා නොගෙන, අනතුරු ඇඟවීමක් පිටුපස මාධ්‍ය සඟවන්න. hide: පෙරහන් කළ අන්තර්ගතය සම්පූර්ණයෙන්ම සඟවන්න, එය නොපවතින ලෙස හැසිරෙන්න @@ -82,7 +81,6 @@ si: activity_api_enabled: සතිපතා බාල්දිවල දේශීයව ප්‍රකාශයට පත් කරන ලද සටහන්, ක්‍රියාකාරී පරිශීලකයින් සහ නව ලියාපදිංචි කිරීම් ගණන app_icon: WEBP, PNG, GIF හෝ JPG. අභිරුචි නිරූපකයක් සමඟ ජංගම උපාංගවල පෙරනිමි යෙදුම් නිරූපකය අභිබවා යයි. backups_retention_period: පරිශීලකයින්ට පසුව බාගත කිරීම සඳහා ඔවුන්ගේ සටහන් වල ලේඛනාගාර ජනනය කිරීමේ හැකියාව ඇත. ධනාත්මක අගයකට සකසා ඇති විට, නිශ්චිත දින ගණනකට පසු මෙම ලේඛනාගාර ඔබගේ ගබඩාවෙන් ස්වයංක්‍රීයව මකා දැමෙනු ඇත. - bootstrap_timeline_accounts: මෙම ගිණුම් නව පරිශීලකයින්ගේ අනුගමනය කිරීමේ නිර්දේශවල ඉහළට අමුණා ඇත. closed_registrations_message: ලියාපදිංචි කිරීම් වසා දැමූ විට පෙන්වනු ලැබේ content_cache_retention_period: අනෙකුත් සේවාදායකයන්ගෙන් ලැබෙන සියලුම පළ කිරීම් (බූස්ට් සහ පිළිතුරු ඇතුළුව) නිශ්චිත දින ගණනකට පසු මකා දැමෙනු ඇත, එම පළ කිරීම් සමඟ කිසිදු දේශීය පරිශීලක අන්තර්ක්‍රියාවක් නොසලකා. මෙයට දේශීය පරිශීලකයෙකු එය පිටු සලකුණු හෝ ප්‍රියතමයන් ලෙස සලකුණු කර ඇති පළ කිරීම් ඇතුළත් වේ. විවිධ අවස්ථා වලින් පරිශීලකයින් අතර පුද්ගලික සඳහන් කිරීම් ද නැති වී යන අතර ප්‍රතිසාධනය කිරීමට නොහැකි වනු ඇත. මෙම සැකසුම භාවිතා කිරීම විශේෂ අරමුණු අවස්ථා සඳහා අදහස් කරන අතර පොදු අරමුණු භාවිතය සඳහා ක්‍රියාත්මක කළ විට බොහෝ පරිශීලක අපේක්ෂාවන් බිඳ දමයි. custom_css: ඔබට Mastodon හි වෙබ් අනුවාදයේ අභිරුචි මෝස්තර යෙදිය හැකිය. @@ -102,10 +100,8 @@ si: status_page_url: ඇනහිටීමක් අතරතුර මෙම සේවාදායකයේ තත්ත්වය මිනිසුන්ට දැකිය හැකි පිටුවක URL එක theme: අමුත්තන් ලොග් අවුට් කළ සහ නව පරිශීලකයින් දකින තේමාව. thumbnail: ඔබගේ සේවාදායක තොරතුරු සමඟ ආසන්න වශයෙන් 2:1 රූපයක් දර්ශනය වේ. - timeline_preview: ලොග් අවුට් වූ අමුත්තන්ට සේවාදායකයේ ඇති නවතම පොදු සටහන් බ්‍රවුස් කිරීමට හැකි වනු ඇත. trendable_by_default: ප්‍රවණතා අන්තර්ගතයන් අතින් සමාලෝචනය කිරීම මඟ හරින්න. කාරණයෙන් පසුවත් තනි අයිතම ප්‍රවණතා වලින් ඉවත් කළ හැකිය. trends: ප්‍රවණතා මඟින් ඔබේ සේවාදායකයේ ආකර්ෂණය ලබා ගන්නා පළ කිරීම්, හැෂ් ටැග් සහ ප්‍රවෘත්ති කථා පෙන්වයි. - trends_as_landing_page: මෙම සේවාදායකයේ විස්තරයක් වෙනුවට පිටව ගිය පරිශීලකයින්ට සහ අමුත්තන්ට ප්‍රවණතා අන්තර්ගතය පෙන්වන්න. ප්‍රවණතා සක්‍රීය කිරීම අවශ්‍ය වේ. form_challenge: current_password: ඔබ ආරක්ෂිත ප්‍රදේශයකට ඇතුල් වේ imports: @@ -225,11 +221,9 @@ si: setting_aggregate_reblogs: කාලරේඛා වල කණ්ඩායම් බූස්ට් setting_always_send_emails: සෑම විටම විද්‍යුත් තැපැල් දැනුම්දීම් යවන්න setting_auto_play_gif: සජීවිකරණ GIF ස්වයංක්‍රීයව ධාවනය කරන්න - setting_boost_modal: වැඩි කිරීමට පෙර තහවුරු කිරීමේ සංවාදය පෙන්වන්න setting_default_language: ලිපිවල භාෂාව setting_default_quote_policy: උපුටා දැක්විය හැක්කේ කාටද? setting_default_sensitive: සෑමවිට මාධ්‍ය සංවේදී බව සලකුණු කරන්න - setting_delete_modal: ලිපියක් මැකීමට පෙර ඒ ගැන විමසන්න setting_disable_hover_cards: හොවර් කිරීමේදී පැතිකඩ පෙරදසුන අබල කරන්න setting_disable_swiping: ස්වයිප් කිරීමේ චලන අබල කරන්න setting_display_media: මාධ්ය සංදර්ශකය @@ -238,7 +232,6 @@ si: setting_display_media_show_all: සියල්ල පෙන්වන්න setting_expand_spoilers: අන්තර්ගත අවවාද සහිත ලිපි සැමවිට දිගහරින්න setting_hide_network: ඔබගේ ජාලය සඟවන්න - setting_missing_alt_text_modal: විකල්ප පෙළ නොමැතිව මාධ්‍ය පළ කිරීමට පෙර තහවුරු කිරීමේ සංවාදය පෙන්වන්න. setting_reduce_motion: සජීවිකරණවල චලනය අඩු කරන්න setting_system_font_ui: පද්ධතියේ පෙරනිමි රුවකුරු භාවිතා කරන්න setting_system_scrollbars_ui: පද්ධතියේ පෙරනිමි අනුචලන තීරුව භාවිතා කරන්න @@ -290,10 +283,8 @@ si: status_page_url: තත්ව පිටු URL එක theme: පෙරනිමි තේමාව thumbnail: සේවාදායක සිඟිති රුව - timeline_preview: පොදු කාලරේඛා වෙත සත්‍යාපනය නොකළ ප්‍රවේශයට ඉඩ දෙන්න. trendable_by_default: පූර්ව සමාලෝචනයකින් තොරව ප්‍රවණතා වලට ඉඩ දෙන්න. trends: ප්‍රවණතා සක්‍රීය කරන්න - trends_as_landing_page: ගොඩබෑමේ පිටුව ලෙස ප්‍රවණතා භාවිතා කරන්න interactions: must_be_follower: අනුගාමිකයින් නොවන අයගෙන් ලැබෙන දැනුම්දීම් අවහිර කරන්න must_be_following: ඔබ අනුගමනය නොකරන පුද්ගලයින්ගෙන් ලැබෙන දැනුම්දීම් අවහිර කරන්න @@ -353,9 +344,7 @@ si: jurisdiction: නීතිමය අධිකරණ බලය min_age: අවම වයස user: - date_of_birth_1i: දහවල date_of_birth_2i: මාසය - date_of_birth_3i: වර්ෂය role: භූමිකාව time_zone: වේලා කලාපය user_role: diff --git a/config/locales/simple_form.sk.yml b/config/locales/simple_form.sk.yml index b6896c73e18a61..f4d2b8f6f8c668 100644 --- a/config/locales/simple_form.sk.yml +++ b/config/locales/simple_form.sk.yml @@ -2,12 +2,21 @@ sk: simple_form: hints: + account: + attribution_domains: Jeden na riadok. Chráni vás pred falošným pripisovaním autorstva. + discoverable: Vaše verejné príspevky a profil môžu byť zobrazované a odporúčané v rôznych častiach Mastodonu a váš profil môže byť navrhovaný ostatným. + display_name: Vaše meno a priezvisko alebo prezývka. + fields: Váš web, zámená, vek, čokoľvek, čo chcete o sebe uviesť. + indexable: Vaše verejné príspevky sa môžu zobrazovať vo výsledkoch vyhľadávania na Mastodone. Ľudia, ktorí s nimi interagovali, ich môžu nájsť vždy. + note: 'Môžete @označiť iných ľudí alebo #hashtagy.' + show_collections: Ľudia si budú môcť prezrieť, ktoré účty sledujete a ktoré účty sledujú vás. Ľudia, ktorých sledujete, o tom budú vždy vedieť. + unlocked: Ľudia vás budú môcť sledovať bez vášho schválenia. Zrušenie vám dá možnosť kontrolovať žiadosti o sledovanie a rozhodovať sa o prijímaní alebo zamietaní nových sledujúcich. account_alias: acct: Urči prezyvku@domenu účtu, s ktorého sa chceš presunúť account_migration: acct: Urči prezyvku@domenu účtu, na ktorý sa chceš presunúť account_warning_preset: - text: Môžeš používať rovnakú syntaxiu ako v rámci príspevkov, čiže URL, haštagy, a spomenutia + text: Môžete používať syntax príspevk, ako sú adresy URL, hashtagy a označenia admin_account_action: include_statuses: Užívateľ uvidí, ktoré príspevky majú za následok moderačný zásah, alebo upozornenie send_email_notification: Užívateľ dostane vysvetlenie ohľadom toho, čo sa stalo s ich účtom @@ -17,29 +26,37 @@ sk: defaults: autofollow: Ľudia ktorí sa zaregistrujú prostredníctvom pozvánky, ťa budú inheď nasledovať avatar: WEBP, PNG, GIF, alebo JPG. Najviac %{size}. Bude zmenšený na %{dimensions}px - bot: Tento účet vykonáva hlavne automatizované akcie, a je pravdepodobne nespravovaný + bot: Tento účet vykonáva hlavne automatizované akcie a je pravdepodobne nespravovaný context: Jedno, alebo viac kritérií, v ktorých má byť filtrovanie uplatnené current_password: Z bezpečtnostných dôvodov prosím zadaj heslo súčasného účtu current_username: Pre potvrdenie prosím zadaj prezývku súčasného účtu digest: Odoslané iba v prípade dlhodobej neprítomnosti, a len ak si obdržal/a nejaké osobné správy kým si bol/a preč - email: Bude ti odoslaný potvrdzujúci email + email: Odošleme vám potvrdzujúci e-mail header: WEBP, PNG, GIF, alebo JPG. Najviac %{size}. Bude zmenšený na %{dimensions}px inbox_url: Skopíruj adresu z hlavnej stránky mostíka, ktorý chceš používať - irreversible: Vytriedené príspevky zmiznú nenávratne, aj keď triedenie neskôr zrušíš - locale: Jazyk užívateľského rozhrania, emailových, a nástenkových oboznámení - password: Zadaj aspoň osem znakov + irreversible: Filtrované príspevky nenávratne zmiznú, aj keď filter neskôr zrušíte + locale: Jazyk používateľského rozhrania, e-mailových a push upozornení + password: Heslo musí obsahovať aspoň 8 znakov phrase: Zhoda sa nájde nezávisle od toho, či je text napísaný, veľkými, alebo malými písmenami, či už v tele, alebo v hlavičke scopes: Ktoré API budú povolené aplikácii pre prístup. Ak vyberieš vrcholný stupeň, nemusíš už potom vyberať po jednom. - setting_aggregate_reblogs: Nezobrazuj nové vyzdvihnutia pre príspevky, ktoré už boli len nedávno povýšené (týka sa iba nanovo získaných povýšení) - setting_default_sensitive: Chúlostivé médiá sú štandardne ukryté, a môžu byť zobrazené kliknutím - setting_display_media_default: Ukry médiá označené ako chúlostivé - setting_display_media_hide_all: Vždy ukry všetky médiá - setting_display_media_show_all: Vždy zobrazuj médiá - setting_use_blurhash: Prechody sú založené na farbách skrytých vizuálov, ale zahaľujú akékoľvek podrobnosti - setting_use_pending_items: Skry aktualizovanie časovej osi tak, aby bola načitávaná iba po kliknutí, namiesto samostatného posúvania + setting_aggregate_reblogs: Nezobrazovať nové zdieľania pre nedávno zdieľané príspevky (týka sa iba nových zdieľaní) + setting_always_send_emails: Pri bežnom používaní Mastodonu nebudete dostávať e-mailové upozornenia + setting_default_sensitive: Citlivé médiá sú predvolene ukryté a môžu byť zobrazené kliknutím + setting_display_media_default: Skrývať médiá označené ako citlivé + setting_display_media_hide_all: Vždy skrývať médiá + setting_display_media_show_all: Vždy zobrazovať médiá + setting_system_scrollbars_ui: Platí len pre počítačové prehliadače využívajúce technológiu Chrome alebo Safari + setting_use_blurhash: Prechody sú založené na farbách skrytých vizuálov, ale skrývajú akékoľvek podrobnosti + setting_use_pending_items: Časová os bude aktualizovaná až po kliknutí, feed sa nebúde posúvať automaticky whole_word: Ak je kľúčové slovo, alebo fráza poskladaná iba s písmen a čísel, bude použité iba ak sa zhoduje s celým výrazom domain_allow: domain: Táto doména bude schopná získavať dáta z tohto servera, a prichádzajúce dáta ním budú spracovávané a uložené + featured_tag: + name: 'Nedávno ste použili napríklad tieto hashtagy:' + form_admin_settings: + site_contact_email: Spôsob, ako vás môžu ľudia kontaktovať pre právne účely alebo podporu. + site_contact_username: Ako vás môžu ľudia na Mastodone nájsť. + thumbnail: 'Obrázok v pomere približne 2 : 1, ktorý sa zobrazí pri informáciách o serveri.' form_challenge: current_password: Vstupuješ do zabezpečenej časti imports: @@ -51,16 +68,23 @@ sk: sign_up_block: Nové registrácie nebudú možné sessions: otp: 'Napíš sem dvoj-faktorový kód z telefónu, alebo použi jeden z tvojích obnovovacích kódov:' + settings: + indexable: Váš profil sa môže zobraziť vo výsledkoch vyhľadávania cez Google, Bing a ďalšie. + show_application: Tebe sa táto informácia zobrazí vždy. tag: name: Zmeniť môžeš iba veľkosť písmen, napríklad aby boli ľahšie čítateľné user: - chosen_languages: Keď je zaškrtnuté, vo verejnej osi budú zobrazené iba príspevky vo vybraných jazykoch + chosen_languages: Po zaškrtnutí budú na verejných časových osiach zobrazované iba príspevky vo vybraných jazykoch labels: account: + attribution_domains: Weby, ktoré vám môžu pripisovať autorstvo + discoverable: Zobrazovať profil a príspevky v objavovacích algoritmoch fields: - name: Označenie - value: Obsah - unlocked: Automaticky prijímaj nových nasledovateľov + name: Definícia + value: Hodnota + indexable: Zobrazovať verejné príspevky vo vyhľadávaní + show_collections: Zobrazovať v profile sledované a sledujúce účty + unlocked: Automaticky schvaľovať nové sledovania account_alias: acct: Adresa starého účtu account_migration: @@ -77,7 +101,7 @@ sk: disable: Deaktivuj none: Neurob nič silence: Utíš - suspend: Vylúč a nenávratne vymaž dáta na účte + suspend: Vylúčiť warning_preset_id: Použi varovnú predlohu announcement: all_day: Celodenná udalosť @@ -86,19 +110,19 @@ sk: text: Oboznámenie defaults: autofollow: Pozvi k následovaniu tvojho profilu - avatar: Maskot + avatar: Profilová fotka bot: Toto je automatizovaný bot účet - chosen_languages: Filtruj jazyky - confirm_new_password: Znovu tvoje nové heslo, pre potvrdenie + chosen_languages: Filtrovanie jazykov + confirm_new_password: Overenie nového hesla confirm_password: Over heslo - context: Triedenie kontextov + context: Kontexty filtra current_password: Súčasné heslo data: Dáta display_name: Zobrazované meno - email: Emailová adresa + email: E-mailová adresa expires_in: Expiruj po fields: Metadáta profilu - header: Obrázok v hlavičke + header: Obrázok v záhlaví inbox_url: URL adresa prechodnej schránky irreversible: Zahoď, namiesto ukrytia locale: Jazyk rozhrania @@ -108,25 +132,28 @@ sk: otp_attempt: Dvoj-faktorový overovací kód password: Heslo phrase: Kľúčové slovo, alebo fráza - setting_advanced_layout: Zapni pokročilé užívateľské rozhranie - setting_aggregate_reblogs: Zoskupuj vyzdvihnutia v časovej osi - setting_auto_play_gif: Automaticky prehrávaj animované GIFy - setting_boost_modal: Zobrazuj potvrdzovacie okno pred povýšením - setting_default_language: Píšeš v jazyku - setting_default_sensitive: Označ všetky mediálne súbory ako chúlostivé - setting_delete_modal: Zobrazuj potvrdzovacie okno pred vymazaním toot-u + setting_advanced_layout: Zapnúť pokročilé používateľské rozhranie + setting_aggregate_reblogs: Zoskupovať zdieľania na časových osiach + setting_always_send_emails: Vždy posielať e-mailové upozornenia + setting_auto_play_gif: Automaticky prehrávať animácie GIF + setting_default_language: Jazyk príspevkov + setting_default_quote_policy: Kto vás môže citovať + setting_default_sensitive: Vždy označovať médiá ako citlivé + setting_disable_hover_cards: Vypnúť náhľady profilov pri ukázaní kurzorom + setting_disable_swiping: Vypnúť gestá posúvaním setting_display_media: Zobrazovanie médií setting_display_media_default: Štandard setting_display_media_hide_all: Ukry všetky setting_display_media_show_all: Ukáž všetky - setting_expand_spoilers: Stále rozbaľ príspevky označené varovaním o chúlostivom obsahu + setting_expand_spoilers: Stále rozbaľovať príspevky označené varovaním o obsahu setting_hide_network: Ukry svoju sieť kontaktov - setting_reduce_motion: Mierni pohyb pri animáciách - setting_system_font_ui: Použi základné systémové písmo + setting_reduce_motion: Stlmiť animácie + setting_system_font_ui: Používať predvolené písmo systému + setting_system_scrollbars_ui: Používať predvolený posuvník systému setting_theme: Vzhľad webu - setting_trends: Ukáž dnešné trendy + setting_trends: Zobrazovať dnešné trendy setting_unfollow_modal: Vyžaduj potvrdenie pred tým, než niekoho prestaneš sledovať - setting_use_blurhash: Ukáž farebné prechody pre skryté médiá + setting_use_blurhash: Zobrazovať farebné prechody skrytých médií setting_use_pending_items: Pomalý režim severity: Závažnosť sign_in_token_attempt: Bezpečnostný kód @@ -135,10 +162,11 @@ sk: username_or_email: Prezývka, alebo email whole_word: Celé slovo featured_tag: - name: Haštag + name: Hashtag form_admin_settings: peers_api_enabled: Zverejni zoznam objavených serverov v API status_page_url: URL adresa stránky stavu + thumbnail: Náhľad servera interactions: must_be_follower: Blokuj oboznámenia od užívateľov, ktorí ma nenasledujú must_be_following: Blokuj oboznámenia od ľudí, ktorých nesledujem @@ -153,17 +181,32 @@ sk: sign_up_requires_approval: Obmedz registrácie severity: Pravidlo notification_emails: + appeal: Niekto sa odvoláva voči moderátorskému rozhodnutiu digest: Zasielať súhrnné emaily - favourite: Zaslať email, ak si niekto obľúbi tvoj príspevok - follow: Niekto ťa začal nasledovať - follow_request: Zaslať email, ak ti niekto pošle žiadosť o sledovanie - mention: Zaslať email, ak ťa niekto spomenie vo svojom príspevku - pending_account: Zaslať email, ak treba prehodnotiť nový účet - reblog: Zaslať email, ak niekto re-tootne tvoj príspevok + favourite: Niekto ohviezdičkuje váš príspevok + follow: Niekto vás začal sledovať + follow_request: Niekto vám posiela žiadosť o sledovanie + mention: Niekto vás označí + pending_account: Je potrebná kontrola nového účtu + quote: Niekto vás cituje + reblog: Niekto zdieľa váš príspevok + report: Niekto niečo nahlásil + software_updates: + all: Upozorňovať na všetky novinky + critical: Upozorňovať iba na kritické novinky + label: Je dostupná nová verzia Mastodonu + none: Nikdy neupozorňovať na novinky (neodporúčané) + patch: Upozorňovať na novinky s riešením chýb + trending_tag: Je potrebné skontrolovať nový trend + settings: + indexable: Zobrazovať profil v internetových vyhľadávačoch + show_application: Zobrazovať apku, z ktorej sú uverejňované príspevky tag: - listable: Povoľ zobrazovanie tohto haštagu v návrhoch vyhľadávaní - name: Haštag - trendable: Povoľ zobrazovanie tohto haštagu medzi trendujúcimi + listable: Povoliť zobrazovanie tohto hashtagu v návrhoch vyhľadávaní + name: Hashtag + trendable: Povoliť zobrazovanie tohto hashtagu medzi trendujúcimi + user: + time_zone: Časové pásmo 'no': Nie recommended: Odporúčané required: diff --git a/config/locales/simple_form.sl.yml b/config/locales/simple_form.sl.yml index 9a2850119cd6df..e87dd799d2aa91 100644 --- a/config/locales/simple_form.sl.yml +++ b/config/locales/simple_form.sl.yml @@ -73,7 +73,6 @@ sl: featured_tag: name: 'Tukaj je nekaj ključnikov, ki ste jih nedavno uporabili:' filters: - action: Izberite, kako naj se program vede, ko se objava sklada s filtrom actions: hide: Povsem skrij filtrirano vsebino, kot da ne bi obstajala warn: Skrij filtrirano vsebino za opozorilom, ki pomenja naslov filtra @@ -81,7 +80,6 @@ sl: activity_api_enabled: Številke krajevno objavljenih objav, dejavnih uporabnikov in novih registracij na tedenskih seznamih app_icon: WEBP, PNG, GIF ali JPG. Zamenja privzeto ikono programa na mobilnih napravah z ikono po meri. backups_retention_period: Uporabniki lahko ustvarijo arhive svojih objav za kasnejši prenos k sebi. Ko je nastavljeno na pozitivno vrednost, bodo ti arhivi po nastavljenem številu dni samodejno izbrisani. - bootstrap_timeline_accounts: Ti računi bodo pripeti na vrh priporočenih sledenj za nove uporabnike. closed_registrations_message: Prikazano, ko so registracije zaprte content_cache_retention_period: Vse objave z drugih strežnikov (vključno z izpostavitvami in odgovori) bodo izbrisani po nastavljenem številu dni, ne glede na krajevne interakcije s temi objavami. To vključuje objave, ki jih je krajevni uporabnik dodal med zaznamke ali priljubljene. Zasebne omembe med uporabniki na različnih strežnikih bodo prav tako izgubljene in jih ne bo moč obnoviti. Uporaba te nastavitve je namenjena strežnikom s posebnim namenom in nasprotuje mnogim pričakovanjem uporabnikov na strežnikih za splošni namen. custom_css: Spletni različici Mastodona lahko uveljavite sloge po meri. @@ -101,10 +99,8 @@ sl: status_page_url: URL strani, kjer je moč videti stanje tega strežnika med prekinjenim delovanjem theme: Tema, ki jo vidijo odjavljeni obiskovalci in novi uporabniki. thumbnail: Slika v razmerju stranic približno 2:1, prikazana vzdolž podatkov o vašem strežniku. - timeline_preview: Odjavljeni obiskovalci bodo lahko brskali po najnovejših javnih objavah, ki so na voljo na strežniku. trendable_by_default: Preskočite ročni pregled vsebine v trendu. Posamezne elemente še vedno lahko odstranite iz trenda post festum. trends: Trendi prikažejo, katere objave, ključniki in novice privlačijo zanimanje na vašem strežniku. - trends_as_landing_page: Odjavljenim uporabnikom in obiskovalcem namesto opisa tega strežnika pokažite vsebine v trendu. Trendi morajo biti omogočeni. form_challenge: current_password: Vstopate v varovano območje imports: @@ -223,10 +219,12 @@ sl: setting_aggregate_reblogs: Skupinske izpostavitve na časovnicah setting_always_send_emails: Vedno pošlji e-obvestila setting_auto_play_gif: Samodejno predvajanje animiranih GIF-ov - setting_boost_modal: Pred izpostavljanjem pokaži potrditveno okno + setting_boost_modal: Nadziraj vidnost objav setting_default_language: Jezik objavljanja + setting_default_privacy: Vidnost objav + setting_default_quote_policy: Kdo lahko citira setting_default_sensitive: Vedno označi medije kot občutljive - setting_delete_modal: Pred brisanjem objave prikaži okno za pritrditev + setting_delete_modal: Pred brisanjem objave me opozori setting_disable_hover_cards: Onemogoči predogled profila pod kazalcem setting_disable_swiping: Onemogoči poteze drsanja setting_display_media: Prikaz medijev @@ -235,7 +233,6 @@ sl: setting_display_media_show_all: Prikaži vse setting_expand_spoilers: Vedno razširi objave, označene z opozorili o vsebini setting_hide_network: Skrij svoje omrežje - setting_missing_alt_text_modal: Pred objavo predstavnosti brez nadomestnega besedila pokaži potrditveno okno setting_reduce_motion: Zmanjšanje premikanja v animacijah setting_system_font_ui: Uporabi privzeto pisavo sistema setting_system_scrollbars_ui: Uporabi privzeti drsni trak sistema @@ -286,10 +283,8 @@ sl: status_page_url: URL strani stanja theme: Privzeta tema thumbnail: Sličica strežnika - timeline_preview: Omogoči neoverjen dostop do javnih časovnic trendable_by_default: Dovoli trende brez predhodnega pregleda trends: Omogoči trende - trends_as_landing_page: Uporabi trende za pristopno stran interactions: must_be_follower: Blokiraj obvestila nesledilcev must_be_following: Blokiraj obvestila oseb, ki jim ne sledite @@ -314,6 +309,7 @@ sl: follow_request: Pošlji e-pošto, ko vam nekdo želi slediti mention: Pošlji e-pošto, ko vas nekdo omeni pending_account: Pošlji e-pošto, ko je potreben pregled novega računa + quote: Nekdo vas je citiral reblog: Pošlji e-sporočilo, ko nekdo izpostavi vašo objavo report: Novo poročilo je oddano software_updates: @@ -349,9 +345,9 @@ sl: jurisdiction: Pravna pristojnost min_age: Najmanjša starost user: - date_of_birth_1i: Dan + date_of_birth_1i: Leto date_of_birth_2i: Mesec - date_of_birth_3i: Leto + date_of_birth_3i: Dan role: Vloga time_zone: Časovni pas user_role: @@ -360,6 +356,9 @@ sl: name: Ime permissions_as_keys: Pravice position: Prioriteta + username_block: + allow_with_approval: Dovoli registracije z odobritvijo + comparison: Metoda primerjave webhook: events: Omogočeni dogodki template: Predloga obremenitev diff --git a/config/locales/simple_form.sq.yml b/config/locales/simple_form.sq.yml index ad26b87a5aab36..acdb944ea3987c 100644 --- a/config/locales/simple_form.sq.yml +++ b/config/locales/simple_form.sq.yml @@ -54,12 +54,17 @@ sq: password: Përdorni të paktën 8 shenja phrase: Do të kërkohet përputhje pavarësish se teksti ose sinjalizimi mbi lëndën e një mesazhi është shkruar me të mëdha apo me të vogla scopes: Cilat API do të lejohen të përdorin aplikacioni. Nëse përzgjidhni një shkallë të epërme, nuk ju duhet të përzgjidhni individualet një nga një. + setting_advanced_layout: Shfaqe Mastodon-in si një skemë me shumë shtylla, duke ju lejuar të shihni rrjedhën kohore, njoftimet dhe një shtyllë të tretë zgjedhur prej jush. Nuk rekomandohet për ekrane të vegjël. setting_aggregate_reblogs: Mos shfaq përforcime të reja për mesazhe që janë përforcuar tani së fundi (prek vetëm përforcime të marra rishtas) setting_always_send_emails: Normalisht s’do të dërgohen njoftime, kur përdorni aktivisht Mastodon-in + setting_boost_modal: Kur është e aktivizuar, përforcimi së pari do të hapë një dialog ripohimi në të cilin mund të ndryshoni dukshmërinë e përforcimit tuaj. + setting_default_quote_policy_private: Në Mastodon s’mund të citohen nga të tjerë postim Vetëm-për-ndjekësit. + setting_default_quote_policy_unlisted: Kur njerëzit ju citojnë, nga rrjedha kohore e gjërave në modë do të kalohen si të fshehura edhe postimet e tyre. setting_default_sensitive: Media rezervat fshihet, si parazgjedhje, dhe mund të shfaqet me një klikim setting_display_media_default: Fshih media me shenjën rezervat setting_display_media_hide_all: Fshih përherë mediat setting_display_media_show_all: Mediat shfaqi përherë + setting_quick_boosting_html: Kur aktivizohet, klikimi mbi ikonën e Përforcimeve %{boost_icon} do të bëjë menjëherë përforcimin, në vend se të hapet menuja hapmbyll e përforcimeve/citimeve. E rikalon veprimin e citimit te menuja %{options_icon} (Mundësi). setting_system_scrollbars_ui: Ka vend vetëm për shfletues desktop bazuar në Safari dhe Chrome setting_use_blurhash: Gradientët bazohen në ngjyrat e elementëve pamorë të fshehur, por errësojnë çfarëdo hollësie setting_use_pending_items: Fshihi përditësimet e rrjedhës kohore pas një klikimi, në vend të rrëshqitjes automatike nëpër prurje @@ -73,7 +78,7 @@ sq: featured_tag: name: 'Ja disa nga hashtag-ët që përdorët tani afër:' filters: - action: Zgjidhni cili veprim të kryhet, kur një postim ka përputhje me një filtër + action: Zgjidhni cili veprim të kryhet, kur një postim ka përkim me një filtër actions: blur: Fshihe median pas një sinjalizimi, pa fshehur vetë tekstin hide: Fshihe plotësisht lëndën e filtruar, duke u sjellë sikur të mos ekzistonte @@ -82,11 +87,12 @@ sq: activity_api_enabled: Numër postimesh të botuar lokalisht, përdoruesish aktiv dhe regjistrimesh të reja sipas matjesh javore app_icon: WEBP, PNG, GIF, ose JPG. Anashkalon ikonë parazgjedhje aplikacioni në pajisje celulare me një ikonë vetjake. backups_retention_period: Përdorues kanë aftësinë të prodhojnë arkiva të postimeve të tyre për t’i shkarkuar më vonë. Kur i jepet një vlerë pozitive, këto arkiva do të fshihen automatikisht prej depozitës tuaj pas numrit të dhënë të ditëve. - bootstrap_timeline_accounts: Këto llogari do të fiksohen në krye të rekomandimeve për ndjekje nga përdorues të rinj. + bootstrap_timeline_accounts: Këto llogari do të fiksohen në krye të rekomandimeve për ndjekje për përdoruesin e ri. Jepni një listë llogarish ndarë me presje. closed_registrations_message: Shfaqur kur mbyllen dritare regjistrimesh content_cache_retention_period: Krejt postimet prej shërbyesve të tjerë (përfshi përforcime dhe përgjigje) do të fshihen pas numrit të caktuar të ditëve, pa marrë parasysh çfarëdo ndërveprimi përdoruesi me këto postime. Kjo përfshin postime kur një përdorues vendor u ka vënë shenjë si faqerojtës, ose të parapëlqyer. Do të humbin gjithashtu dhe përmendje private mes përdoruesish nga instanca të ndryshme dhe s’do të jetë e mundshme të rikthehen. Përdorimi i këtij rregullimi është menduar për instanca me qëllim të caktuar dhe ndërhyn në çka presin mjaft përdorues, kur sendërtohet për përdorim të përgjithshëm. custom_css: Stile vetjakë mund të aplikoni në versionin web të Mastodon-it. favicon: WEBP, PNG, GIF, ose JPG. Anashkalon favikonën parazgjedhje Mastodon me një ikonë vetjake. + landing_page: Përzgjedh cilën faqe shohin vizitorët e rinj, kur vijnë për herë të parë në shërbyesin tuaj. Nëse përzgjidhni “Në modë”, atëherë “në modë” duhet aktivizuar te Rregullime për Zbulime. Nëse përzgjidhni “Prurje vendore”, atëherë “Hyrje te prurje vendore që përmbajnë postime vendore” duhet vënë si “Gjithkush”, te Rregullime për Zbulime. mascot: Anashkalon ilustrimin te ndërfaqja web e thelluar. media_cache_retention_period: Kartela media nga postime të bëra nga përdorues të largët ruhen në një fshehtinë në shërbyesin tuaj. Kur i jepet një vlerë pozitive, media do të fshihet pas numrit të dhënë të ditëve. Nëse të dhënat e medias duhen pas fshirjes, do të rishkarkohen, nëse lënda burim mund të kihet ende. Për shkak kufizimesh mbi sa shpesh skeda paraparjesh lidhjesh ndërveprojnë me sajte palësh të treta, rekomandohet të vihet kjo vlerë të paktën 14 ditë, ose skedat e paraparjes së lidhje s’do të përditësohen duke e kërkuar para asaj kohe. min_age: Përdoruesve do t’ju kërkohet gjatë regjistrimit të ripohojnë datën e lindjes @@ -102,10 +108,9 @@ sq: status_page_url: URL e faqe ku njerëzit mund të shohin gjendjen e këtij shërbyesi, gjatë një ndërprerje të funksionimit theme: Temë që shohin vizitorët që kanë bërë daljen dhe përdorues të rinj. thumbnail: Një figurë afërsisht 2:1 e shfaqur tok me hollësi mbi shërbyesin tuaj. - timeline_preview: Vizitorët që kanë bërë daljen do të jenë në gjendje të shfletojnë postimet më të freskëta publike të passhme në shërbyes. trendable_by_default: Anashkalo shqyrtim dorazi lënde në modë. Gjëra individuale prapë mund të hiqen nga lëndë në modë pas publikimi. trends: Gjërat në modë shfaqin cilat postime, hashtagë dhe histori të reja po tërheqin vëmendjen në shërbyesin tuaj. - trends_as_landing_page: Shfaq lëndë në modë për përdorues jo të futur në llogari dhe për vizitorë, në vend se të një përshkrimi të këtij shërbyesi. Lyp që të jenë të aktivizuara gjërat në modë. + wrapstodon: Jepuni përdoruesve vendorë mundësinë të prodhojnë një përmbledhje lojcake të përdorimit të Mastodon-it prej tyre përgjatë vitit. Kjo veçori është e përdorshme mes 10 dhe 31 dhjetorit të çdo viti dhe u ofrohet përdoruesve që kanë për të paktën një postim Publik, ose Publik të Heshtur dhe që kanë përdorur të paktën një hashtag brenda vitit. form_challenge: current_password: Po hyni në një zonë të sigurt imports: @@ -141,12 +146,16 @@ sq: arbitration_address: Mund të jetë e njëjtë me adresën Fizike më sipër, ose “N/A”, nëse përdoret email. arbitration_website: Mund të jetë një formular web, ose “N/A”, nëse përdoret email. choice_of_law: Qytet, rajon, territor ose shtet, ligjet e brendshme të të cilit do të administrojnë çfarëdo dhe tërë pretendimet. + dmca_address: Për operatorë në ShBA, përdorni adresën e regjistruar te DMCA Designated Agent Directory. Regjistrimi me A P.O. Box bëhet me kërkesë të drejtpërdrejtë, përdorni DMCA Designated Agent Post Office Box Waiver Request që t’i dërgoni email Copyright Office-it dhe t’i përshkruani se jeni një moderator shtëpiak lënde që ka frikë nga hakmarrje apo ndëshkim për veprimet tuaja dhe që ka nevojë të përdor një P.O. Box për heqjen e adresës së shtëpisë tuaj nga sytë e publikut. dmca_email: Mund të jetë i njëjti email i përdorur për “Adresë email për njoftime ligjore” më sipër. domain: Identifikues unik për shërbimin internetor që po ofroni. jurisdiction: Vendosni vendin ku jeton cilido që paguan faturat. Nëse është një shoqëri apo tjetër njësi, vendosni vendin ku është regjistruar, si dhe qytetin, rajonin, territorin apo shtetin përkatës. min_age: S’duhet të jetë nën moshën minimum të domosdoshme nga ligjet në juridiksionin tuaj. user: chosen_languages: Në iu vëntë shenjë, te rrjedha kohore publike do të shfaqen vetëm mesazhe në gjuhët e përzgjedhura + date_of_birth: + one: Na duhet të sigurohemi se jeni të paktën %{count} që të përdorni %{domain}. S’do ta depozitojmë këtë. + other: Na duhet të sigurohemi se jeni të paktën %{count} që të përdorni %{domain}. S’do ta depozitojmë këtë. role: Roli kontrollon cilat leje ka përdoruesi. user_role: color: Ngjyrë për t’u përdorur për rolin nëpër UI, si RGB në format gjashtëmbëdhjetësh @@ -154,6 +163,10 @@ sq: name: Emër publik për rolin, nëse roli është ujdisur të shfaqet si një stemë permissions_as_keys: Përdoruesit me këtë rol do të mund të… position: Role më të lartë vendosin zgjidhje përplasje në disa raste. Disa veprime mund të kryhen vetëm mbi role të një shkalle më të ulët + username_block: + allow_with_approval: Në vend të pengimit aty për aty të regjistrimit, regjistrime me përkim do të duan miratimin tuaj + comparison: Ju lutemi, mbani parasysh Problemin Scunthorpe, kur bllokohen përkime të pjesshme + username: Do të merret si përkim, pavarësisht shkrimit me të mëdha apo të vogla dhe pavarësisht homoglifesh të tilla si "4" për "a", ose "3" për "e" webhook: events: Përzgjidhni akte për dërgim template: Hartoni ngarkesë tuajën JSON, duke përdorur ndërkëmbim ndryshoresh. Lëreni të zbrazët, për JSON-in parazgjedhje. @@ -224,20 +237,23 @@ sq: setting_aggregate_reblogs: Grupoji përforcimet në rrjedha kohore setting_always_send_emails: Dërgo përherë njoftime me email setting_auto_play_gif: Vetëluaji GIF-et e animuar - setting_boost_modal: Shfaq dialog ripohimi përpara përforcimi + setting_boost_modal: Kontrolloni dukshmëri përforcimesh setting_default_language: Gjuhë postimi + setting_default_privacy: Dukshmëri postimi setting_default_quote_policy: Cilët mund të citojnë setting_default_sensitive: Mediave vëru përherë shenjë si rezervat - setting_delete_modal: Shfaq dialog ripohimi përpara fshirjes së një mesazhi + setting_delete_modal: Sinjalizomë, para fshirjes së një postimi setting_disable_hover_cards: Çaktivizo paraparje profili, kur i kalohet kursori përsipër setting_disable_swiping: Çaktivizo lëvizje me fërkim setting_display_media: Shfaqje mediash setting_display_media_default: Parazgjedhje setting_display_media_hide_all: Fshihi krejt setting_display_media_show_all: Shfaqi krejt + setting_emoji_style: Stil emoji-sh setting_expand_spoilers: Mesazhet me sinjalizime mbi lëndën, zgjeroji përherë setting_hide_network: Fshiheni rrjetin tuaj - setting_missing_alt_text_modal: Shfaq dialog ripohimi, para postimi mediash pa tekst alternativ + setting_missing_alt_text_modal: Sinjalizomë, para postimi mediash pa tekst alternativ + setting_quick_boosting: Aktivizo përforcim të shpejtë setting_reduce_motion: Zvogëlo lëvizjen në animacione setting_system_font_ui: Përdor shkronja parazgjedhje të sistemit setting_system_scrollbars_ui: Përdor shtyllë rrëshqitjesh parazgjedhje të sistemit @@ -271,12 +287,17 @@ sq: content_cache_retention_period: Periudhë mbajtjeje lënde të largët custom_css: CSS Vetjake favicon: Favikonë + landing_page: Faqe mbërritje për vizitorë të rinj + local_live_feed_access: Hyrje te prurje të atypëratyshme që përmbajnë postime vendore + local_topic_feed_access: Hyrje te prurje hashtag-ësh dhe lidhjesh që përmbajnë postime vendore mascot: Simbol vetjak (e dikurshme) media_cache_retention_period: Periudhë mbajtjeje lënde media min_age: Domosdosmëri moshe minimum peers_api_enabled: Publiko te API listë shërbyesish të zbuluar profile_directory: Aktivizo drejtori profilesh registrations_mode: Kush mund të regjistrohet + remote_live_feed_access: Hyrje te prurje të atypëratyshme që përmbajnë postime nga larg + remote_topic_feed_access: Hyrje te prurje hashtag-ësh dhe lidhjesh që përmbajnë postime nga larg require_invite_text: Kërko një arsye për pjesëmarrje show_domain_blocks: Shfaq bllokime përkatësish show_domain_blocks_rationale: Shfaq pse janë bllokuar përkatësitë @@ -289,10 +310,9 @@ sq: status_page_url: URL faqeje gjendjesh theme: Temë parazgjedhje thumbnail: Miniaturë shërbyesi - timeline_preview: Lejo hyrje pa mirëfilltësim te rrjedha kohore publike trendable_by_default: Lejoni gjëra në modë pa shqyrtim paraprak trends: Aktivizo gjëra në modë - trends_as_landing_page: Përdor gjërat në modë si faqe hyrëse + wrapstodon: Aktivizo Përmbledhjedon-in interactions: must_be_follower: Blloko njoftime nga jo-ndjekës must_be_following: Blloko njoftime nga persona që s’i ndiqni @@ -317,6 +337,7 @@ sq: follow_request: Dikush kërkoi t’ju ndjekë mention: Dikush ju përmendi pending_account: Llogaria e re lyp shqyrtim + quote: Dikush ju citoi reblog: Dikush përforcoi gjendjen tuaj report: Parashtrohet raportim i ri software_updates: @@ -352,9 +373,9 @@ sq: jurisdiction: Juridiksion ligjor min_age: Mosha minimale user: - date_of_birth_1i: Ditë + date_of_birth_1i: Vit date_of_birth_2i: Muaj - date_of_birth_3i: Vit + date_of_birth_3i: Ditë role: Rol time_zone: Zonë kohore user_role: @@ -363,6 +384,10 @@ sq: name: Emër permissions_as_keys: Leje position: Përparësi + username_block: + allow_with_approval: Lejo regjistrim me miratim + comparison: Metodë krahasimi + username: Fjalë për t’u vëzhguar webhook: events: Akte të aktivizuar template: Gjedhe ngarkese diff --git a/config/locales/simple_form.sr-Latn.yml b/config/locales/simple_form.sr-Latn.yml index 213075e6b43a94..1be4c6f2891de8 100644 --- a/config/locales/simple_form.sr-Latn.yml +++ b/config/locales/simple_form.sr-Latn.yml @@ -70,7 +70,6 @@ sr-Latn: featured_tag: name: 'Evo nekih od heš oznaka koje ste u prethodnom periodu često koristili:' filters: - action: Izaberite koju radnju izvršiti kada objava odgovara filteru actions: hide: Potpuno sakrij filtrirani sadržaj, ponašajući se kao da ne postoji warn: Sakrij filtrirani sadržaj iza upozorenja u kome se navodi naziv filtera @@ -78,7 +77,6 @@ sr-Latn: activity_api_enabled: Brojevi lokalno postavljenih objava, aktivnih korisnika i novih registracija na nedeljnoj bazi app_icon: WEBP, PNG, GIF ili JPG. Zamenjuje podrazumevanu ikonicu aplikacije na mobilnim uređajima prilagođenom ikonicom. backups_retention_period: Korisnici imaju mogućnost da generišu arhive svojih objava za kasnije preuzimanje. Kada se podese na pozitivnu vrednost, ove arhive će se automatski izbrisati iz vašeg skladišta nakon navedenog broja dana. - bootstrap_timeline_accounts: Ovi nalozi će biti zakačeni na vrh preporuka za praćenje novih korisnika. closed_registrations_message: Prikazuje se kada su registracije zatvorene content_cache_retention_period: Sve objave sa drugih servera (uključujući podržavanja i odgovore) će biti izbrisane nakon navedenog broja dana, bez obzira na bilo kakvu interakciju lokalnog korisnika sa tim objavama. Ovo uključuje objave u kojima ih je lokalni korisnik označio kao obeleživače ili omiljene. Privatna pominjanja između korisnika sa različitih instanci će takođe biti izgubljena i nemoguće ih je vratiti. Korišćenje ove postavke je namenjeno za slučajeve posebne namene i krši mnoga očekivanja korisnika kada se primeni za upotrebu opšte namene. custom_css: Možete da primenite prilagođene stilove na veb verziji Mastodon-a. @@ -97,10 +95,8 @@ sr-Latn: status_page_url: URL stranice gde ljudi mogu da vide status servera dok je server oboren theme: Tema koju vide posetioci koji nisu prijavljeni i novi korisnici. thumbnail: Slika u razmeri od približno 2:1 koja se prikazuje pored informacija o Vašem serveru. - timeline_preview: Posetioci koji nisu prijavljeni će moći da pregledaju najnovije javne objave dostupne na serveru. trendable_by_default: Preskoči ručni pregled sadržaja koji je u trendu. Pojedinačne stavke se nakon toga i dalje mogu ukloniti iz trendova. trends: Trendovi pokazuju koje objave, heš oznake i vesti postaju sve popularnije na Vašem serveru. - trends_as_landing_page: Prikaži sadržaj u trendu odjavljenim korisnicima i posetiocima umesto opisa ovog servera. Zahteva da trendovi budu omogućeni. form_challenge: current_password: Ulazite u bezbedno područje imports: @@ -204,10 +200,8 @@ sr-Latn: setting_aggregate_reblogs: Grupiši deljenja u vremenskim linijama setting_always_send_emails: Uvek šalji obaveštenja e-poštom setting_auto_play_gif: Automatski reprodukuj animirane GIF-ove - setting_boost_modal: Prikaži dijalog za potvrdu pre davanja podrške setting_default_language: Jezik objavljivanja setting_default_sensitive: Uvek označi multimediju kao osetljivu - setting_delete_modal: Prikaži dijalog za potvrdu pre brisanja objave setting_disable_hover_cards: Onemogući pregled profila prelaskom kursora setting_disable_swiping: Onemogući pokrete prevlačenja setting_display_media: Prikaz medija @@ -264,10 +258,8 @@ sr-Latn: status_page_url: URL statusne stranice theme: Podrazumevana tema thumbnail: Sličica servera - timeline_preview: Dozvoli neautorizovan pristup javnim vremenskim osama trendable_by_default: Dozvoli trendove bez prethodnog pregleda trends: Omogući trendove - trends_as_landing_page: Koristite trendove kao stranicu dočeka interactions: must_be_follower: Blokiraj obaveštenja od korisnika koji me ne prate must_be_following: Blokiraj obaveštenja od ljudi koje ne pratim diff --git a/config/locales/simple_form.sr.yml b/config/locales/simple_form.sr.yml index 96c5732ebdb80e..2fe3d79c3af2fa 100644 --- a/config/locales/simple_form.sr.yml +++ b/config/locales/simple_form.sr.yml @@ -70,7 +70,6 @@ sr: featured_tag: name: 'Ево неких од хеш ознака које сте у претходном периоду често користили:' filters: - action: Изаберите коју радњу извршити када објава одговара филтеру actions: hide: Потпуно сакриј филтрирани садржај, понашајући се као да не постоји warn: Сакриј филтрирани садржај иза упозорења у коме се наводи назив филтера @@ -78,7 +77,6 @@ sr: activity_api_enabled: Бројеви локално постављених објава, активних корисника и нових регистрација на недељној бази app_icon: WEBP, PNG, GIF или JPG. Замењује подразумевану иконицу апликације на мобилним уређајима прилагођеном иконицом. backups_retention_period: Корисници имају могућност да генеришу архиве својих објава за касније преузимање. Када се подесе на позитивну вредност, ове архиве ће се аутоматски избрисати из вашег складишта након наведеног броја дана. - bootstrap_timeline_accounts: Ови налози ће бити закачени на врх препорука за праћење нових корисника. closed_registrations_message: Приказује се када су регистрације затворене content_cache_retention_period: Све објаве са других сервера (укључујући подржавања и одговоре) ће бити избрисане након наведеног броја дана, без обзира на било какву интеракцију локалног корисника са тим објавама. Ово укључује објаве у којима их је локални корисник означио као обележиваче или омиљене. Приватна помињања између корисника са различитих инстанци ће такође бити изгубљена и немогуће их је вратити. Коришћење ове поставке је намењено за случајеве посебне намене и крши многа очекивања корисника када се примени за употребу опште намене. custom_css: Можете да примените прилагођене стилове на веб верзији Mastodon-а. @@ -97,10 +95,8 @@ sr: status_page_url: URL странице где људи могу да виде статус сервера док је сервер оборен theme: Тема коју виде посетиоци који нису пријављени и нови корисници. thumbnail: Слика у размери од приближно 2:1 која се приказује поред информација о Вашем серверу. - timeline_preview: Посетиоци који нису пријављени ће моћи да прегледају најновије јавне објаве доступне на серверу. trendable_by_default: Прескочи ручни преглед садржаја који је у тренду. Појединачне ставке се након тога и даље могу уклонити из трендова. trends: Трендови показују које објаве, хеш ознаке и вести постају све популарније на Вашем серверу. - trends_as_landing_page: Прикажи садржај у тренду одјављеним корисницима и посетиоцима уместо описа овог сервера. Захтева да трендови буду омогућени. form_challenge: current_password: Улазите у безбедно подручје imports: @@ -204,10 +200,8 @@ sr: setting_aggregate_reblogs: Групиши дељења у временским линијама setting_always_send_emails: Увек шаљи обавештења е-поштом setting_auto_play_gif: Аутоматски репродукуј анимиране GIF-ове - setting_boost_modal: Прикажи дијалог за потврду пре давања подршке setting_default_language: Језик објављивања setting_default_sensitive: Увек означи мултимедију као осетљиву - setting_delete_modal: Прикажи дијалог за потврду пре брисања објаве setting_disable_hover_cards: Онемогући преглед профила преласком курсора setting_disable_swiping: Онемогући покрете превлачења setting_display_media: Приказ медија @@ -264,10 +258,8 @@ sr: status_page_url: URL статусне странице theme: Подразумевана тема thumbnail: Сличица сервера - timeline_preview: Дозволи неауторизован приступ јавним временским осама trendable_by_default: Дозволи трендове без претходног прегледа trends: Омогући трендове - trends_as_landing_page: Користите трендове као страницу дочека interactions: must_be_follower: Блокирај обавештења од корисника који ме не прате must_be_following: Блокирај обавештења од људи које не пратим diff --git a/config/locales/simple_form.sv.yml b/config/locales/simple_form.sv.yml index c4be2a70908fba..7699224561f2a3 100644 --- a/config/locales/simple_form.sv.yml +++ b/config/locales/simple_form.sv.yml @@ -54,8 +54,10 @@ sv: password: Använd minst 8 tecken phrase: Matchas oavsett användande i text eller innehållsvarning för ett inlägg scopes: 'Vilka API: er applikationen kommer tillåtas åtkomst till. Om du väljer en omfattning på högstanivån behöver du inte välja individuella sådana.' + setting_advanced_layout: Visa Mastodon med en layout med flera kolumner, så att du kan se tidslinjen, aviseringar, och en tredje kolumn som du väljer själv. Rekommenderas inte för mindre skärmar. setting_aggregate_reblogs: Visa inte nya boostar för inlägg som nyligen blivit boostade (påverkar endast nymottagna boostar) setting_always_send_emails: E-postnotiser kommer vanligtvis inte skickas när du aktivt använder Mastodon + setting_boost_modal: När den är aktiverad kommer boostningen först att öppna en dialogruta där du kan ändra synligheten på din boost. setting_default_sensitive: Känslig media döljs som standard och kan visas med ett klick setting_display_media_default: Dölj media markerad som känslig setting_display_media_hide_all: Dölj alltid all media @@ -74,7 +76,6 @@ sv: featured_tag: name: 'Här är några av de hashtaggar du använt nyligen:' filters: - action: Välj vilken åtgärd som ska utföras när ett inlägg matchar filtret actions: blur: Dölj media bakom en varning utan att dölja själva texten hide: Dölj det filtrerade innehållet helt, beter sig som om det inte fanns @@ -83,7 +84,6 @@ sv: activity_api_enabled: Antalet lokalt publicerade inlägg, aktiva användare och nya registrerade konton per vecka app_icon: WEBP, PNG, GIF eller JPG. Använd istället för appens egna ikon på mobila enheter. backups_retention_period: Användare har möjlighet att generera arkiv av sina inlägg för att ladda ned senare. När det sätts till ett positivt värde raderas dessa arkiv automatiskt från din lagring efter det angivna antalet dagar. - bootstrap_timeline_accounts: Dessa konton kommer fästas högst upp i nya användares följrekommendationer. closed_registrations_message: Visas när nyregistreringar är avstängda content_cache_retention_period: Alla inlägg från andra servrar (inklusive booster och svar) kommer att raderas efter det angivna antalet dagar, utan hänsyn till någon lokal användarinteraktion med dessa inlägg. Detta inkluderar inlägg där en lokal användare har markerat det som bokmärke eller favoriter. Privata omnämnanden mellan användare från olika instanser kommer också att gå förlorade och blir omöjliga att återställa. Användningen av denna inställning är avsedd för specialfall och bryter många användarförväntningar när de implementeras för allmänt bruk. custom_css: Du kan använda anpassade stilar på webbversionen av Mastodon. @@ -103,10 +103,8 @@ sv: status_page_url: URL till en sida där personer kan se serverns status under ett driftavbrott theme: Tema som utloggade besökare och nya användare ser. thumbnail: En bild i cirka 2:1-proportioner som visas tillsammans med din serverinformation. - timeline_preview: Utloggade besökare kommer kunna bläddra bland de senaste offentliga inläggen som finns på servern. trendable_by_default: Hoppa över manuell granskning av trendande innehåll. Enskilda objekt kan ändå raderas från trender retroaktivt. trends: Trender visar vilka inlägg, hashtaggar och nyheter det pratas om på din server. - trends_as_landing_page: Visa trendande innehåll för utloggade användare och besökare istället för en beskrivning om servern. Kräver att trender är aktiverat. form_challenge: current_password: Du går in i ett säkert område imports: @@ -233,11 +231,11 @@ sv: setting_aggregate_reblogs: Gruppera boostar i tidslinjer setting_always_send_emails: Skicka alltid e-postnotiser setting_auto_play_gif: Spela upp GIF:ar automatiskt - setting_boost_modal: Visa bekräftelsedialog innan boostning setting_default_language: Inläggsspråk + setting_default_privacy: Inläggssynlighet setting_default_quote_policy: Vem kan citera setting_default_sensitive: Markera alltid media som känsligt - setting_delete_modal: Visa bekräftelsedialog innan radering av inlägg + setting_delete_modal: Varna mig innan ett inlägg tas bort setting_disable_hover_cards: Inaktivera profilförhandsgranskning vid hovring setting_disable_swiping: Inaktivera svepande rörelser setting_display_media: Mediavisning @@ -247,7 +245,8 @@ sv: setting_emoji_style: Emoji-stil setting_expand_spoilers: Utöka alltid tutningar markerade med innehållsvarningar setting_hide_network: Göm ditt nätverk - setting_missing_alt_text_modal: Visa bekräftelsedialog innan du skickar media utan alt-text + setting_missing_alt_text_modal: Varna mig innan jag gör mediainlägg utan alt-text + setting_quick_boosting: Aktivera snabb-boostande setting_reduce_motion: Minska rörelser i animationer setting_system_font_ui: Använd systemets standardfont setting_system_scrollbars_ui: Använd systemets standardrullningsfält @@ -281,12 +280,17 @@ sv: content_cache_retention_period: Förvaringsperiod för fjärrinnehåll custom_css: Anpassad CSS favicon: Favicon + landing_page: Startsida för nya besökare + local_live_feed_access: Åtkomst till live-flöden med lokala inlägg + local_topic_feed_access: Åtkomst till hashtagg och länkflöden med lokala inlägg mascot: Anpassad maskot (tekniskt arv) media_cache_retention_period: Tid för bibehållande av mediecache min_age: Åldersgräns peers_api_enabled: Publicera lista över upptäckta servrar i API:et profile_directory: Aktivera profilkatalog registrations_mode: Vem kan registrera sig + remote_live_feed_access: Åtkomst till live-flöden med fjärrinlägg + remote_topic_feed_access: Åtkomst till hashtagg och länkflöden med fjärrinlägg require_invite_text: Kräv anledning för att gå med show_domain_blocks: Visa domänblockeringar show_domain_blocks_rationale: Visa varför domäner blockerades @@ -299,10 +303,9 @@ sv: status_page_url: URL för statussida theme: Standardtema thumbnail: Serverns tumnagelbild - timeline_preview: Tillåt oautentiserad åtkomst till offentliga tidslinjer trendable_by_default: Tillåt trender utan föregående granskning trends: Aktivera trender - trends_as_landing_page: Använd trender som landningssida + wrapstodon: Aktivera Wrapstodon interactions: must_be_follower: Blockera notiser från icke-följare must_be_following: Blockera notiser från personer du inte följer @@ -363,9 +366,9 @@ sv: jurisdiction: Rättslig jurisdiktion min_age: Minimiålder user: - date_of_birth_1i: Dag + date_of_birth_1i: År date_of_birth_2i: Månad - date_of_birth_3i: År + date_of_birth_3i: Dag role: Roll time_zone: Tidszon user_role: diff --git a/config/locales/simple_form.th.yml b/config/locales/simple_form.th.yml index 8ec163d433e975..7e4edbbaf5af22 100644 --- a/config/locales/simple_form.th.yml +++ b/config/locales/simple_form.th.yml @@ -72,7 +72,6 @@ th: featured_tag: name: 'นี่คือแฮชแท็กบางส่วนที่คุณได้ใช้ล่าสุด:' filters: - action: เลือกว่าการกระทำใดที่จะทำเมื่อโพสต์ตรงกับตัวกรอง actions: hide: ซ่อนเนื้อหาที่กรองอยู่อย่างสมบูรณ์ ทำเสมือนว่าไม่มีเนื้อหาอยู่ warn: ซ่อนเนื้อหาที่กรองอยู่หลังคำเตือนที่กล่าวถึงชื่อเรื่องของตัวกรอง @@ -80,7 +79,6 @@ th: activity_api_enabled: จำนวนโพสต์ที่เผยแพร่ในเซิร์ฟเวอร์, ผู้ใช้ที่ใช้งานอยู่ และการลงทะเบียนใหม่ในบักเก็ตรายสัปดาห์ app_icon: WEBP, PNG, GIF หรือ JPG เขียนทับไอคอนแอปเริ่มต้นในอุปกรณ์มือถือด้วยไอคอนที่กำหนดเอง backups_retention_period: ผู้ใช้มีความสามารถในการสร้างการเก็บถาวรของโพสต์ของเขาเพื่อดาวน์โหลดในภายหลัง เมื่อตั้งเป็นค่าบวก จะลบการเก็บถาวรเหล่านี้ออกจากที่เก็บข้อมูลของคุณโดยอัตโนมัติหลังจากจำนวนวันที่ระบุ - bootstrap_timeline_accounts: จะปักหมุดบัญชีเหล่านี้ไว้ด้านบนสุดของคำแนะนำการติดตามของผู้ใช้ใหม่ closed_registrations_message: แสดงเมื่อมีการปิดการลงทะเบียน content_cache_retention_period: จะลบโพสต์ทั้งหมดจากเซิร์ฟเวอร์อื่น ๆ (รวมถึงการดันและการตอบกลับ) หลังจากจำนวนวันที่ระบุ โดยไม่คำนึงถึงการโต้ตอบใด ๆ ของผู้ใช้ในเซิร์ฟเวอร์กับโพสต์เหล่านั้น สิ่งนี้รวมถึงโพสต์ที่ผู้ใช้ในเซิร์ฟเวอร์ได้ทำเครื่องหมายโพสต์ว่าเป็นที่คั่นหน้าหรือรายการโปรด การกล่าวถึงแบบส่วนตัวระหว่างผู้ใช้จากอินสแตนซ์ที่แตกต่างกันจะหายไปและไม่สามารถคืนค่าได้เช่นกัน การใช้การตั้งค่านี้มีไว้สำหรับอินสแตนซ์ที่มีวัตถุประสงค์พิเศษและทำลายความคาดหวังของผู้ใช้จำนวนมากเมื่อนำไปใช้สำหรับการใช้งานที่มีวัตถุประสงค์ทั่วไป custom_css: คุณสามารถนำไปใช้ลักษณะที่กำหนดเองใน Mastodon รุ่นเว็บ @@ -99,10 +97,8 @@ th: status_page_url: URL ของหน้าที่ผู้คนสามารถเห็นสถานะของเซิร์ฟเวอร์นี้ในระหว่างการหยุดทำงาน theme: ชุดรูปแบบที่ผู้เยี่ยมชมที่ออกจากระบบและผู้ใช้ใหม่เห็น thumbnail: แสดงภาพ 2:1 โดยประมาณควบคู่ไปกับข้อมูลเซิร์ฟเวอร์ของคุณ - timeline_preview: ผู้เยี่ยมชมที่ออกจากระบบจะสามารถเรียกดูโพสต์สาธารณะล่าสุดที่มีในเซิร์ฟเวอร์ trendable_by_default: ข้ามการตรวจทานเนื้อหาที่กำลังนิยมด้วยตนเอง ยังคงสามารถเอารายการแต่ละรายการออกจากแนวโน้มได้หลังจากเกิดเหตุ trends: แนวโน้มแสดงว่าโพสต์, แฮชแท็ก และเรื่องข่าวใดกำลังได้รับความสนใจในเซิร์ฟเวอร์ของคุณ - trends_as_landing_page: แสดงเนื้อหาที่กำลังนิยมแก่ผู้ใช้และผู้เยี่ยมชมที่ออกจากระบบแทนที่จะเป็นคำอธิบายของเซิร์ฟเวอร์นี้ ต้องมีการเปิดใช้งานแนวโน้ม form_challenge: current_password: คุณกำลังเข้าสู่พื้นที่ปลอดภัย imports: @@ -208,10 +204,8 @@ th: setting_aggregate_reblogs: จัดกลุ่มการดันในเส้นเวลา setting_always_send_emails: ส่งการแจ้งเตือนอีเมลเสมอ setting_auto_play_gif: เล่น GIF แบบเคลื่อนไหวโดยอัตโนมัติ - setting_boost_modal: แสดงกล่องโต้ตอบการยืนยันก่อนดัน setting_default_language: ภาษาของการโพสต์ setting_default_sensitive: ทำเครื่องหมายสื่อว่าละเอียดอ่อนเสมอ - setting_delete_modal: แสดงกล่องโต้ตอบการยืนยันก่อนลบโพสต์ setting_disable_hover_cards: ปิดใช้งานตัวอย่างโปรไฟล์เมื่อวางเมาส์เหนือ setting_disable_swiping: ปิดใช้งานการเคลื่อนไหวในการปัด setting_display_media: การแสดงสื่อ @@ -220,7 +214,6 @@ th: setting_display_media_show_all: แสดงทั้งหมด setting_expand_spoilers: ขยายโพสต์ที่มีการทำเครื่องหมายด้วยคำเตือนเนื้อหาเสมอ setting_hide_network: ซ่อนกราฟทางสังคมของคุณ - setting_missing_alt_text_modal: แสดงกล่องโต้ตอบการยืนยันก่อนที่จะโพสต์สื่อโดยไม่มีข้อความแสดงแทน setting_reduce_motion: ลดการเคลื่อนไหวในภาพเคลื่อนไหว setting_system_font_ui: ใช้แบบอักษรเริ่มต้นของระบบ setting_system_scrollbars_ui: ใช้แถบเลื่อนเริ่มต้นของระบบ @@ -270,10 +263,8 @@ th: status_page_url: URL ของหน้าสถานะ theme: ชุดรูปแบบเริ่มต้น thumbnail: ภาพขนาดย่อเซิร์ฟเวอร์ - timeline_preview: อนุญาตการเข้าถึงเส้นเวลาสาธารณะที่ไม่ได้รับรองความถูกต้อง trendable_by_default: อนุญาตแนวโน้มโดยไม่มีการตรวจทานล่วงหน้า trends: เปิดใช้งานแนวโน้ม - trends_as_landing_page: ใช้แนวโน้มเป็นหน้าเริ่มต้น interactions: must_be_follower: ปิดกั้นการแจ้งเตือนจากผู้ที่ไม่ใช่ผู้ติดตาม must_be_following: ปิดกั้นการแจ้งเตือนจากผู้คนที่คุณไม่ได้ติดตาม @@ -323,9 +314,9 @@ th: terms_of_service_generator: domain: โดเมน user: - date_of_birth_1i: วัน + date_of_birth_1i: ปี date_of_birth_2i: เดือน - date_of_birth_3i: ปี + date_of_birth_3i: วัน role: บทบาท time_zone: โซนเวลา user_role: diff --git a/config/locales/simple_form.tr.yml b/config/locales/simple_form.tr.yml index 9901691c9ecf11..35df26dc6e57a9 100644 --- a/config/locales/simple_form.tr.yml +++ b/config/locales/simple_form.tr.yml @@ -54,8 +54,10 @@ tr: password: En az 8 karakter kullanın phrase: Metnin büyük/küçük harf durumundan veya gönderinin içerik uyarısından bağımsız olarak eşleştirilecek scopes: Uygulamanın erişmesine izin verilen API'ler. Üst seviye bir kapsam seçtiyseniz, bireysel kapsam seçmenize gerek yoktur. + setting_advanced_layout: Mastodon'u çok sütunlu bir düzen olarak görüntüleyin, böylece zaman akışını, bildirimleri ve seçtiğiniz üçüncü sütunu görüntüleyebilirsiniz. Küçük ekranlar için önerilmez. setting_aggregate_reblogs: Yakın zamanda teşvik edilmiş gönderiler için yeni teşvikleri göstermeyin (yalnızca yeni alınan teşvikleri etkiler) setting_always_send_emails: Normalde, Mastodon'u aktif olarak kullanırken e-posta bildirimleri gönderilmeyecektir + setting_boost_modal: Etkinleştirildiğinde öne çıkarmanızın görünürlüğünü değiştirebileceğiniz bir onay iletişim kutusu açar. setting_default_quote_policy_private: Mastodon'da sadece takipçilere yönelik gönderiler başkaları tarafından alıntılanamaz. setting_default_quote_policy_unlisted: İnsanlar sizden alıntı yaptığında, onların gönderileri de trend zaman tünellerinden gizlenecektir. setting_default_sensitive: Hassas medya varsayılan olarak gizlidir ve bir tıklama ile gösterilebilir @@ -63,6 +65,7 @@ tr: setting_display_media_hide_all: Medyayı her zaman gizle setting_display_media_show_all: Medyayı her zaman göster setting_emoji_style: Emojiler nasıl görüntülensin. "Otomatik" seçeneği yerel emojileri kullanmaya çalışır, ancak eski tarayıcılar için Twemoji'yi kullanır. + setting_quick_boosting_html: Etkinleştirildiğinde, %{boost_icon} Öne Çıkar simgesine tıklandığında, öne çıkar/alıntı açılır menüsünü görüntüleme yerine hemen öne çıkarma işlemi gerçekleştirilir. Alıntı işlevi %{options_icon} (Seçenekler) menüsüne taşınır. setting_system_scrollbars_ui: Yalnızca Safari ve Chrome tabanlı masaüstü tarayıcılar için geçerlidir setting_use_blurhash: Gradyenler gizli görsellerin renklerine dayanır, ancak detayları gizler setting_use_pending_items: Akışı otomatik olarak kaydırmak yerine, zaman çizelgesi güncellemelerini tek bir tıklamayla gizleyin @@ -85,11 +88,12 @@ tr: activity_api_enabled: Yerel olarak yayınlanan gönderi, etkin kullanıcı ve yeni kayıtların haftalık sayıları app_icon: WEBP, PNG, GIF veya JPG. Mobil aygıtlarda varsayılan uygulama simgesini isteğe bağlı bir simgeyle değiştirir. backups_retention_period: Kullanıcılar, gönderilerinin arşivlerini daha sonra indirmek üzere oluşturabilirler. Pozitif bir değer verdilğinde bu arşivler verilmiş olan gün sonunda deponuzdan otomatik olarak silinecektir. - bootstrap_timeline_accounts: Bu hesaplar, yeni kullanıcıların takip önerilerinin tepesinde sabitlenecektir. + bootstrap_timeline_accounts: Bu hesaplar, yeni kullanıcıların takip önerilerinin en üstüne sabitlenecektir. Hesapların virgülle ayrılmış bir listesini girin. closed_registrations_message: Kayıt olma kapalıyken görüntülenir content_cache_retention_period: Diğer sunuculardaki (öne çıkarma ve yanıtlar da dahil olmak üzere) tüm gönderiler belirlenen gün sonunda, yerel bir kullanıcının etkileşimine bakılmadan, silinecektir. Yerel bir kullanıcının yerimlerine veya favorilerine eklediği gönderiler de dahildir. Farklı sunuculardaki kullanıcılar arasındaki özel bahsetmeler de kaybolacak ve geri getirilmeleri mümkün olmayacaktır. Bu ayarın kullanımı özel amaçlı sunucular içindir ve genel amaçlı kullanımda etkinleştirildiğinde kullanıcı beklentilerini karşılamayabilir. custom_css: Mastodon'un web sürümüne özel biçimler uygulayabilirsiniz. favicon: WEBP, PNG, GIF veya JPG. Varsayılan Mastodon simgesini isteğe bağlı bir simgeyle değiştirir. + landing_page: Yeni ziyaretçilerin sunucunuza ilk geldiklerinde görecekleri sayfayı seçer. "Öne çıkanlar" seçeneğini seçerseniz, Keşif Ayarlarında öne çıkanların etkinleştirilmesi gerekir. "Yerel akış" seçeneğini seçerseniz, Keşif Ayarlarında "Yerel gönderileri içeren canlı akışlara erişim" seçeneğinin "Herkes" olarak ayarlanması gerekir. mascot: Gelişmiş web arayüzündeki illüstrasyonu geçersiz kılar. media_cache_retention_period: Uzak kullanıcıların gönderilerindeki ortam dosyaları sunucunuzda önbelleklenir. Pozitif bir değer verildiğinde, ortam dosyaları belirlenen gün sonunda silinecektir. Eğer ortam dosyaları silindikten sonra istenirse, kaynak içerik hala mevcutsa, tekrar indirilecektir. Bağlantı önizleme kartlarının üçüncü parti siteleri yoklamasına ilişkin kısıtlamalar nedeniyle, bu değeri en azından 14 gün olarak ayarlamanız önerilir, yoksa bağlantı önizleme kartları bu süreden önce isteğe bağlı olarak güncellenmeyecektir. min_age: Kullanıcılardan kayıt olurken doğum tarihlerini doğrulamaları istenecektir @@ -105,10 +109,9 @@ tr: status_page_url: İnsanların bir kesinti halinde sunucunun durumunu görebilecekleri bir sayfanın URL'si theme: Giriş yapmamış ziyaretçilerin ve yeni kullanıcıların gördüğü tema. thumbnail: Sunucu bilginizin yanında gösterilen yaklaşık 2:1'lik görüntü. - timeline_preview: Giriş yapmamış ziyaretçiler, sunucuda mevcut olan en son genel gönderileri tarayabilecekler. trendable_by_default: Öne çıkan içeriğin elle incelenmesini atla. Tekil öğeler sonrada öne çıkanlardan kaldırılabilir. trends: Öne çıkanlar, sunucunuzda ilgi toplayan gönderileri, etiketleri ve haber yazılarını gösterir. - trends_as_landing_page: Giriş yapmış kullanıcılar ve ziyaretçilere sunucunun açıklması yerine öne çıkan içeriği göster. Öne çıkanların etkin olması gerekir. + wrapstodon: Yerel kullanıcılara, yıl boyunca Mastodon kullanımlarının eğlenceli bir özetini oluşturma imkanı sunun. Bu özellik, her yıl 10 Aralık ile 31 Aralık tarihleri arasında kullanılabilir ve yıl içinde en az bir adet Halka Açık veya Sessiz Halka Açık gönderi paylaşan ve en az bir hashtag kullanan kullanıcılara sunulur. form_challenge: current_password: Güvenli bir bölgeye giriyorsunuz imports: @@ -235,12 +238,12 @@ tr: setting_aggregate_reblogs: Zaman çizelgesindeki boostları grupla setting_always_send_emails: Her zaman e-posta bildirimleri gönder setting_auto_play_gif: Hareketli GIF'leri otomatik oynat - setting_boost_modal: Paylaşmadan önce onay iletişim kutusu göster + setting_boost_modal: Öne çıkarma görünürlüğünü denetleyin setting_default_language: Gönderi dili setting_default_privacy: Gönderi görünürlüğü setting_default_quote_policy: Kimler alıntılayabilir setting_default_sensitive: Medyayı her zaman hassas olarak işaretle - setting_delete_modal: Bir gönderiyi silmeden önce onay iletişim kutusu göster + setting_delete_modal: Bir gönderiyi silmeden beni uyar setting_disable_hover_cards: Üstüne geldiğinde profil önizlemesini devre dışı bırak setting_disable_swiping: Kaydırma hareketlerini devre dışı bırak setting_display_media: Medya görüntüleme @@ -250,7 +253,8 @@ tr: setting_emoji_style: Emoji stili setting_expand_spoilers: İçerik uyarılarıyla işaretli gönderileri her zaman genişlet setting_hide_network: Sosyal grafiğini gizle - setting_missing_alt_text_modal: Alternatif metni olmayan medya göndermeden önce onay sorusu göster + setting_missing_alt_text_modal: Alternatif metni olmayan bir medya göndermeden önce beni uyar + setting_quick_boosting: Hızlı öne çıkarmayı etkinleştir setting_reduce_motion: Animasyonlarda hareketi azalt setting_system_font_ui: Sistemin varsayılan yazı tipini kullan setting_system_scrollbars_ui: Sistemin varsayılan kaydırma çubuğunu kullan @@ -284,12 +288,17 @@ tr: content_cache_retention_period: Uzak içerik saklama süresi custom_css: Özel CSS favicon: Yer imi simgesi + landing_page: Yeni ziyaretçiler için giriş sayfası + local_live_feed_access: Yerel gönderileri ön plana çıkaran canlı akışlara erişim + local_topic_feed_access: Yerel gönderileri ön plana çıkaran etiket ve bağlantı akışlarına erişim mascot: Özel maskot (eski) media_cache_retention_period: Medya önbelleği saklama süresi min_age: Azami yaş gereksinimi peers_api_enabled: API'de keşfedilen sunucuların listesini yayınla profile_directory: Profil dizinini etkinleştir registrations_mode: Kim kaydolabilir + remote_live_feed_access: Uzaktan gönderileri ön plana çıkaran canlı akışlara erişim + remote_topic_feed_access: Uzaktan gönderileri ön plana çıkaran etiket ve bağlantı akışlarına erişim require_invite_text: Katılmak için bir gerekçe iste show_domain_blocks: Engellenen alan adlarını göster show_domain_blocks_rationale: Alan adlarının neden engellendiğini göster @@ -302,10 +311,9 @@ tr: status_page_url: Durum sayfası URL'si theme: Öntanımlı tema thumbnail: Sunucu küçük resmi - timeline_preview: Genel zaman çizelgelerine yetkisiz erişime izin ver trendable_by_default: Ön incelemesiz öne çıkanlara izin ver trends: Öne çıkanları etkinleştir - trends_as_landing_page: Giriş sayfası olarak öne çıkanları kullan + wrapstodon: Wrapstodonu Etkinleştir interactions: must_be_follower: Takipçim olmayan kişilerden gelen bildirimleri engelle must_be_following: Takip etmediğim kişilerden gelen bildirimleri engelle @@ -366,9 +374,9 @@ tr: jurisdiction: Yasal yetki alanı min_age: Minimum yaş user: - date_of_birth_1i: Gün + date_of_birth_1i: Yıl date_of_birth_2i: Ay - date_of_birth_3i: Yıl + date_of_birth_3i: Gün role: Rol time_zone: Zaman dilimi user_role: diff --git a/config/locales/simple_form.uk.yml b/config/locales/simple_form.uk.yml index 017e041ecda2d4..94a8a4010b61fd 100644 --- a/config/locales/simple_form.uk.yml +++ b/config/locales/simple_form.uk.yml @@ -3,7 +3,7 @@ uk: simple_form: hints: account: - attribution_domains: Один на рядок. Захищає від фальшивих атрибутів. + attribution_domains: Один на рядок. Захищає від фальшивих приписувань авторства. discoverable: Ваші дописи та профіль можуть бути рекомендовані в різних частинах Mastodon і ваш профіль може бути запропонований іншим користувачам. display_name: Ваше повне ім'я або ваш псевдонім. fields: Ваша домашня сторінка, займенники, вік, все, що вам заманеться. @@ -54,12 +54,18 @@ uk: password: Не менше 8 символів phrase: Шукає без врахування регістру у тексті допису або у його попередженні про вміст scopes: Які API додатку буде дозволено використовувати. Якщо ви виберете самий верхній, нижчестоящі будуть обрані автоматично. + setting_advanced_layout: 'Показувати Mastodon як декілька колонок: стрічку, сповіщення й третю колонку на ваш вибір. Не рекомендовано для малих екранів.' setting_aggregate_reblogs: Не показувати поширення для дописів, які нещодавно вже були поширені (не вплине на вже отримані поширення) setting_always_send_emails: Зазвичай, під час активного користування Mastodon, сповіщення не будуть відправлятися електронною поштою + setting_boost_modal: Якщо ввімкнено, при поширенні буде показано діалог підтвердження, в якому можна змінити видимість поширення. + setting_default_quote_policy_private: Mastodon не дозволяє цитувати дописи, адресовані лише підписникам. + setting_default_quote_policy_unlisted: Цитати вашого допису також буде сховано зі стрічок трендів. setting_default_sensitive: Делікатні медіа типово приховані та можуть бути розкриті натисканням setting_display_media_default: Приховувати медіа, позначені як делікатними setting_display_media_hide_all: Завжди приховувати медіа setting_display_media_show_all: Завжди показувати медіа + setting_emoji_style: Як показувати емоджі. «Авто» — використовувати емоджі браузера, а за їхньої відсутності — Twemoji. + setting_quick_boosting_html: Якщо увімкнено, натиск на піктограму %{boost_icon} Поширити призводитиме до негайного поширення, а не відкриватиме меню поширення й цитування. Кнопку цитування буде переміщено до меню %{options_icon} Більше. setting_system_scrollbars_ui: Застосовується лише для настільних браузерів на основі Safari та Chrome setting_use_blurhash: Градієнти, що базуються на кольорах прихованих медіа, але роблять нерозрізненними будь-які деталі setting_use_pending_items: Не додавати нові повідомлення до стрічок миттєво, показувати лише після додаткового клацання @@ -73,7 +79,7 @@ uk: featured_tag: name: 'Ось деякі використані останнім часом хештеґи:' filters: - action: Виберіть дію для виконання коли допис збігається з фільтром + action: Виберіть, що робити, коли допис відповідає фільтру actions: blur: Приховати медіа за попередженням, не приховуючи сам текст hide: Повністю сховати фільтрований вміст, ніби його не існує @@ -82,7 +88,6 @@ uk: activity_api_enabled: Кількість локальних опублікованих дописів, активних і нових користувачів у тижневих розрізах app_icon: WEBP, PNG, GIF або JPG. Замінює типову піктограму застосунку на мобільних пристроях на власну. backups_retention_period: Користувачі мають можливість створювати архіви своїх дописів, щоб завантажити їх пізніше. Якщо встановлено додатне значення, ці архіви будуть автоматично видалені з вашого сховища через вказану кількість днів. - bootstrap_timeline_accounts: Ці облікові записи будуть закріплені в топі пропозицій для нових користувачів. closed_registrations_message: Показується, коли реєстрація закрита content_cache_retention_period: Усі дописи з інших серверів (включно з коментарями та відповідями) будуть видалені через певну кількість днів, незважаючи на будь-яку локальну взаємодію користувачів з цими дописами. Сюди входять дописи, які локальний користувач позначив як закладки або вибране. Приватні згадки між користувачами з різних інстанцій також будуть втрачені і не підлягатимуть відновленню. Використання цього параметра призначено для екземплярів спеціального призначення і порушує багато очікувань користувачів, якщо його застосовано для загального використання. custom_css: Ви можете застосувати користувацькі стилі у вебверсії Mastodon. @@ -102,10 +107,8 @@ uk: status_page_url: URL сторінки, на якій люди можуть бачити статус цього сервера під час його збою в роботі theme: Тема, яку бачать відвідувачі, що вийшли з системи, та нові користувачі. thumbnail: Зображення приблизно 2:1, що показується поряд з відомостями про ваш сервер. - timeline_preview: Зареєстровані відвідувачі зможуть переглядати останні публічні дописи, доступні на сервері. trendable_by_default: Пропустити ручний огляд популярних матеріалів. Індивідуальні елементи все ще можна вилучити з популярних постфактум. trends: Популярні показують, які дописи, хештеґи та новини набувають популярності на вашому сервері. - trends_as_landing_page: Показувати популярні матеріали для зареєстрованих користувачів і відвідувачів замість опису цього сервера. Для активації потрібні тренди. form_challenge: current_password: Ви входите до безпечної зони imports: @@ -160,6 +163,7 @@ uk: url: Куди надсилатимуться події labels: account: + attribution_domains: Сайти, яким можна вказувати вас як автора discoverable: Функції профілю та дописів у алгоритмах виявлення fields: name: Мітка @@ -223,12 +227,14 @@ uk: setting_aggregate_reblogs: Групувати поширення в стрічках setting_always_send_emails: Завжди надсилати сповіщення електронною поштою setting_auto_play_gif: Автоматично відтворювати анімовані GIF - setting_boost_modal: Показувати діалог підтвердження під час поширення + setting_boost_modal: Керувати видимістю поширення setting_default_language: Мова дописів + setting_default_privacy: Видимість дописів + setting_default_quote_policy: Кому можна цитувати setting_default_sensitive: Позначати медіа делікатними - setting_delete_modal: Показувати діалог підтвердження під час видалення допису + setting_delete_modal: Перепитувати, чи видаляти допис setting_disable_hover_cards: Вимкнути попередній перегляд профілю під час наведення мишки - setting_disable_swiping: Вимкнути рух посування + setting_disable_swiping: Вимкнути жести гортання setting_display_media: Показ медіа setting_display_media_default: За промовчанням setting_display_media_hide_all: Сховати всі @@ -236,7 +242,8 @@ uk: setting_emoji_style: Стиль емодзі setting_expand_spoilers: Завжди розгортати дописи з попередженнями про вміст setting_hide_network: Сховати вашу мережу - setting_missing_alt_text_modal: Запитувати перед розміщенням медіа без альтернативного тексту + setting_missing_alt_text_modal: Перепитувати, чи публікувати мультимедіа без альтернативного тексту + setting_quick_boosting: Поширювати швидше setting_reduce_motion: Менше руху в анімаціях setting_system_font_ui: Використовувати типовий системний шрифт setting_system_scrollbars_ui: Використовувати системну панель гортання @@ -288,10 +295,8 @@ uk: status_page_url: URL сторінки статусу theme: Стандартна тема thumbnail: Мініатюра сервера - timeline_preview: Дозволити неавтентифікований доступ до публічних стрічок trendable_by_default: Дозволити популярне без попереднього огляду trends: Увімкнути популярні - trends_as_landing_page: Використовуйте тенденції як цільову сторінку interactions: must_be_follower: Блокувати сповіщення від непідписаних людей must_be_following: Блокувати сповіщення від людей, на яких ви не підписані @@ -351,9 +356,7 @@ uk: jurisdiction: Правова юрисдикція min_age: Мінімальний вік user: - date_of_birth_1i: День date_of_birth_2i: Місяць - date_of_birth_3i: Рік role: Роль time_zone: Часовий пояс user_role: diff --git a/config/locales/simple_form.vi.yml b/config/locales/simple_form.vi.yml index e7e308a1b0e6b9..027f00bf8b6887 100644 --- a/config/locales/simple_form.vi.yml +++ b/config/locales/simple_form.vi.yml @@ -54,15 +54,18 @@ vi: password: Tối thiểu 8 ký tự phrase: Sẽ được hiện thị trong văn bản hoặc cảnh báo nội dung của một tút scopes: Ứng dụng sẽ được phép truy cập những API nào. Nếu bạn chọn quyền cấp cao nhất, không cần chọn quyền nhỏ. + setting_advanced_layout: Hiển thị Mastodon dưới dạng bố cục nhiều cột, cho phép bạn xem dòng thời gian, thông báo và cột thứ ba mà bạn chọn. Không nên dùng cho màn hình nhỏ. setting_aggregate_reblogs: Nếu một tút đã được đăng lại thì sẽ không hiện những lượt đăng lại khác trên bảng tin setting_always_send_emails: Bình thường thì sẽ không gửi khi bạn đang dùng Mastodon - setting_default_quote_policy_private: Tút chỉ dành cho người theo dõi trên Mastodon không thể được người khác trích dẫn. + setting_boost_modal: Nếu được bật, trước khi đăng lại sẽ mở hộp thoại xác nhận - trong đó bạn có thể thay đổi mức độ hiển thị tút của mình. + setting_default_quote_policy_private: Không thể trích dẫn tút chỉ dành cho người theo dõi trên Mastodon. setting_default_quote_policy_unlisted: Khi ai đó trích dẫn bạn, tút của họ cũng sẽ bị ẩn khỏi bảng tin công khai. setting_default_sensitive: Bắt buộc nhấn vào mới có thể xem setting_display_media_default: Click để xem setting_display_media_hide_all: Luôn ẩn setting_display_media_show_all: Luôn hiện setting_emoji_style: Cách hiển thị Emoji. "Tự động" sẽ dùng biểu tượng cảm xúc nguyên bản, nhưng đối với các trình duyệt cũ sẽ chuyển thành Twemoji. + setting_quick_boosting_html: Nếu bật, nhấn biểu tượng %{boost_icon} Đăng lại sẽ lập tức đăng lại thay vì mở menu xổ xuống đăng lại/trích dẫn. Chuyển vị trí hành động trích dẫn sang menu %{options_icon} (Tùy chọn). setting_system_scrollbars_ui: Chỉ áp dụng trình duyệt Chrome và Safari bản desktop setting_use_blurhash: Phủ lớp màu làm nhòe đi hình ảnh nhạy cảm setting_use_pending_items: Dồn lại toàn bộ tút mới và chỉ hiển thị khi nhấn vào @@ -85,11 +88,12 @@ vi: activity_api_enabled: Số lượng tút được đăng trong máy chủ, người dùng đang hoạt động và đăng ký mới hàng tuần app_icon: WEBP, PNG, GIF hoặc JPG. Dùng biểu tượng tùy chỉnh trên thiết bị di động. backups_retention_period: Người dùng có khả năng tạo bản sao lưu các tút của họ để tải xuống sau. Các bản sao lưu này sẽ tự động bị xóa khỏi bộ nhớ của bạn sau số ngày được chỉ định. - bootstrap_timeline_accounts: Những người này sẽ được ghim vào đầu các gợi ý theo dõi của người mới. + bootstrap_timeline_accounts: Những tài khoản này sẽ được ghim lên đầu danh sách đề xuất theo dõi của người dùng mới. Hãy cung cấp danh sách các tài khoản được phân tách bằng dấu phẩy. closed_registrations_message: Được hiển thị khi đóng đăng ký content_cache_retention_period: Tất cả tút từ các máy chủ khác (bao gồm cả đăng lại và trả lời) sẽ bị xóa sau số ngày được chỉ định mà không tính đến bất kỳ tương tác nào của người dùng cục bộ với các tút đó. Điều này bao gồm các tút mà người dùng cục bộ đã đánh dấu nó là dấu trang hoặc mục yêu thích. Những lượt nhắc riêng tư giữa những người dùng từ các máy chủ khác nhau cũng sẽ bị mất và không thể khôi phục. Việc sử dụng cài đặt này dành cho các trường hợp có mục đích đặc biệt và phá vỡ nhiều kỳ vọng của người dùng khi được triển khai cho mục đích sử dụng chung. custom_css: Bạn có thể tùy chỉnh phong cách trên bản web của Mastodon. favicon: WEBP, PNG, GIF hoặc JPG. Dùng favicon Maston tùy chỉnh. + landing_page: Chọn trang mà khách truy cập mới sẽ thấy khi họ lần đầu truy cập máy chủ của bạn. Nếu bạn chọn "Xu hướng", thì cần bật xu hướng trong Cài đặt Khám phá. Nếu bạn chọn "Bảng tin máy chủ", thì cần đặt "Truy cập vào nguồn cấp dữ liệu trực tiếp có bài đăng cục bộ" thành "Mọi người" trong Cài đặt Khám phá. mascot: Ghi đè hình minh họa trong giao diện web nâng cao. media_cache_retention_period: Các tệp phương tiện từ các tút do người dùng máy chủ khác thực hiện sẽ được lưu vào bộ đệm trên máy chủ của bạn. Khi được đặt thành giá trị dương, phương tiện sẽ bị xóa sau số ngày được chỉ định. Nếu dữ liệu phương tiện được yêu cầu sau khi bị xóa, dữ liệu đó sẽ được tải xuống lại nếu nội dung nguồn vẫn còn. Do những hạn chế về tần suất thẻ xem trước liên kết thăm dò ý kiến ​​các trang web của bên thứ ba, bạn nên đặt giá trị này thành ít nhất 14 ngày, nếu không thẻ xem trước liên kết sẽ không được cập nhật theo yêu cầu trước thời gian đó. min_age: Thành viên sẽ được yêu cầu xác nhận ngày sinh của họ trong quá trình đăng ký @@ -105,10 +109,9 @@ vi: status_page_url: URL của trang nơi mọi người có thể xem trạng thái của máy chủ này khi ngừng hoạt động theme: Chủ đề mà khách truy cập đăng xuất và người mới nhìn thấy. thumbnail: 'Một hình ảnh tỉ lệ 2: 1 được hiển thị cùng với thông tin máy chủ của bạn.' - timeline_preview: Khách truy cập đã đăng xuất sẽ có thể xem các tút công khai gần đây nhất trên máy chủ. trendable_by_default: Bỏ qua việc duyệt thủ công nội dung xu hướng. Các mục riêng lẻ vẫn có thể bị xóa khỏi xu hướng sau này. trends: Hiển thị những tút, hashtag và tin tức đang được thảo luận nhiều trên máy chủ của bạn. - trends_as_landing_page: Hiển thị nội dung xu hướng cho người dùng chưa đăng nhập thay vì mô tả về máy chủ này. Yêu cầu xu hướng được kích hoạt. + wrapstodon: Cho phép người dùng máy chủ tạo bản tóm tắt vui nhộn về việc sử dụng Mastodon của họ trong năm. Tính năng này có sẵn từ ngày 10 đến ngày 31 tháng 12 hàng năm, và được cung cấp cho người dùng đã đăng ít nhất một tút Công khai hoặc Riêng tư và sử dụng ít nhất một hashtag trong năm. form_challenge: current_password: Biểu mẫu này an toàn imports: @@ -234,12 +237,12 @@ vi: setting_aggregate_reblogs: Không hiện lượt đăng lại trùng lặp setting_always_send_emails: Luôn gửi email thông báo setting_auto_play_gif: Tự động phát ảnh GIF - setting_boost_modal: Hỏi trước khi đăng lại tút + setting_boost_modal: Kiểm soát khả năng đăng lại setting_default_language: Ngôn ngữ tút setting_default_privacy: Kiểu tút setting_default_quote_policy: Ai có thể trích dẫn setting_default_sensitive: Đánh dấu media nhạy cảm - setting_delete_modal: Hỏi trước khi xóa tút + setting_delete_modal: Cảnh báo tôi trước khi xóa một tút setting_disable_hover_cards: Không popup hồ sơ setting_disable_swiping: Không thao tác vuốt setting_display_media: Media nhạy cảm @@ -249,7 +252,8 @@ vi: setting_emoji_style: Phong cách Emoji setting_expand_spoilers: Luôn mở rộng tút chứa nội dung ẩn setting_hide_network: Ẩn quan hệ của bạn - setting_missing_alt_text_modal: Hỏi trước khi đăng media không có văn bản thay thế + setting_missing_alt_text_modal: Cảnh báo tôi trước khi đăng media mà không có alt text + setting_quick_boosting: Bật đăng lại nhanh setting_reduce_motion: Giảm chuyển động ảnh GIF setting_system_font_ui: Phông chữ mặc định hệ thống setting_system_scrollbars_ui: Thanh cuộn mặc định hệ thống @@ -283,12 +287,17 @@ vi: content_cache_retention_period: Khoảng thời gian lưu giữ nội dung máy chủ khác custom_css: Tùy chỉnh CSS favicon: Favicon + landing_page: Trang mở đầu dành cho khách ghé thăm + local_live_feed_access: Truy cập bảng tin gồm những tút của máy chủ + local_topic_feed_access: Truy cập hashtag và bảng tin liên kết gồm những tút của máy chủ mascot: Tùy chỉnh linh vật (kế thừa) media_cache_retention_period: Thời hạn lưu trữ cache media min_age: Độ tuổi tối thiểu peers_api_enabled: Công khai danh sách các máy chủ được phát hiện trong API profile_directory: Cho phép hiện danh bạ thành viên registrations_mode: Ai có thể đăng ký + remote_live_feed_access: Truy cập bảng tin gồm những tút từ máy chủ khác + remote_topic_feed_access: Truy cập hashtag và bảng tin liên kết gồm những tút từ máy chủ khác require_invite_text: Yêu cầu lí do đăng ký show_domain_blocks: Xem máy chủ chặn show_domain_blocks_rationale: Hiện lý do máy chủ bị chặn @@ -301,10 +310,9 @@ vi: status_page_url: URL trang trạng thái theme: Chủ đề mặc định thumbnail: Hình thu nhỏ của máy chủ - timeline_preview: Cho phép truy cập vào dòng thời gian công khai trendable_by_default: Cho phép lên xu hướng mà không cần duyệt trước trends: Bật xu hướng - trends_as_landing_page: Dùng trang xu hướng làm trang chào mừng + wrapstodon: Bật Wrapstodon interactions: must_be_follower: Những người không theo dõi bạn must_be_following: Những người bạn không theo dõi @@ -365,9 +373,9 @@ vi: jurisdiction: Quyền tài phán pháp lý min_age: Độ tuổi tối thiểu user: - date_of_birth_1i: Ngày + date_of_birth_1i: Năm date_of_birth_2i: Tháng - date_of_birth_3i: Năm + date_of_birth_3i: Ngày role: Vai trò time_zone: Múi giờ user_role: diff --git a/config/locales/simple_form.zh-CN.yml b/config/locales/simple_form.zh-CN.yml index efd37217a4b13e..0bc0ffa4135bb3 100644 --- a/config/locales/simple_form.zh-CN.yml +++ b/config/locales/simple_form.zh-CN.yml @@ -26,9 +26,9 @@ zh-CN: types: disable: 禁止用户使用账号,但不会删除或隐藏账号内容。 none: 用它来向用户发送警告,不会触发其他操作。 - sensitive: 强制将此用户的所有媒体文件标记为敏感内容。 - silence: 阻止用户发送公开嘟文,除了关注者以外,其他人都无法看到他的嘟文和通知。关闭针对此账号的所有举报。 - suspend: 阻止此账号的任何交互并删除其内容。30天内可以撤销操作。关闭针对此账号的所有举报。 + sensitive: 强制将此用户的全部媒体文件标记为敏感内容。 + silence: 阻止用户发送公开嘟文,除了关注者以外,其他人都无法看到他的嘟文和通知。关闭针对此账号的全部举报。 + suspend: 阻止此账号的任何交互并删除其内容。30天内可以撤销操作。关闭针对此账号的全部举报。 warning_preset_id: 可选。你可以在预置文本末尾添加自定义文本 announcement: all_day: 如果选中,只有该时间段内的日期会显示。 @@ -54,8 +54,10 @@ zh-CN: password: 至少需要8个字符 phrase: 匹配将忽略嘟文或内容警告里的字母大小写 scopes: 哪些 API 被允许使用。如果你勾选了更高一级的范围,就不用单独选中子项目了。 + setting_advanced_layout: 将 Mastodon 的界面显示为多列布局,允许你同时查看时间线、通知及可自主选择的第三列。屏幕尺寸较小的情况下不推荐使用。 setting_aggregate_reblogs: 不显示最近已经被转嘟过的嘟文(只会影响新收到的转嘟) setting_always_send_emails: 一般情况下,如果你活跃使用 Mastodon,我们不会向你发送电子邮件通知 + setting_boost_modal: 如果启用,转嘟前会先打开确认对话框,以便更改转嘟的可见性。 setting_default_quote_policy_private: Mastodon上发布的仅限关注者可见的嘟文无法被他人引用。 setting_default_quote_policy_unlisted: 当其他人引用你时,他们的嘟文也会从热门时间线上隐藏。 setting_default_sensitive: 敏感内容默认隐藏,并在点击后显示 @@ -63,6 +65,7 @@ zh-CN: setting_display_media_hide_all: 始终隐藏媒体 setting_display_media_show_all: 始终显示媒体 setting_emoji_style: 如何显示Emoji表情符号。选择“自动”将尝试使用原生Emoji,但在旧浏览器中会备选使用Twemoji。 + setting_quick_boosting_html: 如果启用,点击 %{boost_icon} 转嘟图标将立即转嘟,而非开启“转嘟/引用”的下拉式菜单。这会使引用嘟文操作的按钮移动到 %{options_icon} (选项)菜单中。 setting_system_scrollbars_ui: 仅对基于 Safari 或 Chromium 内核的桌面端浏览器有效 setting_use_blurhash: 渐变是基于模糊后的隐藏内容生成的 setting_use_pending_items: 点击查看时间线更新,而非自动滚动更新动态。 @@ -76,7 +79,7 @@ zh-CN: featured_tag: name: 以下是你最近使用过的标签: filters: - action: 选择在嘟文命中过滤规则时要执行的操作 + action: 请选择在嘟文命中过滤规则时要执行的操作 actions: blur: 将媒体隐藏在警告之后,且不隐藏文字 hide: 选择在嘟文命中过滤规则时要执行的操作 @@ -85,15 +88,16 @@ zh-CN: activity_api_enabled: 本站每周的嘟文数、活跃用户数和新注册用户数 app_icon: WEBP、PNG、GIF 或 JPG。使用自定义图标覆盖移动设备上的默认应用图标。 backups_retention_period: 用户可以生成其嘟文存档以供之后下载。当该值被设为正值时,这些存档将在指定的天数后自动从你的存储中删除。 - bootstrap_timeline_accounts: 这些账号将在新用户关注推荐中置顶显示。 + bootstrap_timeline_accounts: 这些账号将在新用户关注推荐中置顶显示。请提供以逗号分隔的账号列表。 closed_registrations_message: 在关闭注册时显示 content_cache_retention_period: 来自其它实例的所有嘟文(包括转嘟与回复)都将在指定天数后被删除,不论本实例用户是否与这些嘟文产生过交互。这包括被本实例用户喜欢和收藏的嘟文。实例间用户的私下提及也将丢失并无法恢复。此设置针对的是特殊用途的实例,用于一般用途时会打破许多用户的期望。 custom_css: 你可以为网页版 Mastodon 应用自定义样式。 favicon: WEBP、PNG、GIF 或 JPG。使用自定义图标覆盖 Mastodon 的默认图标。 + landing_page: 选择新访客首次访问您的服务器时看到的页面。 如果选择“热门”,则需要在“发现”设置中启用热门趋势。 如果选择“本站动态”,则在“发现”设置中“展示本站嘟文的实时动态访问权限”一项需要设置为“所有人”。 mascot: 覆盖高级网页界面中的绘图形象。 media_cache_retention_period: 来自外站用户嘟文的媒体文件将被缓存到你的实例上。当该值被设为正值时,缓存的媒体文件将在指定天数后被清除。如果媒体文件在被清除后重新被请求,且源站内容仍然可用,它将被重新下载。由于链接预览卡拉取第三方站点的频率受到限制,建议将此值设置为至少 14 天,如果小于该值,链接预览卡将不会按需更新。 min_age: 用户注册时必须确认出生日期 - peers_api_enabled: 本站在联邦宇宙中遇到的站点列表。 此处不包含关于您是否与给定站点联合的数据,只是您的实例知道它。 这由收集一般意义上的联合统计信息的服务使用。 + peers_api_enabled: 本站在联邦宇宙中遇到的站点列表。 此处不包含关于你是否与给定站点联合的数据,只是你的实例知道它。 这由收集一般意义上的联合统计信息的服务使用。 profile_directory: 个人资料目录会列出所有选择可被发现的用户。 require_invite_text: 当注册需要手动批准时,将“你为什么想要加入?”设为必填项 site_contact_email: 他人需要询恰法务或支持信息时的联络方式 @@ -105,10 +109,9 @@ zh-CN: status_page_url: 配置一个网址,当服务中断时,人们可以通过该网址查看服务器的状态。 theme: 给未登录访客和新用户使用的主题。 thumbnail: 与服务器信息一并展示的约 2:1 比例的图像。 - timeline_preview: 未登录访客将能够浏览服务器上的最新公开嘟文。 trendable_by_default: 跳过对热门内容的手工审核。个别项目仍可在之后从趋势中删除。 trends: 热门页中会显示正在你服务器上受到关注的嘟文、标签和新闻故事。 - trends_as_landing_page: 向注销的用户和访问者显示热门内容,而不是对该服务器的描述,需要启用热门。 + wrapstodon: 为本站用户提供生成他们过去一年使用 Mastodon 情况的趣味总结的功能。此功能在每年12月10日至12月31日提供给这一年发布过至少1条公开嘟文(无论是否设置为在时间线上显示)及至少使用过1个话题标签的用户。 form_challenge: current_password: 你正在进入安全区域 imports: @@ -137,22 +140,22 @@ zh-CN: name: 你只能改变字母的大小写,让它更易读 terms_of_service: changelog: 可以使用 Markdown 语法。 - effective_date: 合理的时间范围可以是从您通知用户之日起 10 到 30 天。 + effective_date: 合理的时间范围可以是从你通知用户之日起 10 到 30 天。 text: 可以使用 Markdown 语法。 terms_of_service_generator: admin_email: 法务通知包括反通知、法院命令、内容下架要求与执法机关的要求。 arbitration_address: 可以与上面的实际地址相同,如果使用电子邮件则为“N/A”。 arbitration_website: 可以是网页表单,如果使用电子邮件则为“N/A”。 - choice_of_law: 适用内部实质法律以管辖任何及所有索赔的城市、地区、领土或州。 + choice_of_law: 适用内部实质法律以管辖任何及全部索赔的城市、地区、领土或州。 dmca_address: 如果你是位于美国的运营者,请使用在 DMCA 指定代表名录中注册的地址。如果你需要使用邮政信箱,可以直接申请。请使用 DMCA 指定代表邮政信箱豁免申请表,通过电子邮件联系版权办公室,并声明你是居家内容审核员,因担心审核操作会招致报复或打击报复,需要使用邮政信箱以避免公开家庭住址。 dmca_email: 可以与上面“法律声明的电子邮件地址”使用相同的电子邮件地址。 domain: 你所提供的在线服务的唯一标识。 jurisdiction: 请列出支付运营费用者所在的国家/地区。如果为公司或其他实体,请列出其注册的国家/地区以及相应的城市、地区、领地或州。 - min_age: 不应低于您所在地法律管辖权要求的最低年龄。 + min_age: 不应低于你所在地法律管辖权要求的最低年龄。 user: chosen_languages: 仅选中语言的嘟文会出现在公共时间线上(全不选则显示所有语言的嘟文) date_of_birth: - other: 我们必须确保您至少年满 %{count} 岁才能使用 %{domain}。我们不会存储此信息。 + other: 我们必须确保你至少年满 %{count} 岁才能使用 %{domain}。我们不会存储此信息。 role: 角色用于控制用户拥有的权限。 user_role: color: 在界面各处用于标记该角色的颜色,以十六进制 RGB 格式表示 @@ -234,12 +237,12 @@ zh-CN: setting_aggregate_reblogs: 在时间线中合并转嘟 setting_always_send_emails: 总是发送电子邮件通知 setting_auto_play_gif: 自动播放 GIF 动画 - setting_boost_modal: 在转嘟前询问我 + setting_boost_modal: 控制转嘟可见性 setting_default_language: 发布语言 setting_default_privacy: 嘟文可见性 setting_default_quote_policy: 谁可以引用 setting_default_sensitive: 始终标记媒体为敏感内容 - setting_delete_modal: 在删除嘟文前询问我 + setting_delete_modal: 删除嘟文前警告我 setting_disable_hover_cards: 禁用悬停资料预览 setting_disable_swiping: 禁用滑动动作 setting_display_media: 媒体显示 @@ -249,7 +252,8 @@ zh-CN: setting_emoji_style: 表情符号样式 setting_expand_spoilers: 一律展开具有内容警告的嘟文 setting_hide_network: 隐藏你的社交网络 - setting_missing_alt_text_modal: 发布媒体时若未为其设置替代文本,则显示确认对话框 + setting_missing_alt_text_modal: 发送没有设置替代文本的媒体内容前警告我 + setting_quick_boosting: 启用快速转嘟 setting_reduce_motion: 降低过渡动画效果 setting_system_font_ui: 使用系统默认字体 setting_system_scrollbars_ui: 使用系统默认样式的滚动条 @@ -283,12 +287,17 @@ zh-CN: content_cache_retention_period: 外站内容保留期 custom_css: 自定义 CSS favicon: Favicon + landing_page: 新访客的主页 + local_live_feed_access: 展示本站嘟文的实时动态访问权限 + local_topic_feed_access: 展示本站嘟文的话题标签及实时动态访问权限 mascot: 自定义吉祥物(旧) media_cache_retention_period: 媒体缓存保留期 min_age: 最低年龄要求 peers_api_enabled: 在API中公开的已知实例的服务器的列表 profile_directory: 启用用户目录 registrations_mode: 谁可以注册 + remote_live_feed_access: 展示外站嘟文的实时动态访问权限 + remote_topic_feed_access: 展示外站嘟文的话题标签及实时动态访问权限 require_invite_text: 注册时需要提供理由 show_domain_blocks: 显示站点屏蔽列表 show_domain_blocks_rationale: 显示站点屏蔽原因 @@ -301,10 +310,9 @@ zh-CN: status_page_url: 状态页网址 theme: 默认主题 thumbnail: 本站缩略图 - timeline_preview: 允许未登录用户访问公共时间线 trendable_by_default: 允许在未审核的情况下将话题置为热门 trends: 启用热门 - trends_as_landing_page: 使用热门页作为登陆页面 + wrapstodon: 启用 Wrapstodon 年度回顾 interactions: must_be_follower: 屏蔽来自未关注我的用户的通知 must_be_following: 屏蔽来自我未关注的用户的通知 @@ -333,7 +341,7 @@ zh-CN: reblog: 有人转嘟了我的嘟文 report: 有人提交了新举报 software_updates: - all: 通知所有更新 + all: 通知全部更新 critical: 仅在有关键更新时通知 label: 有新的 Mastodon 版本可用 none: 从不通知更新(不推荐) @@ -365,9 +373,9 @@ zh-CN: jurisdiction: 法律管辖区 min_age: 最低年龄 user: - date_of_birth_1i: 日 + date_of_birth_1i: 年 date_of_birth_2i: 月 - date_of_birth_3i: 年 + date_of_birth_3i: 日 role: 角色 time_zone: 时区 user_role: diff --git a/config/locales/simple_form.zh-HK.yml b/config/locales/simple_form.zh-HK.yml index de4a76c0edb66c..7c57864b2e8dea 100644 --- a/config/locales/simple_form.zh-HK.yml +++ b/config/locales/simple_form.zh-HK.yml @@ -70,14 +70,12 @@ zh-HK: featured_tag: name: 以下是你最近常用的標籤: filters: - action: 選擇當帖文符合篩選器時要執行的動作 actions: hide: 完全隱藏被篩選的內容,猶如它不存在般。 warn: 將已篩選的內容隱藏在篩選器標題的警告後面。 form_admin_settings: activity_api_enabled: 每週本站發佈的帖文、活躍使用者及新註冊的數量 backups_retention_period: 使用者可以生成帖文存檔,以便日後下載。如果設定為正值,這些存檔將在指定天數後自動從你的儲存空間中刪除。 - bootstrap_timeline_accounts: 這些帳號會被置頂在新使用者的追蹤建議上。 closed_registrations_message: 關閉註冊時顯示 content_cache_retention_period: 所有來自其他伺服器的帖文(包括轉推和回覆),不論本站使用者有否與這些帖文互動,帖文都將在指定天數後被刪除。這包括本地使用者標記為書籤或加入最愛的帖文。不同站點之間的私人提及也將丟失,並且無法恢復。此設定適用於有特殊用途的站點,一般使用可能會破壞使用者體驗。 custom_css: 你可以在 Mastodon 網頁版套用自訂樣式。 @@ -95,10 +93,8 @@ zh-HK: status_page_url: 可在服務中斷期間,查看此伺服器狀態的網頁網址 theme: 未登入訪客和新使用者看到的主題。 thumbnail: 一幅約 2:1 的圖片顯示在你的伺服器資訊的旁邊。 - timeline_preview: 未登入的訪客能夠瀏覽伺服器上最新的公開帖文。 trendable_by_default: 跳過對趨勢內容的手動審查,事後仍可從趨勢中刪除個別項目。 trends: 趨勢顯示哪些帖文、標籤和新聞故事在你的伺服器上較有吸引力。 - trends_as_landing_page: 向未登入的使用者及訪客展示趨勢內容,而非只有此伺服器的描述。需要啟用趨勢。 form_challenge: current_password: 你正要進入安全區域 imports: @@ -202,10 +198,8 @@ zh-HK: setting_aggregate_reblogs: 時間軸中的群組轉推 setting_always_send_emails: 總是傳送電郵通知 setting_auto_play_gif: 自動播放 GIF - setting_boost_modal: 在轉推前詢問我 setting_default_language: 文章語言 setting_default_sensitive: 預設我的內容為敏感內容 - setting_delete_modal: 刪除文章前,請要求我確認 setting_disable_swiping: 停用滑動手勢 setting_display_media: 媒體顯示 setting_display_media_default: 預設 @@ -261,10 +255,8 @@ zh-HK: status_page_url: 狀態頁網址 theme: 預設主題 thumbnail: 伺服器縮圖 - timeline_preview: 允許未經認證的人存取公共時間軸 trendable_by_default: 允許未經審核的趨勢 trends: 啟用趨勢 - trends_as_landing_page: 使用趨勢作為登陸頁面 interactions: must_be_follower: 隱藏你關注者以外的人的通知 must_be_following: 隱藏你不關注的人的通知 @@ -313,9 +305,7 @@ zh-HK: terms_of_service_generator: domain: 域名 user: - date_of_birth_1i: 日 date_of_birth_2i: 月 - date_of_birth_3i: 年 role: 角色 time_zone: 時區 user_role: diff --git a/config/locales/simple_form.zh-TW.yml b/config/locales/simple_form.zh-TW.yml index 6bf083139446af..f01c2741d3b5a6 100644 --- a/config/locales/simple_form.zh-TW.yml +++ b/config/locales/simple_form.zh-TW.yml @@ -54,8 +54,10 @@ zh-TW: password: 使用至少 8 個字元 phrase: 無論是嘟文的本文或是內容警告都會被過濾 scopes: 允許使應用程式存取的 API。 若您選擇最高階範圍,則無須選擇個別項目。 + setting_advanced_layout: 將 Mastodon 顯示為多欄位介面,使您能檢視時間軸、通知,以及您所選擇之第三欄位。不建議於較小的螢幕使用。 setting_aggregate_reblogs: 不顯示最近已被轉嘟之嘟文的最新轉嘟(只影響最新收到的嘟文) setting_always_send_emails: 一般情況下若您活躍使用 Mastodon ,我們不會寄送電子郵件通知 + setting_boost_modal: 當啟用時,轉嘟前將先開啟確認對話框,您能於其變更轉嘟之可見性。 setting_default_quote_policy_private: Mastodon 上發佈之僅限跟隨者嘟文無法被其他使用者引用。 setting_default_quote_policy_unlisted: 當其他人引用您時,他們的嘟文也會自熱門時間軸隱藏。 setting_default_sensitive: 敏感內容媒體預設隱藏,且按一下即可重新顯示 @@ -63,6 +65,7 @@ zh-TW: setting_display_media_hide_all: 總是隱藏所有媒體 setting_display_media_show_all: 總是顯示標為敏感內容的媒體 setting_emoji_style: 如何顯示 emoji 表情符號。「自動」將嘗試使用原生 emoji ,但於老式瀏覽器使用 Twemoji。 + setting_quick_boosting_html: 當啟用時,點擊 %{boost_icon} 轉嘟圖示將立即轉嘟而非開啟轉嘟/引用之下拉選單。將引用嘟文操作移至 %{options_icon} (選項)選單中。 setting_system_scrollbars_ui: 僅套用至基於 Safari 或 Chrome 之桌面瀏覽器 setting_use_blurhash: 彩色漸層圖樣是基於隱藏媒體內容顏色產生,所有細節將變得模糊 setting_use_pending_items: 關閉自動捲動更新,時間軸僅於點擊後更新 @@ -85,11 +88,12 @@ zh-TW: activity_api_enabled: 本站使用者的嘟文數量,以及本站的活躍使用者與一週內新使用者數量 app_icon: WEBP、PNG、GIF、或 JPG。於行動裝置上使用自訂圖示替代預設應用程式圖示。 backups_retention_period: 使用者能產生他們嘟文的備份以便日後下載。當設定為正值時,這些嘟文備份將於指定之天數後自您的儲存空間中自動刪除。 - bootstrap_timeline_accounts: 這些帳號將被釘選於新帳號跟隨推薦之上。 + bootstrap_timeline_accounts: 這些帳號將被釘選於新帳號跟隨建議之上。請提供以逗號分隔之帳號列表。 closed_registrations_message: 於註冊關閉時顯示 content_cache_retention_period: 所有來自其他伺服器之嘟文(包括轉嘟與回嘟)將於指定之天數後自動刪除,不論這些嘟文與本地使用者間的任何互動。這將包含本地使用者已標記為書籤或最愛之嘟文。不同站點使用者間之私訊亦將遺失且不可回復。此設定應適用於特殊情況,若常規使用將超乎多數使用者預期。 custom_css: 您於 Mastodon 網頁版本中能套用客製化風格。 favicon: WEBP、PNG、GIF、或 JPG。使用自訂圖示替代預設 Mastodon favicon 圖示。 + landing_page: 選擇當新訪客第一次造訪您伺服器時所見之頁面。若您選擇「熱門趨勢」,則該功能必須於探索設定中啟用。若您選擇「本站時間軸」,則探索設定中「允許瀏覽本站嘟文之即時內容」功能必須設定為「任何人」。 mascot: 覆寫進階網頁介面中的圖例。 media_cache_retention_period: 來自遠端伺服器嘟文中之多媒體內容將快取於您的伺服器。當設定為正值時,這些多媒體內容將於指定之天數後自您的儲存空間中自動刪除。若多媒體資料於刪除後被請求,且原始內容仍可存取,它們將被重新下載。由於連結預覽中第三方網站查詢頻率限制,建議將其設定為至少 14 日,否則於此之前連結預覽將不被即時更新。 min_age: 使用者將於註冊時被要求確認他們的生日 @@ -105,10 +109,9 @@ zh-TW: status_page_url: 當服務中斷時,可以提供使用者了解伺服器資訊頁面之 URL theme: 未登入之訪客或新使用者所見之佈景主題。 thumbnail: 大約 2:1 圖片會顯示於您伺服器資訊之旁。 - timeline_preview: 未登入之訪客能夠瀏覽此伺服器上最新的公開嘟文。 - trendable_by_default: 跳過手動審核熱門內容。仍能於登上熱門趨勢後移除個別內容。 - trends: 熱門趨勢將顯示於您伺服器上正在吸引大量注意力的嘟文、主題標籤、或者新聞。 - trends_as_landing_page: 顯示熱門趨勢內容至未登入使用者及訪客而不是關於此伺服器之描述。需要啟用熱門趨勢。 + trendable_by_default: 跳過手動審核熱門內容。您仍能於登上熱門趨勢後移除個別內容。 + trends: 熱門趨勢將顯示於您伺服器上正在吸引大量注意力之嘟文、主題標籤、或新聞。 + wrapstodon: 提供替本站使用者產生他們過去一年使用 Mastodon 的趣味總結。此功能於每年十二月十號至三十一號提供至於年中發過至少一則公開嘟文(無論顯示於時間軸與否)及至少使用一則主題標籤之使用者。 form_challenge: current_password: 您正要進入安全區域 imports: @@ -234,12 +237,12 @@ zh-TW: setting_aggregate_reblogs: 於時間軸中不重複顯示轉嘟 setting_always_send_emails: 總是發送電子郵件通知 setting_auto_play_gif: 自動播放 GIF 動畫 - setting_boost_modal: 轉嘟前先詢問我 + setting_boost_modal: 控制轉嘟可見性 setting_default_language: 嘟文語言 setting_default_privacy: 嘟文可見性 setting_default_quote_policy: 誰能引用此嘟文 setting_default_sensitive: 總是將媒體標記為敏感內容 - setting_delete_modal: 刪除嘟文前先詢問我 + setting_delete_modal: 於刪除嘟文前警告我 setting_disable_hover_cards: 停用於滑鼠懸停時預覽個人檔案 setting_disable_swiping: 停用滑動手勢 setting_display_media: 媒體顯示 @@ -249,7 +252,8 @@ zh-TW: setting_emoji_style: emoji 風格 setting_expand_spoilers: 永遠展開標有內容警告的嘟文 setting_hide_network: 隱藏您的社交網路 - setting_missing_alt_text_modal: 發表未包含說明文字之多媒體嘟文前先詢問我 + setting_missing_alt_text_modal: 於發出未含有 ALT 說明文字之嘟文前警告我 + setting_quick_boosting: 啟用快速轉嘟 setting_reduce_motion: 減少過渡動畫效果 setting_system_font_ui: 使用系統預設字型 setting_system_scrollbars_ui: 使用系統預設捲動軸 @@ -283,12 +287,17 @@ zh-TW: content_cache_retention_period: 遠端內容保留期限 custom_css: 自訂 CSS favicon: 網站圖示 (Favicon) + landing_page: 新訪客之登陸頁面 + local_live_feed_access: 允許瀏覽本站嘟文之即時內容 + local_topic_feed_access: 允許瀏覽本站嘟文之主題標籤與連結 mascot: 自訂吉祥物 (legacy) media_cache_retention_period: 多媒體快取資料保留期間 min_age: 最低年齡要求 peers_api_enabled: 於 API 中公開已知伺服器的列表 profile_directory: 啟用個人檔案目錄 registrations_mode: 誰能註冊 + remote_live_feed_access: 允許瀏覽聯邦宇宙嘟文之即時內容 + remote_topic_feed_access: 允許瀏覽聯邦宇宙嘟文之主題標籤與連結 require_invite_text: 要求「加入原因」 show_domain_blocks: 顯示封鎖的網域 show_domain_blocks_rationale: 顯示網域被封鎖之原因 @@ -301,10 +310,9 @@ zh-TW: status_page_url: 狀態頁面 URL theme: 預設佈景主題 thumbnail: 伺服器縮圖 - timeline_preview: 允許未登入使用者瀏覽公開時間軸 trendable_by_default: 允許熱門趨勢直接顯示,不需經過審核 trends: 啟用熱門趨勢 - trends_as_landing_page: 以熱門趨勢作為登陸頁面 + wrapstodon: 啟用 Mastodon 年度回顧 interactions: must_be_follower: 封鎖非跟隨者的通知 must_be_following: 封鎖您未跟隨之使用者的通知 @@ -348,7 +356,7 @@ zh-TW: tag: listable: 允許此主題標籤於搜尋及個人檔案目錄中顯示 name: 主題標籤 - trendable: 允許此主題標籤於熱門趨勢下顯示 + trendable: 允許此主題標籤於熱門趨勢中顯示 usable: 允許嘟文使用此主題標籤 terms_of_service: changelog: 有何異動? @@ -365,9 +373,9 @@ zh-TW: jurisdiction: 司法管轄區 min_age: 最低年齡 user: - date_of_birth_1i: 日 + date_of_birth_1i: 年 date_of_birth_2i: 月 - date_of_birth_3i: 年 + date_of_birth_3i: 日 role: 角色 time_zone: 時區 user_role: diff --git a/config/locales/sk.yml b/config/locales/sk.yml index 65aca0b47ed1b2..beabe30ba53616 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -14,7 +14,7 @@ sk: other: Sledujúci following: Nasledujem instance_actor_flash: Toto konto je virtuálny aktér, ktorý predstavuje samotný server, a nie konkrétneho používateľa. Používa sa na účely federácie a nemal by byť pozastavený. - last_active: naposledy aktívny + last_active: posledná aktivita link_verified_on: Vlastníctvo tohto odkazu bolo skontrolované %{date} nothing_here: Nič tu nie je! pin_errors: @@ -41,7 +41,7 @@ sk: approve: Schváľ approved_msg: Úspešne schválená prihláška %{username} are_you_sure: Si si istý/á? - avatar: Maskot + avatar: Profilová fotka by_domain: Doména change_email: changed_msg: E-mail úspešne zmenený! @@ -60,13 +60,13 @@ sk: confirmed: Potvrdený confirming: Potvrdzujúci custom: Vlastné - delete: Vymaž dáta + delete: Vymazať dáta deleted: Vymazané demote: Degraduj destroyed_msg: "%{username} je teraz zaradený do fronty na okamžité vymazanie" disable: Zablokuj disable_sign_in_token_auth: Vypni overovanie e-mailovým tokenom - disable_two_factor_authentication: Vypni dvoj-faktorové overovanie + disable_two_factor_authentication: Vypnúť dvojstupňové overenie disabled: Blokovaný display_name: Ukáž meno domain: Doména @@ -119,14 +119,14 @@ sk: push_subscription_expires: PuSH odoberanie expiruje redownload: Obnov profil redownloaded_msg: Úspešne obnovený profil %{username} z pôvodného - reject: Zamietni + reject: Zamietnuť rejected_msg: Úspešne zamietnutá prihláška %{username} remote_suspension_irreversible: Údaje tohto účtu boli nenávratne zmazané. remote_suspension_reversible_hint_html: Účet bol pozastavený na ich serveri a údaje budú úplne odstránené dňa %{date}. Dovtedy môže vzdialený server účet obnoviť bez akýchkoľvek nepriaznivých účinkov. Ak chceš odstrániť všetky údaje účtu ihneď, môžeš tak urobiť nižšie. - remove_avatar: Vymaž avatar - remove_header: Vymaž záhlavie + remove_avatar: Vymazať profilovú fotku + remove_header: Vymazať záhlavie removed_avatar_msg: Úspešne odstránený obrázok avatara %{username} - removed_header_msg: Úspešne odstránený obrázok hlavičky %{username} + removed_header_msg: Obrázok v záhlaví profilu %{username} bol úspešne odstránený resend_confirmation: already_confirmed: Tento užívateľ je už potvrdený send: Odošli potvrdzovací odkaz znovu @@ -142,7 +142,7 @@ sk: only_password: Iba heslo password_and_2fa: Heslo a dvoj-faktorové overovanie sensitive: Citlivé na silu - sensitized: Označený ako chúlostivý + sensitized: Označený ako citlivý shared_inbox_url: URL zdieľanej schránky show: created_reports: Vytvorené hlásenia @@ -190,15 +190,15 @@ sk: create_unavailable_domain: Vytvor nedostupnú doménu create_user_role: Vytvoriť rolu demote_user: Zniž užívateľskú rolu - destroy_announcement: Vymaž oboznámenie + destroy_announcement: Vymazať oznámenie destroy_canonical_email_block: Zruš blokovanie emailu - destroy_custom_emoji: Vymaž vlastné emotikony + destroy_custom_emoji: Vymazať vlastné emotikony destroy_domain_allow: Zmaž povolenie pre doménu destroy_domain_block: Zruš blokovanie domény destroy_instance: Vyčistiť doménu - destroy_ip_block: Vymaž IP pravidlo - destroy_status: Vymaž príspevok - destroy_unavailable_domain: Vymaž nedostupnú doménu + destroy_ip_block: Vymazať IP pravidlo + destroy_status: Vymazať príspevok + destroy_unavailable_domain: Vymazať nedostupnú doménu destroy_user_role: Zničiť rolu disable_2fa_user: Vypni dvoj-faktorové overovanie disable_custom_emoji: Vypni vlastné emotikony @@ -208,14 +208,14 @@ sk: memorialize_account: Zmena na „in memoriam“ promote_user: Povýš užívateľskú rolu publish_terms_of_service: Zverejni podmienky prevozu - reject_appeal: Zamietni námietku - reject_user: Zamietni užívateľa - remove_avatar_user: Vymaž avatar + reject_appeal: Zamietnuť odvolanie + reject_user: Zamietnuť používateľa + remove_avatar_user: Vymazať profilovú fotku reopen_report: Znovu otvor hlásenie resend_user: Preposlať overovací email reset_password_user: Obnov heslo resolve_report: Vyrieš nahlásený problém - sensitive_account: Vynúť všetky médiá na účte ako chúlostivé + sensitive_account: Označiť všetky médiá na účte ako citlivé silence_account: Utíš účet suspend_account: Pozastav účet unassigned_report: Odober priradenie nahlásenia @@ -263,12 +263,12 @@ sk: reopen_report_html: "%{name} znovu otvoril/a nahlásenie %{target}" reset_password_user_html: "%{name} resetoval/a heslo používateľa %{target}" resolve_report_html: "%{name} vyriešil/a nahlásenie %{target}" - sensitive_account_html: "%{name} označil/a médium od %{target} za chúlostivé" + sensitive_account_html: "%{name} uvádza médiá od %{target} ako citlivé" silence_account_html: "%{name} obmedzil/a účet %{target}" suspend_account_html: "%{name} zablokoval/a účet používateľa %{target}" unassigned_report_html: "%{name} odobral/a report od %{target}" unblock_email_account_html: "%{name} odblokoval/a %{target}ovu/inu emailovú adresu" - unsensitive_account_html: "%{name} odznačil/a médium od %{target} ako chúlostivé" + unsensitive_account_html: "%{name} ruší uvádzanie médií od %{target} ako citlivé" unsilence_account_html: "%{name} zrušil/a obmedzenie %{target}ovho/inho účtu" unsuspend_account_html: "%{name} spojazdnil/a účet %{target}" update_announcement_html: "%{name} aktualizoval/a oboznámenie %{target}" @@ -291,6 +291,8 @@ sk: new: create: Vytvor oznam title: Nové oznámenie + preview: + disclaimer: Keďže si používatelia nemôžu e-mailové upozornenia vypnúť, mali by byť využívané iba na dôležité oznamy, napríklad upozornenia o úniku osobných údajov alebo rušení servera. publish: Zverejni published_msg: Oboznámenie úspešne zverejnené! scheduled_for: Načasované na %{time} @@ -307,7 +309,7 @@ sk: copy_failed_msg: Nebolo možné vytvoriť miestnu kópiu tohto emoji create_new_category: Vytvor novú kategóriu created_msg: Emoji úspešne vytvorené! - delete: Vymaž + delete: Vymazať destroyed_msg: Emoji úspešne zničené! disable: Zakáž disabled: Vypnuté @@ -354,7 +356,7 @@ sk: add_new: Povolená doména created_msg: Doména bola úspešne povolená destroyed_msg: Doména bola odstránená zo zoznamu povolených - export: Exportuj + export: Export import: Nahraj undo: Odober zo zoznamu povolených domain_blocks: @@ -370,7 +372,7 @@ sk: domain: Doména edit: Uprav blokovanie domény existing_domain_block_html: Pre účet %{name} si už nahodil/a přísnejšie obmedzenie, najskôr ho teda musíš odblokovať. - export: Exportuj + export: Export import: Nahraj new: create: Vytvor blokovanie domény @@ -393,10 +395,10 @@ sk: undo: Odvolaj blokovanie domény view: Ukáž blokovanie domén email_domain_blocks: - add_new: Pridaj nový + add_new: Pridať nový allow_registrations_with_approval: Povoľ registrovanie so schválením created_msg: Úspešne zablokovaná emailová doména - delete: Vymaž + delete: Vymazať dns: types: mx: MX záznam @@ -411,7 +413,7 @@ sk: export_domain_allows: new: title: Nahraj povolené domény - no_file: Nevybraný žiaden súbor + no_file: Nebol vybraný žiadny súbor export_domain_blocks: import: existing_relationships_warning: Existujúce vzťahy nasledovania @@ -419,7 +421,7 @@ sk: title: Nahraj zákazy domén new: title: Nahraj zákazy domén - no_file: Nevybraný žiaden súbor + no_file: Nebol vybraný žiadny súbor follow_recommendations: description_html: "Odporúčania na sledovanie pomáhaju novým užívateľom rýchlo nájsť zaujímavý obsah. Ak užívateľ zatiaľ nedostatočne interagoval s ostatnými aby si vyformoval personalizované odporúčania na sledovanie, tak mu budú odporúčané tieto účty. Sú prepočítavané na dennej báze z mixu účtov s nedávnym najvyšším záujmom a najvyšším počtom lokálnych sledujúcich pre daný jazyk." language: Pre jazyk @@ -430,6 +432,7 @@ sk: unsuppress: Obnoviť odporúčanie na sledovanie instances: availability: + failure_threshold_reached: Limit neúspešných pokusov bol dosiahnutý %{date}. no_failures_recorded: Žiadne zlyhania nezaznamenané. title: Dostupnosť back_to_all: Všetko @@ -489,7 +492,7 @@ sk: ip_blocks: add_new: Vytvor pravidlo created_msg: Nové IP pravidlo úspešne pridané - delete: Vymaž + delete: Vymazať expires_in: '1209600': 2 týždne '15778476': 6 mesiacov @@ -504,8 +507,8 @@ sk: relationships: title: Vzťahy užívateľa %{acct} relays: - add_new: Pridaj nový federovací mostík - delete: Vymaž + add_new: Pridať nový federovací mostík + delete: Vymazať description_html: "Federovací mostík je prechodný server, ktorý obmieňa veľké množstvá verejných príspevkov medzi tými servermi ktoré na od neho odoberajú, aj doňho prispievajú. Môže to pomôcť malým a stredným instanciám objavovať federovaný obsah, čo inak vyžaduje aby miestni užívatelia ručne následovali iných ľudí zo vzdialených instancií." disable: Vypni disabled: Vypnutý @@ -532,6 +535,7 @@ sk: action_log: Denník auditu action_taken_by: Zákrok vykonal/a actions: + silence_description_html: Účet bude viditeľný iba pre účty, ktoré ho už sledujú alebo si ho vyhľadali ručne, čo výrazne zredukuje jeho dosah. Toto nastavenie je kedykoľvek možné zmeniť. Ukončí všetky hlásenia voči tomuto účtu. suspend_description_html: Tento účet a všetok jeho obsah bude nedostupný a nakoniec zmazaný, interaktovať s ním bude nemožné. Zvrátiteľné v rámci 30 dní. Uzatvára všetky hlásenia voči tomuto účtu. add_to_report: Pridaj viac do hlásenia already_suspended_badges: @@ -548,18 +552,18 @@ sk: confirm: Potvrď confirm_action: Potvrď moderovací úkon proti @%{acct} created_at: Nahlásené - delete_and_resolve: Vymaž príspevky + delete_and_resolve: Vymazať príspevky forwarded: Preposlané forwarded_to: Preposlané na %{domain} mark_as_resolved: Označiť ako vyriešené - mark_as_sensitive: Označ ako chúlostivé + mark_as_sensitive: Označiť ako citlivý obsah mark_as_unresolved: Označ ako nevyriešené no_one_assigned: Nikoho notes: create: Pridaj poznámku create_and_resolve: Vyrieš s poznámkou create_and_unresolve: Otvor znovu, s poznámkou - delete: Vymaž + delete: Vymazať placeholder: Opíš aké opatrenia boli urobené, alebo akékoľvek iné súvisiace aktualizácie… title: Poznámky remote_user_placeholder: vzdialený užívateľ z %{instance} @@ -576,8 +580,9 @@ sk: action_preambles: suspend_html: 'Chystáš sa pozastaviť účet @%{acct}. To urobí:' actions: - delete_html: Vymaž pohoršujúce príspevky - mark_as_sensitive_html: Označ médiá pohoršujúcich príspevkov za chúlostivé + delete_html: Vymazať príspevky porušujúce pravidlá + mark_as_sensitive_html: Označiť médiá v príspevkoch porušujúcich pravidlá ako citlivé + silence_html: Zásadne obmedzí dosah profilu @%{acct} zmenou jeho viditeľnosti spolu s jeho obsahom iba pre ľudí, ktorí ho už sledujú alebo si ho ručne vyhľadali close_report: 'Označ hlásenie #%{id} za vyriešené' target_origin: Pôvod nahláseného účtu title: Hlásenia @@ -598,7 +603,7 @@ sk: invites: Pozvánky moderation: Moderácia special: Špeciálne - delete: Vymaž + delete: Vymazať edit: Uprav postavenie %{name} everyone: Východzie oprávnenia permissions_count: @@ -609,7 +614,7 @@ sk: privileges: administrator: Správca administrator_description: Užívatelia s týmto povolením, obídu všetky povolenia - delete_user_data: Vymaž užívateľské dáta + delete_user_data: Vymazať používateľské dáta invite_users: Pozvi užívateľov manage_announcements: Spravuj oboznámenia manage_appeals: Spravuj námietky @@ -626,7 +631,7 @@ sk: title: Postavenia rules: add_new: Pridaj pravidlo - delete: Vymaž + delete: Vymazať edit: Uprav pravidlo empty: Žiadne pravidlá servera ešte neboli určené. title: Serverové pravidlá @@ -660,9 +665,11 @@ sk: none: Nikto sa nemôže registrovať open: Ktokoľvek sa môže zaregistrovať warning_hint: Odporúčame používať "Pre registráciu je potrebné schválenie", pokiaľ si niesi istý/á, že tvoj moderovací tím vie zvládnuť spam a záškodné registrácie včas. + security: + authorized_fetch_hint: Vyžadovanie overenia od federovaných serverov umožňuje dôkladnejšie vykonávania blokov na úrovni používateľa aj severa. Prináša to so sebou však aj zhoršenie výkonu a dosahu vašich odpovedí a môže spôsobiť problémy s kompatibilitou s niektorými federovanými službami. Okrem toho to nezabráni prístupu k vašim verejným príspevkom a účtom. title: Nastavenia servera site_uploads: - delete: Vymaž nahratý súbor + delete: Vymazať nahratý súbor destroyed_msg: Nahratie bolo zo stránky úspešne vymazané! software_updates: critical_update: Kritické — prosím aktualizuj rýchlo @@ -680,10 +687,10 @@ sk: back_to_account: Späť na účet back_to_report: Späť na stránku hlásenia batch: - remove_from_report: Vymaž z hlásenia + remove_from_report: Vymazať z hlásenia report: Hlásenie deleted: Vymazané - favourites: Obľúbené + favourites: Ohviezdičkovania history: História verzií in_reply_to: Odpoveď na language: Jazyk @@ -717,7 +724,7 @@ sk: action: Pozri tu pre viac informácií tags: review: Prehodnoť stav - updated_msg: Nastavenia haštagov boli úspešne aktualizované + updated_msg: Nastavenia hashtagov boli úspešne aktualizované title: Spravovanie trends: allow: Povoľ @@ -758,10 +765,10 @@ sk: trending: Populárne warning_presets: add_new: Pridaj nové - delete: Vymaž + delete: Vymazať edit_preset: Uprav varovnú predlohu webhooks: - delete: Vymaž + delete: Vymazať disable: Vypni disabled: Vypnuté enable: Povoľ @@ -789,22 +796,19 @@ sk: new_trending_statuses: title: Populárne príspevky new_trending_tags: - title: Populárne haštagy + title: Populárne hashtagy aliases: add_new: Vytvor alias created_msg: Nový alias úspešne vytvorený. Teraz môžeš začať presun zo starého účtu. deleted_msg: Alias úspešne odstránený. Presun z tamtoho účtu na tento už viac nebude možný. remove: Odpoj alias appearance: - advanced_web_interface: Pokročilé webové rozhranie - advanced_web_interface_hint: 'Ak chceš využiť celkovú šírku tvojej obrazovky, pokročilé webové rozhranie ti umožňuje nastaviť mnoho rôznych stĺpcov, aby si videl/a toľko informácií naraz, koľko chceš: Domov, oboznámenia, federovanú časovú os, a ľubovolný počet zoznamov, či haštagov.' animations_and_accessibility: Animácie a prístupnosť - confirmation_dialogs: Potvrdzovacie dialógy - discovery: Nájdenie + discovery: Objavovanie localization: body: Mastodon je prekladaný dobrovoľníkmi. guide_link_text: Prispievať môže každý. - sensitive_content: Chúlostivý obsah + sensitive_content: Citlivý obsah application_mailer: unsubscribe: Prestaň odoberať view: 'Zobraziť:' @@ -813,7 +817,7 @@ sk: applications: created: Aplikácia bola vytvorená úspešne destroyed: Aplikáciu sa podarilo odstrániť - logout: Odhlás sa + logout: Odhlásiť sa regenerate_token: Znovu vygeneruj prístupový token token_regenerated: Prístupový token bol úspešne vygenerovaný znova warning: Na tieto údaje dávaj ohromný pozor. Nikdy ich s nikým nezďieľaj! @@ -827,8 +831,8 @@ sk: login_link: prihlás sa proceed_to_login_html: Teraz môžeš pokračovať na %{login_link}. welcome_title: Vitaj, %{name}! - delete_account: Vymaž účet - delete_account_html: Pokiaľ chceš svoj účet odtiaľto vymazať, môžeš tak urobiť tu. Budeš požiadaný/á o potvrdenie tohto kroku. + delete_account: Odstránenie účtu + delete_account_html: Pokiaľ chcete odstrániť svoj účet, môžete to urobiť tu. Toto rozhodnutie ešte budete musieť potvrdiť. description: prefix_invited_by_user: "@%{name} ťa pozýva na tento Mastodon server!" prefix_sign_up: Zaregistruj sa na Mastodone už dnes! @@ -839,9 +843,9 @@ sk: invalid_reset_password_token: Token na obnovu hesla vypršal. Prosím vypítaj si nový. log_in_with: Prihlás sa s login: Prihlás sa - logout: Odhlás sa - migrate_account: Presúvam sa na iný účet - migrate_account_html: Ak si želáš presmerovať tento účet na nejaký iný, môžeš si to nastaviť tu. + logout: Odhlásiť sa + migrate_account: Presun na iný účet + migrate_account_html: Ak chcete presmerovať tento účet na nejaký iný, môžete to nastaviť tu. or_log_in_with: Alebo prihlás s progress: confirm: Potvrď email @@ -865,8 +869,21 @@ sk: title: Prihlás sa na %{domain} status: account_status: Stav účtu - redirecting_to: Tvoj účet je neaktívny, lebo v súčasnosti presmerováva na %{acct}. + confirming: Čaká sa na dokončenie overenia e-mailu. + functional: Váš účet je funkčný. + pending: Vaša žiadosť je na kontrole naším tímom. Môže to trvať. Po jej prípadnom schválení dostanete e-mail. + redirecting_to: Váš účet je neaktívny, pretože v súčasnosti presmerúva na %{acct}. + self_destruct: Keďže %{domain} končí, budete mať k účtu iba obmedzený prístup. + view_strikes: Zobraziť predošlé sankcie vášho účtu use_security_key: Použi bezpečnostný kľúč + author_attribution: + example_title: Názorný text + hint_html: Píšete novinové alebo blogové články mimo Mastodonu? Rozhodujte o tom, ako sú vám pripisované, keď ich niekto zdieľa na Mastodone. + instructions: 'Vložte tento kód do kódu HTML vo vašom článku:' + more_from_html: Viac od %{name} + s_blog: Blog %{name} + then_instructions: Potom do poľa nižšie zadajte názov domény média. + title: Uvádzanie autorstva challenge: confirm: Pokračuj hint_html: "Tip: Hodinu nebudeme znovu vyžadovať tvoje heslo." @@ -890,7 +907,7 @@ sk: challenge_not_passed: Údaje, ktoré si zadal/a, sú nesprávne confirm_password: Napíš svoje terajšie heslo pre overenie tvojej identity confirm_username: Zadaj svoju prezývku, na potvrdenie úkonu - proceed: Vymaž účet + proceed: Odstrániť účet success_msg: Tvoj účet bol úspešne vymazaný warning: before: 'Než budeš pokračovať, prosím pozorne si prečítaj tieto poznámky:' @@ -913,14 +930,15 @@ sk: reject_appeal: Zamietni námietku title_actions: disable: Zmrazenie účtu - mark_statuses_as_sensitive: Označenie príspevkov za chúlostivé + mark_statuses_as_sensitive: Označenie príspevkov ako citlivé none: Varovanie - sensitive: Označenie účtu ako chúlostivý + sensitive: Označenie účtu ako citlivého silence: Obmedzenie účtu your_appeal_approved: Tvoja námietka bola schválená your_appeal_pending: Odoslal si námietku edit_profile: basic_information: Základné informácie + hint_html: "Upravte, čo ľudia vidia vo vašom verejom profile a pri vašich príspevkoch. S vyplneným profilom a nahratou profilovou fotkou sa zvýšia šance, že vás iní ľudia budú sledovať a budú s vami komunikovať." other: Ostatné errors: '400': Požiadavka, ktorú si odoslal/a, bola buď nesprávna, alebo znehodnotená. @@ -944,9 +962,9 @@ sk: archive_takeout: date: Dátum download: Stiahni si svoj archív - hint_html: Môžeš si vyžiadať archív svojích príspevkov a nahratých médií. Exportované dáta budú v ActivityPub formáte, čítateľné hociakým kompatibilným softvérom. Archív si je možné vyžiadať každých sedem dní. - in_progress: Balím tvoj archív... - request: Vyžiadaj si tvoj archív + hint_html: Môžete si vyžiadať archív svojich príspevkov a nahratých médií. Dáta budú exportované vo formáte ActivityPub, ktorý prečíta ľubovoľný kompatibilný softvér. Archív si je možné vyžiadať každých sedem dní. + in_progress: Prebieha kompilácia archívu… + request: Požiadať o archív size: Veľkosť blocks: Blokujete bookmarks: Záložky @@ -955,9 +973,10 @@ sk: mutes: Stíšil/a si storage: Úložisko médií featured_tags: - add_new: Pridaj nový + add_new: Pridať nový errors: - limit: Už si si predvolil/a najvyšší možný počet haštagov + limit: Už ste si predvolili najvyšší možný počet hashtagov + hint_html: "Zobrazte vo svojom profile vaše najvýznamnejšie hashtagy. Zvýraznené hashtagy majú viditeľné miesto vo vašom profile a umožňujú vám rýchly prístup k vašim príspevkom, aby ste mali svoje diela a dlhodobé projekty vždy poruke." filters: contexts: account: Profily @@ -968,29 +987,29 @@ sk: edit: add_keyword: Pridaj kľúčové slovo keywords: Kľúčové slová - title: Uprav triedenie + title: Upraviť filter errors: invalid_context: Nebola poskytnutá žiadna, alebo ide o neplatnú súvislosť index: - delete: Vymaž + delete: Vymazať empty: Nemáš žiadné filtrovanie. expires_on: Expiruje dňa %{date} - title: Triedenia + title: Filtre new: save: Uložiť nový filter - title: Pridaj nové triedenie + title: Pridať nový filter statuses: batch: remove: Odstrániť z filtrovania generic: all: Všetko cancel: Zruš - changes_saved_msg: Zmeny boli úspešne uložené! + changes_saved_msg: Zmeny boli úspešne uložené. confirm: Potvrď copy: Kopíruj - delete: Vymaž - order_by: Zoraď podľa - save_changes: Ulož zmeny + delete: Vymazať + order_by: Zoradiť podľa + save_changes: Uložiť zmeny today: dnes validation_errors: few: Niečo ešte nieje celkom v poriadku! Prosím skontroluj %{count} chýb uvedených nižšie @@ -1004,11 +1023,11 @@ sk: failures: Zlyhaní(a) imported: Nahrané modes: - merge: Spoj dohromady - merge_long: Ponechaj existujúce záznamy a pridaj k nim nové - overwrite: Prepíš - overwrite_long: Nahraď súčasné záznamy novými - preface: Môžeš nahrať dáta ktoré si exportoval/a z iného Mastodon serveru, ako sú napríklad zoznamy ľudí ktorých sleduješ, alebo blokuješ. + merge: Pridať + merge_long: Ponechať existujúce záznamy a pridať k nim nové + overwrite: Prepísať + overwrite_long: Nahradiť súčasné záznamy novými + preface: Môžete importovať dáta exportované z iného serveru na Mastodone, napríklad zoznam sledovaných alebo blokovaných účtov. recent_imports: Nedávne nahrania states: finished: Dokončené @@ -1021,7 +1040,7 @@ sk: lists: Nahrávanie zoznamov type_groups: constructive: Sledovania a záložky - destructive: Blokovania a utíšenia + destructive: Blokovania a stíšenia types: blocking: Zoznam blokovaných bookmarks: Záložky @@ -1057,7 +1076,7 @@ sk: title: Pozvi ľudí lists: errors: - limit: Dosiahli ste maximálny počet zoznamov + limit: Bol dosiahnutý maximálny počet zoznamov login_activities: authentication_methods: password: heslom @@ -1065,6 +1084,9 @@ sk: title: História overení mail_subscriptions: unsubscribe: + emails: + notification_emails: + reblog: e-mailové upozornenia na zdieľania title: Ukonči odber media_attachments: validations: @@ -1082,8 +1104,8 @@ sk: not_found: nebolo možné nájsť on_cooldown: Si v spánkovom stave followers_count: Následovatelia v čase presunu - incoming_migrations: Presúvam sa z iného účtu - incoming_migrations_html: K presunutiu z iného účtu na tento, si najskôr potrebuješ vytvoriť alias pre účet. + incoming_migrations: Presun z iného účtu + incoming_migrations_html: Ak sa chcete na tento účet presunúť z iného účtu, najprv si vytvorte nový alias. moved_msg: Tvoj účet teraz presmerováva na %{acct} a tvoji sledovatelia sú presúvaní tam. not_redirecting: Tvoj účet v súčasnosti nepresmerováva na žiaden iný účet. on_cooldown: Účet si si presunul/a len nedávno. Táto vymoženosť bude znovu sprístupnená za %{count} dní. @@ -1099,7 +1121,6 @@ sk: disabled_account: Tvoj súčasný účet už po tomto nebude plne použiteľný. Stále ale budeš mať prístup ku stiahnutiu dát a možnosti znovu-aktivácie. followers: Tento úkon presunie všetkých následovateľov zo súčasného účtu na nový účet only_redirect_html: Ako alternatívu, môžeš iba nastaviť presmerovanie na tvoj profil. - other_data: Žiadne iné dáta nebudú presunuté automaticky redirect: Tvoj súčastný účet bude aktualizovaný s oznamom o presunutí a bude vylúčený z vyhľadávania moderation: title: Moderovanie @@ -1108,12 +1129,12 @@ sk: carry_mutes_over_text: Tento užívateľ sa presunul z účtu %{acct}, ktorý si mal/a stíšený. notification_mailer: favourite: - body: 'Tvoj príspevok bol obľúbený užívateľom %{name}:' + body: "%{name} hviezdičkuje váš príspevok:" subject: "%{name} si obľúbil/a tvoj príspevok" - title: Novo obľúbené + title: Nové ohviezdičkovanie follow: - body: "%{name} ťa teraz nasleduje!" - subject: "%{name} ťa teraz nasleduje" + body: "%{name} vás teraz sleduje." + subject: "%{name} vás teraz sleduje" title: Nový sledovateľ follow_request: action: Spravuj žiadosti o sledovanie @@ -1128,15 +1149,17 @@ sk: poll: subject: Anketa od %{name} skončila reblog: - body: 'Tvoj príspevok bol vyzdvihnutý užívateľom %{name}:' - subject: "%{name} vyzdvihli tvoj príspevok" - title: Novo vyzdvyhnuté + body: "%{name} zdieľa váš príspevok:" + subject: "%{name} zdieľa váš príspevok" + title: Nové zdieľanie status: subject: "%{name} práve prispel/a" update: subject: "%{name} upravil/a príspevok" notifications: - email_events_hint: 'Vyber si udalosti, pre ktoré chceš dostávať oboznámenia:' + administration_emails: Administrátorské e-mailové upozornenia + email_events: Udalosti pre e-mailové upozornenia + email_events_hint: 'Vyberte udalosti, pre ktoré chcete dostávať upozornenia:' otp_authentication: enable: Povoľ pagination: @@ -1157,11 +1180,16 @@ sk: too_many_options: nemôže zahŕňať viac ako %{max} položiek preferences: other: Ostatné - posting_defaults: Východiskové nastavenia príspevkov + posting_defaults: Predvolené nastavenia príspevkov public_timelines: Verejné časové osi privacy: + hint_html: "Upravte si spôsob nachádzania vášho profilu a príspevkov. Tieto funkcie vám môžu pomôcť osloviť väčšie publikum na Mastodone. Skontrolujte tieto nastavenia, aby bolo všetko tak, ako to chcete." privacy: Súkromie + privacy_hint_html: Rozhodujte o tom, koľko svojej aktivity chcete ukázať ostatným. Ľudia nachádzajú zaujímavé profily a aplikácie aj v profiloch iných, v zoznamoch sledovateľov a sledovaných. Máte možnosť ich skryť. + reach: Dosah + reach_hint_html: Rozhodnite o tom, či chcete aby vás iní ľudia nachádzali a sledovali. Chcete, aby sa vaše príspevky zobrazovali vo feede Objavovanie? Chcete, aby bol váš účet zobrazovaný ostatným v návrhoch na sledovanie? Chcete automatický schváliť všetky sledovania vás alebo o nich chcete jednotlivo rozhodovať? search: Vyhľadávanie + search_hint_html: Rozhodujte o tom, ako je možné vás nájsť. Chcete, aby vás ľudia mohli nájsť cez vaše verejné príspevky? Chcete, aby ľudia mimo Mastodonu mohli nachádzať váš profil pri prehľadávaní internetu? Upozorňujeme, že verejné informácie nie je možné úplne odizolovať od vyhľadávačov. title: Súkromie a dosah privacy_policy: title: Pravidlá ochrany súkromia @@ -1177,21 +1205,21 @@ sk: confirm_follow_selected_followers: Si si istý/á, že chceš nasledovať vybraných sledujúcich? confirm_remove_selected_followers: Si si istý/á, že chceš odstrániť vybraných sledovateľov? confirm_remove_selected_follows: Si si istý/á, že chceš odstrániť vybraných sledovaných? - dormant: Spiace + dormant: Neaktívne follow_failure: Nemožno nasledovať niektoré z vybraných účtov. follow_selected_followers: Následuj označených sledovatelov - followers: Sledovatelia - following: Nasledovaní + followers: Sledujú vás + following: Sledované vami invited: Pozvaný/á - last_active: Naposledy aktívny + last_active: Naposledy aktívne most_recent: Najnovšie - moved: Presunuli sa - mutual: Spoločné + moved: Presunuté + mutual: Vzájomné primary: Hlavné relationship: Vzťah - remove_selected_domains: Vymaž všetkých následovateľov z vybraných domén + remove_selected_domains: Vymazať všetky sledujúce účty z vybraných domén remove_selected_followers: Odstráň vybraných následovatrľov - remove_selected_follows: Prestaň sledovať vybraných užívateľov + remove_selected_follows: Zrušiť sledovanie vybraných účtov status: Stav účtu remote_follow: missing_resource: Nemožno nájsť potrebnú presmerovaciu adresu k tvojmu účtu @@ -1204,7 +1232,7 @@ sk: over_daily_limit: Prekročil/a si denný limit %{limit} predplánovaných príspevkov over_total_limit: Prekročil/a si limit %{limit} predplánovaných príspevkov sessions: - activity: Najnovšia aktivita + activity: Posledná aktivita browser: Prehliadač browsers: chrome: Google Chrome @@ -1216,10 +1244,10 @@ sk: safari: Apple Safari unknown_browser: Neznámy prehliadač weibo: Sina/Tencent Weibo - current_session: Aktuálna sezóna + current_session: Vaše aktuálne prihlásenie date: Dátum description: "%{browser} na %{platform}" - explanation: Tieto sú prehliadače ktoré sú teraz prihlásené na tvoj Mastodon účet. + explanation: Tieto prehliadače sú práve prihlásené do vášho účtu na Mastodone. ip: IP adresa platforms: ios: Apple iOS @@ -1227,9 +1255,10 @@ sk: mac: MacOSX unknown_platform: Neznáma platforma windows: Microsoft Windows - revoke: Zamietni - revoke_success: Sezóna úspešne zamietnutá - title: Sezóny + revoke: Zrušiť + revoke_success: Prihlásenie bolo zrušené + title: Aktívne prihlásenia + view_authentication_history: Zobraziť históriu prihlásení účtu settings: account: Účet account_settings: Nastavenia účtu @@ -1239,19 +1268,30 @@ sk: back: Späť na Mastodon delete: Vymazanie účtu development: Vývoj - edit_profile: Uprav profil - featured_tags: Zvýraznené haštagy - import: Importuj + edit_profile: Úprava profilu + featured_tags: Zvýraznené hashtagy + import: Import import_and_export: Import a export migrate: Presuň účet - preferences: Voľby + notifications: E-mailové upozornenia + preferences: Predvoľby profile: Profil - relationships: Sledovania a následovatelia - two_factor_authentication: Dvojfázové overenie + relationships: Sledované a sledujúce účty + severed_relationships: Prerušené vzťahy + statuses_cleanup: Automatické mazanie príspevkov + two_factor_authentication: Dvojstupňové overenie webauthn_authentication: Bezpečnostné kľúče severed_relationships: - lost_followers: Stratení nasledovatelia - lost_follows: Stratené sledovania + download: Stiahnuť (%{count}) + event_type: + account_suspension: Pozastavenie účtu (%{target_name}) + domain_block: Pozastavenie servera (%{target_name}) + user_domain_block: Zablokovali ste %{target_name} + lost_followers: Zrušenie sledovania účtami + lost_follows: Zrušenie sledovania účtov + preamble: Pri blokovaní domény alebo pri pozastavení pripojenia s iným serverom moderátorským tímom vášho servera môžete prísť o sledované a sledujúce účty. Keď sa to stane, tu si môžete stiahnuť zoznam prerušených vzťahov, ktorý môžete skontrolovať a prípadne importovať na inom serveri. + purged: Informácie o tomto serveri boli odstránené administrátorským tímom vášho servera. + type: Udalosť statuses: attached: description: 'Priložené: %{attached}' @@ -1260,14 +1300,14 @@ sk: many: "%{count} obrázkov" one: "%{count} obrázok" other: "%{count} obrázky" - boosted_from_html: Vyzdvihnuté od %{acct_link} + boosted_from_html: Zdieľané od %{acct_link} content_warning: 'Varovanie o obsahu: %{warning}' default_language: Rovnaký ako jazyk rozhrania disallowed_hashtags: - few: 'obsah nepovolených haštagov: %{tags}' - many: 'obsah nepovolených haštagov: %{tags}' - one: 'obsahoval nepovolený haštag: %{tags}' - other: 'obsahoval nepovolené haštagy: %{tags}' + few: 'obsahoval nepovolené hashtagy: %{tags}' + many: 'obsahoval nepovolené hashtagy: %{tags}' + one: 'obsahoval nepovolený hashtag: %{tags}' + other: 'obsahoval nepovolené hashtagy: %{tags}' edited_at_html: Upravené %{date} errors: in_reply_not_found: Príspevok, na ktorý sa snažíš odpovedať, pravdepodobne neexistuje. @@ -1275,29 +1315,50 @@ sk: pin_errors: limit: Už si si pripol ten najvyšší možný počet hlášok ownership: Nieje možné pripnúť hlášku od niekoho iného - reblog: Vyzdvihnutie sa nedá pripnúť + reblog: Zdieľanie nie je možné pripnúť title: '%{name}: „%{quote}"' visibilities: + direct: Súkromné označenie + private: Iba pre sledujúce účty public: Verejné + public_long: Ktokoľvek na Mastodone aj mimo neho + unlisted: Tiché verejné + unlisted_long: Skryté z výsledkov vyhľadávania, populárnych tém a verejných časových osí na Mastodone statuses_cleanup: + enabled: Automaticky mazať staré príspevky + enabled_hint: Automaticky vymaže vaše príspevky po dosiahnutí stanoveného veku, pokiaľ nespadajú do niektorej z výnimiek nižšie exceptions: Výnimky - ignore_favs: Ignoruj obľúbené - ignore_reblogs: Ignoruj vyzdvihnutia - keep_direct: Ponechaj súkromné správy - keep_pinned: Ponechaj pripnuté príspevky - keep_pinned_hint: Nevymaže žiadne s tvojich pripnutých príspevkov - keep_polls: Ponechaj ankety - keep_self_bookmark: Ponechaj príspevky, ktoré sú záložkami - keep_self_fav: Ponechať príspevky, ktoré si si obľúbil/a + explanation: Keďže je mazanie príspevkov drahým procesom, bude sa to diať pomaly a postupne v časoch, keď server nie je inak vyťažovaný. Preto môže príspevkom po dosiahnutí stanoveného veku ešte chvíľu trvať, než budú vymazané. + ignore_favs: Ignorovať hviezdičky + ignore_reblogs: Ignorovať zdieľania + interaction_exceptions: Výnimky podľa interakcií + interaction_exceptions_explanation: Vymazanie príspevkov nie je zaručené, pokiaľ klesnú pod limit hviezdičkovaní alebo zdieľaní až po kontrole. + keep_direct: Ponechať súkromné správy + keep_direct_hint: Vaše súkromné správy nebudú vymazané + keep_media: Ponechať príspevky s médiami + keep_media_hint: Vaše príspevky s mediálnymi prílohami nebudú vymazané + keep_pinned: Ponechať pripnuté príspevky + keep_pinned_hint: Vaše pripnuté príspevky nebudú vymazané + keep_polls: Ponechať ankety + keep_polls_hint: Vaše ankety nebudú vymazané + keep_self_bookmark: Ponechať príspevky označené záložkou + keep_self_bookmark_hint: Vaše príspevky označené záložkou nebudú vymazané + keep_self_fav: Ponechať ohviezdičkované príspevky + keep_self_fav_hint: Vaše ohviezdičkované príspevky nebudú vymazané min_age: '1209600': 2 týždne - '15778476': 6 mesačné - '2629746': 1 mesačné - '31556952': 1 ročné - '5259492': 2 mesačné + '15778476': 6 mesiacov + '2629746': 1 mesiac + '31556952': 1 rok + '5259492': 2 mesiace '604800': 1 týždeň - '63113904': 2 ročné - '7889238': 3 mesačné + '63113904': 2 roky + '7889238': 3 mesiace + min_age_label: Časová hranica + min_favs: Ponechať príspevky s aspoň týmto počtom hviezdičiek + min_favs_hint: Vaše príspevky, ktoré majú aspoň tento počet hviezdičiek, nebudú vymazané. Nevypĺňajte, pokiaľ chcete mazať príspevky bez ohľadu na ich počet hviezdičiek + min_reblogs: Ponechať príspevky s aspoň týmto počtom zdieľaní + min_reblogs_hint: Vaše príspevky, ktoré majú aspoň tento počet zdieľaní, nebudú vymazané. Nevypĺňajte, pokiaľ chcete mazať príspevky bez ohľadu na ich počet zdieľaní stream_entries: sensitive_content: Senzitívny obsah tags: @@ -1309,18 +1370,25 @@ sk: time: formats: default: "%b %d, %R, %H:%M" + with_time_zone: "%b %d, %Y, %H:%M %Z" translation: errors: too_many_requests: V poslednej dobe bolo na prekladateľskú službu vykonaných priveľa požiadaviek. two_factor_authentication: - disable: Zakáž - enabled: Dvojfázové overovanie je povolené - enabled_success: Dvojfázové overovanie úspešne povolené - generate_recovery_codes: Vygeneruj zálohové kódy - lost_recovery_codes: Zálohové kódy ti umožnia dostať sa k svojmu účtu ak stratíš telefón. Pokiaľ si stratila svoje zálohové kódy, môžeš si ich tu znovu vygenerovať. Tvoje staré zálohové kódy budú zneplatnené. - recovery_codes: Zálohuj kódy pre obnovu - recovery_codes_regenerated: Zálohové kódy boli úspešne zvova vygenerované - recovery_instructions_html: Keď hocikedy stratíš prístup k svojmu telefónu, môžeš použiť jeden z prístupových kódov nižšie pre obnovenie prístupu k svojmu účtu. Skladuj tieto prístupové kódy na bezpečnom mieste. Napríklad ich môžeš vytlačiť a uložiť ich spolu s inými dôležitými dokumentami. + add: Pridať + disable: Vypnúť dvojstupňové overenie + disabled_success: Dvojstupňové overenie bolo úspešne vypnuté + edit: Upraviť + enabled: Dvojstupňové overovanie je zapnuté + enabled_success: Dvojstupňové overovanie bolo úspešne zapnuté + generate_recovery_codes: Vygenerovať záložné kódy + lost_recovery_codes: Záložné kódy vás umožnia prístup k účtu v prípade straty mobilného telefónu. Pokiaľ ste svoje zálohové kódy stratili, môžete si tu vygenerovať nové. Staré zálohové kódy budú deaktivované. + methods: Metódy dvojstupňového overenia + otp: Overovacia apka + recovery_codes: Zálohovať záložné kódy + recovery_codes_regenerated: Záložné kódy boli úspešne znova vygenerované + recovery_instructions_html: Ak niekedy prídete o prístup k svojmu mobilnému telefónu, prístup k účtu môžete obnoviť použitím jedného zo záložných kódov nižšie. Záložné kódy si bezpečne uložte. Môžete si ich napríklad vytlačiť a založiť k iným dôležitým dokumentom. + webauthn: Bezpečnostné kľúče user_mailer: appeal_approved: action: Nastavenia účtu @@ -1331,7 +1399,7 @@ sk: backup_ready: explanation: Vyžiadal/a si si úplnú zálohu svojho Mastodon účtu. extra: Teraz je pripravená na stiahnutie! - subject: Tvoj archív je pripravený na stiahnutie + subject: Váš archív je pripravený na stiahnutie title: Odber archívu failed_2fa: details: 'Tu sú podrobnosti o pokuse o prihlásenie:' @@ -1349,9 +1417,9 @@ sk: title: delete_statuses: Príspevky vymazané disable: Účet bol zamrazený - mark_statuses_as_sensitive: Príspevky označené za chúlostivé + mark_statuses_as_sensitive: Príspevky označené ako citlivé none: Varovanie - sensitive: Účet označený za chúlostivý + sensitive: Účty označené ako citlivé silence: Účet bol obmedzený suspend: Tvoj účet bol vylúčený welcome: @@ -1360,9 +1428,11 @@ sk: apps_step: Stiahni naše oficiálne aplikácie. apps_title: Mastodon aplikácie edit_profile_action: Prispôsob + edit_profile_step: Naštartuje svoje interakcie vyplnením svojho profilu. edit_profile_title: Prispôsob si svoj profil explanation: Tu nájdeš nejaké tipy do začiatku feature_action: Zisti viac + feature_creativity: Mastodon vám pri tvorbe obsahu a vyjadrovaní sa online umožňuje používať audio, video, obrázky a fotky, opisy pre zleplšenie prístupnosti, ankety, varovania o obsahu, animované profilové fotky, vlastné emoji, upravené náhľady a ďalšie. Nech už chcete zverejňovať svoje vizuálne diela, hudbu či podcasty, Mastodon je tu pre vás. follow_action: Nasleduj follow_title: Prispôsob svoj domáci kanál follows_title: Koho nasledovať @@ -1371,10 +1441,15 @@ sk: subject: Vitaj na Mastodone title: Vitaj na palube, %{name}! users: - follow_limit_reached: Nemôžeš nasledovať viac ako %{limit} ľudí + follow_limit_reached: Nemôžete sledovať viac ako %{limit} ľudí invalid_otp_token: Neplatný kód pre dvojfaktorovú autentikáciu otp_lost_help_html: Pokiaľ si stratil/a prístup k obom, môžeš dať vedieť %{email} rate_limited: Príliš veľa pokusov o overenie, skús to znova neskôr. signed_in_as: 'Prihlásená/ý ako:' verification: + extra_instructions_html: Tip: Odkaz na vašom webe môže byť neviditeľný. Najdôležitejšia časť je rel="me", zabraňuje tomu, že sa za vás bude niekto vydávať na weboch s obsahom generovaným používateľmi. V záhlaví stránky môžete dokonca namiesto tagu a použiť tag link, ale kód HTML musí byť prístupný bez potreby spustenia JavaScriptu. + here_is_how: Ako na to + hint_html: "Každý na Mastodone môže overiť svoju totožnosť. Okamžite, zadarmo a navždy s využitím otvorených webových štandardov. Potrebujete len svoju vlastnú webovú stránku. Keď odkaz na ňu uvediete vo svojom profile, skontrolujeme, či je prepojená s vaším profilom a úspešné overenie zvýrazníme vizuálnym indikátorom." + instructions_html: Skopírujte kód nižšie a vložte ho do kódu HTML svojho webu. Potom pridajte svoju webovú stránku do jedného z extra polí v karte Úprava profilu a uložte zmeny. verification: Overenie + website_verification: Overenie webu diff --git a/config/locales/sl.yml b/config/locales/sl.yml index 123e8c47c07be0..4cbd1eb43ba013 100644 --- a/config/locales/sl.yml +++ b/config/locales/sl.yml @@ -495,6 +495,24 @@ sl: new: title: Uvozi blokade domen no_file: Nobena datoteka ni izbrana + fasp: + debug: + callbacks: + delete: Izbriši + ip: Naslov IP + providers: + base_url: Osnovna povezava + callback: Povratni klic + delete: Izbriši + edit: Uredi ponudnika + finish_registration: Dokončaj registracijo + name: Ime + registrations: + confirm: Potrdi + reject: Zavrni + save: Shrani + sign_in: Prijava + status: Stanje follow_recommendations: description_html: "Sledi priporočilom pomaga novim uporabnikom, da hitro najdejo zanimivo vsebino. Če uporabnik ni dovolj komuniciral z drugimi, da bi oblikoval prilagojena priporočila za sledenje, se namesto tega priporočajo ti računi. Dnevno se ponovno izračunajo iz kombinacije računov z najvišjimi nedavnimi angažiranostmi in najvišjim številom krajevnih sledilcev za določen jezik." language: Za jezik @@ -569,6 +587,9 @@ sl: all: Vse limited: Omejeno title: Moderiranje + moderation_notes: + description_html: Pokaži in pusti opombe drugim moderatorjem in sebi v prihodnosti + title: Opombe moderiranja private_comment: Zasebni komentar public_comment: Javni komentar purge: Očisti @@ -783,11 +804,16 @@ sl: title: Vloge rules: add_new: Dodaj pravilo + add_translation: Dodaj prevod delete: Izbriši description_html: Večina trdi, da so prebrali in da se strinjajo s pogoji rabe storitve, vendar le-teh ponavadi ne preberejo, dokler ne pride do težav. Poenostavite in naredite pravila svojega strežnika vidna na prvi pogled tako, da jih izpišete v označenem seznamu.Posamezna pravila skušajte ohraniti kratka in enostavna, ne razbijajte pa jih v preveč različnih točk. edit: Uredi pravilo empty: Zaenkrat še ni opredeljenih pravil. + move_down: Premakni navzdol + move_up: Premakni navzgor title: Pravila strežnika + translation: Prevod + translations: Prevodi settings: about: manage_rules: Upravljaj pravila strežnika @@ -812,7 +838,7 @@ sl: title: Privzeto izvzemi uporabnike iz indeksiranja iskalnika discovery: follow_recommendations: Sledi priporočilom - preamble: Izpostavljanje zanimivih vsebin je ključno za pridobivanje novih uporabnikov, ki morda ne poznajo nikogar na Mastodonu. Nadzirajte, kako različne funkcionalnosti razkritja delujejo na vašem strežniku. + privacy: Zasebnost profile_directory: Imenik profilov public_timelines: Javne časovnice publish_statistics: Objavi statistiko @@ -822,6 +848,9 @@ sl: all: Vsem disabled: Nikomur users: Prijavljenim krajevnim uporabnikom + landing_page: + values: + trends: Trendi registrations: moderation_recommandation: Preden prijave odprete za vse poskrbite, da imate v ekipi moderatorjev zadosti aktivnih članov. preamble: Nadzirajte, kdo lahko ustvari račun na vašem strežniku. @@ -875,6 +904,7 @@ sl: no_status_selected: Nobena objava ni bila spremenjena, ker ni bila nobena izbrana open: Odpri objavo original_status: Izvorna objava + quotes: Citati reblogs: Ponovljeni blogi replied_to_html: V odgovor %{acct_link} status_changed: Objava spremenjena @@ -1071,6 +1101,15 @@ sl: two: Uporabili %{count} osebi v zadnjem tednu title: Priporočila in trendi trending: V porastu + username_blocks: + add_new: Dodaj novo + comparison: + contains: Vsebuje + contains_html: Vsebuje %{string} + delete: Izbriši + new: + create: Ustvari pravilo + not_permitted: Ni dovoljeno warning_presets: add_new: Dodaj novo delete: Izbriši @@ -1145,10 +1184,8 @@ sl: hint_html: Če se želite preseliti iz drugega računa v tega, lahko tukaj ustvarite vzdevek, ki je potreben, preden lahko nadaljujete s selitvijo sledilcev iz starega računa v tega. To dejanje je samo po sebi neškodljivo in povratno. Selitev računa sprožite iz starega računa. remove: Razveži vzdevek appearance: - advanced_web_interface: Napredni spletni vmesnik - advanced_web_interface_hint: 'Če želite uporabiti celotno širino zaslona, vam napredni spletni vmesnik omogoča, da si nastavite več različnih stolpcev in da si hkrati ogledate toliko informacij, kot želite: domačo stran, obvestila, združeno časovnico, poljubno število seznamov in ključnikov.' + advanced_settings: Napredne nastavitve animations_and_accessibility: Animacije in dostopnost - confirmation_dialogs: Potrditvena okna discovery: Odkrito localization: body: Mastodon prevajamo prostovoljci. @@ -1337,6 +1374,8 @@ sl: basic_information: Osnovni podatki hint_html: "Prilagodite, kaj ljudje vidijo na vašem javnem profilu in poleg vaših objav. Drugi vam bodo raje sledili nazaj in z vami klepetali, če boste imeli izpolnjen profil in nastavljeno profilno sliko." other: Drugo + emoji_styles: + auto: Samodejno errors: '400': Zahteva, ki ste jo oddali, je neveljavna ali nepravilno oblikovana. '403': Nimate dovoljenja za ogled te strani. @@ -1586,6 +1625,11 @@ sl: expires_at: Poteče uses: Uporabe title: Povabite ljudi + link_preview: + author_html: Avtor/ica %{name} + potentially_sensitive_content: + action: Kliknite za prikaz + hide_button: Skrij lists: errors: limit: Dosegli ste največje število seznamov @@ -1650,7 +1694,6 @@ sl: disabled_account: Vaš trenutni račun zatem ne bo polno uporaben. Vendar pa boste imeli dostop do izvoza podatkov kot tudi do ponovne aktivacije. followers: S tem dejanjem boste preselili vse sledilce iz trenutnega računa na novi račun only_redirect_html: Namesto tega lahko na svojem profilu zgolj vzpostavite preusmeritev. - other_data: Nobeni drugi podatki se ne bodo preselili samodejno redirect: Profil vašega trenutnega računa bo posodobljen z obvestilom o preusmeritvi in bo izključen iz iskanj moderation: title: Moderiranje @@ -1686,6 +1729,9 @@ sl: title: Nova omemba poll: subject: Anketa, ki jo je pripravil/a %{name}, se je iztekla + quote: + body: 'Vašo objavo je citiral/a %{name}:' + title: Nov citat reblog: body: 'Vašo objavo je izpostavil/a %{name}:' subject: "%{name} je izpostavil/a vašo objavo" @@ -1734,6 +1780,7 @@ sl: self_vote: Ne morete glasovati v lastnih anketah too_few_options: mora imeti več kot en element too_many_options: ne more vsebovati več kot %{max} elementov + vote: Glasuj preferences: other: Ostalo posting_defaults: Privzete nastavitev objavljanja @@ -1895,6 +1942,9 @@ sl: two: "%{count} video posnetka" boosted_from_html: Izpostavljeno z računa %{acct_link} content_warning: 'Opozorilo o vsebini: %{warning}' + content_warnings: + hide: Skrij objavo + show: Pokaži več default_language: Enak kot jezik vmesnika disallowed_hashtags: few: 'vsebuje nedovoljene ključnike: %{tags}' @@ -1910,9 +1960,19 @@ sl: limit: Pripeli ste največje število objav ownership: Objava nekoga drugega ne more biti pripeta reblog: Izpostavitev ne more biti pripeta + quote_error: + not_available: Objava ni na voljo + quote_policies: + followers: Samo sledilci + nobody: Samo jaz + public: Vsi title: "%{name}: »%{quote}«" visibilities: + direct: Zasebna omemba + private: Samo sledilci public: Javno + public_long: Vsem, ki so ali niso na Mastodonu + unlisted: Tiho javno statuses_cleanup: enabled: Samodejno izbriši stare objave enabled_hint: Samodejno izbriše vaše objave, ko dosežejo določen starostni prag, razen če ne ustrezajo eni od spodnjih izjem @@ -1957,6 +2017,8 @@ sl: does_not_match_previous_name: se ne ujema s prejšnjim imenom terms_of_service: title: Pogoji uporabe + terms_of_service_interstitial: + title: Spreminjajo se pogoji uporabe domene %{domain} themes: contrast: Mastodon (Visok kontrast) default: Mastodon (Temna) diff --git a/config/locales/sq.yml b/config/locales/sq.yml index 4b1cf77cb6c520..6c64d4dec53a5f 100644 --- a/config/locales/sq.yml +++ b/config/locales/sq.yml @@ -7,6 +7,8 @@ sq: hosted_on: Server Mastodon i strehuar në %{domain} title: Mbi accounts: + errors: + cannot_be_added_to_collections: Kjo llogari s’mund të shtohet në koleksione. followers: one: Ndjekës other: Ndjekës @@ -190,6 +192,7 @@ sq: create_relay: Krijoni Rele create_unavailable_domain: Krijo Përkatësi të Papërdorshme create_user_role: Krijoni Rol + create_username_block: Krijoni Rregull Emrash Përdoruesish demote_user: Zhgradoje Përdoruesin destroy_announcement: Fshije Lajmërimin destroy_canonical_email_block: Fshini Bllokim Email-esh @@ -203,6 +206,7 @@ sq: destroy_status: Fshi Gjendje destroy_unavailable_domain: Fshi Përkatësi të Papërdorshme destroy_user_role: Asgjësoje Rolin + destroy_username_block: Fshini Rregull Emrash Përdoruesish disable_2fa_user: Çaktivizo 2FA-në disable_custom_emoji: Çaktivizo Emotikon Vetjak disable_relay: Çaktivizoje Relenë @@ -237,6 +241,7 @@ sq: update_report: Përditësoni Raportimin update_status: Përditëso Gjendjen update_user_role: Përditësoni Rol + update_username_block: Përditësoni Rregull Emrash Përdoruesish actions: approve_appeal_html: "%{name} miratoi apelim vendimi moderimi nga %{target}" approve_user_html: "%{name} miratoi regjistrim nga %{target}" @@ -255,6 +260,7 @@ sq: create_relay_html: "%{name} krijoi një rele %{target}" create_unavailable_domain_html: "%{name} ndali dërgimin drejt përkatësisë %{target}" create_user_role_html: "%{name} krijoi rolin %{target}" + create_username_block_html: "%{name} shtoi rregull për emra përdoruesish që përmbajnë %{target}" demote_user_html: "%{name} zhgradoi përdoruesin %{target}" destroy_announcement_html: "%{name} fshiu lajmërimin për %{target}" destroy_canonical_email_block_html: "%{name} zhbllokoi email me hashin %{target}" @@ -268,6 +274,7 @@ sq: destroy_status_html: "%{name} hoqi gjendje nga %{target}" destroy_unavailable_domain_html: "%{name} rinisi dërgimin drejt përkatësisë %{target}" destroy_user_role_html: "%{name} fshiu rolin %{target}" + destroy_username_block_html: "%{name} hoqi rregull për emra përdoruesish që përmbajnë %{target}" disable_2fa_user_html: "%{name} çaktivizoi domosdoshmërinë për dyfaktorësh për përdoruesin %{target}" disable_custom_emoji_html: "%{name} çaktivizoi emoxhin %{target}" disable_relay_html: "%{name} çaktivizoi relenë %{target}" @@ -302,6 +309,7 @@ sq: update_report_html: "%{name} përditësoi raportimin %{target}" update_status_html: "%{name} përditësoi gjendjen me %{target}" update_user_role_html: "%{name} ndryshoi rolin për %{target}" + update_username_block_html: "%{name} përditësoi rregull për emra përdoruesish që përmbajnë %{target}" deleted_account: fshiu llogarinë empty: S’u gjetën regjistra. filter_by_action: Filtroji sipas veprimit @@ -505,6 +513,7 @@ sq: select_capabilities: Përzgjidhni Aftësi sign_in: Hyni status: Gjendje + title: Shërbyes Shërbimesh Ndihmëse Fediversi title: FASP follow_recommendations: description_html: "Rekomandimet për ndjekje ndihmojnë përdoruesit e rinj të gjejnë shpejt lëndë me interes. Kur një përdorues nuk ka ndërvepruar mjaftueshëm me të tjerët, që të formohen rekomandime të personalizuara ndjekjeje, rekomandohen këto llogari. Ato përzgjidhen çdo ditë, prej një përzierje llogarish me shkallën më të lartë të angazhimit dhe numrin më të lartë të ndjekësve vendorë për një gjuhë të dhënë." @@ -824,17 +833,28 @@ sq: title: Lëri, si parazgjedhje, përdoruesit jashtë indeksimi nga motorë kërkimesh discovery: follow_recommendations: Rekomandime ndjekjeje - preamble: Shpërfaqja e lëndës interesante është me rëndësi kyçe për mirëseardhjen e përdoruesve të rinj që mund të mos njohin njeri në Mastodon. Kontrolloni se si funksionojnë në shërbyesin tuaj veçori të ndryshme zbulimi. + preamble: Shpërfaqja e lëndës interesante është me rëndësi për mirëseardhjen e përdoruesve të rinj, të cilët mund të mos njohin njeri në Mastodon. Kontrolloni se funksionojnë në shërbyesin tuaj veçori të ndryshme zbulimi lënde. privacy: Privatësi profile_directory: Drejtori profilesh public_timelines: Rrjedha kohore publike publish_statistics: Publiko statistika title: Zbulim trends: Në modë + wrapstodon: Përmbledhjedon domain_blocks: all: Për këdo disabled: Për askënd users: Për përdorues vendorë që kanë bërë hyrjen + feed_access: + modes: + authenticated: Vetëm përdorues të mirëfilltësuar + disabled: Lyp doemos rol specifik përdoruesi + public: Kushdo + landing_page: + values: + about: Mbi + local_feed: Prurje vendore + trends: Në modë registrations: moderation_recommandation: Ju lutemi, sigurohuni si keni një ekip adekuat dhe reagues moderimi, përpara se të hapni regjistrimet për këdo! preamble: Kontrolloni cilët mund të krijojnë llogari në shërbyesin tuaj. @@ -888,6 +908,7 @@ sq: no_status_selected: S’u ndryshua ndonjë gjendje, ngaqë s’u përzgjodh ndonjë e tillë open: Hape postimin original_status: Postim origjinal + quotes: Ctime reblogs: Riblogime replied_to_html: Iu përgjigj %{acct_link} status_changed: Postimi ndryshoi @@ -895,6 +916,7 @@ sq: title: Postime llogarie - @%{name} trending: Në modë view_publicly: Shiheni publikisht + view_quoted_post: Shihni postimin e cituar visibility: Dukshmëri with_media: Me media strikes: @@ -1075,6 +1097,25 @@ sq: other: Përdorur nga %{count} vetë gjatë javës së kaluar title: Rekomandime & Prirje trending: Në modë + username_blocks: + add_new: Shtoni të ri + block_registrations: Blloko regjistrimet + comparison: + contains: Përmban + equals: Është baras me + contains_html: Përmban %{string} + created_msg: U krijua me sukses rregull emrash përdoruesish + delete: Fshije + edit: + title: Përpunoni rregull emrash përdoruesi + matches_exactly_html: Baras me %{string} + new: + create: Krijoni rregull + title: Krijoni rregull të ri emrash përdoruesish + no_username_block_selected: S’u ndryshua ndonjë rregull emrash përdoruesishm ngaqë s’u përzgjodh ndonjë + not_permitted: Jo i lejuar + title: Rregulla emrash përdoruesish + updated_msg: Rregulli i emrave të përdoruesve u përditësua me sukses warning_presets: add_new: Shtoni të ri delete: Fshije @@ -1147,10 +1188,10 @@ sq: hint_html: Nëse doni të kaloni nga një llogari tjetër në këtë këtu, këtu mund të krijoni një alias, i cili është i domosdoshëm përpara se të ecni më tej me kalimin e ndjekësve prej llogarisë së vjetër te kjo këtu. Ky veprim, në vetvete, është i padëmshëm dhe i prapakthyeshëm. Migrimi i llogarisë fillohet prej llogarisë së vjetër. remove: Hiqe aliasin appearance: - advanced_web_interface: Ndërfaqe web e thelluar - advanced_web_interface_hint: 'Nëse doni të shfrytëzoni krejt gjerësinë e ekranit tuaj, ndërfaqja e thelluar web ju lejon të formësoni shumë shtylla për të parë në të njëjtën kohë aq hollësi sa doni: Kreu, njoftime, rrjedhë kohore të federuarash, çfarëdo numri listash dhe hashtag-ësh.' + advanced_settings: Rregullime të mëtejshme animations_and_accessibility: Animacione dhe përdorim nga persona me aftësi të kufizuara - confirmation_dialogs: Dialogë ripohimesh + boosting_preferences: Parapëlqime përforcimesh + boosting_preferences_info_html: "Ndihmëz: Pavarësisht rregullimeve, Shift + Klikim mbi ikonën e Përforcimeve %{icon} do të bëjë menjëherë përforcim." discovery: Zbulim localization: body: Mastodon-i përkthehet nga vullnetarë. @@ -1548,6 +1589,13 @@ sq: expires_at: Skadon më uses: Përdorime title: Ftoni njerëz + link_preview: + author_html: Nga %{name} + potentially_sensitive_content: + action: Klikoni për shfaqje + confirm_visit: Jeni i sigurt se doni të hapet kjo lidhje? + hide_button: Fshihe + label: Lëndë potencalisht me spec lists: errors: limit: Keni mbërritur në numrin maksimum të listave @@ -1612,7 +1660,7 @@ sq: disabled_account: Llogaria juaj e tanishme s’do të jetë plotësisht e përdorshme, pas kësaj. Megjithatë, do të mund të bëni eksportim të dhënash, si dhe riaktivizim. followers: Ky veprim do të kalojë krejt ndjekësit prej llogarisë së tanishme te llogaria e re only_redirect_html: Ndryshe, mund të ujdisni një ridrejtim vetëm te profili juaj. - other_data: S’do të lëvizen të dhëna të tjera automatikisht + other_data: S’do të lëvizen automatikisht të dhëna të tjera (përfshi postimet tuaja dhe listën e llogarive që ndiqni) redirect: Profili i llogarisë tuaj të tanishme do të përditësohet me një shënim ridrejtimi dhe do të përjashtohet prej kërkimesh moderation: title: Moderim @@ -1646,12 +1694,22 @@ sq: body: 'U përmendët nga %{name} në:' subject: U përmendët nga %{name} title: Përmendje e re + moderation_warning: + subject: Ju është dhënë një sinjalizim moderimi poll: subject: Përfundoi një pyetësor nga %{name} + quote: + body: 'Postimi juaj u citua nga %{name}:' + subject: "%{name} citoi postimin tuaj" + title: Citim i ri + quoted_update: + subject: "%{name} përpunoi një postim që keni cituar" reblog: body: 'Gjendja juaj u përforcua nga %{name}:' subject: "%{name} përforcoi gjendjen tuaj" title: Përforcim i ri + severed_relationships: + subject: Keni humbur lidhjet, për shkak të një vendimi moderimi status: subject: "%{name} sapo postoi" update: @@ -1696,6 +1754,9 @@ sq: self_vote: S’mund të votoni në pyetësorët tuaj too_few_options: duhet të ketë më tepër se një element too_many_options: s’mund të përmbajë më tepër se %{max} elementë + vote: Votoni + posting_defaults: + explanation: Këto rregullime do të përdoren si parazgjedhje, kur krijoni postime të reja, por mund t’i përpunoni për postim, brenda hartuesit. preferences: other: Tjetër posting_defaults: Parazgjedhje postimesh @@ -1851,6 +1912,9 @@ sq: other: "%{count} video" boosted_from_html: Përforcuar nga %{acct_link} content_warning: 'Sinjalizim lënde: %{warning}' + content_warnings: + hide: Fshihe postimin + show: Shfaq më tepër default_language: Njësoj me gjuhën e ndërfaqes disallowed_hashtags: one: 'përmbante një hashtag të palejuar: %{tags}' @@ -1858,15 +1922,30 @@ sq: edited_at_html: Përpunuar më %{date} errors: in_reply_not_found: Gjendja të cilës po provoni t’i përgjigjeni s’duket se ekziston. + quoted_status_not_found: Postimi që po rrekeni të citoni nuk duket se ekziston. + quoted_user_not_mentioned: S’mund të citohet një përdorues që s’është përmendur në një postim Përmendje Private. over_character_limit: u tejkalua kufi shenjash prej %{max} pin_errors: direct: Postimet që janë të dukshme vetëm për përdoruesit e përmendur s’mund të fiksohen limit: Keni fiksuar tashmë numrin maksimum të mesazheve ownership: S’mund të fiksohen mesazhet e të tjerëve reblog: S’mund të fiksohet një përforcim + quote_error: + not_available: Postim që s’mund të kihet + pending_approval: Postim pezull + revoked: Postim i hequr nga autori + quote_policies: + followers: Vetëm ndjekës + nobody: Thjesht unë + public: Cilido + quote_post_author: U citua një postim nga %{acct} title: '%{name}: "%{quote}"' visibilities: + direct: Përmendje private + private: Vetëm ndjekës public: Publike + public_long: Cilido që hyn e del në Mastodon + unlisted_long: Fshehur nga përfundime kërkimi në Mastodon, rrjedha kohore gjërash në modë dhe publike statuses_cleanup: enabled: Fshi automatikisht postime të vjetra enabled_hint: Fshin automatikisht postimet tuaja, pasi mbërrijnë një prag të caktuar moshe, hiq rastin kur ka përputhje me një nga përjashtimet më poshtë @@ -2093,3 +2172,5 @@ sq: not_supported: Ky shfletues nuk mbulon kyçe sigurie otp_required: Që të përdoren kyçe sigurie, ju lutemi, së pari aktivizoni mirëfilltësimin dyfaktorësh. registered_on: Regjistruar më %{date} + wrapstodon: + description: Shihni si u përdor Mastodon-in këtë vit nga %{name}! diff --git a/config/locales/sr-Latn.yml b/config/locales/sr-Latn.yml index 3173aa34384761..3ea914a016f149 100644 --- a/config/locales/sr-Latn.yml +++ b/config/locales/sr-Latn.yml @@ -745,7 +745,6 @@ sr-Latn: title: Podrazumevano isključi korisnike iz indeksiranja pretraživača discovery: follow_recommendations: Preporuke za praćenje - preamble: Održavanje zanimljivih sadržaja na površini je ključno u privlačenju novih korisnika koji možda ne znaju nikoga na Mastodon-u. Kontrolišite kako različiti načini istraživanja funkcionišu na vašem serveru. profile_directory: Direktorijum profilâ public_timelines: Javne vremenske linije publish_statistics: Objavi statistiku @@ -1003,10 +1002,7 @@ sr-Latn: hint_html: Ako želite da se preselite sa drugog naloga na ovaj, ovde možete napraviti pseudonim, koji je neophodan pre nego što možete nastaviti sa prebacivanjem pratilaca sa starog naloga na ovaj. Ova radnja sama po sebi je bezopasna i reverzibilna. Preseljenje naloga se inicira sa starog naloga. remove: Odveži pseudonim appearance: - advanced_web_interface: Napredno veb okruženje - advanced_web_interface_hint: 'Ako želite da iskoristite celu širinu ekrana, napredno veb okruženje vam omogućuje da konfigurišete mnogo različitih kolona da biste videli onoliko informacija u isto vreme koliko želite: početnu stranicu, obaveštenja, združenu vremensku liniju, bilo koji broj lista i heš oznaka.' animations_and_accessibility: Animacije i pristupačnost - confirmation_dialogs: Dijalozi potvrde discovery: Otkrivanje localization: body: Mastodon prevode dobrovoljci. @@ -1402,7 +1398,6 @@ sr-Latn: disabled_account: Vaš trenutni nalog više neće biti upotrebljiv. Međutim, imaćete pristup izvozu podataka kao i reaktivaciji. followers: Ova radnja će premestiti sve pratioce sa trenutnog naloga na novi nalog only_redirect_html: Umesto preseljenja, možete samo dodati preusmeravajući link na svoj profil.. - other_data: Ostali podaci neće biti automatski prebačeni redirect: Profil Vašeg sadašnjeg naloga će biti ažuriran sa obaveštenjem o preusmerenju i biće isključen iz pretrage moderation: title: Moderacija diff --git a/config/locales/sr.yml b/config/locales/sr.yml index 9fc9ed255f79ea..22ba9f0791de5a 100644 --- a/config/locales/sr.yml +++ b/config/locales/sr.yml @@ -775,7 +775,6 @@ sr: title: Подразумевано искључи кориснике из индексирања претраживача discovery: follow_recommendations: Препоруке за праћење - preamble: Одржавање занимљивих садржаја на површини је кључно у привлачењу нових корисника који можда не знају никога на Mastodon-у. Контролишите како различити начини истраживања функционишу на вашем серверу. profile_directory: Директоријум профилâ public_timelines: Јавне временске линије publish_statistics: Објави статистику @@ -1033,10 +1032,7 @@ sr: hint_html: Ако желите да се преселите са другог налога на овај, овде можете направити псеудоним, који је неопходан пре него што можете наставити са пребацивањем пратилаца са старог налога на овај. Ова радња сама по себи је безопасна и реверзибилна. Пресељење налога се иницира са старог налога. remove: Одвежи псеудоним appearance: - advanced_web_interface: Напредно веб окружење - advanced_web_interface_hint: 'Ако желите да искористите целу ширину екрана, напредно веб окружење вам омогућује да конфигуришете много различитих колона да бисте видели онолико информација у исто време колико желите: почетну страницу, обавештења, здружену временску линију, било који број листа и хеш ознака.' animations_and_accessibility: Анимације и приступачност - confirmation_dialogs: Дијалози потврде discovery: Откривање localization: body: Mastodon преводе добровољци. @@ -1432,7 +1428,6 @@ sr: disabled_account: Ваш тренутни налог више неће бити употребљив. Међутим, имаћете приступ извозу података као и реактивацији. followers: Ова радња ће преместити све пратиоце са тренутног налога на нови налог only_redirect_html: Уместо пресељења, можете само додати преусмеравајући линк на свој профил.. - other_data: Остали подаци неће бити аутоматски пребачени redirect: Профил Вашег садашњег налога ће бити ажуриран са обавештењем о преусмерењу и биће искључен из претраге moderation: title: Модерација diff --git a/config/locales/sv.yml b/config/locales/sv.yml index 4a844f8a941864..1f6d68750cb5bc 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -796,6 +796,8 @@ sv: view_dashboard_description: Ger användare tillgång till instrumentpanelen och olika mätvärden view_devops: DevOps view_devops_description: Ger användare tillgång till instrumentpanelerna Sidekiq och pgHero + view_feeds: Visa live- och ämnesflöden + view_feeds_description: Ger användare tillgång till live-och ämnesflöden oavsett serverinställningar title: Roller rules: add_new: Lägg till regel @@ -837,17 +839,28 @@ sv: title: Undantag användare från sökmotorindexering som standard discovery: follow_recommendations: Följrekommendationer - preamble: Att visa intressant innehåll är avgörande i onboarding av nya användare som kanske inte känner någon på Mastodon. Styr hur olika upptäcktsfunktioner fungerar på din server. + preamble: Att visa intressant innehåll är avgörande för nya användare som kanske inte känner någon på Mastodon. Styr hur olika upptäcktsfunktioner fungerar på din Server. privacy: Integritet profile_directory: Profilkatalog public_timelines: Offentliga tidslinjer publish_statistics: Publicera statistik title: Upptäck trends: Trender + wrapstodon: Wrapstodon domain_blocks: all: Till alla disabled: För ingen users: För inloggade lokala användare + feed_access: + modes: + authenticated: Endast autentiserade användare + disabled: Kräv specifik användarroll + public: Alla + landing_page: + values: + about: Om + local_feed: Lokalt flöde + trends: Trender registrations: moderation_recommandation: Se till att du har ett tillräckligt och reaktivt modereringsteam innan du öppnar registreringar till alla! preamble: Kontrollera vem som kan skapa ett konto på din server. @@ -901,6 +914,7 @@ sv: no_status_selected: Inga inlägg ändrades eftersom inga valdes open: Öppna inlägg original_status: Ursprungligt inlägg + quotes: Citat reblogs: Ombloggningar replied_to_html: Svarade på %{acct_link} status_changed: Inlägg ändrat @@ -908,6 +922,7 @@ sv: title: Kontoinlägg - @%{name} trending: Trendande view_publicly: Visa offentligt + view_quoted_post: Visa citerat inlägg visibility: Synlighet with_media: Med media strikes: @@ -1182,10 +1197,10 @@ sv: hint_html: Om du vill flytta från ett annat konto till detta kan du skapa ett alias här, detta krävs innan du kan fortsätta med att flytta följare från det gamla kontot till detta. Denna åtgärd är ofarlig och kan ångras. Kontomigreringen initieras från det gamla kontot.. remove: Avlänka alias appearance: - advanced_web_interface: Avancerat webbgränssnitt - advanced_web_interface_hint: 'Om du vill utnyttja hela skärmens bredd så kan du i det avancerade webbgränssnittet ställa in många olika kolumner för att se så mycket information samtidigt som du vill: Hem, notiser, federerad tidslinje, valfritt antal listor och hashtaggar.' + advanced_settings: Avancerade inställningar animations_and_accessibility: Animationer och tillgänglighet - confirmation_dialogs: Bekräftelsedialoger + boosting_preferences: Boostinställningar + boosting_preferences_info_html: "Tips: Oavsett inställningar kommer Skifttangenten + Klick på %{icon} boostikonen att boosta omedelbart." discovery: Upptäck localization: body: Mastodon översätts av volontärer. @@ -1587,6 +1602,13 @@ sv: expires_at: Utgår uses: Användningar title: Bjud in andra + link_preview: + author_html: Av %{name} + potentially_sensitive_content: + action: Klicka för att visa + confirm_visit: Är du säker på att du vill öppna den här länken? + hide_button: Dölj + label: Potentiellt känsligt innehåll lists: errors: limit: Du har nått det maximala antalet listor @@ -1651,7 +1673,7 @@ sv: disabled_account: Ditt nuvarande konto kommer inte att kunna användas fullt ut efteråt. Du kommer dock att ha tillgång till dataexport samt återaktivering. followers: Den här åtgärden kommer att flytta alla följare från det nuvarande kontot till det nya kontot only_redirect_html: Alternativt kan du bara sätta upp en omdirigering på din profil. - other_data: Ingen annan data kommer att flyttas automatiskt + other_data: Inga andra data kommer att flyttas automatiskt (inklusive dina inlägg och listan över konton du följer) redirect: Ditt nuvarande kontos profil kommer att uppdateras med ett meddelande om omdirigering och uteslutas från sökningar moderation: title: Moderera @@ -1740,6 +1762,8 @@ sv: too_few_options: måste ha mer än ett objekt too_many_options: kan inte innehålla mer än %{max} objekt vote: Rösta + posting_defaults: + explanation: Dessa inställningar kommer att användas som standard när du skapar nya inlägg, men du kan redigera dem per inlägg i kompositören. preferences: other: Annat posting_defaults: Standardinställningar för inlägg @@ -1895,6 +1919,9 @@ sv: other: "%{count} videor" boosted_from_html: Boostad från %{acct_link} content_warning: 'Innehållsvarning: %{warning}' + content_warnings: + hide: Dölj inlägg + show: Visa mer default_language: Samma som användargränssnittet disallowed_hashtags: one: 'innehöll en otillåten hashtag: %{tags}' @@ -1903,15 +1930,30 @@ sv: errors: in_reply_not_found: Inlägget du försöker svara på verkar inte existera. quoted_status_not_found: Inlägget du försöker svara på verkar inte existera. + quoted_user_not_mentioned: Kan inte citera en icke-omnämnt användare i ett privat omnämnandeinlägg. over_character_limit: teckengräns på %{max} har överskridits pin_errors: direct: Inlägg som endast är synliga för nämnda användare kan inte fästas limit: Du har redan fäst det maximala antalet inlägg ownership: Någon annans inlägg kan inte fästas reblog: En boost kan inte fästas + quote_error: + not_available: Inlägg ej tillgängligt + pending_approval: Väntande inlägg + revoked: Inlägg borttaget av författaren + quote_policies: + followers: Endast följare + nobody: Bara jag + public: Alla + quote_post_author: Citerade ett inlägg av %{acct} title: '%{name}: "%{quote}"' visibilities: + direct: Privat omnämnande + private: Endast följare public: Offentlig + public_long: Alla på och utanför Mastodon + unlisted: Offentlig (begränsad) + unlisted_long: Dold från Mastodon-sökresultat, trendar och offentliga tidslinjer statuses_cleanup: enabled: Ta automatiskt bort gamla inlägg enabled_hint: Raderar dina inlägg automatiskt när de når en specifik ålder, såvida de inte matchar något av undantagen nedan diff --git a/config/locales/th.yml b/config/locales/th.yml index 9f4aeab65ffff0..ce17782c2f0429 100644 --- a/config/locales/th.yml +++ b/config/locales/th.yml @@ -69,7 +69,7 @@ th: email_status: สถานะอีเมล enable: เลิกอายัด enable_sign_in_token_auth: เปิดใช้งานการรับรองความถูกต้องด้วยโทเคนอีเมล - enabled: เปิดใช้งานอยู่ + enabled: เปิดใช้งานแล้ว enabled_msg: เลิกอายัดบัญชีของ %{username} สำเร็จ followers: ผู้ติดตาม follows: การติดตาม @@ -92,7 +92,7 @@ th: moderation: active: ใช้งานอยู่ all: ทั้งหมด - disabled: ปิดใช้งานอยู่ + disabled: ปิดใช้งานแล้ว pending: รอดำเนินการ silenced: จำกัดอยู่ suspended: ระงับอยู่ @@ -187,6 +187,7 @@ th: create_relay: สร้างรีเลย์ create_unavailable_domain: สร้างโดเมนที่ไม่พร้อมใช้งาน create_user_role: สร้างบทบาท + create_username_block: สร้างกฎชื่อผู้ใช้ demote_user: ลดขั้นผู้ใช้ destroy_announcement: ลบประกาศ destroy_canonical_email_block: ลบการปิดกั้นอีเมล @@ -200,6 +201,7 @@ th: destroy_status: ลบโพสต์ destroy_unavailable_domain: ลบโดเมนที่ไม่พร้อมใช้งาน destroy_user_role: ทำลายบทบาท + destroy_username_block: ลบกฎชื่อผู้ใช้ disable_2fa_user: ปิดใช้งาน 2FA disable_custom_emoji: ปิดใช้งานอีโมจิที่กำหนดเอง disable_relay: ปิดใช้งานรีเลย์ @@ -234,6 +236,7 @@ th: update_report: อัปเดตรายงาน update_status: อัปเดตโพสต์ update_user_role: อัปเดตบทบาท + update_username_block: อัปเดตกฎชื่อผู้ใช้ actions: approve_appeal_html: "%{name} ได้อนุมัติการอุทธรณ์การตัดสินใจในการกลั่นกรองจาก %{target}" approve_user_html: "%{name} ได้อนุมัติการลงทะเบียนจาก %{target}" @@ -333,11 +336,11 @@ th: delete: ลบ destroyed_msg: ทำลายอีโมโจสำเร็จ! disable: ปิดใช้งาน - disabled: ปิดใช้งานอยู่ + disabled: ปิดใช้งานแล้ว disabled_msg: ปิดใช้งานอีโมจินั้นสำเร็จ emoji: อีโมจิ enable: เปิดใช้งาน - enabled: เปิดใช้งานอยู่ + enabled: เปิดใช้งานแล้ว enabled_msg: เปิดใช้งานอีโมจินั้นสำเร็จ image_hint: PNG หรือ GIF สูงสุด %{size} list: แสดงรายการ @@ -481,6 +484,7 @@ th: save: บันทึก sign_in: ลงชื่อเข้า status: สถานะ + title: FASP follow_recommendations: description_html: "คำแนะนำการติดตามช่วยให้ผู้ใช้ใหม่ค้นหาเนื้อหาที่น่าสนใจได้อย่างรวดเร็ว เมื่อผู้ใช้ไม่ได้โต้ตอบกับผู้อื่นมากพอที่จะสร้างคำแนะนำการติดตามเฉพาะบุคคล จะแนะนำบัญชีเหล่านี้แทน จะคำนวณคำแนะนำใหม่เป็นประจำทุกวันจากบัญชีต่าง ๆ ที่มีการมีส่วนร่วมล่าสุดสูงสุดและจำนวนผู้ติดตามในเซิร์ฟเวอร์สูงสุดสำหรับภาษาที่กำหนด" language: สำหรับภาษา @@ -588,10 +592,10 @@ th: delete: ลบ description_html: "รีเลย์การติดต่อกับภายนอก เป็นเซิร์ฟเวอร์ตัวกลางที่แลกเปลี่ยนโพสต์สาธารณะจำนวนมากระหว่างเซิร์ฟเวอร์ที่บอกรับและเผยแพร่ไปยังรีเลย์ รีเลย์สามารถช่วยให้เซิร์ฟเวอร์ขนาดเล็กและขนาดกลางค้นพบเนื้อหาจากจักรวาลสหพันธ์ ซึ่งมิฉะนั้นจะต้องให้ผู้ใช้ในเซิร์ฟเวอร์ติดตามผู้คนอื่น ๆ ในเซิร์ฟเวอร์ระยะไกลด้วยตนเอง" disable: ปิดใช้งาน - disabled: ปิดใช้งานอยู่ + disabled: ปิดใช้งานแล้ว enable: เปิดใช้งาน enable_hint: เมื่อเปิดใช้งาน เซิร์ฟเวอร์ของคุณจะบอกรับโพสต์สาธารณะทั้งหมดจากรีเลย์นี้ และจะเริ่มส่งโพสต์สาธารณะของเซิร์ฟเวอร์นี้ไปยังรีเลย์ - enabled: เปิดใช้งานอยู่ + enabled: เปิดใช้งานแล้ว inbox_url: URL ของรีเลย์ pending: กำลังรอการอนุมัติของรีเลย์ save_and_enable: บันทึกและเปิดใช้งาน @@ -782,7 +786,6 @@ th: title: เลือกให้ผู้ใช้ไม่รับการทำดัชนีโดยเครื่องมือค้นหาเป็นค่าเริ่มต้น discovery: follow_recommendations: คำแนะนำการติดตาม - preamble: การแสดงเนื้อหาที่น่าสนใจเป็นเครื่องมือในการเตรียมความพร้อมให้ผู้ใช้ใหม่ที่อาจไม่รู้จักใครก็ตามใน Mastodon ควบคุมวิธีที่คุณลักษณะการค้นพบต่าง ๆ ทำงานในเซิร์ฟเวอร์ของคุณ privacy: ความเป็นส่วนตัว profile_directory: ไดเรกทอรีโปรไฟล์ public_timelines: เส้นเวลาสาธารณะ @@ -793,6 +796,14 @@ th: all: ให้กับทุกคน disabled: ให้กับไม่มีใคร users: ให้กับผู้ใช้ในเซิร์ฟเวอร์ที่เข้าสู่ระบบ + feed_access: + modes: + public: ทุกคน + landing_page: + values: + about: เกี่ยวกับ + local_feed: ฟีดในเซิร์ฟเวอร์ + trends: แนวโน้ม registrations: moderation_recommandation: โปรดตรวจสอบให้แน่ใจว่าคุณมีทีมการกลั่นกรองที่เพียงพอและมีปฏิกิริยาตอบสนองก่อนที่คุณจะเปิดการลงทะเบียนให้กับทุกคน! preamble: ควบคุมผู้ที่สามารถสร้างบัญชีในเซิร์ฟเวอร์ของคุณ @@ -1020,6 +1031,14 @@ th: other: ใช้โดย %{count} คนในช่วงสัปดาห์ที่ผ่านมา title: คำแนะนำและแนวโน้ม trending: กำลังนิยม + username_blocks: + delete: ลบ + edit: + title: แก้ไขกฎชื่อผู้ใช้ + new: + create: สร้างกฎ + title: สร้างกฎชื่อผู้ใช้ใหม่ + title: กฎชื่อผู้ใช้ warning_presets: add_new: เพิ่มใหม่ delete: ลบ @@ -1031,7 +1050,7 @@ th: delete: ลบ description_html: "เว็บฮุค ทำให้ Mastodon สามารถส่ง การแจ้งเตือนตามเวลาจริง แบบผลักเกี่ยวกับเหตุการณ์ที่เลือกไปยังแอปพลิเคชันของคุณเอง ดังนั้นแอปพลิเคชันของคุณสามารถ ทริกเกอร์การตอบสนองได้โดยอัตโนมัติ" disable: ปิดใช้งาน - disabled: ปิดใช้งานอยู่ + disabled: ปิดใช้งานแล้ว edit: แก้ไขปลายทาง empty: คุณยังไม่ได้กำหนดค่าปลายทางเว็บฮุคใด ๆ enable: เปิดใช้งาน @@ -1091,10 +1110,8 @@ th: hint_html: หากคุณต้องการย้ายจากบัญชีอื่นไปยังบัญชีนี้ ที่นี่คุณสามารถสร้างนามแฝง ซึ่งจำเป็นก่อนที่คุณจะสามารถดำเนินการต่อด้วยการย้ายผู้ติดตามจากบัญชีเก่าไปยังบัญชีนี้ การกระทำนี้โดยตัวการกระทำเอง ไม่เป็นอันตรายและย้อนกลับได้ การโยกย้ายบัญชีเริ่มต้นจากบัญชีเก่า remove: เลิกเชื่อมโยงนามแฝง appearance: - advanced_web_interface: ส่วนติดต่อเว็บขั้นสูง - advanced_web_interface_hint: 'หากคุณต้องการใช้ประโยชน์จากความกว้างหน้าจอทั้งหมดของคุณ ส่วนติดต่อเว็บขั้นสูงอนุญาตให้คุณกำหนดค่าคอลัมน์ต่าง ๆ จำนวนมากเพื่อให้เห็นข้อมูลได้มากในเวลาเดียวกันเท่าที่คุณต้องการ: หน้าแรก, การแจ้งเตือน, เส้นเวลาที่ติดต่อกับภายนอก, รายการและแฮชแท็กจำนวนเท่าใดก็ได้' + advanced_settings: การตั้งค่าขั้นสูง animations_and_accessibility: ภาพเคลื่อนไหวและการช่วยการเข้าถึง - confirmation_dialogs: กล่องโต้ตอบการยืนยัน discovery: การค้นพบ localization: body: Mastodon ได้รับการแปลโดยอาสาสมัคร @@ -1469,6 +1486,11 @@ th: expires_at: หมดอายุเมื่อ uses: การใช้งาน title: เชิญผู้คน + link_preview: + author_html: โดย %{name} + potentially_sensitive_content: + action: คลิกเพื่อแสดง + hide_button: ซ่อน lists: errors: limit: คุณมีรายการถึงจำนวนสูงสุดแล้ว @@ -1533,7 +1555,6 @@ th: disabled_account: บัญชีปัจจุบันของคุณจะไม่สามารถใช้งานได้อย่างเต็มที่หลังจากนั้น อย่างไรก็ตาม คุณจะสามารถเข้าถึงการส่งออกข้อมูลเช่นเดียวกับการเปิดใช้งานใหม่ followers: การกระทำนี้จะย้ายผู้ติดตามทั้งหมดจากบัญชีปัจจุบันไปยังบัญชีใหม่ only_redirect_html: หรืออีกวิธีหนึ่ง คุณสามารถ ตั้งเพียงการเปลี่ยนเส้นทางในโปรไฟล์ของคุณเท่านั้น - other_data: จะไม่ย้ายข้อมูลอื่น ๆ โดยอัตโนมัติ redirect: จะอัปเดตโปรไฟล์ของบัญชีปัจจุบันของคุณด้วยข้อสังเกตการเปลี่ยนเส้นทางและจะไม่รวมอยู่ในการค้นหา moderation: title: การกลั่นกรอง @@ -1617,6 +1638,7 @@ th: self_vote: คุณไม่สามารถลงคะแนนในการสำรวจความคิดเห็นของคุณเอง too_few_options: ต้องมีมากกว่าหนึ่งรายการ too_many_options: ไม่สามารถมีมากกว่า %{max} รายการ + vote: ลงคะแนน preferences: other: อื่น ๆ posting_defaults: ค่าเริ่มต้นการโพสต์ @@ -1769,6 +1791,9 @@ th: other: "%{count} วิดีโอ" boosted_from_html: ดันจาก %{acct_link} content_warning: 'คำเตือนเนื้อหา: %{warning}' + content_warnings: + hide: ซ่อนโพสต์ + show: แสดงเพิ่มเติม default_language: เหมือนกับภาษาส่วนติดต่อ disallowed_hashtags: other: 'มีแฮชแท็กที่ไม่อนุญาต: %{tags}' @@ -1781,9 +1806,16 @@ th: limit: คุณได้ปักหมุดโพสต์ถึงจำนวนสูงสุดไปแล้ว ownership: ไม่สามารถปักหมุดโพสต์ของคนอื่น reblog: ไม่สามารถปักหมุดการดัน + quote_policies: + followers: ผู้ติดตามเท่านั้น + nobody: แค่ฉัน + public: ใครก็ตาม title: '%{name}: "%{quote}"' visibilities: + direct: การกล่าวถึงแบบส่วนตัว + private: ผู้ติดตามเท่านั้น public: สาธารณะ + unlisted_long: ซ่อนจากผลลัพธ์การค้นหา, กำลังนิยม และเส้นเวลาสาธารณะของ Mastodon statuses_cleanup: enabled: ลบโพสต์เก่าโดยอัตโนมัติ enabled_hint: ลบโพสต์ของคุณโดยอัตโนมัติเมื่อโพสต์ถึงค่าเกณฑ์อายุที่ระบุ เว้นแต่โพสต์ตรงกับหนึ่งในข้อยกเว้นด้านล่าง diff --git a/config/locales/tr.yml b/config/locales/tr.yml index 50319622df6a55..0eb31119e8e8a1 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -7,6 +7,8 @@ tr: hosted_on: Mastodon %{domain} üzerinde barındırılıyor title: Hakkında accounts: + errors: + cannot_be_added_to_collections: Bu hesap koleksiyonlara eklenemez. followers: one: Takipçi other: Takipçiler @@ -796,6 +798,8 @@ tr: view_dashboard_description: Kullanıcıların ana panele ve çeşitli ölçütlere erişmesine izin verir view_devops: DevOps view_devops_description: Kullanıcıların Sidekiq ve pgHero panellerine erişmesine izin verir + view_feeds: Canlı ve konu akışlarını görüntüle + view_feeds_description: Kullanıcıların sunucu ayarlarından bağımsız olarak canlı ve konu akışlarına erişmelerini sağlar title: Roller rules: add_new: Kural ekle @@ -844,10 +848,21 @@ tr: publish_statistics: İstatistikleri yayınla title: Keşfet trends: Öne çıkanlar + wrapstodon: Wrapstodon domain_blocks: all: Herkes için disabled: Hiç kimseye users: Oturum açan yerel kullanıcılara + feed_access: + modes: + authenticated: Sadece yetkilendirilmiş kullanıcılar + disabled: Belirli kullanıcı rolü gerekir + public: Herkes + landing_page: + values: + about: Hakkında + local_feed: Yerel akış + trends: Öne çıkanlar registrations: moderation_recommandation: Lütfen kayıtları herkese açmadan önce yeterli ve duyarlı bir denetleyici ekibine sahip olduğunuzdan emin olun! preamble: Sunucunuzda kimin hesap oluşturabileceğini denetleyin. @@ -901,6 +916,7 @@ tr: no_status_selected: Hiçbiri seçilmediğinden hiçbir durum değiştirilmedi open: Gönderiyi aç original_status: Özgün gönderi + quotes: Alıntılar reblogs: Yeniden Paylaşımlar replied_to_html: Yanıtladı %{acct_link} status_changed: Gönderi değişti @@ -908,6 +924,7 @@ tr: title: Hesap gönderileri - @%{name} trending: Öne çıkanlar view_publicly: Herkese açık görüntüle + view_quoted_post: Alıntılanmış gönderiyi görüntüle visibility: Görünürlük with_media: Medya ile strikes: @@ -1182,10 +1199,10 @@ tr: hint_html: Başka bir hesaptan bu hesaba taşınmak istiyorsanız, takipçileri eski hesaptan bu hesaba taşımadan önce gerekli olan takma adı burada oluşturabilirsiniz. Bu eylem kendi başına zararsızdır ve geri döndürülebilir. Hesap taşıma işlemi eski hesaptan başlatılır. remove: Takma adların bağlantısını kaldır appearance: - advanced_web_interface: Gelişmiş web arayüzü - advanced_web_interface_hint: 'Tüm ekran genişliğinizden yararlanmak istiyorsanız, gelişmiş web arayüzü istediğiniz kadar bilgi görecek kadar çok sayıda farklı sütunu yapılandırmanıza olanak tanır: Anasayfa, bildirimler, birleşik zaman çizelgesi, istediğiniz sayıda liste ve etiket.' + advanced_settings: Gelişmiş ayarlar animations_and_accessibility: Animasyonlar ve erişilebilirlik - confirmation_dialogs: Onay iletişim kutuları + boosting_preferences: Öne çıkarma seçenekleri + boosting_preferences_info_html: "İpucu: Ayarlardan bağımsız olarak, %{icon} Öne Çıkar simgesine Shift + Tıklama uygulandığından öne çıkarma hemen gerçekleştirilir." discovery: Keşfet localization: body: Mastodon, gönüllüler tarafından çevrilmektedir. @@ -1587,6 +1604,13 @@ tr: expires_at: Bitiş tarihi uses: Kullanım title: İnsanları davet et + link_preview: + author_html: 'Yazan: %{name}' + potentially_sensitive_content: + action: Göstermek için tıklayın + confirm_visit: Bu bağlantıyı açmak istediğinizden emin misiniz? + hide_button: Gizle + label: Potansiyel olarak hassas içerik lists: errors: limit: Azami liste sayısına ulaştınız @@ -1651,7 +1675,7 @@ tr: disabled_account: Sonrasında, mevcut hesabınız tamamen kullanılabilir olmayacaktır. Ancak, yeniden etkinleştirme işleminin yanı sıra veri dışa aktarma erişimine sahip olacaksınız. followers: Bu eylem tüm takipçileri şu anki hesaptan yeni hesaba taşıyacaktır only_redirect_html: Alternatif olarak, sadece profilinize bir yönlendirme koyabilirsiniz. - other_data: Başka bir veri otomatik olarak taşınmayacaktır + other_data: Başka (gönderileriniz ve takip ettiğiniz hesapların listesi dahil) hiçbir veri otomatik olarak taşınmayacaktır redirect: Mevcut hesabınızın profili bir yönlendirme bildirimi ile güncellenecek ve aramaların dışında tutulacaktır moderation: title: Denetim @@ -1685,16 +1709,22 @@ tr: body: "%{name} senden bahsetti:" subject: "%{name} senden bahsetti" title: Yeni bahsetme + moderation_warning: + subject: Hesabınız bir denetim uyarısı aldı poll: subject: Anket %{name} tarafından sonlandırıldı quote: body: "%{name} durumunuzu yeniden paylaştı:" subject: "%{name} gönderini yeniden paylaştı" title: Yeni alıntı + quoted_update: + subject: "%{name} alıntıladığınız bir gönderiyi düzenledi" reblog: body: "%{name} durumunuzu yeniden paylaştı:" subject: "%{name} durumunuzu yeniden paylaştı" title: Yeni paylaşım + severed_relationships: + subject: Bir denetim kararı nedeniyle bağlantılarınızı kaybettiniz status: subject: "%{name} az önce gönderdi" update: @@ -1897,6 +1927,9 @@ tr: other: "%{count} videolar" boosted_from_html: "%{acct_link} kişisinden yeniden paylaştı" content_warning: 'İçerik uyarısı: %{warning}' + content_warnings: + hide: Gönderiyi gizle + show: Daha fazlasını göster default_language: Arayüz diliyle aynı disallowed_hashtags: one: 'izin verilmeyen bir etiket içeriyordu: %{tags}' @@ -1905,16 +1938,22 @@ tr: errors: in_reply_not_found: Yanıtlamaya çalıştığınız durum yok gibi görünüyor. quoted_status_not_found: Alıntılamaya çalıştığınız gönderi mevcut görünmüyor. + quoted_user_not_mentioned: Özel Bahsetme gönderisinde bahsedilmeyen bir kullanıcıyı alıntılamak mümkün değildir. over_character_limit: "%{max} karakter limiti aşıldı" pin_errors: direct: Sadece değinilen kullanıcıların görebileceği gönderiler üstte tutulamaz limit: Halihazırda maksimum sayıda gönderi sabitlediniz ownership: Başkasının gönderisi sabitlenemez reblog: Bir gönderi sabitlenemez + quote_error: + not_available: Gönderi kullanılamıyor + pending_approval: Gönderi beklemede + revoked: Gönderi yazarı tarafından kaldırıldı quote_policies: followers: Sadece takipçiler nobody: Sadece ben public: Herkes + quote_post_author: "@%{acct} adlı kullanıcının bir gönderisini alıntıladı" title: '%{name}: "%{quote}"' visibilities: direct: Özel değini @@ -2149,3 +2188,6 @@ tr: not_supported: Bu tarayıcı güvenlik anahtarlarını desteklemiyor otp_required: Güvenlik anahtarlarını kullanmak için lütfen önce iki adımlı kimlik doğrulamayı etkinleştirin. registered_on: "%{date} tarihinde kaydoldu" + wrapstodon: + description: "%{name} kullanıcısının bu yıl Mastodon'u nasıl kullandığını görün!" + title: "%{name} için %{year} Wrapstodon'u" diff --git a/config/locales/uk.yml b/config/locales/uk.yml index 7afa383dc06e93..589badd7440a8f 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -825,7 +825,6 @@ uk: title: Усталено відмовитись від індексації користувачів пошуковими системами discovery: follow_recommendations: Поради щодо підписок - preamble: Показ цікавих матеріалів відіграє важливу роль у залученні нових користувачів, які, можливо, не знають нікого з Mastodon. Контролюйте роботу різних функцій виявлення на вашому сервері. privacy: Конфіденційність profile_directory: Каталог профілів public_timelines: Публічна стрічка @@ -889,6 +888,7 @@ uk: no_status_selected: Жодного допису не було змінено, оскільки жодного з них не було вибрано open: Відкрити допис original_status: Оригінальний допис + quotes: Цитати reblogs: Поширення replied_to_html: Відповів %{acct_link} status_changed: Допис змінено @@ -1160,10 +1160,10 @@ uk: hint_html: Якщо ви збираєтеся мігрувати з іншого облікового запису на цей, ви можете налаштувати псевдонім, який потрібен для перенесення підписок зі старою облікового запису. Ця дія сама по собі нешкідлива і її можна скасувати. Міграція облікового запису починається зі старого облікового запису. remove: Від'єднати псевдонім appearance: - advanced_web_interface: Розширений вебінтерфейс - advanced_web_interface_hint: 'Якщо ви бажаєте використовувати всю ширину вашого екрана, розширений вебінтерфейс дає змогу налаштовувати одночасний показ багатьох стовпчиків: головна, сповіщення, федеративна стрічка, будь-яка кількість списків і хештеґів.' + advanced_settings: Додаткові налаштування animations_and_accessibility: Анімація та доступність - confirmation_dialogs: Діалоги підтвердження + boosting_preferences: Параметри поширення + boosting_preferences_info_html: "Підказка: незалежно від налаштувань, Shift + натиск на піктограму %{icon} Поширити призводитиме до негайного поширення." discovery: Виявлення localization: body: Mastodon перекладено волонтерами. @@ -1274,7 +1274,7 @@ uk: more_from_html: Більше від %{name} s_blog: Блог %{name} then_instructions: Потім додайте доменне ім'я публікації у поле нижче. - title: Атрибути авторства + title: Зазначення авторства challenge: confirm: Далі hint_html: "Підказка: ми не будемо запитувати ваш пароль впродовж наступної години." @@ -1542,6 +1542,9 @@ uk: expires_at: Час роботи uses: Використання title: Запросити людей + link_preview: + potentially_sensitive_content: + action: Натисніть, щоб показати lists: errors: limit: Ви досягли максимальної кількості списків @@ -1606,7 +1609,6 @@ uk: disabled_account: Поточний обліковий запис не буде повністю придатний до використання. Проте ви матимете доступ до експорту даних та повторної активації. followers: Ця дія призведе до переміщення всіх підписників з поточного облікового запису до нового облікового запису only_redirect_html: Або ж ви можете просто налаштувати перенаправлення у ваш профіль. - other_data: Ніякі інші дані не будуть переміщені автоматично redirect: Профіль цього облікового запису буде оновлено з заміткою про перенаправлення, а також виключений з пошуку moderation: title: Модерація @@ -1872,18 +1874,25 @@ uk: limit: Ви вже закріпили максимальну кількість дописів ownership: Не можна закріпити чужий допис reblog: Не можна закріпити просунутий допис + quote_policies: + followers: Лише підписникам + public: Будь-кому title: '%{name}: "%{quote}"' visibilities: + private: Лише підписникам public: Для всіх + public_long: Будь-кому на Mastodon і за його межами + unlisted: Тихо публічно + unlisted_long: Сховано з результатів пошуку Mastodon, трендових і публічних стрічок statuses_cleanup: enabled: Автовидалення давніх дописів enabled_hint: Автоматично видаляє ваші дописи після досягнення вказаного вікового порогу, якщо вони не є одним з наведених винятків exceptions: Винятки explanation: Оскільки видалення дописів є складною операцією, то це відбувається повільно, коли сервер не дуже завантажено. З цієї причини ваші дописи можуть бути видалені через деякий час після досягнення вікового порогу. - ignore_favs: Ігнорувати обране + ignore_favs: Ігнорувати вподобані ignore_reblogs: Ігнорувати поширення interaction_exceptions: Винятки базуються на взаємодії - interaction_exceptions_explanation: Зверніть увагу, що немає гарантії, щоб дописи були видалені, якщо вони йдуть нижче улюблених або підсилювачів після того, як вони перейшли за них. + interaction_exceptions_explanation: 'Зауважте: якщо допис вподобають або поширять достатню кількість разів, а потім це скасують, то його може все одно бути залишено.' keep_direct: Зберігати приватні повідомлення keep_direct_hint: Не видаляти ваших особистих повідомлень keep_media: Зберігати повідомлення з вкладеними мультимедійними файлами @@ -1906,8 +1915,8 @@ uk: '63113904': 2 роки '7889238': 3 місяці min_age_label: Поріг давності - min_favs: Залишати дописи в обраному більше ніж - min_favs_hint: Не видаляти ваших дописів, що були поширені більш ніж вказану кількість разів. Залиште порожнім, щоб видаляти дописи, попри кількість їхніх поширень + min_favs: Залишати дописи, вподобані стільки разів + min_favs_hint: Не видаляти ваших дописів, які були вподобані принаймні стільки разів. Залиште порожнім, щоб видаляти дописи незалежно від кількості вподобань min_reblogs: Залишати дописи, поширені більше ніж min_reblogs_hint: Не видаляти ваших дописів, що були поширені більш ніж вказану кількість разів. Залиште порожнім, щоб видаляти дописи, попри кількість їхніх поширень stream_entries: diff --git a/config/locales/vi.yml b/config/locales/vi.yml index 0af555c913edca..035ef775ff0d36 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -7,6 +7,8 @@ vi: hosted_on: "%{domain} vận hành nhờ Mastodon" title: Giới thiệu accounts: + errors: + cannot_be_added_to_collections: Tài khoản này không thể thêm vào bộ sưu tập. followers: other: Người theo dõi following: Theo dõi @@ -213,7 +215,7 @@ vi: enable_user: Cho phép đăng nhập memorialize_account: Gán tưởng niệm promote_user: Nâng vai trò - publish_terms_of_service: Đăng Điều khoản Dịch vụ + publish_terms_of_service: Tạo Điều khoản Dịch vụ reject_appeal: Từ chối khiếu nại reject_user: Từ chối đăng ký remove_avatar_user: Xóa ảnh đại diện @@ -782,6 +784,8 @@ vi: view_dashboard_description: Cho phép truy cập trang tổng quan và các chỉ số khác view_devops: Nhà phát triển view_devops_description: Cho phép truy cập trang tổng quan Sidekiq và pgHero + view_feeds: Xem nguồn cấp dữ liệu trực tiếp và theo chủ đề + view_feeds_description: Cho phép người dùng truy cập nguồn cấp dữ liệu trực tiếp và theo chủ đề bất kể cài đặt máy chủ title: Danh sách vai trò rules: add_new: Thêm nội quy @@ -823,17 +827,28 @@ vi: title: Mặc định người dùng không xuất hiện trong công cụ tìm kiếm discovery: follow_recommendations: Gợi ý theo dõi - preamble: Hiển thị nội dung thú vị là công cụ để thu hút người dùng mới, những người có thể không quen bất kỳ ai trong Mastodon. Kiểm soát cách các tính năng khám phá hoạt động trên máy chủ của bạn. + preamble: Hiển thị nội dung thú vị là công cụ để thu hút người dùng mới, những người có thể không quen bất kỳ ai trên Mastodon. Kiểm soát cách các tính năng khám phá hoạt động trên máy chủ của bạn. privacy: Riêng tư profile_directory: Danh bạ public_timelines: Bảng tin publish_statistics: Công khai số liệu thống kê title: Khám phá trends: Xu hướng + wrapstodon: Wrapstodon domain_blocks: all: Tới mọi người disabled: Không ai users: Để đăng nhập người cục bộ + feed_access: + modes: + authenticated: Chỉ những người dùng đã xác minh + disabled: Yêu cầu vai trò người dùng cụ thể + public: Mọi người + landing_page: + values: + about: Giới thiệu + local_feed: Bảng tin máy chủ + trends: Xu hướng registrations: moderation_recommandation: Vui lòng đảm bảo rằng bạn có một đội ngũ kiểm duyệt và phản ứng nhanh trước khi mở đăng ký cho mọi người! preamble: Kiểm soát những ai có thể tạo tài khoản trên máy chủ của bạn. @@ -887,6 +902,7 @@ vi: no_status_selected: Bạn chưa chọn bất kỳ tút nào open: Mở tút original_status: Tút gốc + quotes: Trích dẫn reblogs: Lượt đăng lại replied_to_html: Trả lời đến %{acct_link} status_changed: Tút đã sửa @@ -894,6 +910,7 @@ vi: title: Tút từ tài khoản - @%{name} trending: Xu hướng view_publicly: Xem công khai + view_quoted_post: Xem những tút được trích dẫn visibility: Hiển thị with_media: Có media strikes: @@ -1163,10 +1180,10 @@ vi: hint_html: Nếu bạn muốn chuyển từ máy chủ khác sang máy chủ này, bắt buộc bạn phải tạo tên người dùng mới thì mới có thể tiến hành chuyển được người theo dõi. Hành động này không ảnh hưởng gì và có thể đảo ngược. Việc di chuyển tài khoản được bắt đầu từ tài khoản cũ. remove: Bỏ liên kết bí danh appearance: - advanced_web_interface: Bố cục - advanced_web_interface_hint: Xếp giao diện thành nhiều cột, hợp với màn hình rộng. + advanced_settings: Cài đặt nâng cao animations_and_accessibility: Hiệu ứng - confirmation_dialogs: Hộp thoại xác nhận + boosting_preferences: Thiết lập đăng lại + boosting_preferences_info_html: "Mẹo: Bất kể cài đặt, Shift + Click trên biểu tượng %{icon} Đăng lại sẽ lập tức đăng lại." discovery: Khám phá localization: body: Mastodon được dịch bởi tình nguyện viên. @@ -1548,6 +1565,13 @@ vi: expires_at: Hết hạn uses: Sử dụng title: Mời bạn bè + link_preview: + author_html: Bởi %{name} + potentially_sensitive_content: + action: Nhấn để xem + confirm_visit: Bạn có chắc muốn mở liên kết này? + hide_button: Ẩn + label: Có khả năng là nội dung nhạy cảm lists: errors: limit: Bạn đã đạt đến số lượng danh sách tối đa @@ -1612,7 +1636,7 @@ vi: disabled_account: Tài khoản này sẽ không thể tiếp tục dùng nữa. Tuy nhiên, bạn có quyền truy cập để xuất dữ liệu cũng như kích hoạt lại. followers: Hành động này sẽ chuyển tất cả người theo dõi từ tài khoản hiện tại sang tài khoản mới only_redirect_html: Ngoài ra, bạn có thể đặt chuyển hướng trên trang hồ sơ của bạn. - other_data: Dữ liệu khác sẽ không được di chuyển tự động + other_data: Không có dữ liệu nào khác sẽ được tự động di chuyển (bao gồm tút của bạn và danh sách tài khoản bạn theo dõi) redirect: Trang hồ sơ hiện tại của bạn sẽ hiển thị thông báo chuyển hướng và bị loại khỏi kết quả tìm kiếm moderation: title: Kiểm duyệt @@ -1646,16 +1670,22 @@ vi: body: 'Bạn vừa được nhắc đến bởi %{name} trong:' subject: Bạn vừa được nhắc đến bởi %{name} title: Lượt nhắc mới + moderation_warning: + subject: Bạn vừa nhận một cảnh báo kiểm duyệt poll: subject: Vốt của %{name} đã kết thúc quote: body: 'Tút của bạn được trích dẫn bởi %{name}:' subject: "%{name} vừa trích dẫn tút của bạn" title: Trích dẫn mới + quoted_update: + subject: "%{name} đã chỉnh sửa tút mà bạn trích dẫn" reblog: body: Tút của bạn vừa được %{name} đăng lại subject: "%{name} vừa đăng lại tút của bạn" title: Lượt đăng lại mới + severed_relationships: + subject: Bạn bị mất các mối quan hệ vì một quyết định kiểm duyệt status: subject: Bài đăng mới từ %{name} update: @@ -1702,7 +1732,7 @@ vi: too_many_options: tối đa %{max} lựa chọn vote: Vốt posting_defaults: - explanation: Các thiết lập này sẽ được sử dụng làm mặc định khi bạn tạo tút mới, nhưng bạn có thể chỉnh sửa cho từng tút khi soạn thảo. + explanation: Các thiết lập này sẽ được sử dụng làm mặc định khi bạn tạo tút mới. Bạn vẫn có thể chỉnh sửa từng tút khi soạn thảo. preferences: other: Khác posting_defaults: Mặc định cho tút @@ -1710,7 +1740,7 @@ vi: privacy: hint_html: Tùy chỉnh cách mọi người tìm thấy hồ sơ và tút của bạn. privacy: Riêng tư - privacy_hint_html: Kiểm soát mức độ chi tiết bạn muốn tiết lộ. Mọi người khám phá các hồ sơ thú vị và các ứng dụng thú vị bằng cách theo dõi những người khác và xem họ đăng từ ứng dụng nào, nhưng có thể bạn muốn ẩn nó đi. + privacy_hint_html: Kiểm soát mức độ tiết lộ chi tiết. Mọi người khám phá các hồ sơ thú vị và các ứng dụng thú vị bằng cách xem bạn theo dõi ai và bạn đăng bằng ứng dụng nào, nhưng có thể bạn muốn ẩn nó đi. reach: Tiếp cận reach_hint_html: Kiểm soát cách bạn được đề xuất và theo dõi. Bạn có muốn tút của mình xuất hiện trên trang Khám phá không? Bạn có muốn bạn xuất hiện trong các đề xuất theo dõi của người khác không? Bạn muốn tự động chấp nhận tất cả những người theo dõi mới hay tự duyệt từng người theo dõi? search: Tìm kiếm @@ -1855,6 +1885,9 @@ vi: other: "%{count} video" boosted_from_html: Đã đăng lại từ %{acct_link} content_warning: 'Cảnh báo nội dung: %{warning}' + content_warnings: + hide: Ẩn tút + show: Mở rộng default_language: Giống giao diện disallowed_hashtags: other: 'chứa các hashtag bị cấm: %{tags}' @@ -1862,16 +1895,22 @@ vi: errors: in_reply_not_found: Bạn đang trả lời một tút không còn tồn tại. quoted_status_not_found: Bạn đang trích dẫn một tút không còn tồn tại. + quoted_user_not_mentioned: Không thể trích dẫn người dùng không được nhắc đến trong tút Nhắn riêng. over_character_limit: vượt quá giới hạn %{max} ký tự pin_errors: direct: Không thể ghim những tút nhắn riêng limit: Bạn đã ghim quá số lượng tút cho phép ownership: Không thể ghim tút của người khác reblog: Không thể ghim tút đăng lại + quote_error: + not_available: Tút không khả dụng + pending_approval: Tút đang chờ duyệt + revoked: Tút gốc đã bị tác giả gỡ quote_policies: followers: Chỉ người theo dõi nobody: Chỉ tôi public: Bất cứ ai + quote_post_author: Trích dẫn từ tút của @%{acct} title: '%{name}: "%{quote}"' visibilities: direct: Nhắn riêng @@ -2105,3 +2144,6 @@ vi: not_supported: Trình duyệt của bạn không hỗ trợ khóa bảo mật otp_required: Để dùng khóa bảo mật, trước tiên hãy kích hoạt xác thực 2 bước. registered_on: Đăng ký vào %{date} + wrapstodon: + description: Xem năm nay %{name} đã dùng Mastodon như thế nào! + title: Wrapstodon %{year} cho %{name} diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml index 3200bdfd23e648..87356403d2bc3b 100644 --- a/config/locales/zh-CN.yml +++ b/config/locales/zh-CN.yml @@ -7,6 +7,8 @@ zh-CN: hosted_on: 运行在 %{domain} 上的 Mastodon 实例 title: 关于本站 accounts: + errors: + cannot_be_added_to_collections: 此账号无法被添加到关注列表内。 followers: other: 关注者 following: 正在关注 @@ -71,7 +73,7 @@ zh-CN: enable_sign_in_token_auth: 启用邮件令牌身份验证 enabled: 已启用 enabled_msg: 成功解冻 %{username} 的账号 - followers: 粉丝 + followers: 关注者 follows: 关注 header: 封面图 inbox_url: 收件箱(Inbox)URL @@ -118,7 +120,7 @@ zh-CN: reject: 拒绝 rejected_msg: 已拒绝 %{username} 的注册申请 remote_suspension_irreversible: 此账号的数据已被不可逆转地删除。 - remote_suspension_reversible_hint_html: 账号已在他们的服务器上封禁,数据将在 %{date} 完全删除。 在此之前,远程服务器仍可恢复此账号,并且没有任何不良影响。 如果你想立即移除该账号的所有数据,可以在下面进行。 + remote_suspension_reversible_hint_html: 账号已在他们的服务器上封禁,数据将在 %{date} 完全删除。 在此之前,远程服务器仍可恢复此账号,并且没有任何不良影响。 如果你想立即移除该账号的全部数据,可以在下面进行。 remove_avatar: 删除头像 remove_header: 移除封面图 removed_avatar_msg: 成功删除 %{username} 的头像 @@ -536,7 +538,7 @@ zh-CN: confirm_purge: 确定要删除此站点的全部数据吗? content_policies: comment: 内部备注 - description_html: 你可以设置应用于此域名所有账号和其所有子域名的内容策略。 + description_html: 你可以设置应用于此域名全部账号和其全部子域名的内容策略。 limited_federation_mode_description_html: 你可以选择是否允许与此实例联合。 policies: reject_media: 拒收媒体 @@ -578,12 +580,12 @@ zh-CN: created_msg: 实例管理员备注创建成功! description_html: 查看备注或向其他管理员留言 destroyed_msg: 实例管理员备注删除成功! - placeholder: 有关此实例的信息、已采取的行动,或任何能帮助您将来管理此实例的事项。 + placeholder: 有关此实例的信息、已采取的行动,或任何能帮助你将来管理此实例的事项。 title: 审核注意事项 private_comment: 私密评论 public_comment: 公开评论 purge: 清除 - purge_description_html: 如果你确认此域名已永久离线,可以从存储中删除此域名的所有账号记录和相关数据。这将会需要一段时间。 + purge_description_html: 如果你确认此域名已永久离线,可以从存储中删除此域名的全部账号记录和相关数据。这将会需要一段时间。 title: 联合 total_blocked_by_us: 被本站屏蔽的 total_followed_by_them: 被对方关注的 @@ -624,7 +626,7 @@ zh-CN: disable: 禁用 disabled: 已禁用 enable: 启用 - enable_hint: 启用此功能后,你的实例会订阅此中继站的所有公开嘟文,并同时向其推送本服务器的公开嘟文。 + enable_hint: 启用此功能后,你的实例会订阅此中继站的全部公开嘟文,并同时向其推送本服务器的公开嘟文。 enabled: 已启用 inbox_url: 中继站 URL pending: 等待中继站的确认 @@ -647,8 +649,8 @@ zh-CN: mark_as_sensitive_description_html: 被举报的嘟文将被标记为敏感,同时该账号将被标记一次处罚,以供未来同一账号再次违规时参考。 other_description_html: 查看更多控制该账号行为的选项,并自定义编写与被举报账号的通信。 resolve_description_html: 不会对被举报账号采取任何动作,举报将被关闭,也不会留下处罚记录。 - silence_description_html: 只有关注或手工搜索此账号才能查看其资料,将严重限制其触达范围。可随时撤销。关闭针对此账号的所有举报。 - suspend_description_html: 该账号及其所有内容将无法访问并最终被删除,且无法与该账号进行互动。 在 30 天内可随时撤销。关闭针对此账号的所有举报。 + silence_description_html: 只有关注或手工搜索此账号才能查看其资料,将严重限制其触达范围。可随时撤销。关闭针对此账号的全部举报。 + suspend_description_html: 该账号及其全部内容将无法访问并最终被删除,且无法与该账号进行互动。 在 30 天内可随时撤销。关闭针对此账号的全部举报。 actions_description_html: 决定采取何种措施处理此举报。如果对被举报账号采取惩罚性措施,将向其发送一封电子邮件通知。但若选中垃圾信息类别则不会发送通知。 actions_description_remote_html: 决定采取何种行动来解决此举报。 这只会影响你的服务器如何与该远程账号的通信并处理其内容。 actions_no_posts: 该举报没有相关嘟文可供删除 @@ -711,7 +713,7 @@ zh-CN: silence_html: 严格限制 @%{acct} 的影响力,方法是让他们的个人资料和内容仅对已经关注他们的人可见,或手动查找其个人资料时 suspend_html: 暂停 @%{acct},使他们的个人资料和内容无法访问,也无法与之互动 close_report: '将举报 #%{id} 标记为已解决' - close_reports_html: 将针对 @%{acct}所有举报标记为已解决 + close_reports_html: 将针对 @%{acct}全部举报标记为已解决 delete_data_html: 从现在起 30 天后删除 @%{acct} 的个人资料和内容,除非他们同时解除暂停。 preview_preamble_html: "@%{acct} 将收到包含以下内容的警告:" record_strike_html: 记录一次针对 @%{acct} 的警示,以帮助你在这个账号上的未来违规事件中得到重视。 @@ -738,7 +740,7 @@ zh-CN: description_html: 使用 用户角色,你可以自定义你的用户可以访问的 Mastodon 功能和区域。 edit: 编辑角色 '%{name}' everyone: 默认权限 - everyone_full_description_html: 该角色是基础角色,会影响所有用户,包括未指定角色的用户。 其他所有的角色都继承该角色的权限。 + everyone_full_description_html: 该角色是基础角色,会影响全部用户,包括未指定角色的用户。 其他全部的角色都继承该角色的权限。 permissions_count: other: "%{count} 个权限" privileges: @@ -782,6 +784,8 @@ zh-CN: view_dashboard_description: 允许用户访问信息面板和各种指标 view_devops: 开发运维 view_devops_description: 允许用户访问 Sidekiq 和 pgHero 控制面板 + view_feeds: 查看实时动态和话题 + view_feeds_description: 允许用户无视服务器设置访问实时动态和话题 title: 角色 rules: add_new: 添加规则 @@ -803,8 +807,8 @@ zh-CN: rules_hint: 有一个专门区域用于显示用户需要遵守的规则。 title: 关于本站 allow_referrer_origin: - desc: 当您的用户点击指向外部网站的链接时,他们的浏览器可能会将您的 Mastodon 服务器地址作为referrer发送。如果这会唯一地识别出您的用户(例如,如果这是一个私人 Mastodon 服务器),请禁用此功能。 - title: 允许外部网站将您的 Mastodon 服务器视为流量来源 + desc: 当你的用户点击指向外部网站的链接时,他们的浏览器可能会将你的 Mastodon 服务器地址作为 referrer 发送。如果这会唯一地识别出你的用户,例如,如果这是一个私人 Mastodon 服务器,请禁用此功能。 + title: 允许外部网站将你的 Mastodon 服务器视为流量来源 appearance: preamble: 自定义 Mastodon 的网页界面。 title: 外观 @@ -819,23 +823,34 @@ zh-CN: preamble: 控制用户生成的内容在 Mastodon 中如何存储。 title: 内容保留 default_noindex: - desc_html: 影响所有尚未更改此设置的用户 + desc_html: 影响全部尚未更改此设置的用户 title: 默认不让用户被搜索引擎索引 discovery: follow_recommendations: 关注推荐 - preamble: 露出有趣的内容有助于新加入 Mastodon 的用户融入。可在这里控制多种发现功能如何在你的服务器上工作。 + preamble: 展示有趣的内容有助于新加入 Mastodon 的用户融入。可在这里控制多种发现功能如何在你的服务器上工作。 privacy: 隐私 profile_directory: 个人资料目录 public_timelines: 公共时间线 publish_statistics: 发布统计数据 title: 发现 trends: 热门 + wrapstodon: Wrapstodon 年度回顾 domain_blocks: - all: 对所有人 + all: 对每个人 disabled: 不对任何人 users: 对已登录的本站用户 + feed_access: + modes: + authenticated: 仅已登录用户 + disabled: 需要特定的用户角色 + public: 每个人 + landing_page: + values: + about: 关于 + local_feed: 本站动态 + trends: 热门 registrations: - moderation_recommandation: 在向所有人开放注册之前,请确保你拥有一个人手足够且反应迅速的管理团队! + moderation_recommandation: 在向每个人开放注册之前,请确保你拥有一个人手足够且反应迅速的管理团队! preamble: 控制谁可以在你的服务器上创建账号。 title: 注册 registrations_mode: @@ -887,6 +902,7 @@ zh-CN: no_status_selected: 因为没有嘟文被选中,所以没有更改 open: 展开嘟文 original_status: 原始嘟文 + quotes: 引用嘟文 reblogs: 转发 replied_to_html: 回复 %{acct_link} status_changed: 嘟文已编辑 @@ -894,6 +910,7 @@ zh-CN: title: 该账号的嘟文 - @%{name} trending: 当前热门 view_publicly: 以公开身份查看 + view_quoted_post: 查看引用的嘟文 visibility: 可见性 with_media: 含有媒体文件 strikes: @@ -948,10 +965,10 @@ zh-CN: message_html: 有一个Mastodon错误修复更新可用。 upload_check_privacy_error: action: 点击这里查看更多信息 - message_html: "您的网站服务器配置错误,您用户的隐私处于危险中。" + message_html: "你的网站服务器配置错误,你的用户的隐私处于危险中。" upload_check_privacy_error_object_storage: action: 点击这里查看更多信息 - message_html: "您的对象存储空间配置错误,您用户的隐私处于危险中。" + message_html: "你的对象存储空间配置错误,你的用户的隐私处于危险中。" tags: moderation: not_trendable: 不在热门中显示 @@ -1163,10 +1180,10 @@ zh-CN: hint_html: 如果你想从另一个账号迁移到这里,可以先在这里创建一个别名。要把旧账号的关注者迁移过来,这一步是必须的。设置别名的操作是无害且可撤销的账号迁移的操作会从旧账号发起。 remove: 取消关联别名 appearance: - advanced_web_interface: 高级 Web 界面 - advanced_web_interface_hint: 在高级网页界面支持自定义多栏显示,你可以利用整个屏幕的宽度,同时查看首页、通知、跨站时间线及任意数量的列表和话题。 + advanced_settings: 高级设置 animations_and_accessibility: 动画与可访问性 - confirmation_dialogs: 确认对话框 + boosting_preferences: 转嘟首选项 + boosting_preferences_info_html: "提示:无论当前设置如何,对 %{icon} 转嘟图标按Shift + 鼠标单击将会立即转嘟。" discovery: 发现 localization: body: Mastodon 由志愿者翻译。 @@ -1441,7 +1458,7 @@ zh-CN: all_items_on_page_selected_html: other: 此页面上的所有 %{count} 项目已被选中。 all_matching_items_selected_html: - other: 所有 %{count} 匹配你搜索的项目都已被选中。 + other: 全部 %{count} 匹配你搜索的项目都已被选中。 cancel: 取消 changes_saved_msg: 更改保存成功! confirm: 确认 @@ -1452,7 +1469,7 @@ zh-CN: order_by: 排序方式 save_changes: 保存更改 select_all_matching_items: - other: 选择匹配你搜索的所有 %{count} 个项目。 + other: 选择匹配你搜索的全部 %{count} 个项目。 today: 今天 validation_errors: other: 出错啦!检查一下下面 %{count} 处出错的地方吧 @@ -1479,7 +1496,7 @@ zh-CN: domain_blocking_html: other: 你即将使用来自 %{filename} 的最多 %{count} 个站点域名替换你的站点屏蔽列表。 following_html: - other: 你即将关注来自 %{filename} 的最多 %{count} 个账号,并停止关注其他所有人。 + other: 你即将关注来自 %{filename} 的最多 %{count} 个账号,并停止关注其他任何人。 lists_html: other: 你即将使用来自 %{filename} 的内容替换你的列表。最多将会有 %{count} 个账号 被添加到新列表。 muting_html: @@ -1548,6 +1565,13 @@ zh-CN: expires_at: 失效时间 uses: 已使用次数 title: 邀请用户 + link_preview: + author_html: 来自 %{name} + potentially_sensitive_content: + action: 点击查看 + confirm_visit: 你确定要打开此链接吗? + hide_button: 隐藏 + label: 可能为敏感内容 lists: errors: limit: 你已达到列表数量的上限 @@ -1610,9 +1634,9 @@ zh-CN: before: 在继续前,请仔细阅读下列说明: cooldown: 移动后会有一个冷却期,在此期间你将无法再次移动 disabled_account: 此后,你的当前账号将无法使用。但是,你仍然有权导出数据或者重新激活。 - followers: 这步操作将把所有关注者从当前账号移动到新账号 + followers: 这步操作将把全部关注者从当前账号移动到新账号 only_redirect_html: 或者,你可以只在你的账号资料上设置一个跳转。 - other_data: 不会自动移动其它数据 + other_data: 其他数据不会自动移动(包括您的嘟文及您关注的账号列表) redirect: 在收到一个跳转通知后,你当前的账号资料将会更新,并被排除在搜索范围外 moderation: title: 审核 @@ -1646,16 +1670,22 @@ zh-CN: body: "%{name} 提到了你:" subject: "%{name} 提到了你" title: 新的提及 + moderation_warning: + subject: 你收到了一条管理警告 poll: subject: "%{name} 创建的一个投票已经结束" quote: body: 你的嘟文被 %{name} 引用了: subject: "%{name} 引用了你的嘟文" title: 新引用 + quoted_update: + subject: "%{name} 编辑了一条你之前引用过的嘟文" reblog: body: 你的嘟文被 %{name} 转嘟了: subject: "%{name} 转嘟了你的嘟文" title: 新的转嘟 + severed_relationships: + subject: 由于管理决定,您已失去网络上的关注关系 status: subject: "%{name} 刚刚发布嘟文" update: @@ -1702,7 +1732,7 @@ zh-CN: too_many_options: 不能超过 %{max} 项 vote: 投票 posting_defaults: - explanation: 这些设置会在您撰写新嘟文时作为默认值应用,但每篇嘟文的设置也可以在编辑器处单独修改。 + explanation: 这些设置会在你撰写新嘟文时作为默认值应用,但每篇嘟文的设置也可以在编辑器处单独修改。 preferences: other: 其他 posting_defaults: 发布默认值 @@ -1712,7 +1742,7 @@ zh-CN: privacy: 隐私 privacy_hint_html: 控制你愿意向他人透露多少信息。通过浏览他人的关注列表和查看他们发嘟所用的应用,人们可以发现有趣的用户和酷炫的应用,但你可能更喜欢将其隐藏起来。 reach: 范围 - reach_hint_html: 控制你是否希望被新人发现和关注。你是否希望你的嘟文出现在“探索”页面上?你是否希望其他人在关注推荐中看到你?你是想自动接受所有新粉丝,还是对每个粉丝都进行仔细的筛选? + reach_hint_html: 控制你是否希望被新人发现和关注。你是否希望你的嘟文出现在“探索”页面上?你是否希望其他人在关注推荐中看到你?你是想自动接受全部新关注者,还是对每个关注者都进行仔细的筛选? search: 搜索 search_hint_html: 控制你希望被找到的方式。你想让人们通过你公开发布的内容来找到你吗?当在网络上搜索时,你是否希望Mastodon之外的人能够找到你的个人资料?请注意,我们无法保证完全排除所有搜索引擎对公开信息的索引。 title: 隐私与可达性 @@ -1729,7 +1759,7 @@ zh-CN: activity: 账号活动 confirm_follow_selected_followers: 你确定想要关注所选的关注者吗? confirm_remove_selected_followers: 你确定想要取关所选的关注者吗? - confirm_remove_selected_follows: 您确定要删除选定的关注者吗? + confirm_remove_selected_follows: 你确定要删除选定的关注者吗? dormant: 休眠 follow_failure: 无法关注选中的部分账号。 follow_selected_followers: 关注选中的关注者 @@ -1742,7 +1772,7 @@ zh-CN: mutual: 互相关注 primary: 主要 relationship: 关系 - remove_selected_domains: 删除在选定站点中的所有关注者 + remove_selected_domains: 删除在选定站点中的全部关注者 remove_selected_followers: 移除选中的关注者 remove_selected_follows: 取消关注所选用户 status: 账号状态 @@ -1855,29 +1885,38 @@ zh-CN: other: "%{count} 段视频" boosted_from_html: 转嘟自 %{acct_link} content_warning: 内容警告:%{warning} + content_warnings: + hide: 隐藏嘟文 + show: 显示更多 default_language: 与界面显示语言相同 disallowed_hashtags: other: 包含以下被禁止的话题:%{tags} edited_at_html: 编辑于 %{date} errors: in_reply_not_found: 你回复的嘟文似乎不存在 - quoted_status_not_found: 您尝试引用的嘟文似乎不存在。 + quoted_status_not_found: 你尝试引用的嘟文似乎不存在。 + quoted_user_not_mentioned: 无法在私下提及嘟文中引用未提及的用户。 over_character_limit: 超过了 %{max} 字的限制 pin_errors: direct: 仅对被提及的用户可见的帖子不能被置顶 limit: 你置顶的嘟文数量已达上限 ownership: 不能置顶别人的嘟文 reblog: 不能置顶转嘟 + quote_error: + not_available: 嘟文不可用 + pending_approval: 嘟文待发布 + revoked: 嘟文已被作者删除 quote_policies: followers: 仅关注者 nobody: 仅限自己 public: 任何人 + quote_post_author: 引用了 %{acct} 的嘟文 title: "%{name}:“%{quote}”" visibilities: direct: 私下提及 private: 仅关注者 public: 公开 - public_long: 所有人(无论是否注册了 Mastodon) + public_long: 任何人(无论是否注册了 Mastodon) unlisted: 悄悄公开 unlisted_long: 不显示在Mastodon的搜索结果、热门趋势、公共时间线上 statuses_cleanup: @@ -1925,7 +1964,7 @@ zh-CN: terms_of_service: title: 服务条款 terms_of_service_interstitial: - future_preamble_html: 我们将更改部分服务条款,这些更改将在%{date}生效。我们建议您查看更新后的条款。 + future_preamble_html: 我们将更改部分服务条款,这些更改将在%{date}生效。我们建议你查看更新后的条款。 past_preamble_html: 自你上次访问起,我们更改了一些服务条款。我们建议你查看更新后的条款。 review_link: 查看服务条款 title: "%{domain} 更新了服务条款" @@ -2012,7 +2051,7 @@ zh-CN: delete_statuses: 你的一些嘟文被发现违反了一条或多条社区准则,并已被 %{instance} 的管理员删除。 disable: 你不能再使用你的账号,但你的个人资料和其他数据保持不变。你可以请求数据备份,更改账号设置或删除账号。 mark_statuses_as_sensitive: 你的一些嘟文已经被 %{instance} 管理员标记为敏感内容。这意味着别人需要在嘟文中点击媒体,才能显示媒体预览。你可以在今后发布嘟文时自行将媒体标记为敏感内容。 - sensitive: 即刻起,你上传的所有媒体文件都将被标记为敏感内容并隐藏,在点击警告后才能查看。 + sensitive: 即刻起,你上传的全部媒体文件都将被标记为敏感内容并隐藏,在点击警告后才能查看。 silence: 你可以继续使用你的账号,但只有已关注你的人才能在看到你在此服务器上的嘟文,并且你会被排除在各类公共列表之外。其他用户仍可以手动关注你。 suspend: 你不能再使用你的账号,并且你的个人资料和其他数据都将无法访问。在大约30天内,你仍可以登录并请求数据备份,之后相关数据将被完全删除。我们会保留一些基础数据以避免封禁失效。 reason: 理由: @@ -2074,9 +2113,9 @@ zh-CN: title: "%{name},欢迎你的加入!" users: follow_limit_reached: 你不能关注超过 %{limit} 个人 - go_to_sso_account_settings: 转到您的身份提供商进行账号设置 + go_to_sso_account_settings: 转到你的身份提供商进行账号设置 invalid_otp_token: 输入的双因素认证代码无效 - otp_lost_help_html: 如果你不慎丢失了所有的代码,请联系 %{email} 寻求帮助 + otp_lost_help_html: 如果你不慎丢失了全部的代码,请联系 %{email} 寻求帮助 rate_limited: 验证尝试次数过多,请稍后再试。 seamless_external_login: 你通过外部服务登录,因此密码和邮件设置不可用。 signed_in_as: 当前登录的账号: @@ -2105,3 +2144,6 @@ zh-CN: not_supported: 此浏览器不支持安全密钥 otp_required: 要使用安全密钥,请先启用双因素认证。 registered_on: 注册于 %{date} + wrapstodon: + description: 来看看%{name}今年是怎么使用Mastodon的! + title: "%{name}的%{year}年Wrapstodon年度回顾" diff --git a/config/locales/zh-HK.yml b/config/locales/zh-HK.yml index c0e6029819ff02..039a89bce6cd2c 100644 --- a/config/locales/zh-HK.yml +++ b/config/locales/zh-HK.yml @@ -736,7 +736,6 @@ zh-HK: title: 預設用戶不在搜尋引擎索引之內 discovery: follow_recommendations: 追蹤建議 - preamble: 呈現有趣的內容有助於吸引不認識 Mastodon 的使用者新手上路。控制各種探索功能在你的伺服器上的運作方式。 privacy: 私隱 profile_directory: 個人檔案目錄 public_timelines: 公共時間軸 @@ -1001,10 +1000,7 @@ zh-HK: hint_html: 如果你想由另一個帳戶轉移到此帳號,你可以在此處創建別名 (alias),然後系統容許你將關注者由舊帳戶轉移到此帳號。此操作是無害且可以還原的帳號遷移程序,需要在舊帳號啟動。 remove: 取消連結別名 (Alias) appearance: - advanced_web_interface: 進階網頁介面 - advanced_web_interface_hint: 如果你想善用整個螢幕闊度,你可以啟用「進階網頁介面」,在畫面上配置多個不同的欄目,讓你能根據需要,同時查看盡可能多的信息,支援的欄位包括:主頁、通知、其他站點和任何的列表和標籤。 animations_and_accessibility: 動畫和輔助功能 - confirmation_dialogs: 確認對話框 discovery: 探索 localization: body: Mastodon 是由志願者翻譯的。 @@ -1386,7 +1382,6 @@ zh-HK: disabled_account: 你的當前帳號將不能完整地運作。但你將會有權力匯出你的數據和重新啟用你的帳號。 followers: 此操作將會由當前帳號轉移所有追隨者至新帳號 only_redirect_html: 另外,你亦可只在你的個人資料頁面放上重新定向標記. - other_data: 並不會自動轉移其他數據 redirect: 你的當前帳號的個人資料頁面將會加上一個重新定向公告,並會在搜尋結果中被剔除。 moderation: title: 營運 diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 101ea1850a28bc..7c71c2487adfe5 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -7,6 +7,8 @@ zh-TW: hosted_on: 於 %{domain} 託管之 Mastodon 站點 title: 關於本站 accounts: + errors: + cannot_be_added_to_collections: 此帳號無法被加至集合中。 followers: other: 跟隨者 following: 正在跟隨 @@ -782,6 +784,8 @@ zh-TW: view_dashboard_description: 允許使用者存取儀表板與各種指標 view_devops: DevOps view_devops_description: 允許使用者存取 Sidekiq 與 pgHero 儀表板 + view_feeds: 檢視即時內容與主題 + view_feeds_description: 允許使用者無論伺服器設定為何皆可存取即時內容與主題 title: 角色 rules: add_new: 新增規則 @@ -825,17 +829,28 @@ zh-TW: title: 預設將使用者排除於搜尋引擎索引 discovery: follow_recommendations: 跟隨建議 - preamble: 呈現有趣的內容有助於 Mastodon 上一人不識的新手上路。控制各種不同的分類於您伺服器上如何被探索到。 + preamble: 呈現有趣的內容有助於 Mastodon 上一人不識的新手上路。控制您伺服器上各類探索功能之運作方式。 privacy: 隱私權 profile_directory: 個人檔案目錄 public_timelines: 公開時間軸 publish_statistics: 公開統計資料 title: 探索 trends: 熱門趨勢 + wrapstodon: Mastodon 年度回顧 domain_blocks: all: 至任何人 disabled: 至沒有人 users: 套用至所有登入的本站使用者 + feed_access: + modes: + authenticated: 僅限已登入之使用者 + disabled: 需要特定使用者權限 + public: 任何人 + landing_page: + values: + about: 關於 + local_feed: 本站時間軸 + trends: 熱門趨勢 registrations: moderation_recommandation: 對所有人開放註冊之前,請確保您有人手充足且反應靈敏的管理員團隊! preamble: 控制誰能於您伺服器上建立帳號。 @@ -889,6 +904,7 @@ zh-TW: no_status_selected: 因未選取嘟文,所以什麼事都沒發生。 open: 公開嘟文 original_status: 原始嘟文 + quotes: 引用嘟文 reblogs: 轉嘟 replied_to_html: 回覆給 %{acct_link} status_changed: 嘟文已編輯 @@ -896,6 +912,7 @@ zh-TW: title: 嘟文帳號 - @%{name} trending: 熱門 view_publicly: 公開檢視 + view_quoted_post: 檢視引用嘟文 visibility: 可見性 with_media: 含有媒體檔案 strikes: @@ -1165,10 +1182,10 @@ zh-TW: hint_html: 如果想由其他帳號轉移至此帳號,您能於此處新增別名,稍後系統將容許您將跟隨者由舊帳號轉移至此。此項作業是無害且可復原的帳號的遷移程序需要於舊帳號啟動。 remove: 取消連結別名 appearance: - advanced_web_interface: 進階網頁介面 - advanced_web_interface_hint: 進階網頁介面能使您設定許多不同的欄位來善用螢幕空間,依需要同時查看許多不同的資訊如:首頁、通知、聯邦宇宙時間軸、任意數量的列表與主題標籤。 + advanced_settings: 進階設定 animations_and_accessibility: 動畫與無障礙設定 - confirmation_dialogs: 確認對話框 + boosting_preferences: 轉嘟偏好設定 + boosting_preferences_info_html: "小技巧: 無論設定為何, Shift + Click 於 %{icon} 轉嘟圖示將會立即轉嘟。" discovery: 探索 localization: body: Mastodon 是由志願者所翻譯。 @@ -1550,6 +1567,13 @@ zh-TW: expires_at: 失效時間 uses: 已使用次數 title: 邀請使用者 + link_preview: + author_html: 來自 %{name} + potentially_sensitive_content: + action: 點擊以顯示 + confirm_visit: 您確定要開啟此連結嗎? + hide_button: 隱藏 + label: 可能為敏感內容 lists: errors: limit: 您所建立之列表數量已達上限 @@ -1614,7 +1638,7 @@ zh-TW: disabled_account: 之後您的目前帳號將完全無法使用。但您可以存取資料匯出與重新啟用。 followers: 此動作將會將目前帳號的所有跟隨者轉移至新帳號 only_redirect_html: 或者,您也可以僅於您的個人檔案中設定重新導向。 - other_data: 其他資料並不會自動轉移 + other_data: 不會自動移動其他資料(包括您的嘟文和您跟隨之帳號列表) redirect: 您目前的帳號將於個人檔案頁面新增重新導向公告,並會被排除於搜尋結果之外 moderation: title: 站務 @@ -1648,16 +1672,22 @@ zh-TW: body: "%{name} 於嘟文中提及您:" subject: "%{name} 於嘟文中提及您" title: 新的提及 + moderation_warning: + subject: 您已收到管理員警告 poll: subject: 由 %{name} 發起的投票已結束 quote: body: 您的嘟文被 %{name} 引用: subject: "%{name} 已引用您的嘟文" title: 新引用 + quoted_update: + subject: "%{name} 已編輯一則您曾引用之嘟文" reblog: body: 您的嘟文被 %{name} 轉嘟: subject: "%{name} 已轉嘟您的嘟文" title: 新的轉嘟 + severed_relationships: + subject: 由於管理員之決定,您已失去您的社交網路 status: subject: "%{name} 剛剛嘟文" update: @@ -1857,6 +1887,9 @@ zh-TW: other: "%{count} 段影片" boosted_from_html: 轉嘟自 %{acct_link} content_warning: 內容警告: %{warning} + content_warnings: + hide: 隱藏嘟文 + show: 顯示更多 default_language: 與介面語言相同 disallowed_hashtags: other: 含有不得使用的標籤: %{tags} @@ -1864,16 +1897,22 @@ zh-TW: errors: in_reply_not_found: 您嘗試回覆之嘟文似乎不存在。 quoted_status_not_found: 您嘗試引用之嘟文似乎不存在。 + quoted_user_not_mentioned: 無法於私訊嘟文中引用無提及之使用者。 over_character_limit: 已超過 %{max} 字的限制 pin_errors: direct: 無法釘選只有僅提及使用者可見之嘟文 limit: 釘選嘟文的數量已達上限 ownership: 不能釘選他人的嘟文 reblog: 不能釘選轉嘟 + quote_error: + not_available: 無法取得該嘟文 + pending_approval: 嘟文正在等候審核中 + revoked: 嘟文已被作者移除 quote_policies: followers: 僅限跟隨者 nobody: 僅有我 public: 所有人 + quote_post_author: 已引用 %{acct} 之嘟文 title: "%{name}:「%{quote}」" visibilities: direct: 私訊 @@ -2111,3 +2150,6 @@ zh-TW: not_supported: 此瀏覽器並不支援安全金鑰 otp_required: 請先啟用兩階段驗證以使用安全金鑰。 registered_on: 註冊於 %{date} + wrapstodon: + description: 來瞧瞧 %{name} 今年是如何使用 Mastodon 吧! + title: "%{name} 的 %{year} Mastodon 年度回顧 (Wrapstodon)" diff --git a/config/navigation.rb b/config/navigation.rb index 5be491f2fc7788..a04fdbd3d03183 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -12,7 +12,8 @@ if: -> { Rails.configuration.x.mastodon.software_update_url.present? && current_user.can?(:view_devops) && SoftwareUpdate.urgent_pending? }, html: { class: 'warning' } - n.item :profile, safe_join([material_symbol('person'), t('settings.profile')]), settings_profile_path, if: -> { current_user.functional? && !self_destruct }, highlights_on: %r{/settings/profile|/settings/featured_tags|/settings/verification|/settings/privacy} + n.item :profile, safe_join([material_symbol('person'), t('settings.profile')]), settings_profile_path, if: -> { current_user.functional? && !self_destruct }, highlights_on: %r{/settings/profile|/settings/featured_tags|/settings/verification} + n.item :privacy, safe_join([material_symbol('globe'), t('privacy.title')]), settings_privacy_path, if: -> { current_user.functional? && !self_destruct }, highlights_on: %r{/settings/privacy} n.item :preferences, safe_join([material_symbol('settings'), t('settings.preferences')]), settings_preferences_path, if: -> { current_user.functional? && !self_destruct } do |s| s.item :appearance, safe_join([material_symbol('computer'), t('settings.appearance')]), settings_preferences_appearance_path diff --git a/config/puma.rb b/config/puma.rb index d34c14b425d393..50f791b0440ae4 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -12,8 +12,7 @@ bind "tcp://#{ENV.fetch('BIND', '127.0.0.1')}:#{ENV.fetch('PORT', 3000)}" end -environment ENV.fetch('RAILS_ENV') { 'development' } -workers ENV.fetch('WEB_CONCURRENCY') { 2 }.to_i +workers ENV.fetch('WEB_CONCURRENCY') { 2 }.to_i preload_app! @@ -44,12 +43,6 @@ end end -before_worker_boot do - ActiveSupport.on_load(:active_record) do - ActiveRecord::Base.establish_connection - end -end - plugin :tmp_restart set_remote_address(proxy_protocol: :v1) if ENV['PROXY_PROTO_V1'] == 'true' diff --git a/config/roles.yml b/config/roles.yml index f443250d177f76..9729cdcdc7f76e 100644 --- a/config/roles.yml +++ b/config/roles.yml @@ -4,6 +4,7 @@ moderator: permissions: - view_dashboard - view_audit_log + - view_feeds - manage_users - manage_reports - manage_taxonomies @@ -13,6 +14,7 @@ admin: permissions: - view_dashboard - view_audit_log + - view_feeds - manage_users - manage_user_access - delete_user_data diff --git a/config/routes.rb b/config/routes.rb index 227b229a5bb276..3685e695f91f52 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -95,7 +95,20 @@ def redirect_with_vary(path) get '/authorize_follow', to: redirect { |_, request| "/authorize_interaction?#{request.params.to_query}" } - resources :accounts, path: 'users', only: [:show], param: :username do + concern :account_resources do + resources :followers, only: [:index], controller: :follower_accounts + resources :following, only: [:index], controller: :following_accounts + + scope module: :activitypub do + resource :outbox, only: [:show] + resource :inbox, only: [:create] + resources :collections, only: [:show] + resource :followers_synchronization, only: [:show] + resources :quote_authorizations, only: [:show] + end + end + + resources :accounts, path: 'users', only: [:show], param: :username, concerns: :account_resources do resources :statuses, only: [:show] do member do get :activity @@ -106,16 +119,19 @@ def redirect_with_vary(path) resources :likes, only: [:index], module: :activitypub resources :shares, only: [:index], module: :activitypub end + end - resources :followers, only: [:index], controller: :follower_accounts - resources :following, only: [:index], controller: :following_accounts + scope path: 'ap', as: 'ap' do + resources :accounts, path: 'users', only: [:show], param: :id, concerns: :account_resources do + resources :statuses, only: [:show] do + member do + get :activity + end - scope module: :activitypub do - resource :outbox, only: [:show] - resource :inbox, only: [:create] - resources :collections, only: [:show] - resource :followers_synchronization, only: [:show] - resources :quote_authorizations, only: [:show] + resources :replies, only: [:index], module: :activitypub + resources :likes, only: [:index], module: :activitypub + resources :shares, only: [:index], module: :activitypub + end end end @@ -147,6 +163,7 @@ def redirect_with_vary(path) get '/@:account_username/followers', to: 'follower_accounts#index' get '/@:account_username/:id', to: 'statuses#show', as: :short_account_status get '/@:account_username/:id/embed', to: 'statuses#embed', as: :embed_short_account_status + get '/@:account_username/wrapstodon/:year/:share_key', to: 'wrapstodon#show', as: :public_wrapstodon end get '/@:username_with_domain/(*any)', to: 'home#index', constraints: { username_with_domain: %r{([^/])+?} }, as: :account_with_domain, format: false diff --git a/config/routes/api.rb b/config/routes/api.rb index e5a59682b7b15b..b600f1631dbec6 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -6,7 +6,15 @@ # Experimental JSON / REST API namespace :v1_alpha do + resources :accounts, only: [] do + resources :collections, only: [:index] + end + resources :async_refreshes, only: :show + + resources :collections, only: [:show, :create, :update, :destroy] do + resources :items, only: [:create, :destroy], controller: 'collection_items' + end end # JSON / REST API @@ -71,6 +79,8 @@ resources :annual_reports, only: [:index, :show] do member do post :read + post :generate + get :state end end diff --git a/config/settings.yml b/config/settings.yml index 212dc4033560c8..016ca997b44a61 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -12,14 +12,16 @@ defaults: &defaults registrations_mode: 'none' profile_directory: true closed_registrations_message: '' - timeline_preview: false + local_live_feed_access: 'authenticated' + remote_live_feed_access: 'authenticated' + local_topic_feed_access: 'authenticated' + remote_topic_feed_access: 'authenticated' show_staff_badge: true preview_sensitive_media: false noindex: false flavour: 'glitch' skin: 'system' trends: true - trends_as_landing_page: true trendable_by_default: false trending_status_cw: true hide_followers_count: false @@ -36,7 +38,9 @@ defaults: &defaults require_invite_text: false backups_retention_period: 7 captcha_enabled: false - allow_referer_origin: false + allow_referrer_origin: false + landing_page: 'trends' + wrapstodon: true development: <<: *defaults diff --git a/config/vite/plugin-emoji-compressed.ts b/config/vite/plugin-emoji-compressed.ts index e8a2174c3c02cc..dd81d19f55ac5e 100644 --- a/config/vite/plugin-emoji-compressed.ts +++ b/config/vite/plugin-emoji-compressed.ts @@ -15,9 +15,8 @@ export function MastodonEmojiCompressed(): Plugin { }, async load(id) { if (id === resolvedVirtualModuleId) { - const { default: emojiCompressed } = await import( - '../../app/javascript/mastodon/features/emoji/emoji_compressed.mjs' - ); + const { default: emojiCompressed } = + await import('../../app/javascript/mastodon/features/emoji/emoji_compressed.mjs'); return `export default ${JSON.stringify(emojiCompressed)};`; } diff --git a/config/vite/plugin-mastodon-themes.ts b/config/vite/plugin-mastodon-themes.ts index 251d2d8e724db6..a8d75b25f9565e 100644 --- a/config/vite/plugin-mastodon-themes.ts +++ b/config/vite/plugin-mastodon-themes.ts @@ -40,7 +40,6 @@ export function MastodonThemes(): Plugin { // Get all files mentioned in the themes.yml file. const themes = await loadThemesFromConfig(projectRoot); - for (const [themeName, themePath] of Object.entries(themes)) { entrypoints[`themes/${themeName}`] = path.resolve(jsRoot, themePath); } diff --git a/db/migrate/20250924170259_add_id_scheme_to_accounts.rb b/db/migrate/20250924170259_add_id_scheme_to_accounts.rb new file mode 100644 index 00000000000000..7dd987dcc0e168 --- /dev/null +++ b/db/migrate/20250924170259_add_id_scheme_to_accounts.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddIdSchemeToAccounts < ActiveRecord::Migration[8.0] + def change + add_column :accounts, :id_scheme, :integer, default: 0 + end +end diff --git a/db/migrate/20251002140103_migrate_timeline_preview_setting.rb b/db/migrate/20251002140103_migrate_timeline_preview_setting.rb new file mode 100644 index 00000000000000..ad0d4ab4c2f454 --- /dev/null +++ b/db/migrate/20251002140103_migrate_timeline_preview_setting.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class MigrateTimelinePreviewSetting < ActiveRecord::Migration[8.0] + class Setting < ApplicationRecord; end + + def up + Setting.reset_column_information + + setting = Setting.find_by(var: 'timeline_preview') + return unless setting.present? && setting.attributes['value'].present? + + value = YAML.safe_load(setting.attributes['value'], permitted_classes: [ActiveSupport::HashWithIndifferentAccess, Symbol]) + + Setting.upsert_all( + %w(local_live_feed_access remote_live_feed_access local_topic_feed_access remote_topic_feed_access).map do |var| + { var: var, value: value ? "--- public\n" : "--- authenticated\n" } + end, + unique_by: index_exists?(:settings, [:thing_type, :thing_id, :var]) ? [:thing_type, :thing_id, :var] : :var + ) + end + + def down; end +end diff --git a/db/migrate/20251007100627_add_index_follows_on_target_account_id_and_account_id.rb b/db/migrate/20251007100627_add_index_follows_on_target_account_id_and_account_id.rb new file mode 100644 index 00000000000000..23c08d2d061fe2 --- /dev/null +++ b/db/migrate/20251007100627_add_index_follows_on_target_account_id_and_account_id.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddIndexFollowsOnTargetAccountIdAndAccountId < ActiveRecord::Migration[8.0] + disable_ddl_transaction! + + def change + add_index :follows, [:target_account_id, :account_id], algorithm: :concurrently + end +end diff --git a/db/migrate/20251007100813_remove_index_follows_on_target_account_id.rb b/db/migrate/20251007100813_remove_index_follows_on_target_account_id.rb new file mode 100644 index 00000000000000..142086f35829bb --- /dev/null +++ b/db/migrate/20251007100813_remove_index_follows_on_target_account_id.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class RemoveIndexFollowsOnTargetAccountId < ActiveRecord::Migration[8.0] + disable_ddl_transaction! + + def change + remove_index :follows, [:target_account_id], algorithm: :concurrently + end +end diff --git a/db/migrate/20251007142305_change_defaults_for_account_id_scheme.rb b/db/migrate/20251007142305_change_defaults_for_account_id_scheme.rb new file mode 100644 index 00000000000000..6c07236968a05d --- /dev/null +++ b/db/migrate/20251007142305_change_defaults_for_account_id_scheme.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class ChangeDefaultsForAccountIdScheme < ActiveRecord::Migration[8.0] + def change + change_column_default :accounts, :id_scheme, from: 0, to: 1 + end +end diff --git a/db/migrate/20251023210145_migrate_landing_page_setting.rb b/db/migrate/20251023210145_migrate_landing_page_setting.rb new file mode 100644 index 00000000000000..db9dc333b9afe0 --- /dev/null +++ b/db/migrate/20251023210145_migrate_landing_page_setting.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class MigrateLandingPageSetting < ActiveRecord::Migration[8.0] + class Setting < ApplicationRecord; end + + def up + Setting.reset_column_information + + setting = Setting.find_by(var: 'trends_as_landing_page') + return unless setting.present? && setting.attributes['value'].present? + + value = YAML.safe_load(setting.attributes['value'], permitted_classes: [ActiveSupport::HashWithIndifferentAccess, Symbol]) + + Setting.upsert({ + var: 'landing_page', + value: value ? "--- trends\n" : "--- about\n", + }, + unique_by: index_exists?(:settings, [:thing_type, :thing_id, :var]) ? [:thing_type, :thing_id, :var] : :var) + end + + def down; end +end diff --git a/db/migrate/20251117023614_add_thumbnail_storage_schema_version.rb b/db/migrate/20251117023614_add_thumbnail_storage_schema_version.rb new file mode 100644 index 00000000000000..0a8119642d4ba7 --- /dev/null +++ b/db/migrate/20251117023614_add_thumbnail_storage_schema_version.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddThumbnailStorageSchemaVersion < ActiveRecord::Migration[8.0] + def change + add_column :media_attachments, :thumbnail_storage_schema_version, :integer + end +end diff --git a/db/migrate/20251118115657_create_collections.rb b/db/migrate/20251118115657_create_collections.rb new file mode 100644 index 00000000000000..299cc7aade6c38 --- /dev/null +++ b/db/migrate/20251118115657_create_collections.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class CreateCollections < ActiveRecord::Migration[8.0] + def change + create_table :collections do |t| + t.references :account, null: false, foreign_key: true + t.string :name, null: false + t.text :description, null: false + t.string :uri + t.boolean :local, null: false # rubocop:disable Rails/ThreeStateBooleanColumn + t.boolean :sensitive, null: false # rubocop:disable Rails/ThreeStateBooleanColumn + t.boolean :discoverable, null: false # rubocop:disable Rails/ThreeStateBooleanColumn + t.references :tag, foreign_key: true + t.integer :original_number_of_items + + t.timestamps + end + end +end diff --git a/db/migrate/20251119093332_create_collection_items.rb b/db/migrate/20251119093332_create_collection_items.rb new file mode 100644 index 00000000000000..9fc5d99df53c2f --- /dev/null +++ b/db/migrate/20251119093332_create_collection_items.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class CreateCollectionItems < ActiveRecord::Migration[8.0] + def change + create_table :collection_items do |t| + t.references :collection, null: false, foreign_key: { on_delete: :cascade } + t.references :account, foreign_key: true + t.integer :position, null: false, default: 1 + t.string :object_uri, index: { unique: true, where: 'activity_uri IS NOT NULL' } + t.string :approval_uri, index: { unique: true, where: 'approval_uri IS NOT NULL' } + t.string :activity_uri + t.datetime :approval_last_verified_at + t.integer :state, null: false, default: 0 + + t.timestamps + end + end +end diff --git a/db/migrate/20251201154910_add_featured_emoji_to_custom_emoji_categories.rb b/db/migrate/20251201154910_add_featured_emoji_to_custom_emoji_categories.rb new file mode 100644 index 00000000000000..149b0b994ecaf2 --- /dev/null +++ b/db/migrate/20251201154910_add_featured_emoji_to_custom_emoji_categories.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class AddFeaturedEmojiToCustomEmojiCategories < ActiveRecord::Migration[8.0] + def change + add_column :custom_emoji_categories, :featured_emoji_id, :bigint, null: true + add_foreign_key :custom_emoji_categories, :custom_emojis, column: :featured_emoji_id, on_delete: :nullify, validate: false + end +end diff --git a/db/migrate/20251201155054_validate_add_featured_emoji_to_custom_emoji_categories.rb b/db/migrate/20251201155054_validate_add_featured_emoji_to_custom_emoji_categories.rb new file mode 100644 index 00000000000000..3ff5ce7b5ef25a --- /dev/null +++ b/db/migrate/20251201155054_validate_add_featured_emoji_to_custom_emoji_categories.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class ValidateAddFeaturedEmojiToCustomEmojiCategories < ActiveRecord::Migration[8.0] + def change + validate_foreign_key :custom_emoji_categories, :custom_emojis + end +end diff --git a/db/migrate/20251202140424_add_share_key_to_generated_annual_reports.rb b/db/migrate/20251202140424_add_share_key_to_generated_annual_reports.rb new file mode 100644 index 00000000000000..aa4d15a15a26a0 --- /dev/null +++ b/db/migrate/20251202140424_add_share_key_to_generated_annual_reports.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddShareKeyToGeneratedAnnualReports < ActiveRecord::Migration[8.0] + def change + add_column :generated_annual_reports, :share_key, :string + end +end diff --git a/db/migrate/20251209093813_add_item_count_to_collections.rb b/db/migrate/20251209093813_add_item_count_to_collections.rb new file mode 100644 index 00000000000000..071cfab46ab962 --- /dev/null +++ b/db/migrate/20251209093813_add_item_count_to_collections.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddItemCountToCollections < ActiveRecord::Migration[8.0] + def change + add_column :collections, :item_count, :integer, default: 0, null: false + end +end diff --git a/db/migrate/20251217091936_add_feature_approval_policy_to_accounts.rb b/db/migrate/20251217091936_add_feature_approval_policy_to_accounts.rb new file mode 100644 index 00000000000000..6d6e6e820568e1 --- /dev/null +++ b/db/migrate/20251217091936_add_feature_approval_policy_to_accounts.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddFeatureApprovalPolicyToAccounts < ActiveRecord::Migration[8.0] + def change + add_column :accounts, :feature_approval_policy, :integer, null: false, default: 0 + end +end diff --git a/db/schema.rb b/db/schema.rb index fd1a4c88ab8d21..216c1e00cfd574 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2025_09_12_082651) do +ActiveRecord::Schema[8.0].define(version: 2025_12_17_091936) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" @@ -199,6 +199,8 @@ t.boolean "indexable", default: false, null: false t.string "attribution_domains", default: [], array: true t.string "following_url", default: "", null: false + t.integer "id_scheme", default: 1 + t.integer "feature_approval_policy", default: 0, null: false t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin t.index "lower((username)::text), COALESCE(lower((domain)::text), ''::text)", name: "index_accounts_on_username_and_domain_lower", unique: true t.index ["domain", "id"], name: "index_accounts_on_domain_and_id" @@ -350,6 +352,40 @@ t.index ["reference_account_id"], name: "index_canonical_email_blocks_on_reference_account_id" end + create_table "collection_items", force: :cascade do |t| + t.bigint "collection_id", null: false + t.bigint "account_id" + t.integer "position", default: 1, null: false + t.string "object_uri" + t.string "approval_uri" + t.string "activity_uri" + t.datetime "approval_last_verified_at" + t.integer "state", default: 0, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["account_id"], name: "index_collection_items_on_account_id" + t.index ["approval_uri"], name: "index_collection_items_on_approval_uri", unique: true, where: "(approval_uri IS NOT NULL)" + t.index ["collection_id"], name: "index_collection_items_on_collection_id" + t.index ["object_uri"], name: "index_collection_items_on_object_uri", unique: true, where: "(activity_uri IS NOT NULL)" + end + + create_table "collections", force: :cascade do |t| + t.bigint "account_id", null: false + t.string "name", null: false + t.text "description", null: false + t.string "uri" + t.boolean "local", null: false + t.boolean "sensitive", null: false + t.boolean "discoverable", null: false + t.bigint "tag_id" + t.integer "original_number_of_items" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "item_count", default: 0, null: false + t.index ["account_id"], name: "index_collections_on_account_id" + t.index ["tag_id"], name: "index_collections_on_tag_id" + end + create_table "conversation_mutes", force: :cascade do |t| t.bigint "conversation_id", null: false t.bigint "account_id", null: false @@ -370,6 +406,7 @@ t.string "name" t.datetime "created_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false + t.bigint "featured_emoji_id" t.index ["name"], name: "index_custom_emoji_categories_on_name", unique: true end @@ -570,7 +607,7 @@ t.boolean "notify", default: false, null: false t.string "languages", array: true t.index ["account_id", "target_account_id"], name: "index_follows_on_account_id_and_target_account_id", unique: true - t.index ["target_account_id"], name: "index_follows_on_target_account_id" + t.index ["target_account_id", "account_id"], name: "index_follows_on_target_account_id_and_account_id" end create_table "generated_annual_reports", force: :cascade do |t| @@ -581,6 +618,7 @@ t.datetime "viewed_at" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.string "share_key" t.index ["account_id", "year"], name: "index_generated_annual_reports_on_account_id_and_year", unique: true end @@ -693,6 +731,7 @@ t.integer "thumbnail_file_size" t.datetime "thumbnail_updated_at", precision: nil t.string "thumbnail_remote_url" + t.integer "thumbnail_storage_schema_version" t.index ["account_id", "status_id"], name: "index_media_attachments_on_account_id_and_status_id", order: { status_id: :desc } t.index ["scheduled_status_id"], name: "index_media_attachments_on_scheduled_status_id", where: "(scheduled_status_id IS NOT NULL)" t.index ["shortcode"], name: "index_media_attachments_on_shortcode", unique: true, opclass: :text_pattern_ops, where: "(shortcode IS NOT NULL)" @@ -1388,8 +1427,13 @@ add_foreign_key "bulk_import_rows", "bulk_imports", on_delete: :cascade add_foreign_key "bulk_imports", "accounts", on_delete: :cascade add_foreign_key "canonical_email_blocks", "accounts", column: "reference_account_id", on_delete: :cascade + add_foreign_key "collection_items", "accounts" + add_foreign_key "collection_items", "collections", on_delete: :cascade + add_foreign_key "collections", "accounts" + add_foreign_key "collections", "tags" add_foreign_key "conversation_mutes", "accounts", name: "fk_225b4212bb", on_delete: :cascade add_foreign_key "conversation_mutes", "conversations", on_delete: :cascade + add_foreign_key "custom_emoji_categories", "custom_emojis", column: "featured_emoji_id", on_delete: :nullify add_foreign_key "custom_filter_keywords", "custom_filters", on_delete: :cascade add_foreign_key "custom_filter_statuses", "custom_filters", on_delete: :cascade add_foreign_key "custom_filter_statuses", "statuses", on_delete: :cascade diff --git a/dist/mastodon-prometheus-exporter.service b/dist/mastodon-prometheus-exporter.service new file mode 100644 index 00000000000000..28691b05757f9c --- /dev/null +++ b/dist/mastodon-prometheus-exporter.service @@ -0,0 +1,52 @@ +[Unit] +Description=Prometheus Metrics Exporter Server for Mastodon +Documentation="https://docs.joinmastodon.org/admin/config/#prometheus https://github.com/discourse/prometheus_exporter/?tab=readme-ov-file#exporter-process-configuration" +After=network.target + +[Service] +Type=simple +User=mastodon +WorkingDirectory=/home/mastodon/live +Environment="LD_PRELOAD=libjemalloc.so" +ExecStart=/home/mastodon/.rbenv/shims/bundle exec bin/prometheus_exporter -p 9394 -b 127.0.0.1 --prefix 'mastodon_' +ExecReload=/bin/kill -SIGUSR1 $MAINPID +TimeoutSec=15 +Restart=always +# Proc filesystem +ProcSubset=pid +ProtectProc=invisible +# Capabilities +CapabilityBoundingSet= +# Security +NoNewPrivileges=true +# Sandboxing +ProtectSystem=strict +PrivateTmp=true +PrivateDevices=true +PrivateUsers=true +ProtectHostname=true +ProtectKernelLogs=true +ProtectKernelModules=true +ProtectKernelTunables=true +ProtectControlGroups=true +RestrictAddressFamilies=AF_INET +RestrictAddressFamilies=AF_INET6 +RestrictAddressFamilies=AF_NETLINK +RestrictAddressFamilies=AF_UNIX +RestrictNamespaces=true +LockPersonality=true +RestrictRealtime=true +RestrictSUIDSGID=true +RemoveIPC=true +PrivateMounts=true +ProtectClock=true +# System Call Filtering +SystemCallArchitectures=native +SystemCallFilter=~@cpu-emulation @debug @keyring @ipc @mount @obsolete @privileged @setuid +SystemCallFilter=@chown +SystemCallFilter=pipe +SystemCallFilter=pipe2 +ReadWritePaths=/home/mastodon/live + +[Install] +WantedBy=multi-user.target diff --git a/dist/nginx.conf b/dist/nginx.conf index 8b7c68d2872c1c..a421e00006066c 100644 --- a/dist/nginx.conf +++ b/dist/nginx.conf @@ -52,6 +52,7 @@ server { keepalive_timeout 70; sendfile on; client_max_body_size 99m; + proxy_read_timeout 120; ## Increase if you experience 504 errors uploading media root /home/mastodon/live/public; @@ -66,65 +67,53 @@ server { gzip_static on; location / { - try_files $uri @proxy; - } - - # If Docker is used for deployment and Rails serves static files, - # then needed must replace line `try_files $uri =404;` with `try_files $uri @proxy;`. - location = /sw.js { - add_header Cache-Control "public, max-age=604800, must-revalidate"; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; - try_files $uri =404; + try_files $uri @mastodon; } - location ~ ^/assets/ { + # If Docker is used for deployment and Rails serves static files, + # then needed uncomment line with `try_files $uri @mastodon;`. + location ^~ /assets/ { add_header Cache-Control "public, max-age=2419200, must-revalidate"; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; - try_files $uri =404; + # try_files $uri @mastodon; } - location ~ ^/avatars/ { + location ^~ /avatars/ { add_header Cache-Control "public, max-age=2419200, must-revalidate"; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; - try_files $uri =404; } - location ~ ^/emoji/ { + location ^~ /emoji/ { add_header Cache-Control "public, max-age=2419200, must-revalidate"; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; - try_files $uri =404; } - location ~ ^/headers/ { + location ^~ /headers/ { add_header Cache-Control "public, max-age=2419200, must-revalidate"; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; - try_files $uri =404; } - location ~ ^/packs/ { + location ^~ /ocr/ { add_header Cache-Control "public, max-age=2419200, must-revalidate"; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; - try_files $uri =404; } - location ~ ^/shortcuts/ { + location ^~ /packs/ { add_header Cache-Control "public, max-age=2419200, must-revalidate"; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; - try_files $uri =404; } - location ~ ^/sounds/ { + location ^~ /sounds/ { add_header Cache-Control "public, max-age=2419200, must-revalidate"; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; - try_files $uri =404; } - location ~ ^/system/ { + location ^~ /system/ { add_header Cache-Control "public, max-age=2419200, immutable"; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; add_header X-Content-Type-Options nosniff; add_header Content-Security-Policy "default-src 'none'; form-action 'none'"; - try_files $uri =404; } location ^~ /api/v1/streaming { @@ -141,12 +130,10 @@ server { proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; - add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; - tcp_nodelay on; } - location @proxy { + location @mastodon { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/docker-compose.yml b/docker-compose.yml index d0ae54ed0ca942..fae93955f14165 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -27,7 +27,7 @@ services: # es: # restart: always - # image: docker.elastic.co/elasticsearch/elasticsearch:7.17.4 + # image: docker.elastic.co/elasticsearch/elasticsearch:7.17.29 # environment: # - "ES_JAVA_OPTS=-Xms512m -Xmx512m -Des.enforce.bootstrap.checks=true" # - "xpack.license.self_generated.type=basic" @@ -59,7 +59,7 @@ services: web: # You can uncomment the following line if you want to not use the prebuilt image, for example if you have local code changes # build: . - image: ghcr.io/glitch-soc/mastodon:v4.4.4 + image: ghcr.io/glitch-soc/mastodon:v4.5.3 restart: always env_file: .env.production command: bundle exec puma -C config/puma.rb @@ -83,7 +83,7 @@ services: # build: # dockerfile: ./streaming/Dockerfile # context: . - image: ghcr.io/glitch-soc/mastodon-streaming:v4.4.4 + image: ghcr.io/glitch-soc/mastodon-streaming:v4.5.3 restart: always env_file: .env.production command: node ./streaming/index.js @@ -102,7 +102,7 @@ services: sidekiq: # You can uncomment the following line if you want to not use the prebuilt image, for example if you have local code changes # build: . - image: ghcr.io/glitch-soc/mastodon:v4.4.4 + image: ghcr.io/glitch-soc/mastodon:v4.5.3 restart: always env_file: .env.production command: bundle exec sidekiq @@ -115,7 +115,7 @@ services: volumes: - ./public/system:/mastodon/public/system healthcheck: - test: ['CMD-SHELL', "ps aux | grep '[s]idekiq\ 7' || false"] + test: ['CMD-SHELL', "ps aux | grep '[s]idekiq\ 8' || false"] ## Uncomment to enable federation with tor instances along with adding the following ENV variables ## http_hidden_proxy=http://privoxy:8118 diff --git a/eslint.config.mjs b/eslint.config.mjs index e41e1c9b32c833..3647695d3a93d1 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -5,7 +5,6 @@ import path from 'node:path'; import js from '@eslint/js'; import { globalIgnores } from 'eslint/config'; import formatjs from 'eslint-plugin-formatjs'; -// @ts-expect-error -- No typings import importPlugin from 'eslint-plugin-import'; import jsdoc from 'eslint-plugin-jsdoc'; import jsxA11Y from 'eslint-plugin-jsx-a11y'; @@ -199,10 +198,12 @@ export default tseslint.config([ 'tmp/**/*', 'vendor/**/*', 'streaming/**/*', + '.bundle/**/*', + 'storybook-static/**/*', ]), react.configs.flat.recommended, react.configs.flat['jsx-runtime'], - reactHooks.configs['recommended-latest'], + reactHooks.configs.flat.recommended, jsxA11Y.flatConfigs.recommended, importPlugin.flatConfigs.react, // @ts-expect-error -- For some reason the formatjs package exports an empty object? @@ -309,6 +310,7 @@ export default tseslint.config([ 'react/jsx-tag-spacing': 'error', 'react/jsx-wrap-multilines': 'error', 'react/self-closing-comp': 'error', + 'react/button-has-type': 'error', }, }, { @@ -351,7 +353,7 @@ export default tseslint.config([ tseslint.configs.stylisticTypeChecked, react.configs.flat.recommended, react.configs.flat['jsx-runtime'], - reactHooks.configs['recommended-latest'], + reactHooks.configs.flat.recommended, jsxA11Y.flatConfigs.recommended, importPlugin.flatConfigs.react, importPlugin.flatConfigs.typescript, diff --git a/ide-helper.js b/ide-helper.js deleted file mode 100644 index 9e645cb0ebe1c1..00000000000000 --- a/ide-helper.js +++ /dev/null @@ -1,12 +0,0 @@ -/* global path */ -/* -Preferences | Languages & Frameworks | JavaScript | Webpack | webpack configuration file -jetbrains://WebStorm/settings?name=Languages+%26+Frameworks--JavaScript--Webpack -*/ -module.exports = { - resolve: { - alias: { - 'mastodon': path.resolve(__dirname, 'app/javascript/mastodon'), - }, - }, -}; diff --git a/lib/mastodon/cli/accounts.rb b/lib/mastodon/cli/accounts.rb index 1b33f56055a901..25e966bd8eabc9 100644 --- a/lib/mastodon/cli/accounts.rb +++ b/lib/mastodon/cli/accounts.rb @@ -165,14 +165,17 @@ def modify(username) user.role_id = nil end - password = SecureRandom.hex if options[:reset_password] - user.password = password if options[:reset_password] user.email = options[:email] if options[:email] user.disabled = false if options[:enable] user.disabled = true if options[:disable] user.approved = true if options[:approve] user.disable_two_factor! if options[:disable_2fa] + # Password changes are a little different, as we also need to ensure + # sessions, subscriptions, and access tokens are revoked after changing: + password = SecureRandom.hex if options[:reset_password] + user.change_password!(password) if options[:reset_password] + if user.save user.confirm if options[:confirm] diff --git a/lib/mastodon/cli/email_domain_blocks.rb b/lib/mastodon/cli/email_domain_blocks.rb index 0cc9ccb7058bae..a6093685b9bb35 100644 --- a/lib/mastodon/cli/email_domain_blocks.rb +++ b/lib/mastodon/cli/email_domain_blocks.rb @@ -5,9 +5,33 @@ module Mastodon::CLI class EmailDomainBlocks < Base + option :only_blocked, type: :boolean, defaut: false + option :only_with_approval, type: :boolean, default: false desc 'list', 'List blocked e-mail domains' + long_desc <<-LONG_DESC + By default this command lists all domains in the email domain block list + and their associated MX records (if included). + + If the --only-blocked option is provided, this command will list only email + domains that are fully blocked from signup. + + If the --only-with-approval option is provided, this command will list only + email domains that are allowed to be used but require manual approval. + + The --only-blocked and --only-with-approval options are mutually exclusive. + LONG_DESC def list - EmailDomainBlock.parents.find_each do |parent| + fail_with_message 'Cannot specify both --only-blocked and --only-with-approval' if options[:only_blocked] && options[:only_with_approval] + + base_query = EmailDomainBlock.parents + + if options[:only_blocked] + base_query = base_query.where(allow_with_approval: false) + elsif options[:only_with_approval] + base_query = base_query.where(allow_with_approval: true) + end + + base_query.find_each do |parent| say(parent.domain.to_s, :white) shell.indent do @@ -19,6 +43,7 @@ def list end option :with_dns_records, type: :boolean + option :allow_with_approval, type: :boolean, defaut: false desc 'add DOMAIN...', 'Block e-mail domain(s)' long_desc <<-LONG_DESC Blocking an e-mail domain prevents users from signing up @@ -30,6 +55,9 @@ def list This can be helpful if you are blocking an e-mail server that has many different domains pointing to it as it allows you to essentially block it at the root. + + When the --allow-with-approval option is set, the email domains provided will + have to be manually approved for signup. LONG_DESC def add(*domains) fail_with_message 'No domain(s) given' if domains.empty? @@ -47,19 +75,18 @@ def add(*domains) other_domains = [] other_domains = DomainResource.new(domain).mx if options[:with_dns_records] - email_domain_block = EmailDomainBlock.new(domain: domain, other_domains: other_domains) + email_domain_block = EmailDomainBlock.new(domain: domain, other_domains: other_domains, allow_with_approval: options[:allow_with_approval]) email_domain_block.save! processed += 1 (email_domain_block.other_domains || []).uniq.each do |hostname| - another_email_domain_block = EmailDomainBlock.new(domain: hostname, parent: email_domain_block) - if EmailDomainBlock.exists?(domain: hostname) say("#{hostname} is already blocked.", :yellow) skipped += 1 next end + another_email_domain_block = EmailDomainBlock.new(domain: hostname, parent: email_domain_block, allow_with_approval: options[:allow_with_approval]) another_email_domain_block.save! processed += 1 end diff --git a/lib/mastodon/cli/ip_blocks.rb b/lib/mastodon/cli/ip_blocks.rb index ea6b508c6fe8e4..ba31e6b5ce4e06 100644 --- a/lib/mastodon/cli/ip_blocks.rb +++ b/lib/mastodon/cli/ip_blocks.rb @@ -107,9 +107,9 @@ def export IpBlock.severity_no_access.find_each do |ip_block| case options[:format] when 'nginx' - say "deny #{ip_block.ip}/#{ip_block.ip.prefix};" + say "deny #{ip_block.to_cidr};" else - say "#{ip_block.ip}/#{ip_block.ip.prefix}" + say ip_block.to_cidr end end end diff --git a/lib/mastodon/cli/statuses.rb b/lib/mastodon/cli/statuses.rb index 7104181e978713..7188bc970cecbe 100644 --- a/lib/mastodon/cli/statuses.rb +++ b/lib/mastodon/cli/statuses.rb @@ -62,6 +62,8 @@ def remove_statuses AND NOT EXISTS (SELECT 1 FROM mentions WHERE statuses.id = mentions.status_id AND mentions.account_id IN (SELECT accounts.id FROM accounts WHERE domain IS NULL)) AND NOT EXISTS (SELECT 1 FROM favourites WHERE statuses.id = favourites.status_id AND favourites.account_id IN (SELECT accounts.id FROM accounts WHERE domain IS NULL)) AND NOT EXISTS (SELECT 1 FROM bookmarks WHERE statuses.id = bookmarks.status_id AND bookmarks.account_id IN (SELECT accounts.id FROM accounts WHERE domain IS NULL)) + AND NOT EXISTS (SELECT 1 FROM quotes JOIN statuses statuses1 ON quotes.status_id = statuses1.id WHERE quotes.quoted_status_id = statuses.id AND (statuses1.uri IS NULL OR statuses1.local)) + AND NOT EXISTS (SELECT 1 FROM quotes JOIN statuses statuses1 ON quotes.quoted_status_id = statuses1.id WHERE quotes.status_id = statuses.id AND (statuses1.uri IS NULL OR statuses1.local)) #{clean_followed_sql} SQL diff --git a/lib/mastodon/cli/upgrade.rb b/lib/mastodon/cli/upgrade.rb index 2cb510579484a5..d5822cacc0c87f 100644 --- a/lib/mastodon/cli/upgrade.rb +++ b/lib/mastodon/cli/upgrade.rb @@ -123,12 +123,12 @@ def upgrade_storage_filesystem(progress, attachment, style) progress.log("Moving #{previous_path} to #{upgraded_path}") if options[:verbose] begin - move_previous_to_upgraded + move_previous_to_upgraded(previous_path, upgraded_path) rescue => e progress.log(pastel.red("Error processing #{previous_path}: #{e}")) success = false - remove_directory + remove_directory(upgraded_path) end end diff --git a/lib/mastodon/email_configuration_helper.rb b/lib/mastodon/email_configuration_helper.rb index af86472786ad0f..be017a4a1ff818 100644 --- a/lib/mastodon/email_configuration_helper.rb +++ b/lib/mastodon/email_configuration_helper.rb @@ -8,25 +8,23 @@ module EmailConfigurationHelper # `config/email.yml`) into the format that `ActionMailer` understands def convert_smtp_settings(config) enable_starttls = nil - enable_starttls_auto = nil case config[:enable_starttls] when 'always' - enable_starttls = true - when 'never' + enable_starttls = :always + when 'never', 'false' enable_starttls = false when 'auto' - enable_starttls_auto = true + enable_starttls = :auto else - enable_starttls_auto = config[:enable_starttls_auto] != 'false' + enable_starttls = config[:enable_starttls_auto] ? :auto : false unless config[:tls] || config[:ssl] end authentication = config[:authentication] == 'none' ? nil : (config[:authentication] || 'plain') - config.merge( + config.without(:enable_starttls_auto).merge( authentication:, - enable_starttls:, - enable_starttls_auto: + enable_starttls: ) end end diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index 043f22b28e5333..9ac54d1b7c1858 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -9,7 +9,7 @@ def major end def minor - 5 + 6 end def patch @@ -17,7 +17,7 @@ def patch end def default_prerelease - 'alpha.2' + 'alpha.1' end def prerelease diff --git a/lib/tasks/db.rake b/lib/tasks/db.rake index 054f8b0177b641..e5ca6ac2ac5b1b 100644 --- a/lib/tasks/db.rake +++ b/lib/tasks/db.rake @@ -63,7 +63,7 @@ namespace :db do task pre_migration_check: :environment do pg_version = ActiveRecord::Base.connection.database_version - abort 'This version of Mastodon requires PostgreSQL 13.0 or newer. Please update PostgreSQL before updating Mastodon.' if pg_version < 130_000 + abort 'This version of Mastodon requires PostgreSQL 14.0 or newer. Please update PostgreSQL before updating Mastodon.' if pg_version < 140_000 schema_version = ActiveRecord::Migrator.current_version abort <<~MESSAGE if ENV['SKIP_POST_DEPLOYMENT_MIGRATIONS'] && schema_version < 2023_09_07_150100 diff --git a/lib/tasks/emojis.rake b/lib/tasks/emojis.rake index 330101f555a88c..053dfd83bd0063 100644 --- a/lib/tasks/emojis.rake +++ b/lib/tasks/emojis.rake @@ -48,6 +48,7 @@ def get_image(row, emoji_base, fallback, compressed) if path.exist? Vips::Image.new_from_file(path.to_s, dpi: 64) else + puts "Missing emoji: #{row['b'] || row['unified']}" fallback end end @@ -59,7 +60,7 @@ end namespace :emojis do desc 'Generate a unicode to filename mapping' task :generate do - source = 'http://www.unicode.org/Public/emoji/15.1/emoji-test.txt' + source = 'https://www.unicode.org/Public/emoji/16.0/emoji-test.txt' codes = [] dest = Rails.root.join('app', 'javascript', 'mastodon', 'features', 'emoji', 'emoji_map.json') @@ -120,7 +121,7 @@ namespace :emojis do desc 'Generate the JSON emoji data' task :generate_json do - data_source = 'https://raw.githubusercontent.com/iamcal/emoji-data/refs/tags/v15.1.2/emoji.json' + data_source = 'https://raw.githubusercontent.com/iamcal/emoji-data/refs/tags/v16.0.0/emoji.json' keyword_source = 'https://raw.githubusercontent.com/muan/emojilib/refs/tags/v3.0.12/dist/emoji-en-US.json' data_dest = Rails.root.join('app', 'javascript', 'mastodon', 'features', 'emoji', 'emoji_data.json') @@ -224,6 +225,6 @@ namespace :emojis do end joined = Vips::Image.arrayjoin(comp.flatten, across: size, hspacing: 34, halign: :centre, vspacing: 34, valign: :centre) - joined.write_to_file(emoji_base.join('sheet_15_1.png').to_s, palette: true, dither: 0, Q: 100) + joined.write_to_file(emoji_base.join('sheet_16_0.png').to_s, palette: true, dither: 0, Q: 100) end end diff --git a/lib/tasks/tests.rake b/lib/tasks/tests.rake index 9385e390ded43b..0c5c6a09caf734 100644 --- a/lib/tasks/tests.rake +++ b/lib/tasks/tests.rake @@ -144,6 +144,16 @@ namespace :tests do exit(1) end + unless Setting.landing_page == 'about' + puts 'Landing page settings not migrated as expected' + exit(1) + end + + unless Setting.local_live_feed_access == 'authenticated' + puts 'Local live feed access not migrated as expected' + exit(1) + end + puts 'No errors found. Database state is consistent with a successful migration process.' end @@ -162,6 +172,13 @@ namespace :tests do (1, 'https://example.com/users/foobar', 'foobar@example.com', now(), now()), (1, 'https://example.com/users/foobar', 'foobar@example.com', now(), now()); + /* trends_as_landing_page is technically not a 3.3.0 setting, but it's easier to just add it here */ + INSERT INTO "settings" + (id, thing_type, thing_id, var, value, created_at, updated_at) + VALUES + (7, NULL, NULL, 'timeline_preview', E'--- false\n', now(), now()), + (8, NULL, NULL, 'trends_as_landing_page', E'--- false\n', now(), now()); + /* Doorkeeper records While the `read:me` scope was technically not valid in 3.3.0, it is still useful for the purposes of testing the `ChangeReadMeScopeToProfile` diff --git a/lib/vite_ruby/sri_extensions.rb b/lib/vite_ruby/sri_extensions.rb index d97ab352da00b8..31363272bfa3c3 100644 --- a/lib/vite_ruby/sri_extensions.rb +++ b/lib/vite_ruby/sri_extensions.rb @@ -107,6 +107,7 @@ def vite_javascript_tag(*names, stylesheet, integrity: vite_manifest.integrity_hash_for_file(stylesheet), media: media, + crossorigin: crossorigin, **options ) end diff --git a/package.json b/package.json index d4800470924288..d1334b803a0e4a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@mastodon/mastodon", "license": "AGPL-3.0-or-later", - "packageManager": "yarn@4.9.4", + "packageManager": "yarn@4.12.0", "engines": { "node": ">=20" }, @@ -47,8 +47,7 @@ "@formatjs/intl-pluralrules": "^5.4.4", "@gamestdio/websocket": "^0.3.2", "@github/webauthn-json": "^2.1.1", - "@optimize-lodash/rollup-plugin": "^5.0.2", - "@rails/ujs": "7.1.502", + "@optimize-lodash/rollup-plugin": "^6.0.0", "@react-spring/web": "^9.7.5", "@reduxjs/toolkit": "^2.0.1", "@use-gesture/react": "^10.3.1", @@ -67,6 +66,7 @@ "core-js": "^3.30.2", "cross-env": "^10.0.0", "debug": "^4.4.1", + "delegated-events": "^1.1.2", "detect-passive-events": "^2.0.3", "emoji-mart": "npm:emoji-mart-lazyload@latest", "emojibase": "^16.0.0", @@ -101,10 +101,8 @@ "react-redux-loading-bar": "^5.0.8", "react-router": "^5.3.4", "react-router-dom": "^5.3.4", - "react-router-scroll-4": "^1.0.0-beta.1", "react-select": "^5.7.3", "react-sparklines": "^1.7.0", - "react-swipeable-views": "^0.14.0", "react-textarea-autosize": "^8.4.1", "react-toggle": "^4.1.3", "redux-immutable": "^4.0.0", @@ -113,19 +111,19 @@ "rollup-plugin-gzip": "^4.1.1", "rollup-plugin-visualizer": "^6.0.3", "sass": "^1.62.1", + "scroll-behavior": "^0.11.0", "stacktrace-js": "^2.0.2", "stringz": "^2.1.0", "substring-trie": "^1.0.2", - "tesseract.js": "^6.0.0", + "tesseract.js": "^7.0.0", "tiny-queue": "^0.2.1", "twitter-text": "3.1.0", "use-debounce": "^10.0.0", "vite": "^7.1.1", "vite-plugin-manifest-sri": "^0.2.0", "vite-plugin-pwa": "^1.0.2", - "vite-plugin-static-copy": "^3.1.1", "vite-plugin-svgr": "^4.3.0", - "vite-tsconfig-paths": "^5.1.4", + "vite-tsconfig-paths": "^6.0.0", "wicg-inert": "^3.1.2", "workbox-expiration": "^7.3.0", "workbox-routing": "^7.3.0", @@ -135,10 +133,10 @@ "devDependencies": { "@eslint/js": "^9.23.0", "@formatjs/cli": "^6.1.1", - "@storybook/addon-a11y": "^9.1.1", - "@storybook/addon-docs": "^9.1.1", - "@storybook/addon-vitest": "^9.1.1", - "@storybook/react-vite": "^9.1.1", + "@storybook/addon-a11y": "^10.0.6", + "@storybook/addon-docs": "^10.0.6", + "@storybook/addon-vitest": "^10.0.6", + "@storybook/react-vite": "^10.0.6", "@testing-library/dom": "^10.4.1", "@testing-library/react": "^16.3.0", "@types/debug": "^4", @@ -152,7 +150,6 @@ "@types/object-assign": "^4.0.30", "@types/prop-types": "^15.7.5", "@types/punycode": "^2.1.0", - "@types/rails__ujs": "^6.0.4", "@types/react": "^18.2.7", "@types/react-dom": "^18.2.4", "@types/react-helmet": "^6.1.6", @@ -160,41 +157,42 @@ "@types/react-router": "^5.1.20", "@types/react-router-dom": "^5.3.3", "@types/react-sparklines": "^1.7.2", - "@types/react-swipeable-views": "^0.13.1", "@types/react-test-renderer": "^18.0.0", "@types/react-toggle": "^4.0.3", "@types/redux-immutable": "^4.0.3", "@types/requestidlecallback": "^0.3.5", - "@vitest/browser": "^3.2.4", - "@vitest/coverage-v8": "^3.2.4", - "@vitest/ui": "^3.2.4", - "chromatic": "^13.1.3", + "@vitest/browser": "^4.0.5", + "@vitest/browser-playwright": "^4.0.5", + "@vitest/coverage-v8": "^4.0.5", + "@vitest/ui": "^4.0.5", + "chromatic": "^13.3.3", "eslint": "^9.23.0", "eslint-import-resolver-typescript": "^4.2.5", "eslint-plugin-formatjs": "^5.3.1", "eslint-plugin-import": "~2.32.0", - "eslint-plugin-jsdoc": "^54.0.0", + "eslint-plugin-jsdoc": "^61.1.11", "eslint-plugin-jsx-a11y": "~6.10.2", "eslint-plugin-promise": "~7.2.1", "eslint-plugin-react": "^7.37.4", - "eslint-plugin-react-hooks": "^5.2.0", - "eslint-plugin-storybook": "^9.0.4", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-storybook": "^10.0.2", "fake-indexeddb": "^6.0.1", "globals": "^16.0.0", "husky": "^9.0.11", - "lint-staged": "^16.0.0", - "msw": "^2.10.2", - "msw-storybook-addon": "^2.0.5", - "playwright": "^1.54.1", + "lint-staged": "^16.2.6", + "msw": "^2.12.1", + "msw-storybook-addon": "^2.0.6", + "playwright": "^1.57.0", "prettier": "^3.3.3", "react-test-renderer": "^18.2.0", - "storybook": "^9.1.1", + "storybook": "^10.0.5", "stylelint": "^16.19.1", "stylelint-config-prettier-scss": "^1.0.0", - "stylelint-config-standard-scss": "^15.0.1", - "typescript": "~5.7.3", - "typescript-eslint": "^8.29.1", - "vitest": "^3.2.4" + "stylelint-config-standard-scss": "^16.0.0", + "typescript": "~5.9.0", + "typescript-eslint": "^8.45.0", + "typescript-plugin-css-modules": "^5.2.0", + "vitest": "^4.0.5" }, "resolutions": { "@types/react": "^18.2.7", diff --git a/public/emoji/1f1e8-1f1f6.svg b/public/emoji/1f1e8-1f1f6.svg new file mode 100644 index 00000000000000..06294f8625c4c6 --- /dev/null +++ b/public/emoji/1f1e8-1f1f6.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/emoji/1f426-200d-2b1b.svg b/public/emoji/1f426-200d-2b1b.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1f6dd.svg b/public/emoji/1f6dd.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1f6de.svg b/public/emoji/1f6de.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1f6df.svg b/public/emoji/1f6df.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1f7f0.svg b/public/emoji/1f7f0.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1f91d-1f3fb.svg b/public/emoji/1f91d-1f3fb.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1f91d-1f3fc.svg b/public/emoji/1f91d-1f3fc.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1f91d-1f3fd.svg b/public/emoji/1f91d-1f3fd.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1f91d-1f3fe.svg b/public/emoji/1f91d-1f3fe.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1f91d-1f3ff.svg b/public/emoji/1f91d-1f3ff.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1f9cc.svg b/public/emoji/1f9cc.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fa75.svg b/public/emoji/1fa75.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fa76.svg b/public/emoji/1fa76.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fa77.svg b/public/emoji/1fa77.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fa7b.svg b/public/emoji/1fa7b.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fa7c.svg b/public/emoji/1fa7c.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fa88.svg b/public/emoji/1fa88.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fa89.svg b/public/emoji/1fa89.svg new file mode 100644 index 00000000000000..aa360815080c31 --- /dev/null +++ b/public/emoji/1fa89.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/emoji/1fa8f.svg b/public/emoji/1fa8f.svg new file mode 100644 index 00000000000000..1a80ea2dc26eaa --- /dev/null +++ b/public/emoji/1fa8f.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/emoji/1faa9.svg b/public/emoji/1faa9.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faaa.svg b/public/emoji/1faaa.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faab.svg b/public/emoji/1faab.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faac.svg b/public/emoji/1faac.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fab7.svg b/public/emoji/1fab7.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fab8.svg b/public/emoji/1fab8.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fab9.svg b/public/emoji/1fab9.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faba.svg b/public/emoji/1faba.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fabe.svg b/public/emoji/1fabe.svg new file mode 100644 index 00000000000000..3b9b6bfa121947 --- /dev/null +++ b/public/emoji/1fabe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/emoji/1fac3-1f3fb.svg b/public/emoji/1fac3-1f3fb.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fac3-1f3fc.svg b/public/emoji/1fac3-1f3fc.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fac3-1f3fd.svg b/public/emoji/1fac3-1f3fd.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fac3-1f3fe.svg b/public/emoji/1fac3-1f3fe.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fac3-1f3ff.svg b/public/emoji/1fac3-1f3ff.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fac3.svg b/public/emoji/1fac3.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fac4-1f3fb.svg b/public/emoji/1fac4-1f3fb.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fac4-1f3fc.svg b/public/emoji/1fac4-1f3fc.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fac4-1f3fd.svg b/public/emoji/1fac4-1f3fd.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fac4-1f3fe.svg b/public/emoji/1fac4-1f3fe.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fac4-1f3ff.svg b/public/emoji/1fac4-1f3ff.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fac4.svg b/public/emoji/1fac4.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fac5-1f3fb.svg b/public/emoji/1fac5-1f3fb.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fac5-1f3fc.svg b/public/emoji/1fac5-1f3fc.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fac5-1f3fd.svg b/public/emoji/1fac5-1f3fd.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fac5-1f3fe.svg b/public/emoji/1fac5-1f3fe.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fac5-1f3ff.svg b/public/emoji/1fac5-1f3ff.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fac5.svg b/public/emoji/1fac5.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fac6.svg b/public/emoji/1fac6.svg new file mode 100644 index 00000000000000..be06881146cfb0 --- /dev/null +++ b/public/emoji/1fac6.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/emoji/1fad7.svg b/public/emoji/1fad7.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fad8.svg b/public/emoji/1fad8.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fad9.svg b/public/emoji/1fad9.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fadc.svg b/public/emoji/1fadc.svg new file mode 100644 index 00000000000000..9922df663e20f1 --- /dev/null +++ b/public/emoji/1fadc.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/emoji/1fadf.svg b/public/emoji/1fadf.svg new file mode 100644 index 00000000000000..1b9de852b53ae9 --- /dev/null +++ b/public/emoji/1fadf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/emoji/1fae0.svg b/public/emoji/1fae0.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fae1.svg b/public/emoji/1fae1.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fae2.svg b/public/emoji/1fae2.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fae3.svg b/public/emoji/1fae3.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fae4.svg b/public/emoji/1fae4.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fae5.svg b/public/emoji/1fae5.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fae6.svg b/public/emoji/1fae6.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fae7.svg b/public/emoji/1fae7.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1fae9.svg b/public/emoji/1fae9.svg new file mode 100644 index 00000000000000..7ec75458cca8fb --- /dev/null +++ b/public/emoji/1fae9.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/emoji/1faf0-1f3fb.svg b/public/emoji/1faf0-1f3fb.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf0-1f3fc.svg b/public/emoji/1faf0-1f3fc.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf0-1f3fd.svg b/public/emoji/1faf0-1f3fd.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf0-1f3fe.svg b/public/emoji/1faf0-1f3fe.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf0-1f3ff.svg b/public/emoji/1faf0-1f3ff.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf0.svg b/public/emoji/1faf0.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf1-1f3fb-200d-1faf2-1f3fc.svg b/public/emoji/1faf1-1f3fb-200d-1faf2-1f3fc.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf1-1f3fb-200d-1faf2-1f3fd.svg b/public/emoji/1faf1-1f3fb-200d-1faf2-1f3fd.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf1-1f3fb-200d-1faf2-1f3fe.svg b/public/emoji/1faf1-1f3fb-200d-1faf2-1f3fe.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf1-1f3fb-200d-1faf2-1f3ff.svg b/public/emoji/1faf1-1f3fb-200d-1faf2-1f3ff.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf1-1f3fb.svg b/public/emoji/1faf1-1f3fb.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf1-1f3fc-200d-1faf2-1f3fb.svg b/public/emoji/1faf1-1f3fc-200d-1faf2-1f3fb.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf1-1f3fc-200d-1faf2-1f3fd.svg b/public/emoji/1faf1-1f3fc-200d-1faf2-1f3fd.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf1-1f3fc-200d-1faf2-1f3fe.svg b/public/emoji/1faf1-1f3fc-200d-1faf2-1f3fe.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf1-1f3fc-200d-1faf2-1f3ff.svg b/public/emoji/1faf1-1f3fc-200d-1faf2-1f3ff.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf1-1f3fc.svg b/public/emoji/1faf1-1f3fc.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf1-1f3fd-200d-1faf2-1f3fb.svg b/public/emoji/1faf1-1f3fd-200d-1faf2-1f3fb.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf1-1f3fd-200d-1faf2-1f3fc.svg b/public/emoji/1faf1-1f3fd-200d-1faf2-1f3fc.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf1-1f3fd-200d-1faf2-1f3fe.svg b/public/emoji/1faf1-1f3fd-200d-1faf2-1f3fe.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf1-1f3fd-200d-1faf2-1f3ff.svg b/public/emoji/1faf1-1f3fd-200d-1faf2-1f3ff.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf1-1f3fd.svg b/public/emoji/1faf1-1f3fd.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf1-1f3fe-200d-1faf2-1f3fb.svg b/public/emoji/1faf1-1f3fe-200d-1faf2-1f3fb.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf1-1f3fe-200d-1faf2-1f3fc.svg b/public/emoji/1faf1-1f3fe-200d-1faf2-1f3fc.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf1-1f3fe-200d-1faf2-1f3fd.svg b/public/emoji/1faf1-1f3fe-200d-1faf2-1f3fd.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf1-1f3fe-200d-1faf2-1f3ff.svg b/public/emoji/1faf1-1f3fe-200d-1faf2-1f3ff.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf1-1f3fe.svg b/public/emoji/1faf1-1f3fe.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf1-1f3ff-200d-1faf2-1f3fb.svg b/public/emoji/1faf1-1f3ff-200d-1faf2-1f3fb.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf1-1f3ff-200d-1faf2-1f3fc.svg b/public/emoji/1faf1-1f3ff-200d-1faf2-1f3fc.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf1-1f3ff-200d-1faf2-1f3fd.svg b/public/emoji/1faf1-1f3ff-200d-1faf2-1f3fd.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf1-1f3ff-200d-1faf2-1f3fe.svg b/public/emoji/1faf1-1f3ff-200d-1faf2-1f3fe.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf1-1f3ff.svg b/public/emoji/1faf1-1f3ff.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf1.svg b/public/emoji/1faf1.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf2-1f3fb.svg b/public/emoji/1faf2-1f3fb.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf2-1f3fc.svg b/public/emoji/1faf2-1f3fc.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf2-1f3fd.svg b/public/emoji/1faf2-1f3fd.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf2-1f3fe.svg b/public/emoji/1faf2-1f3fe.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf2-1f3ff.svg b/public/emoji/1faf2-1f3ff.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf2.svg b/public/emoji/1faf2.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf3-1f3fb.svg b/public/emoji/1faf3-1f3fb.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf3-1f3fc.svg b/public/emoji/1faf3-1f3fc.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf3-1f3fd.svg b/public/emoji/1faf3-1f3fd.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf3-1f3fe.svg b/public/emoji/1faf3-1f3fe.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf3-1f3ff.svg b/public/emoji/1faf3-1f3ff.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf3.svg b/public/emoji/1faf3.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf4-1f3fb.svg b/public/emoji/1faf4-1f3fb.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf4-1f3fc.svg b/public/emoji/1faf4-1f3fc.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf4-1f3fd.svg b/public/emoji/1faf4-1f3fd.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf4-1f3fe.svg b/public/emoji/1faf4-1f3fe.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf4-1f3ff.svg b/public/emoji/1faf4-1f3ff.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf4.svg b/public/emoji/1faf4.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf5-1f3fb.svg b/public/emoji/1faf5-1f3fb.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf5-1f3fc.svg b/public/emoji/1faf5-1f3fc.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf5-1f3fd.svg b/public/emoji/1faf5-1f3fd.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf5-1f3fe.svg b/public/emoji/1faf5-1f3fe.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf5-1f3ff.svg b/public/emoji/1faf5-1f3ff.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf5.svg b/public/emoji/1faf5.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf6-1f3fb.svg b/public/emoji/1faf6-1f3fb.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf6-1f3fc.svg b/public/emoji/1faf6-1f3fc.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf6-1f3fd.svg b/public/emoji/1faf6-1f3fd.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf6-1f3fe.svg b/public/emoji/1faf6-1f3fe.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf6-1f3ff.svg b/public/emoji/1faf6-1f3ff.svg old mode 100755 new mode 100644 diff --git a/public/emoji/1faf6.svg b/public/emoji/1faf6.svg old mode 100755 new mode 100644 diff --git a/public/emoji/sheet_16_0.png b/public/emoji/sheet_16_0.png new file mode 100644 index 00000000000000..f8945ba5e23556 Binary files /dev/null and b/public/emoji/sheet_16_0.png differ diff --git a/spec/controllers/auth/registrations_controller_spec.rb b/spec/controllers/auth/registrations_controller_spec.rb index 04c2d5dbbbbc6d..c7bbe130920fbb 100644 --- a/spec/controllers/auth/registrations_controller_spec.rb +++ b/spec/controllers/auth/registrations_controller_spec.rb @@ -12,11 +12,11 @@ allow(Rails.configuration.x).to receive(:single_user_mode).and_return(true) end - it 'redirects to root' do + it 'redirects to sign-in' do Fabricate(:account) get path - expect(response).to redirect_to '/' + expect(response).to redirect_to '/auth/sign_in' expect(Rails.configuration.x).to have_received(:single_user_mode) end end @@ -27,10 +27,10 @@ allow(Rails.configuration.x).to receive(:single_user_mode).and_return(false) end - it 'redirects to root' do + it 'redirects to sign-in' do get path - expect(response).to redirect_to '/' + expect(response).to redirect_to '/auth/sign_in' expect(Rails.configuration.x).to have_received(:single_user_mode) end end diff --git a/spec/controllers/concerns/account_controller_concern_spec.rb b/spec/controllers/concerns/account_controller_concern_spec.rb index df6278a6e98e66..b5c8c166433f5c 100644 --- a/spec/controllers/concerns/account_controller_concern_spec.rb +++ b/spec/controllers/concerns/account_controller_concern_spec.rb @@ -58,7 +58,7 @@ def success expect(response) .to have_http_status(200) .and have_http_link_header(webfinger_url(resource: account.to_webfinger_s)).for(rel: 'lrdd', type: 'application/jrd+json') - .and have_http_link_header(account_url(account, protocol: :https)).for(rel: 'alternate', type: 'application/activity+json') + .and have_http_link_header(ActivityPub::TagManager.instance.uri_for(account)).for(rel: 'alternate', type: 'application/activity+json') expect(response.body) .to include(account.username) end diff --git a/spec/controllers/concerns/api/error_handling_spec.rb b/spec/controllers/concerns/api/error_handling_spec.rb index eff01605d2a89a..496f38648729e8 100644 --- a/spec/controllers/concerns/api/error_handling_spec.rb +++ b/spec/controllers/concerns/api/error_handling_spec.rb @@ -32,7 +32,7 @@ def failure Mastodon::ValidationError => 422, OpenSSL::SSL::SSLError => 503, Seahorse::Client::NetworkingError => 503, - Stoplight::Error::RedLight => 503, + Stoplight::Error::RedLight.new(:name, cool_off_time: 1, retry_after: 1) => 503, }.each do |error, code| it "Handles error class of #{error}" do allow(FakeService) diff --git a/spec/controllers/follower_accounts_controller_spec.rb b/spec/controllers/follower_accounts_controller_spec.rb index e14ed00e609a24..358341cb7299a3 100644 --- a/spec/controllers/follower_accounts_controller_spec.rb +++ b/spec/controllers/follower_accounts_controller_spec.rb @@ -49,14 +49,45 @@ expect(response.parsed_body) .to include( orderedItems: contain_exactly( - include(follow_from_bob.account.username), - include(follow_from_chris.account.username) + ActivityPub::TagManager.instance.uri_for(follow_from_bob.account), + ActivityPub::TagManager.instance.uri_for(follow_from_chris.account) ), totalItems: eq(2), partOf: be_present ) end + context 'when account hides their network' do + before { alice.update(hide_collections: true) } + + it 'returns forbidden response' do + expect(response) + .to have_http_status(403) + expect(response.parsed_body) + .to include(error: /forbidden/i) + end + end + + context 'when request is signed in and user blocks an account' do + let(:account) { Fabricate :account } + + before do + Fabricate :block, account:, target_account: follower_bob + sign_in(account.user) + end + + it 'returns followers without blocked' do + expect(response) + .to have_http_status(200) + expect(response.parsed_body) + .to include( + orderedItems: contain_exactly( + include(follow_from_chris.account.id.to_s) + ) + ) + end + end + context 'when account is permanently suspended' do before do alice.suspend! diff --git a/spec/controllers/following_accounts_controller_spec.rb b/spec/controllers/following_accounts_controller_spec.rb index fea4d4845c8497..7f11a50395adb3 100644 --- a/spec/controllers/following_accounts_controller_spec.rb +++ b/spec/controllers/following_accounts_controller_spec.rb @@ -49,14 +49,45 @@ expect(response.parsed_body) .to include( orderedItems: contain_exactly( - include(follow_of_bob.target_account.username), - include(follow_of_chris.target_account.username) + ActivityPub::TagManager.instance.uri_for(follow_of_bob.target_account), + ActivityPub::TagManager.instance.uri_for(follow_of_chris.target_account) ), totalItems: eq(2), partOf: be_present ) end + context 'when account hides their network' do + before { alice.update(hide_collections: true) } + + it 'returns forbidden response' do + expect(response) + .to have_http_status(403) + expect(response.parsed_body) + .to include(error: /forbidden/i) + end + end + + context 'when request is signed in and user blocks an account' do + let(:account) { Fabricate :account } + + before do + Fabricate :block, account:, target_account: followee_bob + sign_in(account.user) + end + + it 'returns followers without blocked' do + expect(response) + .to have_http_status(200) + expect(response.parsed_body) + .to include( + orderedItems: contain_exactly( + include(follow_of_chris.target_account.id.to_s) + ) + ) + end + end + context 'when account is permanently suspended' do before do alice.suspend! diff --git a/spec/fabricators/account_fabricator.rb b/spec/fabricators/account_fabricator.rb index 6ec89a1cb651b9..bce8803be75094 100644 --- a/spec/fabricators/account_fabricator.rb +++ b/spec/fabricators/account_fabricator.rb @@ -17,3 +17,7 @@ discoverable true indexable true end + +Fabricator(:remote_account, from: :account) do + domain 'example.com' +end diff --git a/spec/fabricators/collection_fabricator.rb b/spec/fabricators/collection_fabricator.rb new file mode 100644 index 00000000000000..a6a8411ba00b67 --- /dev/null +++ b/spec/fabricators/collection_fabricator.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +Fabricator(:collection) do + account { Fabricate.build(:account) } + name { sequence(:name) { |i| "Collection ##{i}" } } + description 'People to follow' + local true + sensitive false + discoverable true +end diff --git a/spec/fabricators/collection_item_fabricator.rb b/spec/fabricators/collection_item_fabricator.rb new file mode 100644 index 00000000000000..db55f7d23737a5 --- /dev/null +++ b/spec/fabricators/collection_item_fabricator.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +Fabricator(:collection_item) do + collection { Fabricate.build(:collection) } + account { Fabricate.build(:account) } + position { sequence(:position, 1) } + state :accepted +end + +Fabricator(:unverified_remote_collection_item, from: :collection_item) do + account nil + state :pending + object_uri { Fabricate.build(:remote_account).uri } + approval_uri { sequence(:uri) { |i| "https://example.com/authorizations/#{i}" } } +end diff --git a/spec/fabricators/user_fabricator.rb b/spec/fabricators/user_fabricator.rb index 104d7f99314d2e..300a6d9ba6743b 100644 --- a/spec/fabricators/user_fabricator.rb +++ b/spec/fabricators/user_fabricator.rb @@ -25,3 +25,9 @@ Fabricator(:owner_user, from: :user) do role UserRole.find_by(name: 'Owner') end + +Fabricator(:private_user, from: :user) do + account_attributes do + { discoverable: false, locked: true, indexable: false } + end +end diff --git a/spec/helpers/admin/account_moderation_notes_helper_spec.rb b/spec/helpers/admin/account_moderation_notes_helper_spec.rb index d8fc0ee233554e..a68c8abba94a50 100644 --- a/spec/helpers/admin/account_moderation_notes_helper_spec.rb +++ b/spec/helpers/admin/account_moderation_notes_helper_spec.rb @@ -17,7 +17,7 @@ end context 'with account' do - let(:account) { Fabricate(:account) } + let(:account) { Fabricate.build(:account, id: 123) } it 'returns a labeled avatar link to the account' do expect(parsed_html.a[:href]).to eq admin_account_path(account.id) @@ -39,7 +39,7 @@ end context 'with account' do - let(:account) { Fabricate(:account) } + let(:account) { Fabricate.build(:account, id: 123) } it 'returns an inline link to the account' do expect(parsed_html.a[:href]).to eq admin_account_path(account.id) diff --git a/spec/helpers/admin/dashboard_helper_spec.rb b/spec/helpers/admin/dashboard_helper_spec.rb index 9c674fb4b9664d..db95eb9f2ce644 100644 --- a/spec/helpers/admin/dashboard_helper_spec.rb +++ b/spec/helpers/admin/dashboard_helper_spec.rb @@ -4,8 +4,9 @@ RSpec.describe Admin::DashboardHelper do describe 'relevant_account_timestamp' do + let(:account) { Fabricate(:account) } + context 'with an account with older sign in' do - let(:account) { Fabricate(:account) } let(:stamp) { 10.days.ago } it 'returns a time element' do @@ -18,8 +19,6 @@ end context 'with an account with newer sign in' do - let(:account) { Fabricate(:account) } - it 'returns a time element' do account.user.update(current_sign_in_at: 10.hours.ago) result = helper.relevant_account_timestamp(account) @@ -29,8 +28,6 @@ end context 'with an account where the user is pending' do - let(:account) { Fabricate(:account) } - it 'returns a time element' do account.user.update(current_sign_in_at: nil) account.user.update(approved: false) @@ -42,7 +39,6 @@ end context 'with an account with a last status value' do - let(:account) { Fabricate(:account) } let(:stamp) { 5.minutes.ago } it 'returns a time element' do @@ -56,8 +52,6 @@ end context 'with an account without sign in or last status or pending' do - let(:account) { Fabricate(:account) } - it 'returns a time element' do account.user.update(current_sign_in_at: nil) result = helper.relevant_account_timestamp(account) diff --git a/spec/helpers/admin/trends/statuses_helper_spec.rb b/spec/helpers/admin/trends/statuses_helper_spec.rb index 6abc4569b41bd6..634b4c94c3904c 100644 --- a/spec/helpers/admin/trends/statuses_helper_spec.rb +++ b/spec/helpers/admin/trends/statuses_helper_spec.rb @@ -54,7 +54,7 @@ context 'with a status that has emoji' do before { Fabricate(:custom_emoji, shortcode: 'florpy') } - let(:status) { Fabricate(:status, text: 'hello there :florpy:') } + let(:status) { Fabricate.build(:status, text: 'hello there :florpy:') } it 'renders a correct preview text' do result = helper.one_line_preview(status) diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 82f22d4886a710..46dbd80de08d9f 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -3,8 +3,8 @@ require 'rails_helper' RSpec.describe ApplicationHelper do - describe 'body_classes' do - context 'with a body class string from a controller' do + describe 'html_classes' do + context 'with non-default user settings' do before do user = Fabricate :user user.settings['web.use_system_font'] = true @@ -15,20 +15,11 @@ end it 'uses the current theme and user settings classes in the result' do - expect(helper.body_classes) - .to match(/flavour-glitch/) - .and match(/skin-default/) - .and match(/system-font/) + expect(helper.html_classes) + .to match(/system-font/) .and match(/reduce-motion/) end - it 'includes values set via content_for' do - helper.content_for(:body_classes) { 'admin' } - - expect(helper.body_classes) - .to match(/admin/) - end - private def controller_helpers @@ -36,14 +27,22 @@ def controller_helpers def current_account @current_account ||= Fabricate(:account, user: User.last) end - - def current_flavour = 'glitch' - def current_skin = 'default' end end end end + describe 'body_classes' do + context 'with a body class string from a controller' do + it 'includes values set via content_for' do + helper.content_for(:body_classes) { 'admin' } + + expect(helper.body_classes) + .to match(/admin/) + end + end + end + describe 'locale_direction' do it 'adds rtl body class if locale is Arabic' do I18n.with_locale(:ar) do diff --git a/spec/helpers/filters_helper_spec.rb b/spec/helpers/filters_helper_spec.rb new file mode 100644 index 00000000000000..7cbacdfa34b319 --- /dev/null +++ b/spec/helpers/filters_helper_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe FiltersHelper do + describe '#filter_keywords' do + subject { helper.filter_keywords(filter) } + + let(:filter) { Fabricate.build :custom_filter, keywords: } + let(:keywords) { words.map { |keyword| Fabricate.build(:custom_filter_keyword, keyword:) } } + + context 'with few keywords' do + let(:words) { %w(One) } + + it { is_expected.to eq('One') } + end + + context 'with many keywords' do + let(:words) { %w(One Two Three Four Five Six Seven Eight Nine Ten) } + + it { is_expected.to eq('One, Two, Three, Four, Five, …') } + end + end +end diff --git a/spec/helpers/formatting_helper_spec.rb b/spec/helpers/formatting_helper_spec.rb index 5ff534e4eb3873..4e605850c1a176 100644 --- a/spec/helpers/formatting_helper_spec.rb +++ b/spec/helpers/formatting_helper_spec.rb @@ -18,7 +18,7 @@ end context 'with a spoiler and an emoji and a poll' do - let(:status) { Fabricate(:status, text: 'Hello :world: <>', spoiler_text: 'This is a spoiler<>', poll: Fabricate(:poll, options: %w(Yes<> No))) } + let(:status) { Fabricate(:status, text: 'Hello :world: <>', spoiler_text: 'This is a spoiler<>', poll: Fabricate.build(:poll, options: %w(Yes<> No))) } before { Fabricate :custom_emoji, shortcode: 'world' } diff --git a/spec/helpers/home_helper_spec.rb b/spec/helpers/home_helper_spec.rb index a056eae364dfb2..e63c03528c05c1 100644 --- a/spec/helpers/home_helper_spec.rb +++ b/spec/helpers/home_helper_spec.rb @@ -21,7 +21,7 @@ end context 'with a valid account' do - let(:account) { Fabricate(:account) } + let(:account) { Fabricate.build(:account) } before { helper.extend controller_helpers } diff --git a/spec/helpers/media_component_helper_spec.rb b/spec/helpers/media_component_helper_spec.rb index a44b9b841509b9..60c9f84da219a8 100644 --- a/spec/helpers/media_component_helper_spec.rb +++ b/spec/helpers/media_component_helper_spec.rb @@ -5,8 +5,10 @@ RSpec.describe MediaComponentHelper do before { helper.extend controller_helpers } + let(:media) { Fabricate.build(:media_attachment, type:, status: Fabricate.build(:status)) } + describe 'render_video_component' do - let(:media) { Fabricate(:media_attachment, type: :video, status: Fabricate(:status)) } + let(:type) { :video } let(:result) { helper.render_video_component(media.status) } it 'renders a react component for the video' do @@ -15,7 +17,7 @@ end describe 'render_audio_component' do - let(:media) { Fabricate(:media_attachment, type: :audio, status: Fabricate(:status)) } + let(:type) { :audio } let(:result) { helper.render_audio_component(media.status) } it 'renders a react component for the audio' do @@ -24,7 +26,7 @@ end describe 'render_media_gallery_component' do - let(:media) { Fabricate(:media_attachment, type: :audio, status: Fabricate(:status)) } + let(:type) { :audio } let(:result) { helper.render_media_gallery_component(media.status) } it 'renders a react component for the media gallery' do diff --git a/spec/helpers/statuses_helper_spec.rb b/spec/helpers/statuses_helper_spec.rb index 07ad72eda956f9..dbfc216605a64e 100644 --- a/spec/helpers/statuses_helper_spec.rb +++ b/spec/helpers/statuses_helper_spec.rb @@ -24,16 +24,13 @@ end describe '#media_summary' do - it 'describes the media on a status' do - status = Fabricate :status - Fabricate :media_attachment, status: status, type: :video - Fabricate :media_attachment, status: status, type: :audio - Fabricate :media_attachment, status: status, type: :image + subject { helper.media_summary(status) } - result = helper.media_summary(status) + let(:status) { Fabricate.build :status } - expect(result).to eq('Attached: 1 image · 1 video · 1 audio') - end + before { %i(video audio image).each { |type| Fabricate.build :media_attachment, status:, type: } } + + it { is_expected.to eq('Attached: 1 image · 1 video · 1 audio') } end describe 'visibility_icon' do diff --git a/spec/helpers/theme_helper_spec.rb b/spec/helpers/theme_helper_spec.rb index 875a15321f08c8..c94bf303d83ef3 100644 --- a/spec/helpers/theme_helper_spec.rb +++ b/spec/helpers/theme_helper_spec.rb @@ -12,7 +12,8 @@ it 'returns the mastodon-light and application stylesheets with correct color schemes' do expect(html_links.first.attributes.symbolize_keys) .to include( - href: have_attributes(value: match(/mastodon-light/)), + # This is now identical to the default theme & will be unified very soon + href: have_attributes(value: match(/default/)), media: have_attributes(value: 'not all and (prefers-color-scheme: dark)') ) expect(html_links.last.attributes.symbolize_keys) diff --git a/spec/lib/activitypub/activity/create_spec.rb b/spec/lib/activitypub/activity/create_spec.rb index 72579dc51b27f4..8639dadaf51603 100644 --- a/spec/lib/activitypub/activity/create_spec.rb +++ b/spec/lib/activitypub/activity/create_spec.rb @@ -988,6 +988,30 @@ def activity_for_object(json) end end + context 'with an unverifiable quote of a dead post' do + let(:quoted_status) { Fabricate(:status) } + + let(:object_json) do + build_object( + type: 'Note', + content: 'woah what she said is amazing', + quote: { type: 'Tombstone' } + ) + end + + it 'creates a status with an unverified quote' do + expect { subject.perform }.to change(sender.statuses, :count).by(1) + + status = sender.statuses.first + expect(status).to_not be_nil + expect(status.quote).to_not be_nil + expect(status.quote).to have_attributes( + state: 'deleted', + approval_uri: nil + ) + end + end + context 'with an unverifiable unknown post' do let(:unknown_post_uri) { 'https://unavailable.example.com/unavailable-post' } @@ -1072,6 +1096,60 @@ def activity_for_object(json) end end + context 'with a quote of a known reblog that is otherwise valid' do + let(:quoted_account) { Fabricate(:account, domain: 'quoted.example.com') } + let(:quoted_status) { Fabricate(:status, account: quoted_account, reblog: Fabricate(:status)) } + let(:approval_uri) { 'https://quoted.example.com/quote-approval' } + + let(:object_json) do + build_object( + type: 'Note', + content: 'woah what she said is amazing', + quote: ActivityPub::TagManager.instance.uri_for(quoted_status), + quoteAuthorization: approval_uri + ) + end + + before do + stub_request(:get, approval_uri).to_return(headers: { 'Content-Type': 'application/activity+json' }, body: Oj.dump({ + '@context': [ + 'https://www.w3.org/ns/activitystreams', + { + QuoteAuthorization: 'https://w3id.org/fep/044f#QuoteAuthorization', + gts: 'https://gotosocial.org/ns#', + interactionPolicy: { + '@id': 'gts:interactionPolicy', + '@type': '@id', + }, + interactingObject: { + '@id': 'gts:interactingObject', + '@type': '@id', + }, + interactionTarget: { + '@id': 'gts:interactionTarget', + '@type': '@id', + }, + }, + ], + type: 'QuoteAuthorization', + id: approval_uri, + attributedTo: ActivityPub::TagManager.instance.uri_for(quoted_status.account), + interactingObject: object_json[:id], + interactionTarget: ActivityPub::TagManager.instance.uri_for(quoted_status), + })) + end + + it 'creates a status without the verified quote' do + expect { subject.perform }.to change(sender.statuses, :count).by(1) + + status = sender.statuses.first + expect(status).to_not be_nil + expect(status.quote).to_not be_nil + expect(status.quote.state).to_not eq 'accepted' + expect(status.quote.quoted_status).to be_nil + end + end + context 'when a vote to a local poll' do let(:poll) { Fabricate(:poll, options: %w(Yellow Blue)) } let!(:local_status) { Fabricate(:status, poll: poll) } diff --git a/spec/lib/activitypub/activity/quote_request_spec.rb b/spec/lib/activitypub/activity/quote_request_spec.rb index aae4ce0338329f..db80448a80b6a4 100644 --- a/spec/lib/activitypub/activity/quote_request_spec.rb +++ b/spec/lib/activitypub/activity/quote_request_spec.rb @@ -87,7 +87,7 @@ context 'when trying to quote a quotable local status' do before do stub_request(:get, 'https://example.com/unknown-status').to_return(status: 200, body: Oj.dump(status_json), headers: { 'Content-Type': 'application/activity+json' }) - quoted_post.update(quote_approval_policy: Status::QUOTE_APPROVAL_POLICY_FLAGS[:public] << 16) + quoted_post.update(quote_approval_policy: InteractionPolicy::POLICY_FLAGS[:public] << 16) end it 'accepts the quote and sends an Accept activity' do @@ -105,7 +105,7 @@ let(:instrument) { status_json.without('@context') } before do - quoted_post.update(quote_approval_policy: Status::QUOTE_APPROVAL_POLICY_FLAGS[:public] << 16) + quoted_post.update(quote_approval_policy: InteractionPolicy::POLICY_FLAGS[:public] << 16) end it 'accepts the quote and sends an Accept activity' do diff --git a/spec/lib/activitypub/activity/update_spec.rb b/spec/lib/activitypub/activity/update_spec.rb index b829f3a5ad6d06..d905f68d8f13dd 100644 --- a/spec/lib/activitypub/activity/update_spec.rb +++ b/spec/lib/activitypub/activity/update_spec.rb @@ -149,18 +149,17 @@ shared_examples 'updates counts' do it 'updates the reblog count' do - expect(status.untrusted_reblogs_count).to eq reblogs + expect { subject.perform }.to change { status.reload.untrusted_reblogs_count }.to(reblogs) end it 'updates the favourites count' do - expect(status.untrusted_favourites_count).to eq favourites + expect { subject.perform }.to change { status.reload.untrusted_favourites_count }.to(favourites) end end context 'with an implicit update' do before do status.update!(uri: ActivityPub::TagManager.instance.uri_for(status)) - subject.perform end it_behaves_like 'updates counts' @@ -173,11 +172,89 @@ before do status.update!(uri: ActivityPub::TagManager.instance.uri_for(status)) - subject.perform end it_behaves_like 'updates counts' end end + + context 'with an Article object' do + let(:updated) { nil } + let(:favourites) { 50 } + let(:reblogs) { 100 } + + let!(:status) do + Fabricate( + :status, + uri: 'https://example.com/statuses/article', + account: sender, + text: "

Future of the Fediverse

\n\n

Guest article by John Mastodon

The fediverse is great reading this you will find out why!

" + ) + end + + let(:json) do + { + '@context': 'https://www.w3.org/ns/activitystreams', + id: 'foo', + type: 'Update', + actor: sender.uri, + object: { + type: 'Article', + id: status.uri, + name: 'Future of the Fediverse', + summary: '

Guest article by Jane Mastodon

The fediverse is great reading this you will find out why!

', + content: 'Foo', + updated: updated, + likes: { + id: "#{status.uri}/likes", + type: 'Collection', + totalItems: favourites, + }, + shares: { + id: "#{status.uri}/shares", + type: 'Collection', + totalItems: reblogs, + }, + }, + }.with_indifferent_access + end + + shared_examples 'updates counts' do + it 'updates the reblog count' do + expect { subject.perform }.to change { status.reload.untrusted_reblogs_count }.to(reblogs) + end + + it 'updates the favourites count' do + expect { subject.perform }.to change { status.reload.untrusted_favourites_count }.to(favourites) + end + end + + context 'with an implicit update' do + before do + status.update!(uri: ActivityPub::TagManager.instance.uri_for(status)) + end + + it_behaves_like 'updates counts' + end + + context 'with an explicit update' do + let(:favourites) { 150 } + let(:reblogs) { 200 } + let(:updated) { Time.now.utc.iso8601 } + + before do + status.update!(uri: ActivityPub::TagManager.instance.uri_for(status)) + end + + it_behaves_like 'updates counts' + + it 'changes the contents as expected' do + expect { subject.perform } + .to(change { status.reload.text }) + + expect(status.text).to start_with("

Future of the Fediverse

\n\n

Guest article by Jane Mastodon

The fediverse is great reading this you will find out why!

") + end + end + end end end diff --git a/spec/lib/activitypub/activity_spec.rb b/spec/lib/activitypub/activity_spec.rb index 218da04d9b5e7a..d7d0700dc65c0b 100644 --- a/spec/lib/activitypub/activity_spec.rb +++ b/spec/lib/activitypub/activity_spec.rb @@ -34,6 +34,8 @@ } end + let(:publication_date) { 1.hour.ago.utc } + let(:create_json) do { '@context': [ @@ -52,7 +54,7 @@ 'https://www.w3.org/ns/activitystreams#Public', ], content: 'foo', - published: '2025-05-24T11:03:10Z', + published: publication_date.iso8601, quote: ActivityPub::TagManager.instance.uri_for(quoted_status), }, }.deep_stringify_keys @@ -77,7 +79,7 @@ 'https://www.w3.org/ns/activitystreams#Public', ], content: 'foo', - published: '2025-05-24T11:03:10Z', + published: publication_date.iso8601, quote: ActivityPub::TagManager.instance.uri_for(quoted_status), quoteAuthorization: approval_uri, }, diff --git a/spec/lib/activitypub/forwarder_spec.rb b/spec/lib/activitypub/forwarder_spec.rb new file mode 100644 index 00000000000000..f72e3342183e83 --- /dev/null +++ b/spec/lib/activitypub/forwarder_spec.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe ActivityPub::Forwarder do + subject { described_class.new(account, payload, status) } + + let(:account) { Fabricate(:account) } + let(:remote_account) { Fabricate(:account, domain: 'example.com') } + let(:status) { Fabricate(:status, account: remote_account) } + + let(:signature) { { type: 'RsaSignature2017', signatureValue: 'foo' } } + let(:payload) do + { + '@context': [ + 'https://www.w3.org/ns/activitystreams', + 'https://w3id.org/security/v1', + ], + signature: signature, + type: 'Delete', + object: ActivityPub::TagManager.instance.uri_for(status), + }.deep_stringify_keys + end + + describe '#forwardable?' do + context 'when payload has an inlined signature' do + it 'returns true' do + expect(subject.forwardable?).to be true + end + end + + context 'when payload has an no inlined signature' do + let(:signature) { nil } + + it 'returns true' do + expect(subject.forwardable?).to be false + end + end + end + + describe '#forward!' do + let(:alice) { Fabricate(:account) } + let(:bob) { Fabricate(:account) } + let(:eve) { Fabricate(:account, domain: 'remote1.example.com', inbox_url: 'https://remote1.example.com/users/eve/inbox', protocol: :activitypub) } + let(:mallory) { Fabricate(:account, domain: 'remote2.example.com', inbox_url: 'https://remote2.example.com/users/mallory/inbox', protocol: :activitypub) } + + before do + alice.statuses.create!(reblog: status) + Fabricate(:quote, status: Fabricate(:status, account: bob), quoted_status: status, state: :accepted) + + eve.follow!(alice) + mallory.follow!(bob) + end + + it 'correctly forwards to expected remote followers' do + expect { subject.forward! } + .to enqueue_sidekiq_job(ActivityPub::LowPriorityDeliveryWorker).with(Oj.dump(payload), anything, eve.preferred_inbox_url) + .and enqueue_sidekiq_job(ActivityPub::LowPriorityDeliveryWorker).with(Oj.dump(payload), anything, mallory.preferred_inbox_url) + end + end +end diff --git a/spec/lib/activitypub/parser/interaction_policy_parser_spec.rb b/spec/lib/activitypub/parser/interaction_policy_parser_spec.rb new file mode 100644 index 00000000000000..0af445eab20522 --- /dev/null +++ b/spec/lib/activitypub/parser/interaction_policy_parser_spec.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe ActivityPub::Parser::InteractionPolicyParser do + subject { described_class.new(json_policy, account) } + + let(:account) do + Fabricate(:account, + uri: 'https://foo.test', + domain: 'foo.test', + followers_url: 'https://foo.test/followers', + following_url: 'https://foo.test/following') + end + + describe '#bitmap' do + context 'when no policy is given' do + let(:json_policy) { nil } + + it 'returns zero' do + expect(subject.bitmap).to be_zero + end + end + + context 'with special public URI' do + let(:json_policy) do + { + 'manualApproval' => [public_uri], + } + end + + shared_examples 'setting the public bit' do + it 'sets the public bit' do + expect(subject.bitmap).to eq 0b10 + end + end + + context 'when public URI is given in full' do + let(:public_uri) { 'https://www.w3.org/ns/activitystreams#Public' } + + it_behaves_like 'setting the public bit' + end + + context 'when public URI is abbreviated using namespace' do + let(:public_uri) { 'as:Public' } + + it_behaves_like 'setting the public bit' + end + + context 'when public URI is abbreviated without namespace' do + let(:public_uri) { 'Public' } + + it_behaves_like 'setting the public bit' + end + end + + context 'when mixing array and scalar values' do + let(:json_policy) do + { + 'automaticApproval' => 'https://foo.test', + 'manualApproval' => [ + 'https://foo.test/followers', + 'https://foo.test/following', + ], + } + end + + it 'sets the correct flags' do + expect(subject.bitmap).to eq 0b100000000000000001100 + end + end + + context 'when including individual actor URIs' do + let(:json_policy) do + { + 'automaticApproval' => ['https://example.com/actor', 'https://masto.example.com/@user'], + 'manualApproval' => ['https://masto.example.com/@other'], + } + end + + it 'sets the unsupported bit' do + expect(subject.bitmap).to eq 0b10000000000000001 + end + end + + context "when giving the affected actor's URI in addition to other supported URIs" do + let(:json_policy) do + { + 'manualApproval' => [ + 'https://foo.test/followers', + 'https://foo.test/following', + 'https://foo.test', + ], + } + end + + it 'is being ignored' do + expect(subject.bitmap).to eq 0b1100 + end + end + end +end diff --git a/spec/lib/activitypub/parser/status_parser_spec.rb b/spec/lib/activitypub/parser/status_parser_spec.rb index b251b63f43cfb5..3084f3ffd611e9 100644 --- a/spec/lib/activitypub/parser/status_parser_spec.rb +++ b/spec/lib/activitypub/parser/status_parser_spec.rb @@ -148,7 +148,7 @@ end it 'returns a policy not allowing anyone to quote' do - expect(subject).to eq(Status::QUOTE_APPROVAL_POLICY_FLAGS[:public] << 16) + expect(subject).to eq(InteractionPolicy::POLICY_FLAGS[:public] << 16) end end @@ -174,7 +174,7 @@ end it 'returns a policy allowing everyone including followers' do - expect(subject).to eq Status::QUOTE_APPROVAL_POLICY_FLAGS[:public] | (Status::QUOTE_APPROVAL_POLICY_FLAGS[:followers] << 16) + expect(subject).to eq InteractionPolicy::POLICY_FLAGS[:public] | (InteractionPolicy::POLICY_FLAGS[:followers] << 16) end end end diff --git a/spec/lib/activitypub/tag_manager_spec.rb b/spec/lib/activitypub/tag_manager_spec.rb index 7a4cf3c1b86c3d..cad46ad90324b9 100644 --- a/spec/lib/activitypub/tag_manager_spec.rb +++ b/spec/lib/activitypub/tag_manager_spec.rb @@ -7,7 +7,7 @@ subject { described_class.instance } - let(:domain) { "#{Rails.configuration.x.use_https ? 'https' : 'http'}://#{Rails.configuration.x.web_domain}" } + let(:host_prefix) { "#{Rails.configuration.x.use_https ? 'https' : 'http'}://#{Rails.configuration.x.web_domain}" } describe '#public_collection?' do it 'returns true for the special public collection and common shorthands' do @@ -22,18 +22,180 @@ end describe '#url_for' do - it 'returns a string starting with web domain' do - account = Fabricate(:account) - expect(subject.url_for(account)).to be_a(String) - .and start_with(domain) + context 'with a local account' do + let(:account) { Fabricate(:account, id_scheme: :username_ap_id) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.url_for(account)) + .to eq("#{host_prefix}/@#{account.username}") + end + + context 'when using a numeric ID based scheme' do + let(:account) { Fabricate(:account, id_scheme: :numeric_ap_id) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.url_for(account)) + .to eq("#{host_prefix}/@#{account.username}") + end + end + end + + context 'with a remote account' do + let(:account) { Fabricate(:account, domain: 'example.com', url: 'https://example.com/profiles/dskjfsdf') } + + it 'returns the expected URL' do + expect(subject.url_for(account)).to eq account.url + end + end + + context 'with a local status' do + let(:account) { Fabricate(:account, id_scheme: :username_ap_id) } + let(:status) { Fabricate(:status, account: account) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.url_for(status)) + .to eq("#{host_prefix}/@#{status.account.username}/#{status.id}") + end + + context 'when using a numeric ID based scheme' do + let(:account) { Fabricate(:account, id_scheme: :numeric_ap_id) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.url_for(status)) + .to eq("#{host_prefix}/@#{status.account.username}/#{status.id}") + end + end + end + + context 'with a remote status' do + let(:account) { Fabricate(:account, domain: 'example.com', url: 'https://example.com/profiles/dskjfsdf') } + let(:status) { Fabricate(:status, account: account, url: 'https://example.com/posts/1234') } + + it 'returns the expected URL' do + expect(subject.url_for(status)).to eq status.url + end end end describe '#uri_for' do - it 'returns a string starting with web domain' do - account = Fabricate(:account) - expect(subject.uri_for(account)).to be_a(String) - .and start_with(domain) + context 'with the instance actor' do + it 'returns a string starting with web domain and with the expected path' do + expect(subject.uri_for(Account.representative)) + .to eq("#{host_prefix}/actor") + end + end + + context 'with a local account' do + let(:account) { Fabricate(:account, id_scheme: :username_ap_id) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.uri_for(account)) + .to eq("#{host_prefix}/users/#{account.username}") + end + + context 'when using a numeric ID based scheme' do + let(:account) { Fabricate(:account, id_scheme: :numeric_ap_id) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.uri_for(account)) + .to eq("#{host_prefix}/ap/users/#{account.id}") + end + end + end + + context 'with a remote account' do + let(:account) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/profiles/dskjfsdf') } + + it 'returns the expected URL' do + expect(subject.uri_for(account)).to eq account.uri + end + end + + context 'with a local status' do + let(:account) { Fabricate(:account, id_scheme: :username_ap_id) } + let(:status) { Fabricate(:status, account: account) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.uri_for(status)) + .to eq("#{host_prefix}/users/#{status.account.username}/statuses/#{status.id}") + end + + context 'when using a numeric ID based scheme' do + let(:account) { Fabricate(:account, id_scheme: :numeric_ap_id) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.uri_for(status)) + .to eq("#{host_prefix}/ap/users/#{status.account.id}/statuses/#{status.id}") + end + end + end + + context 'with a remote status' do + let(:account) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/profiles/dskjfsdf') } + let(:status) { Fabricate(:status, account: account, uri: 'https://example.com/posts/1234') } + + it 'returns the expected URL' do + expect(subject.uri_for(status)).to eq status.uri + end + end + + context 'with a local conversation' do + let(:account) { Fabricate(:account, id_scheme: :username_ap_id) } + let(:status) { Fabricate(:status, account: account) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.uri_for(status.conversation)) + .to eq("#{host_prefix}/contexts/#{status.account.id}-#{status.id}") + end + + context 'when using a numeric ID based scheme' do + let(:account) { Fabricate(:account, id_scheme: :numeric_ap_id) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.uri_for(status.conversation)) + .to eq("#{host_prefix}/contexts/#{status.account.id}-#{status.id}") + end + end + end + + context 'with a remote conversation' do + let(:account) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/profiles/dskjfsdf') } + let(:status) { Fabricate(:status, account: account, uri: 'https://example.com/posts/1234') } + + before do + status.conversation.update!(uri: 'https://example.com/conversations/1234') + end + + it 'returns the expected URL' do + expect(subject.uri_for(status.conversation)).to eq status.conversation.uri + end + end + end + + describe '#key_uri_for' do + context 'with the instance actor' do + it 'returns a string starting with web domain and with the expected path' do + expect(subject.key_uri_for(Account.representative)) + .to eq("#{host_prefix}/actor#main-key") + end + end + + context 'with a local account' do + let(:account) { Fabricate(:account, id_scheme: :username_ap_id) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.key_uri_for(account)) + .to eq("#{host_prefix}/users/#{account.username}#main-key") + end + + context 'when using a numeric ID based scheme' do + let(:account) { Fabricate(:account, id_scheme: :numeric_ap_id) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.key_uri_for(account)) + .to eq("#{host_prefix}/ap/users/#{account.id}#main-key") + end + end end end @@ -49,7 +211,236 @@ it 'returns a string starting with web domain' do status = Fabricate(:status) expect(subject.uri_for(status)).to be_a(String) - .and start_with(domain) + .and start_with(host_prefix) + end + end + end + + describe '#approval_uri_for' do + context 'with a valid local approval' do + let(:quoted_account) { Fabricate(:account, id_scheme: :username_ap_id) } + let(:quoted_status) { Fabricate(:status, account: quoted_account) } + let(:quote) { Fabricate(:quote, state: :accepted, quoted_status: quoted_status) } + + it 'returns a string with the web domain and expected path' do + expect(subject.approval_uri_for(quote)) + .to eq("#{host_prefix}/users/#{quote.quoted_account.username}/quote_authorizations/#{quote.id}") + end + + context 'when using a numeric ID based scheme' do + let(:quoted_account) { Fabricate(:account, id_scheme: :numeric_ap_id) } + + it 'returns a string with the web domain and expected path' do + expect(subject.approval_uri_for(quote)) + .to eq("#{host_prefix}/ap/users/#{quote.quoted_account_id}/quote_authorizations/#{quote.id}") + end + end + end + + context 'with an unapproved local quote' do + let(:quoted_account) { Fabricate(:account, id_scheme: :username_ap_id) } + let(:quoted_status) { Fabricate(:status, account: quoted_account) } + let(:quote) { Fabricate(:quote, state: :rejected, quoted_status: quoted_status) } + + it 'returns nil' do + expect(subject.approval_uri_for(quote)) + .to be_nil + end + + context 'when using a numeric ID based scheme' do + let(:quoted_account) { Fabricate(:account, id_scheme: :numeric_ap_id) } + + it 'returns nil' do + expect(subject.approval_uri_for(quote)) + .to be_nil + end + end + end + + context 'with a valid remote approval' do + let(:quoted_account) { Fabricate(:account, domain: 'example.com') } + let(:quoted_status) { Fabricate(:status, account: quoted_account) } + let(:quote) { Fabricate(:quote, status: Fabricate(:status), state: :accepted, quoted_status: quoted_status, approval_uri: 'https://example.com/approvals/1') } + + it 'returns the expected URI' do + expect(subject.approval_uri_for(quote)).to eq quote.approval_uri + end + end + + context 'with an unapproved local quote but check_approval override' do + let(:quoted_account) { Fabricate(:account, id_scheme: :username_ap_id) } + let(:quoted_status) { Fabricate(:status, account: quoted_account) } + let(:quote) { Fabricate(:quote, state: :rejected, quoted_status: quoted_status) } + + it 'returns a string with the web domain and expected path' do + expect(subject.approval_uri_for(quote, check_approval: false)) + .to eq("#{host_prefix}/users/#{quote.quoted_account.username}/quote_authorizations/#{quote.id}") + end + + context 'when using a numeric ID based scheme' do + let(:quoted_account) { Fabricate(:account, id_scheme: :numeric_ap_id) } + + it 'returns a string with the web domain and expected path' do + expect(subject.approval_uri_for(quote, check_approval: false)) + .to eq("#{host_prefix}/ap/users/#{quote.quoted_account_id}/quote_authorizations/#{quote.id}") + end + end + end + end + + describe '#replies_uri_for' do + context 'with a local status' do + let(:account) { Fabricate(:account, id_scheme: :username_ap_id) } + let(:status) { Fabricate(:status, account: account) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.replies_uri_for(status)) + .to eq("#{host_prefix}/users/#{status.account.username}/statuses/#{status.id}/replies") + end + + context 'when using a numeric ID based scheme' do + let(:account) { Fabricate(:account, id_scheme: :numeric_ap_id) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.replies_uri_for(status)) + .to eq("#{host_prefix}/ap/users/#{status.account.id}/statuses/#{status.id}/replies") + end + end + end + end + + describe '#likes_uri_for' do + context 'with a local status' do + let(:account) { Fabricate(:account, id_scheme: :username_ap_id) } + let(:status) { Fabricate(:status, account: account) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.likes_uri_for(status)) + .to eq("#{host_prefix}/users/#{status.account.username}/statuses/#{status.id}/likes") + end + + context 'when using a numeric ID based scheme' do + let(:account) { Fabricate(:account, id_scheme: :numeric_ap_id) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.likes_uri_for(status)) + .to eq("#{host_prefix}/ap/users/#{status.account.id}/statuses/#{status.id}/likes") + end + end + end + end + + describe '#shares_uri_for' do + context 'with a local status' do + let(:account) { Fabricate(:account, id_scheme: :username_ap_id) } + let(:status) { Fabricate(:status, account: account) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.shares_uri_for(status)) + .to eq("#{host_prefix}/users/#{status.account.username}/statuses/#{status.id}/shares") + end + + context 'when using a numeric ID based scheme' do + let(:account) { Fabricate(:account, id_scheme: :numeric_ap_id) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.shares_uri_for(status)) + .to eq("#{host_prefix}/ap/users/#{status.account.id}/statuses/#{status.id}/shares") + end + end + end + end + + describe '#following_uri_for' do + context 'with a local account' do + let(:account) { Fabricate(:account, id_scheme: :username_ap_id) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.following_uri_for(account)) + .to eq("#{host_prefix}/users/#{account.username}/following") + end + + context 'when using a numeric ID based scheme' do + let(:account) { Fabricate(:account, id_scheme: :numeric_ap_id) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.following_uri_for(account)) + .to eq("#{host_prefix}/ap/users/#{account.id}/following") + end + end + end + end + + describe '#followers_uri_for' do + context 'with a local account' do + let(:account) { Fabricate(:account, id_scheme: :username_ap_id) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.followers_uri_for(account)) + .to eq("#{host_prefix}/users/#{account.username}/followers") + end + + context 'when using a numeric ID based scheme' do + let(:account) { Fabricate(:account, id_scheme: :numeric_ap_id) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.followers_uri_for(account)) + .to eq("#{host_prefix}/ap/users/#{account.id}/followers") + end + end + end + end + + describe '#inbox_uri_for' do + context 'with the instance actor' do + it 'returns a string starting with web domain and with the expected path' do + expect(subject.inbox_uri_for(Account.representative)) + .to eq("#{host_prefix}/actor/inbox") + end + end + + context 'with a local account' do + let(:account) { Fabricate(:account, id_scheme: :username_ap_id) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.inbox_uri_for(account)) + .to eq("#{host_prefix}/users/#{account.username}/inbox") + end + + context 'when using a numeric ID based scheme' do + let(:account) { Fabricate(:account, id_scheme: :numeric_ap_id) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.inbox_uri_for(account)) + .to eq("#{host_prefix}/ap/users/#{account.id}/inbox") + end + end + end + end + + describe '#outbox_uri_for' do + context 'with the instance actor' do + it 'returns a string starting with web domain and with the expected path' do + expect(subject.outbox_uri_for(Account.representative)) + .to eq("#{host_prefix}/actor/outbox") + end + end + + context 'with a local account' do + let(:account) { Fabricate(:account, id_scheme: :username_ap_id) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.outbox_uri_for(account)) + .to eq("#{host_prefix}/users/#{account.username}/outbox") + end + + context 'when using a numeric ID based scheme' do + let(:account) { Fabricate(:account, id_scheme: :numeric_ap_id) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.outbox_uri_for(account)) + .to eq("#{host_prefix}/ap/users/#{account.id}/outbox") + end end end end @@ -61,20 +452,32 @@ end it 'returns followers collection for unlisted status' do - status = Fabricate(:status, visibility: :unlisted) + status = Fabricate(:status, visibility: :unlisted, account: Fabricate(:account, id_scheme: :username_ap_id)) expect(subject.to(status)).to eq [account_followers_url(status.account)] end + it 'returns followers collection for unlisted status when using a numeric ID based scheme' do + status = Fabricate(:status, visibility: :unlisted, account: Fabricate(:account, id_scheme: :numeric_ap_id)) + expect(subject.to(status)).to eq [ap_account_followers_url(status.account_id)] + end + it 'returns followers collection for private status' do - status = Fabricate(:status, visibility: :private) + status = Fabricate(:status, visibility: :private, account: Fabricate(:account, id_scheme: :username_ap_id)) expect(subject.to(status)).to eq [account_followers_url(status.account)] end + it 'returns followers collection for private status when using a numeric ID based scheme' do + status = Fabricate(:status, visibility: :private, account: Fabricate(:account, id_scheme: :numeric_ap_id)) + expect(subject.to(status)).to eq [ap_account_followers_url(status.account_id)] + end + it 'returns URIs of mentions for direct status' do status = Fabricate(:status, visibility: :direct) mentioned = Fabricate(:account) + mentioned_numeric = Fabricate(:account, id_scheme: :numeric_ap_id) status.mentions.create(account: mentioned) - expect(subject.to(status)).to eq [subject.uri_for(mentioned)] + status.mentions.create(account: mentioned_numeric) + expect(subject.to(status)).to eq [subject.uri_for(mentioned), subject.uri_for(mentioned_numeric)] end it "returns URIs of mentioned group's followers for direct statuses to groups" do @@ -111,10 +514,15 @@ describe '#cc' do it 'returns followers collection for public status' do - status = Fabricate(:status, visibility: :public) + status = Fabricate(:status, visibility: :public, account: Fabricate(:account, id_scheme: :username_ap_id)) expect(subject.cc(status)).to eq [account_followers_url(status.account)] end + it 'returns followers collection for public status when using a numeric ID based scheme' do + status = Fabricate(:status, visibility: :public, account: Fabricate(:account, id_scheme: :numeric_ap_id)) + expect(subject.cc(status)).to eq [ap_account_followers_url(status.account_id)] + end + it 'returns public collection for unlisted status' do status = Fabricate(:status, visibility: :unlisted) expect(subject.cc(status)).to eq ['https://www.w3.org/ns/activitystreams#Public'] @@ -133,8 +541,10 @@ it 'returns URIs of mentions for non-direct status' do status = Fabricate(:status, visibility: :public) mentioned = Fabricate(:account) + mentioned_numeric = Fabricate(:account, id_scheme: :numeric_ap_id) status.mentions.create(account: mentioned) - expect(subject.cc(status)).to include(subject.uri_for(mentioned)) + status.mentions.create(account: mentioned_numeric) + expect(subject.cc(status)).to include(subject.uri_for(mentioned), subject.uri_for(mentioned_numeric)) end context 'with followers and requested followers' do @@ -181,8 +591,9 @@ end describe '#uri_to_local_id' do + let(:account) { Fabricate(:account, id_scheme: :username_ap_id) } + it 'returns the local ID' do - account = Fabricate(:account) expect(subject.uri_to_local_id(subject.uri_for(account), :username)).to eq account.username end end diff --git a/spec/lib/annual_report/commonly_interacted_with_accounts_spec.rb b/spec/lib/annual_report/commonly_interacted_with_accounts_spec.rb deleted file mode 100644 index 12bf3810db6d60..00000000000000 --- a/spec/lib/annual_report/commonly_interacted_with_accounts_spec.rb +++ /dev/null @@ -1,50 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe AnnualReport::CommonlyInteractedWithAccounts do - describe '#generate' do - subject { described_class.new(account, Time.zone.now.year) } - - context 'with an inactive account' do - let(:account) { Fabricate :account } - - it 'builds a report for an account' do - expect(subject.generate) - .to include( - commonly_interacted_with_accounts: be_an(Array).and(be_empty) - ) - end - end - - context 'with an active account' do - let(:account) { Fabricate :account } - - let(:other_account) { Fabricate :account } - let(:most_other_account) { Fabricate :account } - - before do - _other = Fabricate :status - - Fabricate :status, account: account, reply: true, in_reply_to_id: Fabricate(:status, account: other_account).id - Fabricate :status, account: account, reply: true, in_reply_to_id: Fabricate(:status, account: other_account).id - - Fabricate :status, account: account, reply: true, in_reply_to_id: Fabricate(:status, account: most_other_account).id - Fabricate :status, account: account, reply: true, in_reply_to_id: Fabricate(:status, account: most_other_account).id - Fabricate :status, account: account, reply: true, in_reply_to_id: Fabricate(:status, account: most_other_account).id - end - - it 'builds a report for an account' do - expect(subject.generate) - .to include( - commonly_interacted_with_accounts: eq( - [ - { account_id: most_other_account.id.to_s, count: 3 }, - { account_id: other_account.id.to_s, count: 2 }, - ] - ) - ) - end - end - end -end diff --git a/spec/lib/annual_report/most_reblogged_accounts_spec.rb b/spec/lib/annual_report/most_reblogged_accounts_spec.rb deleted file mode 100644 index 956549c32527f1..00000000000000 --- a/spec/lib/annual_report/most_reblogged_accounts_spec.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe AnnualReport::MostRebloggedAccounts do - describe '#generate' do - subject { described_class.new(account, Time.zone.now.year) } - - context 'with an inactive account' do - let(:account) { Fabricate :account } - - it 'builds a report for an account' do - expect(subject.generate) - .to include( - most_reblogged_accounts: be_an(Array).and(be_empty) - ) - end - end - - context 'with an active account' do - let(:account) { Fabricate :account } - - let(:other_account) { Fabricate :account } - let(:most_other_account) { Fabricate :account } - - before do - _other = Fabricate :status - Fabricate :status, account: account, reblog: Fabricate(:status, account: other_account) - Fabricate :status, account: account, reblog: Fabricate(:status, account: other_account) - - Fabricate :status, account: account, reblog: Fabricate(:status, account: most_other_account) - Fabricate :status, account: account, reblog: Fabricate(:status, account: most_other_account) - Fabricate :status, account: account, reblog: Fabricate(:status, account: most_other_account) - end - - it 'builds a report for an account' do - expect(subject.generate) - .to include( - most_reblogged_accounts: eq( - [ - { account_id: most_other_account.id.to_s, count: 3 }, - { account_id: other_account.id.to_s, count: 2 }, - ] - ) - ) - end - end - end -end diff --git a/spec/lib/annual_report/most_used_apps_spec.rb b/spec/lib/annual_report/most_used_apps_spec.rb deleted file mode 100644 index ab7022ef2037a2..00000000000000 --- a/spec/lib/annual_report/most_used_apps_spec.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe AnnualReport::MostUsedApps do - describe '#generate' do - subject { described_class.new(account, Time.zone.now.year) } - - context 'with an inactive account' do - let(:account) { Fabricate :account } - - it 'builds a report for an account' do - expect(subject.generate) - .to include( - most_used_apps: be_an(Array).and(be_empty) - ) - end - end - - context 'with an active account' do - let(:account) { Fabricate :account } - - let(:application) { Fabricate :application, name: 'App' } - let(:most_application) { Fabricate :application, name: 'Most App' } - - before do - _other = Fabricate :status - Fabricate.times 2, :status, account: account, application: application - Fabricate.times 3, :status, account: account, application: most_application - end - - it 'builds a report for an account' do - expect(subject.generate) - .to include( - most_used_apps: eq( - [ - { name: most_application.name, count: 3 }, - { name: application.name, count: 2 }, - ] - ) - ) - end - end - end -end diff --git a/spec/lib/annual_report/percentiles_spec.rb b/spec/lib/annual_report/percentiles_spec.rb deleted file mode 100644 index 11df81cfb6c80a..00000000000000 --- a/spec/lib/annual_report/percentiles_spec.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe AnnualReport::Percentiles do - describe '#generate' do - subject { described_class.new(account, year) } - - let(:year) { Time.zone.now.year } - - context 'with an inactive account' do - let(:account) { Fabricate :account } - - it 'builds a report for an account' do - described_class.prepare(year) - - expect(subject.generate) - .to include( - percentiles: include( - statuses: 100 - ) - ) - end - end - - context 'with an active account' do - let(:account) { Fabricate :account } - - before do - Fabricate.times 2, :status # Others as `account` - Fabricate.times 2, :status, account: account - end - - it 'builds a report for an account' do - described_class.prepare(year) - - expect(subject.generate) - .to include( - percentiles: include( - statuses: 50 - ) - ) - end - end - end -end diff --git a/spec/lib/annual_report/time_series_spec.rb b/spec/lib/annual_report/time_series_spec.rb index 219d6c08347a28..046ac5b20257e0 100644 --- a/spec/lib/annual_report/time_series_spec.rb +++ b/spec/lib/annual_report/time_series_spec.rb @@ -13,7 +13,7 @@ expect(subject.generate) .to include( time_series: match( - include(followers: 0, following: 0, month: 1, statuses: 0) + include(followers: 0, month: 12, statuses: 0) ) ) end @@ -37,7 +37,7 @@ expect(subject.generate) .to include( time_series: match( - include(followers: 1, following: 1, month: 1, statuses: 1) + include(followers: 1, month: 12, statuses: 1) ) ) end diff --git a/spec/lib/annual_report/top_hashtags_spec.rb b/spec/lib/annual_report/top_hashtags_spec.rb index b9cc9392ed224a..1438999392ee8f 100644 --- a/spec/lib/annual_report/top_hashtags_spec.rb +++ b/spec/lib/annual_report/top_hashtags_spec.rb @@ -44,7 +44,6 @@ top_hashtags: eq( [ { name: most_tag.name, count: 3 }, - { name: tag.name, count: 2 }, ] ) ) diff --git a/spec/lib/annual_report/top_statuses_spec.rb b/spec/lib/annual_report/top_statuses_spec.rb index af29df1f651a0b..24fa2139ce25d2 100644 --- a/spec/lib/annual_report/top_statuses_spec.rb +++ b/spec/lib/annual_report/top_statuses_spec.rb @@ -40,8 +40,8 @@ .to include( top_statuses: include( by_reblogs: reblogged_status.id.to_s, - by_favourites: favourited_status.id.to_s, - by_replies: replied_status.id.to_s + by_favourites: nil, + by_replies: nil ) ) end diff --git a/spec/lib/annual_report/type_distribution_spec.rb b/spec/lib/annual_report/type_distribution_spec.rb deleted file mode 100644 index 89a31fb20718de..00000000000000 --- a/spec/lib/annual_report/type_distribution_spec.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe AnnualReport::TypeDistribution do - describe '#generate' do - subject { described_class.new(account, Time.zone.now.year) } - - context 'with an inactive account' do - let(:account) { Fabricate :account } - - it 'builds a report for an account' do - expect(subject.generate) - .to include( - type_distribution: include( - total: 0, - reblogs: 0, - replies: 0, - standalone: 0 - ) - ) - end - end - - context 'with an active account' do - let(:account) { Fabricate :account } - - before do - _other = Fabricate :status - Fabricate :status, reblog: Fabricate(:status), account: account - Fabricate :status, in_reply_to_id: Fabricate(:status).id, account: account, reply: true - Fabricate :status, account: account - end - - it 'builds a report for an account' do - expect(subject.generate) - .to include( - type_distribution: include( - total: 3, - reblogs: 1, - replies: 1, - standalone: 1 - ) - ) - end - end - end -end diff --git a/spec/lib/annual_report_spec.rb b/spec/lib/annual_report_spec.rb index fa898d3ac551bd..bd4d0f33876a06 100644 --- a/spec/lib/annual_report_spec.rb +++ b/spec/lib/annual_report_spec.rb @@ -13,13 +13,4 @@ .to change(GeneratedAnnualReport, :count).by(1) end end - - describe '.prepare' do - before { Fabricate :status } - - it 'generates records from source class which prepare data' do - expect { described_class.prepare(Time.current.year) } - .to change(AnnualReport::StatusesPerAccountCount, :count).by(1) - end - end end diff --git a/spec/lib/extractor_spec.rb b/spec/lib/extractor_spec.rb index bc3ee8ac496a2b..e1a57d57883ca5 100644 --- a/spec/lib/extractor_spec.rb +++ b/spec/lib/extractor_spec.rb @@ -35,12 +35,24 @@ end describe 'extract_hashtags_with_indices' do - it 'returns an empty array if it does not have #' do + it 'returns an empty array if it does not have # or #' do text = 'a string without hash sign' extracted = described_class.extract_hashtags_with_indices(text) expect(extracted).to eq [] end + it 'returns hashtags preceded by an ASCII hash' do + text = 'hello #world' + extracted = described_class.extract_hashtags_with_indices(text) + expect(extracted).to eq [{ hashtag: 'world', indices: [6, 12] }] + end + + it 'returns hashtags preceded by a full-width hash' do + text = 'hello #world' + extracted = described_class.extract_hashtags_with_indices(text) + expect(extracted).to eq [{ hashtag: 'world', indices: [6, 12] }] + end + it 'does not exclude normal hash text before ://' do text = '#hashtag://' extracted = described_class.extract_hashtags_with_indices(text) diff --git a/spec/lib/interaction_policy_spec.rb b/spec/lib/interaction_policy_spec.rb new file mode 100644 index 00000000000000..2382c74349a45c --- /dev/null +++ b/spec/lib/interaction_policy_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe InteractionPolicy do + subject { described_class.new(bitmap) } + + let(:bitmap) { (0b0101 << 16) | 0b0010 } + + describe described_class::SubPolicy do + subject { InteractionPolicy.new(bitmap) } + + describe '#as_keys' do + it 'returns the expected values' do + expect(subject.automatic.as_keys).to eq ['unsupported_policy', 'followers'] + expect(subject.manual.as_keys).to eq ['public'] + end + end + + describe '#public?' do + it 'returns the expected values' do + expect(subject.automatic.public?).to be false + expect(subject.manual.public?).to be true + end + end + + describe '#unsupported_policy?' do + it 'returns the expected values' do + expect(subject.automatic.unsupported_policy?).to be true + expect(subject.manual.unsupported_policy?).to be false + end + end + + describe '#followers?' do + it 'returns the expected values' do + expect(subject.automatic.followers?).to be true + expect(subject.manual.followers?).to be false + end + end + end +end diff --git a/spec/lib/mastodon/cli/accounts_spec.rb b/spec/lib/mastodon/cli/accounts_spec.rb index 111703a18bbe6b..927c6ca8debed8 100644 --- a/spec/lib/mastodon/cli/accounts_spec.rb +++ b/spec/lib/mastodon/cli/accounts_spec.rb @@ -361,11 +361,20 @@ def account_from_options context 'with --reset-password option' do let(:options) { { reset_password: true } } + let(:user) { Fabricate(:user, password: original_password) } + let(:original_password) { 'foobar12345' } + let(:new_password) { 'new_password12345' } + it 'returns a new password for the user' do - allow(SecureRandom).to receive(:hex).and_return('new_password') + allow(SecureRandom).to receive(:hex).and_return(new_password) + allow(Account).to receive(:find_local).and_return(user.account) + allow(user).to receive(:change_password!).and_call_original expect { subject } - .to output_results('new_password') + .to output_results(new_password) + + expect(user).to have_received(:change_password!).with(new_password) + expect(user.reload).to_not be_external_or_valid_password(original_password) end end diff --git a/spec/lib/mastodon/cli/email_domain_blocks_spec.rb b/spec/lib/mastodon/cli/email_domain_blocks_spec.rb index 6ce1a7c5f3aee6..1662785f392a71 100644 --- a/spec/lib/mastodon/cli/email_domain_blocks_spec.rb +++ b/spec/lib/mastodon/cli/email_domain_blocks_spec.rb @@ -15,17 +15,62 @@ describe '#list' do let(:action) { :list } + context 'with both --only-blocked and --only-with-approval' do + let(:options) { { only_blocked: true, only_with_approval: true } } + + it 'warns about usage and exits' do + expect { subject } + .to raise_error(Thor::Error, 'Cannot specify both --only-blocked and --only-with-approval') + end + end + context 'with email domain block records' do let!(:parent_block) { Fabricate(:email_domain_block) } let!(:child_block) { Fabricate(:email_domain_block, parent: parent_block) } + let!(:parent_allow_block) { Fabricate(:email_domain_block, allow_with_approval: true) } + let!(:child_allow_block) { Fabricate(:email_domain_block, parent: parent_allow_block, allow_with_approval: true) } - it 'lists the blocks' do + it 'lists all the blocks by default' do expect { subject } .to output_results( parent_block.domain, - child_block.domain + child_block.domain, + parent_allow_block.domain, + child_allow_block.domain ) end + + context 'with the --only-blocked flag set' do + let(:options) { { only_blocked: true } } + + it 'lists only blocked domains' do + expect { subject } + .to output_results( + parent_block.domain, + child_block.domain + ) + .and not_output_results( + parent_allow_block.domain, + child_allow_block.domain + ) + end + end + + context 'with the --only-with-approval flag set' do + let(:options) { { only_with_approval: true } } + + it 'lists only manually approvable domains' do + expect { subject } + .to output_results( + parent_allow_block.domain, + child_allow_block.domain + ) + .and not_output_results( + parent_block.domain, + child_block.domain + ) + end + end end end @@ -56,6 +101,7 @@ context 'when no blocks exist' do let(:domain) { 'host.example' } let(:arguments) { [domain] } + let(:options) { { allow_with_approval: false } } it 'adds a new block' do expect { subject } @@ -67,7 +113,7 @@ context 'with --with-dns-records true' do let(:domain) { 'host.example' } let(:arguments) { [domain] } - let(:options) { { with_dns_records: true } } + let(:options) { { allow_with_approval: false, with_dns_records: true } } before do configure_mx(domain: domain, exchange: 'other.host') diff --git a/spec/lib/mastodon/cli/ip_blocks_spec.rb b/spec/lib/mastodon/cli/ip_blocks_spec.rb index d531b8b7a86b9d..1f903ac065c4d0 100644 --- a/spec/lib/mastodon/cli/ip_blocks_spec.rb +++ b/spec/lib/mastodon/cli/ip_blocks_spec.rb @@ -251,12 +251,12 @@ def other_ranges it 'exports blocked IPs with "no_access" severity in plain format' do expect { subject } - .to output_results("#{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}\n#{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix}") + .to output_results("#{first_ip_range_block.to_cidr}\n#{second_ip_range_block.to_cidr}") end it 'does not export blocked IPs with different severities' do expect { subject } - .to_not output_results("#{third_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}") + .to_not output_results(third_ip_range_block.to_cidr) end end @@ -265,19 +265,19 @@ def other_ranges it 'exports blocked IPs with "no_access" severity in plain format' do expect { subject } - .to output_results("deny #{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix};\ndeny #{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix};") + .to output_results("deny #{first_ip_range_block.to_cidr};\ndeny #{second_ip_range_block.to_cidr};") end it 'does not export blocked IPs with different severities' do expect { subject } - .to_not output_results("deny #{third_ip_range_block.ip}/#{first_ip_range_block.ip.prefix};") + .to_not output_results("deny #{third_ip_range_block.to_cidr};") end end context 'when --format option is not provided' do it 'exports blocked IPs in plain format by default' do expect { subject } - .to output_results("#{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}\n#{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix}") + .to output_results("#{first_ip_range_block.to_cidr}\n#{second_ip_range_block.to_cidr}") end end end diff --git a/spec/lib/mastodon/email_configuration_helper_spec.rb b/spec/lib/mastodon/email_configuration_helper_spec.rb index db513672f06c0c..2894699d05711c 100644 --- a/spec/lib/mastodon/email_configuration_helper_spec.rb +++ b/spec/lib/mastodon/email_configuration_helper_spec.rb @@ -21,8 +21,9 @@ base_configuration.merge({ enable_starttls: 'always' }) end - it 'converts this to `true`' do - expect(converted_settings[:enable_starttls]).to be true + it 'converts this to `:always`' do + expect(converted_settings[:enable_starttls]).to eq :always + expect(converted_settings[:enable_starttls_auto]).to be_nil end end @@ -33,6 +34,7 @@ it 'converts this to `false`' do expect(converted_settings[:enable_starttls]).to be false + expect(converted_settings[:enable_starttls_auto]).to be_nil end end @@ -41,28 +43,43 @@ base_configuration.merge({ enable_starttls: 'auto' }) end - it 'sets `enable_starttls_auto` instead' do - expect(converted_settings[:enable_starttls]).to be_nil - expect(converted_settings[:enable_starttls_auto]).to be true + it 'sets `enable_starttls` to `:auto`' do + expect(converted_settings[:enable_starttls]).to eq :auto + expect(converted_settings[:enable_starttls_auto]).to be_nil end end context 'when `enable_starttls` is unset' do - context 'when `enable_starttls_auto` is unset' do - let(:configuration) { base_configuration } + context 'when `enable_starttls_auto` is true' do + let(:configuration) do + base_configuration.merge({ enable_starttls_auto: true }) + end + + it 'sets `enable_starttls` to `:auto`' do + expect(converted_settings[:enable_starttls]).to eq :auto + expect(converted_settings[:enable_starttls_auto]).to be_nil + end + end + + context 'when `tls` is set to true' do + let(:configuration) do + base_configuration.merge({ tls: true }) + end - it 'sets `enable_starttls_auto` to `true`' do - expect(converted_settings[:enable_starttls_auto]).to be true + it 'sets `enable_starttls` to `nil`' do + expect(converted_settings[:enable_starttls]).to be_nil + expect(converted_settings[:enable_starttls_auto]).to be_nil end end - context 'when `enable_starttls_auto` is set to "false"' do + context 'when `enable_starttls_auto` is set to false' do let(:configuration) do - base_configuration.merge({ enable_starttls_auto: 'false' }) + base_configuration.merge({ enable_starttls_auto: false }) end - it 'sets `enable_starttls_auto` to `false`' do - expect(converted_settings[:enable_starttls_auto]).to be false + it 'sets `enable_starttls` to `false`' do + expect(converted_settings[:enable_starttls]).to be false + expect(converted_settings[:enable_starttls_auto]).to be_nil end end end diff --git a/spec/lib/mastodon/redis_configuration_spec.rb b/spec/lib/mastodon/redis_configuration_spec.rb index d8a8dd86d29af7..560b16ed309fdf 100644 --- a/spec/lib/mastodon/redis_configuration_spec.rb +++ b/spec/lib/mastodon/redis_configuration_spec.rb @@ -73,8 +73,6 @@ end shared_examples 'sentinel support' do |prefix = nil| - prefix = prefix ? "#{prefix}_" : '' - context 'when configuring sentinel support' do around do |example| ClimateControl.modify "#{prefix}REDIS_PASSWORD": 'testpass1', "#{prefix}REDIS_HOST": 'redis2.example.com', "#{prefix}REDIS_SENTINELS": '192.168.0.1:3000,192.168.0.2:4000', "#{prefix}REDIS_SENTINEL_MASTER": 'mainsentinel' do @@ -199,7 +197,7 @@ it_behaves_like 'secondary configuration', 'SIDEKIQ' it_behaves_like 'setting a different driver' - it_behaves_like 'sentinel support', 'SIDEKIQ' + it_behaves_like 'sentinel support', 'SIDEKIQ_' end describe '#cache' do @@ -225,6 +223,6 @@ it_behaves_like 'secondary configuration', 'CACHE' it_behaves_like 'setting a different driver' - it_behaves_like 'sentinel support', 'CACHE' + it_behaves_like 'sentinel support', 'CACHE_' end end diff --git a/spec/lib/permalink_redirector_spec.rb b/spec/lib/permalink_redirector_spec.rb index 5a544c3d38e136..81fa05449e96d0 100644 --- a/spec/lib/permalink_redirector_spec.rb +++ b/spec/lib/permalink_redirector_spec.rb @@ -10,39 +10,77 @@ Fabricate(:status, account: remote_account, id: 123, url: 'https://example.com/status-123') end - it 'returns path for legacy account links' do - redirector = described_class.new('accounts/2') - expect(redirector.redirect_path).to eq 'https://example.com/@alice' + it 'returns path for deck URLs with query params' do + redirector = described_class.new('/deck/directory?local=true') + expect(redirector.redirect_path).to eq '/directory?local=true' end - it 'returns path for legacy status links' do - redirector = described_class.new('statuses/123') - expect(redirector.redirect_path).to eq 'https://example.com/status-123' - end + context 'when account is not suspended' do + it 'returns path for legacy account links' do + redirector = described_class.new('accounts/2') + expect(redirector.redirect_path).to eq 'https://example.com/@alice' + end - it 'returns path for pretty account links' do - redirector = described_class.new('@alice@example.com') - expect(redirector.redirect_path).to eq 'https://example.com/@alice' - end + it 'returns path for legacy status links' do + redirector = described_class.new('statuses/123') + expect(redirector.redirect_path).to eq 'https://example.com/status-123' + end - it 'returns path for pretty status links' do - redirector = described_class.new('@alice/123') - expect(redirector.redirect_path).to eq 'https://example.com/status-123' - end + it 'returns path for pretty account links' do + redirector = described_class.new('@alice@example.com') + expect(redirector.redirect_path).to eq 'https://example.com/@alice' + end - it 'returns path for legacy status links with a query param' do - redirector = described_class.new('statuses/123?foo=bar') - expect(redirector.redirect_path).to eq 'https://example.com/status-123' - end + it 'returns path for pretty status links' do + redirector = described_class.new('@alice/123') + expect(redirector.redirect_path).to eq 'https://example.com/status-123' + end - it 'returns path for pretty status links with a query param' do - redirector = described_class.new('@alice/123?foo=bar') - expect(redirector.redirect_path).to eq 'https://example.com/status-123' + it 'returns path for legacy status links with a query param' do + redirector = described_class.new('statuses/123?foo=bar') + expect(redirector.redirect_path).to eq 'https://example.com/status-123' + end + + it 'returns path for pretty status links with a query param' do + redirector = described_class.new('@alice/123?foo=bar') + expect(redirector.redirect_path).to eq 'https://example.com/status-123' + end end - it 'returns path for deck URLs with query params' do - redirector = described_class.new('/deck/directory?local=true') - expect(redirector.redirect_path).to eq '/directory?local=true' + context 'when account is suspended' do + before do + remote_account.suspend! + end + + it 'returns nil for legacy account links' do + redirector = described_class.new('accounts/2') + expect(redirector.redirect_path).to be_nil + end + + it 'returns nil for legacy status links' do + redirector = described_class.new('statuses/123') + expect(redirector.redirect_path).to be_nil + end + + it 'returns nil for pretty account links' do + redirector = described_class.new('@alice@example.com') + expect(redirector.redirect_path).to be_nil + end + + it 'returns nil for pretty status links' do + redirector = described_class.new('@alice/123') + expect(redirector.redirect_path).to be_nil + end + + it 'returns nil for legacy status links with a query param' do + redirector = described_class.new('statuses/123?foo=bar') + expect(redirector.redirect_path).to be_nil + end + + it 'returns nil for pretty status links with a query param' do + redirector = described_class.new('@alice/123?foo=bar') + expect(redirector.redirect_path).to be_nil + end end end end diff --git a/spec/lib/status_cache_hydrator_spec.rb b/spec/lib/status_cache_hydrator_spec.rb index 085866ef1d35f5..a6fea36397a0ca 100644 --- a/spec/lib/status_cache_hydrator_spec.rb +++ b/spec/lib/status_cache_hydrator_spec.rb @@ -29,7 +29,7 @@ end context 'when handling a status with a quote policy' do - let(:status) { Fabricate(:status, quote_approval_policy: Status::QUOTE_APPROVAL_POLICY_FLAGS[:followers] << 16) } + let(:status) { Fabricate(:status, quote_approval_policy: InteractionPolicy::POLICY_FLAGS[:followers] << 16) } before do account.follow!(status.account) @@ -176,6 +176,28 @@ end end + context 'when the quoted post has a poll authored by the user' do + let(:poll) { Fabricate(:poll, account: account) } + let(:quoted_status) { Fabricate(:status, poll: poll, account: account) } + + it 'renders the same attributes as a full render' do + expect(subject).to eql(compare_to_hash) + end + end + + context 'when the quoted post has been voted in' do + let(:poll) { Fabricate(:poll, options: %w(Yellow Blue)) } + let(:quoted_status) { Fabricate(:status, poll: poll) } + + before do + VoteService.new.call(account, poll, [0]) + end + + it 'renders the same attributes as a full render' do + expect(subject).to eql(compare_to_hash) + end + end + context 'when the quoted post matches account filters' do let(:quoted_status) { Fabricate(:status, text: 'this toot is about that banned word') } diff --git a/spec/mailers/previews/notification_mailer_preview.rb b/spec/mailers/previews/notification_mailer_preview.rb index ae2d6802bcf24a..29ab047dfb198a 100644 --- a/spec/mailers/previews/notification_mailer_preview.rb +++ b/spec/mailers/previews/notification_mailer_preview.rb @@ -35,7 +35,9 @@ def reblog # Preview this email at http://localhost:3000/rails/mailers/notification_mailer/quote def quote - activity = Quote.first + notification = Notification.where(type: 'quote').order(:created_at).last + activity = notification.activity + mailer_for(activity.quoted_account, activity).quote end diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb index 88f9d12cac8cd3..153de1ef1c8caa 100644 --- a/spec/mailers/user_mailer_spec.rb +++ b/spec/mailers/user_mailer_spec.rb @@ -27,6 +27,7 @@ address: 'localhost', port: 25, authentication: 'none', + enable_starttls_auto: true, } end @@ -44,8 +45,7 @@ address: 'localhost', port: 25, authentication: nil, - enable_starttls: nil, - enable_starttls_auto: true, + enable_starttls: :auto, }) end end @@ -141,7 +141,9 @@ end describe '#warning' do - let(:strike) { Fabricate(:account_warning, target_account: receiver.account, text: 'dont worry its just the testsuite', action: 'suspend') } + let(:status) { Fabricate(:status, account: receiver.account) } + let(:quote) { Fabricate(:quote, state: :accepted, status: status) } + let(:strike) { Fabricate(:account_warning, target_account: receiver.account, text: 'dont worry its just the testsuite', action: 'suspend', status_ids: [quote.status_id]) } let(:mail) { described_class.warning(receiver, strike) } it 'renders warning notification' do diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index be400fecd4a41c..89a2f78c2e1039 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -781,4 +781,37 @@ def fields_empty_name expect(subject.reload.followers_count).to eq 15 end end + + describe '#featureable?' do + subject { Fabricate.build(:account, domain: (local ? nil : 'example.com'), discoverable:) } + + context 'when account is local' do + let(:local) { true } + + context 'when account is discoverable' do + let(:discoverable) { true } + + it 'returns `true`' do + expect(subject.featureable?).to be true + end + end + + context 'when account is not discoverable' do + let(:discoverable) { false } + + it 'returns `false`' do + expect(subject.featureable?).to be false + end + end + end + + context 'when account is remote' do + let(:local) { false } + let(:discoverable) { true } + + it 'returns `false`' do + expect(subject.featureable?).to be false + end + end + end end diff --git a/spec/models/collection_item_spec.rb b/spec/models/collection_item_spec.rb new file mode 100644 index 00000000000000..3592c75e66789a --- /dev/null +++ b/spec/models/collection_item_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe CollectionItem do + describe 'Validations' do + subject { Fabricate.build(:collection_item) } + + it { is_expected.to define_enum_for(:state) } + + it { is_expected.to validate_numericality_of(:position).only_integer.is_greater_than(0) } + + context 'when account inclusion is accepted' do + subject { Fabricate.build(:collection_item, state: :accepted) } + + it { is_expected.to validate_presence_of(:account) } + end + + context 'when item is local and account is remote' do + subject { Fabricate.build(:collection_item, account: remote_account) } + + let(:remote_account) { Fabricate.build(:remote_account) } + + it { is_expected.to validate_presence_of(:activity_uri) } + end + + context 'when item is not local' do + subject { Fabricate.build(:collection_item, collection: remote_collection) } + + let(:remote_collection) { Fabricate.build(:collection, local: false) } + + it { is_expected.to validate_absence_of(:approval_uri) } + end + + context 'when account is not present' do + subject { Fabricate.build(:unverified_remote_collection_item) } + + it { is_expected.to validate_presence_of(:object_uri) } + end + end + + describe 'Creation' do + let(:collection) { Fabricate(:collection) } + let(:other_collection) { Fabricate(:collection) } + let(:account) { Fabricate(:account) } + let(:other_account) { Fabricate(:account) } + + it 'automatically sets the `position` if absent' do + first_item = collection.collection_items.create(account:) + second_item = collection.collection_items.create(account: other_account) + unrelated_item = other_collection.collection_items.create(account:) + custom_item = other_collection.collection_items.create(account: other_account, position: 7) + + expect(first_item.position).to eq 1 + expect(second_item.position).to eq 2 + expect(unrelated_item.position).to eq 1 + expect(custom_item.position).to eq 7 + end + end +end diff --git a/spec/models/collection_spec.rb b/spec/models/collection_spec.rb new file mode 100644 index 00000000000000..659e01786942b3 --- /dev/null +++ b/spec/models/collection_spec.rb @@ -0,0 +1,129 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Collection do + describe 'Validations' do + subject { Fabricate.build :collection } + + it { is_expected.to validate_presence_of(:name) } + + it { is_expected.to validate_presence_of(:description) } + + it { is_expected.to_not allow_value(nil).for(:local) } + + it { is_expected.to_not allow_value(nil).for(:sensitive) } + + it { is_expected.to_not allow_value(nil).for(:discoverable) } + + context 'when collection is remote' do + subject { Fabricate.build :collection, local: false } + + it { is_expected.to validate_presence_of(:uri) } + + it { is_expected.to validate_presence_of(:original_number_of_items) } + end + + context 'when using a hashtag as category' do + subject { Fabricate.build(:collection, tag:) } + + context 'when hashtag is usable' do + let(:tag) { Fabricate.build(:tag) } + + it { is_expected.to be_valid } + end + + context 'when hashtag is not usable' do + let(:tag) { Fabricate.build(:tag, usable: false) } + + it { is_expected.to_not be_valid } + end + end + + context 'when there are more items than allowed' do + subject { Fabricate.build(:collection, collection_items:) } + + let(:collection_items) { Fabricate.build_times(described_class::MAX_ITEMS + 1, :collection_item, collection: nil) } + + it { is_expected.to_not be_valid } + end + end + + describe '#item_for' do + subject { Fabricate(:collection) } + + let!(:items) { Fabricate.times(2, :collection_item, collection: subject) } + + context 'when given no account' do + it 'returns all items' do + expect(subject.items_for).to match_array(items) + end + end + + context 'when given an account' do + let(:account) { Fabricate(:account) } + + before do + account.block!(items.first.account) + end + + it 'does not return items blocked by this account' do + expect(subject.items_for(account)).to contain_exactly(items.last) + end + end + end + + describe '#tag_name=' do + context 'when the collection is new and has no tag' do + subject { Fabricate.build(:collection) } + + context 'when the tag exists' do + let!(:tag) { Fabricate.create(:tag, name: 'people') } + + it 'correctly assigns the existing tag' do + subject.tag_name = '#people' + + expect(subject.tag).to eq tag + end + end + + context 'when the tag does not exist' do + it 'creates and assigns a new tag' do + expect do + subject.tag_name = '#people' + end.to change(Tag, :count).by(1) + + expect(subject.tag).to be_present + expect(subject.tag.name).to eq 'people' + end + end + end + + context 'when the collection is persisted and has a tag' do + subject { Fabricate(:collection, tag:) } + + let!(:tag) { Fabricate(:tag, name: 'people') } + + context 'when the new tag is the same' do + it 'does not change the object' do + subject.tag_name = '#People' + + expect(subject.tag).to eq tag + expect(subject).to_not be_changed + end + end + + context 'when the new tag is different' do + it 'creates and assigns a new tag' do + expect do + subject.tag_name = '#bots' + end.to change(Tag, :count).by(1) + + expect(subject.tag).to be_present + expect(subject.tag.name).to eq 'bots' + expect(subject).to be_changed + end + end + end + end +end diff --git a/spec/models/concerns/account/finder_concern_spec.rb b/spec/models/concerns/account/finder_concern_spec.rb index b3fae56dfc51e8..18d5c204754c35 100644 --- a/spec/models/concerns/account/finder_concern_spec.rb +++ b/spec/models/concerns/account/finder_concern_spec.rb @@ -3,6 +3,37 @@ require 'rails_helper' RSpec.describe Account::FinderConcern do + describe '.representative' do + context 'with an instance actor using an invalid legacy username' do + let(:legacy_value) { 'localhost:3000' } + + before { Account.find(Account::INSTANCE_ACTOR_ID).update_attribute(:username, legacy_value) } + + it 'updates the username to the new value' do + expect { Account.representative } + .to change { Account.find(Account::INSTANCE_ACTOR_ID).username }.from(legacy_value).to('mastodon.internal') + end + end + + context 'without an instance actor' do + before { Account.find(Account::INSTANCE_ACTOR_ID).destroy! } + + it 'creates an instance actor' do + expect { Account.representative } + .to change(Account.where(id: Account::INSTANCE_ACTOR_ID), :count).from(0).to(1) + end + end + + context 'with a correctly loaded instance actor' do + let(:instance_actor) { Account.find(Account::INSTANCE_ACTOR_ID) } + + it 'returns the instance actor record' do + expect(Account.representative) + .to eq(instance_actor) + end + end + end + describe 'local finders' do let!(:account) { Fabricate(:account, username: 'Alice') } diff --git a/spec/models/concerns/account/interaction_policy_concern_spec.rb b/spec/models/concerns/account/interaction_policy_concern_spec.rb new file mode 100644 index 00000000000000..586a33b77f4ed4 --- /dev/null +++ b/spec/models/concerns/account/interaction_policy_concern_spec.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Account::InteractionPolicyConcern do + describe '#feature_policy_as_keys' do + context 'when account is local' do + context 'when account is discoverable' do + let(:account) { Fabricate(:account) } + + it 'returns public for automtatic and nothing for manual' do + expect(account.feature_policy_as_keys(:automatic)).to eq [:public] + expect(account.feature_policy_as_keys(:manual)).to eq [] + end + end + + context 'when account is not discoverable' do + let(:account) { Fabricate(:account, discoverable: false) } + + it 'returns empty arrays for both inputs' do + expect(account.feature_policy_as_keys(:automatic)).to eq [] + expect(account.feature_policy_as_keys(:manual)).to eq [] + end + end + end + + context 'when account is remote' do + let(:account) { Fabricate(:account, domain: 'example.com', feature_approval_policy: (0b0101 << 16) | 0b0010) } + + it 'returns the expected values' do + expect(account.feature_policy_as_keys(:automatic)).to eq ['unsupported_policy', 'followers'] + expect(account.feature_policy_as_keys(:manual)).to eq ['public'] + end + end + end + + describe '#feature_policy_for_account' do + context 'when account is remote' do + let(:account) { Fabricate(:account, domain: 'example.com', feature_approval_policy:) } + let(:feature_approval_policy) { (0b0101 << 16) | 0b0010 } + let(:other_account) { Fabricate(:account) } + + context 'when no policy is available' do + let(:feature_approval_policy) { 0 } + + context 'when both accounts are the same' do + it 'returns :automatic' do + expect(account.feature_policy_for_account(account)).to eq :automatic + end + end + + context 'with two different accounts' do + it 'returns :missing' do + expect(account.feature_policy_for_account(other_account)).to eq :missing + end + end + end + + context 'when the other account is not following the account' do + it 'returns :manual because of the public entry in the manual policy' do + expect(account.feature_policy_for_account(other_account)).to eq :manual + end + end + + context 'when the other account is following the account' do + before do + other_account.follow!(account) + end + + it 'returns :automatic because of the followers entry in the automatic policy' do + expect(account.feature_policy_for_account(other_account)).to eq :automatic + end + end + + context 'when the account falls into the unknown bucket' do + let(:feature_approval_policy) { (0b0001 << 16) | 0b0100 } + + it 'returns :automatic because of the followers entry in the automatic policy' do + expect(account.feature_policy_for_account(other_account)).to eq :unknown + end + end + end + end +end diff --git a/spec/models/concerns/account/interactions_spec.rb b/spec/models/concerns/account/interactions_spec.rb index e6e9076edb6cad..cc50c465517422 100644 --- a/spec/models/concerns/account/interactions_spec.rb +++ b/spec/models/concerns/account/interactions_spec.rb @@ -302,9 +302,24 @@ subject { account.following?(target_account) } context 'when following target_account' do - it 'returns true' do + before do account.active_relationships.create(target_account: target_account) - expect(subject).to be true + end + + it 'returns true' do + result = nil + expect { result = subject }.to execute_queries + expect(result).to be true + end + + context 'when relations are preloaded' do + it 'does not query the database to get the result' do + account.preload_relations!([target_account.id]) + + result = nil + expect { result = subject }.to_not execute_queries + expect(result).to be true + end end end @@ -336,9 +351,26 @@ subject { account.blocking?(target_account) } context 'when blocking target_account' do - it 'returns true' do + before do account.block_relationships.create(target_account: target_account) - expect(subject).to be true + end + + it 'returns true' do + result = nil + expect { result = subject }.to execute_queries + + expect(result).to be true + end + + context 'when relations are preloaded' do + it 'does not query the database to get the result' do + account.preload_relations!([target_account.id]) + + result = nil + expect { result = subject }.to_not execute_queries + + expect(result).to be true + end end end @@ -349,16 +381,65 @@ end end + describe '#blocked_by?' do + subject { account.blocked_by?(target_account) } + + context 'when blocked by target_account' do + before do + target_account.block_relationships.create(target_account: account) + end + + it 'returns true' do + result = nil + expect { result = subject }.to execute_queries + + expect(result).to be true + end + + context 'when relations are preloaded' do + it 'does not query the database to get the result' do + account.preload_relations!([target_account.id]) + + result = nil + expect { result = subject }.to_not execute_queries + + expect(result).to be true + end + end + end + + context 'when not blocked by target_account' do + it 'returns false' do + expect(subject).to be false + end + end + end + describe '#domain_blocking?' do subject { account.domain_blocking?(domain) } let(:domain) { 'example.com' } context 'when blocking the domain' do - it 'returns true' do + before do account_domain_block = Fabricate(:account_domain_block, domain: domain) account.domain_blocks << account_domain_block - expect(subject).to be true + end + + it 'returns true' do + result = nil + expect { result = subject }.to execute_queries + expect(result).to be true + end + + context 'when relations are preloaded' do + it 'does not query the database to get the result' do + account.preload_relations!([], [domain]) + + result = nil + expect { result = subject }.to_not execute_queries + expect(result).to be true + end end end @@ -373,10 +454,25 @@ subject { account.muting?(target_account) } context 'when muting target_account' do - it 'returns true' do + before do mute = Fabricate(:mute, account: account, target_account: target_account) account.mute_relationships << mute - expect(subject).to be true + end + + it 'returns true' do + result = nil + expect { result = subject }.to execute_queries + expect(result).to be true + end + + context 'when relations are preloaded' do + it 'does not query the database to get the result' do + account.preload_relations!([target_account.id]) + + result = nil + expect { result = subject }.to_not execute_queries + expect(result).to be true + end end end @@ -563,6 +659,22 @@ me.follow!(remote_alice) expect(remote_alice.local_followers_hash).to eq Digest::SHA256.hexdigest(ActivityPub::TagManager.instance.uri_for(me)) end + + context 'when using numeric ID based scheme' do + let(:me) { Fabricate(:account, username: 'Me', id_scheme: :numeric_ap_id) } + + it 'returns correct hash for local users' do + expect(remote_alice.local_followers_hash).to eq Digest::SHA256.hexdigest(ActivityPub::TagManager.instance.uri_for(me)) + end + + it 'invalidates cache as needed when removing or adding followers' do + expect(remote_alice.local_followers_hash).to eq Digest::SHA256.hexdigest(ActivityPub::TagManager.instance.uri_for(me)) + me.unfollow!(remote_alice) + expect(remote_alice.local_followers_hash).to eq '0000000000000000000000000000000000000000000000000000000000000000' + me.follow!(remote_alice) + expect(remote_alice.local_followers_hash).to eq Digest::SHA256.hexdigest(ActivityPub::TagManager.instance.uri_for(me)) + end + end end describe 'muting an account' do diff --git a/spec/models/concerns/status/interaction_policy_concern_spec.rb b/spec/models/concerns/status/interaction_policy_concern_spec.rb index b59a1186d9bd62..ebc261fc76d5f8 100644 --- a/spec/models/concerns/status/interaction_policy_concern_spec.rb +++ b/spec/models/concerns/status/interaction_policy_concern_spec.rb @@ -15,6 +15,22 @@ describe '#quote_policy_for_account' do let(:account) { Fabricate(:account) } + context 'when the account is the author' do + let(:status) { Fabricate(:status, account: account, quote_approval_policy: 0) } + + it 'returns :automatic' do + expect(status.quote_policy_for_account(account)).to eq :automatic + end + + context 'when it is a reblog' do + let(:status) { Fabricate(:status, account: account, quote_approval_policy: 0, reblog: Fabricate(:status)) } + + it 'returns :automatic' do + expect(status.quote_policy_for_account(account)).to eq :denied + end + end + end + context 'when the account is not following the user' do it 'returns :manual because of the public entry in the manual policy' do expect(status.quote_policy_for_account(account)).to eq :manual diff --git a/spec/models/domain_allow_spec.rb b/spec/models/domain_allow_spec.rb index 0c69aaff8dc84d..ac0c113d3a9ca3 100644 --- a/spec/models/domain_allow_spec.rb +++ b/spec/models/domain_allow_spec.rb @@ -27,4 +27,27 @@ it { is_expected.to contain_exactly(allowed_domain.domain, other_allowed_domain.domain) } end end + + describe '.rule_for' do + subject { described_class.rule_for(domain) } + + let(:domain) { 'host.example' } + + context 'with no records' do + it { is_expected.to be_nil } + end + + context 'with matching record' do + let!(:domain_allow) { Fabricate :domain_allow, domain: } + + it { is_expected.to eq(domain_allow) } + end + + context 'when called with non normalized string' do + let!(:domain_allow) { Fabricate :domain_allow, domain: } + let(:domain) { ' HOST.example/' } + + it { is_expected.to eq(domain_allow) } + end + end end diff --git a/spec/models/domain_block_spec.rb b/spec/models/domain_block_spec.rb index 14f904ea7fa6a5..e7f463c8f569a4 100644 --- a/spec/models/domain_block_spec.rb +++ b/spec/models/domain_block_spec.rb @@ -35,6 +35,17 @@ expect(described_class.rule_for('example.com')).to eq block end + it 'returns most specific rule matching a blocked domain' do + _block = Fabricate(:domain_block, domain: 'example.com') + blog_block = Fabricate(:domain_block, domain: 'blog.example.com') + expect(described_class.rule_for('host.blog.example.com')).to eq blog_block + end + + it 'returns rule matching a blocked domain when string needs normalization' do + block = Fabricate(:domain_block, domain: 'example.com') + expect(described_class.rule_for(' example.com/')).to eq block + end + it 'returns a rule matching a subdomain of a blocked domain' do block = Fabricate(:domain_block, domain: 'example.com') expect(described_class.rule_for('sub.example.com')).to eq block diff --git a/spec/models/email_domain_block_spec.rb b/spec/models/email_domain_block_spec.rb index 5874c5e53c4595..c3662b2d6cd83f 100644 --- a/spec/models/email_domain_block_spec.rb +++ b/spec/models/email_domain_block_spec.rb @@ -4,6 +4,8 @@ RSpec.describe EmailDomainBlock do describe 'block?' do + subject { described_class.block?(input) } + let(:input) { nil } context 'when given an e-mail address' do @@ -14,12 +16,12 @@ it 'returns true if the domain is blocked' do Fabricate(:email_domain_block, domain: 'example.com') - expect(described_class.block?(input)).to be true + expect(subject).to be true end it 'returns false if the domain is not blocked' do Fabricate(:email_domain_block, domain: 'other-example.com') - expect(described_class.block?(input)).to be false + expect(subject).to be false end end @@ -28,7 +30,7 @@ it 'returns true if it is a subdomain of a blocked domain' do Fabricate(:email_domain_block, domain: 'example.com') - expect(described_class.block?(input)).to be true + expect(subject).to be true end end end @@ -38,8 +40,40 @@ it 'returns true if the domain is blocked' do Fabricate(:email_domain_block, domain: 'mail.foo.com') - expect(described_class.block?(input)).to be true + expect(subject).to be true end end + + context 'when given nil' do + it { is_expected.to be false } + end + + context 'when given empty string' do + let(:input) { '' } + + it { is_expected.to be true } + end + end + + describe '.requires_approval?' do + subject { described_class.requires_approval?(input) } + + let(:input) { nil } + + context 'with a matching block requiring approval' do + before { Fabricate :email_domain_block, domain: input, allow_with_approval: true } + + let(:input) { 'host.example' } + + it { is_expected.to be true } + end + + context 'with a matching block not requiring approval' do + before { Fabricate :email_domain_block, domain: input, allow_with_approval: false } + + let(:input) { 'host.example' } + + it { is_expected.to be false } + end end end diff --git a/spec/models/export_spec.rb b/spec/models/export_spec.rb index 81aaf8858556fe..ce4c966087849f 100644 --- a/spec/models/export_spec.rb +++ b/spec/models/export_spec.rb @@ -14,15 +14,20 @@ end describe '#to_bookmarks_csv' do - before { Fabricate.times(2, :bookmark, account: account) } - + let!(:bookmark) { Fabricate(:bookmark, account: account) } let(:export) { CSV.parse(subject.to_bookmarks_csv) } + let!(:second_bookmark) { Fabricate(:bookmark, account: account) } + let!(:bookmark_of_soft_deleted) { Fabricate(:bookmark, account: account) } + + before do + bookmark_of_soft_deleted.status.discard + end it 'returns a csv of bookmarks' do expect(export) .to contain_exactly( - include(/statuses/), - include(/statuses/) + [ActivityPub::TagManager.instance.uri_for(bookmark.status)], + [ActivityPub::TagManager.instance.uri_for(second_bookmark.status)] ) end end diff --git a/spec/models/form/admin_settings_spec.rb b/spec/models/form/admin_settings_spec.rb index 899d56703a9a63..e734734e000376 100644 --- a/spec/models/form/admin_settings_spec.rb +++ b/spec/models/form/admin_settings_spec.rb @@ -53,4 +53,8 @@ end end end + + describe '#persisted?' do + it { is_expected.to be_persisted } + end end diff --git a/spec/models/ip_block_spec.rb b/spec/models/ip_block_spec.rb index 18fb7ea136bfe0..a8b0809511a3e8 100644 --- a/spec/models/ip_block_spec.rb +++ b/spec/models/ip_block_spec.rb @@ -26,6 +26,22 @@ end end + describe '#to_cidr' do + subject { Fabricate.build(:ip_block, ip:).to_cidr } + + context 'with an IP and a specified prefix' do + let(:ip) { '192.168.1.0/24' } + + it { is_expected.to eq('192.168.1.0/24') } + end + + context 'with an IP and a default prefix' do + let(:ip) { '192.168.1.0' } + + it { is_expected.to eq('192.168.1.0/32') } + end + end + describe '.blocked?' do context 'when the IP is blocked' do it 'returns true' do diff --git a/spec/models/preview_card_provider_spec.rb b/spec/models/preview_card_provider_spec.rb index 561c93d0b2571d..cd3283faa3f0aa 100644 --- a/spec/models/preview_card_provider_spec.rb +++ b/spec/models/preview_card_provider_spec.rb @@ -25,4 +25,36 @@ end end end + + describe '.matching_domain' do + subject { described_class.matching_domain(domain) } + + let(:domain) { 'host.example' } + + context 'without matching domains' do + it { is_expected.to be_nil } + end + + context 'with exact matching domain' do + let!(:preview_card_provider) { Fabricate :preview_card_provider, domain: 'host.example' } + + it { is_expected.to eq(preview_card_provider) } + end + + context 'with matching domain segment' do + let!(:preview_card_provider) { Fabricate :preview_card_provider, domain: 'host.example' } + let(:domain) { 'www.blog.host.example' } + + it { is_expected.to eq(preview_card_provider) } + end + + context 'with multiple matching records' do + let!(:preview_card_provider_more) { Fabricate :preview_card_provider, domain: 'blog.host.example' } + let(:domain) { 'www.blog.host.example' } + + before { Fabricate :preview_card_provider, domain: 'host.example' } + + it { is_expected.to eq(preview_card_provider_more) } + end + end end diff --git a/spec/models/public_feed_spec.rb b/spec/models/public_feed_spec.rb index 5f2444dc6bce33..db422c67d42c2d 100644 --- a/spec/models/public_feed_spec.rb +++ b/spec/models/public_feed_spec.rb @@ -5,6 +5,11 @@ RSpec.describe PublicFeed do let(:account) { Fabricate(:account) } + before do + Setting.local_live_feed_access = 'public' + Setting.remote_live_feed_access = 'public' + end + describe '#get' do subject { described_class.new(nil).get(20).map(&:id) } @@ -264,5 +269,320 @@ end end end + + context 'when both local_live_feed_access and remote_live_feed_access are disabled' do + before do + Setting.local_live_feed_access = 'disabled' + Setting.remote_live_feed_access = 'disabled' + end + + context 'without local_only option' do + subject { described_class.new(viewer).get(20).map(&:id) } + + let(:viewer) { nil } + + let!(:local_account) { Fabricate(:account, domain: nil) } + let!(:remote_account) { Fabricate(:account, domain: 'test.com') } + let!(:local_status) { Fabricate(:status, account: local_account) } + let!(:remote_status) { Fabricate(:status, account: remote_account) } + + context 'without a viewer' do + let(:viewer) { nil } + + it 'returns an empty list' do + expect(subject).to be_empty + end + end + + context 'with a viewer' do + let(:viewer) { Fabricate(:account, username: 'viewer') } + + it 'returns an empty list' do + expect(subject).to be_empty + end + end + + context 'with a moderator as viewer' do + let(:viewer) { Fabricate(:moderator_user).account } + + it 'includes all expected statuses' do + expect(subject).to include(local_status.id) + expect(subject).to include(remote_status.id) + end + end + end + + context 'with a local_only option set' do + subject { described_class.new(viewer, local: true).get(20).map(&:id) } + + let!(:local_account) { Fabricate(:account, domain: nil) } + let!(:remote_account) { Fabricate(:account, domain: 'test.com') } + let!(:local_status) { Fabricate(:status, account: local_account) } + let!(:remote_status) { Fabricate(:status, account: remote_account) } + + context 'without a viewer' do + let(:viewer) { nil } + + it 'returns an empty list' do + expect(subject).to be_empty + end + end + + context 'with a viewer' do + let(:viewer) { Fabricate(:account, username: 'viewer') } + + it 'returns an empty list' do + expect(subject).to be_empty + end + end + + context 'with a moderator as viewer' do + let(:viewer) { Fabricate(:moderator_user).account } + + it 'does not include remote instances statuses' do + expect(subject).to include(local_status.id) + expect(subject).to_not include(remote_status.id) + end + end + end + + context 'with a remote_only option set' do + subject { described_class.new(viewer, remote: true).get(20).map(&:id) } + + let!(:local_account) { Fabricate(:account, domain: nil) } + let!(:remote_account) { Fabricate(:account, domain: 'test.com') } + let!(:local_status) { Fabricate(:status, account: local_account) } + let!(:remote_status) { Fabricate(:status, account: remote_account) } + + context 'without a viewer' do + let(:viewer) { nil } + + it 'returns an empty list' do + expect(subject).to be_empty + end + end + + context 'with a viewer' do + let(:viewer) { Fabricate(:account, username: 'viewer') } + + it 'returns an empty list' do + expect(subject).to be_empty + end + end + + context 'with a moderator as viewer' do + let(:viewer) { Fabricate(:moderator_user).account } + + it 'includes remote statuses only' do + expect(subject).to_not include(local_status.id) + expect(subject).to include(remote_status.id) + end + end + end + end + + context 'when local_live_feed_access is disabled' do + before do + Setting.local_live_feed_access = 'disabled' + end + + context 'without local_only option' do + subject { described_class.new(viewer).get(20).map(&:id) } + + let(:viewer) { nil } + + let!(:local_account) { Fabricate(:account, domain: nil) } + let!(:remote_account) { Fabricate(:account, domain: 'test.com') } + let!(:local_status) { Fabricate(:status, account: local_account) } + let!(:remote_status) { Fabricate(:status, account: remote_account) } + + context 'without a viewer' do + let(:viewer) { nil } + + it 'does not include local instances statuses' do + expect(subject).to_not include(local_status.id) + expect(subject).to include(remote_status.id) + end + end + + context 'with a viewer' do + let(:viewer) { Fabricate(:account, username: 'viewer') } + + it 'does not include local instances statuses' do + expect(subject).to_not include(local_status.id) + expect(subject).to include(remote_status.id) + end + end + end + + context 'with a local_only option set' do + subject { described_class.new(viewer, local: true).get(20).map(&:id) } + + let!(:local_account) { Fabricate(:account, domain: nil) } + let!(:remote_account) { Fabricate(:account, domain: 'test.com') } + let!(:local_status) { Fabricate(:status, account: local_account) } + let!(:remote_status) { Fabricate(:status, account: remote_account) } + + context 'without a viewer' do + let(:viewer) { nil } + + it 'returns an empty list' do + expect(subject).to be_empty + end + end + + context 'with a viewer' do + let(:viewer) { Fabricate(:account, username: 'viewer') } + + it 'returns an empty list' do + expect(subject).to be_empty + end + end + + context 'with a moderator as viewer' do + let(:viewer) { Fabricate(:moderator_user).account } + + it 'does not include remote instances statuses' do + expect(subject).to include(local_status.id) + expect(subject).to_not include(remote_status.id) + end + end + end + + context 'with a remote_only option set' do + subject { described_class.new(viewer, remote: true).get(20).map(&:id) } + + let!(:local_account) { Fabricate(:account, domain: nil) } + let!(:remote_account) { Fabricate(:account, domain: 'test.com') } + let!(:local_status) { Fabricate(:status, account: local_account) } + let!(:remote_status) { Fabricate(:status, account: remote_account) } + + context 'without a viewer' do + let(:viewer) { nil } + + it 'does not include local instances statuses' do + expect(subject).to_not include(local_status.id) + expect(subject).to include(remote_status.id) + end + end + + context 'with a viewer' do + let(:viewer) { Fabricate(:account, username: 'viewer') } + + it 'does not include local instances statuses' do + expect(subject).to_not include(local_status.id) + expect(subject).to include(remote_status.id) + end + end + end + end + + context 'when remote_live_feed_access is disabled' do + before do + Setting.remote_live_feed_access = 'disabled' + end + + context 'without local_only option' do + subject { described_class.new(viewer).get(20).map(&:id) } + + let(:viewer) { nil } + + let!(:local_account) { Fabricate(:account, domain: nil) } + let!(:remote_account) { Fabricate(:account, domain: 'test.com') } + let!(:local_status) { Fabricate(:status, account: local_account) } + let!(:remote_status) { Fabricate(:status, account: remote_account) } + + context 'without a viewer' do + let(:viewer) { nil } + + it 'does not include remote instances statuses' do + expect(subject).to include(local_status.id) + expect(subject).to_not include(remote_status.id) + end + end + + context 'with a viewer' do + let(:viewer) { Fabricate(:account, username: 'viewer') } + + it 'does not include remote instances statuses' do + expect(subject).to include(local_status.id) + expect(subject).to_not include(remote_status.id) + end + + it 'is not affected by personal domain blocks' do + viewer.block_domain!('test.com') + expect(subject).to include(local_status.id) + expect(subject).to_not include(remote_status.id) + end + end + end + + context 'with a local_only option set' do + subject { described_class.new(viewer, local: true).get(20).map(&:id) } + + let!(:local_account) { Fabricate(:account, domain: nil) } + let!(:remote_account) { Fabricate(:account, domain: 'test.com') } + let!(:local_status) { Fabricate(:status, account: local_account) } + let!(:remote_status) { Fabricate(:status, account: remote_account) } + + context 'without a viewer' do + let(:viewer) { nil } + + it 'does not include remote instances statuses' do + expect(subject).to include(local_status.id) + expect(subject).to_not include(remote_status.id) + end + end + + context 'with a viewer' do + let(:viewer) { Fabricate(:account, username: 'viewer') } + + it 'does not include remote instances statuses' do + expect(subject).to include(local_status.id) + expect(subject).to_not include(remote_status.id) + end + + it 'is not affected by personal domain blocks' do + viewer.block_domain!('test.com') + expect(subject).to include(local_status.id) + expect(subject).to_not include(remote_status.id) + end + end + end + + context 'with a remote_only option set' do + subject { described_class.new(viewer, remote: true).get(20).map(&:id) } + + let!(:local_account) { Fabricate(:account, domain: nil) } + let!(:remote_account) { Fabricate(:account, domain: 'test.com') } + let!(:local_status) { Fabricate(:status, account: local_account) } + let!(:remote_status) { Fabricate(:status, account: remote_account) } + + context 'without a viewer' do + let(:viewer) { nil } + + it 'returns an empty list' do + expect(subject).to be_empty + end + end + + context 'with a viewer' do + let(:viewer) { Fabricate(:account, username: 'viewer') } + + it 'returns an empty list' do + expect(subject).to be_empty + end + end + + context 'with a moderator as viewer' do + let(:viewer) { Fabricate(:moderator_user).account } + + it 'does not include local instances statuses' do + expect(subject).to_not include(local_status.id) + expect(subject).to include(remote_status.id) + end + end + end + end end end diff --git a/spec/models/remote_follow_spec.rb b/spec/models/remote_follow_spec.rb index b3ec7b737e23fe..aad32825625615 100644 --- a/spec/models/remote_follow_spec.rb +++ b/spec/models/remote_follow_spec.rb @@ -68,7 +68,7 @@ def expected_subscribe_url Addressable::URI.new( host: 'quitter.no', path: '/main/ostatussub', - query_values: { profile: "https://#{Rails.configuration.x.local_domain}/users/alice" }, + query_values: { profile: ActivityPub::TagManager.instance.uri_for(account) }, scheme: 'https' ).to_s end diff --git a/spec/models/session_activation_spec.rb b/spec/models/session_activation_spec.rb index 63d22f02081b5e..036a6848cb3110 100644 --- a/spec/models/session_activation_spec.rb +++ b/spec/models/session_activation_spec.rb @@ -39,20 +39,24 @@ end describe '.activate' do - let(:options) { { user: Fabricate(:user), session_id: '1' } } + let(:user) { Fabricate :user } + let!(:session_activation) { Fabricate :session_activation, user: } - it 'calls create! and purge_old' do - allow(described_class).to receive(:create!).with(**options) - allow(described_class).to receive(:purge_old) - - described_class.activate(**options) - - expect(described_class).to have_received(:create!).with(**options) - expect(described_class).to have_received(:purge_old) + around do |example| + original = Rails.configuration.x.max_session_activations + Rails.configuration.x.max_session_activations = 1 + example.run + Rails.configuration.x.max_session_activations = original end - it 'returns an instance of SessionActivation' do - expect(described_class.activate(**options)).to be_a described_class + it 'creates a new activation and purges older ones' do + result = described_class.activate(user: user, session_id: '123') + + expect(result) + .to be_a(described_class) + .and have_attributes(session_id: '123', user:) + expect { session_activation.reload } + .to raise_error(ActiveRecord::RecordNotFound) end end diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb index 4e48772765571c..69686d507614ad 100644 --- a/spec/models/status_spec.rb +++ b/spec/models/status_spec.rb @@ -599,34 +599,6 @@ end end - describe 'before_validation' do - it 'sets account being replied to correctly over intermediary nodes' do - first_status = Fabricate(:status, account: bob) - intermediary = Fabricate(:status, thread: first_status, account: alice) - final = Fabricate(:status, thread: intermediary, account: alice) - - expect(final.in_reply_to_account_id).to eq bob.id - end - - it 'creates new conversation for stand-alone status' do - expect(described_class.create(account: alice, text: 'First').conversation_id).to_not be_nil - end - - it 'keeps conversation of parent node' do - parent = Fabricate(:status, text: 'First') - expect(described_class.create(account: alice, thread: parent, text: 'Response').conversation_id).to eq parent.conversation_id - end - - it 'sets `local` to true for status by local account' do - expect(described_class.create(account: alice, text: 'foo').local).to be true - end - - it 'sets `local` to false for status by remote account' do - alice.update(domain: 'example.com') - expect(described_class.create(account: alice, text: 'foo').local).to be false - end - end - describe 'Validations' do context 'with a remote account' do subject { Fabricate.build :status, account: remote_account } @@ -675,13 +647,49 @@ end end end - end - describe 'after_create' do - it 'saves ActivityPub uri as uri for local status' do - status = described_class.create(account: alice, text: 'foo') - status.reload - expect(status.uri).to start_with('https://') + describe 'Wiring up replies and conversations' do + it 'sets account being replied to correctly over intermediary nodes' do + first_status = Fabricate(:status, account: bob) + intermediary = Fabricate(:status, thread: first_status, account: alice) + final = Fabricate(:status, thread: intermediary, account: alice) + + expect(final.in_reply_to_account_id).to eq bob.id + end + + it 'creates new conversation for stand-alone status' do + new_status = nil + expect do + new_status = described_class.create(account: alice, text: 'First') + end.to change(Conversation, :count).by(1) + + expect(new_status.conversation_id).to_not be_nil + expect(new_status.conversation.parent_status_id).to eq new_status.id + end + + it 'keeps conversation of parent node' do + parent = Fabricate(:status, text: 'First') + expect(described_class.create(account: alice, thread: parent, text: 'Response').conversation_id).to eq parent.conversation_id + end + end + + describe 'Setting the `local` flag correctly' do + it 'sets `local` to true for status by local account' do + expect(described_class.create(account: alice, text: 'foo').local).to be true + end + + it 'sets `local` to false for status by remote account' do + alice.update(domain: 'example.com') + expect(described_class.create(account: alice, text: 'foo').local).to be false + end + end + + describe 'after_create' do + it 'saves ActivityPub uri as uri for local status' do + status = described_class.create(account: alice, text: 'foo') + status.reload + expect(status.uri).to start_with('https://') + end end end end diff --git a/spec/models/tag_feed_spec.rb b/spec/models/tag_feed_spec.rb index 939b9f3e82e30e..c4abe079e6e7f8 100644 --- a/spec/models/tag_feed_spec.rb +++ b/spec/models/tag_feed_spec.rb @@ -3,6 +3,11 @@ require 'rails_helper' RSpec.describe TagFeed do + before do + Setting.local_topic_feed_access = 'public' + Setting.remote_topic_feed_access = 'public' + end + describe '#get' do let(:account) { Fabricate(:account) } let(:tag_cats) { Fabricate(:tag, name: 'cats') } @@ -80,5 +85,311 @@ expect(results).to include(status) end end + + context 'when both local_topic_feed_access and remote_topic_feed_access are disabled' do + before do + Setting.local_topic_feed_access = 'disabled' + Setting.remote_topic_feed_access = 'disabled' + end + + context 'without local_only option' do + subject { described_class.new(tag_cats, viewer).get(20).map(&:id) } + + let(:viewer) { nil } + + let!(:remote_account) { Fabricate(:account, domain: 'test.com') } + let!(:local_status) { status_tagged_with_cats } + let!(:remote_status) { Fabricate(:status, account: remote_account, tags: [tag_cats]) } + + context 'without a viewer' do + let(:viewer) { nil } + + it 'returns an empty list' do + expect(subject).to be_empty + end + end + + context 'with a viewer' do + let(:viewer) { Fabricate(:account, username: 'viewer') } + + it 'returns an empty list' do + expect(subject).to be_empty + end + end + + context 'with a moderator as viewer' do + let(:viewer) { Fabricate(:moderator_user).account } + + it 'includes all expected statuses' do + expect(subject).to include(local_status.id) + expect(subject).to include(remote_status.id) + end + end + end + + context 'with a local_only option set' do + subject { described_class.new(tag_cats, viewer, local: true).get(20).map(&:id) } + + let!(:remote_account) { Fabricate(:account, domain: 'test.com') } + let!(:local_status) { status_tagged_with_cats } + let!(:remote_status) { Fabricate(:status, account: remote_account, tags: [tag_cats]) } + + context 'without a viewer' do + let(:viewer) { nil } + + it 'returns an empty list' do + expect(subject).to be_empty + end + end + + context 'with a viewer' do + let(:viewer) { Fabricate(:account, username: 'viewer') } + + it 'returns an empty list' do + expect(subject).to be_empty + end + end + + context 'with a moderator as viewer' do + let(:viewer) { Fabricate(:moderator_user).account } + + it 'does not include remote instances statuses' do + expect(subject).to include(local_status.id) + expect(subject).to_not include(remote_status.id) + end + end + end + + context 'with a remote_only option set' do + subject { described_class.new(tag_cats, viewer, remote: true).get(20).map(&:id) } + + let!(:remote_account) { Fabricate(:account, domain: 'test.com') } + let!(:local_status) { status_tagged_with_cats } + let!(:remote_status) { Fabricate(:status, account: remote_account, tags: [tag_cats]) } + + context 'without a viewer' do + let(:viewer) { nil } + + it 'returns an empty list' do + expect(subject).to be_empty + end + end + + context 'with a viewer' do + let(:viewer) { Fabricate(:account, username: 'viewer') } + + it 'returns an empty list' do + expect(subject).to be_empty + end + end + + context 'with a moderator as viewer' do + let(:viewer) { Fabricate(:moderator_user).account } + + it 'includes remote statuses only' do + expect(subject).to_not include(local_status.id) + expect(subject).to include(remote_status.id) + end + end + end + end + + context 'when local_topic_feed_access is disabled' do + before do + Setting.local_topic_feed_access = 'disabled' + end + + context 'without local_only option' do + subject { described_class.new(tag_cats, viewer).get(20).map(&:id) } + + let(:viewer) { nil } + + let!(:remote_account) { Fabricate(:account, domain: 'test.com') } + let!(:local_status) { status_tagged_with_cats } + let!(:remote_status) { Fabricate(:status, account: remote_account, tags: [tag_cats]) } + + context 'without a viewer' do + let(:viewer) { nil } + + it 'does not include local instances statuses' do + expect(subject).to_not include(local_status.id) + expect(subject).to include(remote_status.id) + end + end + + context 'with a viewer' do + let(:viewer) { Fabricate(:account, username: 'viewer') } + + it 'does not include local instances statuses' do + expect(subject).to_not include(local_status.id) + expect(subject).to include(remote_status.id) + end + end + end + + context 'with a local_only option set' do + subject { described_class.new(tag_cats, viewer, local: true).get(20).map(&:id) } + + let!(:remote_account) { Fabricate(:account, domain: 'test.com') } + let!(:local_status) { status_tagged_with_cats } + let!(:remote_status) { Fabricate(:status, account: remote_account, tags: [tag_cats]) } + + context 'without a viewer' do + let(:viewer) { nil } + + it 'returns an empty list' do + expect(subject).to be_empty + end + end + + context 'with a viewer' do + let(:viewer) { Fabricate(:account, username: 'viewer') } + + it 'returns an empty list' do + expect(subject).to be_empty + end + end + + context 'with a moderator as viewer' do + let(:viewer) { Fabricate(:moderator_user).account } + + it 'does not include remote instances statuses' do + expect(subject).to include(local_status.id) + expect(subject).to_not include(remote_status.id) + end + end + end + + context 'with a remote_only option set' do + subject { described_class.new(tag_cats, viewer, remote: true).get(20).map(&:id) } + + let!(:remote_account) { Fabricate(:account, domain: 'test.com') } + let!(:local_status) { status_tagged_with_cats } + let!(:remote_status) { Fabricate(:status, account: remote_account, tags: [tag_cats]) } + + context 'without a viewer' do + let(:viewer) { nil } + + it 'does not include local instances statuses' do + expect(subject).to_not include(local_status.id) + expect(subject).to include(remote_status.id) + end + end + + context 'with a viewer' do + let(:viewer) { Fabricate(:account, username: 'viewer') } + + it 'does not include local instances statuses' do + expect(subject).to_not include(local_status.id) + expect(subject).to include(remote_status.id) + end + end + end + end + + context 'when remote_topic_feed_access is disabled' do + before do + Setting.remote_topic_feed_access = 'disabled' + end + + context 'without local_only option' do + subject { described_class.new(tag_cats, viewer).get(20).map(&:id) } + + let(:viewer) { nil } + + let!(:remote_account) { Fabricate(:account, domain: 'test.com') } + let!(:local_status) { status_tagged_with_cats } + let!(:remote_status) { Fabricate(:status, account: remote_account, tags: [tag_cats]) } + + context 'without a viewer' do + let(:viewer) { nil } + + it 'does not include remote instances statuses' do + expect(subject).to include(local_status.id) + expect(subject).to_not include(remote_status.id) + end + end + + context 'with a viewer' do + let(:viewer) { Fabricate(:account, username: 'viewer') } + + it 'does not include remote instances statuses' do + expect(subject).to include(local_status.id) + expect(subject).to_not include(remote_status.id) + end + + it 'is not affected by personal domain blocks' do + viewer.block_domain!('test.com') + expect(subject).to include(local_status.id) + expect(subject).to_not include(remote_status.id) + end + end + end + + context 'with a local_only option set' do + subject { described_class.new(tag_cats, viewer, local: true).get(20).map(&:id) } + + let!(:remote_account) { Fabricate(:account, domain: 'test.com') } + let!(:local_status) { status_tagged_with_cats } + let!(:remote_status) { Fabricate(:status, account: remote_account, tags: [tag_cats]) } + + context 'without a viewer' do + let(:viewer) { nil } + + it 'does not include remote instances statuses' do + expect(subject).to include(local_status.id) + expect(subject).to_not include(remote_status.id) + end + end + + context 'with a viewer' do + let(:viewer) { Fabricate(:account, username: 'viewer') } + + it 'does not include remote instances statuses' do + expect(subject).to include(local_status.id) + expect(subject).to_not include(remote_status.id) + end + + it 'is not affected by personal domain blocks' do + viewer.block_domain!('test.com') + expect(subject).to include(local_status.id) + expect(subject).to_not include(remote_status.id) + end + end + end + + context 'with a remote_only option set' do + subject { described_class.new(tag_cats, viewer, remote: true).get(20).map(&:id) } + + let!(:remote_account) { Fabricate(:account, domain: 'test.com') } + let!(:local_status) { status_tagged_with_cats } + let!(:remote_status) { Fabricate(:status, account: remote_account, tags: [tag_cats]) } + + context 'without a viewer' do + let(:viewer) { nil } + + it 'returns an empty list' do + expect(subject).to be_empty + end + end + + context 'with a viewer' do + let(:viewer) { Fabricate(:account, username: 'viewer') } + + it 'returns an empty list' do + expect(subject).to be_empty + end + end + + context 'with a moderator as viewer' do + let(:viewer) { Fabricate(:moderator_user).account } + + it 'does not include local instances statuses' do + expect(subject).to_not include(local_status.id) + expect(subject).to include(remote_status.id) + end + end + end + end end end diff --git a/spec/models/tag_spec.rb b/spec/models/tag_spec.rb index 18378c000d2e61..79f8edf79a1c7a 100644 --- a/spec/models/tag_spec.rb +++ b/spec/models/tag_spec.rb @@ -57,6 +57,11 @@ def previous_name_error_message end end + describe 'Normalizations' do + it { is_expected.to normalize(:display_name).from('#HelloWorld').to('HelloWorld') } + it { is_expected.to normalize(:display_name).from('Hello❤️World').to('HelloWorld') } + end + describe 'HASHTAG_RE' do subject { described_class::HASHTAG_RE } @@ -84,6 +89,10 @@ def previous_name_error_message expect(subject.match('this is #aesthetic').to_s).to eq '#aesthetic' end + it 'matches #foo' do + expect(subject.match('this is #foo').to_s).to eq '#foo' + end + it 'matches digits at the start' do expect(subject.match('hello #3d').to_s).to eq '#3d' end @@ -247,17 +256,44 @@ def previous_name_error_message end describe '.find_or_create_by_names' do - let(:upcase_string) { 'abcABCabcABCやゆよ' } - let(:downcase_string) { 'abcabcabcabcやゆよ' } + context 'when called with a block' do + let(:upcase_string) { 'abcABCabcABCやゆよ' } + let(:downcase_string) { 'abcabcabcabcやゆよ' } + let(:args) { [upcase_string, downcase_string] } + + it 'runs the block once per normalized tag regardless of duplicates' do + expect { |block| described_class.find_or_create_by_names(args, &block) } + .to yield_control.once + end + end + + context 'when passed an array' do + it 'creates multiples tags' do + expect { described_class.find_or_create_by_names(%w(tips tags toes)) } + .to change(described_class, :count).by(3) + end + end + + context 'when passed a string' do + it 'creates a single tag' do + expect { described_class.find_or_create_by_names('test') } + .to change(described_class, :count).by(1) + end + end + end - it 'runs a passed block once per tag regardless of duplicates' do - count = 0 + describe '.find_or_create_by_names_race_condition' do + it 'handles simultaneous inserts of the same tag in different cases without error' do + tag_name_upper = 'Rails' + tag_name_lower = 'rails' - described_class.find_or_create_by_names([upcase_string, downcase_string]) do |_tag| - count += 1 + multi_threaded_execution(2) do |index| + described_class.find_or_create_by_names(index.zero? ? tag_name_upper : tag_name_lower) end - expect(count).to eq 1 + tags = described_class.where('lower(name) = ?', tag_name_lower.downcase) + expect(tags.count).to eq(1) + expect(tags.first.name.downcase).to eq(tag_name_lower.downcase) end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index a9ab15a956ed6c..4ea2e6a79ca649 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -39,6 +39,15 @@ end it { is_expected.to allow_value('admin@localhost').for(:email) } + + context 'when registration form time is present' do + subject { Fabricate.build :user } + + before { stub_const 'RegistrationFormTimeValidator::REGISTRATION_FORM_MIN_TIME', 3.seconds } + + it { is_expected.to allow_value(10.seconds.ago).for(:registration_form_time) } + it { is_expected.to_not allow_value(1.second.ago).for(:registration_form_time).against(:base) } + end end describe 'Normalizations' do @@ -382,12 +391,15 @@ let(:current_sign_in_at) { Time.zone.now } - before do + it 'disables user' do + allow(redis).to receive(:publish) + user.disable! - end - it 'disables user' do expect(user).to have_attributes(disabled: true) + + expect(redis) + .to have_received(:publish).with("timeline:system:#{user.account.id}", Oj.dump(event: :kill)).once end end diff --git a/spec/policies/account_policy_spec.rb b/spec/policies/account_policy_spec.rb index 8b2edb15b0196d..f877bded252c70 100644 --- a/spec/policies/account_policy_spec.rb +++ b/spec/policies/account_policy_spec.rb @@ -156,4 +156,36 @@ end end end + + permissions :feature? do + context 'when account is featureable?' do + it 'permits' do + expect(subject).to permit(alice, john) + end + end + + context 'when account is not featureable' do + before { allow(alice).to receive(:featureable?).and_return(false) } + + it 'denies' do + expect(subject).to_not permit(john, alice) + end + end + + context 'when account is blocked' do + before { alice.block!(john) } + + it 'denies' do + expect(subject).to_not permit(alice, john) + end + end + + context 'when account is blocking' do + before { john.block!(alice) } + + it 'denies' do + expect(subject).to_not permit(alice, john) + end + end + end end diff --git a/spec/policies/collection_policy_spec.rb b/spec/policies/collection_policy_spec.rb new file mode 100644 index 00000000000000..156e1a765728d5 --- /dev/null +++ b/spec/policies/collection_policy_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe CollectionPolicy do + let(:policy) { described_class } + let(:collection) { Fabricate(:collection) } + let(:owner) { collection.account } + let(:other_user) { Fabricate(:account) } + + permissions :index? do + it 'permits everyone to index' do + expect(policy).to permit(nil, Collection) + expect(policy).to permit(owner, Collection) + end + end + + permissions :show? do + it 'permits everyone to show' do + expect(policy).to permit(nil, collection) + expect(policy).to permit(owner, collection) + expect(policy).to permit(other_user, collection) + end + end + + permissions :create? do + it 'permits any user' do + expect(policy).to_not permit(nil, Collection) + + expect(policy).to permit(owner, Collection) + expect(policy).to permit(other_user, Collection) + end + end + + permissions :update?, :destroy? do + it 'only permits the owner' do + expect(policy).to_not permit(nil, collection) + expect(policy).to_not permit(other_user, collection) + + expect(policy).to permit(owner, collection) + end + end +end diff --git a/spec/policies/status_policy_spec.rb b/spec/policies/status_policy_spec.rb index fb2d6fff3586e3..65bdbd4bc42107 100644 --- a/spec/policies/status_policy_spec.rb +++ b/spec/policies/status_policy_spec.rb @@ -164,19 +164,19 @@ end it 'grants access when public and policy allows everyone' do - status.quote_approval_policy = Status::QUOTE_APPROVAL_POLICY_FLAGS[:public] + status.quote_approval_policy = InteractionPolicy::POLICY_FLAGS[:public] viewer = Fabricate(:account) expect(subject).to permit(viewer, status) end it 'denies access when public and policy allows followers but viewer is not one' do - status.quote_approval_policy = Status::QUOTE_APPROVAL_POLICY_FLAGS[:followers] + status.quote_approval_policy = InteractionPolicy::POLICY_FLAGS[:followers] viewer = Fabricate(:account) expect(subject).to_not permit(viewer, status) end it 'grants access when public and policy allows followers and viewer is one' do - status.quote_approval_policy = Status::QUOTE_APPROVAL_POLICY_FLAGS[:followers] + status.quote_approval_policy = InteractionPolicy::POLICY_FLAGS[:followers] viewer = Fabricate(:account) viewer.follow!(status.account) expect(subject).to permit(viewer, status) diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 3d3e556f353d3b..6be93ecb70ef99 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -30,7 +30,8 @@ # This needs to be defined before Rails is initialized STREAMING_PORT = ENV.fetch('TEST_STREAMING_PORT', '4020') -ENV['STREAMING_API_BASE_URL'] = "http://localhost:#{STREAMING_PORT}" +STREAMING_HOST = ENV.fetch('TEST_STREAMING_HOST', 'localhost') +ENV['STREAMING_API_BASE_URL'] = "http://#{STREAMING_HOST}:#{STREAMING_PORT}" require_relative '../config/environment' diff --git a/spec/requests/accounts_spec.rb b/spec/requests/accounts_spec.rb index 72913ebf22da07..cc2a5be7c5f5d8 100644 --- a/spec/requests/accounts_spec.rb +++ b/spec/requests/accounts_spec.rb @@ -5,6 +5,14 @@ RSpec.describe 'Accounts show response' do let(:account) { Fabricate(:account) } + context 'with numeric-based identifiers' do + it 'returns http success' do + get "/ap/users/#{account.id}" + + expect(response).to have_http_status(200) + end + end + context 'with an unapproved account' do before { account.user.update(approved: false) } diff --git a/spec/requests/activitypub/outboxes_spec.rb b/spec/requests/activitypub/outboxes_spec.rb index 22b2f97c071932..c109214635bdf2 100644 --- a/spec/requests/activitypub/outboxes_spec.rb +++ b/spec/requests/activitypub/outboxes_spec.rb @@ -215,7 +215,7 @@ def targets_public_collection?(item) def targets_followers_collection?(item, account) item[:to].include?( - account_followers_url(account, ActionMailer::Base.default_url_options) + ActivityPub::TagManager.instance.followers_uri_for(account) ) end end diff --git a/spec/requests/activitypub/replies_spec.rb b/spec/requests/activitypub/replies_spec.rb index 313cab2a448aba..4cd02b187d99bd 100644 --- a/spec/requests/activitypub/replies_spec.rb +++ b/spec/requests/activitypub/replies_spec.rb @@ -220,6 +220,12 @@ it_behaves_like 'allowed access' end + context 'with no signature and requesting the numeric AP path' do + subject { get ap_account_status_replies_path(account_id: status.account_id, status_id: status.id, only_other_accounts: only_other_accounts) } + + it_behaves_like 'allowed access' + end + context 'with signature' do subject { get account_status_replies_path(account_username: status.account.username, status_id: status.id, only_other_accounts: only_other_accounts), headers: nil, sign_with: remote_querier } diff --git a/spec/requests/api/v1/accounts/credentials_spec.rb b/spec/requests/api/v1/accounts/credentials_spec.rb index 84bea97e800c7d..f68ebbdb2285d8 100644 --- a/spec/requests/api/v1/accounts/credentials_spec.rb +++ b/spec/requests/api/v1/accounts/credentials_spec.rb @@ -3,10 +3,7 @@ require 'rails_helper' RSpec.describe 'credentials API' do - let(:user) { Fabricate(:user, account_attributes: { discoverable: false, locked: true, indexable: false }) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:scopes) { 'read:accounts write:accounts' } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', user_fabricator: :private_user, oauth_scopes: 'read:accounts write:accounts' describe 'GET /api/v1/accounts/verify_credentials' do subject do @@ -91,6 +88,11 @@ expect(response).to have_http_status(422) expect(response.content_type) .to start_with('application/json') + expect(response.parsed_body) + .to include( + error: /Validation failed/, + details: include(note: contain_exactly(include(error: 'ERR_TOO_LONG', description: /character limit/))) + ) end end diff --git a/spec/requests/api/v1/accounts/endorsements_spec.rb b/spec/requests/api/v1/accounts/endorsements_spec.rb index 6e0996a1f1bc4b..c8ad5297726c4c 100644 --- a/spec/requests/api/v1/accounts/endorsements_spec.rb +++ b/spec/requests/api/v1/accounts/endorsements_spec.rb @@ -3,10 +3,8 @@ require 'rails_helper' RSpec.describe 'Accounts Pins API' do - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:scopes) { 'write:accounts' } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', oauth_scopes: 'write:accounts' + let(:kevin) { Fabricate(:user) } before do diff --git a/spec/requests/api/v1/accounts/familiar_followers_spec.rb b/spec/requests/api/v1/accounts/familiar_followers_spec.rb index c698c2d6892a72..7c71f36a245857 100644 --- a/spec/requests/api/v1/accounts/familiar_followers_spec.rb +++ b/spec/requests/api/v1/accounts/familiar_followers_spec.rb @@ -3,10 +3,8 @@ require 'rails_helper' RSpec.describe 'Accounts Familiar Followers API' do - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:scopes) { 'read:follows' } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', oauth_scopes: 'read:follows' + let(:account) { Fabricate(:account) } describe 'GET /api/v1/accounts/familiar_followers' do diff --git a/spec/requests/api/v1/accounts/featured_tags_spec.rb b/spec/requests/api/v1/accounts/featured_tags_spec.rb index 54d92eb1cf0e42..f2aaf8dfd6884b 100644 --- a/spec/requests/api/v1/accounts/featured_tags_spec.rb +++ b/spec/requests/api/v1/accounts/featured_tags_spec.rb @@ -3,11 +3,9 @@ require 'rails_helper' RSpec.describe 'account featured tags API' do - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:scopes) { 'read:accounts' } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } - let(:account) { Fabricate(:account) } + include_context 'with API authentication', oauth_scopes: 'read:accounts' + + let(:account) { Fabricate(:account) } describe 'GET /api/v1/accounts/:id/featured_tags' do subject do diff --git a/spec/requests/api/v1/accounts/follower_accounts_spec.rb b/spec/requests/api/v1/accounts/follower_accounts_spec.rb index 61987fac1cc667..1f0779701b2049 100644 --- a/spec/requests/api/v1/accounts/follower_accounts_spec.rb +++ b/spec/requests/api/v1/accounts/follower_accounts_spec.rb @@ -3,10 +3,8 @@ require 'rails_helper' RSpec.describe 'API V1 Accounts FollowerAccounts' do - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:scopes) { 'read:accounts' } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', oauth_scopes: 'read:accounts' + let(:account) { Fabricate(:account) } let(:alice) { Fabricate(:account) } let(:bob) { Fabricate(:account) } diff --git a/spec/requests/api/v1/accounts/following_accounts_spec.rb b/spec/requests/api/v1/accounts/following_accounts_spec.rb index aae811467d2243..193cf2196f03f6 100644 --- a/spec/requests/api/v1/accounts/following_accounts_spec.rb +++ b/spec/requests/api/v1/accounts/following_accounts_spec.rb @@ -3,10 +3,8 @@ require 'rails_helper' RSpec.describe 'API V1 Accounts FollowingAccounts' do - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:scopes) { 'read:accounts' } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', oauth_scopes: 'read:accounts' + let(:account) { Fabricate(:account) } let(:alice) { Fabricate(:account) } let(:bob) { Fabricate(:account) } diff --git a/spec/requests/api/v1/accounts/identity_proofs_spec.rb b/spec/requests/api/v1/accounts/identity_proofs_spec.rb index ba04ed45b9f8b5..21b32fd3b40eec 100644 --- a/spec/requests/api/v1/accounts/identity_proofs_spec.rb +++ b/spec/requests/api/v1/accounts/identity_proofs_spec.rb @@ -3,10 +3,8 @@ require 'rails_helper' RSpec.describe 'Accounts Identity Proofs API' do - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:scopes) { 'read:accounts' } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', oauth_scopes: 'read:accounts' + let(:account) { Fabricate(:account) } describe 'GET /api/v1/accounts/identity_proofs' do diff --git a/spec/requests/api/v1/accounts/lists_spec.rb b/spec/requests/api/v1/accounts/lists_spec.rb index cb1ff6b9f28a7c..63b1dc7816ffd6 100644 --- a/spec/requests/api/v1/accounts/lists_spec.rb +++ b/spec/requests/api/v1/accounts/lists_spec.rb @@ -3,10 +3,8 @@ require 'rails_helper' RSpec.describe 'Accounts Lists API' do - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:scopes) { 'read:lists' } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', oauth_scopes: 'read:lists' + let(:account) { Fabricate(:account) } let(:list) { Fabricate(:list, account: user.account) } diff --git a/spec/requests/api/v1/accounts/lookup_spec.rb b/spec/requests/api/v1/accounts/lookup_spec.rb index 77c09c0902b502..a4d125a87cdfd2 100644 --- a/spec/requests/api/v1/accounts/lookup_spec.rb +++ b/spec/requests/api/v1/accounts/lookup_spec.rb @@ -3,10 +3,8 @@ require 'rails_helper' RSpec.describe 'Accounts Lookup API' do - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:scopes) { 'read:accounts' } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', oauth_scopes: 'read:accounts' + let(:account) { Fabricate(:account) } describe 'GET /api/v1/accounts/lookup' do diff --git a/spec/requests/api/v1/accounts/notes_spec.rb b/spec/requests/api/v1/accounts/notes_spec.rb index e616df1e6f4857..e70a465153386b 100644 --- a/spec/requests/api/v1/accounts/notes_spec.rb +++ b/spec/requests/api/v1/accounts/notes_spec.rb @@ -3,10 +3,8 @@ require 'rails_helper' RSpec.describe 'Accounts Notes API' do - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:scopes) { 'write:accounts' } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', oauth_scopes: 'write:accounts' + let(:account) { Fabricate(:account) } let(:comment) { 'foo' } diff --git a/spec/requests/api/v1/accounts/relationships_spec.rb b/spec/requests/api/v1/accounts/relationships_spec.rb index 52aeb0132807ad..d735e4d2410021 100644 --- a/spec/requests/api/v1/accounts/relationships_spec.rb +++ b/spec/requests/api/v1/accounts/relationships_spec.rb @@ -7,10 +7,7 @@ get '/api/v1/accounts/relationships', headers: headers, params: params end - let(:user) { Fabricate(:user) } - let(:scopes) { 'read:follows' } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', oauth_scopes: 'read:follows' let(:simon) { Fabricate(:account) } let(:lewis) { Fabricate(:account) } diff --git a/spec/requests/api/v1/accounts/search_spec.rb b/spec/requests/api/v1/accounts/search_spec.rb index dc24813e7392ad..5a01628c9ac0e1 100644 --- a/spec/requests/api/v1/accounts/search_spec.rb +++ b/spec/requests/api/v1/accounts/search_spec.rb @@ -3,10 +3,7 @@ require 'rails_helper' RSpec.describe 'Accounts Search API' do - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:scopes) { 'read:accounts' } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', oauth_scopes: 'read:accounts' describe 'GET /api/v1/accounts/search' do it 'returns http success' do diff --git a/spec/requests/api/v1/accounts/statuses_spec.rb b/spec/requests/api/v1/accounts/statuses_spec.rb index 1e219502874352..0a1daa0488b231 100644 --- a/spec/requests/api/v1/accounts/statuses_spec.rb +++ b/spec/requests/api/v1/accounts/statuses_spec.rb @@ -3,10 +3,7 @@ require 'rails_helper' RSpec.describe 'API V1 Accounts Statuses' do - let(:user) { Fabricate(:user) } - let(:scopes) { 'read:statuses' } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', oauth_scopes: 'read:statuses' describe 'GET /api/v1/accounts/:account_id/statuses' do it 'returns expected headers', :aggregate_failures do diff --git a/spec/requests/api/v1/accounts_spec.rb b/spec/requests/api/v1/accounts_spec.rb index 0e64915baf3140..e3416fc3376e3a 100644 --- a/spec/requests/api/v1/accounts_spec.rb +++ b/spec/requests/api/v1/accounts_spec.rb @@ -3,10 +3,7 @@ require 'rails_helper' RSpec.describe '/api/v1/accounts' do - let(:user) { Fabricate(:user) } - let(:scopes) { '' } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication' describe 'GET /api/v1/accounts?id[]=:id' do let(:account) { Fabricate(:account) } @@ -99,6 +96,28 @@ end end + context 'when missing username value' do + subject do + post '/api/v1/accounts', headers: headers, params: { password: '12345678', email: 'hello@world.tld', agreement: 'true' } + end + + it 'returns http unprocessable entity with username error message' do + expect { subject } + .to not_change(User, :count) + .and not_change(Account, :count) + + expect(response) + .to have_http_status(422) + expect(response.media_type) + .to eq('application/json') + expect(response.parsed_body) + .to include( + error: /Validation failed/, + details: include(username: contain_exactly(include(error: 'ERR_BLANK', description: /can't be blank/))) + ) + end + end + context 'when age verification is enabled' do before do Setting.min_age = 16 @@ -118,6 +137,11 @@ .to have_http_status(422) expect(response.content_type) .to start_with('application/json') + expect(response.parsed_body) + .to include( + error: /Validation failed/, + details: include(date_of_birth: contain_exactly(include(error: 'ERR_BELOW_LIMIT', description: /below the age limit/))) + ) end end diff --git a/spec/requests/api/v1/admin/account_actions_spec.rb b/spec/requests/api/v1/admin/account_actions_spec.rb index 4884dba9c79dfc..c1273f468dd1b0 100644 --- a/spec/requests/api/v1/admin/account_actions_spec.rb +++ b/spec/requests/api/v1/admin/account_actions_spec.rb @@ -3,11 +3,7 @@ require 'rails_helper' RSpec.describe 'Account actions' do - let(:role) { UserRole.find_by(name: 'Admin') } - let(:user) { Fabricate(:user, role: role) } - let(:scopes) { 'admin:write admin:write:accounts' } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', user_fabricator: :admin_user, oauth_scopes: 'admin:write admin:write:accounts' shared_examples 'a successful notification delivery' do it 'notifies the user about the action taken', :inline_jobs do diff --git a/spec/requests/api/v1/admin/accounts_spec.rb b/spec/requests/api/v1/admin/accounts_spec.rb index 6a681f9c5e5b2a..d94fcd31e40a39 100644 --- a/spec/requests/api/v1/admin/accounts_spec.rb +++ b/spec/requests/api/v1/admin/accounts_spec.rb @@ -3,11 +3,7 @@ require 'rails_helper' RSpec.describe 'Accounts' do - let(:role) { UserRole.find_by(name: 'Admin') } - let(:user) { Fabricate(:user, role: role) } - let(:scopes) { 'admin:read:accounts admin:write:accounts' } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', user_fabricator: :admin_user, oauth_scopes: 'admin:read:accounts admin:write:accounts' describe 'GET /api/v1/admin/accounts' do subject do diff --git a/spec/requests/api/v1/admin/canonical_email_blocks_spec.rb b/spec/requests/api/v1/admin/canonical_email_blocks_spec.rb index 25af0a26afef4d..b3af0a923d905d 100644 --- a/spec/requests/api/v1/admin/canonical_email_blocks_spec.rb +++ b/spec/requests/api/v1/admin/canonical_email_blocks_spec.rb @@ -3,11 +3,7 @@ require 'rails_helper' RSpec.describe 'Canonical Email Blocks' do - let(:role) { UserRole.find_by(name: 'Admin') } - let(:user) { Fabricate(:user, role: role) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:scopes) { 'admin:read:canonical_email_blocks admin:write:canonical_email_blocks' } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', user_fabricator: :admin_user, oauth_scopes: 'admin:read:canonical_email_blocks admin:write:canonical_email_blocks' describe 'GET /api/v1/admin/canonical_email_blocks' do subject do diff --git a/spec/requests/api/v1/admin/dimensions_spec.rb b/spec/requests/api/v1/admin/dimensions_spec.rb index 3a4cd91716ad5d..488a13ef0f2ff8 100644 --- a/spec/requests/api/v1/admin/dimensions_spec.rb +++ b/spec/requests/api/v1/admin/dimensions_spec.rb @@ -3,9 +3,8 @@ require 'rails_helper' RSpec.describe 'Admin Dimensions' do - let(:user) { Fabricate(:admin_user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', user_fabricator: :admin_user + let(:account) { Fabricate(:account) } describe 'GET /api/v1/admin/dimensions' do diff --git a/spec/requests/api/v1/admin/domain_allows_spec.rb b/spec/requests/api/v1/admin/domain_allows_spec.rb index fba1eb15d373fb..f3ae4076dbc242 100644 --- a/spec/requests/api/v1/admin/domain_allows_spec.rb +++ b/spec/requests/api/v1/admin/domain_allows_spec.rb @@ -3,11 +3,7 @@ require 'rails_helper' RSpec.describe 'Domain Allows' do - let(:role) { UserRole.find_by(name: 'Admin') } - let(:user) { Fabricate(:user, role: role) } - let(:scopes) { 'admin:read admin:write' } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', user_fabricator: :admin_user, oauth_scopes: 'admin:read admin:write' describe 'GET /api/v1/admin/domain_allows' do subject do diff --git a/spec/requests/api/v1/admin/domain_blocks_spec.rb b/spec/requests/api/v1/admin/domain_blocks_spec.rb index 0b01d04f9a0fdb..d532b0d25f980b 100644 --- a/spec/requests/api/v1/admin/domain_blocks_spec.rb +++ b/spec/requests/api/v1/admin/domain_blocks_spec.rb @@ -3,11 +3,7 @@ require 'rails_helper' RSpec.describe 'Domain Blocks' do - let(:role) { UserRole.find_by(name: 'Admin') } - let(:user) { Fabricate(:user, role: role) } - let(:scopes) { 'admin:read:domain_blocks admin:write:domain_blocks' } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', user_fabricator: :admin_user, oauth_scopes: 'admin:read:domain_blocks admin:write:domain_blocks' describe 'GET /api/v1/admin/domain_blocks' do subject do diff --git a/spec/requests/api/v1/admin/email_domain_blocks_spec.rb b/spec/requests/api/v1/admin/email_domain_blocks_spec.rb index 2788a45a4a2991..a75b5abde4fda5 100644 --- a/spec/requests/api/v1/admin/email_domain_blocks_spec.rb +++ b/spec/requests/api/v1/admin/email_domain_blocks_spec.rb @@ -3,12 +3,7 @@ require 'rails_helper' RSpec.describe 'Email Domain Blocks' do - let(:role) { UserRole.find_by(name: 'Admin') } - let(:user) { Fabricate(:user, role: role) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:account) { Fabricate(:account) } - let(:scopes) { 'admin:read:email_domain_blocks admin:write:email_domain_blocks' } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', user_fabricator: :admin_user, oauth_scopes: 'admin:read:email_domain_blocks admin:write:email_domain_blocks' describe 'GET /api/v1/admin/email_domain_blocks' do subject do diff --git a/spec/requests/api/v1/admin/ip_blocks_spec.rb b/spec/requests/api/v1/admin/ip_blocks_spec.rb index 59ef8d29665b00..a9d62752941353 100644 --- a/spec/requests/api/v1/admin/ip_blocks_spec.rb +++ b/spec/requests/api/v1/admin/ip_blocks_spec.rb @@ -3,11 +3,7 @@ require 'rails_helper' RSpec.describe 'IP Blocks' do - let(:role) { UserRole.find_by(name: 'Admin') } - let(:user) { Fabricate(:user, role: role) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:scopes) { 'admin:read:ip_blocks admin:write:ip_blocks' } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', user_fabricator: :admin_user, oauth_scopes: 'admin:read:ip_blocks admin:write:ip_blocks' describe 'GET /api/v1/admin/ip_blocks' do subject do @@ -97,7 +93,7 @@ expect(response.parsed_body) .to include( - ip: eq("#{ip_block.ip}/#{ip_block.ip.prefix}"), + ip: eq(ip_block.to_cidr), severity: eq(ip_block.severity.to_s) ) end @@ -216,7 +212,7 @@ expect(response.content_type) .to start_with('application/json') expect(response.parsed_body).to match(hash_including({ - ip: "#{ip_block.ip}/#{ip_block.ip.prefix}", + ip: ip_block.to_cidr, severity: 'sign_up_requires_approval', comment: 'Decreasing severity', })) diff --git a/spec/requests/api/v1/admin/measures_spec.rb b/spec/requests/api/v1/admin/measures_spec.rb index b55cd0f1b200cc..6c35da5656d044 100644 --- a/spec/requests/api/v1/admin/measures_spec.rb +++ b/spec/requests/api/v1/admin/measures_spec.rb @@ -3,10 +3,8 @@ require 'rails_helper' RSpec.describe 'Admin Measures' do - let(:user) { Fabricate(:admin_user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } - let(:account) { Fabricate(:account) } + include_context 'with API authentication', user_fabricator: :admin_user + let(:params) do { keys: %w(instance_accounts instance_follows instance_followers), diff --git a/spec/requests/api/v1/admin/reports_spec.rb b/spec/requests/api/v1/admin/reports_spec.rb index 987f0eda7fb816..54dd4c9c8c49fe 100644 --- a/spec/requests/api/v1/admin/reports_spec.rb +++ b/spec/requests/api/v1/admin/reports_spec.rb @@ -3,11 +3,7 @@ require 'rails_helper' RSpec.describe 'Reports' do - let(:role) { UserRole.find_by(name: 'Admin') } - let(:user) { Fabricate(:user, role: role) } - let(:scopes) { 'admin:read:reports admin:write:reports' } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', user_fabricator: :admin_user, oauth_scopes: 'admin:read:reports admin:write:reports' describe 'GET /api/v1/admin/reports' do subject do diff --git a/spec/requests/api/v1/admin/retention_spec.rb b/spec/requests/api/v1/admin/retention_spec.rb index 25e626e259329f..9c7be0981d3042 100644 --- a/spec/requests/api/v1/admin/retention_spec.rb +++ b/spec/requests/api/v1/admin/retention_spec.rb @@ -3,9 +3,8 @@ require 'rails_helper' RSpec.describe 'Admin Retention' do - let(:user) { Fabricate(:admin_user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', user_fabricator: :admin_user + let(:account) { Fabricate(:account) } describe 'GET /api/v1/admin/retention' do diff --git a/spec/requests/api/v1/admin/tags_spec.rb b/spec/requests/api/v1/admin/tags_spec.rb index 3a57432af78c48..c84536d1b57d78 100644 --- a/spec/requests/api/v1/admin/tags_spec.rb +++ b/spec/requests/api/v1/admin/tags_spec.rb @@ -3,10 +3,8 @@ require 'rails_helper' RSpec.describe 'Tags' do - let(:role) { UserRole.find_by(name: 'Admin') } - let(:user) { Fabricate(:user, role: role) } - let(:scopes) { 'admin:read admin:write' } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + include_context 'with API authentication', user_fabricator: :admin_user, oauth_scopes: 'admin:read admin:write' + let(:tag) { Fabricate(:tag) } let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } diff --git a/spec/requests/api/v1/admin/trends/links/links_spec.rb b/spec/requests/api/v1/admin/trends/links/links_spec.rb index 51e800734aaaec..e49c9a0709a51d 100644 --- a/spec/requests/api/v1/admin/trends/links/links_spec.rb +++ b/spec/requests/api/v1/admin/trends/links/links_spec.rb @@ -3,11 +3,7 @@ require 'rails_helper' RSpec.describe 'Links' do - let(:role) { UserRole.find_by(name: 'Admin') } - let(:user) { Fabricate(:user, role: role) } - let(:scopes) { 'admin:read admin:write' } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', user_fabricator: :admin_user, oauth_scopes: 'admin:read admin:write' describe 'GET /api/v1/admin/trends/links' do subject do diff --git a/spec/requests/api/v1/admin/trends/links/preview_card_providers_spec.rb b/spec/requests/api/v1/admin/trends/links/preview_card_providers_spec.rb index d46d0ff5555404..5fe6cac47f51d1 100644 --- a/spec/requests/api/v1/admin/trends/links/preview_card_providers_spec.rb +++ b/spec/requests/api/v1/admin/trends/links/preview_card_providers_spec.rb @@ -3,11 +3,8 @@ require 'rails_helper' RSpec.describe 'API V1 Admin Trends Links Preview Card Providers' do - let(:role) { UserRole.find_by(name: 'Admin') } - let(:user) { Fabricate(:user, role: role) } - let(:scopes) { 'admin:read admin:write' } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', user_fabricator: :admin_user, oauth_scopes: 'admin:read admin:write' + let(:account) { Fabricate(:account) } let(:preview_card_provider) { Fabricate(:preview_card_provider) } diff --git a/spec/requests/api/v1/admin/trends/statuses_spec.rb b/spec/requests/api/v1/admin/trends/statuses_spec.rb index c63d8d925c7c98..90a3d80a2a4e8e 100644 --- a/spec/requests/api/v1/admin/trends/statuses_spec.rb +++ b/spec/requests/api/v1/admin/trends/statuses_spec.rb @@ -3,13 +3,10 @@ require 'rails_helper' RSpec.describe 'API V1 Admin Trends Statuses' do - let(:role) { UserRole.find_by(name: 'Admin') } - let(:user) { Fabricate(:user, role: role) } - let(:scopes) { 'admin:read admin:write' } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + include_context 'with API authentication', user_fabricator: :admin_user, oauth_scopes: 'admin:read admin:write' + let(:account) { Fabricate(:account) } let(:status) { Fabricate(:status) } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } describe 'GET /api/v1/admin/trends/statuses' do it 'returns http success' do diff --git a/spec/requests/api/v1/admin/trends/tags_spec.rb b/spec/requests/api/v1/admin/trends/tags_spec.rb index 433cc6c5a6e5f3..750ee8975d3da6 100644 --- a/spec/requests/api/v1/admin/trends/tags_spec.rb +++ b/spec/requests/api/v1/admin/trends/tags_spec.rb @@ -3,13 +3,10 @@ require 'rails_helper' RSpec.describe 'API V1 Admin Trends Tags' do - let(:role) { UserRole.find_by(name: 'Admin') } - let(:user) { Fabricate(:user, role: role) } - let(:scopes) { 'admin:read admin:write' } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + include_context 'with API authentication', user_fabricator: :admin_user, oauth_scopes: 'admin:read admin:write' + let(:account) { Fabricate(:account) } let(:tag) { Fabricate(:tag) } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } describe 'GET /api/v1/admin/trends/tags' do it 'returns http success' do diff --git a/spec/requests/api/v1/announcements_spec.rb b/spec/requests/api/v1/announcements_spec.rb index 97a4442aa98855..b7ba47e1d2d873 100644 --- a/spec/requests/api/v1/announcements_spec.rb +++ b/spec/requests/api/v1/announcements_spec.rb @@ -3,10 +3,7 @@ require 'rails_helper' RSpec.describe 'API V1 Announcements' do - let(:user) { Fabricate(:user) } - let(:scopes) { 'read' } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', oauth_scopes: 'read' let!(:announcement) { Fabricate(:announcement) } diff --git a/spec/requests/api/v1/annual_reports_spec.rb b/spec/requests/api/v1/annual_reports_spec.rb index b9831d17e2c4f9..e79309145e3a54 100644 --- a/spec/requests/api/v1/annual_reports_spec.rb +++ b/spec/requests/api/v1/annual_reports_spec.rb @@ -3,9 +3,7 @@ require 'rails_helper' RSpec.describe 'API V1 Annual Reports' do - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication' describe 'GET /api/v1/annual_reports' do context 'when not authorized' do @@ -44,6 +42,157 @@ end end + describe 'GET /api/v1/annual_reports/:year/state' do + context 'when not authorized' do + it 'returns http unauthorized' do + get '/api/v1/annual_reports/2025/state' + + expect(response) + .to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') + end + end + + context 'with wrong scope' do + before do + get '/api/v1/annual_reports/2025/state', headers: headers + end + + it_behaves_like 'forbidden for wrong scope', 'write write:accounts' + end + + context 'with correct scope' do + let(:scopes) { 'read:accounts' } + + context 'when a report is already generated' do + before do + Fabricate(:generated_annual_report, account: user.account, year: 2025) + end + + it 'returns http success and available status' do + get '/api/v1/annual_reports/2025/state', headers: headers + + expect(response) + .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + + expect(response.parsed_body) + .to be_present + .and include(state: 'available') + end + end + + context 'when the feature is not enabled' do + before do + Setting.wrapstodon = false + end + + it 'returns http success and ineligible status' do + get '/api/v1/annual_reports/2025/state', headers: headers + + expect(response) + .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + + expect(response.parsed_body) + .to be_present + .and include(state: 'ineligible') + end + end + + context 'when the feature is enabled and time is within window' do + before do + travel_to Time.utc(2025, 12, 20) + + status = Fabricate(:status, visibility: :public, account: user.account) + status.tags << Fabricate(:tag) + end + + it 'returns http success and eligible status' do + get '/api/v1/annual_reports/2025/state', headers: headers + + expect(response) + .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + + expect(response.parsed_body) + .to be_present + .and include(state: 'eligible') + end + end + + context 'when the feature is enabled but we are out of the time window' do + before do + travel_to Time.utc(2025, 6, 20) + + status = Fabricate(:status, visibility: :public, account: user.account) + status.tags << Fabricate(:tag) + end + + it 'returns http success and ineligible status' do + get '/api/v1/annual_reports/2025/state', headers: headers + + expect(response) + .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + + expect(response.parsed_body) + .to be_present + .and include(state: 'ineligible') + end + end + end + end + + describe 'POST /api/v1/annual_reports/:id/generate' do + context 'when not authorized' do + it 'returns http unauthorized' do + post '/api/v1/annual_reports/2025/generate' + + expect(response) + .to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') + end + end + + context 'with wrong scope' do + before do + post '/api/v1/annual_reports/2025/generate', headers: headers + end + + it_behaves_like 'forbidden for wrong scope', 'read read:accounts' + end + + context 'with correct scope' do + let(:scopes) { 'write:accounts' } + + context 'when the feature is enabled and time is within window' do + before do + travel_to Time.utc(2025, 12, 20) + + status = Fabricate(:status, visibility: :public, account: user.account) + status.tags << Fabricate(:tag) + end + + it 'returns http accepted, create an async job and schedules a job' do + expect { post '/api/v1/annual_reports/2025/generate', headers: headers } + .to enqueue_sidekiq_job(GenerateAnnualReportWorker).with(user.account_id, 2025) + + expect(response) + .to have_http_status(202) + + expect(response.headers['Mastodon-Async-Refresh']).to be_present + end + end + end + end + describe 'POST /api/v1/annual_reports/:id/read' do context 'with correct scope' do let(:scopes) { 'write:accounts' } diff --git a/spec/requests/api/v1/apps/credentials_spec.rb b/spec/requests/api/v1/apps/credentials_spec.rb index 8c0292d8c3e92b..09363362d62b11 100644 --- a/spec/requests/api/v1/apps/credentials_spec.rb +++ b/spec/requests/api/v1/apps/credentials_spec.rb @@ -75,6 +75,33 @@ end end + context 'with client credentials' do + let(:application) { Fabricate(:application, scopes: 'read admin:write') } + let(:token) { Fabricate(:client_credentials_token, application: application, scopes: 'read admin:write') } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + it 'returns http success and returns app information' do + subject + + expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + + expect(response.parsed_body).to match( + a_hash_including( + id: token.application.id.to_s, + name: token.application.name, + website: token.application.website, + scopes: token.application.scopes.map(&:to_s), + redirect_uris: token.application.redirect_uris, + # Deprecated properties as of 4.3: + redirect_uri: token.application.redirect_uri.split.first, + vapid_key: Rails.configuration.x.vapid.public_key + ) + ) + end + end + context 'without an oauth token' do let(:headers) { {} } diff --git a/spec/requests/api/v1/blocks_spec.rb b/spec/requests/api/v1/blocks_spec.rb index 498cf932756de8..f02739d911379f 100644 --- a/spec/requests/api/v1/blocks_spec.rb +++ b/spec/requests/api/v1/blocks_spec.rb @@ -3,10 +3,7 @@ require 'rails_helper' RSpec.describe 'Blocks' do - let(:user) { Fabricate(:user) } - let(:scopes) { 'read:blocks' } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', oauth_scopes: 'read:blocks' describe 'GET /api/v1/blocks' do subject do diff --git a/spec/requests/api/v1/bookmarks_spec.rb b/spec/requests/api/v1/bookmarks_spec.rb index c78e6912365b51..bd61644f22d70c 100644 --- a/spec/requests/api/v1/bookmarks_spec.rb +++ b/spec/requests/api/v1/bookmarks_spec.rb @@ -3,10 +3,7 @@ require 'rails_helper' RSpec.describe 'Bookmarks' do - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:scopes) { 'read:bookmarks' } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', oauth_scopes: 'read:bookmarks' describe 'GET /api/v1/bookmarks' do subject do diff --git a/spec/requests/api/v1/conversations_spec.rb b/spec/requests/api/v1/conversations_spec.rb index 6e2ac1df53e0a8..6c928bc43293eb 100644 --- a/spec/requests/api/v1/conversations_spec.rb +++ b/spec/requests/api/v1/conversations_spec.rb @@ -3,10 +3,9 @@ require 'rails_helper' RSpec.describe 'API V1 Conversations' do + include_context 'with API authentication', oauth_scopes: 'read:statuses' + let!(:user) { Fabricate(:user, account_attributes: { username: 'alice' }) } - let(:scopes) { 'read:statuses' } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } let(:other) { Fabricate(:user) } diff --git a/spec/requests/api/v1/custom_emojis_spec.rb b/spec/requests/api/v1/custom_emojis_spec.rb index e860fbeb17c105..8b832540eff07d 100644 --- a/spec/requests/api/v1/custom_emojis_spec.rb +++ b/spec/requests/api/v1/custom_emojis_spec.rb @@ -3,9 +3,7 @@ require 'rails_helper' RSpec.describe 'Custom Emojis' do - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id) } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication' describe 'GET /api/v1/custom_emojis' do before do diff --git a/spec/requests/api/v1/directories_spec.rb b/spec/requests/api/v1/directories_spec.rb index 07e65f49b75b54..69bfdc834350c0 100644 --- a/spec/requests/api/v1/directories_spec.rb +++ b/spec/requests/api/v1/directories_spec.rb @@ -3,10 +3,9 @@ require 'rails_helper' RSpec.describe 'Directories API' do + include_context 'with API authentication', oauth_scopes: 'read:follows' + let(:user) { Fabricate(:user, confirmed_at: nil) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:scopes) { 'read:follows' } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } describe 'GET /api/v1/directories' do context 'with no params' do diff --git a/spec/requests/api/v1/domain_blocks_spec.rb b/spec/requests/api/v1/domain_blocks_spec.rb index 339f49fe761f75..0843c479837320 100644 --- a/spec/requests/api/v1/domain_blocks_spec.rb +++ b/spec/requests/api/v1/domain_blocks_spec.rb @@ -3,10 +3,7 @@ require 'rails_helper' RSpec.describe 'Domain blocks' do - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:scopes) { 'read:blocks write:blocks' } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', oauth_scopes: 'read:blocks write:blocks' describe 'GET /api/v1/domain_blocks' do subject do diff --git a/spec/requests/api/v1/endorsements_spec.rb b/spec/requests/api/v1/endorsements_spec.rb index 730ba6350cf05e..07ca476f3a89fc 100644 --- a/spec/requests/api/v1/endorsements_spec.rb +++ b/spec/requests/api/v1/endorsements_spec.rb @@ -3,9 +3,7 @@ require 'rails_helper' RSpec.describe 'Endorsements' do - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication' describe 'GET /api/v1/endorsements' do context 'when not authorized' do diff --git a/spec/requests/api/v1/favourites_spec.rb b/spec/requests/api/v1/favourites_spec.rb index 44d0239556b2ab..dcc0286e0b0313 100644 --- a/spec/requests/api/v1/favourites_spec.rb +++ b/spec/requests/api/v1/favourites_spec.rb @@ -3,10 +3,7 @@ require 'rails_helper' RSpec.describe 'Favourites' do - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:scopes) { 'read:favourites' } - let(:headers) { { Authorization: "Bearer #{token.token}" } } + include_context 'with API authentication', oauth_scopes: 'read:favourites' describe 'GET /api/v1/favourites' do subject do diff --git a/spec/requests/api/v1/featured_tags_spec.rb b/spec/requests/api/v1/featured_tags_spec.rb index 7a5f92cdfd418b..eafbe2185eb360 100644 --- a/spec/requests/api/v1/featured_tags_spec.rb +++ b/spec/requests/api/v1/featured_tags_spec.rb @@ -3,10 +3,7 @@ require 'rails_helper' RSpec.describe 'FeaturedTags' do - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:scopes) { 'read:accounts write:accounts' } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', oauth_scopes: 'read:accounts write:accounts' describe 'GET /api/v1/featured_tags' do context 'with wrong scope' do diff --git a/spec/requests/api/v1/filters_spec.rb b/spec/requests/api/v1/filters_spec.rb index 51f03cc04d44c7..32103a93429796 100644 --- a/spec/requests/api/v1/filters_spec.rb +++ b/spec/requests/api/v1/filters_spec.rb @@ -3,9 +3,7 @@ require 'rails_helper' RSpec.describe 'API V1 Filters' do - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication' describe 'GET /api/v1/filters' do let(:scopes) { 'read:filters' } diff --git a/spec/requests/api/v1/follow_requests_spec.rb b/spec/requests/api/v1/follow_requests_spec.rb index f0f73d38ad0d9e..7ebfce3d918053 100644 --- a/spec/requests/api/v1/follow_requests_spec.rb +++ b/spec/requests/api/v1/follow_requests_spec.rb @@ -3,10 +3,9 @@ require 'rails_helper' RSpec.describe 'Follow requests' do - let(:user) { Fabricate(:user, account_attributes: { locked: true }) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:scopes) { 'read:follows write:follows' } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', oauth_scopes: 'read:follows write:follows' + + let(:user) { Fabricate(:user, account_attributes: { locked: true }) } describe 'GET /api/v1/follow_requests' do subject do diff --git a/spec/requests/api/v1/followed_tags_spec.rb b/spec/requests/api/v1/followed_tags_spec.rb index b0191b523fc308..a19162c012f11b 100644 --- a/spec/requests/api/v1/followed_tags_spec.rb +++ b/spec/requests/api/v1/followed_tags_spec.rb @@ -3,10 +3,7 @@ require 'rails_helper' RSpec.describe 'Followed tags' do - let(:user) { Fabricate(:user) } - let(:scopes) { 'read:follows' } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', oauth_scopes: 'read:follows' describe 'GET /api/v1/followed_tags' do subject do diff --git a/spec/requests/api/v1/instance_spec.rb b/spec/requests/api/v1/instance_spec.rb index 62c90f55b2d64a..b65f2d10bf0fab 100644 --- a/spec/requests/api/v1/instance_spec.rb +++ b/spec/requests/api/v1/instance_spec.rb @@ -3,9 +3,7 @@ require 'rails_helper' RSpec.describe 'Instances' do - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id) } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication' describe 'GET /api/v1/instance' do context 'when not logged in' do diff --git a/spec/requests/api/v1/lists_spec.rb b/spec/requests/api/v1/lists_spec.rb index 226632c5ac2935..04a03998ad573b 100644 --- a/spec/requests/api/v1/lists_spec.rb +++ b/spec/requests/api/v1/lists_spec.rb @@ -3,10 +3,7 @@ require 'rails_helper' RSpec.describe 'Lists' do - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:scopes) { 'read:lists write:lists' } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', oauth_scopes: 'read:lists write:lists' describe 'GET /api/v1/lists' do subject do diff --git a/spec/requests/api/v1/markers_spec.rb b/spec/requests/api/v1/markers_spec.rb index d7cd78924bcec1..0e6ecc56855b23 100644 --- a/spec/requests/api/v1/markers_spec.rb +++ b/spec/requests/api/v1/markers_spec.rb @@ -3,10 +3,7 @@ require 'rails_helper' RSpec.describe 'API Markers' do - let(:user) { Fabricate(:user) } - let(:scopes) { 'read:statuses write:statuses' } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', oauth_scopes: 'read:statuses write:statuses' describe 'GET /api/v1/markers' do before do diff --git a/spec/requests/api/v1/media_spec.rb b/spec/requests/api/v1/media_spec.rb index 4d6e250207d022..347ff4b27979df 100644 --- a/spec/requests/api/v1/media_spec.rb +++ b/spec/requests/api/v1/media_spec.rb @@ -3,10 +3,7 @@ require 'rails_helper' RSpec.describe 'Media' do - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:scopes) { 'write:media' } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', oauth_scopes: 'write:media' describe 'GET /api/v1/media/:id' do subject do diff --git a/spec/requests/api/v1/mutes_spec.rb b/spec/requests/api/v1/mutes_spec.rb index 61e32cb9ae0452..4b94a5cb5a85c7 100644 --- a/spec/requests/api/v1/mutes_spec.rb +++ b/spec/requests/api/v1/mutes_spec.rb @@ -3,10 +3,7 @@ require 'rails_helper' RSpec.describe 'Mutes' do - let(:user) { Fabricate(:user) } - let(:scopes) { 'read:mutes' } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', oauth_scopes: 'read:mutes' describe 'GET /api/v1/mutes' do subject do diff --git a/spec/requests/api/v1/notifications_spec.rb b/spec/requests/api/v1/notifications_spec.rb index 0e8eb6ad3ba2e6..f2ff396795c59d 100644 --- a/spec/requests/api/v1/notifications_spec.rb +++ b/spec/requests/api/v1/notifications_spec.rb @@ -3,10 +3,9 @@ require 'rails_helper' RSpec.describe 'Notifications' do + include_context 'with API authentication', oauth_scopes: 'read:notifications write:notifications' + let(:user) { Fabricate(:user, account_attributes: { username: 'alice' }) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:scopes) { 'read:notifications write:notifications' } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } describe 'GET /api/v1/notifications/unread_count', :inline_jobs do subject do diff --git a/spec/requests/api/v1/polls_spec.rb b/spec/requests/api/v1/polls_spec.rb index c93231e1ee6a44..f01f112da9de26 100644 --- a/spec/requests/api/v1/polls_spec.rb +++ b/spec/requests/api/v1/polls_spec.rb @@ -3,10 +3,7 @@ require 'rails_helper' RSpec.describe 'Polls' do - let(:user) { Fabricate(:user) } - let(:scopes) { 'read:statuses' } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', oauth_scopes: 'read:statuses' describe 'GET /api/v1/polls/:id' do subject do diff --git a/spec/requests/api/v1/preferences_spec.rb b/spec/requests/api/v1/preferences_spec.rb index e03b9cf1087cd6..02d63c9d28ce88 100644 --- a/spec/requests/api/v1/preferences_spec.rb +++ b/spec/requests/api/v1/preferences_spec.rb @@ -3,9 +3,7 @@ require 'rails_helper' RSpec.describe 'Preferences' do - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication' describe 'GET /api/v1/preferences' do context 'when not authorized' do diff --git a/spec/requests/api/v1/profiles_spec.rb b/spec/requests/api/v1/profiles_spec.rb index de7a20b133cacd..131df7a278ed84 100644 --- a/spec/requests/api/v1/profiles_spec.rb +++ b/spec/requests/api/v1/profiles_spec.rb @@ -3,6 +3,8 @@ require 'rails_helper' RSpec.describe 'Deleting profile images' do + include_context 'with API authentication', oauth_scopes: 'write:accounts' + let(:account) do Fabricate( :account, @@ -10,9 +12,7 @@ header: fixture_file_upload('attachment.jpg', 'image/jpeg') ) end - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: account.user.id, scopes: scopes) } - let(:scopes) { 'write:accounts' } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + let(:user) { account.user } describe 'DELETE /api/v1/profile' do context 'when deleting an avatar' do diff --git a/spec/requests/api/v1/reports_spec.rb b/spec/requests/api/v1/reports_spec.rb index 1f113c649ee4b9..38d9f542c29b43 100644 --- a/spec/requests/api/v1/reports_spec.rb +++ b/spec/requests/api/v1/reports_spec.rb @@ -3,10 +3,7 @@ require 'rails_helper' RSpec.describe 'Reports' do - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:scopes) { 'write:reports' } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', oauth_scopes: 'write:reports' describe 'POST /api/v1/reports' do subject do diff --git a/spec/requests/api/v1/scheduled_status_spec.rb b/spec/requests/api/v1/scheduled_status_spec.rb index 3a1b81ce65c1d4..ad446257b40f47 100644 --- a/spec/requests/api/v1/scheduled_status_spec.rb +++ b/spec/requests/api/v1/scheduled_status_spec.rb @@ -3,9 +3,7 @@ require 'rails_helper' RSpec.describe 'Scheduled Statuses' do - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication' describe 'GET /api/v1/scheduled_statuses' do context 'when not authorized' do diff --git a/spec/requests/api/v1/statuses/interaction_policies_spec.rb b/spec/requests/api/v1/statuses/interaction_policies_spec.rb index aa447de17fa19d..aa5819cdd70518 100644 --- a/spec/requests/api/v1/statuses/interaction_policies_spec.rb +++ b/spec/requests/api/v1/statuses/interaction_policies_spec.rb @@ -46,7 +46,7 @@ context 'when changing the interaction policy' do it 'changes the interaction policy, returns the updated status, and schedules distribution jobs' do expect { subject } - .to change { status.reload.quote_approval_policy }.to(Status::QUOTE_APPROVAL_POLICY_FLAGS[:followers] << 16) + .to change { status.reload.quote_approval_policy }.to(InteractionPolicy::POLICY_FLAGS[:followers] << 16) expect(response).to have_http_status(200) expect(response.content_type) @@ -60,7 +60,7 @@ ) expect(DistributionWorker) - .to have_enqueued_sidekiq_job(status.id, { 'update' => true }) + .to have_enqueued_sidekiq_job(status.id, { 'update' => true, 'skip_notifications' => true }) expect(ActivityPub::StatusUpdateDistributionWorker) .to have_enqueued_sidekiq_job(status.id, { 'updated_at' => anything }) end diff --git a/spec/requests/api/v1/statuses/pins_spec.rb b/spec/requests/api/v1/statuses/pins_spec.rb index 05d8f570cc8455..66ed1510a48727 100644 --- a/spec/requests/api/v1/statuses/pins_spec.rb +++ b/spec/requests/api/v1/statuses/pins_spec.rb @@ -57,6 +57,21 @@ end end + context 'when the account is already at MAX status pins' do + before { StatusPinValidator::PIN_LIMIT.times { Fabricate(:status_pin, account: user.account) } } + + it 'returns http unprocessable entity' do + subject + + expect(response) + .to have_http_status(422) + expect(response.media_type) + .to eq('application/json') + expect(response.parsed_body) + .to include(error: /already pinned the maximum/) + end + end + context 'when the status does not exist' do it 'returns http not found' do post '/api/v1/statuses/-1/pin', headers: headers diff --git a/spec/requests/api/v1/statuses/quotes_spec.rb b/spec/requests/api/v1/statuses/quotes_spec.rb index 9456556ce99fad..01e9e17b07785f 100644 --- a/spec/requests/api/v1/statuses/quotes_spec.rb +++ b/spec/requests/api/v1/statuses/quotes_spec.rb @@ -17,7 +17,7 @@ let!(:accepted_quote) { Fabricate(:quote, quoted_status: status, state: :accepted) } let!(:rejected_quote) { Fabricate(:quote, quoted_status: status, state: :rejected) } let!(:pending_quote) { Fabricate(:quote, quoted_status: status, state: :pending) } - let!(:another_accepted_quote) { Fabricate(:quote, quoted_status: status, state: :accepted) } + let!(:accepted_private_quote) { Fabricate(:quote, status: Fabricate(:status, visibility: :private), quoted_status: status, state: :accepted) } context 'with an OAuth token' do let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } @@ -30,7 +30,7 @@ expect(response) .to have_http_status(200) .and include_pagination_headers( - prev: api_v1_status_quotes_url(limit: 2, since_id: another_accepted_quote.id), + prev: api_v1_status_quotes_url(limit: 2, since_id: accepted_private_quote.id), next: api_v1_status_quotes_url(limit: 2, max_id: accepted_quote.id) ) expect(response.content_type) @@ -39,7 +39,7 @@ expect(response.parsed_body) .to contain_exactly( include(id: accepted_quote.status.id.to_s), - include(id: another_accepted_quote.status.id.to_s) + include(id: accepted_private_quote.status.id.to_s) ) expect(response.parsed_body) @@ -52,12 +52,29 @@ context 'with a different user than the post owner' do let(:status) { Fabricate(:status) } - it 'returns http forbidden' do + it 'returns http success and statuses but not private ones' do subject - expect(response).to have_http_status(403) + expect(response) + .to have_http_status(200) + .and include_pagination_headers( + prev: api_v1_status_quotes_url(limit: 2, since_id: accepted_private_quote.id), + next: api_v1_status_quotes_url(limit: 2, max_id: accepted_quote.id) + ) expect(response.content_type) .to start_with('application/json') + + expect(response.parsed_body) + .to contain_exactly( + include(id: accepted_quote.status.id.to_s) + ) + + expect(response.parsed_body) + .to_not include( + include(id: rejected_quote.status.id.to_s), + include(id: pending_quote.status.id.to_s), + include(id: accepted_private_quote.id.to_s) + ) end end end diff --git a/spec/requests/api/v1/statuses/translations_spec.rb b/spec/requests/api/v1/statuses/translations_spec.rb index e316bd451b0b16..eb17d9f6a76294 100644 --- a/spec/requests/api/v1/statuses/translations_spec.rb +++ b/spec/requests/api/v1/statuses/translations_spec.rb @@ -28,20 +28,56 @@ context 'with an oauth token' do describe 'POST /api/v1/statuses/:status_id/translate' do - let(:status) { Fabricate(:status, account: user.account, text: 'Hola', language: 'es') } + subject { post "/api/v1/statuses/#{status.id}/translate", headers: headers } before do translation = TranslationService::Translation.new(text: 'Hello') service = instance_double(TranslationService::DeepL, translate: [translation]) allow(TranslationService).to receive_messages(configured?: true, configured: service) Rails.cache.write('translation_service/languages', { 'es' => ['en'] }) - post "/api/v1/statuses/#{status.id}/translate", headers: headers end - it 'returns http success' do - expect(response).to have_http_status(200) - expect(response.content_type) - .to start_with('application/json') + context 'with a public status' do + let(:status) { Fabricate(:status, account: user.account, text: 'Hola', language: 'es') } + + it 'returns http success' do + subject + + expect(response) + .to have_http_status(200) + expect(response.media_type) + .to eq('application/json') + end + end + + context 'with a public status marked with the same language as the current locale when translation backend cannot do same-language translation' do + let(:status) { Fabricate(:status, account: user.account, text: 'Esto está en español pero está marcado como inglés.', language: 'en') } + + it 'returns http forbidden with error message' do + subject + + expect(response) + .to have_http_status(403) + expect(response.media_type) + .to eq('application/json') + expect(response.parsed_body) + .to include(error: /not allowed/) + end + end + + context 'with a private status' do + let(:status) { Fabricate(:status, visibility: :private, account: user.account, text: 'Hola', language: 'es') } + + it 'returns http forbidden' do + subject + + expect(response) + .to have_http_status(403) + expect(response.media_type) + .to eq('application/json') + expect(response.parsed_body) + .to include(error: /not allowed/) + end end end end diff --git a/spec/requests/api/v1/statuses_spec.rb b/spec/requests/api/v1/statuses_spec.rb index 249abc2440250f..5cfd4eaa4865bc 100644 --- a/spec/requests/api/v1/statuses_spec.rb +++ b/spec/requests/api/v1/statuses_spec.rb @@ -4,10 +4,10 @@ RSpec.describe '/api/v1/statuses' do context 'with an oauth token' do - let(:user) { Fabricate(:user) } + include_context 'with API authentication' + let(:client_app) { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') } let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, application: client_app, scopes: scopes) } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } describe 'GET /api/v1/statuses?id[]=:id' do let(:status) { Fabricate(:status) } @@ -133,6 +133,21 @@ expect(response).to have_http_status(200) expect(response.content_type) .to start_with('application/json') + expect(response.headers['Mastodon-Async-Refresh']).to be_nil + end + + context 'with a remote status' do + let(:status) { Fabricate(:status, account: Fabricate(:account, domain: 'example.com'), created_at: 1.hour.ago, updated_at: 1.hour.ago) } + + it 'returns http success and queues discovery of new posts' do + expect { get "/api/v1/statuses/#{status.id}/context", headers: headers } + .to enqueue_sidekiq_job(ActivityPub::FetchAllRepliesWorker) + + expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.headers['Mastodon-Async-Refresh']).to match(/result_count=0/) + end end end @@ -228,7 +243,7 @@ end context 'with a self-quote post' do - let(:quoted_status) { Fabricate(:status, account: user.account) } + let!(:quoted_status) { Fabricate(:status, account: user.account) } let(:params) do { status: 'Hello world, this is a self-quote', @@ -236,6 +251,69 @@ } end + it 'returns a quote post, as well as rate limit headers', :aggregate_failures do + expect { subject }.to change(user.account.statuses, :count).by(1) + + expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body[:quote]).to be_present + expect(response.headers['X-RateLimit-Limit']).to eq RateLimiter::FAMILIES[:statuses][:limit].to_s + expect(response.headers['X-RateLimit-Remaining']).to eq (RateLimiter::FAMILIES[:statuses][:limit] - 1).to_s + end + end + + context 'with a quote to a non-mentioned user in a Private Mention' do + let!(:quoted_status) { Fabricate(:status, quote_approval_policy: InteractionPolicy::POLICY_FLAGS[:public] << 16) } + let(:params) do + { + status: 'Hello, this is a quote', + quoted_status_id: quoted_status.id, + visibility: :direct, + } + end + + it 'returns an error and does not create a post', :aggregate_failures do + expect { subject }.to_not change(user.account.statuses, :count) + + expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') + end + end + + context 'with a quote to a mentioned user in a Private Mention' do + let!(:quoted_status) { Fabricate(:status, quote_approval_policy: InteractionPolicy::POLICY_FLAGS[:public] << 16) } + let(:params) do + { + status: "Hello @#{quoted_status.account.acct}, this is a quote", + quoted_status_id: quoted_status.id, + visibility: :direct, + } + end + + it 'returns a quote post, as well as rate limit headers', :aggregate_failures do + expect { subject }.to change(user.account.statuses, :count).by(1) + + expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body[:quote]).to be_present + expect(response.headers['X-RateLimit-Limit']).to eq RateLimiter::FAMILIES[:statuses][:limit].to_s + expect(response.headers['X-RateLimit-Remaining']).to eq (RateLimiter::FAMILIES[:statuses][:limit] - 1).to_s + end + end + + context 'with a quote of a reblog' do + let(:quoted_status) { Fabricate(:status, quote_approval_policy: InteractionPolicy::POLICY_FLAGS[:public] << 16) } + let(:reblog) { Fabricate(:status, reblog: quoted_status) } + let(:params) do + { + status: 'Hello world, this is a self-quote', + quoted_status_id: reblog.id, + } + end + it 'returns a quote post, as well as rate limit headers', :aggregate_failures do subject @@ -243,6 +321,7 @@ expect(response.content_type) .to start_with('application/json') expect(response.parsed_body[:quote]).to be_present + expect(response.parsed_body[:quote][:quoted_status][:id]).to eq quoted_status.id.to_s expect(response.headers['X-RateLimit-Limit']).to eq RateLimiter::FAMILIES[:statuses][:limit].to_s expect(response.headers['X-RateLimit-Remaining']).to eq (RateLimiter::FAMILIES[:statuses][:limit] - 1).to_s end @@ -422,7 +501,7 @@ it 'updates the status', :aggregate_failures do expect { subject } - .to change { status.reload.quote_approval_policy }.to(Status::QUOTE_APPROVAL_POLICY_FLAGS[:public] << 16) + .to change { status.reload.quote_approval_policy }.to(InteractionPolicy::POLICY_FLAGS[:public] << 16) expect(response).to have_http_status(200) expect(response.content_type) diff --git a/spec/requests/api/v1/suggestions_spec.rb b/spec/requests/api/v1/suggestions_spec.rb index 0a32d8899bce43..13ac6e78558531 100644 --- a/spec/requests/api/v1/suggestions_spec.rb +++ b/spec/requests/api/v1/suggestions_spec.rb @@ -3,10 +3,7 @@ require 'rails_helper' RSpec.describe 'Suggestions' do - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:scopes) { 'read' } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', oauth_scopes: 'read' describe 'GET /api/v1/suggestions' do subject do diff --git a/spec/requests/api/v1/tags_spec.rb b/spec/requests/api/v1/tags_spec.rb index 5beda68db0a85b..42b9180880de82 100644 --- a/spec/requests/api/v1/tags_spec.rb +++ b/spec/requests/api/v1/tags_spec.rb @@ -3,10 +3,7 @@ require 'rails_helper' RSpec.describe 'Tags' do - let(:user) { Fabricate(:user) } - let(:scopes) { 'write:follows' } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', oauth_scopes: 'write:follows' describe 'GET /api/v1/tags/:id' do subject do diff --git a/spec/requests/api/v1/timelines/link_spec.rb b/spec/requests/api/v1/timelines/link_spec.rb index dc4d0d9506ea74..2a3ede9c488f7b 100644 --- a/spec/requests/api/v1/timelines/link_spec.rb +++ b/spec/requests/api/v1/timelines/link_spec.rb @@ -87,7 +87,7 @@ context 'when the instance does not allow public preview' do before do - Form::AdminSettings.new(timeline_preview: false).save + Form::AdminSettings.new(local_topic_feed_access: 'authenticated', remote_topic_feed_access: 'authenticated').save end it_behaves_like 'forbidden for wrong scope', 'profile' @@ -123,7 +123,8 @@ context 'when the instance allows public preview' do before do - Setting.timeline_preview = true + Setting.local_topic_feed_access = 'public' + Setting.remote_topic_feed_access = 'public' end context 'with an authorized user' do diff --git a/spec/requests/api/v1/timelines/public_spec.rb b/spec/requests/api/v1/timelines/public_spec.rb index 83d903935637f0..29a0f3b7063b6a 100644 --- a/spec/requests/api/v1/timelines/public_spec.rb +++ b/spec/requests/api/v1/timelines/public_spec.rb @@ -37,7 +37,8 @@ let(:expected_statuses) { [local_status, remote_status, media_status] } before do - Setting.timeline_preview = true + Setting.local_live_feed_access = 'public' + Setting.remote_live_feed_access = 'public' end it_behaves_like 'forbidden for wrong scope', 'profile' @@ -102,7 +103,7 @@ context 'when the instance does not allow public preview' do before do - Form::AdminSettings.new(timeline_preview: false).save + Form::AdminSettings.new(local_live_feed_access: 'authenticated', remote_live_feed_access: 'authenticated').save end it_behaves_like 'forbidden for wrong scope', 'profile' diff --git a/spec/requests/api/v1/timelines/tag_spec.rb b/spec/requests/api/v1/timelines/tag_spec.rb index 1d6844364d6161..0992d59c7990e7 100644 --- a/spec/requests/api/v1/timelines/tag_spec.rb +++ b/spec/requests/api/v1/timelines/tag_spec.rb @@ -14,7 +14,8 @@ end before do - Setting.timeline_preview = true + Setting.local_topic_feed_access = 'public' + Setting.remote_topic_feed_access = 'public' end shared_examples 'a successful request to the tag timeline' do @@ -103,7 +104,7 @@ context 'when the instance does not allow public preview' do before do - Form::AdminSettings.new(timeline_preview: false).save + Form::AdminSettings.new(local_topic_feed_access: 'authenticated', remote_topic_feed_access: 'authenticated').save end it_behaves_like 'forbidden for wrong scope', 'profile' diff --git a/spec/requests/api/v1_alpha/async_refreshes_spec.rb b/spec/requests/api/v1_alpha/async_refreshes_spec.rb index 0cd85cf99bf48c..8b29b7599c6959 100644 --- a/spec/requests/api/v1_alpha/async_refreshes_spec.rb +++ b/spec/requests/api/v1_alpha/async_refreshes_spec.rb @@ -3,9 +3,8 @@ require 'rails_helper' RSpec.describe 'AsyncRefreshes' do - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication' + let(:job) { AsyncRefresh.new('test_job') } describe 'GET /api/v1_alpha/async_refreshes/:id' do diff --git a/spec/requests/api/v1_alpha/collection_items_spec.rb b/spec/requests/api/v1_alpha/collection_items_spec.rb new file mode 100644 index 00000000000000..5c44a7edf80e80 --- /dev/null +++ b/spec/requests/api/v1_alpha/collection_items_spec.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Api::V1Alpha::CollectionItems', feature: :collections do + include_context 'with API authentication', oauth_scopes: 'read:collections write:collections' + + describe 'POST /api/v1_alpha/collections/:collection_id/items' do + subject do + post "/api/v1_alpha/collections/#{collection.id}/items", headers: headers, params: params + end + + let(:collection) { Fabricate(:collection, account: user.account) } + let(:params) { {} } + + it_behaves_like 'forbidden for wrong scope', 'read' + + context 'when user is owner of the collection' do + context 'with valid params' do + let(:other_account) { Fabricate(:account) } + let(:params) { { account_id: other_account.id } } + + it 'creates a collection item and returns http success' do + expect do + subject + end.to change(collection.collection_items, :count).by(1) + + expect(response).to have_http_status(200) + end + end + + context 'with invalid params' do + it 'returns http unprocessable content' do + expect do + subject + end.to_not change(CollectionItem, :count) + + expect(response).to have_http_status(422) + end + end + end + + context 'when user is not the owner of the collection' do + let(:collection) { Fabricate(:collection) } + let(:other_account) { Fabricate(:account) } + let(:params) { { account_id: other_account.id } } + + it 'returns http forbidden' do + subject + + expect(response).to have_http_status(403) + end + end + end + + describe 'DELETE /api/v1_alpha/collections/:collection_id/items/:id' do + subject do + delete "/api/v1_alpha/collections/#{collection.id}/items/#{item.id}", headers: headers + end + + let(:collection) { Fabricate(:collection, account: user.account) } + let(:item) { Fabricate(:collection_item, collection:) } + + it_behaves_like 'forbidden for wrong scope', 'read' + + context 'when user is owner of the collection' do + context 'when item belongs to collection' do + it 'deletes the collection item and returns http success' do + item # Make sure this exists before calling the API + + expect do + subject + end.to change(collection.collection_items, :count).by(-1) + + expect(response).to have_http_status(200) + end + end + + context 'when item does not belong to to collection' do + let(:item) { Fabricate(:collection_item) } + + it 'returns http not found' do + item # Make sure this exists before calling the API + + expect do + subject + end.to_not change(CollectionItem, :count) + + expect(response).to have_http_status(404) + end + end + end + + context 'when user is not the owner of the collection' do + let(:collection) { Fabricate(:collection) } + + it 'returns http forbidden' do + subject + + expect(response).to have_http_status(403) + end + end + end +end diff --git a/spec/requests/api/v1_alpha/collections_spec.rb b/spec/requests/api/v1_alpha/collections_spec.rb new file mode 100644 index 00000000000000..3921fabfde820d --- /dev/null +++ b/spec/requests/api/v1_alpha/collections_spec.rb @@ -0,0 +1,249 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Api::V1Alpha::Collections', feature: :collections do + include_context 'with API authentication', oauth_scopes: 'read:collections write:collections' + + describe 'GET /api/v1_alpha/accounts/:account_id/collections' do + subject do + get "/api/v1_alpha/accounts/#{account.id}/collections", headers: headers, params: params + end + + let(:params) { {} } + + let(:account) { Fabricate(:account) } + + before { Fabricate.times(3, :collection, account:) } + + it 'returns all collections for the given account and http success' do + subject + + expect(response).to have_http_status(200) + expect(response.parsed_body.size).to eq 3 + end + + context 'with limit param' do + let(:params) { { limit: '1' } } + + it 'returns only a single result' do + subject + + expect(response).to have_http_status(200) + expect(response.parsed_body.size).to eq 1 + + expect(response) + .to include_pagination_headers( + next: api_v1_alpha_account_collections_url(account, limit: 1, offset: 1) + ) + end + end + + context 'with limit and offset params' do + let(:params) { { limit: '1', offset: '1' } } + + it 'returns the correct result and headers' do + subject + + expect(response).to have_http_status(200) + expect(response.parsed_body.size).to eq 1 + + expect(response) + .to include_pagination_headers( + prev: api_v1_alpha_account_collections_url(account, limit: 1, offset: 0), + next: api_v1_alpha_account_collections_url(account, limit: 1, offset: 2) + ) + end + end + end + + describe 'GET /api/v1_alpha/collections/:id' do + subject do + get "/api/v1_alpha/collections/#{collection.id}", headers: headers + end + + let(:collection) { Fabricate(:collection) } + let!(:items) { Fabricate.times(2, :collection_item, collection:) } + + shared_examples 'unfiltered, successful request' do + it 'includes all items in the response' do + subject + + expect(response).to have_http_status(200) + expect(response.parsed_body[:items].size).to eq 2 + end + end + + context 'when user is not signed in' do + let(:headers) { {} } + + it_behaves_like 'unfiltered, successful request' + end + + context 'when user is signed in' do + context 'when the user has not blocked or muted anyone' do + it_behaves_like 'unfiltered, successful request' + end + + context 'when the user has blocked an account' do + before do + user.account.block!(items.first.account) + end + + it 'only includes the non-blocked account in the response' do + subject + + expect(response).to have_http_status(200) + expect(response.parsed_body[:items].size).to eq 1 + expect(response.parsed_body[:items][0]['position']).to eq items.last.position + end + end + end + end + + describe 'POST /api/v1_alpha/collections' do + subject do + post '/api/v1_alpha/collections', headers: headers, params: params + end + + let(:params) { {} } + + it_behaves_like 'forbidden for wrong scope', 'read' + + context 'with valid params' do + let(:params) do + { + name: 'Low-traffic bots', + description: 'Really nice bots, please follow', + sensitive: '0', + discoverable: '1', + } + end + + it 'creates a collection and returns http success' do + expect do + subject + end.to change(Collection, :count).by(1) + + expect(response).to have_http_status(200) + end + end + + context 'with invalid params' do + it 'returns http unprocessable content and detailed errors' do + expect do + subject + end.to_not change(Collection, :count) + + expect(response).to have_http_status(422) + expect(response.parsed_body).to include({ + 'error' => a_hash_including({ + 'details' => a_hash_including({ + 'name' => [{ 'error' => 'ERR_BLANK', 'description' => "can't be blank" }], + 'description' => [{ 'error' => 'ERR_BLANK', 'description' => "can't be blank" }], + }), + }), + }) + end + end + end + + describe 'PATCH /api/v1_alpha/collections/:id' do + subject do + patch "/api/v1_alpha/collections/#{collection.id}", headers: headers, params: params + end + + let(:collection) { Fabricate(:collection) } + let(:params) { {} } + + context 'when user is not owner' do + it 'returns http forbidden' do + subject + + expect(response).to have_http_status(403) + end + end + + context 'when user is the owner' do + let(:collection) do + Fabricate(:collection, + account: user.account, + name: 'Pople to follow', + description: 'Cool pople', + sensitive: true, + discoverable: false) + end + + it_behaves_like 'forbidden for wrong scope', 'read:collections' + + context 'with valid params' do + let(:params) do + { + name: 'People to follow', + description: 'Cool people', + sensitive: '0', + discoverable: '1', + } + end + + it 'updates the collection and returns http success' do + subject + collection.reload + + expect(response).to have_http_status(200) + expect(collection.name).to eq 'People to follow' + expect(collection.description).to eq 'Cool people' + expect(collection.sensitive).to be false + expect(collection.discoverable).to be true + end + end + + context 'with invalid params' do + let(:params) { { name: '' } } + + it 'returns http unprocessable content and detailed errors' do + subject + + expect(response).to have_http_status(422) + expect(response.parsed_body).to include({ + 'error' => a_hash_including({ + 'details' => a_hash_including({ + 'name' => [{ 'error' => 'ERR_BLANK', 'description' => "can't be blank" }], + }), + }), + }) + end + end + end + end + + describe 'DELETE /api/v1_alpha/collections/:id' do + subject do + delete "/api/v1_alpha/collections/#{collection.id}", headers: headers + end + + let(:collection) { Fabricate(:collection) } + + context 'when user is not owner' do + it 'returns http forbidden' do + subject + + expect(response).to have_http_status(403) + end + end + + context 'when user is the owner' do + let(:collection) { Fabricate(:collection, account: user.account) } + + it_behaves_like 'forbidden for wrong scope', 'read:collections' + + it 'deletes the collection and returns http success' do + collection + + expect { subject }.to change(Collection, :count).by(-1) + + expect(response).to have_http_status(200) + end + end + end +end diff --git a/spec/requests/api/v2/filters_spec.rb b/spec/requests/api/v2/filters_spec.rb index 304afc7bd8dad6..cfa607cff06cfb 100644 --- a/spec/requests/api/v2/filters_spec.rb +++ b/spec/requests/api/v2/filters_spec.rb @@ -3,10 +3,7 @@ require 'rails_helper' RSpec.describe 'Filters' do - let(:user) { Fabricate(:user) } - let(:scopes) { 'read:filters write:filters' } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', oauth_scopes: 'read:filters write:filters' shared_examples 'unauthorized for invalid token' do let(:headers) { { 'Authorization' => '' } } diff --git a/spec/requests/api/v2/instance_spec.rb b/spec/requests/api/v2/instance_spec.rb index 3350d68f7f7d6f..467b5e530c231e 100644 --- a/spec/requests/api/v2/instance_spec.rb +++ b/spec/requests/api/v2/instance_spec.rb @@ -3,9 +3,7 @@ require 'rails_helper' RSpec.describe 'Instances' do - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id) } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication' describe 'GET /api/v2/instance' do context 'when logged out' do @@ -44,6 +42,26 @@ end end + context 'when wrapstodon is enabled' do + before do + travel_to Time.utc(2025, 12, 20) + end + + it 'returns http success and the wrapstodon year' do + get api_v2_instance_path + + expect(response) + .to have_http_status(200) + + expect(response.content_type) + .to start_with('application/json') + + expect(response.parsed_body) + .to be_present + .and include(wrapstodon: 2025) + end + end + def include_configuration_limits include( configuration: include( diff --git a/spec/requests/api/v2/media_spec.rb b/spec/requests/api/v2/media_spec.rb index 18ebb9cddae441..04e48bc02c3942 100644 --- a/spec/requests/api/v2/media_spec.rb +++ b/spec/requests/api/v2/media_spec.rb @@ -3,10 +3,7 @@ require 'rails_helper' RSpec.describe 'Media API', :attachment_processing do - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:scopes) { 'write' } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', oauth_scopes: 'write' describe 'POST /api/v2/media' do context 'when small media format attachment is processed immediately' do diff --git a/spec/requests/api/v2/notifications_spec.rb b/spec/requests/api/v2/notifications_spec.rb index 69feb6cb6e0a08..4b4aa1b475147c 100644 --- a/spec/requests/api/v2/notifications_spec.rb +++ b/spec/requests/api/v2/notifications_spec.rb @@ -3,10 +3,9 @@ require 'rails_helper' RSpec.describe 'Notifications' do + include_context 'with API authentication', oauth_scopes: 'read:notifications write:notifications' + let(:user) { Fabricate(:user, account_attributes: { username: 'alice' }) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:scopes) { 'read:notifications write:notifications' } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } describe 'GET /api/v2/notifications/unread_count', :inline_jobs do subject do diff --git a/spec/requests/api/v2/search_spec.rb b/spec/requests/api/v2/search_spec.rb index 6beab4c8c7dbc4..c60861b48f0c9a 100644 --- a/spec/requests/api/v2/search_spec.rb +++ b/spec/requests/api/v2/search_spec.rb @@ -4,10 +4,7 @@ RSpec.describe 'Search API' do context 'with token' do - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:scopes) { 'read:search' } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', oauth_scopes: 'read:search' describe 'GET /api/v2/search' do let!(:bob) { Fabricate(:account, username: 'bob_test') } diff --git a/spec/requests/api/v2/suggestions_spec.rb b/spec/requests/api/v2/suggestions_spec.rb index 578bf1b61b978d..dd876046bcc055 100644 --- a/spec/requests/api/v2/suggestions_spec.rb +++ b/spec/requests/api/v2/suggestions_spec.rb @@ -3,10 +3,7 @@ require 'rails_helper' RSpec.describe 'Suggestions API' do - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:scopes) { 'read' } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', oauth_scopes: 'read' describe 'GET /api/v2/suggestions' do let(:bob) { Fabricate(:account) } diff --git a/spec/requests/api/web/embeds_spec.rb b/spec/requests/api/web/embeds_spec.rb index 3cc2f977f87fb5..ad71172e87d505 100644 --- a/spec/requests/api/web/embeds_spec.rb +++ b/spec/requests/api/web/embeds_spec.rb @@ -65,9 +65,7 @@ end context 'with an API token' do - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') } - let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + include_context 'with API authentication', oauth_scopes: 'read' context 'when the requested status is local' do let(:id) { status.id } diff --git a/spec/requests/link_headers_spec.rb b/spec/requests/link_headers_spec.rb index 5010f7f221f5ea..c782744f016da4 100644 --- a/spec/requests/link_headers_spec.rb +++ b/spec/requests/link_headers_spec.rb @@ -11,7 +11,7 @@ expect(response) .to have_http_link_header(webfinger_url(resource: account.to_webfinger_s)).for(rel: 'lrdd', type: 'application/jrd+json') - .and have_http_link_header(account_url(account)).for(rel: 'alternate', type: 'application/activity+json') + .and have_http_link_header(ActivityPub::TagManager.instance.uri_for(account)).for(rel: 'alternate', type: 'application/activity+json') end end end diff --git a/spec/requests/media_spec.rb b/spec/requests/media_spec.rb index a448a87492e972..523c4689d6a5c5 100644 --- a/spec/requests/media_spec.rb +++ b/spec/requests/media_spec.rb @@ -87,4 +87,17 @@ end end end + + describe 'GET /media/:medium_id/player' do + context 'when media type is not large format type' do + let(:media) { Fabricate :media_attachment } + + it 'responds with not found' do + get medium_player_path(media) + + expect(response) + .to have_http_status(404) + end + end + end end diff --git a/spec/requests/oauth/userinfo_spec.rb b/spec/requests/oauth/userinfo_spec.rb index 0aa5a21120007c..280a3a35418244 100644 --- a/spec/requests/oauth/userinfo_spec.rb +++ b/spec/requests/oauth/userinfo_spec.rb @@ -19,7 +19,7 @@ expect(response.content_type).to start_with('application/json') expect(response.parsed_body).to include({ iss: root_url, - sub: account_url(account), + sub: ActivityPub::TagManager.instance.uri_for(account), name: account.display_name, preferred_username: account.username, profile: short_account_url(account), diff --git a/spec/requests/signature_verification_spec.rb b/spec/requests/signature_verification_spec.rb index eccb2babc98050..3119138a0a01d4 100644 --- a/spec/requests/signature_verification_spec.rb +++ b/spec/requests/signature_verification_spec.rb @@ -352,34 +352,7 @@ end end - # TODO: Remove when feature is enabled - context 'with an HTTP Message Signature (final RFC version) when support is disabled' do - before { Fabricate(:account, domain: 'remote.domain', uri: 'https://remote.domain/users/bob', private_key: nil, public_key: actor_keypair.public_key.to_pem) } - - context 'with a valid signature on a GET request' do - let(:signature_input) do - 'sig1=("@method" "@target-uri");created=1703066400;keyid="https://remote.domain/users/bob#main-key"' - end - let(:signature_header) do - 'sig1=:WfM6q/qBqhUyqPUDt9metjadJGtLLpmMTBzk/t+R3byKe4/TGAXC6vBB/M6NsD5qv8GCmQGtisCMQxJQO0IGODGzi+Jv+eqDJ50agMVXNV6nUOzY44c4/XTPoI98qyx1oEMa4Hefy3vSYKq96iDVAc+RDLCMTeGP3wn9wizjD1SNmU0RZI1bTB+eCkywMP9mM5zXzUOYF+Qkuf+WdEpPR1XUGPlnqfdvPalcKVfaI/VThBjI91D/lmUGoa69x4EBEHM+aJmW6086e7/dVh+FndKkdGfXslZXFZKi2flTGQZgEWLn948SqAaJQROkJg8B14Sb1NONS1qZBhK3Mum8Pg==:' # rubocop:disable Layout/LineLength - end - - it 'cannot verify signature', :aggregate_failures do - get '/activitypub/signature_required', headers: { - 'Host' => 'www.example.com', - 'Signature-Input' => signature_input, - 'Signature' => signature_header, - } - - expect(response).to have_http_status(401) - expect(response.parsed_body).to match( - error: 'Error parsing signature parameters' - ) - end - end - end - - context 'with an HTTP Message Signature (final RFC version)', feature: :http_message_signatures do + context 'with an HTTP Message Signature (final RFC version)' do context 'with a known account' do let!(:actor) { Fabricate(:account, domain: 'remote.domain', uri: 'https://remote.domain/users/bob', private_key: nil, public_key: actor_keypair.public_key.to_pem) } diff --git a/spec/requests/statuses_spec.rb b/spec/requests/statuses_spec.rb index a5e4482dfa6ae2..d12f4f28cf3233 100644 --- a/spec/requests/statuses_spec.rb +++ b/spec/requests/statuses_spec.rb @@ -360,6 +360,30 @@ def sign_in_with_session(user) .to include(content: include(status.text)) end end + + context 'with JSON and querying the new paths' do + subject do + get ap_account_status_path(account_id: status.account_id, id: status.id), + headers: { 'Accept' => 'application/activity+json' }, + sign_with: remote_account + end + + let(:format) { 'json' } + + it 'renders ActivityPub Note object successfully', :aggregate_failures do + subject + + expect(response) + .to have_http_status(200) + .and have_cacheable_headers.with_vary('Accept, Accept-Language, Cookie') + expect(response.headers).to include( + 'Content-Type' => include('application/activity+json'), + 'Link' => include('activity+json') + ) + expect(response.parsed_body) + .to include(content: include(status.text)) + end + end end context 'when status has private visibility' do diff --git a/spec/requests/well_known/webfinger_spec.rb b/spec/requests/well_known/webfinger_spec.rb index 0c4a3c03474111..593cecbb896cd5 100644 --- a/spec/requests/well_known/webfinger_spec.rb +++ b/spec/requests/well_known/webfinger_spec.rb @@ -27,7 +27,7 @@ expect(response.parsed_body) .to include( subject: eq(alice.to_webfinger_s), - aliases: include("https://#{Rails.configuration.x.local_domain}/@alice", "https://#{Rails.configuration.x.local_domain}/users/alice") + aliases: include("https://#{Rails.configuration.x.local_domain}/@alice", ActivityPub::TagManager.instance.uri_for(alice)) ) end end diff --git a/spec/requests/wrapstodon_spec.rb b/spec/requests/wrapstodon_spec.rb new file mode 100644 index 00000000000000..62bd4dacd91759 --- /dev/null +++ b/spec/requests/wrapstodon_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Wrapstodon' do + let(:generated_annual_report) { AnnualReport.new(user.account, Time.current.year).generate } + let(:user) { Fabricate :user } + + describe 'GET /@:account_username/wrapstodon/:year/:share_key' do + context 'when share_key is invalid' do + it 'returns not found' do + get public_wrapstodon_path(account_username: user.account.username, year: generated_annual_report.year, share_key: 'sharks') + + expect(response) + .to have_http_status(404) + end + end + end +end diff --git a/spec/serializers/activitypub/actor_serializer_spec.rb b/spec/serializers/activitypub/actor_serializer_spec.rb index ad2445595375ac..734da6673c0d3d 100644 --- a/spec/serializers/activitypub/actor_serializer_spec.rb +++ b/spec/serializers/activitypub/actor_serializer_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' RSpec.describe ActivityPub::ActorSerializer do - subject { serialized_record_json(record, described_class) } + subject { serialized_record_json(record, described_class, adapter: ActivityPub::Adapter) } describe '#type' do context 'with the instance actor' do @@ -36,4 +36,39 @@ it { is_expected.to include('type' => 'Person') } end end + + describe '#interactionPolicy' do + let(:record) { Fabricate(:account) } + + # TODO: Remove when feature flag is removed + context 'when collections feature is disabled?' do + it 'is not present' do + expect(subject).to_not have_key('interactionPolicy') + end + end + + context 'when collections feature is enabled', feature: :collections do + context 'when actor is discoverable' do + it 'includes an automatic policy allowing everyone' do + expect(subject).to include('interactionPolicy' => { + 'canFeature' => { + 'automaticApproval' => ['https://www.w3.org/ns/activitystreams#Public'], + }, + }) + end + end + + context 'when actor is not discoverable' do + let(:record) { Fabricate(:account, discoverable: false) } + + it 'includes an automatic policy limited to the actor itself' do + expect(subject).to include('interactionPolicy' => { + 'canFeature' => { + 'automaticApproval' => [ActivityPub::TagManager.instance.uri_for(record)], + }, + }) + end + end + end + end end diff --git a/spec/serializers/activitypub/note_serializer_spec.rb b/spec/serializers/activitypub/note_serializer_spec.rb index 04179e9bf4dd15..4970de709d1154 100644 --- a/spec/serializers/activitypub/note_serializer_spec.rb +++ b/spec/serializers/activitypub/note_serializer_spec.rb @@ -58,8 +58,23 @@ def reply_items end end + context 'with a deleted quote' do + let(:quoted_status) { Fabricate(:status) } + + before do + Fabricate(:quote, status: parent, quoted_status: nil, state: :accepted) + end + + it 'has the expected shape' do + expect(subject).to include({ + 'type' => 'Note', + 'quote' => { 'type' => 'Tombstone' }, + }) + end + end + context 'with a quote policy' do - let(:parent) { Fabricate(:status, quote_approval_policy: Status::QUOTE_APPROVAL_POLICY_FLAGS[:followers] << 16) } + let(:parent) { Fabricate(:status, quote_approval_policy: InteractionPolicy::POLICY_FLAGS[:followers] << 16) } it 'has the expected shape' do expect(subject).to include({ diff --git a/spec/serializers/rest/account_serializer_spec.rb b/spec/serializers/rest/account_serializer_spec.rb index 5fd4f8d706d394..998de6b0fb990a 100644 --- a/spec/serializers/rest/account_serializer_spec.rb +++ b/spec/serializers/rest/account_serializer_spec.rb @@ -3,12 +3,18 @@ require 'rails_helper' RSpec.describe REST::AccountSerializer do - subject { serialized_record_json(account, described_class) } + subject do + serialized_record_json(account, described_class, options: { + scope: current_user, + scope_name: :current_user, + }) + end let(:default_datetime) { DateTime.new(2024, 11, 28, 16, 20, 0) } let(:role) { Fabricate(:user_role, name: 'Role', highlighted: true) } let(:user) { Fabricate(:user, role: role) } let(:account) { user.account } + let(:current_user) { Fabricate(:user) } context 'when the account is suspended' do before do @@ -68,4 +74,51 @@ expect(subject['last_status_at']).to eq('2024-11-28') end end + + describe '#feature_approval' do + # TODO: Remove when feature flag is removed + context 'when collections feature is disabled' do + it 'does not include the approval policy' do + expect(subject).to_not have_key('feature_approval') + end + end + + context 'when collections feature is enabled', feature: :collections do + context 'when account is local' do + context 'when account is discoverable' do + it 'includes a policy that allows featuring' do + expect(subject['feature_approval']).to include({ + 'automatic' => ['public'], + 'manual' => [], + 'current_user' => 'automatic', + }) + end + end + + context 'when account is not discoverable' do + let(:account) { Fabricate(:account, discoverable: false) } + + it 'includes a policy that disallows featuring' do + expect(subject['feature_approval']).to include({ + 'automatic' => [], + 'manual' => [], + 'current_user' => 'denied', + }) + end + end + end + + context 'when account is remote' do + let(:account) { Fabricate(:account, domain: 'example.com', feature_approval_policy: 0b11000000000000000010) } + + it 'includes the matching policy' do + expect(subject['feature_approval']).to include({ + 'automatic' => ['followers', 'following'], + 'manual' => ['public'], + 'current_user' => 'manual', + }) + end + end + end + end end diff --git a/spec/serializers/rest/base_collection_serializer_spec.rb b/spec/serializers/rest/base_collection_serializer_spec.rb new file mode 100644 index 00000000000000..5ac6bc615d058c --- /dev/null +++ b/spec/serializers/rest/base_collection_serializer_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe REST::BaseCollectionSerializer do + subject do + serialized_record_json(collection, described_class, options: { + scope: current_user, + scope_name: :current_user, + }) + end + + let(:current_user) { nil } + + let(:tag) { Fabricate(:tag, name: 'discovery') } + let(:collection) do + Fabricate(:collection, + id: 2342, + name: 'Exquisite follows', + description: 'Always worth a follow', + local: true, + sensitive: true, + discoverable: false, + tag:) + end + + it 'includes the relevant attributes' do + expect(subject) + .to include( + 'id' => '2342', + 'name' => 'Exquisite follows', + 'description' => 'Always worth a follow', + 'local' => true, + 'sensitive' => true, + 'discoverable' => false, + 'tag' => a_hash_including('name' => 'discovery'), + 'created_at' => match_api_datetime_format, + 'updated_at' => match_api_datetime_format + ) + end +end diff --git a/spec/serializers/rest/collection_item_serializer_spec.rb b/spec/serializers/rest/collection_item_serializer_spec.rb new file mode 100644 index 00000000000000..b12553ec034b8c --- /dev/null +++ b/spec/serializers/rest/collection_item_serializer_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe REST::CollectionItemSerializer do + subject { serialized_record_json(collection_item, described_class) } + + let(:collection_item) do + Fabricate(:collection_item, + id: 2342, + state:, + position: 4) + end + + context 'when state is `accepted`' do + let(:state) { :accepted } + + it 'includes the relevant attributes including the account' do + expect(subject) + .to include( + 'id' => '2342', + 'account' => an_instance_of(Hash), + 'state' => 'accepted', + 'position' => 4 + ) + end + end + + %i(pending rejected revoked).each do |unaccepted_state| + context "when state is `#{unaccepted_state}`" do + let(:state) { unaccepted_state } + + it 'does not include an account' do + expect(subject.keys).to_not include('account') + end + end + end +end diff --git a/spec/serializers/rest/collection_serializer_spec.rb b/spec/serializers/rest/collection_serializer_spec.rb new file mode 100644 index 00000000000000..f0baf7dff874a2 --- /dev/null +++ b/spec/serializers/rest/collection_serializer_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe REST::CollectionSerializer do + subject do + serialized_record_json(collection, described_class, options: { + scope: current_user, + scope_name: :current_user, + }) + end + + let(:current_user) { nil } + + let(:tag) { Fabricate(:tag, name: 'discovery') } + let(:collection) do + Fabricate(:collection, + id: 2342, + name: 'Exquisite follows', + description: 'Always worth a follow', + local: true, + sensitive: true, + discoverable: false, + tag:) + end + + it 'includes the relevant attributes' do + expect(subject) + .to include( + 'account' => an_instance_of(Hash), + 'id' => '2342', + 'name' => 'Exquisite follows', + 'description' => 'Always worth a follow', + 'local' => true, + 'sensitive' => true, + 'discoverable' => false, + 'tag' => a_hash_including('name' => 'discovery'), + 'created_at' => match_api_datetime_format, + 'updated_at' => match_api_datetime_format + ) + end +end diff --git a/spec/serializers/rest/scheduled_status_serializer_spec.rb b/spec/serializers/rest/scheduled_status_serializer_spec.rb index 2cf00986542c4e..9eb27035d3fd44 100644 --- a/spec/serializers/rest/scheduled_status_serializer_spec.rb +++ b/spec/serializers/rest/scheduled_status_serializer_spec.rb @@ -10,14 +10,18 @@ ) end - let(:scheduled_status) { Fabricate.build(:scheduled_status, scheduled_at: 4.minutes.from_now, params: { application_id: 123 }) } + let(:scheduled_status) { Fabricate.build(:scheduled_status, scheduled_at: 4.minutes.from_now, params: { application_id: 123, quoted_status_id: 456, quote_approval_policy: InteractionPolicy::POLICY_FLAGS[:public] << 16 }) } describe 'serialization' do it 'returns expected values and removes application_id from params' do expect(subject.deep_symbolize_keys) .to include( scheduled_at: be_a(String).and(match_api_datetime_format), - params: include(:application_id) + params: a_hash_including( + application_id: 123, + quoted_status_id: '456', + quote_approval_policy: 'public' + ) ) end end diff --git a/spec/services/activitypub/fetch_remote_status_service_spec.rb b/spec/services/activitypub/fetch_remote_status_service_spec.rb index 07d05d762f102a..6afee5f25ef263 100644 --- a/spec/services/activitypub/fetch_remote_status_service_spec.rb +++ b/spec/services/activitypub/fetch_remote_status_service_spec.rb @@ -330,7 +330,7 @@ end it 'updates status' do - expect(existing_status.reload.quote_approval_policy).to eq(Status::QUOTE_APPROVAL_POLICY_FLAGS[:public] << 16) + expect(existing_status.reload.quote_approval_policy).to eq(InteractionPolicy::POLICY_FLAGS[:public] << 16) end end end diff --git a/spec/services/activitypub/process_account_service_spec.rb b/spec/services/activitypub/process_account_service_spec.rb index eb0ba3524a77ce..b2c6bc17b2228d 100644 --- a/spec/services/activitypub/process_account_service_spec.rb +++ b/spec/services/activitypub/process_account_service_spec.rb @@ -272,6 +272,49 @@ end end + context 'with interaction policy' do + let(:payload) do + { + id: 'https://foo.test', + type: 'Actor', + inbox: 'https://foo.test/inbox', + followers: 'https://foo.test/followers', + following: 'https://foo.test/following', + interactionPolicy: { + canFeature: { + automaticApproval: 'https://foo.test', + manualApproval: [ + 'https://foo.test/followers', + 'https://foo.test/following', + ], + }, + }, + }.with_indifferent_access + end + + before do + stub_request(:get, %r{^https://foo\.test/follow}) + .to_return(status: 200, body: '', headers: {}) + end + + # TODO: Remove when feature flag is removed + context 'when collections feature is disabled' do + it 'does not set the interaction policy' do + account = subject.call('user1', 'foo.test', payload) + + expect(account.feature_approval_policy).to be_zero + end + end + + context 'when collections feature is enabled', feature: :collections do + it 'sets the interaction policy to the correct value' do + account = subject.call('user1', 'foo.test', payload) + + expect(account.feature_approval_policy).to eq 0b100000000000000001100 + end + end + end + private def create_some_remote_accounts diff --git a/spec/services/activitypub/process_status_update_service_spec.rb b/spec/services/activitypub/process_status_update_service_spec.rb index 7af2c67387fb4a..1a19cbb782bf10 100644 --- a/spec/services/activitypub/process_status_update_service_spec.rb +++ b/spec/services/activitypub/process_status_update_service_spec.rb @@ -602,7 +602,7 @@ context 'when an approved quote of a local post gets updated through an explicit update, removing text' do let(:quoted_account) { Fabricate(:account) } - let(:quoted_status) { Fabricate(:status, account: quoted_account, quote_approval_policy: Status::QUOTE_APPROVAL_POLICY_FLAGS[:public] << 16) } + let(:quoted_status) { Fabricate(:status, account: quoted_account, quote_approval_policy: InteractionPolicy::POLICY_FLAGS[:public] << 16) } let!(:quote) { Fabricate(:quote, status: status, quoted_status: quoted_status, state: :accepted) } let(:approval_uri) { ActivityPub::TagManager.instance.approval_uri_for(quote) } @@ -638,7 +638,7 @@ context 'when an approved quote of a local post gets updated through an explicit update' do let(:quoted_account) { Fabricate(:account) } - let(:quoted_status) { Fabricate(:status, account: quoted_account, quote_approval_policy: Status::QUOTE_APPROVAL_POLICY_FLAGS[:public] << 16) } + let(:quoted_status) { Fabricate(:status, account: quoted_account, quote_approval_policy: InteractionPolicy::POLICY_FLAGS[:public] << 16) } let!(:quote) { Fabricate(:quote, status: status, quoted_status: quoted_status, state: :accepted) } let(:approval_uri) { ActivityPub::TagManager.instance.approval_uri_for(quote) } @@ -810,6 +810,72 @@ end end + context 'when the status adds a verifiable quote of a reblog through an explicit update' do + let(:quoted_account) { Fabricate(:account, domain: 'quoted.example.com') } + let(:quoted_status) { Fabricate(:status, account: quoted_account, reblog: Fabricate(:status)) } + let(:approval_uri) { 'https://quoted.example.com/approvals/1' } + + let(:payload) do + { + '@context': [ + 'https://www.w3.org/ns/activitystreams', + { + '@id': 'https://w3id.org/fep/044f#quote', + '@type': '@id', + }, + { + '@id': 'https://w3id.org/fep/044f#quoteAuthorization', + '@type': '@id', + }, + ], + id: 'foo', + type: 'Note', + summary: 'Show more', + content: 'Hello universe', + updated: '2021-09-08T22:39:25Z', + quote: ActivityPub::TagManager.instance.uri_for(quoted_status), + quoteAuthorization: approval_uri, + } + end + + before do + stub_request(:get, approval_uri).to_return(headers: { 'Content-Type': 'application/activity+json' }, body: Oj.dump({ + '@context': [ + 'https://www.w3.org/ns/activitystreams', + { + QuoteAuthorization: 'https://w3id.org/fep/044f#QuoteAuthorization', + gts: 'https://gotosocial.org/ns#', + interactionPolicy: { + '@id': 'gts:interactionPolicy', + '@type': '@id', + }, + interactingObject: { + '@id': 'gts:interactingObject', + '@type': '@id', + }, + interactionTarget: { + '@id': 'gts:interactionTarget', + '@type': '@id', + }, + }, + ], + type: 'QuoteAuthorization', + id: approval_uri, + attributedTo: ActivityPub::TagManager.instance.uri_for(quoted_status.account), + interactingObject: ActivityPub::TagManager.instance.uri_for(status), + interactionTarget: ActivityPub::TagManager.instance.uri_for(quoted_status), + })) + end + + it 'updates the approval URI but does not verify the quote' do + expect { subject.call(status, json, json) } + .to change(status, :quote).from(nil) + expect(status.quote.approval_uri).to eq approval_uri + expect(status.quote.state).to_not eq 'accepted' + expect(status.quote.quoted_status).to be_nil + end + end + context 'when the status adds a unverifiable quote through an implicit update' do let(:quoted_account) { Fabricate(:account, domain: 'quoted.example.com') } let(:quoted_status) { Fabricate(:status, account: quoted_account) } @@ -1053,6 +1119,44 @@ end end + context 'when the status swaps a verified quote with an ID-less Tombstone through an explicit update' do + let(:quoted_account) { Fabricate(:account, domain: 'quoted.example.com') } + let(:quoted_status) { Fabricate(:status, account: quoted_account) } + let(:second_quoted_status) { Fabricate(:status, account: quoted_account) } + let!(:quote) { Fabricate(:quote, status: status, quoted_status: quoted_status, approval_uri: approval_uri, state: :accepted) } + let(:approval_uri) { 'https://quoted.example.com/approvals/1' } + + let(:payload) do + { + '@context': [ + 'https://www.w3.org/ns/activitystreams', + { + '@id': 'https://w3id.org/fep/044f#quote', + '@type': '@id', + }, + { + '@id': 'https://w3id.org/fep/044f#quoteAuthorization', + '@type': '@id', + }, + ], + id: 'foo', + type: 'Note', + summary: 'Show more', + content: 'Hello universe', + updated: '2021-09-08T22:39:25Z', + quote: { type: 'Tombstone' }, + } + end + + it 'updates the URI and unverifies the quote' do + expect { subject.call(status, json, json) } + .to change { status.quote.quoted_status }.from(quoted_status).to(nil) + .and change { status.quote.state }.from('accepted').to('deleted') + + expect { quote.reload }.to raise_error(ActiveRecord::RecordNotFound) + end + end + context 'when the status swaps a verified quote with another verifiable quote through an explicit update' do let(:quoted_account) { Fabricate(:account, domain: 'quoted.example.com') } let(:second_quoted_account) { Fabricate(:account, domain: 'second-quoted.example.com') } diff --git a/spec/services/activitypub/synchronize_followers_service_spec.rb b/spec/services/activitypub/synchronize_followers_service_spec.rb index b0bd02dac887a2..813658d149b889 100644 --- a/spec/services/activitypub/synchronize_followers_service_spec.rb +++ b/spec/services/activitypub/synchronize_followers_service_spec.rb @@ -6,7 +6,7 @@ subject { described_class.new } let(:actor) { Fabricate(:account, domain: 'example.com', uri: 'http://example.com/account', inbox_url: 'http://example.com/inbox') } - let(:alice) { Fabricate(:account, username: 'alice') } + let(:alice) { Fabricate(:account, username: 'alice', id_scheme: :numeric_ap_id) } let(:bob) { Fabricate(:account, username: 'bob') } let(:eve) { Fabricate(:account, username: 'eve') } let(:mallory) { Fabricate(:account, username: 'mallory') } diff --git a/spec/services/add_account_to_collection_service_spec.rb b/spec/services/add_account_to_collection_service_spec.rb new file mode 100644 index 00000000000000..98d88d0b8f93c5 --- /dev/null +++ b/spec/services/add_account_to_collection_service_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe AddAccountToCollectionService do + subject { described_class.new } + + let(:collection) { Fabricate.create(:collection) } + + describe '#call' do + context 'when given a featurable account' do + let(:account) { Fabricate(:account) } + + it 'creates a new CollectionItem in the `accepted` state' do + expect do + subject.call(collection, account) + end.to change(collection.collection_items, :count).by(1) + + new_item = collection.collection_items.last + expect(new_item.state).to eq 'accepted' + expect(new_item.account).to eq account + end + end + + context 'when given an account that is not featureable' do + let(:account) { Fabricate(:account, discoverable: false) } + + it 'raises an error' do + expect do + subject.call(collection, account) + end.to raise_error(Mastodon::NotPermittedError) + end + end + end +end diff --git a/spec/services/create_collection_service_spec.rb b/spec/services/create_collection_service_spec.rb new file mode 100644 index 00000000000000..f88a366a6c2625 --- /dev/null +++ b/spec/services/create_collection_service_spec.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe CreateCollectionService do + subject { described_class.new } + + let(:author) { Fabricate.create(:account) } + + describe '#call' do + let(:base_params) do + { + name: 'People to follow', + description: 'All my favourites', + sensitive: false, + discoverable: true, + } + end + + context 'when given valid parameters' do + it 'creates a new local collection' do + collection = nil + + expect do + collection = subject.call(base_params, author) + end.to change(Collection, :count).by(1) + + expect(collection).to be_a(Collection) + expect(collection).to be_local + end + + context 'when given account ids' do + let(:accounts) do + Fabricate.times(2, :account) + end + let(:account_ids) { accounts.map { |a| a.id.to_s } } + let(:params) do + base_params.merge(account_ids:) + end + + it 'also creates collection items' do + expect do + subject.call(params, author) + end.to change(CollectionItem, :count).by(2) + end + + context 'when one account may not be added' do + before do + accounts.last.update(discoverable: false) + end + + it 'raises an error' do + expect do + subject.call(params, author) + end.to raise_error(Mastodon::NotPermittedError) + end + end + end + + context 'when given a tag' do + let(:params) { base_params.merge(tag_name: '#people') } + + context 'when the tag exists' do + let!(:tag) { Fabricate.create(:tag, name: 'people') } + + it 'correctly assigns the existing tag' do + collection = subject.call(params, author) + + expect(collection.tag).to eq tag + end + end + + context 'when the tag does not exist' do + it 'creates a new tag' do + collection = nil + + expect do + collection = subject.call(params, author) + end.to change(Tag, :count).by(1) + + expect(collection.tag.name).to eq 'people' + end + end + end + end + + context 'when given invalid parameters' do + it 'raises an exception' do + expect do + subject.call({}, author) + end.to raise_error(ActiveRecord::RecordInvalid) + end + end + end +end diff --git a/spec/services/post_status_service_spec.rb b/spec/services/post_status_service_spec.rb index 96289cdeee1acf..d226d77167a818 100644 --- a/spec/services/post_status_service_spec.rb +++ b/spec/services/post_status_service_spec.rb @@ -161,9 +161,9 @@ end it 'creates a status with the quote approval policy set' do - status = create_status_with_options(quote_approval_policy: Status::QUOTE_APPROVAL_POLICY_FLAGS[:followers] << 16) + status = create_status_with_options(quote_approval_policy: InteractionPolicy::POLICY_FLAGS[:followers] << 16) - expect(status.quote_approval_policy).to eq(Status::QUOTE_APPROVAL_POLICY_FLAGS[:followers] << 16) + expect(status.quote_approval_policy).to eq(InteractionPolicy::POLICY_FLAGS[:followers] << 16) end it 'processes mentions' do diff --git a/spec/services/process_mentions_service_spec.rb b/spec/services/process_mentions_service_spec.rb index 3cc83d82f3cb08..61faf3d04a0582 100644 --- a/spec/services/process_mentions_service_spec.rb +++ b/spec/services/process_mentions_service_spec.rb @@ -8,9 +8,9 @@ let(:account) { Fabricate(:account, username: 'alice') } context 'when mentions contain blocked accounts' do - let(:non_blocked_account) { Fabricate(:account) } - let(:individually_blocked_account) { Fabricate(:account) } - let(:domain_blocked_account) { Fabricate(:account, domain: 'evil.com') } + let!(:non_blocked_account) { Fabricate(:account) } + let!(:individually_blocked_account) { Fabricate(:account) } + let!(:domain_blocked_account) { Fabricate(:account, domain: 'evil.com', protocol: :activitypub) } let(:status) { Fabricate(:status, account: account, text: "Hello @#{non_blocked_account.acct} @#{individually_blocked_account.acct} @#{domain_blocked_account.acct}", visibility: :public) } before do diff --git a/spec/services/remove_status_service_spec.rb b/spec/services/remove_status_service_spec.rb index f2b46f05b002ba..3cb2eceec5966a 100644 --- a/spec/services/remove_status_service_spec.rb +++ b/spec/services/remove_status_service_spec.rb @@ -129,4 +129,20 @@ def undo_activity_for(status) ) ) end + + context 'when removed status is a quote of a local user', inline_jobs: false do + let(:original_status) { Fabricate(:status, account: alice) } + let(:status) { Fabricate(:status, account: jeff) } + + before do + bill.follow!(jeff) + Fabricate(:quote, status: status, quoted_status: original_status, state: :accepted) + original_status.discard + end + + it 'sends deletion without crashing' do + expect { subject.call(status.reload) } + .to enqueue_sidekiq_job(ActivityPub::DeliveryWorker).with(/Delete/, jeff.id, bill.inbox_url) + end + end end diff --git a/spec/support/api_authentication.rb b/spec/support/api_authentication.rb new file mode 100644 index 00000000000000..83f76ab077396a --- /dev/null +++ b/spec/support/api_authentication.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +RSpec.shared_context 'with API authentication' do |oauth_scopes: '', user_fabricator: :user| + let(:user) { Fabricate(user_fabricator) } + let(:scopes) { oauth_scopes } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } +end diff --git a/spec/support/command_line_helpers.rb b/spec/support/command_line_helpers.rb index 09b2b70ba10261..5cd86b10199499 100644 --- a/spec/support/command_line_helpers.rb +++ b/spec/support/command_line_helpers.rb @@ -7,3 +7,5 @@ def output_results(*) ).to_stdout end end + +RSpec::Matchers.define_negated_matcher :not_output_results, :output_results diff --git a/spec/support/examples/api.rb b/spec/support/examples/api.rb index ddc61fcbe08b6d..350166b10d53d2 100644 --- a/spec/support/examples/api.rb +++ b/spec/support/examples/api.rb @@ -13,6 +13,7 @@ RSpec.shared_examples 'forbidden for wrong role' do |wrong_role| let(:role) { UserRole.find_by(name: wrong_role) } + let(:user) { Fabricate(:user, role:) } it 'returns http forbidden' do # Some examples have a subject which needs to be called to make a request diff --git a/spec/support/matchers/sql_queries.rb b/spec/support/matchers/sql_queries.rb new file mode 100644 index 00000000000000..c37eefb06dbbaa --- /dev/null +++ b/spec/support/matchers/sql_queries.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'active_record/testing/query_assertions' + +# Implement something similar to Rails' built-in assertion. +# Can be removed once https://github.com/rspec/rspec-rails/pull/2818 +# has been merged and released. +RSpec::Matchers.define :execute_queries do |expected = nil| + match do |actual| + counter = ActiveRecord::Assertions::QueryAssertions::SQLCounter.new + + queries = ActiveSupport::Notifications.subscribed(counter, 'sql.active_record') do + actual.call + counter.log + end + + if expected.nil? + queries.count >= 1 + else + queries.count == expected + end + end + + supports_block_expectations +end diff --git a/spec/support/streaming_client.rb b/spec/support/streaming_client.rb new file mode 100644 index 00000000000000..02186e781c7d3e --- /dev/null +++ b/spec/support/streaming_client.rb @@ -0,0 +1,205 @@ +# frozen_string_literal: true + +require 'websocket/driver' + +class StreamingClient + module AUTHENTICATION + SUBPROTOCOL = 1 + AUTHORIZATION_HEADER = 2 + QUERY_PARAMETER = 3 + end + + class Connection + attr_reader :url, :messages, :last_error + attr_accessor :logger, :protocols + + def initialize(url) + @uri = URI.parse(url) + @query_params = @uri.query.present? ? URI.decode_www_form(@uri.query).to_h : {} + @protocols = nil + @headers = {} + + @dead = false + + @events_queue = Thread::Queue.new + @messages = [] + @last_error = nil + end + + def set_header(key, value) + @headers[key] = value + end + + def set_query_param(key, value) + @query_params[key] = value + end + + def driver + return @driver if defined?(@driver) + + @uri.query = URI.encode_www_form(@query_params) + @url = @uri.to_s + @tcp = TCPSocket.new(@uri.host, @uri.port) + + @driver = WebSocket::Driver.client(self, { + protocols: @protocols, + }) + + @headers.each_pair do |key, value| + @driver.set_header(key, value) + end + + at_exit do + @driver.close + end + + @driver.on(:open) do + @events_queue.enq({ event: :opened }) + end + + @driver.on(:message) do |event| + @events_queue.enq({ event: :message, payload: event.data }) + @messages << event.data + end + + @driver.on(:error) do |event| + logger&.debug(event.message) + @events_queue.enq({ event: :error, payload: event }) + @last_error = event + end + + @driver.on(:close) do |event| + @events_queue.enq({ event: :closing, payload: event }) + finalize(event) + end + + @thread = Thread.new do + @driver.parse(@tcp.read(1)) until @dead || @tcp.closed? + rescue Errno::ECONNRESET + # Create a synthetic close event: + close_event = WebSocket::Driver::CloseEvent.new( + WebSocket::Driver::Hybi::ERRORS[:unexpected_condition], + 'Connection reset' + ) + + finalize(close_event) + end + + @driver + end + + def wait_for_event(expected_event, timeout: 10) + Timeout.timeout(timeout) do + loop do + event = dequeue_event + + return nil if event.nil? && @events_queue.closed? + return event[:payload] unless event.nil? || event[:event] != expected_event + end + end + end + + def write(data) + @tcp.write(data) + rescue Errno::EPIPE => e + logger&.debug("EPIPE: #{e}") + end + + def finalize(event) + @dead = true + @events_queue.enq({ event: :closed, payload: event }) + @events_queue.close + @thread.kill + end + + def dequeue_event + event = @events_queue.pop + logger&.debug(event) unless event.nil? + event + end + end + + def initialize + @logger = Logger.new($stdout) + @logger.level = 'info' + + @connection = Connection.new("ws://#{STREAMING_HOST}:#{STREAMING_PORT}/api/v1/streaming") + @connection.logger = @logger + end + + def debug! + @logger.debug! + end + + def authenticate(access_token, authentication_method = StreamingClient::AUTHENTICATION::SUBPROTOCOL) + raise 'Invalid access_token passed to StreamingClient, expected a string' unless access_token.is_a?(String) + + case authentication_method + when AUTHENTICATION::QUERY_PARAMETER + @connection.set_query_param('access_token', access_token) + when AUTHENTICATION::SUBPROTOCOL + @connection.protocols = access_token + when AUTHENTICATION::AUTHORIZATION_HEADER + @connection.set_header('Authorization', "Bearer #{access_token}") + else + raise 'Invalid authentication method' + end + end + + def connect + @connection.driver.start + @connection.wait_for_event(:opened) + end + + def subscribe(channel, **params) + send(Oj.dump({ type: 'subscribe', stream: channel }.merge(params))) + end + + def wait_for(event = nil) + @connection.wait_for_event(event) + end + + def wait_for_message + message = @connection.wait_for_event(:message) + event = Oj.load(message) + event['payload'] = Oj.load(event['payload']) if event['payload'] + + event.deep_symbolize_keys + end + + delegate :status, :state, to: :'@connection.driver' + delegate :messages, to: :@connection + + def open? + state == :open + end + + def closing? + state == :closing + end + + def closed? + state == :closed + end + + def send(message) + @connection.driver.text(message) if open? + end + + def close + return if closed? + + @connection.driver.close unless closing? + @connection.wait_for_event(:closed) + end +end + +module StreamingClientHelper + def streaming_client + @streaming_client ||= StreamingClient.new + end +end + +RSpec.configure do |config| + config.include StreamingClientHelper, :streaming +end diff --git a/spec/support/streaming_server_manager.rb b/spec/support/streaming_server_manager.rb index d98f7dd960735e..b565ed79a88d56 100644 --- a/spec/support/streaming_server_manager.rb +++ b/spec/support/streaming_server_manager.rb @@ -12,6 +12,11 @@ def start(port: 4020) queue = Queue.new + if ENV['DEBUG_STREAMING_SERVER'].present? + logger = Logger.new($stdout) + logger.level = 'debug' + end + @queue = queue @running_thread = Thread.new do @@ -31,7 +36,7 @@ def start(port: 4020) # Spawn a thread to listen on streaming server output output_thread = Thread.new do stdout_err.each_line do |line| - Rails.logger.info "Streaming server: #{line}" + logger&.info "Streaming server: #{line}" if status == :starting && line.match('Streaming API now listening on') status = :started @@ -115,12 +120,12 @@ def stop self.use_transactional_tests = true end - private - def streaming_server_manager @streaming_server_manager ||= StreamingServerManager.new end + private + def streaming_examples_present? RSpec.world.filtered_examples.values.flatten.any? { |example| example.metadata[:streaming] == true } end diff --git a/spec/support/threading_helpers.rb b/spec/support/threading_helpers.rb index edf45822cae8b4..cb2a70fb7a01c8 100644 --- a/spec/support/threading_helpers.rb +++ b/spec/support/threading_helpers.rb @@ -1,17 +1,18 @@ # frozen_string_literal: true +require 'concurrent/atomic/cyclic_barrier' + module ThreadingHelpers def multi_threaded_execution(thread_count) - wait_for_start = true + barrier = Concurrent::CyclicBarrier.new(thread_count) - threads = Array.new(thread_count) do + threads = Array.new(thread_count) do |index| Thread.new do - true while wait_for_start - yield + barrier.wait + yield(index) end end - wait_for_start = false threads.each(&:join) end end diff --git a/spec/system/admin/dashboard_spec.rb b/spec/system/admin/dashboard_spec.rb index 06d31cde44e965..d0cedd2ed19ffa 100644 --- a/spec/system/admin/dashboard_spec.rb +++ b/spec/system/admin/dashboard_spec.rb @@ -9,6 +9,7 @@ before do stub_system_checks Fabricate :software_update + Fabricate :tag, requested_review_at: 5.minutes.ago sign_in(user) end @@ -18,6 +19,7 @@ expect(page) .to have_title(I18n.t('admin.dashboard.title')) .and have_content(I18n.t('admin.system_checks.software_version_patch_check.message_html')) + .and have_content('0 pending hashtags') end private diff --git a/spec/system/auth/registrations_spec.rb b/spec/system/auth/registrations_spec.rb index 4c08bf47eea3e8..3a103667e6bd1b 100644 --- a/spec/system/auth/registrations_spec.rb +++ b/spec/system/auth/registrations_spec.rb @@ -3,6 +3,17 @@ require 'rails_helper' RSpec.describe 'Auth Registration' do + context 'when there are server rules' do + let!(:rule) { Fabricate :rule, text: 'You must be seven meters tall' } + + it 'shows rules page before proceeding with sign up' do + visit new_user_registration_path + expect(page) + .to have_title(I18n.t('auth.register')) + .and have_content(rule.text) + end + end + context 'when age verification is enabled' do before { Setting.min_age = 16 } diff --git a/spec/system/home_spec.rb b/spec/system/home_spec.rb index 0838b3d8e7e667..ae52cebfae6f42 100644 --- a/spec/system/home_spec.rb +++ b/spec/system/home_spec.rb @@ -23,5 +23,42 @@ .to have_css('noscript', text: /Mastodon/) .and have_css('body', class: 'app-body') end + + context 'when the landing page is set to about' do + before do + Setting.landing_page = 'about' + end + + it 'visits the root path and is redirected to the about page', :js do + visit root_path + + expect(page).to have_current_path('/about') + end + end + + context 'when the landing page is set to trends' do + before do + Setting.landing_page = 'trends' + end + + it 'visits the root path and is redirected to the trends page', :js do + visit root_path + + expect(page).to have_current_path('/explore') + end + end + + context 'when the landing page is set to local_feed' do + before do + Setting.local_live_feed_access = 'public' # glitch-soc defaults to "authenticated" + Setting.landing_page = 'local_feed' + end + + it 'visits the root path and is redirected to the local live feed page', :js do + visit root_path + + expect(page).to have_current_path('/public/local') + end + end end end diff --git a/spec/system/media_spec.rb b/spec/system/media_spec.rb index d014c7e88ef96d..ec069cdcaa9ee3 100644 --- a/spec/system/media_spec.rb +++ b/spec/system/media_spec.rb @@ -4,19 +4,47 @@ RSpec.describe 'Media' do describe 'Player page' do + let(:status) { Fabricate :status } + + before { status.media_attachments << media } + context 'when signed in' do before { sign_in Fabricate(:user) } - it 'visits the media player page and renders the media' do - status = Fabricate :status - media = Fabricate :media_attachment, type: :video - status.media_attachments << media + context 'when media type is video' do + let(:media) { Fabricate :media_attachment, type: :video } + + it 'visits the player page and renders media' do + visit medium_player_path(media) + + expect(page) + .to have_css('body', class: 'player') + .and have_css('div[data-component="Video"] video[controls="controls"] source') + end + end + + context 'when media type is gifv' do + let(:media) { Fabricate :media_attachment, type: :gifv } + + it 'visits the player page and renders media' do + visit medium_player_path(media) + + expect(page) + .to have_css('body', class: 'player') + .and have_css('div[data-component="MediaGallery"] video[loop="loop"] source') + end + end + + context 'when media type is audio' do + let(:media) { Fabricate :media_attachment, type: :audio } - visit medium_player_path(media) + it 'visits the player page and renders media' do + visit medium_player_path(media) - expect(page) - .to have_css('body', class: 'player') - .and have_css('div[data-component="Video"]') + expect(page) + .to have_css('body', class: 'player') + .and have_css('div[data-component="Audio"] audio source') + end end end end diff --git a/spec/system/settings/applications_spec.rb b/spec/system/settings/applications_spec.rb index 62656c2b8e0a41..024a6403555d77 100644 --- a/spec/system/settings/applications_spec.rb +++ b/spec/system/settings/applications_spec.rb @@ -104,7 +104,12 @@ def submit_form let(:redis_pipeline_stub) { instance_double(Redis::PipelinedConnection, publish: nil) } let!(:access_token) { Fabricate(:accessible_access_token, application: application) } - before { stub_redis_pipeline } + before do + # Disable wrapstodon to avoid redis calls that we don't want to stub + Setting.wrapstodon = false + + stub_redis_pipeline + end it 'destroys the record and tells the broader universe about that' do visit settings_applications_path diff --git a/spec/system/streaming/channel_subscriptions_spec.rb b/spec/system/streaming/channel_subscriptions_spec.rb new file mode 100644 index 00000000000000..447ea64f22f35f --- /dev/null +++ b/spec/system/streaming/channel_subscriptions_spec.rb @@ -0,0 +1,144 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Channel Subscriptions', :inline_jobs, :streaming do + let(:application) { Fabricate(:application, confidential: false) } + let(:scopes) { nil } + let(:access_token) { Fabricate(:accessible_access_token, resource_owner_id: user_account.user.id, application: application, scopes: scopes) } + + let(:user_account) { Fabricate(:account, username: 'alice', domain: nil) } + let(:bob_account) { Fabricate(:account, username: 'bob') } + + after do + streaming_client.close + end + + context 'when the access token has insufficient scope to read statuses' do + let(:scopes) { 'profile' } + + it 'cannot subscribe to the public:local channel' do + streaming_client.authenticate(access_token.token) + + streaming_client.connect + streaming_client.subscribe('public:local') + + # Receive the error back from the subscription attempt: + message = streaming_client.wait_for_message + + expect(message).to include( + error: 'Access token does not have the required scopes', + status: 401 + ) + end + end + + context 'when the access token has read scope' do + let(:scopes) { 'read' } + + it 'can subscribing to the public:local channel' do + streaming_client.authenticate(access_token.token) + + streaming_client.connect + streaming_client.subscribe('public:local') + + # We need to publish a status as there is no positive acknowledgement of + # subscriptions: + status = PostStatusService.new.call(bob_account, text: 'Hello @alice') + + # And then we want to receive that status: + message = streaming_client.wait_for_message + + expect(message).to include( + stream: be_an(Array).and(contain_exactly('public:local')), + event: 'update', + payload: include( + id: status.id.to_s + ) + ) + end + + it 'can subscribing to the user:notifications channel' do + streaming_client.authenticate(access_token.token) + + streaming_client.connect + streaming_client.subscribe('user:notification') + + # We need to perform an action that triggers a notification as there is + # no positive acknowledgement of subscriptions: + first_status = PostStatusService.new.call(user_account, text: 'Test') + ReblogService.new.call(bob_account, first_status) + + message = streaming_client.wait_for_message + + expect(message).to include( + event: 'notification', + stream: ['user:notification'] + ) + end + end + + context 'when the access token has read:statuses scope' do + let(:scopes) { 'read:statuses' } + + it 'can subscribing to the public:local channel' do + streaming_client.authenticate(access_token.token) + + streaming_client.connect + streaming_client.subscribe('public:local') + + # We need to publish a status as there is no positive acknowledgement of + # subscriptions: + status = PostStatusService.new.call(bob_account, text: 'Hello @alice') + + # And then we want to receive that status: + message = streaming_client.wait_for_message + + expect(message).to include( + stream: be_an(Array).and(contain_exactly('public:local')), + event: 'update', + payload: include( + id: status.id.to_s + ) + ) + end + + it 'cannot subscribing to the user:notifications channel' do + streaming_client.authenticate(access_token.token) + + streaming_client.connect + streaming_client.subscribe('user:notification') + + # We should receive an error back immediately: + message = streaming_client.wait_for_message + + expect(message).to include( + error: 'Access token does not have the required scopes', + status: 401 + ) + end + end + + context 'when the access token has read:notifications scope' do + let(:scopes) { 'read:notifications' } + + it 'can subscribing to the user:notifications channel' do + streaming_client.authenticate(access_token.token) + + streaming_client.connect + streaming_client.subscribe('user:notification') + + # We need to perform an action that triggers a notification as there is + # no positive acknowledgement of subscriptions: + first_status = PostStatusService.new.call(user_account, text: 'Test') + ReblogService.new.call(bob_account, first_status) + + message = streaming_client.wait_for_message + + expect(message).to include( + event: 'notification', + stream: ['user:notification'] + ) + end + end +end diff --git a/spec/system/streaming/streaming_spec.rb b/spec/system/streaming/streaming_spec.rb new file mode 100644 index 00000000000000..f5d3ba114265ae --- /dev/null +++ b/spec/system/streaming/streaming_spec.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: true + +require 'rails_helper' +RSpec.describe 'Streaming', :inline_jobs, :streaming do + let(:authentication_method) { StreamingClient::AUTHENTICATION::SUBPROTOCOL } + let(:user) { Fabricate(:user) } + let(:scopes) { '' } + let(:application) { Fabricate(:application, confidential: false) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, application: application, scopes: scopes) } + let(:access_token) { token.token } + + before do + streaming_client.authenticate(access_token, authentication_method) + end + + after do + streaming_client.close + end + + context 'when authenticating via subprotocol' do + it 'is able to connect' do + streaming_client.connect + + expect(streaming_client.status).to eq(101) + expect(streaming_client.open?).to be(true) + end + end + + context 'when authenticating via authorization header' do + let(:authentication_method) { StreamingClient::AUTHENTICATION::AUTHORIZATION_HEADER } + + it 'is able to connect successfully' do + streaming_client.connect + + expect(streaming_client.status).to eq(101) + expect(streaming_client.open?).to be(true) + end + end + + context 'when authenticating via query parameter' do + let(:authentication_method) { StreamingClient::AUTHENTICATION::QUERY_PARAMETER } + + it 'is able to connect successfully' do + streaming_client.connect + + expect(streaming_client.status).to eq(101) + expect(streaming_client.open?).to be(true) + end + end + + context 'with a revoked access token' do + before do + token.revoke + end + + it 'receives an 401 unauthorized error' do + streaming_client.connect + + expect(streaming_client.status).to eq(401) + expect(streaming_client.open?).to be(false) + end + end + + context 'when revoking an access token after connection' do + it 'disconnects the client' do + streaming_client.connect + + expect(streaming_client.status).to eq(101) + expect(streaming_client.open?).to be(true) + + token.revoke + + expect(streaming_client.wait_for(:closed).code).to be(1000) + expect(streaming_client.open?).to be(false) + end + end + + context 'with a disabled user account' do + before do + user.disable! + end + + it 'receives an 401 unauthorized error when trying to connect' do + streaming_client.connect + + expect(streaming_client.status).to eq(401) + expect(streaming_client.open?).to be(false) + end + end + + context 'when the user account is disabled whilst connected' do + it 'terminates the connection for the user' do + streaming_client.connect + + user.disable! + + expect(streaming_client.wait_for(:closed).code).to be(1000) + expect(streaming_client.open?).to be(false) + end + end + + context 'with a suspended user account' do + before do + user.account.suspend! + end + + it 'receives an 401 unauthorized error when trying to connect' do + streaming_client.connect + + expect(streaming_client.status).to eq(401) + expect(streaming_client.open?).to be(false) + end + end + + context 'when the user account is suspended whilst connected' do + it 'terminates the connection for the user' do + streaming_client.connect + + user.account.suspend! + + expect(streaming_client.wait_for(:closed).code).to be(1000) + expect(streaming_client.open?).to be(false) + end + end +end diff --git a/spec/system/wrapstodon_spec.rb b/spec/system/wrapstodon_spec.rb new file mode 100644 index 00000000000000..67d544dd47eb46 --- /dev/null +++ b/spec/system/wrapstodon_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Wrapstodon' do + describe 'Viewing a wrapstodon' do + let(:generated_annual_report) { AnnualReport.new(user.account, Time.current.year).generate } + let(:user) { Fabricate :user } + + context 'when signed in' do + before { sign_in user } + + it 'visits the wrap page and renders the web app' do + visit public_wrapstodon_path(account_username: user.account.username, year: generated_annual_report.year, share_key: generated_annual_report.share_key) + + expect(page) + .to have_css('#wrapstodon') + .and have_private_cache_control + end + end + end +end diff --git a/spec/workers/activitypub/delivery_worker_spec.rb b/spec/workers/activitypub/delivery_worker_spec.rb index 9e6805c68b4f06..a1eb7ebfa997a0 100644 --- a/spec/workers/activitypub/delivery_worker_spec.rb +++ b/spec/workers/activitypub/delivery_worker_spec.rb @@ -25,12 +25,23 @@ .to have_been_made.once end + context 'when using a numeric ID based scheme' do + let(:sender) { Fabricate(:account, id_scheme: :numeric_ap_id) } + + it 'performs a request to synchronize collection' do + subject.perform(payload, sender.id, url, { synchronize_followers: true }) + + expect(request_to_url) + .to have_been_made.once + end + end + def request_to_url a_request(:post, url) .with( headers: { 'Collection-Synchronization' => <<~VALUES.squish, - collectionId="#{account_followers_url(sender)}", digest="somehash", url="#{account_followers_synchronization_url(sender)}" + collectionId="#{ActivityPub::TagManager.instance.followers_uri_for(sender)}", digest="somehash", url="#{account_followers_synchronization_url(sender)}" VALUES } ) diff --git a/spec/workers/publish_scheduled_status_worker_spec.rb b/spec/workers/publish_scheduled_status_worker_spec.rb index a91e66596582c5..81480f7e54cb5a 100644 --- a/spec/workers/publish_scheduled_status_worker_spec.rb +++ b/spec/workers/publish_scheduled_status_worker_spec.rb @@ -13,8 +13,12 @@ end context 'when the account is not disabled' do + let(:user) { Fabricate(:user) } + let(:scheduled_status) { Fabricate(:scheduled_status, account: user.account, params: { text: 'Hello world, future!', quoted_status_id: Fabricate(:status, account: user.account).id }) } + it 'creates a status and removes scheduled record' do expect(scheduled_status.account.statuses.first.text).to eq 'Hello world, future!' + expect(scheduled_status.account.statuses.first.quote).to_not be_nil expect(ScheduledStatus.find_by(id: scheduled_status.id)).to be_nil end diff --git a/streaming/Dockerfile b/streaming/Dockerfile index 679425dfcc2944..74c0c42aae1ccd 100644 --- a/streaming/Dockerfile +++ b/streaming/Dockerfile @@ -8,9 +8,9 @@ ARG TARGETPLATFORM=${TARGETPLATFORM} ARG BUILDPLATFORM=${BUILDPLATFORM} ARG BASE_REGISTRY="docker.io" -# Node version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"] +# Node version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="22"] # renovate: datasource=node-version depName=node -ARG NODE_MAJOR_VERSION="22" +ARG NODE_MAJOR_VERSION="24" # Debian image to use for base image, change with [--build-arg DEBIAN_VERSION="trixie"] ARG DEBIAN_VERSION="trixie" # Node image to use for base image based on combined variables (ex: 20-trixie-slim) @@ -32,20 +32,20 @@ ARG GID="991" # Apply Mastodon build options based on options above ENV \ -# Apply Mastodon version information + # Apply Mastodon version information MASTODON_VERSION_PRERELEASE="${MASTODON_VERSION_PRERELEASE}" \ MASTODON_VERSION_METADATA="${MASTODON_VERSION_METADATA}" \ -# Apply timezone + # Apply timezone TZ=${TZ} ENV \ -# Configure the IP to bind Mastodon to when serving traffic + # Configure the IP to bind Mastodon to when serving traffic BIND="0.0.0.0" \ -# Explicitly set PORT to match the exposed port + # Explicitly set PORT to match the exposed port PORT=4000 \ -# Use production settings for Yarn, Node and related nodejs based tools + # Use production settings for Yarn, Node and related nodejs based tools NODE_ENV="production" \ -# Add Ruby and Mastodon installation to the PATH + # Add Ruby and Mastodon installation to the PATH DEBIAN_FRONTEND="noninteractive" # Set default shell used for running commands @@ -56,29 +56,29 @@ ARG TARGETPLATFORM RUN echo "Target platform is ${TARGETPLATFORM}" RUN \ -# Remove automatic apt cache Docker cleanup scripts + # Remove automatic apt cache Docker cleanup scripts rm -f /etc/apt/apt.conf.d/docker-clean; \ -# Sets timezone + # Sets timezone echo "${TZ}" > /etc/localtime; \ -# Creates mastodon user/group and sets home directory + # Creates mastodon user/group and sets home directory groupadd -g "${GID}" mastodon; \ useradd -l -u "${UID}" -g "${GID}" -m -d /opt/mastodon mastodon; \ -# Creates symlink for /mastodon folder + # Creates symlink for /mastodon folder ln -s /opt/mastodon /mastodon; # hadolint ignore=DL3008,DL3005 RUN \ -# Mount Apt cache and lib directories from Docker buildx caches ---mount=type=cache,id=apt-cache-${TARGETPLATFORM},target=/var/cache/apt,sharing=locked \ ---mount=type=cache,id=apt-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \ -# Upgrade to check for security updates to Debian image + # Mount Apt cache and lib directories from Docker buildx caches + --mount=type=cache,id=apt-cache-${TARGETPLATFORM},target=/var/cache/apt,sharing=locked \ + --mount=type=cache,id=apt-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \ + # Upgrade to check for security updates to Debian image apt-get update; \ apt-get dist-upgrade -yq; \ apt-get install -y --no-install-recommends \ - ca-certificates \ - curl \ - tzdata \ - wget \ + ca-certificates \ + curl \ + tzdata \ + wget \ ; # Set /opt/mastodon as working directory @@ -86,24 +86,23 @@ WORKDIR /opt/mastodon # Copy Node package configuration files from build system to container COPY package.json yarn.lock .yarnrc.yml /opt/mastodon/ -COPY .yarn /opt/mastodon/.yarn # Copy Streaming source code from build system to container COPY ./streaming /opt/mastodon/streaming RUN \ -# Mount local Corepack and Yarn caches from Docker buildx caches ---mount=type=cache,id=corepack-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/corepack,sharing=locked \ ---mount=type=cache,id=yarn-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/yarn,sharing=locked \ + # Mount local Corepack and Yarn caches from Docker buildx caches + --mount=type=cache,id=corepack-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/corepack,sharing=locked \ + --mount=type=cache,id=yarn-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/yarn,sharing=locked \ # Configure Corepack rm /usr/local/bin/yarn*; \ corepack enable; \ corepack prepare --activate; RUN \ -# Mount Corepack and Yarn caches from Docker buildx caches ---mount=type=cache,id=corepack-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/corepack,sharing=locked \ ---mount=type=cache,id=yarn-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/yarn,sharing=locked \ -# Install Node packages + # Mount Corepack and Yarn caches from Docker buildx caches + --mount=type=cache,id=corepack-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/corepack,sharing=locked \ + --mount=type=cache,id=yarn-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/yarn,sharing=locked \ + # Install Node packages yarn workspaces focus --production @mastodon/streaming; # Set the running user for resulting container diff --git a/streaming/database.js b/streaming/database.js index 553c9149cc0aa6..9dd7a97cfe72bb 100644 --- a/streaming/database.js +++ b/streaming/database.js @@ -65,7 +65,7 @@ export function configFromEnv(env, environment) { if (typeof parsedUrl.ssl === 'boolean') { baseConfig.ssl = parsedUrl.ssl; } else if (typeof parsedUrl.ssl === 'object' && !Array.isArray(parsedUrl.ssl) && parsedUrl.ssl !== null) { - /** @type {Record} */ + /** @type {Record} */ const sslOptions = parsedUrl.ssl; baseConfig.ssl = {}; diff --git a/streaming/index.js b/streaming/index.js index d53f4ffcd4fe6c..b13b6403103bcc 100644 --- a/streaming/index.js +++ b/streaming/index.js @@ -19,6 +19,7 @@ import * as Redis from './redis.js'; import { isTruthy, normalizeHashtag, firstParam } from './utils.js'; const environment = process.env.NODE_ENV || 'development'; +const PERMISSION_VIEW_FEEDS = 0x0000000000100000; // Correctly detect and load .env or .env.production file based on environment: const dotenvFile = environment === 'production' ? '.env.production' : '.env'; @@ -44,6 +45,18 @@ initializeLogLevel(process.env, environment); * @property {string[]} scopes * @property {string} accountId * @property {string[]} chosenLanguages + * @property {number} permissions + */ + +/** + * @typedef {http.IncomingMessage & ResolvedAccount & { + * path: string + * query: Record + * remoteAddress?: string + * cachedFilters: unknown + * scopes: string[] + * necessaryScopes: string[] + * }} Request */ @@ -52,8 +65,8 @@ initializeLogLevel(process.env, environment); * from redis and when receiving a message from a client over a websocket * connection, this is why it accepts a `req` argument. * @param {string} json - * @param {any?} req - * @returns {Object.|null} + * @param {Request?} req + * @returns {Object.|null} */ const parseJSON = (json, req) => { try { @@ -78,17 +91,6 @@ const parseJSON = (json, req) => { } }; -const PUBLIC_CHANNELS = [ - 'public', - 'public:media', - 'public:local', - 'public:local:media', - 'public:remote', - 'public:remote:media', - 'hashtag', - 'hashtag:local', -]; - // Used for priming the counters/gauges for the various metrics that are // per-channel const CHANNEL_NAMES = [ @@ -97,7 +99,14 @@ const CHANNEL_NAMES = [ 'user:notification', 'list', 'direct', - ...PUBLIC_CHANNELS + 'public', + 'public:media', + 'public:local', + 'public:local:media', + 'public:remote', + 'public:remote:media', + 'hashtag', + 'hashtag:local', ]; const startServer = async () => { @@ -174,6 +183,7 @@ const startServer = async () => { let resolvedAccount; try { + // @ts-expect-error resolvedAccount = await accountFromRequest(request); } catch (err) { // Unfortunately for using the on('upgrade') setup, we need to manually @@ -224,7 +234,7 @@ const startServer = async () => { }); /** - * @type {Object.): void>>} + * @type {Object.): void>>} */ const subs = {}; @@ -342,7 +352,7 @@ const startServer = async () => { }; /** - * @param {http.IncomingMessage & ResolvedAccount} req + * @param {Request} req * @param {string[]} necessaryScopes * @returns {boolean} */ @@ -351,11 +361,11 @@ const startServer = async () => { /** * @param {string} token - * @param {any} req + * @param {Request} req * @returns {Promise} */ const accountFromToken = async (token, req) => { - const result = await pgPool.query('SELECT oauth_access_tokens.id, oauth_access_tokens.resource_owner_id, users.account_id, users.chosen_languages, oauth_access_tokens.scopes FROM oauth_access_tokens INNER JOIN users ON oauth_access_tokens.resource_owner_id = users.id WHERE oauth_access_tokens.token = $1 AND oauth_access_tokens.revoked_at IS NULL LIMIT 1', [token]); + const result = await pgPool.query('SELECT oauth_access_tokens.id, oauth_access_tokens.resource_owner_id, users.account_id, users.chosen_languages, oauth_access_tokens.scopes, COALESCE(user_roles.permissions, 0) AS permissions FROM oauth_access_tokens INNER JOIN users ON oauth_access_tokens.resource_owner_id = users.id INNER JOIN accounts ON accounts.id = users.account_id LEFT OUTER JOIN user_roles ON user_roles.id = users.role_id WHERE oauth_access_tokens.token = $1 AND oauth_access_tokens.revoked_at IS NULL AND users.disabled IS FALSE AND accounts.suspended_at IS NULL LIMIT 1', [token]); if (result.rows.length === 0) { throw new AuthenticationError('Invalid access token'); @@ -371,17 +381,18 @@ const startServer = async () => { scopes: result.rows[0].scopes.split(' '), accountId: result.rows[0].account_id, chosenLanguages: result.rows[0].chosen_languages, + permissions: result.rows[0].permissions, }; }; /** - * @param {any} req + * @param {Request} req * @returns {Promise} */ const accountFromRequest = (req) => new Promise((resolve, reject) => { const authorization = req.headers.authorization; - const location = url.parse(req.url, true); - const accessToken = location.query.access_token || req.headers['sec-websocket-protocol']; + const location = req.url ? url.parse(req.url, true) : undefined; + const accessToken = location?.query.access_token || req.headers['sec-websocket-protocol']; if (!authorization && !accessToken) { reject(new AuthenticationError('Missing access token')); @@ -390,11 +401,12 @@ const startServer = async () => { const token = authorization ? authorization.replace(/^Bearer /, '') : accessToken; + // @ts-expect-error resolve(accountFromToken(token, req)); }); /** - * @param {any} req + * @param {Request} req * @returns {string|undefined} */ const channelNameFromPath = req => { @@ -426,7 +438,7 @@ const startServer = async () => { }; /** - * @param {http.IncomingMessage & ResolvedAccount} req + * @param {Request} req * @param {import('pino').Logger} logger * @param {string|undefined} channelName * @returns {Promise.} @@ -434,12 +446,6 @@ const startServer = async () => { const checkScopes = (req, logger, channelName) => new Promise((resolve, reject) => { logger.debug(`Checking OAuth scopes for ${channelName}`); - // When accessing public channels, no scopes are needed - if (channelName && PUBLIC_CHANNELS.includes(channelName)) { - resolve(); - return; - } - // The `read` scope has the highest priority, if the token has it // then it can access all streams const requiredScopes = ['read']; @@ -470,7 +476,7 @@ const startServer = async () => { */ /** - * @param {any} req + * @param {Request} req * @param {SystemMessageHandlers} eventHandlers * @returns {SubscriptionListener} */ @@ -495,7 +501,7 @@ const startServer = async () => { }; /** - * @param {http.IncomingMessage & ResolvedAccount} req + * @param {Request} req * @param {http.OutgoingMessage} res */ const subscribeHttpToSystemChannel = (req, res) => { @@ -522,8 +528,8 @@ const startServer = async () => { }; /** - * @param {any} req - * @param {any} res + * @param {Request} req + * @param {http.ServerResponse} res * @param {function(Error=): void} next */ const authenticationMiddleware = (req, res, next) => { @@ -552,8 +558,8 @@ const startServer = async () => { /** * @param {Error} err - * @param {any} req - * @param {any} res + * @param {Request} req + * @param {http.ServerResponse} res * @param {function(Error=): void} next */ const errorMiddleware = (err, req, res, next) => { @@ -571,16 +577,15 @@ const startServer = async () => { }; /** - * @param {any[]} arr + * @param {string[]} arr * @param {number=} shift * @returns {string} */ - // @ts-ignore const placeholders = (arr, shift = 0) => arr.map((_, i) => `$${i + 1 + shift}`).join(', '); /** * @param {string} listId - * @param {any} req + * @param {Request} req * @returns {Promise.} */ const authorizeListAccess = async (listId, req) => { @@ -593,18 +598,56 @@ const startServer = async () => { } }; + /** + * @param {string} kind + * @param {ResolvedAccount} account + * @returns {Promise.<{ localAccess: boolean, remoteAccess: boolean }>} + */ + const getFeedAccessSettings = async (kind, account) => { + const access = { localAccess: true, remoteAccess: true }; + + if (account.permissions & PERMISSION_VIEW_FEEDS) { + return access; + } + + let localAccessVar, remoteAccessVar; + + if (kind === 'hashtag') { + localAccessVar = 'local_topic_feed_access'; + remoteAccessVar = 'remote_topic_feed_access'; + } else { + localAccessVar = 'local_live_feed_access'; + remoteAccessVar = 'remote_live_feed_access'; + } + + const result = await pgPool.query('SELECT var, value FROM settings WHERE var IN ($1, $2)', [localAccessVar, remoteAccessVar]); + + result.rows.forEach((row) => { + if (row.var === localAccessVar) { + access.localAccess = row.value !== "--- disabled\n"; + } else { + access.remoteAccess = row.value !== "--- disabled\n"; + } + }); + + return access; + }; + /** * @param {string[]} channelIds - * @param {http.IncomingMessage & ResolvedAccount} req + * @param {Request} req * @param {import('pino').Logger} log * @param {function(string, string): void} output * @param {undefined | function(string[], SubscriptionListener): void} attachCloseHandler * @param {'websocket' | 'eventsource'} destinationType - * @param {boolean=} needsFiltering - * @param {boolean=} allowLocalOnly + * @param {Object} options + * @param {boolean} options.needsFiltering + * @param {boolean=} options.filterLocal + * @param {boolean=} options.filterRemote + * @param {boolean=} options.allowLocalOnly * @returns {SubscriptionListener} */ - const streamFrom = (channelIds, req, log, output, attachCloseHandler, destinationType, needsFiltering = false, allowLocalOnly = false) => { + const streamFrom = (channelIds, req, log, output, attachCloseHandler, destinationType, { needsFiltering, filterLocal, filterRemote, allowLocalOnly } = { needsFiltering: false, filterLocal: false, filterRemote: false, allowLocalOnly: false }) => { log.info({ channelIds }, `Starting stream`); /** @@ -651,6 +694,7 @@ const startServer = async () => { // The channels that need filtering are determined in the function // `channelNameToIds` defined below: if (!needsFiltering || (event !== 'update' && event !== 'status.update')) { + // @ts-expect-error transmit(event, payload); return; } @@ -658,8 +702,16 @@ const startServer = async () => { // The rest of the logic from here on in this function is to handle // filtering of statuses: + const localPayload = payload.account.username === payload.account.acct; + if (localPayload ? filterLocal : filterRemote) { + log.debug(`Message ${payload.id} filtered by feed settings`); + return; + } + // Filter based on language: + // @ts-expect-error if (Array.isArray(req.chosenLanguages) && req.chosenLanguages.indexOf(payload.language) === -1) { + // @ts-expect-error log.debug(`Message ${payload.id} filtered by language (${payload.language})`); return; } @@ -671,8 +723,9 @@ const startServer = async () => { } // Filter based on domain blocks, blocks, mutes, or custom filters: - // @ts-ignore + // @ts-expect-error const targetAccountIds = [payload.account.id].concat(payload.mentions.map(item => item.id)); + // @ts-expect-error const accountDomain = payload.account.acct.split('@')[1]; // TODO: Move this logic out of the message handling loop @@ -683,7 +736,7 @@ const startServer = async () => { } const queries = [ - // @ts-ignore + // @ts-expect-error client.query(`SELECT 1 FROM blocks WHERE (account_id = $1 AND target_account_id IN (${placeholders(targetAccountIds, 2)})) @@ -692,17 +745,19 @@ const startServer = async () => { SELECT 1 FROM mutes WHERE account_id = $1 - AND target_account_id IN (${placeholders(targetAccountIds, 2)})`, [req.accountId, payload.account.id].concat(targetAccountIds)), + AND target_account_id IN (${placeholders(targetAccountIds, 2)})`, [req.accountId, payload. + // @ts-expect-error + account.id].concat(targetAccountIds)), ]; if (accountDomain) { - // @ts-ignore + // @ts-expect-error queries.push(client.query('SELECT 1 FROM account_domain_blocks WHERE account_id = $1 AND domain = $2', [req.accountId, accountDomain])); } - // @ts-ignore + // @ts-expect-error if (!payload.filtered && !req.cachedFilters) { - // @ts-ignore + // @ts-expect-error queries.push(client.query('SELECT filter.id AS id, filter.phrase AS title, filter.context AS context, filter.expires_at AS expires_at, filter.action AS filter_action, keyword.keyword AS keyword, keyword.whole_word AS whole_word FROM custom_filter_keywords keyword JOIN custom_filters filter ON keyword.custom_filter_id = filter.id WHERE filter.account_id = $1 AND (filter.expires_at IS NULL OR filter.expires_at > NOW())', [req.accountId])); } @@ -711,6 +766,7 @@ const startServer = async () => { // Handling blocks & mutes and domain blocks: If one of those applies, // then we don't transmit the payload of the event to the client + // @ts-expect-error if (values[0].rows.length > 0 || (accountDomain && values[1].rows.length > 0)) { return; } @@ -727,9 +783,9 @@ const startServer = async () => { // TODO: Move this logic out of the message handling lifecycle // @ts-ignore if (!req.cachedFilters) { + // @ts-expect-error const filterRows = values[accountDomain ? 2 : 1].rows; - // @ts-ignore req.cachedFilters = filterRows.reduce((cache, filter) => { if (cache[filter.id]) { cache[filter.id].keywords.push([filter.keyword, filter.whole_word]); @@ -759,9 +815,9 @@ const startServer = async () => { // needs to be done in a separate loop as the database returns one // filterRow per keyword, so we need all the keywords before // constructing the regular expression - // @ts-ignore + // @ts-expect-error Object.keys(req.cachedFilters).forEach((key) => { - // @ts-ignore + // @ts-expect-error req.cachedFilters[key].regexp = new RegExp(req.cachedFilters[key].keywords.map(([keyword, whole_word]) => { let expr = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); @@ -782,16 +838,14 @@ const startServer = async () => { // Apply cachedFilters against the payload, constructing a // `filter_results` array of FilterResult entities - // @ts-ignore if (req.cachedFilters) { const status = payload; // TODO: Calculate searchableContent in Ruby on Rails: - // @ts-ignore + // @ts-expect-error const searchableContent = ([status.spoiler_text || '', status.content].concat((status.poll && status.poll.options) ? status.poll.options.map(option => option.title) : [])).concat(status.media_attachments.map(att => att.description)).join('\n\n').replace(//g, '\n').replace(/<\/p>

/g, '\n\n'); const searchableTextContent = JSDOM.fragment(searchableContent).textContent; const now = new Date(); - // @ts-ignore const filter_results = Object.values(req.cachedFilters).reduce((results, cachedFilter) => { // Check the filter hasn't expired before applying: if (cachedFilter.expires_at !== null && cachedFilter.expires_at < now) { @@ -851,8 +905,8 @@ const startServer = async () => { }; /** - * @param {any} req - * @param {any} res + * @param {Request} req + * @param {http.ServerResponse} res * @returns {function(string, string): void} */ const streamToHttp = (req, res) => { @@ -894,7 +948,7 @@ const startServer = async () => { }; /** - * @param {any} req + * @param {Request} req * @param {function(): void} [closeHandler] * @returns {function(string[], SubscriptionListener): void} */ @@ -944,10 +998,13 @@ const startServer = async () => { app.use(api); + // @ts-expect-error api.use(authenticationMiddleware); + // @ts-expect-error api.use(errorMiddleware); - api.get('/api/v1/streaming/*', (req, res) => { + api.get('/api/v1/streaming/*splat', (req, res) => { + // @ts-expect-error const channelName = channelNameFromPath(req); // FIXME: In theory we'd never actually reach here due to @@ -958,12 +1015,15 @@ const startServer = async () => { return; } + // @ts-expect-error channelNameToIds(req, channelName, req.query).then(({ channelIds, options }) => { + // @ts-expect-error const onSend = streamToHttp(req, res); + // @ts-expect-error const onEnd = streamHttpEnd(req, subscriptionHeartbeat(channelIds)); // @ts-ignore - streamFrom(channelIds, req, req.log, onSend, onEnd, 'eventsource', options.needsFiltering, options.allowLocalOnly); + streamFrom(channelIds, req, req.log, onSend, onEnd, 'eventsource', options); }).catch(err => { const {statusCode, errorMessage } = extractErrorStatusAndMessage(err); @@ -982,7 +1042,7 @@ const startServer = async () => { */ /** - * @param {any} req + * @param {Request} req * @returns {string[]} */ const channelsForUserStream = req => { @@ -996,12 +1056,28 @@ const startServer = async () => { }; /** - * @param {any} req + * @param {Request} req * @param {string} name * @param {StreamParams} params - * @returns {Promise.<{ channelIds: string[], options: { needsFiltering: boolean } }>} + * @returns {Promise.<{ channelIds: string[], options: { needsFiltering: boolean, filterLocal?: boolean, filterRemote?: boolean, allowLocalOnly?: boolean } }>} */ const channelNameToIds = (req, name, params) => new Promise((resolve, reject) => { + /** + * @param {string} feedKind + * @param {string} channelId + * @param {{ needsFiltering: boolean, allowLocalOnly: boolean }} options + */ + const resolveFeed = (feedKind, channelId, options) => { + getFeedAccessSettings(feedKind, req).then(({ localAccess, remoteAccess }) => { + resolve({ + channelIds: [channelId], + options: { ...options, filterLocal: !localAccess, filterRemote: !remoteAccess }, + }); + }).catch(() => { + reject(new Error('Error getting feed access settings')); + }); + }; + switch (name) { case 'user': resolve({ @@ -1018,60 +1094,28 @@ const startServer = async () => { break; case 'public': - resolve({ - channelIds: ['timeline:public'], - options: { needsFiltering: true, allowLocalOnly: isTruthy(params.allow_local_only) }, - }); - + resolveFeed('public', 'timeline:public', { needsFiltering: true, allowLocalOnly: isTruthy(params.allow_local_only) }); break; case 'public:allow_local_only': - resolve({ - channelIds: ['timeline:public'], - options: { needsFiltering: true, allowLocalOnly: true }, - }); - + resolveFeed('public', 'timeline:public', { needsFiltering: true, allowLocalOnly: true }); break; case 'public:local': - resolve({ - channelIds: ['timeline:public:local'], - options: { needsFiltering: true, allowLocalOnly: true }, - }); - + resolveFeed('public', 'timeline:public:local', { needsFiltering: true, allowLocalOnly: true }); break; case 'public:remote': - resolve({ - channelIds: ['timeline:public:remote'], - options: { needsFiltering: true, allowLocalOnly: false }, - }); - + resolveFeed('public', 'timeline:public:remote', { needsFiltering: true, allowLocalOnly: false }); break; case 'public:media': - resolve({ - channelIds: ['timeline:public:media'], - options: { needsFiltering: true, allowLocalOnly: isTruthy(params.allow_local_only) }, - }); - + resolveFeed('public', 'timeline:public:media', { needsFiltering: true, allowLocalOnly: isTruthy(params.allow_local_only) }); break; case 'public:allow_local_only:media': - resolve({ - channelIds: ['timeline:public:media'], - options: { needsFiltering: true, allowLocalOnly: true }, - }); - + resolveFeed('public', 'timeline:public:media', { needsFiltering: true, allowLocalOnly: true }); break; case 'public:local:media': - resolve({ - channelIds: ['timeline:public:local:media'], - options: { needsFiltering: true, allowLocalOnly: true }, - }); - + resolveFeed('public', 'timeline:public:local:media', { needsFiltering: true, allowLocalOnly: true }); break; case 'public:remote:media': - resolve({ - channelIds: ['timeline:public:remote:media'], - options: { needsFiltering: true, allowLocalOnly: false }, - }); - + resolveFeed('public', 'timeline:public:remote:media', { needsFiltering: true, allowLocalOnly: false }); break; case 'direct': resolve({ @@ -1083,24 +1127,20 @@ const startServer = async () => { case 'hashtag': if (!params.tag) { reject(new RequestError('Missing tag name parameter')); - } else { - resolve({ - channelIds: [`timeline:hashtag:${normalizeHashtag(params.tag)}`], - options: { needsFiltering: true, allowLocalOnly: true }, - }); + return; } + resolveFeed('hashtag', `timeline:hashtag:${normalizeHashtag(params.tag)}`, { needsFiltering: true, allowLocalOnly: true }); + break; case 'hashtag:local': if (!params.tag) { reject(new RequestError('Missing tag name parameter')); - } else { - resolve({ - channelIds: [`timeline:hashtag:${normalizeHashtag(params.tag)}:local`], - options: { needsFiltering: true, allowLocalOnly: true }, - }); + return; } + resolveFeed('hashtag', `timeline:hashtag:${normalizeHashtag(params.tag)}:local`, { needsFiltering: true, allowLocalOnly: true }); + break; case 'list': if (!params.list) { @@ -1141,7 +1181,7 @@ const startServer = async () => { /** * @typedef WebSocketSession * @property {import('ws').WebSocket & { isAlive: boolean}} websocket - * @property {http.IncomingMessage & ResolvedAccount} request + * @property {Request} request * @property {import('pino').Logger} logger * @property {Object.} subscriptions */ @@ -1163,7 +1203,7 @@ const startServer = async () => { const onSend = streamToWs(request, websocket, streamNameFromChannelName(channelName, params)); const stopHeartbeat = subscriptionHeartbeat(channelIds); - const listener = streamFrom(channelIds, request, logger, onSend, undefined, 'websocket', options.needsFiltering, options.allowLocalOnly); + const listener = streamFrom(channelIds, request, logger, onSend, undefined, 'websocket', options); metrics.connectedChannels.labels({ type: 'websocket', channel: channelName }).inc(); @@ -1267,7 +1307,7 @@ const startServer = async () => { /** * @param {import('ws').WebSocket & { isAlive: boolean }} ws - * @param {http.IncomingMessage & ResolvedAccount} req + * @param {Request} req * @param {import('pino').Logger} log */ function onConnection(ws, req, log) { @@ -1334,9 +1374,19 @@ const startServer = async () => { const { type, stream, ...params } = json; if (type === 'subscribe') { - subscribeWebsocketToChannel(session, firstParam(stream), params); + subscribeWebsocketToChannel( + session, + // @ts-expect-error + firstParam(stream), + params + ); } else if (type === 'unsubscribe') { - unsubscribeWebsocketFromChannel(session, firstParam(stream), params); + unsubscribeWebsocketFromChannel( + session, + // @ts-expect-error + firstParam(stream), + params + ); } else { // Unknown action type } @@ -1356,13 +1406,13 @@ const startServer = async () => { setInterval(() => { wss.clients.forEach(ws => { - // @ts-ignore + // @ts-expect-error if (ws.isAlive === false) { ws.terminate(); return; } - // @ts-ignore + // @ts-expect-error ws.isAlive = false; ws.ping('', false); }); @@ -1392,14 +1442,16 @@ const startServer = async () => { }; /** - * @param {any} server + * @param {http.Server} server * @param {function(string): void} [onSuccess] */ const attachServerWithConfig = (server, onSuccess) => { if (process.env.SOCKET) { server.listen(process.env.SOCKET, () => { if (onSuccess) { + // @ts-expect-error fs.chmodSync(server.address(), 0o666); + // @ts-expect-error onSuccess(server.address()); } }); @@ -1414,6 +1466,7 @@ const attachServerWithConfig = (server, onSuccess) => { server.listen(port, bind, () => { if (onSuccess) { + // @ts-expect-error onSuccess(`${server.address().address}:${server.address().port}`); } }); diff --git a/streaming/lint-staged.config.mjs b/streaming/lint-staged.config.mjs index 430a999b9ab144..5f9230acbd55d7 100644 --- a/streaming/lint-staged.config.mjs +++ b/streaming/lint-staged.config.mjs @@ -1,5 +1,4 @@ const config = { - '*': 'prettier --ignore-unknown --write', '*.{js,ts}': 'eslint --fix', '**/*.ts': () => 'tsc -p tsconfig.json --noEmit', }; diff --git a/streaming/logging.js b/streaming/logging.js index e1c552c22ed8f1..61946b622c3756 100644 --- a/streaming/logging.js +++ b/streaming/logging.js @@ -100,7 +100,7 @@ export function createWebsocketLogger(request, resolvedAccount) { /** * Initializes the log level based on the environment - * @param {Object} env + * @param {Object} env * @param {string} environment */ export function initializeLogLevel(env, environment) { diff --git a/streaming/metrics.js b/streaming/metrics.js index 263339a1cafb6c..d84aa0c28dbd26 100644 --- a/streaming/metrics.js +++ b/streaming/metrics.js @@ -9,7 +9,7 @@ import metrics from 'prom-client'; * @property {metrics.Gauge} redisSubscriptions * @property {metrics.Counter} redisMessagesReceived * @property {metrics.Counter<"type">} messagesSent - * @property {import('express').RequestHandler<{}>} requestHandler + * @property {import('express').RequestHandler} requestHandler */ /** @@ -93,7 +93,7 @@ export function setupMetrics(channels, pgPool) { messagesSent.inc({ type: 'eventsource' }, 0); /** - * @type {import('express').RequestHandler<{}>} + * @type {import('express').RequestHandler} */ const requestHandler = (req, res) => { metrics.register.metrics().then((output) => { diff --git a/streaming/package.json b/streaming/package.json index 40a737a61dad6a..0f6651b741ac4b 100644 --- a/streaming/package.json +++ b/streaming/package.json @@ -1,7 +1,7 @@ { "name": "@mastodon/streaming", "license": "AGPL-3.0-or-later", - "packageManager": "yarn@4.9.4", + "packageManager": "yarn@4.12.0", "engines": { "node": ">=20" }, @@ -19,27 +19,26 @@ "dependencies": { "cors": "^2.8.5", "dotenv": "^16.0.3", - "express": "^4.18.2", + "express": "^5.1.0", "ioredis": "^5.3.2", "jsdom": "^27.0.0", "pg": "^8.5.0", "pg-connection-string": "^2.6.0", - "pino": "^9.0.0", - "pino-http": "^10.0.0", + "pino": "^10.0.0", + "pino-http": "^11.0.0", "prom-client": "^15.0.0", - "uuid": "^11.0.0", + "uuid": "^13.0.0", "ws": "^8.12.1" }, "devDependencies": { "@eslint/js": "^9.23.0", "@types/cors": "^2.8.16", - "@types/express": "^4.17.17", + "@types/express": "^5.0.5", "@types/pg": "^8.6.6", - "@types/uuid": "^10.0.0", "@types/ws": "^8.5.9", "globals": "^16.0.0", "pino-pretty": "^13.0.0", - "typescript": "~5.7.3", + "typescript": "~5.9.0", "typescript-eslint": "^8.28.0" }, "optionalDependencies": { diff --git a/streaming/redis.js b/streaming/redis.js index 040246fda93d6f..e8f28c0f90c46d 100644 --- a/streaming/redis.js +++ b/streaming/redis.js @@ -6,6 +6,7 @@ import { parseIntFromEnvValue } from './utils.js'; * @typedef RedisConfiguration * @property {string|undefined} url * @property {import('ioredis').RedisOptions} options + * @property {string|undefined} namespace */ /** diff --git a/streaming/utils.js b/streaming/utils.js index 47c63dd4caed7a..dd5e82c67cab92 100644 --- a/streaming/utils.js +++ b/streaming/utils.js @@ -13,11 +13,15 @@ const FALSE_VALUES = [ ]; /** - * @param {any} value + * @typedef {typeof FALSE_VALUES[number]} FalseValue + */ + +/** + * @param {unknown} value * @returns {boolean} */ export function isTruthy(value) { - return value && !FALSE_VALUES.includes(value); + return !!value && !FALSE_VALUES.includes(/** @type {FalseValue} */ (value)); } /** diff --git a/stylelint.config.js b/stylelint.config.js index fabcd538f1df41..1352c8356756e6 100644 --- a/stylelint.config.js +++ b/stylelint.config.js @@ -2,7 +2,7 @@ module.exports = { extends: ['stylelint-config-standard-scss', 'stylelint-config-prettier-scss'], ignoreFiles: [ 'app/javascript/styles/mastodon/reset.scss', - 'app/javascript/flavours/glitch/styles/reset.scss', + 'app/javascript/flavours/glitch/styles/mastodon/reset.scss', 'app/javascript/styles/win95.scss', 'coverage/**/*', 'node_modules/**/*', @@ -33,7 +33,7 @@ module.exports = { }, overrides: [ { - 'files': ['app/javascript/styles/entrypoints/mailer.scss'], + files: ['app/javascript/styles/entrypoints/mailer.scss'], rules: { 'property-no-unknown': [ true, @@ -44,5 +44,14 @@ module.exports = { ], }, }, + { + files: ['app/javascript/**/*.module.scss'], + rules: { + 'selector-pseudo-class-no-unknown': [ + true, + { ignorePseudoClasses: ['global'] }, + ] + } + }, ], }; diff --git a/tsconfig.json b/tsconfig.json index 3948210956196a..5663aeada43b17 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -26,7 +26,8 @@ "flavours/glitch/*": ["app/javascript/flavours/glitch/*"], "images/*": ["app/javascript/images/*"], "styles/*": ["app/javascript/styles/*"] - } + }, + "plugins": [{ "name": "typescript-plugin-css-modules" }] }, "include": [ "vite.config.mts", diff --git a/vite.config.mts b/vite.config.mts index dee10a77bf3b40..046722f325b26b 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -15,7 +15,6 @@ import { } from 'vite'; import manifestSRI from 'vite-plugin-manifest-sri'; import { VitePWA } from 'vite-plugin-pwa'; -import { viteStaticCopy } from 'vite-plugin-static-copy'; import svgr from 'vite-plugin-svgr'; import tsconfigPaths from 'vite-tsconfig-paths'; @@ -27,6 +26,8 @@ import { MastodonAssetsManifest } from './config/vite/plugin-assets-manifest'; const jsRoot = path.resolve(__dirname, 'app/javascript'); +const cssAliasClasses: ReadonlyArray = ['components', 'features']; + export const config: UserConfigFnPromise = async ({ mode, command }) => { const isProdBuild = mode === 'production' && command === 'build'; @@ -49,6 +50,45 @@ export const config: UserConfigFnPromise = async ({ mode, command }) => { }, }, css: { + modules: { + generateScopedName(name, filename) { + let prefix = ''; + + // Use the top two segments of the path as the prefix. + const [parentDirName, dirName] = path + .dirname(filename) + .split(path.sep) + .slice(-2) + .map((dir) => dir.toLowerCase()); + + // If the parent directory is in the cssAliasClasses list, use + // the first four letters of it as the prefix, otherwise use the full name. + if (parentDirName) { + if (cssAliasClasses.includes(parentDirName)) { + prefix = parentDirName.slice(0, 4); + } else { + prefix = parentDirName; + } + } + + // If we have a directory name, append it to the prefix. + if (dirName) { + prefix = `${prefix}_${dirName}`; + } + + // If the file is not styles.module.scss or style.module.scss, + // append the file base name to the prefix. + const baseName = path.basename( + filename, + `.module${path.extname(filename)}`, + ); + if (baseName !== 'styles' && baseName !== 'style') { + prefix = `${prefix}_${baseName}`; + } + + return `_${prefix}__${name}`; + }, + }, postcss: { plugins: [ postcssPresetEnv({ @@ -73,7 +113,6 @@ export const config: UserConfigFnPromise = async ({ mode, command }) => { port: 3036, }, build: { - target: 'modules', commonjsOptions: { transformMixedEsModules: true }, chunkSizeWarningLimit: 1 * 1024 * 1024, // 1MB sourcemap: true, @@ -81,6 +120,8 @@ export const config: UserConfigFnPromise = async ({ mode, command }) => { manifest: true, outDir, assetsDir: 'assets', + assetsInlineLimit: (filePath, _) => + /\.woff2?$/.exec(filePath) ? false : undefined, rollupOptions: { input: await findEntrypoints(), output: { @@ -115,6 +156,14 @@ export const config: UserConfigFnPromise = async ({ mode, command }) => { }, }, }, + experimental: { + /** + * Setting this causes Vite to not rely on the base config for import URLs, + * and instead uses import.meta.url, which is what we want for proper CDN support. + * @see https://github.com/mastodon/mastodon/pull/37310 + */ + renderBuiltUrl: () => undefined, + }, worker: { format: 'es', }, @@ -127,21 +176,6 @@ export const config: UserConfigFnPromise = async ({ mode, command }) => { }), MastodonThemes(), MastodonAssetsManifest(), - viteStaticCopy({ - targets: [ - { - src: path.resolve( - __dirname, - 'node_modules/emojibase-data/**/compact.json', - ), - dest: 'emoji', - rename(_name, ext, dir) { - const locale = path.basename(path.dirname(dir)); - return `${locale}.${ext}`; - }, - }, - ], - }), MastodonServiceWorkerLocales(), MastodonEmojiCompressed(), legacy({ diff --git a/vitest.config.mts b/vitest.config.mts index b129c293f4cfae..16c1ba2e9ef3a1 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -1,6 +1,7 @@ import { resolve } from 'node:path'; import { storybookTest } from '@storybook/addon-vitest/vitest-plugin'; +import { playwright } from '@vitest/browser-playwright'; import { configDefaults, defineConfig, @@ -23,7 +24,7 @@ const storybookTests: TestProjectInlineConfiguration = { browser: { enabled: true, headless: true, - provider: 'playwright', + provider: playwright(), instances: [{ browser: 'chromium' }], }, setupFiles: [resolve(__dirname, '.storybook/vitest.setup.ts')], diff --git a/yarn.lock b/yarn.lock index 2f258d9d38814e..6226be07a25f6d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,6 +12,13 @@ __metadata: languageName: node linkType: hard +"@acemir/cssom@npm:^0.9.28": + version: 0.9.28 + resolution: "@acemir/cssom@npm:0.9.28" + checksum: 10c0/1e192d216c4236171d9930b42b9a965052d4578b23c6ddaa17c7c3d0820ffb872258544a83af163ae2d41b3bdccd6b6c4c14b2d32eb9f8b8b63972d74f46bd83 + languageName: node + linkType: hard + "@adobe/css-tools@npm:^4.4.0": version: 4.4.3 resolution: "@adobe/css-tools@npm:4.4.3" @@ -19,13 +26,10 @@ __metadata: languageName: node linkType: hard -"@ampproject/remapping@npm:^2.3.0": - version: 2.3.0 - resolution: "@ampproject/remapping@npm:2.3.0" - dependencies: - "@jridgewell/gen-mapping": "npm:^0.3.5" - "@jridgewell/trace-mapping": "npm:^0.3.24" - checksum: 10c0/81d63cca5443e0f0c72ae18b544cc28c7c0ec2cea46e7cb888bb0e0f411a1191d0d6b7af798d54e30777d8d1488b2ec0732aac2be342d3d7d3ffd271c6f489ed +"@adobe/css-tools@npm:~4.3.1": + version: 4.3.3 + resolution: "@adobe/css-tools@npm:4.3.3" + checksum: 10c0/e76e712df713964b87cdf2aca1f0477f19bebd845484d5fcba726d3ec7782366e2f26ec8cb2dcfaf47081a5c891987d8a9f5c3f30d11e1eb3c1848adc27fcb24 languageName: node linkType: hard @@ -42,28 +46,29 @@ __metadata: languageName: node linkType: hard -"@asamuzakjp/css-color@npm:^4.0.3": - version: 4.0.4 - resolution: "@asamuzakjp/css-color@npm:4.0.4" +"@asamuzakjp/css-color@npm:^4.1.0": + version: 4.1.0 + resolution: "@asamuzakjp/css-color@npm:4.1.0" dependencies: "@csstools/css-calc": "npm:^2.1.4" - "@csstools/css-color-parser": "npm:^3.0.10" + "@csstools/css-color-parser": "npm:^3.1.0" "@csstools/css-parser-algorithms": "npm:^3.0.5" "@csstools/css-tokenizer": "npm:^3.0.4" - lru-cache: "npm:^11.1.0" - checksum: 10c0/5a4eb3c8594f58f3df06c867a6cda4a33f702f5cd682d6afa5074813f16fd05e732653ac79bd6fc66390554e158ac478103ad5e885fd9cf154b69bb67639e82f + lru-cache: "npm:^11.2.2" + checksum: 10c0/097b9270a5befb765885dda43d6914ccbaa575565525d307e8ba3ba07f98e466062f4a77b9657e65160db9110c41c59cf5a6937479647f73637aeddf5c421579 languageName: node linkType: hard -"@asamuzakjp/dom-selector@npm:^6.5.4": - version: 6.5.4 - resolution: "@asamuzakjp/dom-selector@npm:6.5.4" +"@asamuzakjp/dom-selector@npm:^6.7.6": + version: 6.7.6 + resolution: "@asamuzakjp/dom-selector@npm:6.7.6" dependencies: "@asamuzakjp/nwsapi": "npm:^2.3.9" bidi-js: "npm:^1.0.3" css-tree: "npm:^3.1.0" is-potential-custom-element-name: "npm:^1.0.1" - checksum: 10c0/aeb032b0f7832c891d5f9900b381fe0c7187343eb46a353f07ffbaac2d222bb80ac7504006e7e1b4c45af9e640fd093e14f951870a146d3ad80fd67670c9a115 + lru-cache: "npm:^11.2.4" + checksum: 10c0/1715faae0787f0c8430b3a0ff3db8576a5b9a4f964408d0808fc2060ab01e0c2f5d8e26409de54b8641433c891dab8b561b196e58798811146084c561a4954ce languageName: node linkType: hard @@ -92,39 +97,39 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:^7.18.9, @babel/core@npm:^7.21.3, @babel/core@npm:^7.24.4, @babel/core@npm:^7.26.10, @babel/core@npm:^7.28.0, @babel/core@npm:^7.28.4": - version: 7.28.4 - resolution: "@babel/core@npm:7.28.4" +"@babel/core@npm:^7.18.9, @babel/core@npm:^7.21.3, @babel/core@npm:^7.24.4, @babel/core@npm:^7.26.10, @babel/core@npm:^7.28.0, @babel/core@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/core@npm:7.28.5" dependencies: "@babel/code-frame": "npm:^7.27.1" - "@babel/generator": "npm:^7.28.3" + "@babel/generator": "npm:^7.28.5" "@babel/helper-compilation-targets": "npm:^7.27.2" "@babel/helper-module-transforms": "npm:^7.28.3" "@babel/helpers": "npm:^7.28.4" - "@babel/parser": "npm:^7.28.4" + "@babel/parser": "npm:^7.28.5" "@babel/template": "npm:^7.27.2" - "@babel/traverse": "npm:^7.28.4" - "@babel/types": "npm:^7.28.4" + "@babel/traverse": "npm:^7.28.5" + "@babel/types": "npm:^7.28.5" "@jridgewell/remapping": "npm:^2.3.5" convert-source-map: "npm:^2.0.0" debug: "npm:^4.1.0" gensync: "npm:^1.0.0-beta.2" json5: "npm:^2.2.3" semver: "npm:^6.3.1" - checksum: 10c0/ef5a6c3c6bf40d3589b5593f8118cfe2602ce737412629fb6e26d595be2fcbaae0807b43027a5c42ec4fba5b895ff65891f2503b5918c8a3ea3542ab44d4c278 + checksum: 10c0/535f82238027621da6bdffbdbe896ebad3558b311d6f8abc680637a9859b96edbf929ab010757055381570b29cf66c4a295b5618318d27a4273c0e2033925e72 languageName: node linkType: hard -"@babel/generator@npm:^7.28.3": - version: 7.28.3 - resolution: "@babel/generator@npm:7.28.3" +"@babel/generator@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/generator@npm:7.28.5" dependencies: - "@babel/parser": "npm:^7.28.3" - "@babel/types": "npm:^7.28.2" + "@babel/parser": "npm:^7.28.5" + "@babel/types": "npm:^7.28.5" "@jridgewell/gen-mapping": "npm:^0.3.12" "@jridgewell/trace-mapping": "npm:^0.3.28" jsesc: "npm:^3.0.2" - checksum: 10c0/0ff58bcf04f8803dcc29479b547b43b9b0b828ec1ee0668e92d79f9e90f388c28589056637c5ff2fd7bcf8d153c990d29c448d449d852bf9d1bc64753ca462bc + checksum: 10c0/9f219fe1d5431b6919f1a5c60db8d5d34fe546c0d8f5a8511b32f847569234ffc8032beb9e7404649a143f54e15224ecb53a3d11b6bb85c3203e573d91fca752 languageName: node linkType: hard @@ -294,10 +299,10 @@ __metadata: languageName: node linkType: hard -"@babel/helper-validator-identifier@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/helper-validator-identifier@npm:7.27.1" - checksum: 10c0/c558f11c4871d526498e49d07a84752d1800bf72ac0d3dad100309a2eaba24efbf56ea59af5137ff15e3a00280ebe588560534b0e894a4750f8b1411d8f78b84 +"@babel/helper-validator-identifier@npm:^7.27.1, @babel/helper-validator-identifier@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/helper-validator-identifier@npm:7.28.5" + checksum: 10c0/42aaebed91f739a41f3d80b72752d1f95fd7c72394e8e4bd7cdd88817e0774d80a432451bcba17c2c642c257c483bf1d409dd4548883429ea9493a3bc4ab0847 languageName: node linkType: hard @@ -329,14 +334,14 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.25.4, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.28.3, @babel/parser@npm:^7.28.4": - version: 7.28.4 - resolution: "@babel/parser@npm:7.28.4" +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.24.4, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/parser@npm:7.28.5" dependencies: - "@babel/types": "npm:^7.28.4" + "@babel/types": "npm:^7.28.5" bin: parser: ./bin/babel-parser.js - checksum: 10c0/58b239a5b1477ac7ed7e29d86d675cc81075ca055424eba6485872626db2dc556ce63c45043e5a679cd925e999471dba8a3ed4864e7ab1dbf64306ab72c52707 + checksum: 10c0/5bbe48bf2c79594ac02b490a41ffde7ef5aa22a9a88ad6bcc78432a6ba8a9d638d531d868bd1f104633f1f6bba9905746e15185b8276a3756c42b765d131b1ef languageName: node linkType: hard @@ -1169,16 +1174,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:7.0.0": - version: 7.0.0 - resolution: "@babel/runtime@npm:7.0.0" - dependencies: - regenerator-runtime: "npm:^0.12.0" - checksum: 10c0/fbbdf86380a1cfa6ce32a743549f4e4c8b8eb06a18be5054441cc0f66e75a747ae43b042d8989f4657027e1be3b9a82069865ccc5080838f004abd1161093742 - languageName: node - linkType: hard - -"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.8, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.2.0, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.3, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": +"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.8, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.3, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": version: 7.27.0 resolution: "@babel/runtime@npm:7.27.0" dependencies: @@ -1198,28 +1194,28 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.26.10, @babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.0, @babel/traverse@npm:^7.28.3, @babel/traverse@npm:^7.28.4": - version: 7.28.4 - resolution: "@babel/traverse@npm:7.28.4" +"@babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.26.10, @babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.0, @babel/traverse@npm:^7.28.3, @babel/traverse@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/traverse@npm:7.28.5" dependencies: "@babel/code-frame": "npm:^7.27.1" - "@babel/generator": "npm:^7.28.3" + "@babel/generator": "npm:^7.28.5" "@babel/helper-globals": "npm:^7.28.0" - "@babel/parser": "npm:^7.28.4" + "@babel/parser": "npm:^7.28.5" "@babel/template": "npm:^7.27.2" - "@babel/types": "npm:^7.28.4" + "@babel/types": "npm:^7.28.5" debug: "npm:^4.3.1" - checksum: 10c0/ee678fdd49c9f54a32e07e8455242390d43ce44887cea6567b233fe13907b89240c377e7633478a32c6cf1be0e17c2f7f3b0c59f0666e39c5074cc47b968489c + checksum: 10c0/f6c4a595993ae2b73f2d4cd9c062f2e232174d293edd4abe1d715bd6281da8d99e47c65857e8d0917d9384c65972f4acdebc6749a7c40a8fcc38b3c7fb3e706f languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.25.4, @babel/types@npm:^7.26.10, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.2, @babel/types@npm:^7.28.4, @babel/types@npm:^7.4.4": - version: 7.28.4 - resolution: "@babel/types@npm:7.28.4" +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.26.10, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.4, @babel/types@npm:^7.28.5, @babel/types@npm:^7.4.4": + version: 7.28.5 + resolution: "@babel/types@npm:7.28.5" dependencies: "@babel/helper-string-parser": "npm:^7.27.1" - "@babel/helper-validator-identifier": "npm:^7.27.1" - checksum: 10c0/ac6f909d6191319e08c80efbfac7bd9a25f80cc83b43cd6d82e7233f7a6b9d6e7b90236f3af7400a3f83b576895bcab9188a22b584eb0f224e80e6d4e95f4517 + "@babel/helper-validator-identifier": "npm:^7.28.5" + checksum: 10c0/a5a483d2100befbf125793640dec26b90b95fd233a94c19573325898a5ce1e52cdfa96e495c7dcc31b5eca5b66ce3e6d4a0f5a4a62daec271455959f208ab08a languageName: node linkType: hard @@ -1230,50 +1226,25 @@ __metadata: languageName: node linkType: hard -"@bundled-es-modules/cookie@npm:^2.0.1": - version: 2.0.1 - resolution: "@bundled-es-modules/cookie@npm:2.0.1" - dependencies: - cookie: "npm:^0.7.2" - checksum: 10c0/dfac5e36127e827c5557b8577f17a8aa94c057baff6d38555917927b99da0ecf0b1357e7fedadc8853ecdbd4a8a7fa1f5e64111b2a656612f4a36376f5bdbe8d - languageName: node - linkType: hard - -"@bundled-es-modules/statuses@npm:^1.0.1": - version: 1.0.1 - resolution: "@bundled-es-modules/statuses@npm:1.0.1" - dependencies: - statuses: "npm:^2.0.1" - checksum: 10c0/c1a8ede3efa8da61ccda4b98e773582a9733edfbeeee569d4630785f8e018766202edb190a754a3ec7a7f6bd738e857829affc2fdb676b6dab4db1bb44e62785 - languageName: node - linkType: hard - -"@cacheable/memoize@npm:^2.0.1": - version: 2.0.1 - resolution: "@cacheable/memoize@npm:2.0.1" +"@cacheable/memory@npm:^2.0.5": + version: 2.0.5 + resolution: "@cacheable/memory@npm:2.0.5" dependencies: - "@cacheable/utils": "npm:^2.0.1" - checksum: 10c0/40ab683429132654b95edc1229c175c45045c239d647bffa75165ae572cd8da0ee9852dfb7c6baca4e8ecc0e3005c2555222ae02add8206d4a227106b1c5fc8d + "@cacheable/utils": "npm:^2.3.0" + "@keyv/bigmap": "npm:^1.1.0" + hookified: "npm:^1.12.2" + keyv: "npm:^5.5.4" + checksum: 10c0/bef5b26de58c4ca20d7cce457d053766b5fb13de48bf444e0ecf56481a16e6556a194dafc28f41906ae4e6cd053ef3d57686c770b8e7a2d381648505bd673839 languageName: node linkType: hard -"@cacheable/memory@npm:^2.0.1": - version: 2.0.1 - resolution: "@cacheable/memory@npm:2.0.1" +"@cacheable/utils@npm:^2.3.0": + version: 2.3.1 + resolution: "@cacheable/utils@npm:2.3.1" dependencies: - "@cacheable/memoize": "npm:^2.0.1" - "@cacheable/utils": "npm:^2.0.1" - "@keyv/bigmap": "npm:^1.0.0" - hookified: "npm:^1.12.0" - keyv: "npm:^5.5.1" - checksum: 10c0/dde1caf7fe66febe8dc9d32ac4eaee48b6b1e690881adfaf3c41188a00892a20514cd7847e33a64bad09c4c5d6e1377eb8e373b02b4b9fb5fa27e6a67297c625 - languageName: node - linkType: hard - -"@cacheable/utils@npm:^2.0.1": - version: 2.0.1 - resolution: "@cacheable/utils@npm:2.0.1" - checksum: 10c0/63806cca7f60add1f7bd4acee279c723760322316661a9fc6deab9ba7c03569e6cbe744d88159d4e72c1f6d296bba53b53c396d01d52294d45c9fed04c416ca4 + hashery: "npm:^1.2.0" + keyv: "npm:^5.5.4" + checksum: 10c0/04802bc11293ff569204e5f143cd11314856e3453de3e5757068cfd9df5c974a80aa9974c8400d88b22de3af70a7d8511d2d7fe927356365f41b765693a4c4bb languageName: node linkType: hard @@ -1304,7 +1275,7 @@ __metadata: languageName: node linkType: hard -"@csstools/css-color-parser@npm:^3.0.10, @csstools/css-color-parser@npm:^3.1.0": +"@csstools/css-color-parser@npm:^3.1.0": version: 3.1.0 resolution: "@csstools/css-color-parser@npm:3.1.0" dependencies: @@ -1326,7 +1297,7 @@ __metadata: languageName: node linkType: hard -"@csstools/css-syntax-patches-for-csstree@npm:^1.0.14": +"@csstools/css-syntax-patches-for-csstree@npm:1.0.14": version: 1.0.14 resolution: "@csstools/css-syntax-patches-for-csstree@npm:1.0.14" peerDependencies: @@ -1335,6 +1306,13 @@ __metadata: languageName: node linkType: hard +"@csstools/css-syntax-patches-for-csstree@npm:^1.0.19": + version: 1.0.20 + resolution: "@csstools/css-syntax-patches-for-csstree@npm:1.0.20" + checksum: 10c0/335fcd24eb563068338153066d580bfdfc87b1e0f7650432a332e925c88d247a56f8e5851cd27dd68e49cde2dbeb465db60a51bb92a18e6721b5166b2e046d91 + languageName: node + linkType: hard + "@csstools/css-tokenizer@npm:^3.0.4": version: 3.0.4 resolution: "@csstools/css-tokenizer@npm:3.0.4" @@ -1699,6 +1677,15 @@ __metadata: languageName: node linkType: hard +"@csstools/postcss-position-area-property@npm:^1.0.0": + version: 1.0.0 + resolution: "@csstools/postcss-position-area-property@npm:1.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/38f770454d46bfed01d43a3f5e7ac07d3111399b374a7198ae6503cdb6288e410c7b4199f5a7af8f16aeb688216445ade97be417c084313d6c56f55e50d34559 + languageName: node + linkType: hard + "@csstools/postcss-progressive-custom-properties@npm:^4.2.1": version: 4.2.1 resolution: "@csstools/postcss-progressive-custom-properties@npm:4.2.1" @@ -1775,6 +1762,18 @@ __metadata: languageName: node linkType: hard +"@csstools/postcss-system-ui-font-family@npm:^1.0.0": + version: 1.0.0 + resolution: "@csstools/postcss-system-ui-font-family@npm:1.0.0" + dependencies: + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/6a81761ae3cae643659b1416a7a892cf1505474896193b8abc26cff319cb6b1a20b64c5330d64019fba458e058da3abc9407d0ebf0c102289c0b79ef99b4c6d6 + languageName: node + linkType: hard + "@csstools/postcss-text-decoration-shorthand@npm:^4.0.3": version: 4.0.3 resolution: "@csstools/postcss-text-decoration-shorthand@npm:4.0.3" @@ -1894,10 +1893,10 @@ __metadata: languageName: node linkType: hard -"@dual-bundle/import-meta-resolve@npm:^4.1.0": - version: 4.1.0 - resolution: "@dual-bundle/import-meta-resolve@npm:4.1.0" - checksum: 10c0/55069e550ee2710e738dd8bbd34aba796cede456287454b50c3be46fbef8695d00625677f3f41f5ffbec1174c0f57f314da9a908388bc9f8ad41a8438db884d9 +"@dual-bundle/import-meta-resolve@npm:^4.2.1": + version: 4.2.1 + resolution: "@dual-bundle/import-meta-resolve@npm:4.2.1" + checksum: 10c0/8f1e572c14c4d20ea35734635085213abd13bd440c251309cf8ae5ed1082f6759cefa1c2c52a631f76859c57e26062f78a8cee4acc239c0edc87cd316a5d3b5b languageName: node linkType: hard @@ -2053,16 +2052,23 @@ __metadata: languageName: node linkType: hard -"@es-joy/jsdoccomment@npm:~0.52.0": - version: 0.52.0 - resolution: "@es-joy/jsdoccomment@npm:0.52.0" +"@es-joy/jsdoccomment@npm:~0.76.0": + version: 0.76.0 + resolution: "@es-joy/jsdoccomment@npm:0.76.0" dependencies: "@types/estree": "npm:^1.0.8" - "@typescript-eslint/types": "npm:^8.34.1" + "@typescript-eslint/types": "npm:^8.46.0" comment-parser: "npm:1.4.1" esquery: "npm:^1.6.0" - jsdoc-type-pratt-parser: "npm:~4.1.0" - checksum: 10c0/4def78060ef58859f31757b9d30c4939fc33e7d9ee85637a7f568c1d209c33aa0abd2cf5a3a4f3662ec5b12b85ecff2f2035d809dc93b9382a31a6dfb200d83c + jsdoc-type-pratt-parser: "npm:~6.10.0" + checksum: 10c0/8fe4edec7d60562787ea8c77193ebe8737a9e28ec3143d383506b63890d0ffd45a2813e913ad1f00f227cb10e3a1fb913e5a696b33d499dc564272ff1a6f3fdb + languageName: node + linkType: hard + +"@es-joy/resolve.exports@npm:1.2.0": + version: 1.2.0 + resolution: "@es-joy/resolve.exports@npm:1.2.0" + checksum: 10c0/7e4713471f5eccb17a925a12415a2d9e372a42376813a19f6abd9c35e8d01ab1403777265817da67c6150cffd4f558d9ad51e26a8de6911dad89d9cb7eedacd8 languageName: node linkType: hard @@ -2073,6 +2079,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/aix-ppc64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/aix-ppc64@npm:0.27.2" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/android-arm64@npm:0.25.5": version: 0.25.5 resolution: "@esbuild/android-arm64@npm:0.25.5" @@ -2080,6 +2093,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/android-arm64@npm:0.27.2" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/android-arm@npm:0.25.5": version: 0.25.5 resolution: "@esbuild/android-arm@npm:0.25.5" @@ -2087,6 +2107,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/android-arm@npm:0.27.2" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + "@esbuild/android-x64@npm:0.25.5": version: 0.25.5 resolution: "@esbuild/android-x64@npm:0.25.5" @@ -2094,6 +2121,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/android-x64@npm:0.27.2" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + "@esbuild/darwin-arm64@npm:0.25.5": version: 0.25.5 resolution: "@esbuild/darwin-arm64@npm:0.25.5" @@ -2101,6 +2135,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/darwin-arm64@npm:0.27.2" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/darwin-x64@npm:0.25.5": version: 0.25.5 resolution: "@esbuild/darwin-x64@npm:0.25.5" @@ -2108,6 +2149,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/darwin-x64@npm:0.27.2" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@esbuild/freebsd-arm64@npm:0.25.5": version: 0.25.5 resolution: "@esbuild/freebsd-arm64@npm:0.25.5" @@ -2115,6 +2163,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/freebsd-arm64@npm:0.27.2" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/freebsd-x64@npm:0.25.5": version: 0.25.5 resolution: "@esbuild/freebsd-x64@npm:0.25.5" @@ -2122,6 +2177,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/freebsd-x64@npm:0.27.2" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/linux-arm64@npm:0.25.5": version: 0.25.5 resolution: "@esbuild/linux-arm64@npm:0.25.5" @@ -2129,6 +2191,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-arm64@npm:0.27.2" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/linux-arm@npm:0.25.5": version: 0.25.5 resolution: "@esbuild/linux-arm@npm:0.25.5" @@ -2136,6 +2205,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-arm@npm:0.27.2" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "@esbuild/linux-ia32@npm:0.25.5": version: 0.25.5 resolution: "@esbuild/linux-ia32@npm:0.25.5" @@ -2143,6 +2219,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ia32@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-ia32@npm:0.27.2" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/linux-loong64@npm:0.25.5": version: 0.25.5 resolution: "@esbuild/linux-loong64@npm:0.25.5" @@ -2150,6 +2233,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-loong64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-loong64@npm:0.27.2" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + "@esbuild/linux-mips64el@npm:0.25.5": version: 0.25.5 resolution: "@esbuild/linux-mips64el@npm:0.25.5" @@ -2157,6 +2247,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-mips64el@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-mips64el@npm:0.27.2" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + "@esbuild/linux-ppc64@npm:0.25.5": version: 0.25.5 resolution: "@esbuild/linux-ppc64@npm:0.25.5" @@ -2164,6 +2261,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ppc64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-ppc64@npm:0.27.2" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/linux-riscv64@npm:0.25.5": version: 0.25.5 resolution: "@esbuild/linux-riscv64@npm:0.25.5" @@ -2171,6 +2275,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-riscv64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-riscv64@npm:0.27.2" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + "@esbuild/linux-s390x@npm:0.25.5": version: 0.25.5 resolution: "@esbuild/linux-s390x@npm:0.25.5" @@ -2178,6 +2289,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-s390x@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-s390x@npm:0.27.2" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + "@esbuild/linux-x64@npm:0.25.5": version: 0.25.5 resolution: "@esbuild/linux-x64@npm:0.25.5" @@ -2185,6 +2303,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-x64@npm:0.27.2" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + "@esbuild/netbsd-arm64@npm:0.25.5": version: 0.25.5 resolution: "@esbuild/netbsd-arm64@npm:0.25.5" @@ -2192,6 +2317,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/netbsd-arm64@npm:0.27.2" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/netbsd-x64@npm:0.25.5": version: 0.25.5 resolution: "@esbuild/netbsd-x64@npm:0.25.5" @@ -2199,6 +2331,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/netbsd-x64@npm:0.27.2" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/openbsd-arm64@npm:0.25.5": version: 0.25.5 resolution: "@esbuild/openbsd-arm64@npm:0.25.5" @@ -2206,6 +2345,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/openbsd-arm64@npm:0.27.2" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/openbsd-x64@npm:0.25.5": version: 0.25.5 resolution: "@esbuild/openbsd-x64@npm:0.25.5" @@ -2213,6 +2359,20 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/openbsd-x64@npm:0.27.2" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openharmony-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/openharmony-arm64@npm:0.27.2" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/sunos-x64@npm:0.25.5": version: 0.25.5 resolution: "@esbuild/sunos-x64@npm:0.25.5" @@ -2220,6 +2380,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/sunos-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/sunos-x64@npm:0.27.2" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + "@esbuild/win32-arm64@npm:0.25.5": version: 0.25.5 resolution: "@esbuild/win32-arm64@npm:0.25.5" @@ -2227,6 +2394,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/win32-arm64@npm:0.27.2" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/win32-ia32@npm:0.25.5": version: 0.25.5 resolution: "@esbuild/win32-ia32@npm:0.25.5" @@ -2234,6 +2408,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-ia32@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/win32-ia32@npm:0.27.2" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/win32-x64@npm:0.25.5": version: 0.25.5 resolution: "@esbuild/win32-x64@npm:0.25.5" @@ -2241,14 +2422,21 @@ __metadata: languageName: node linkType: hard -"@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0, @eslint-community/eslint-utils@npm:^4.7.0": - version: 4.7.0 - resolution: "@eslint-community/eslint-utils@npm:4.7.0" +"@esbuild/win32-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/win32-x64@npm:0.27.2" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@eslint-community/eslint-utils@npm:^4.4.0, @eslint-community/eslint-utils@npm:^4.7.0, @eslint-community/eslint-utils@npm:^4.8.0": + version: 4.9.0 + resolution: "@eslint-community/eslint-utils@npm:4.9.0" dependencies: eslint-visitor-keys: "npm:^3.4.3" peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - checksum: 10c0/c0f4f2bd73b7b7a9de74b716a664873d08ab71ab439e51befe77d61915af41a81ecec93b408778b3a7856185244c34c2c8ee28912072ec14def84ba2dec70adf + checksum: 10c0/8881e22d519326e7dba85ea915ac7a143367c805e6ba1374c987aa2fbdd09195cc51183d2da72c0e2ff388f84363e1b220fd0d19bef10c272c63455162176817 languageName: node linkType: hard @@ -2259,30 +2447,32 @@ __metadata: languageName: node linkType: hard -"@eslint/config-array@npm:^0.21.0": - version: 0.21.0 - resolution: "@eslint/config-array@npm:0.21.0" +"@eslint/config-array@npm:^0.21.1": + version: 0.21.1 + resolution: "@eslint/config-array@npm:0.21.1" dependencies: - "@eslint/object-schema": "npm:^2.1.6" + "@eslint/object-schema": "npm:^2.1.7" debug: "npm:^4.3.1" minimatch: "npm:^3.1.2" - checksum: 10c0/0ea801139166c4aa56465b309af512ef9b2d3c68f9198751bbc3e21894fe70f25fbf26e1b0e9fffff41857bc21bfddeee58649ae6d79aadcd747db0c5dca771f + checksum: 10c0/2f657d4edd6ddcb920579b72e7a5b127865d4c3fb4dda24f11d5c4f445a93ca481aebdbd6bf3291c536f5d034458dbcbb298ee3b698bc6c9dd02900fe87eec3c languageName: node linkType: hard -"@eslint/config-helpers@npm:^0.3.0": - version: 0.3.0 - resolution: "@eslint/config-helpers@npm:0.3.0" - checksum: 10c0/013ae7b189eeae8b30cc2ee87bc5c9c091a9cd615579003290eb28bebad5d78806a478e74ba10b3fe08ed66975b52af7d2cd4b4b43990376412b14e5664878c8 +"@eslint/config-helpers@npm:^0.4.2": + version: 0.4.2 + resolution: "@eslint/config-helpers@npm:0.4.2" + dependencies: + "@eslint/core": "npm:^0.17.0" + checksum: 10c0/92efd7a527b2d17eb1a148409d71d80f9ac160b565ac73ee092252e8bf08ecd08670699f46b306b94f13d22e88ac88a612120e7847570dd7cdc72f234d50dcb4 languageName: node linkType: hard -"@eslint/core@npm:^0.15.0, @eslint/core@npm:^0.15.1": - version: 0.15.1 - resolution: "@eslint/core@npm:0.15.1" +"@eslint/core@npm:^0.17.0": + version: 0.17.0 + resolution: "@eslint/core@npm:0.17.0" dependencies: "@types/json-schema": "npm:^7.0.15" - checksum: 10c0/abaf641940776638b8c15a38d99ce0dac551a8939310ec81b9acd15836a574cf362588eaab03ab11919bc2a0f9648b19ea8dee33bf12675eb5b6fd38bda6f25e + checksum: 10c0/9a580f2246633bc752298e7440dd942ec421860d1946d0801f0423830e67887e4aeba10ab9a23d281727a978eb93d053d1922a587d502942a713607f40ed704e languageName: node linkType: hard @@ -2303,27 +2493,27 @@ __metadata: languageName: node linkType: hard -"@eslint/js@npm:9.32.0, @eslint/js@npm:^9.23.0": - version: 9.32.0 - resolution: "@eslint/js@npm:9.32.0" - checksum: 10c0/f71e8f9146638d11fb15238279feff98801120a4d4130f1c587c4f09b024ff5ec01af1ba88e97ba6b7013488868898a668f77091300cc3d4394c7a8ed32d2667 +"@eslint/js@npm:9.39.1, @eslint/js@npm:^9.23.0": + version: 9.39.1 + resolution: "@eslint/js@npm:9.39.1" + checksum: 10c0/6f7f26f8cdb7ad6327bbf9741973b6278eb946f18f70e35406e88194b0d5c522d0547a34a02f2a208eec95c5d1388cdf7ccb20039efd2e4cb6655615247a50f1 languageName: node linkType: hard -"@eslint/object-schema@npm:^2.1.6": - version: 2.1.6 - resolution: "@eslint/object-schema@npm:2.1.6" - checksum: 10c0/b8cdb7edea5bc5f6a96173f8d768d3554a628327af536da2fc6967a93b040f2557114d98dbcdbf389d5a7b290985ad6a9ce5babc547f36fc1fde42e674d11a56 +"@eslint/object-schema@npm:^2.1.7": + version: 2.1.7 + resolution: "@eslint/object-schema@npm:2.1.7" + checksum: 10c0/936b6e499853d1335803f556d526c86f5fe2259ed241bc665000e1d6353828edd913feed43120d150adb75570cae162cf000b5b0dfc9596726761c36b82f4e87 languageName: node linkType: hard -"@eslint/plugin-kit@npm:^0.3.4": - version: 0.3.4 - resolution: "@eslint/plugin-kit@npm:0.3.4" +"@eslint/plugin-kit@npm:^0.4.1": + version: 0.4.1 + resolution: "@eslint/plugin-kit@npm:0.4.1" dependencies: - "@eslint/core": "npm:^0.15.1" + "@eslint/core": "npm:^0.17.0" levn: "npm:^0.4.1" - checksum: 10c0/64331ca100f62a0115d10419a28059d0f377e390192163b867b9019517433d5073d10b4ec21f754fa01faf832aceb34178745924baab2957486f8bf95fd628d2 + checksum: 10c0/51600f78b798f172a9915dffb295e2ffb44840d583427bc732baf12ecb963eb841b253300e657da91d890f4b323d10a1bd12934bf293e3018d8bb66fdce5217b languageName: node linkType: hard @@ -2354,10 +2544,10 @@ __metadata: linkType: hard "@formatjs/cli@npm:^6.1.1": - version: 6.7.2 - resolution: "@formatjs/cli@npm:6.7.2" + version: 6.7.4 + resolution: "@formatjs/cli@npm:6.7.4" peerDependencies: - "@glimmer/syntax": ^0.94.9 + "@glimmer/syntax": ^0.95.0 "@vue/compiler-core": ^3.5.12 content-tag: ^3.0.0 ember-template-recast: ^6.1.5 @@ -2381,19 +2571,19 @@ __metadata: optional: true bin: formatjs: bin/formatjs - checksum: 10c0/fbcb1d35915a5b1542e4fa3f3f79e23fa5988681e6c238d7e8a3d4d32e18df3e5c36cebe01b04e011bb5c91f96f4d08d36af750259aa799d3e81943084e2f0c2 + checksum: 10c0/11ff474cd79bef464a2aa50d0a5145542aaf414445399f3eb6d5693c0f3ebed1fdb90b58a5f88a5c834a3344624baf7b457d622a4697534ada271ad97532eac9 languageName: node linkType: hard -"@formatjs/ecma402-abstract@npm:2.3.4": - version: 2.3.4 - resolution: "@formatjs/ecma402-abstract@npm:2.3.4" +"@formatjs/ecma402-abstract@npm:2.3.6": + version: 2.3.6 + resolution: "@formatjs/ecma402-abstract@npm:2.3.6" dependencies: "@formatjs/fast-memoize": "npm:2.2.7" - "@formatjs/intl-localematcher": "npm:0.6.1" + "@formatjs/intl-localematcher": "npm:0.6.2" decimal.js: "npm:^10.4.3" tslib: "npm:^2.8.0" - checksum: 10c0/2644bc618a34dc610ef9691281eeb45ae6175e6982cf19f1bd140672fc95c748747ce3c85b934649ea7e4a304f7ae0060625fd53d5df76f92ca3acf743e1eb0a + checksum: 10c0/63be2a73d3168bf45ab5d50db58376e852db5652d89511ae6e44f1fa03ad96ebbfe9b06a1dfaa743db06e40eb7f33bd77530b9388289855cca79a0e3fc29eacf languageName: node linkType: hard @@ -2406,75 +2596,74 @@ __metadata: languageName: node linkType: hard -"@formatjs/icu-messageformat-parser@npm:2.11.2": - version: 2.11.2 - resolution: "@formatjs/icu-messageformat-parser@npm:2.11.2" +"@formatjs/icu-messageformat-parser@npm:2.11.4": + version: 2.11.4 + resolution: "@formatjs/icu-messageformat-parser@npm:2.11.4" dependencies: - "@formatjs/ecma402-abstract": "npm:2.3.4" - "@formatjs/icu-skeleton-parser": "npm:1.8.14" + "@formatjs/ecma402-abstract": "npm:2.3.6" + "@formatjs/icu-skeleton-parser": "npm:1.8.16" tslib: "npm:^2.8.0" - checksum: 10c0/a121f2d2c6b36a1632ffd64c3545e2500c8ee0f7fee5db090318c035d635c430ab123faedb5d000f18d9423a7b55fbf670b84e2e2dd72cc307a38aed61d3b2e0 + checksum: 10c0/3ea9e9dae18282881d19a5f88107b6013f514ec8675684ed2c04bee2a174032377858937243e3bd9c9263a470988a3773a53bf8d208a34a78e7843ce66f87f3b languageName: node linkType: hard -"@formatjs/icu-skeleton-parser@npm:1.8.14": - version: 1.8.14 - resolution: "@formatjs/icu-skeleton-parser@npm:1.8.14" +"@formatjs/icu-skeleton-parser@npm:1.8.16": + version: 1.8.16 + resolution: "@formatjs/icu-skeleton-parser@npm:1.8.16" dependencies: - "@formatjs/ecma402-abstract": "npm:2.3.4" + "@formatjs/ecma402-abstract": "npm:2.3.6" tslib: "npm:^2.8.0" - checksum: 10c0/a1807ed6e90b8a2e8d0e5b5125e6f9a2c057d3cff377fb031d2333af7cfaa6de4ed3a15c23da7294d4c3557f8b28b2163246434a19720f26b5db0497d97e9b58 + checksum: 10c0/6fa1586dc11c925cd8d17e927cc635d238c969a6b7e97282a924376f78622fc25336c407589d19796fb6f8124a0e7765f99ecdb1aac014edcfbe852e7c3d87f3 languageName: node linkType: hard -"@formatjs/intl-localematcher@npm:0.6.1": - version: 0.6.1 - resolution: "@formatjs/intl-localematcher@npm:0.6.1" +"@formatjs/intl-localematcher@npm:0.6.2": + version: 0.6.2 + resolution: "@formatjs/intl-localematcher@npm:0.6.2" dependencies: tslib: "npm:^2.8.0" - checksum: 10c0/bacbedd508519c1bb5ca2620e89dc38f12101be59439aa14aa472b222915b462cb7d679726640f6dcf52a05dd218b5aa27ccd60f2e5010bb96f1d4929848cde0 + checksum: 10c0/22a17a4c67160b6c9f52667914acfb7b79cd6d80630d4ac6d4599ce447cb89d2a64f7d58fa35c3145ddb37fef893f0a45b9a55e663a4eb1f2ae8b10a89fac235 languageName: node linkType: hard "@formatjs/intl-pluralrules@npm:^5.4.4": - version: 5.4.4 - resolution: "@formatjs/intl-pluralrules@npm:5.4.4" + version: 5.4.6 + resolution: "@formatjs/intl-pluralrules@npm:5.4.6" dependencies: - "@formatjs/ecma402-abstract": "npm:2.3.4" - "@formatjs/intl-localematcher": "npm:0.6.1" + "@formatjs/ecma402-abstract": "npm:2.3.6" + "@formatjs/intl-localematcher": "npm:0.6.2" decimal.js: "npm:^10.4.3" tslib: "npm:^2.8.0" - checksum: 10c0/8a8ec9f2fad40d9fa654a68de06fb18aaa6f0eafa908f41397f057366740625c12da627c6de68e0396fcd67ceaaa2c5c20a4b102f71ac8694abd9e76cceca949 + checksum: 10c0/95dd6fb3e9bd84ce44cc194f6f815d690703bd60b75bf2ae895535d2d9a1a675765879de9b54f854882fc1335cbfac6a535873d5b2d75cc5ca93c6ca172aa272 languageName: node linkType: hard -"@formatjs/intl@npm:3.1.6": - version: 3.1.6 - resolution: "@formatjs/intl@npm:3.1.6" +"@formatjs/intl@npm:3.1.8": + version: 3.1.8 + resolution: "@formatjs/intl@npm:3.1.8" dependencies: - "@formatjs/ecma402-abstract": "npm:2.3.4" + "@formatjs/ecma402-abstract": "npm:2.3.6" "@formatjs/fast-memoize": "npm:2.2.7" - "@formatjs/icu-messageformat-parser": "npm:2.11.2" - intl-messageformat: "npm:10.7.16" + "@formatjs/icu-messageformat-parser": "npm:2.11.4" + intl-messageformat: "npm:10.7.18" tslib: "npm:^2.8.0" peerDependencies: typescript: ^5.6.0 peerDependenciesMeta: typescript: optional: true - checksum: 10c0/a31f8d2569c9f2384f67a76f1cc2c8bfc2721c97a7dee0e971b6cfc0f223449bab0cfdc29140e3b71d74b04573c20ee8600909d256293e296a809da69a141530 + checksum: 10c0/b291e867bcde491737f70254ec30898e120f36784b5ee2911dcc271fbd744e90382f03232ac7f5a55d46071f4ffccfc84b63445734117b75ca1ced659f6b7827 languageName: node linkType: hard -"@formatjs/ts-transformer@npm:3.14.0": - version: 3.14.0 - resolution: "@formatjs/ts-transformer@npm:3.14.0" +"@formatjs/ts-transformer@npm:3.14.2": + version: 3.14.2 + resolution: "@formatjs/ts-transformer@npm:3.14.2" dependencies: - "@formatjs/icu-messageformat-parser": "npm:2.11.2" - "@types/json-stable-stringify": "npm:^1.1.0" + "@formatjs/icu-messageformat-parser": "npm:2.11.4" "@types/node": "npm:^22.0.0" chalk: "npm:^4.1.2" - json-stable-stringify: "npm:^1.1.1" + json-stable-stringify: "npm:^1.3.0" tslib: "npm:^2.8.0" typescript: "npm:^5.6.0" peerDependencies: @@ -2482,7 +2671,7 @@ __metadata: peerDependenciesMeta: ts-jest: optional: true - checksum: 10c0/80599a9e76685d710aeba88857fc3d682cc4f17996e870c582a40288e149d6dd496a3be0684682f7ed3892ca677c6b9cbd6b804dbf457da87b493a7a756a18a1 + checksum: 10c0/990cf49cdc318e37825ec26b1b24d7368e89c5d03184867a4accd8b35d6d6d99a20a8abe6366c9870e56da9e04f4672990ca428686306c9ad8204b401c7d19f8 languageName: node linkType: hard @@ -2595,10 +2784,26 @@ __metadata: languageName: node linkType: hard -"@ioredis/commands@npm:^1.3.0": - version: 1.3.0 - resolution: "@ioredis/commands@npm:1.3.0" - checksum: 10c0/5ab990a8f69c20daf3d7d64307aa9f13ee727c92ab4c7664a6943bb500227667a0c368892e9c4913f06416377db47dba78d58627fe723da476d25f2c04a6d5aa +"@ioredis/commands@npm:1.4.0": + version: 1.4.0 + resolution: "@ioredis/commands@npm:1.4.0" + checksum: 10c0/99afe21fba794f84a2b84cceabcc370a7622e7b8b97a6589456c07c9fa62a15d54c5546f6f7214fb9a2458b1fa87579d5c531aaf48e06cc9be156d5923892c8d + languageName: node + linkType: hard + +"@isaacs/balanced-match@npm:^4.0.1": + version: 4.0.1 + resolution: "@isaacs/balanced-match@npm:4.0.1" + checksum: 10c0/7da011805b259ec5c955f01cee903da72ad97c5e6f01ca96197267d3f33103d5b2f8a1af192140f3aa64526c593c8d098ae366c2b11f7f17645d12387c2fd420 + languageName: node + linkType: hard + +"@isaacs/brace-expansion@npm:^5.0.0": + version: 5.0.0 + resolution: "@isaacs/brace-expansion@npm:5.0.0" + dependencies: + "@isaacs/balanced-match": "npm:^4.0.1" + checksum: 10c0/b4d4812f4be53afc2c5b6c545001ff7a4659af68d4484804e9d514e183d20269bb81def8682c01a22b17c4d6aed14292c8494f7d2ac664e547101c1a905aa977 languageName: node linkType: hard @@ -2625,13 +2830,6 @@ __metadata: languageName: node linkType: hard -"@istanbuljs/schema@npm:^0.1.2": - version: 0.1.3 - resolution: "@istanbuljs/schema@npm:0.1.3" - checksum: 10c0/61c5286771676c9ca3eb2bd8a7310a9c063fb6e0e9712225c8471c582d157392c88f5353581c8c9adbe0dff98892317d2fdfc56c3499aa42e0194405206a963a - languageName: node - linkType: hard - "@joshwooding/vite-plugin-react-docgen-typescript@npm:0.6.1": version: 0.6.1 resolution: "@joshwooding/vite-plugin-react-docgen-typescript@npm:0.6.1" @@ -2686,29 +2884,32 @@ __metadata: languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.0": - version: 1.5.0 - resolution: "@jridgewell/sourcemap-codec@npm:1.5.0" - checksum: 10c0/2eb864f276eb1096c3c11da3e9bb518f6d9fc0023c78344cdc037abadc725172c70314bdb360f2d4b7bffec7f5d657ce006816bc5d4ecb35e61b66132db00c18 +"@jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.0, @jridgewell/sourcemap-codec@npm:^1.5.5": + version: 1.5.5 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" + checksum: 10c0/f9e538f302b63c0ebc06eecb1dd9918dd4289ed36147a0ddce35d6ea4d7ebbda243cda7b2213b6a5e1d8087a298d5cf630fb2bd39329cdecb82017023f6081a0 languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.23, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.28": - version: 0.3.29 - resolution: "@jridgewell/trace-mapping@npm:0.3.29" +"@jridgewell/trace-mapping@npm:^0.3.23, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.28, @jridgewell/trace-mapping@npm:^0.3.31": + version: 0.3.31 + resolution: "@jridgewell/trace-mapping@npm:0.3.31" dependencies: "@jridgewell/resolve-uri": "npm:^3.1.0" "@jridgewell/sourcemap-codec": "npm:^1.4.14" - checksum: 10c0/fb547ba31658c4d74eb17e7389f4908bf7c44cef47acb4c5baa57289daf68e6fe53c639f41f751b3923aca67010501264f70e7b49978ad1f040294b22c37b333 + checksum: 10c0/4b30ec8cd56c5fd9a661f088230af01e0c1a3888d11ffb6b47639700f71225be21d1f7e168048d6d4f9449207b978a235c07c8f15c07705685d16dc06280e9d9 languageName: node linkType: hard -"@keyv/bigmap@npm:^1.0.0": - version: 1.0.2 - resolution: "@keyv/bigmap@npm:1.0.2" +"@keyv/bigmap@npm:^1.1.0": + version: 1.3.0 + resolution: "@keyv/bigmap@npm:1.3.0" dependencies: - hookified: "npm:^1.12.1" - checksum: 10c0/1fe415265241b015c19891dc6c1909b41a5a033e57339b40f85af27355d2f52b52df01795a3f7ba37d3ec2b67e147c05914965775254ff8dbd1701adab45208a + hashery: "npm:^1.2.0" + hookified: "npm:^1.13.0" + peerDependencies: + keyv: ^5.5.4 + checksum: 10c0/68fe63451097067d8359dc25b7e5b832fe9d99493ca32602686026c8d14c9ca7ecaf19312df9420c7f54df8de1b26e7305f9189fa364a82f39730710eb895f9e languageName: node linkType: hard @@ -2732,14 +2933,13 @@ __metadata: "@formatjs/intl-pluralrules": "npm:^5.4.4" "@gamestdio/websocket": "npm:^0.3.2" "@github/webauthn-json": "npm:^2.1.1" - "@optimize-lodash/rollup-plugin": "npm:^5.0.2" - "@rails/ujs": "npm:7.1.502" + "@optimize-lodash/rollup-plugin": "npm:^6.0.0" "@react-spring/web": "npm:^9.7.5" "@reduxjs/toolkit": "npm:^2.0.1" - "@storybook/addon-a11y": "npm:^9.1.1" - "@storybook/addon-docs": "npm:^9.1.1" - "@storybook/addon-vitest": "npm:^9.1.1" - "@storybook/react-vite": "npm:^9.1.1" + "@storybook/addon-a11y": "npm:^10.0.6" + "@storybook/addon-docs": "npm:^10.0.6" + "@storybook/addon-vitest": "npm:^10.0.6" + "@storybook/react-vite": "npm:^10.0.6" "@testing-library/dom": "npm:^10.4.1" "@testing-library/react": "npm:^16.3.0" "@types/debug": "npm:^4" @@ -2753,7 +2953,6 @@ __metadata: "@types/object-assign": "npm:^4.0.30" "@types/prop-types": "npm:^15.7.5" "@types/punycode": "npm:^2.1.0" - "@types/rails__ujs": "npm:^6.0.4" "@types/react": "npm:^18.2.7" "@types/react-dom": "npm:^18.2.4" "@types/react-helmet": "npm:^6.1.6" @@ -2761,7 +2960,6 @@ __metadata: "@types/react-router": "npm:^5.1.20" "@types/react-router-dom": "npm:^5.3.3" "@types/react-sparklines": "npm:^1.7.2" - "@types/react-swipeable-views": "npm:^0.13.1" "@types/react-test-renderer": "npm:^18.0.0" "@types/react-toggle": "npm:^4.0.3" "@types/redux-immutable": "npm:^4.0.3" @@ -2769,9 +2967,10 @@ __metadata: "@use-gesture/react": "npm:^10.3.1" "@vitejs/plugin-legacy": "npm:^7.2.1" "@vitejs/plugin-react": "npm:^5.0.0" - "@vitest/browser": "npm:^3.2.4" - "@vitest/coverage-v8": "npm:^3.2.4" - "@vitest/ui": "npm:^3.2.4" + "@vitest/browser": "npm:^4.0.5" + "@vitest/browser-playwright": "npm:^4.0.5" + "@vitest/coverage-v8": "npm:^4.0.5" + "@vitest/ui": "npm:^4.0.5" arrow-key-navigation: "npm:^1.2.0" async-mutex: "npm:^0.5.0" atrament: "npm:0.2.4" @@ -2779,13 +2978,14 @@ __metadata: babel-plugin-formatjs: "npm:^10.5.37" babel-plugin-transform-react-remove-prop-types: "npm:^0.4.24" blurhash: "npm:^2.0.5" - chromatic: "npm:^13.1.3" + chromatic: "npm:^13.3.3" classnames: "npm:^2.3.2" cocoon-js-vanilla: "npm:^1.5.1" color-blend: "npm:^4.0.0" core-js: "npm:^3.30.2" cross-env: "npm:^10.0.0" debug: "npm:^4.4.1" + delegated-events: "npm:^1.1.2" detect-passive-events: "npm:^2.0.3" emoji-mart: "npm:emoji-mart-lazyload@latest" emojibase: "npm:^16.0.0" @@ -2796,12 +2996,12 @@ __metadata: eslint-import-resolver-typescript: "npm:^4.2.5" eslint-plugin-formatjs: "npm:^5.3.1" eslint-plugin-import: "npm:~2.32.0" - eslint-plugin-jsdoc: "npm:^54.0.0" + eslint-plugin-jsdoc: "npm:^61.1.11" eslint-plugin-jsx-a11y: "npm:~6.10.2" eslint-plugin-promise: "npm:~7.2.1" eslint-plugin-react: "npm:^7.37.4" - eslint-plugin-react-hooks: "npm:^5.2.0" - eslint-plugin-storybook: "npm:^9.0.4" + eslint-plugin-react-hooks: "npm:^7.0.1" + eslint-plugin-storybook: "npm:^10.0.2" fake-indexeddb: "npm:^6.0.1" fast-glob: "npm:^3.3.3" favico.js: "npm:^0.3.10" @@ -2816,13 +3016,13 @@ __metadata: intl-messageformat: "npm:^10.7.16" js-yaml: "npm:^4.1.0" lande: "npm:^1.0.10" - lint-staged: "npm:^16.0.0" + lint-staged: "npm:^16.2.6" lodash: "npm:^4.17.21" marky: "npm:^1.2.5" - msw: "npm:^2.10.2" - msw-storybook-addon: "npm:^2.0.5" + msw: "npm:^2.12.1" + msw-storybook-addon: "npm:^2.0.6" path-complete-extname: "npm:^1.0.0" - playwright: "npm:^1.54.1" + playwright: "npm:^1.57.0" postcss-preset-env: "npm:^10.1.5" prettier: "npm:^3.3.3" prop-types: "npm:^15.8.1" @@ -2838,10 +3038,8 @@ __metadata: react-redux-loading-bar: "npm:^5.0.8" react-router: "npm:^5.3.4" react-router-dom: "npm:^5.3.4" - react-router-scroll-4: "npm:^1.0.0-beta.1" react-select: "npm:^5.7.3" react-sparklines: "npm:^1.7.0" - react-swipeable-views: "npm:^0.14.0" react-test-renderer: "npm:^18.2.0" react-textarea-autosize: "npm:^8.4.1" react-toggle: "npm:^4.1.3" @@ -2851,26 +3049,27 @@ __metadata: rollup-plugin-gzip: "npm:^4.1.1" rollup-plugin-visualizer: "npm:^6.0.3" sass: "npm:^1.62.1" + scroll-behavior: "npm:^0.11.0" stacktrace-js: "npm:^2.0.2" - storybook: "npm:^9.1.1" + storybook: "npm:^10.0.5" stringz: "npm:^2.1.0" stylelint: "npm:^16.19.1" stylelint-config-prettier-scss: "npm:^1.0.0" - stylelint-config-standard-scss: "npm:^15.0.1" + stylelint-config-standard-scss: "npm:^16.0.0" substring-trie: "npm:^1.0.2" - tesseract.js: "npm:^6.0.0" + tesseract.js: "npm:^7.0.0" tiny-queue: "npm:^0.2.1" twitter-text: "npm:3.1.0" - typescript: "npm:~5.7.3" - typescript-eslint: "npm:^8.29.1" + typescript: "npm:~5.9.0" + typescript-eslint: "npm:^8.45.0" + typescript-plugin-css-modules: "npm:^5.2.0" use-debounce: "npm:^10.0.0" vite: "npm:^7.1.1" vite-plugin-manifest-sri: "npm:^0.2.0" vite-plugin-pwa: "npm:^1.0.2" - vite-plugin-static-copy: "npm:^3.1.1" vite-plugin-svgr: "npm:^4.3.0" - vite-tsconfig-paths: "npm:^5.1.4" - vitest: "npm:^3.2.4" + vite-tsconfig-paths: "npm:^6.0.0" + vitest: "npm:^4.0.5" wicg-inert: "npm:^3.1.2" workbox-expiration: "npm:^7.3.0" workbox-routing: "npm:^7.3.0" @@ -2892,27 +3091,26 @@ __metadata: dependencies: "@eslint/js": "npm:^9.23.0" "@types/cors": "npm:^2.8.16" - "@types/express": "npm:^4.17.17" + "@types/express": "npm:^5.0.5" "@types/pg": "npm:^8.6.6" - "@types/uuid": "npm:^10.0.0" "@types/ws": "npm:^8.5.9" bufferutil: "npm:^4.0.7" cors: "npm:^2.8.5" dotenv: "npm:^16.0.3" - express: "npm:^4.18.2" + express: "npm:^5.1.0" globals: "npm:^16.0.0" ioredis: "npm:^5.3.2" jsdom: "npm:^27.0.0" pg: "npm:^8.5.0" pg-connection-string: "npm:^2.6.0" - pino: "npm:^9.0.0" - pino-http: "npm:^10.0.0" + pino: "npm:^10.0.0" + pino-http: "npm:^11.0.0" pino-pretty: "npm:^13.0.0" prom-client: "npm:^15.0.0" - typescript: "npm:~5.7.3" + typescript: "npm:~5.9.0" typescript-eslint: "npm:^8.28.0" utf-8-validate: "npm:^6.0.3" - uuid: "npm:^11.0.0" + uuid: "npm:^13.0.0" ws: "npm:^8.12.1" dependenciesMeta: bufferutil: @@ -2934,9 +3132,9 @@ __metadata: languageName: node linkType: hard -"@mswjs/interceptors@npm:^0.39.1": - version: 0.39.2 - resolution: "@mswjs/interceptors@npm:0.39.2" +"@mswjs/interceptors@npm:^0.40.0": + version: 0.40.0 + resolution: "@mswjs/interceptors@npm:0.40.0" dependencies: "@open-draft/deferred-promise": "npm:^2.2.0" "@open-draft/logger": "npm:^0.3.0" @@ -2944,7 +3142,7 @@ __metadata: is-node-process: "npm:^1.2.0" outvariant: "npm:^1.4.3" strict-event-emitter: "npm:^0.5.1" - checksum: 10c0/5698e33930a6b6e7cc78cf762291be60c91c6348faa22750acc41ef41528e7891e74541ccfb668ba470d964233fd2121c44d0224a2917eedeba2459cf0b78ca2 + checksum: 10c0/4500f17b65910b2633182fdb15a81ccb6ccd4488a8c45bc2f7acdaaff4621c3cce5362e6b59ddc4fa28d315d0efb0608fd1f0d536bc5345141f8ac03fd7fab22 languageName: node linkType: hard @@ -3039,25 +3237,25 @@ __metadata: languageName: node linkType: hard -"@optimize-lodash/rollup-plugin@npm:^5.0.2": - version: 5.0.2 - resolution: "@optimize-lodash/rollup-plugin@npm:5.0.2" +"@optimize-lodash/rollup-plugin@npm:^6.0.0": + version: 6.0.0 + resolution: "@optimize-lodash/rollup-plugin@npm:6.0.0" dependencies: - "@optimize-lodash/transform": "npm:3.0.6" + "@optimize-lodash/transform": "npm:4.0.0" "@rollup/pluginutils": "npm:^5.1.0" peerDependencies: rollup: ">= 4.x" - checksum: 10c0/ad3a6baafe1422a2bc0c2c2d1e32961214431ff714f4fff793b65174291b931a55aa2797507115e3e0cb8473c311611c817c7d70d2b591473c5930500bc9edd7 + checksum: 10c0/2332d642d3e48c5e58618d4b21e77b9b0d723dc8742ca8b49868dd85f931cdce222d0fe3a94948e435888731ed8d33ddc5d1374a200cef074693505c1c66ea15 languageName: node linkType: hard -"@optimize-lodash/transform@npm:3.0.6": - version: 3.0.6 - resolution: "@optimize-lodash/transform@npm:3.0.6" +"@optimize-lodash/transform@npm:4.0.0": + version: 4.0.0 + resolution: "@optimize-lodash/transform@npm:4.0.0" dependencies: estree-walker: "npm:^2.0.2" magic-string: "npm:~0.30.11" - checksum: 10c0/48a877a41e8cb642daccd3a9d815b05c55ac63fc2fdeea6f3e60239e8dcf46a3cd56f6699d67dfd0aebb2e2bb0f019fde6dd7880fb13a7a6db99a53392273578 + checksum: 10c0/8c32212f2ef49103894a75f91958aae43d0498d9db636fb2b7d043d87f241a5a747bff20d0bd5a1595b8704af92cc293806ce00f90836e86c21c1da02672cd48 languageName: node linkType: hard @@ -3205,6 +3403,13 @@ __metadata: languageName: node linkType: hard +"@pinojs/redact@npm:^0.4.0": + version: 0.4.0 + resolution: "@pinojs/redact@npm:0.4.0" + checksum: 10c0/4b311ba17ee0cf154ff9c39eb063ec04cd0d0017cb3750efcdf06c2d485df3e1095e13e872175993568c5568c23e4508dd877c981bbc9c5ae5e384d569efcdff + languageName: node + linkType: hard + "@pkgjs/parseargs@npm:^0.11.0": version: 0.11.0 resolution: "@pkgjs/parseargs@npm:0.11.0" @@ -3226,13 +3431,6 @@ __metadata: languageName: node linkType: hard -"@rails/ujs@npm:7.1.502": - version: 7.1.502 - resolution: "@rails/ujs@npm:7.1.502" - checksum: 10c0/79b46e8abd03e3fc633d93cc4e4c23838c628b775802fb38c2ce68b0e609ce287a67b81db112a93cc78c07ec82ca3b4cf87e74eb556d35148ce5f64c8be9201f - languageName: node - linkType: hard - "@react-spring/animated@npm:~9.7.5": version: 9.7.5 resolution: "@react-spring/animated@npm:9.7.5" @@ -3300,8 +3498,8 @@ __metadata: linkType: hard "@reduxjs/toolkit@npm:^2.0.1": - version: 2.9.0 - resolution: "@reduxjs/toolkit@npm:2.9.0" + version: 2.9.2 + resolution: "@reduxjs/toolkit@npm:2.9.2" dependencies: "@standard-schema/spec": "npm:^1.0.0" "@standard-schema/utils": "npm:^0.3.0" @@ -3317,7 +3515,7 @@ __metadata: optional: true react-redux: optional: true - checksum: 10c0/eef65436b3cd96a264de09e94b8a9d585773578442ef3c1c5f2b3bb261a727405e89b004965198f95c5391645b7dbc6576dc07b46de1bede1d6c62c13c17c7d0 + checksum: 10c0/577416200c76ffd82bce6158aaeb63e836ed2c2a14e670253056dcaec505da77643e79b47208b4e493a0c120a4a2bc049efe60cd555a2699053af5b03f2f2953 languageName: node linkType: hard @@ -3332,10 +3530,10 @@ __metadata: languageName: node linkType: hard -"@rolldown/pluginutils@npm:1.0.0-beta.35": - version: 1.0.0-beta.35 - resolution: "@rolldown/pluginutils@npm:1.0.0-beta.35" - checksum: 10c0/feb6ab8f77ef2bde675099409c3ccd6a168f35a3c3e88482df3ca42494260fd42befe36e8e90ce358847a12aaab94cd8fe7069cf1e905edf91eb411d933906d9 +"@rolldown/pluginutils@npm:1.0.0-beta.53": + version: 1.0.0-beta.53 + resolution: "@rolldown/pluginutils@npm:1.0.0-beta.53" + checksum: 10c0/e8b0a7eb76be22f6f103171f28072de821525a4e400454850516da91a7381957932ff0ce495f227bcb168e86815788b0c1d249ca9e34dca366a82c8825b714ce languageName: node linkType: hard @@ -3431,142 +3629,142 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-android-arm-eabi@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-android-arm-eabi@npm:4.46.2" +"@rollup/rollup-android-arm-eabi@npm:4.46.4": + version: 4.46.4 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.46.4" conditions: os=android & cpu=arm languageName: node linkType: hard -"@rollup/rollup-android-arm64@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-android-arm64@npm:4.46.2" +"@rollup/rollup-android-arm64@npm:4.46.4": + version: 4.46.4 + resolution: "@rollup/rollup-android-arm64@npm:4.46.4" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-arm64@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-darwin-arm64@npm:4.46.2" +"@rollup/rollup-darwin-arm64@npm:4.46.4": + version: 4.46.4 + resolution: "@rollup/rollup-darwin-arm64@npm:4.46.4" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-x64@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-darwin-x64@npm:4.46.2" +"@rollup/rollup-darwin-x64@npm:4.46.4": + version: 4.46.4 + resolution: "@rollup/rollup-darwin-x64@npm:4.46.4" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-freebsd-arm64@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-freebsd-arm64@npm:4.46.2" +"@rollup/rollup-freebsd-arm64@npm:4.46.4": + version: 4.46.4 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.46.4" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-freebsd-x64@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-freebsd-x64@npm:4.46.2" +"@rollup/rollup-freebsd-x64@npm:4.46.4": + version: 4.46.4 + resolution: "@rollup/rollup-freebsd-x64@npm:4.46.4" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-linux-arm-gnueabihf@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.46.2" +"@rollup/rollup-linux-arm-gnueabihf@npm:4.46.4": + version: 4.46.4 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.46.4" conditions: os=linux & cpu=arm & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm-musleabihf@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.46.2" +"@rollup/rollup-linux-arm-musleabihf@npm:4.46.4": + version: 4.46.4 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.46.4" conditions: os=linux & cpu=arm & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-arm64-gnu@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.46.2" +"@rollup/rollup-linux-arm64-gnu@npm:4.46.4": + version: 4.46.4 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.46.4" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm64-musl@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-linux-arm64-musl@npm:4.46.2" +"@rollup/rollup-linux-arm64-musl@npm:4.46.4": + version: 4.46.4 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.46.4" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-loongarch64-gnu@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.46.2" +"@rollup/rollup-linux-loongarch64-gnu@npm:4.46.4": + version: 4.46.4 + resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.46.4" conditions: os=linux & cpu=loong64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-ppc64-gnu@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.46.2" +"@rollup/rollup-linux-ppc64-gnu@npm:4.46.4": + version: 4.46.4 + resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.46.4" conditions: os=linux & cpu=ppc64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-gnu@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.46.2" +"@rollup/rollup-linux-riscv64-gnu@npm:4.46.4": + version: 4.46.4 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.46.4" conditions: os=linux & cpu=riscv64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-musl@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.46.2" +"@rollup/rollup-linux-riscv64-musl@npm:4.46.4": + version: 4.46.4 + resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.46.4" conditions: os=linux & cpu=riscv64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-s390x-gnu@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.46.2" +"@rollup/rollup-linux-s390x-gnu@npm:4.46.4": + version: 4.46.4 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.46.4" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-gnu@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-linux-x64-gnu@npm:4.46.2" +"@rollup/rollup-linux-x64-gnu@npm:4.46.4": + version: 4.46.4 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.46.4" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-musl@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-linux-x64-musl@npm:4.46.2" +"@rollup/rollup-linux-x64-musl@npm:4.46.4": + version: 4.46.4 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.46.4" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-win32-arm64-msvc@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.46.2" +"@rollup/rollup-win32-arm64-msvc@npm:4.46.4": + version: 4.46.4 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.46.4" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-win32-ia32-msvc@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.46.2" +"@rollup/rollup-win32-ia32-msvc@npm:4.46.4": + version: 4.46.4 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.46.4" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@rollup/rollup-win32-x64-msvc@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-win32-x64-msvc@npm:4.46.2" +"@rollup/rollup-win32-x64-msvc@npm:4.46.4": + version: 4.46.4 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.46.4" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -3578,6 +3776,13 @@ __metadata: languageName: node linkType: hard +"@sindresorhus/base62@npm:^1.0.0": + version: 1.0.0 + resolution: "@sindresorhus/base62@npm:1.0.0" + checksum: 10c0/9a14df0f058fdf4731c30f0f05728a4822144ee42236030039d7fa5a1a1072c2879feba8091fd4a17c8922d1056bc07bada77c31fddc3e15836fc05a266fd918 + languageName: node + linkType: hard + "@standard-schema/spec@npm:^1.0.0": version: 1.0.0 resolution: "@standard-schema/spec@npm:1.0.0" @@ -3592,80 +3797,96 @@ __metadata: languageName: node linkType: hard -"@storybook/addon-a11y@npm:^9.1.1": - version: 9.1.7 - resolution: "@storybook/addon-a11y@npm:9.1.7" +"@storybook/addon-a11y@npm:^10.0.6": + version: 10.0.6 + resolution: "@storybook/addon-a11y@npm:10.0.6" dependencies: "@storybook/global": "npm:^5.0.0" axe-core: "npm:^4.2.0" peerDependencies: - storybook: ^9.1.7 - checksum: 10c0/c34f057acec41d6116669663e2a18b230c8905c409de6ef7fcff2cfe00741b8c435165c43301565e99ff275e541180de866c48c26d2676313334e2dd4696f5a3 + storybook: ^10.0.6 + checksum: 10c0/44ab785b48476a9de44686be2d60c3720ed59c0d95d4ca7e5693ec2acb0cc2b062e4abde0a04f6bc0aeb54156e00a65803bb85c2a21f69c8329139096316b299 languageName: node linkType: hard -"@storybook/addon-docs@npm:^9.1.1": - version: 9.1.7 - resolution: "@storybook/addon-docs@npm:9.1.7" +"@storybook/addon-docs@npm:^10.0.6": + version: 10.0.6 + resolution: "@storybook/addon-docs@npm:10.0.6" dependencies: "@mdx-js/react": "npm:^3.0.0" - "@storybook/csf-plugin": "npm:9.1.7" - "@storybook/icons": "npm:^1.4.0" - "@storybook/react-dom-shim": "npm:9.1.7" + "@storybook/csf-plugin": "npm:10.0.6" + "@storybook/icons": "npm:^1.6.0" + "@storybook/react-dom-shim": "npm:10.0.6" react: "npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" react-dom: "npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^9.1.7 - checksum: 10c0/81f530b1230c60d344662aedb4839baa099da287c0188e8d5e10ab8cac0b02bb747e7d6475dbb1a85cba0cb5c475b8d48936355dd359020652ccbaba7d664aa0 + storybook: ^10.0.6 + checksum: 10c0/4b1a59416cf54853a09a156e1e1016c13f4d477f73562be960b12d86eeb86d1f1d2da5111255cc9da1cd232955d0ea68b1c42e4e87671042682dcb7eb2a059a0 languageName: node linkType: hard -"@storybook/addon-vitest@npm:^9.1.1": - version: 9.1.7 - resolution: "@storybook/addon-vitest@npm:9.1.7" +"@storybook/addon-vitest@npm:^10.0.6": + version: 10.0.6 + resolution: "@storybook/addon-vitest@npm:10.0.6" dependencies: "@storybook/global": "npm:^5.0.0" - "@storybook/icons": "npm:^1.4.0" + "@storybook/icons": "npm:^1.6.0" prompts: "npm:^2.4.0" ts-dedent: "npm:^2.2.0" peerDependencies: - "@vitest/browser": ^3.0.0 - "@vitest/runner": ^3.0.0 - storybook: ^9.1.7 - vitest: ^3.0.0 + "@vitest/browser": ^3.0.0 || ^4.0.0 + "@vitest/browser-playwright": ^4.0.0 + "@vitest/runner": ^3.0.0 || ^4.0.0 + storybook: ^10.0.6 + vitest: ^3.0.0 || ^4.0.0 peerDependenciesMeta: "@vitest/browser": optional: true + "@vitest/browser-playwright": + optional: true "@vitest/runner": optional: true vitest: optional: true - checksum: 10c0/744358491481d21e7728d07bd244038d378b5883257c3a2291b2fa5abef917493c1421921bc020fecfb15740a1b6331fdf598b15c91961ffc7ea924f8d945957 + checksum: 10c0/6377cfbac4c2f9f9b43006131e0273c2d2f8b76b0ce01fdcb42f2935ac245a2236fab60b2543507a69852dba1ac4e3b770b0a96f5595123e0f9599609d0a57e2 languageName: node linkType: hard -"@storybook/builder-vite@npm:9.1.7": - version: 9.1.7 - resolution: "@storybook/builder-vite@npm:9.1.7" +"@storybook/builder-vite@npm:10.0.6": + version: 10.0.6 + resolution: "@storybook/builder-vite@npm:10.0.6" dependencies: - "@storybook/csf-plugin": "npm:9.1.7" + "@storybook/csf-plugin": "npm:10.0.6" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^9.1.7 + storybook: ^10.0.6 vite: ^5.0.0 || ^6.0.0 || ^7.0.0 - checksum: 10c0/39306d65373a934c4274196d5451467acb4128252c61b7abb801153fdf9b8278ca4ee8c9911def229f9c6045f24d39610fb5f0e553892478e9f541cdb9287859 + checksum: 10c0/1e5f163f2abd62f99292ee48cde10f68d8db1ba6f4613c20cb2af679d44c3b548c7a2209338d24b4ffda2a245ae68bfcfc57af9f76de7f4a251253635c4179d8 languageName: node linkType: hard -"@storybook/csf-plugin@npm:9.1.7": - version: 9.1.7 - resolution: "@storybook/csf-plugin@npm:9.1.7" +"@storybook/csf-plugin@npm:10.0.6": + version: 10.0.6 + resolution: "@storybook/csf-plugin@npm:10.0.6" dependencies: - unplugin: "npm:^1.3.1" + unplugin: "npm:^2.3.5" peerDependencies: - storybook: ^9.1.7 - checksum: 10c0/059fa960174a3f421eb09b41d0da43c43a46548a2535acc17a0e7989bb78f954180212a43b58392dfec6e92a3482621a13ec1b6ff00d1157a2355d369129842a + esbuild: "*" + rollup: "*" + storybook: ^10.0.6 + vite: "*" + webpack: "*" + peerDependenciesMeta: + esbuild: + optional: true + rollup: + optional: true + vite: + optional: true + webpack: + optional: true + checksum: 10c0/422286e7d2ef3f64ea2a71bdd1cad0bc3e850b31574f048529616eeb3cd0b1216b5d680c8b36bd300e31d141ad7781586cc7d57763babf993c31430b854491c4 languageName: node linkType: hard @@ -3676,64 +3897,74 @@ __metadata: languageName: node linkType: hard -"@storybook/icons@npm:^1.4.0": - version: 1.4.0 - resolution: "@storybook/icons@npm:1.4.0" +"@storybook/icons@npm:^1.6.0": + version: 1.6.0 + resolution: "@storybook/icons@npm:1.6.0" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - checksum: 10c0/fd0514fb3fa431a8b5939fe1d9fc336b253ef2c25b34792d2d4ee59e13321108d34f8bf223a0981482f54f83c5ef47ffd1a98c376ca9071011c1b8afe2b01d43 + checksum: 10c0/bbec9201a78a730195f9cf377b15856dc414a54d04e30d16c379d062425cc617bfd0d6586ba1716012cfbdab461f0c9693a6a52920f9bd09c7b4291fb116f59c languageName: node linkType: hard -"@storybook/react-dom-shim@npm:9.1.7": - version: 9.1.7 - resolution: "@storybook/react-dom-shim@npm:9.1.7" +"@storybook/icons@npm:^2.0.0": + version: 2.0.1 + resolution: "@storybook/icons@npm:2.0.1" peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^9.1.7 - checksum: 10c0/bed0ebf47f2d2027a28d82be1af5c2413297bfae928b0c6d6a0752f3e30819821660c6abdcc75fa97e15593dc1c181dc36b36f488c8710db3adcabaf70695964 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10c0/df2bbf1a5b50f12ab1bf78cae6de4dbf7c49df0e3a5f845553b51b20adbe8386a09fd172ea60342379f9284bb528cba2d0e2659cae6eb8d015cf92c8b32f1222 languageName: node linkType: hard -"@storybook/react-vite@npm:^9.1.1": - version: 9.1.7 - resolution: "@storybook/react-vite@npm:9.1.7" +"@storybook/react-dom-shim@npm:10.0.6": + version: 10.0.6 + resolution: "@storybook/react-dom-shim@npm:10.0.6" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.0.6 + checksum: 10c0/067b86aeadc96d0fedccd3e047c4b506484e7b2af21796b995e97cd3f4f31d77f8a2674fb29f77b45245353cc714a4511f49fa618a7386ba12706c663701da08 + languageName: node + linkType: hard + +"@storybook/react-vite@npm:^10.0.6": + version: 10.0.6 + resolution: "@storybook/react-vite@npm:10.0.6" dependencies: "@joshwooding/vite-plugin-react-docgen-typescript": "npm:0.6.1" "@rollup/pluginutils": "npm:^5.0.2" - "@storybook/builder-vite": "npm:9.1.7" - "@storybook/react": "npm:9.1.7" - find-up: "npm:^7.0.0" + "@storybook/builder-vite": "npm:10.0.6" + "@storybook/react": "npm:10.0.6" + empathic: "npm:^2.0.0" magic-string: "npm:^0.30.0" react-docgen: "npm:^8.0.0" resolve: "npm:^1.22.8" tsconfig-paths: "npm:^4.2.0" peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^9.1.7 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.0.6 vite: ^5.0.0 || ^6.0.0 || ^7.0.0 - checksum: 10c0/be74d1cacafc83f0c9dc22019aeb1e62a3c6faa2bcf98c1cd109ba3219fb61387d1be9875ffa5a59e824e7b7182c3491de47b2dcb4136368ee6c2189cff74e20 + checksum: 10c0/1689b7d866650912a8fe5ff2ec1c35292d408085d2d819f3a13055805f72d67b5c0fbeabe359de9a65f6f129500e20b935e832b280559afe5050b10432ccf6f2 languageName: node linkType: hard -"@storybook/react@npm:9.1.7": - version: 9.1.7 - resolution: "@storybook/react@npm:9.1.7" +"@storybook/react@npm:10.0.6": + version: 10.0.6 + resolution: "@storybook/react@npm:10.0.6" dependencies: "@storybook/global": "npm:^5.0.0" - "@storybook/react-dom-shim": "npm:9.1.7" + "@storybook/react-dom-shim": "npm:10.0.6" peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^9.1.7 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.0.6 typescript: ">= 4.9.x" peerDependenciesMeta: typescript: optional: true - checksum: 10c0/6ba0d3877075c380d8ab47423cc8e93ff297018fadbbe2ecd224e3d60e6a0fdba561ea84c1ffb39228cb2efd1d8d432710c6ec0337b30f74a62dc2384df96c91 + checksum: 10c0/7545a3f84a64ccfacc84f41d4f4b744fb71861f1a413ba48e4440694fb89f591273182771324097c420fca65253d738bde852e14a776e5b1df30a0d4beb100b0 languageName: node linkType: hard @@ -3876,7 +4107,7 @@ __metadata: languageName: node linkType: hard -"@testing-library/dom@npm:^10.4.0, @testing-library/dom@npm:^10.4.1": +"@testing-library/dom@npm:^10.4.1": version: 10.4.1 resolution: "@testing-library/dom@npm:10.4.1" dependencies: @@ -4030,13 +4261,6 @@ __metadata: languageName: node linkType: hard -"@types/cookie@npm:^0.6.0": - version: 0.6.0 - resolution: "@types/cookie@npm:0.6.0" - checksum: 10c0/5b326bd0188120fb32c0be086b141b1481fec9941b76ad537f9110e10d61ee2636beac145463319c71e4be67a17e85b81ca9e13ceb6e3bb63b93d16824d6c149 - languageName: node - linkType: hard - "@types/cors@npm:^2.8.16": version: 2.8.19 resolution: "@types/cors@npm:2.8.19" @@ -4109,27 +4333,26 @@ __metadata: languageName: node linkType: hard -"@types/express-serve-static-core@npm:^4.17.33": - version: 4.17.41 - resolution: "@types/express-serve-static-core@npm:4.17.41" +"@types/express-serve-static-core@npm:^5.0.0": + version: 5.1.0 + resolution: "@types/express-serve-static-core@npm:5.1.0" dependencies: "@types/node": "npm:*" "@types/qs": "npm:*" "@types/range-parser": "npm:*" "@types/send": "npm:*" - checksum: 10c0/dc166cbf4475c00a81fbcab120bf7477c527184be11ae149df7f26d9c1082114c68f8d387a2926fe80291b06477c8bbd9231ff4f5775de328e887695aefce269 + checksum: 10c0/1918233c68a0c69695f78331af1aed5fb5190f91da6309318f700adeb78573be840b5d206cb8eda804b65a9989fdeccdaaf84c1e95adc3615052749224b64519 languageName: node linkType: hard -"@types/express@npm:^4.17.17": - version: 4.17.23 - resolution: "@types/express@npm:4.17.23" +"@types/express@npm:^5.0.5": + version: 5.0.6 + resolution: "@types/express@npm:5.0.6" dependencies: "@types/body-parser": "npm:*" - "@types/express-serve-static-core": "npm:^4.17.33" - "@types/qs": "npm:*" - "@types/serve-static": "npm:*" - checksum: 10c0/60490cd4f73085007247e7d4fafad0a7abdafa34fa3caba2757512564ca5e094ece7459f0f324030a63d513f967bb86579a8682af76ae2fd718e889b0a2a4fe8 + "@types/express-serve-static-core": "npm:^5.0.0" + "@types/serve-static": "npm:^2" + checksum: 10c0/f1071e3389a955d4f9a38aae38634121c7cd9b3171ba4201ec9b56bd534aba07866839d278adc0dda05b942b05a901a02fd174201c3b1f70ce22b10b6c68f24b languageName: node linkType: hard @@ -4188,13 +4411,6 @@ __metadata: languageName: node linkType: hard -"@types/json-stable-stringify@npm:^1.1.0": - version: 1.1.0 - resolution: "@types/json-stable-stringify@npm:1.1.0" - checksum: 10c0/8f69944701510243cd3a83aa44363a8a4d366f11a659b258f69fb3ad0f94ab1e2533206a2c929ac7fd18784d201b663b3f02a45934f545c926f051d8cb4df095 - languageName: node - linkType: hard - "@types/json5@npm:^0.0.29": version: 0.0.29 resolution: "@types/json5@npm:0.0.29" @@ -4203,9 +4419,9 @@ __metadata: linkType: hard "@types/lodash@npm:^4.14.195": - version: 4.17.20 - resolution: "@types/lodash@npm:4.17.20" - checksum: 10c0/98cdd0faae22cbb8079a01a3bb65aa8f8c41143367486c1cbf5adc83f16c9272a2a5d2c1f541f61d0d73da543c16ee1d21cf2ef86cb93cd0cc0ac3bced6dd88f + version: 4.17.21 + resolution: "@types/lodash@npm:4.17.21" + checksum: 10c0/73cb006e047d8871e9d63f3a165543bf16c44a5b6fe3f9f6299e37cb8d67a7b1d55ac730959a81f9def510fd07232ff7e30e05413e5d5a12793baad84ebe36c3 languageName: node linkType: hard @@ -4216,13 +4432,6 @@ __metadata: languageName: node linkType: hard -"@types/mime@npm:*": - version: 3.0.4 - resolution: "@types/mime@npm:3.0.4" - checksum: 10c0/db478bc0f99e40f7b3e01d356a9bdf7817060808a294978111340317bcd80ca35382855578c5b60fbc84ae449674bd9bb38427b18417e1f8f19e4f72f8b242cd - languageName: node - linkType: hard - "@types/mime@npm:^1": version: 1.3.5 resolution: "@types/mime@npm:1.3.5" @@ -4261,13 +4470,13 @@ __metadata: linkType: hard "@types/pg@npm:^8.6.6": - version: 8.15.5 - resolution: "@types/pg@npm:8.15.5" + version: 8.15.6 + resolution: "@types/pg@npm:8.15.6" dependencies: "@types/node": "npm:*" pg-protocol: "npm:*" pg-types: "npm:^2.2.0" - checksum: 10c0/19a3cc1811918753f8c827733648c3a85c7b0355bf207c44eb1a3b79b2e6a0d85cb5457ec550d860fc9be7e88c7587a3600958ec8c61fa1ad573061c63af93f0 + checksum: 10c0/7f93f83a4da0dc6133918f824d826fa34e78fb8cf86392d28a0e095c836c6910c014ced5d4b364d83e8485a65ce369adeb9663b14ba301241d4c0f80073007f3 languageName: node linkType: hard @@ -4278,6 +4487,24 @@ __metadata: languageName: node linkType: hard +"@types/postcss-modules-local-by-default@npm:^4.0.2": + version: 4.0.2 + resolution: "@types/postcss-modules-local-by-default@npm:4.0.2" + dependencies: + postcss: "npm:^8.0.0" + checksum: 10c0/af13e40673abf96f1427c467bc9d96986fc0fb702f65ef702de05f70e51af2212bc0bdf177bfd817e418f2238bf210fdee3541dd2d939fde9a4df5f8972ad716 + languageName: node + linkType: hard + +"@types/postcss-modules-scope@npm:^3.0.4": + version: 3.0.4 + resolution: "@types/postcss-modules-scope@npm:3.0.4" + dependencies: + postcss: "npm:^8.0.0" + checksum: 10c0/6364674e429143fd686e0238d071377cf9ae1780a77f99e99292a06adc93057609146aaf55c09310ae99526c37e56be5a8a843086c0ff95513bd34c6bc4c7480 + languageName: node + linkType: hard + "@types/prop-types@npm:*, @types/prop-types@npm:^15.7.5": version: 15.7.15 resolution: "@types/prop-types@npm:15.7.15" @@ -4299,13 +4526,6 @@ __metadata: languageName: node linkType: hard -"@types/rails__ujs@npm:^6.0.4": - version: 6.0.4 - resolution: "@types/rails__ujs@npm:6.0.4" - checksum: 10c0/7477cb03a0e1339b9cd5c8ac4a197a153e2ff48742b2f527c5a39dcdf80f01493011e368483290d3717662c63066fada3ab203a335804cbb3573cf575f37007e - languageName: node - linkType: hard - "@types/range-parser@npm:*": version: 1.2.7 resolution: "@types/range-parser@npm:1.2.7" @@ -4371,15 +4591,6 @@ __metadata: languageName: node linkType: hard -"@types/react-swipeable-views@npm:^0.13.1": - version: 0.13.6 - resolution: "@types/react-swipeable-views@npm:0.13.6" - dependencies: - "@types/react": "npm:*" - checksum: 10c0/a26879146748417234bb7f44c5a71e6bab2b76c0b34c72f0493c18403487a5d77021510e8665bd8bd22786904fbbd90d6db55c8dd2bf983c32421139de851c94 - languageName: node - linkType: hard - "@types/react-test-renderer@npm:^18.0.0": version: 18.3.1 resolution: "@types/react-test-renderer@npm:18.3.1" @@ -4408,12 +4619,12 @@ __metadata: linkType: hard "@types/react@npm:^18.2.7": - version: 18.3.24 - resolution: "@types/react@npm:18.3.24" + version: 18.3.27 + resolution: "@types/react@npm:18.3.27" dependencies: "@types/prop-types": "npm:*" - csstype: "npm:^3.0.2" - checksum: 10c0/9e188fa8e50f172cf647fc48fea2e04d88602afff47190b697de281a8ac88df9ee059864757a2a438ff599eaf9276d9a9e0e60585e88f7d57f01a2e4877d37ec + csstype: "npm:^3.2.2" + checksum: 10c0/a761d2f58de03d0714806cc65d32bb3d73fb33a08dd030d255b47a295e5fff2a775cf1c20b786824d8deb6454eaccce9bc6998d9899c14fc04bbd1b0b0b72897 languageName: node linkType: hard @@ -4449,23 +4660,22 @@ __metadata: linkType: hard "@types/send@npm:*": - version: 0.17.4 - resolution: "@types/send@npm:0.17.4" + version: 0.17.6 + resolution: "@types/send@npm:0.17.6" dependencies: "@types/mime": "npm:^1" "@types/node": "npm:*" - checksum: 10c0/7f17fa696cb83be0a104b04b424fdedc7eaba1c9a34b06027239aba513b398a0e2b7279778af521f516a397ced417c96960e5f50fcfce40c4bc4509fb1a5883c + checksum: 10c0/a9d76797f0637738062f1b974e0fcf3d396a28c5dc18c3f95ecec5dabda82e223afbc2d56a0bca46b6326fd7bb229979916cea40de2270a98128fd94441b87c2 languageName: node linkType: hard -"@types/serve-static@npm:*": - version: 1.15.5 - resolution: "@types/serve-static@npm:1.15.5" +"@types/serve-static@npm:^2": + version: 2.2.0 + resolution: "@types/serve-static@npm:2.2.0" dependencies: "@types/http-errors": "npm:*" - "@types/mime": "npm:*" "@types/node": "npm:*" - checksum: 10c0/811d1a2f7e74a872195e7a013bcd87a2fb1edf07eaedcb9dcfd20c1eb4bc56ad4ea0d52141c13192c91ccda7c8aeb8a530d8a7e60b9c27f5990d7e62e0fecb03 + checksum: 10c0/a3c6126bdbf9685e6c7dc03ad34639666eff32754e912adeed9643bf3dd3aa0ff043002a7f69039306e310d233eb8e160c59308f95b0a619f32366bbc48ee094 languageName: node linkType: hard @@ -4490,13 +4700,6 @@ __metadata: languageName: node linkType: hard -"@types/uuid@npm:^10.0.0": - version: 10.0.0 - resolution: "@types/uuid@npm:10.0.0" - checksum: 10c0/9a1404bf287164481cb9b97f6bb638f78f955be57c40c6513b7655160beb29df6f84c915aaf4089a1559c216557dc4d2f79b48d978742d3ae10b937420ddac60 - languageName: node - linkType: hard - "@types/warning@npm:^3.0.0": version: 3.0.2 resolution: "@types/warning@npm:3.0.2" @@ -4513,140 +4716,139 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:8.38.0": - version: 8.38.0 - resolution: "@typescript-eslint/eslint-plugin@npm:8.38.0" +"@typescript-eslint/eslint-plugin@npm:8.48.1": + version: 8.48.1 + resolution: "@typescript-eslint/eslint-plugin@npm:8.48.1" dependencies: "@eslint-community/regexpp": "npm:^4.10.0" - "@typescript-eslint/scope-manager": "npm:8.38.0" - "@typescript-eslint/type-utils": "npm:8.38.0" - "@typescript-eslint/utils": "npm:8.38.0" - "@typescript-eslint/visitor-keys": "npm:8.38.0" + "@typescript-eslint/scope-manager": "npm:8.48.1" + "@typescript-eslint/type-utils": "npm:8.48.1" + "@typescript-eslint/utils": "npm:8.48.1" + "@typescript-eslint/visitor-keys": "npm:8.48.1" graphemer: "npm:^1.4.0" ignore: "npm:^7.0.0" natural-compare: "npm:^1.4.0" ts-api-utils: "npm:^2.1.0" peerDependencies: - "@typescript-eslint/parser": ^8.38.0 + "@typescript-eslint/parser": ^8.48.1 eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/199b82e9f0136baecf515df7c31bfed926a7c6d4e6298f64ee1a77c8bdd7a8cb92a2ea55a5a345c9f2948a02f7be6d72530efbe803afa1892b593fbd529d0c27 + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/aeb4692ac27ded73dce5ddba08d46f15d617651f629cdfc5e874dd4ac767eac0523807f1f4e51f6f80675efff78e5937690f1c58740b8cb92b44b87d757a6a1a languageName: node linkType: hard -"@typescript-eslint/parser@npm:8.38.0": - version: 8.38.0 - resolution: "@typescript-eslint/parser@npm:8.38.0" +"@typescript-eslint/parser@npm:8.48.1": + version: 8.48.1 + resolution: "@typescript-eslint/parser@npm:8.48.1" dependencies: - "@typescript-eslint/scope-manager": "npm:8.38.0" - "@typescript-eslint/types": "npm:8.38.0" - "@typescript-eslint/typescript-estree": "npm:8.38.0" - "@typescript-eslint/visitor-keys": "npm:8.38.0" + "@typescript-eslint/scope-manager": "npm:8.48.1" + "@typescript-eslint/types": "npm:8.48.1" + "@typescript-eslint/typescript-estree": "npm:8.48.1" + "@typescript-eslint/visitor-keys": "npm:8.48.1" debug: "npm:^4.3.4" peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/5580c2a328f0c15f85e4a0961a07584013cc0aca85fe868486187f7c92e9e3f6602c6e3dab917b092b94cd492ed40827c6f5fea42730bef88eb17592c947adf4 + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/54ec22c82cc631f56131bfed9747f8cadf52ab123463a406c5221f258f9533431c4a33ebe21ef178840d50235e69bb370d36aa2fd6a066e7223b38bfa41a1788 languageName: node linkType: hard -"@typescript-eslint/project-service@npm:8.38.0": - version: 8.38.0 - resolution: "@typescript-eslint/project-service@npm:8.38.0" +"@typescript-eslint/project-service@npm:8.48.1": + version: 8.48.1 + resolution: "@typescript-eslint/project-service@npm:8.48.1" dependencies: - "@typescript-eslint/tsconfig-utils": "npm:^8.38.0" - "@typescript-eslint/types": "npm:^8.38.0" + "@typescript-eslint/tsconfig-utils": "npm:^8.48.1" + "@typescript-eslint/types": "npm:^8.48.1" debug: "npm:^4.3.4" peerDependencies: - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/87d2f55521e289bbcdc666b1f4587ee2d43039cee927310b05abaa534b528dfb1b5565c1545bb4996d7fbdf9d5a3b0aa0e6c93a8f1289e3fcfd60d246364a884 + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/0aeeea5e65d0f837bd9a47265f144f14ca72969d259ee929e63e06526b21f4e990e70c7bafdb2ceb3783373df7d9f5bae32c328a4c6403606f01339bc984b3f5 languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:8.38.0": - version: 8.38.0 - resolution: "@typescript-eslint/scope-manager@npm:8.38.0" +"@typescript-eslint/scope-manager@npm:8.48.1": + version: 8.48.1 + resolution: "@typescript-eslint/scope-manager@npm:8.48.1" dependencies: - "@typescript-eslint/types": "npm:8.38.0" - "@typescript-eslint/visitor-keys": "npm:8.38.0" - checksum: 10c0/ceaf489ea1f005afb187932a7ee363dfe1e0f7cc3db921283991e20e4c756411a5e25afbec72edd2095d6a4384f73591f4c750cf65b5eaa650c90f64ef9fe809 + "@typescript-eslint/types": "npm:8.48.1" + "@typescript-eslint/visitor-keys": "npm:8.48.1" + checksum: 10c0/16514823784cb598817b87d3d2b4fb618ab8b2378b3401a4c1160a5c914e51e7a925c3c1e7be73e0250e38390f0be70fecb3e0e0bdde7b243d74444933b95d3e languageName: node linkType: hard -"@typescript-eslint/tsconfig-utils@npm:8.38.0, @typescript-eslint/tsconfig-utils@npm:^8.38.0": - version: 8.38.0 - resolution: "@typescript-eslint/tsconfig-utils@npm:8.38.0" +"@typescript-eslint/tsconfig-utils@npm:8.48.1, @typescript-eslint/tsconfig-utils@npm:^8.48.1": + version: 8.48.1 + resolution: "@typescript-eslint/tsconfig-utils@npm:8.48.1" peerDependencies: - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/1a90da16bf1f7cfbd0303640a8ead64a0080f2b1d5969994bdac3b80abfa1177f0c6fbf61250bae082e72cf5014308f2f5cc98edd6510202f13420a7ffd07a84 + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/0d540f7ab3018ed1bab8f008c0d30229e0ea12806fdbf1c756572b5cf536a1f2a6c59ca2544c09bcd5b89dcfcf79e5f6be3d765e725492b9c7e4cd64fcecffc6 languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:8.38.0": - version: 8.38.0 - resolution: "@typescript-eslint/type-utils@npm:8.38.0" +"@typescript-eslint/type-utils@npm:8.48.1": + version: 8.48.1 + resolution: "@typescript-eslint/type-utils@npm:8.48.1" dependencies: - "@typescript-eslint/types": "npm:8.38.0" - "@typescript-eslint/typescript-estree": "npm:8.38.0" - "@typescript-eslint/utils": "npm:8.38.0" + "@typescript-eslint/types": "npm:8.48.1" + "@typescript-eslint/typescript-estree": "npm:8.48.1" + "@typescript-eslint/utils": "npm:8.48.1" debug: "npm:^4.3.4" ts-api-utils: "npm:^2.1.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/27795c4bd0be395dda3424e57d746639c579b7522af1c17731b915298a6378fd78869e8e141526064b6047db2c86ba06444469ace19c98cda5779d06f4abd37c + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/c98a71f7d374be249ecc7c9f20b0a867a73ad4f64e646a6bf9f2c1a5d74f0dc7bd59e9c94a0842068caa366af39ae0c550ede6d653b5c9418a0a587510bbb6d5 languageName: node linkType: hard -"@typescript-eslint/types@npm:8.38.0, @typescript-eslint/types@npm:^8.34.1, @typescript-eslint/types@npm:^8.38.0": - version: 8.38.0 - resolution: "@typescript-eslint/types@npm:8.38.0" - checksum: 10c0/f0ac0060c98c0f3d1871f107177b6ae25a0f1846ca8bd8cfc7e1f1dd0ddce293cd8ac4a5764d6a767de3503d5d01defcd68c758cb7ba6de52f82b209a918d0d2 +"@typescript-eslint/types@npm:8.48.1, @typescript-eslint/types@npm:^8.46.0, @typescript-eslint/types@npm:^8.48.1": + version: 8.48.1 + resolution: "@typescript-eslint/types@npm:8.48.1" + checksum: 10c0/366b8140f4c69319f1796b66b33c0c6e16eb6cbe543b9517003104e12ed143b620c1433ccf60d781a629d9433bd509a363c0c9d21fd438c17bb8840733af6caa languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:8.38.0": - version: 8.38.0 - resolution: "@typescript-eslint/typescript-estree@npm:8.38.0" +"@typescript-eslint/typescript-estree@npm:8.48.1": + version: 8.48.1 + resolution: "@typescript-eslint/typescript-estree@npm:8.48.1" dependencies: - "@typescript-eslint/project-service": "npm:8.38.0" - "@typescript-eslint/tsconfig-utils": "npm:8.38.0" - "@typescript-eslint/types": "npm:8.38.0" - "@typescript-eslint/visitor-keys": "npm:8.38.0" + "@typescript-eslint/project-service": "npm:8.48.1" + "@typescript-eslint/tsconfig-utils": "npm:8.48.1" + "@typescript-eslint/types": "npm:8.48.1" + "@typescript-eslint/visitor-keys": "npm:8.48.1" debug: "npm:^4.3.4" - fast-glob: "npm:^3.3.2" - is-glob: "npm:^4.0.3" minimatch: "npm:^9.0.4" semver: "npm:^7.6.0" + tinyglobby: "npm:^0.2.15" ts-api-utils: "npm:^2.1.0" peerDependencies: - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/00a00f6549877f4ae5c2847fa5ac52bf42cbd59a87533856c359e2746e448ed150b27a6137c92fd50c06e6a4b39e386d6b738fac97d80d05596e81ce55933230 + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/72c0802f74222160f6a13ebbd32b0d504142a2427678c87ea78fc32672c65fd522377d43b31a97c944cbd0aefc36b320bf02f04e47c44f2797d6ccd0a8aa30ec languageName: node linkType: hard -"@typescript-eslint/utils@npm:8.38.0, @typescript-eslint/utils@npm:^8.27.0, @typescript-eslint/utils@npm:^8.8.1": - version: 8.38.0 - resolution: "@typescript-eslint/utils@npm:8.38.0" +"@typescript-eslint/utils@npm:8.48.1, @typescript-eslint/utils@npm:^8.27.0, @typescript-eslint/utils@npm:^8.8.1": + version: 8.48.1 + resolution: "@typescript-eslint/utils@npm:8.48.1" dependencies: "@eslint-community/eslint-utils": "npm:^4.7.0" - "@typescript-eslint/scope-manager": "npm:8.38.0" - "@typescript-eslint/types": "npm:8.38.0" - "@typescript-eslint/typescript-estree": "npm:8.38.0" + "@typescript-eslint/scope-manager": "npm:8.48.1" + "@typescript-eslint/types": "npm:8.48.1" + "@typescript-eslint/typescript-estree": "npm:8.48.1" peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/e97a45bf44f315f9ed8c2988429e18c88e3369c9ee3227ee86446d2d49f7325abebbbc9ce801e178f676baa986d3e1fd4b5391f1640c6eb8944c123423ae43bb + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/1775ac217b578f52d6c1e85258098f8ef764d04830c6ce11043b434860da80f1a5f7cc1b9f2e0a63de161e83b8d876f7ae8362d7644d5d8e636e60ad5eeff4e2 languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:8.38.0": - version: 8.38.0 - resolution: "@typescript-eslint/visitor-keys@npm:8.38.0" +"@typescript-eslint/visitor-keys@npm:8.48.1": + version: 8.48.1 + resolution: "@typescript-eslint/visitor-keys@npm:8.48.1" dependencies: - "@typescript-eslint/types": "npm:8.38.0" + "@typescript-eslint/types": "npm:8.48.1" eslint-visitor-keys: "npm:^4.2.1" - checksum: 10c0/071a756e383f41a6c9e51d78c8c64bd41cd5af68b0faef5fbaec4fa5dbd65ec9e4cd610c2e2cdbe9e2facc362995f202850622b78e821609a277b5b601a1d4ec + checksum: 10c0/ecf4078ce63c296dd340672b516f42bf452534c75af7e7d6c1a3f32b143ff184cb3a4071d7429a9f870371ff9091a790acce28b85ce3c450bfc60554c79d43ca languageName: node linkType: hard @@ -4827,72 +5029,78 @@ __metadata: linkType: hard "@vitejs/plugin-react@npm:^5.0.0": - version: 5.0.3 - resolution: "@vitejs/plugin-react@npm:5.0.3" + version: 5.1.2 + resolution: "@vitejs/plugin-react@npm:5.1.2" dependencies: - "@babel/core": "npm:^7.28.4" + "@babel/core": "npm:^7.28.5" "@babel/plugin-transform-react-jsx-self": "npm:^7.27.1" "@babel/plugin-transform-react-jsx-source": "npm:^7.27.1" - "@rolldown/pluginutils": "npm:1.0.0-beta.35" + "@rolldown/pluginutils": "npm:1.0.0-beta.53" "@types/babel__core": "npm:^7.20.5" - react-refresh: "npm:^0.17.0" + react-refresh: "npm:^0.18.0" peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 - checksum: 10c0/3fc071455630a0584c170c544d20fc3edaccfb60a1e03ea14ca76f049f2657eb645aba9c216db016b8d70e4f894285a78fcd92ef63a2fcfa7864da378ac52761 + checksum: 10c0/d788f269cdf7474425071ba7c4ea7013f174ddaef12b758defe809a551a03ac62a4a80cd858872deb618e7936ccc7cffe178bc12b62e9c836a467e13f15b9390 languageName: node linkType: hard -"@vitest/browser@npm:^3.2.4": - version: 3.2.4 - resolution: "@vitest/browser@npm:3.2.4" +"@vitest/browser-playwright@npm:^4.0.5": + version: 4.0.15 + resolution: "@vitest/browser-playwright@npm:4.0.15" dependencies: - "@testing-library/dom": "npm:^10.4.0" - "@testing-library/user-event": "npm:^14.6.1" - "@vitest/mocker": "npm:3.2.4" - "@vitest/utils": "npm:3.2.4" - magic-string: "npm:^0.30.17" - sirv: "npm:^3.0.1" - tinyrainbow: "npm:^2.0.0" - ws: "npm:^8.18.2" + "@vitest/browser": "npm:4.0.15" + "@vitest/mocker": "npm:4.0.15" + tinyrainbow: "npm:^3.0.3" peerDependencies: playwright: "*" - vitest: 3.2.4 - webdriverio: ^7.0.0 || ^8.0.0 || ^9.0.0 + vitest: 4.0.15 peerDependenciesMeta: playwright: - optional: true - safaridriver: - optional: true - webdriverio: - optional: true - checksum: 10c0/0db39daad675aad187eff27d5a7f17a9f533d7abc7476ee1a0b83a9c62a7227b24395f4814e034ecb2ebe39f1a2dec0a8c6a7f79b8d5680c3ac79e408727d742 + optional: false + checksum: 10c0/ce357cc96b5d391fa701d545089475feac64e6febdce0d95a75e7c9c29ad35650372e6930a492750af2a4633f4f9354463968f435713da4f035befeb4e3ecf84 languageName: node linkType: hard -"@vitest/coverage-v8@npm:^3.2.4": - version: 3.2.4 - resolution: "@vitest/coverage-v8@npm:3.2.4" +"@vitest/browser@npm:4.0.15, @vitest/browser@npm:^4.0.5": + version: 4.0.15 + resolution: "@vitest/browser@npm:4.0.15" + dependencies: + "@vitest/mocker": "npm:4.0.15" + "@vitest/utils": "npm:4.0.15" + magic-string: "npm:^0.30.21" + pixelmatch: "npm:7.1.0" + pngjs: "npm:^7.0.0" + sirv: "npm:^3.0.2" + tinyrainbow: "npm:^3.0.3" + ws: "npm:^8.18.3" + peerDependencies: + vitest: 4.0.15 + checksum: 10c0/b74c1ab5b03a494b1a91e270417a794e616d3d9d5002de816b6a9913073fdf5939ca63b30a37e4e865cb9402b8682254facaf4b854d002b65b6ea85fccf38253 + languageName: node + linkType: hard + +"@vitest/coverage-v8@npm:^4.0.5": + version: 4.0.15 + resolution: "@vitest/coverage-v8@npm:4.0.15" dependencies: - "@ampproject/remapping": "npm:^2.3.0" "@bcoe/v8-coverage": "npm:^1.0.2" - ast-v8-to-istanbul: "npm:^0.3.3" - debug: "npm:^4.4.1" + "@vitest/utils": "npm:4.0.15" + ast-v8-to-istanbul: "npm:^0.3.8" istanbul-lib-coverage: "npm:^3.2.2" istanbul-lib-report: "npm:^3.0.1" istanbul-lib-source-maps: "npm:^5.0.6" - istanbul-reports: "npm:^3.1.7" - magic-string: "npm:^0.30.17" - magicast: "npm:^0.3.5" - std-env: "npm:^3.9.0" - test-exclude: "npm:^7.0.1" - tinyrainbow: "npm:^2.0.0" - peerDependencies: - "@vitest/browser": 3.2.4 - vitest: 3.2.4 + istanbul-reports: "npm:^3.2.0" + magicast: "npm:^0.5.1" + obug: "npm:^2.1.1" + std-env: "npm:^3.10.0" + tinyrainbow: "npm:^3.0.3" + peerDependencies: + "@vitest/browser": 4.0.15 + vitest: 4.0.15 peerDependenciesMeta: "@vitest/browser": optional: true - checksum: 10c0/cae3e58d81d56e7e1cdecd7b5baab7edd0ad9dee8dec9353c52796e390e452377d3f04174d40b6986b17c73241a5e773e422931eaa8102dcba0605ff24b25193 + checksum: 10c0/8810cb35fc443bdd5da46ea90804d7657d17ceb20dc9f7e05c7f6212480039c6079ec4ff0c305a658044b7cbd8792a71c7ae6661258fc5f3022e04bea04186a0 languageName: node linkType: hard @@ -4909,26 +5117,40 @@ __metadata: languageName: node linkType: hard -"@vitest/mocker@npm:3.2.4": - version: 3.2.4 - resolution: "@vitest/mocker@npm:3.2.4" +"@vitest/expect@npm:4.0.15": + version: 4.0.15 + resolution: "@vitest/expect@npm:4.0.15" dependencies: - "@vitest/spy": "npm:3.2.4" + "@standard-schema/spec": "npm:^1.0.0" + "@types/chai": "npm:^5.2.2" + "@vitest/spy": "npm:4.0.15" + "@vitest/utils": "npm:4.0.15" + chai: "npm:^6.2.1" + tinyrainbow: "npm:^3.0.3" + checksum: 10c0/0cb98a4918ca84b28cd14120bb66c1bc3084f8f95b649066cdab2f5234ecdbe247cdc6bc47c0d939521d964ff3c150aadd9558272495c26872c9f3a97373bf7b + languageName: node + linkType: hard + +"@vitest/mocker@npm:4.0.15": + version: 4.0.15 + resolution: "@vitest/mocker@npm:4.0.15" + dependencies: + "@vitest/spy": "npm:4.0.15" estree-walker: "npm:^3.0.3" - magic-string: "npm:^0.30.17" + magic-string: "npm:^0.30.21" peerDependencies: msw: ^2.4.9 - vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + vite: ^6.0.0 || ^7.0.0-0 peerDependenciesMeta: msw: optional: true vite: optional: true - checksum: 10c0/f7a4aea19bbbf8f15905847ee9143b6298b2c110f8b64789224cb0ffdc2e96f9802876aa2ca83f1ec1b6e1ff45e822abb34f0054c24d57b29ab18add06536ccd + checksum: 10c0/7a164aa25daab3e92013ec95aab5c5778e805b1513e266ce6c00e0647eb9f7b281e33fcaf0d9d2aed88321079183b60c1aeab90961f618c24e2e3a5143308850 languageName: node linkType: hard -"@vitest/pretty-format@npm:3.2.4, @vitest/pretty-format@npm:^3.2.4": +"@vitest/pretty-format@npm:3.2.4": version: 3.2.4 resolution: "@vitest/pretty-format@npm:3.2.4" dependencies: @@ -4937,25 +5159,33 @@ __metadata: languageName: node linkType: hard -"@vitest/runner@npm:3.2.4": - version: 3.2.4 - resolution: "@vitest/runner@npm:3.2.4" +"@vitest/pretty-format@npm:4.0.15": + version: 4.0.15 + resolution: "@vitest/pretty-format@npm:4.0.15" dependencies: - "@vitest/utils": "npm:3.2.4" + tinyrainbow: "npm:^3.0.3" + checksum: 10c0/d863f3818627b198f9c66515f8faa200e76a1c30c7f2b25ac805e253204ae51abbfa6b6211c58b2d75e1a273a2e6925e3a8fa480ebfa9c479d75a19815e1cbea + languageName: node + linkType: hard + +"@vitest/runner@npm:4.0.15": + version: 4.0.15 + resolution: "@vitest/runner@npm:4.0.15" + dependencies: + "@vitest/utils": "npm:4.0.15" pathe: "npm:^2.0.3" - strip-literal: "npm:^3.0.0" - checksum: 10c0/e8be51666c72b3668ae3ea348b0196656a4a5adb836cb5e270720885d9517421815b0d6c98bfdf1795ed02b994b7bfb2b21566ee356a40021f5bf4f6ed4e418a + checksum: 10c0/0b0f23b8fed1a98bb85d71a7fc105726e0fae68667b090c40b636011126fef548a5f853eab40aaf47314913ab6480eefe2aa5bd6bcefc4116e034fdc1ac0f7d0 languageName: node linkType: hard -"@vitest/snapshot@npm:3.2.4": - version: 3.2.4 - resolution: "@vitest/snapshot@npm:3.2.4" +"@vitest/snapshot@npm:4.0.15": + version: 4.0.15 + resolution: "@vitest/snapshot@npm:4.0.15" dependencies: - "@vitest/pretty-format": "npm:3.2.4" - magic-string: "npm:^0.30.17" + "@vitest/pretty-format": "npm:4.0.15" + magic-string: "npm:^0.30.21" pathe: "npm:^2.0.3" - checksum: 10c0/f8301a3d7d1559fd3d59ed51176dd52e1ed5c2d23aa6d8d6aa18787ef46e295056bc726a021698d8454c16ed825ecba163362f42fa90258bb4a98cfd2c9424fc + checksum: 10c0/f05a19f74512cbad9bcfe4afe814c676b72b7e54ccf09c5b36e06e73614a24fdba47bdb8a94279162b7fdf83c9c840f557073a114a9339df7e75ccb9f4e99218 languageName: node linkType: hard @@ -4968,20 +5198,27 @@ __metadata: languageName: node linkType: hard -"@vitest/ui@npm:^3.2.4": - version: 3.2.4 - resolution: "@vitest/ui@npm:3.2.4" +"@vitest/spy@npm:4.0.15": + version: 4.0.15 + resolution: "@vitest/spy@npm:4.0.15" + checksum: 10c0/22319cead44964882d9e7bd197a9cd317c945ff75a4a9baefe06c32c5719d4cb887e86b4560d79716765f288a93a6c76e78e3eeab0000f24b2236dab678b7c34 + languageName: node + linkType: hard + +"@vitest/ui@npm:^4.0.5": + version: 4.0.15 + resolution: "@vitest/ui@npm:4.0.15" dependencies: - "@vitest/utils": "npm:3.2.4" + "@vitest/utils": "npm:4.0.15" fflate: "npm:^0.8.2" flatted: "npm:^3.3.3" pathe: "npm:^2.0.3" - sirv: "npm:^3.0.1" - tinyglobby: "npm:^0.2.14" - tinyrainbow: "npm:^2.0.0" + sirv: "npm:^3.0.2" + tinyglobby: "npm:^0.2.15" + tinyrainbow: "npm:^3.0.3" peerDependencies: - vitest: 3.2.4 - checksum: 10c0/c3de1b757905d050706c7ab0199185dd8c7e115f2f348b8d5a7468528c6bf90c2c46096e8901602349ac04f5ba83ac23cd98c38827b104d5151cf8ba21739a0c + vitest: 4.0.15 + checksum: 10c0/f6d1729de4d0ab43fbcc48aa1935315ab1c039fcf216abc01222e9170e0cb1829300a950952ca025ee9af0618fb4c24199cb1a912a76466c87a0eee86ed91d11 languageName: node linkType: hard @@ -4996,6 +5233,16 @@ __metadata: languageName: node linkType: hard +"@vitest/utils@npm:4.0.15": + version: 4.0.15 + resolution: "@vitest/utils@npm:4.0.15" + dependencies: + "@vitest/pretty-format": "npm:4.0.15" + tinyrainbow: "npm:^3.0.3" + checksum: 10c0/2ef661c2c2359ae956087f0b728b6a0f7555cd10524a7def27909f320f6b8ba00560ee1bd856bf68d4debc01020cea21b200203a5d2af36c44e94528c5587aee + languageName: node + linkType: hard + "abbrev@npm:^3.0.0": version: 3.0.1 resolution: "abbrev@npm:3.0.1" @@ -5003,13 +5250,13 @@ __metadata: languageName: node linkType: hard -"accepts@npm:~1.3.8": - version: 1.3.8 - resolution: "accepts@npm:1.3.8" +"accepts@npm:^2.0.0": + version: 2.0.0 + resolution: "accepts@npm:2.0.0" dependencies: - mime-types: "npm:~2.1.34" - negotiator: "npm:0.6.3" - checksum: 10c0/3a35c5f5586cfb9a21163ca47a5f77ac34fa8ceb5d17d2fa2c0d81f41cbd7f8c6fa52c77e2c039acc0f4d09e71abdc51144246900f6bef5e3c4b333f77d89362 + mime-types: "npm:^3.0.0" + negotiator: "npm:^1.0.0" + checksum: 10c0/98374742097e140891546076215f90c32644feacf652db48412329de4c2a529178a81aa500fbb13dd3e6cbf6e68d829037b123ac037fc9a08bcec4b87b358eef languageName: node linkType: hard @@ -5022,7 +5269,7 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.14.0, acorn@npm:^8.15.0, acorn@npm:^8.8.2": +"acorn@npm:^8.15.0, acorn@npm:^8.8.2": version: 8.15.0 resolution: "acorn@npm:8.15.0" bin: @@ -5117,16 +5364,6 @@ __metadata: languageName: node linkType: hard -"anymatch@npm:~3.1.2": - version: 3.1.3 - resolution: "anymatch@npm:3.1.3" - dependencies: - normalize-path: "npm:^3.0.0" - picomatch: "npm:^2.0.4" - checksum: 10c0/57b06ae984bc32a0d22592c87384cd88fe4511b1dd7581497831c56d41939c8a001b28e7b853e1450f2bf61992dfcaa8ae2d0d161a0a90c4fb631ef07098fbac - languageName: node - linkType: hard - "are-docs-informative@npm:^0.0.2": version: 0.0.2 resolution: "are-docs-informative@npm:0.0.2" @@ -5167,13 +5404,6 @@ __metadata: languageName: node linkType: hard -"array-flatten@npm:1.1.1": - version: 1.1.1 - resolution: "array-flatten@npm:1.1.1" - checksum: 10c0/806966c8abb2f858b08f5324d9d18d7737480610f3bd5d3498aaae6eb5efdc501a884ba019c9b4a8f02ff67002058749d05548fd42fa8643f02c9c7f22198b91 - languageName: node - linkType: hard - "array-includes@npm:^3.1.6, array-includes@npm:^3.1.8, array-includes@npm:^3.1.9": version: 3.1.9 resolution: "array-includes@npm:3.1.9" @@ -5308,14 +5538,14 @@ __metadata: languageName: node linkType: hard -"ast-v8-to-istanbul@npm:^0.3.3": - version: 0.3.3 - resolution: "ast-v8-to-istanbul@npm:0.3.3" +"ast-v8-to-istanbul@npm:^0.3.8": + version: 0.3.8 + resolution: "ast-v8-to-istanbul@npm:0.3.8" dependencies: - "@jridgewell/trace-mapping": "npm:^0.3.25" + "@jridgewell/trace-mapping": "npm:^0.3.31" estree-walker: "npm:^3.0.3" js-tokens: "npm:^9.0.1" - checksum: 10c0/ffc39bc3ab4b8c1f7aea945960ce6b1e518bab3da7c800277eab2da07d397eeae4a2cb8a5a5f817225646c8ea495c1e4434fbe082c84bae8042abddef53f50b2 + checksum: 10c0/6f7d74fc36011699af6d4ad88ecd8efc7d74bd90b8e8dbb1c69d43c8f4bec0ed361fb62a5b5bd98bbee02ee87c62cd8bcc25a39634964e45476bf5489dfa327f languageName: node linkType: hard @@ -5370,13 +5600,13 @@ __metadata: languageName: node linkType: hard -"autoprefixer@npm:^10.4.21": - version: 10.4.21 - resolution: "autoprefixer@npm:10.4.21" +"autoprefixer@npm:^10.4.22": + version: 10.4.22 + resolution: "autoprefixer@npm:10.4.22" dependencies: - browserslist: "npm:^4.24.4" - caniuse-lite: "npm:^1.0.30001702" - fraction.js: "npm:^4.3.7" + browserslist: "npm:^4.27.0" + caniuse-lite: "npm:^1.0.30001754" + fraction.js: "npm:^5.3.4" normalize-range: "npm:^0.1.2" picocolors: "npm:^1.1.1" postcss-value-parser: "npm:^4.2.0" @@ -5384,7 +5614,7 @@ __metadata: postcss: ^8.1.0 bin: autoprefixer: bin/autoprefixer - checksum: 10c0/de5b71d26d0baff4bbfb3d59f7cf7114a6030c9eeb66167acf49a32c5b61c68e308f1e0f869d92334436a221035d08b51cd1b2f2c4689b8d955149423c16d4d4 + checksum: 10c0/2ae8d135af2deaaa5065a3a466c877787373c0ed766b8a8e8259d7871db79c1a7e1d9f6c9541c54fa95647511d3c2066bb08a30160e58c9bfa75506f9c18f3aa languageName: node linkType: hard @@ -5405,13 +5635,13 @@ __metadata: linkType: hard "axios@npm:^1.4.0": - version: 1.12.2 - resolution: "axios@npm:1.12.2" + version: 1.13.2 + resolution: "axios@npm:1.13.2" dependencies: follow-redirects: "npm:^1.15.6" form-data: "npm:^4.0.4" proxy-from-env: "npm:^1.1.0" - checksum: 10c0/80b063e318cf05cd33a4d991cea0162f3573481946f9129efb7766f38fde4c061c34f41a93a9f9521f02b7c9565ccbc197c099b0186543ac84a24580017adfed + checksum: 10c0/e8a42e37e5568ae9c7a28c348db0e8cf3e43d06fcbef73f0048669edfe4f71219664da7b6cc991b0c0f01c28a48f037c515263cb79be1f1ae8ff034cd813867b languageName: node linkType: hard @@ -5423,21 +5653,21 @@ __metadata: linkType: hard "babel-plugin-formatjs@npm:^10.5.37": - version: 10.5.39 - resolution: "babel-plugin-formatjs@npm:10.5.39" + version: 10.5.41 + resolution: "babel-plugin-formatjs@npm:10.5.41" dependencies: "@babel/core": "npm:^7.26.10" "@babel/helper-plugin-utils": "npm:^7.26.5" "@babel/plugin-syntax-jsx": "npm:^7.25.9" "@babel/traverse": "npm:^7.26.10" "@babel/types": "npm:^7.26.10" - "@formatjs/icu-messageformat-parser": "npm:2.11.2" - "@formatjs/ts-transformer": "npm:3.14.0" + "@formatjs/icu-messageformat-parser": "npm:2.11.4" + "@formatjs/ts-transformer": "npm:3.14.2" "@types/babel__core": "npm:^7.20.5" "@types/babel__helper-plugin-utils": "npm:^7.10.3" "@types/babel__traverse": "npm:^7.20.6" tslib: "npm:^2.8.0" - checksum: 10c0/08ddc1516e6504bc794257cc7a5b788068afce5f0bdb1c98458d6e7eb9e5b96385f5f4912f92909aad72b4e20083c1472e16d7c05d008bd84a5f3a6d38bb1e95 + checksum: 10c0/bbe0e182185c72e4136a4cf37b2366952ad5b1d090de00a132757d2a65a5a6aef95ada93dffdc4ed0cf4338a0ff29c5a0d025d77e8b774b088c69bd68ac07ca6 languageName: node linkType: hard @@ -5509,21 +5739,12 @@ __metadata: languageName: node linkType: hard -"baseline-browser-mapping@npm:^2.8.3": - version: 2.8.6 - resolution: "baseline-browser-mapping@npm:2.8.6" +"baseline-browser-mapping@npm:^2.9.0": + version: 2.9.2 + resolution: "baseline-browser-mapping@npm:2.9.2" bin: baseline-browser-mapping: dist/cli.js - checksum: 10c0/ea628db5048d1e5c0251d4783e0496f5ce8de7a0e20ea29c8876611cb0acf58ffc76bf6561786c6388db22f130646e3ecb91eebc1c03954552a21d38fa38320f - languageName: node - linkType: hard - -"better-opn@npm:^3.0.2": - version: 3.0.2 - resolution: "better-opn@npm:3.0.2" - dependencies: - open: "npm:^8.0.4" - checksum: 10c0/911ef25d44da75aabfd2444ce7a4294a8000ebcac73068c04a60298b0f7c7506b60421aa4cd02ac82502fb42baaff7e4892234b51e6923eded44c5a11185f2f5 + checksum: 10c0/4f9be09e20261ed26f19e9b95454dcb8d8371b87983c57cd9f70b9572e9b3053577f0d8d6d91297bdb605337747680686e22f62522a6e57ae2488fcacf641188 languageName: node linkType: hard @@ -5536,13 +5757,6 @@ __metadata: languageName: node linkType: hard -"binary-extensions@npm:^2.0.0": - version: 2.3.0 - resolution: "binary-extensions@npm:2.3.0" - checksum: 10c0/75a59cafc10fb12a11d510e77110c6c7ae3f4ca22463d52487709ca7f18f69d886aa387557cc9864fbdb10153d0bdb4caacabf11541f55e89ed6e18d12ece2b5 - languageName: node - linkType: hard - "bintrees@npm:1.0.2": version: 1.0.2 resolution: "bintrees@npm:1.0.2" @@ -5564,23 +5778,20 @@ __metadata: languageName: node linkType: hard -"body-parser@npm:1.20.3": - version: 1.20.3 - resolution: "body-parser@npm:1.20.3" +"body-parser@npm:^2.2.1": + version: 2.2.1 + resolution: "body-parser@npm:2.2.1" dependencies: - bytes: "npm:3.1.2" - content-type: "npm:~1.0.5" - debug: "npm:2.6.9" - depd: "npm:2.0.0" - destroy: "npm:1.2.0" - http-errors: "npm:2.0.0" - iconv-lite: "npm:0.4.24" - on-finished: "npm:2.4.1" - qs: "npm:6.13.0" - raw-body: "npm:2.5.2" - type-is: "npm:~1.6.18" - unpipe: "npm:1.0.0" - checksum: 10c0/0a9a93b7518f222885498dcecaad528cf010dd109b071bf471c93def4bfe30958b83e03496eb9c1ad4896db543d999bb62be1a3087294162a88cfa1b42c16310 + bytes: "npm:^3.1.2" + content-type: "npm:^1.0.5" + debug: "npm:^4.4.3" + http-errors: "npm:^2.0.0" + iconv-lite: "npm:^0.7.0" + on-finished: "npm:^2.4.1" + qs: "npm:^6.14.0" + raw-body: "npm:^3.0.1" + type-is: "npm:^2.0.1" + checksum: 10c0/ce9608cff4114a908c09e8f57c7b358cd6fef66100320d01244d4c141448d7a6710c4051cc9d6191f8c6b3c7fa0f5b040c7aa1b6bbeb5462e27e668e64cb15bd languageName: node linkType: hard @@ -5603,7 +5814,7 @@ __metadata: languageName: node linkType: hard -"braces@npm:^3.0.3, braces@npm:~3.0.2": +"braces@npm:^3.0.3": version: 3.0.3 resolution: "braces@npm:3.0.3" dependencies: @@ -5625,18 +5836,18 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.24.0, browserslist@npm:^4.24.4, browserslist@npm:^4.25.1, browserslist@npm:^4.26.0": - version: 4.26.2 - resolution: "browserslist@npm:4.26.2" +"browserslist@npm:^4.24.0, browserslist@npm:^4.25.1, browserslist@npm:^4.27.0, browserslist@npm:^4.28.0": + version: 4.28.1 + resolution: "browserslist@npm:4.28.1" dependencies: - baseline-browser-mapping: "npm:^2.8.3" - caniuse-lite: "npm:^1.0.30001741" - electron-to-chromium: "npm:^1.5.218" - node-releases: "npm:^2.0.21" - update-browserslist-db: "npm:^1.1.3" + baseline-browser-mapping: "npm:^2.9.0" + caniuse-lite: "npm:^1.0.30001759" + electron-to-chromium: "npm:^1.5.263" + node-releases: "npm:^2.0.27" + update-browserslist-db: "npm:^1.2.0" bin: browserslist: cli.js - checksum: 10c0/1146339dad33fda77786b11ea07f1c40c48899edd897d73a9114ee0dbb1ee6475bb4abda263a678c104508bdca8e66760ff8e10be1947d3e20d34bae01d8b89b + checksum: 10c0/545a5fa9d7234e3777a7177ec1e9134bb2ba60a69e6b95683f6982b1473aad347c77c1264ccf2ac5dea609a9731fbfbda6b85782bdca70f80f86e28a402504bd languageName: node linkType: hard @@ -5657,17 +5868,19 @@ __metadata: languageName: node linkType: hard -"bytes@npm:3.1.2": - version: 3.1.2 - resolution: "bytes@npm:3.1.2" - checksum: 10c0/76d1c43cbd602794ad8ad2ae94095cddeb1de78c5dddaa7005c51af10b0176c69971a6d88e805a90c2b6550d76636e43c40d8427a808b8645ede885de4a0358e +"bundle-name@npm:^4.1.0": + version: 4.1.0 + resolution: "bundle-name@npm:4.1.0" + dependencies: + run-applescript: "npm:^7.0.0" + checksum: 10c0/8e575981e79c2bcf14d8b1c027a3775c095d362d1382312f444a7c861b0e21513c0bd8db5bd2b16e50ba0709fa622d4eab6b53192d222120305e68359daece29 languageName: node linkType: hard -"cac@npm:^6.7.14": - version: 6.7.14 - resolution: "cac@npm:6.7.14" - checksum: 10c0/4ee06aaa7bab8981f0d54e5f5f9d4adcd64058e9697563ce336d8a3878ed018ee18ebe5359b2430eceae87e0758e62ea2019c3f52ae6e211b1bd2e133856cd10 +"bytes@npm:^3.1.2, bytes@npm:~3.1.2": + version: 3.1.2 + resolution: "bytes@npm:3.1.2" + checksum: 10c0/76d1c43cbd602794ad8ad2ae94095cddeb1de78c5dddaa7005c51af10b0176c69971a6d88e805a90c2b6550d76636e43c40d8427a808b8645ede885de4a0358e languageName: node linkType: hard @@ -5691,16 +5904,16 @@ __metadata: languageName: node linkType: hard -"cacheable@npm:^2.0.1": - version: 2.0.1 - resolution: "cacheable@npm:2.0.1" +"cacheable@npm:^2.2.0": + version: 2.2.0 + resolution: "cacheable@npm:2.2.0" dependencies: - "@cacheable/memoize": "npm:^2.0.1" - "@cacheable/memory": "npm:^2.0.1" - "@cacheable/utils": "npm:^2.0.1" - hookified: "npm:^1.12.0" - keyv: "npm:^5.5.1" - checksum: 10c0/c4c16af5997850531a02b0efa150d3a06fff9560eca15a16e4042038a63487e967f00f5f1b63310523877b2ac4038e732faf0e707a5ff5d10cb861c01ed67ca8 + "@cacheable/memory": "npm:^2.0.5" + "@cacheable/utils": "npm:^2.3.0" + hookified: "npm:^1.13.0" + keyv: "npm:^5.5.4" + qified: "npm:^0.5.2" + checksum: 10c0/39b09e68b0a3da6c53dba1ed10dd13b63bf03f1fcb93ee941dd324d758e2d84466f1acb0c760fa78b23377be15ee58dc9acfb1fde85a89d2483d195b2f75e249 languageName: node linkType: hard @@ -5750,10 +5963,10 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.30001702, caniuse-lite@npm:^1.0.30001741": - version: 1.0.30001743 - resolution: "caniuse-lite@npm:1.0.30001743" - checksum: 10c0/1bd730ca10d881a1ca9f55ce864d34c3b18501718c03976e0d3419f4694b715159e13fdef6d58ad47b6d2445d315940f3a01266658876828c820a3331aac021d +"caniuse-lite@npm:^1.0.30001754, caniuse-lite@npm:^1.0.30001759": + version: 1.0.30001759 + resolution: "caniuse-lite@npm:1.0.30001759" + checksum: 10c0/b0f415960ba34995cda18e0d25c4e602f6917b9179290a76bdd0311423505b78cc93e558a90c98a22a1cc6b1781ab720ef6beea24ec7e29a1c1164ca72eac3a2 languageName: node linkType: hard @@ -5770,6 +5983,13 @@ __metadata: languageName: node linkType: hard +"chai@npm:^6.2.1": + version: 6.2.1 + resolution: "chai@npm:6.2.1" + checksum: 10c0/0c2d84392d7c6d44ca5d14d94204f1760e22af68b83d1f4278b5c4d301dabfc0242da70954dd86b1eda01e438f42950de6cf9d569df2103678538e4014abe50b + languageName: node + linkType: hard + "chalk@npm:^3.0.0": version: 3.0.0 resolution: "chalk@npm:3.0.0" @@ -5804,25 +6024,6 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:^3.6.0": - version: 3.6.0 - resolution: "chokidar@npm:3.6.0" - dependencies: - anymatch: "npm:~3.1.2" - braces: "npm:~3.0.2" - fsevents: "npm:~2.3.2" - glob-parent: "npm:~5.1.2" - is-binary-path: "npm:~2.1.0" - is-glob: "npm:~4.0.1" - normalize-path: "npm:~3.0.0" - readdirp: "npm:~3.6.0" - dependenciesMeta: - fsevents: - optional: true - checksum: 10c0/8361dcd013f2ddbe260eacb1f3cb2f2c6f2b0ad118708a343a5ed8158941a39cb8fb1d272e0f389712e74ee90ce8ba864eece9e0e62b9705cb468a2f6d917462 - languageName: node - linkType: hard - "chokidar@npm:^4.0.0": version: 4.0.0 resolution: "chokidar@npm:4.0.0" @@ -5839,9 +6040,9 @@ __metadata: languageName: node linkType: hard -"chromatic@npm:^13.1.3": - version: 13.2.0 - resolution: "chromatic@npm:13.2.0" +"chromatic@npm:^13.3.3": + version: 13.3.3 + resolution: "chromatic@npm:13.3.3" peerDependencies: "@chromatic-com/cypress": ^0.*.* || ^1.0.0 "@chromatic-com/playwright": ^0.*.* || ^1.0.0 @@ -5854,7 +6055,7 @@ __metadata: chroma: dist/bin.js chromatic: dist/bin.js chromatic-cli: dist/bin.js - checksum: 10c0/0f3419b45c648746ce4bb332a8c00548a41d0981a83c44f259fccced83245109448f1f713dd0379af4f386f0e614b11a52be5c0693ec71a99edc8aaed477c5bc + checksum: 10c0/6fc54df030113d91ef00a2050f5cb13ca182b355dae2c29cdd326fac6cf21d8ddc2cd93dc3f5db04379b7769d4df8e3ea5f18c3642e9e3a48545565f992a838c languageName: node linkType: hard @@ -5962,10 +6163,10 @@ __metadata: languageName: node linkType: hard -"commander@npm:14.0.1": - version: 14.0.1 - resolution: "commander@npm:14.0.1" - checksum: 10c0/64439c0651ddd01c1d0f48c8f08e97c18a0a1fa693879451f1203ad01132af2c2aa85da24cf0d8e098ab9e6dc385a756be670d2999a3c628ec745c3ec124587b +"commander@npm:^14.0.2": + version: 14.0.2 + resolution: "commander@npm:14.0.2" + checksum: 10c0/245abd1349dbad5414cb6517b7b5c584895c02c4f7836ff5395f301192b8566f9796c82d7bd6c92d07eba8775fe4df86602fca5d86d8d10bcc2aded1e21c2aeb languageName: node linkType: hard @@ -5997,16 +6198,16 @@ __metadata: languageName: node linkType: hard -"content-disposition@npm:0.5.4": - version: 0.5.4 - resolution: "content-disposition@npm:0.5.4" +"content-disposition@npm:^1.0.0": + version: 1.0.0 + resolution: "content-disposition@npm:1.0.0" dependencies: safe-buffer: "npm:5.2.1" - checksum: 10c0/bac0316ebfeacb8f381b38285dc691c9939bf0a78b0b7c2d5758acadad242d04783cee5337ba7d12a565a19075af1b3c11c728e1e4946de73c6ff7ce45f3f1bb + checksum: 10c0/c7b1ba0cea2829da0352ebc1b7f14787c73884bc707c8bc2271d9e3bf447b372270d09f5d3980dc5037c749ceef56b9a13fccd0b0001c87c3f12579967e4dd27 languageName: node linkType: hard -"content-type@npm:~1.0.4, content-type@npm:~1.0.5": +"content-type@npm:^1.0.5": version: 1.0.5 resolution: "content-type@npm:1.0.5" checksum: 10c0/b76ebed15c000aee4678c3707e0860cb6abd4e680a598c0a26e17f0bfae723ec9cc2802f0ff1bc6e4d80603719010431d2231018373d4dde10f9ccff9dadf5af @@ -6027,27 +6228,36 @@ __metadata: languageName: node linkType: hard -"cookie-signature@npm:1.0.6": - version: 1.0.6 - resolution: "cookie-signature@npm:1.0.6" - checksum: 10c0/b36fd0d4e3fef8456915fcf7742e58fbfcc12a17a018e0eb9501c9d5ef6893b596466f03b0564b81af29ff2538fd0aa4b9d54fe5ccbfb4c90ea50ad29fe2d221 - languageName: node - linkType: hard - -"cookie@npm:0.7.1": - version: 0.7.1 - resolution: "cookie@npm:0.7.1" - checksum: 10c0/5de60c67a410e7c8dc8a46a4b72eb0fe925871d057c9a5d2c0e8145c4270a4f81076de83410c4d397179744b478e33cd80ccbcc457abf40a9409ad27dcd21dde +"cookie-signature@npm:^1.2.1": + version: 1.2.2 + resolution: "cookie-signature@npm:1.2.2" + checksum: 10c0/54e05df1a293b3ce81589b27dddc445f462f6fa6812147c033350cd3561a42bc14481674e05ed14c7bd0ce1e8bb3dc0e40851bad75415733711294ddce0b7bc6 languageName: node linkType: hard -"cookie@npm:^0.7.2": +"cookie@npm:^0.7.1": version: 0.7.2 resolution: "cookie@npm:0.7.2" checksum: 10c0/9596e8ccdbf1a3a88ae02cf5ee80c1c50959423e1022e4e60b91dd87c622af1da309253d8abdb258fb5e3eacb4f08e579dc58b4897b8087574eee0fd35dfa5d2 languageName: node linkType: hard +"cookie@npm:^1.0.2": + version: 1.0.2 + resolution: "cookie@npm:1.0.2" + checksum: 10c0/fd25fe79e8fbcfcaf6aa61cd081c55d144eeeba755206c058682257cb38c4bd6795c6620de3f064c740695bb65b7949ebb1db7a95e4636efb8357a335ad3f54b + languageName: node + linkType: hard + +"copy-anything@npm:^2.0.1": + version: 2.0.6 + resolution: "copy-anything@npm:2.0.6" + dependencies: + is-what: "npm:^3.14.1" + checksum: 10c0/2702998a8cc015f9917385b7f16b0d85f1f6e5e2fd34d99f14df584838f492f49aa0c390d973684c687e895c5c58d08b308a0400ac3e1e3d6fa1e5884a5402ad + languageName: node + linkType: hard + "core-js-compat@npm:^3.43.0": version: 3.44.0 resolution: "core-js-compat@npm:3.44.0" @@ -6065,9 +6275,9 @@ __metadata: linkType: hard "core-js@npm:^3.30.2, core-js@npm:^3.45.0": - version: 3.45.1 - resolution: "core-js@npm:3.45.1" - checksum: 10c0/c38e5fae5a05ee3a129c45e10056aafe61dbb15fd35d27e0c289f5490387541c89741185e0aeb61acb558559c6697e016c245cca738fa169a73f2b06cd30e6b6 + version: 3.47.0 + resolution: "core-js@npm:3.47.0" + checksum: 10c0/9b1a7088b7c660c7b8f1d4c90bb1816a8d5352ebdcb7bc742e3a0e4eb803316b5aa17bacb8769522342196351a5430178f46914644f2bfdb94ce0ced3c7fd523 languageName: node linkType: hard @@ -6129,19 +6339,19 @@ __metadata: linkType: hard "cross-env@npm:^10.0.0": - version: 10.0.0 - resolution: "cross-env@npm:10.0.0" + version: 10.1.0 + resolution: "cross-env@npm:10.1.0" dependencies: "@epic-web/invariant": "npm:^1.0.0" cross-spawn: "npm:^7.0.6" bin: cross-env: dist/bin/cross-env.js cross-env-shell: dist/bin/cross-env-shell.js - checksum: 10c0/d16ffc3734106577d57b6253d81ab50294623bd59f96e161033eaf99c1c308ffbaba8463c23a6c0f72e841eff467cb7007a0a551f27554fcf2bbf6598cd828f9 + checksum: 10c0/834a862db456ba1fedf6c6da43436b123ae38f514fa286d6f0937c14fa83f13469f77f70f2812db041ae2d84f82bac627040b8686030aca27fbdf113dfa38b63 languageName: node linkType: hard -"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.6": +"cross-spawn@npm:^7.0.6": version: 7.0.6 resolution: "cross-spawn@npm:7.0.6" dependencies: @@ -6216,10 +6426,10 @@ __metadata: languageName: node linkType: hard -"cssdb@npm:^8.4.2": - version: 8.4.2 - resolution: "cssdb@npm:8.4.2" - checksum: 10c0/3c88610ba9e3f87f9ecf068b72261e90de8bb1f5d1dceefc79ff42b2e19f5814135937ad057b7f8c4bf58212f911e5f9d2f6f0910af3da127170009f1f75689c +"cssdb@npm:^8.5.2": + version: 8.5.2 + resolution: "cssdb@npm:8.5.2" + checksum: 10c0/12f7ed29dda0d74b209d6470acd246b335aac507c2786c17f20709f856eabb24e6d43ff44507898f5a1b0a101b286b997d95682e44b06f4c7cb4bd7081db7c32 languageName: node linkType: hard @@ -6232,21 +6442,21 @@ __metadata: languageName: node linkType: hard -"cssstyle@npm:^5.3.0": - version: 5.3.0 - resolution: "cssstyle@npm:5.3.0" +"cssstyle@npm:^5.3.4": + version: 5.3.4 + resolution: "cssstyle@npm:5.3.4" dependencies: - "@asamuzakjp/css-color": "npm:^4.0.3" - "@csstools/css-syntax-patches-for-csstree": "npm:^1.0.14" + "@asamuzakjp/css-color": "npm:^4.1.0" + "@csstools/css-syntax-patches-for-csstree": "npm:1.0.14" css-tree: "npm:^3.1.0" - checksum: 10c0/6ceddc5b696d1220b9a3e41775860f58d29046cdbabc1e6b9c19bc48a6bfdcde749c45023417abf7ca3319429566c079a3e6f6e191ceab159dbced6187cb8b7c + checksum: 10c0/7499ea8cbc2f759ded275428e0811d147baa6a964a44577711cee5edabee2230cf76b6bd20a556603f99ebc6fff80afdcba6c00bcbb1d41ae50cd09cd9fe9a2d languageName: node linkType: hard -"csstype@npm:^3.0.2": - version: 3.1.2 - resolution: "csstype@npm:3.1.2" - checksum: 10c0/32c038af259897c807ac738d9eab16b3d86747c72b09d5c740978e06f067f9b7b1737e1b75e407c7ab1fe1543dc95f20e202b4786aeb1b8d3bdf5d5ce655e6c6 +"csstype@npm:^3.0.2, csstype@npm:^3.2.2": + version: 3.2.3 + resolution: "csstype@npm:3.2.3" + checksum: 10c0/cd29c51e70fa822f1cecd8641a1445bed7063697469d35633b516e60fe8c1bde04b08f6c5b6022136bb669b64c63d4173af54864510fbb4ee23281801841a3ce languageName: node linkType: hard @@ -6307,16 +6517,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:2.6.9": - version: 2.6.9 - resolution: "debug@npm:2.6.9" - dependencies: - ms: "npm:2.0.0" - checksum: 10c0/121908fb839f7801180b69a7e218a40b5a0b718813b886b7d6bdb82001b931c938e2941d1e4450f33a1b1df1da653f5f7a0440c197f29fbf8a6e9d45ff6ef589 - languageName: node - linkType: hard - -"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.6, debug@npm:^4.4.1": +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6, debug@npm:^4.4.0, debug@npm:^4.4.1, debug@npm:^4.4.3": version: 4.4.3 resolution: "debug@npm:4.4.3" dependencies: @@ -6337,10 +6538,10 @@ __metadata: languageName: node linkType: hard -"decimal.js@npm:^10.4.3, decimal.js@npm:^10.5.0": - version: 10.5.0 - resolution: "decimal.js@npm:10.5.0" - checksum: 10c0/785c35279df32762143914668df35948920b6c1c259b933e0519a69b7003fc0a5ed2a766b1e1dda02574450c566b21738a45f15e274b47c2ac02072c0d1f3ac3 +"decimal.js@npm:^10.4.3, decimal.js@npm:^10.6.0": + version: 10.6.0 + resolution: "decimal.js@npm:10.6.0" + checksum: 10c0/07d69fbcc54167a340d2d97de95f546f9ff1f69d2b45a02fd7a5292412df3cd9eb7e23065e532a318f5474a2e1bccf8392fdf0443ef467f97f3bf8cb0477e5aa languageName: node linkType: hard @@ -6365,6 +6566,23 @@ __metadata: languageName: node linkType: hard +"default-browser-id@npm:^5.0.0": + version: 5.0.1 + resolution: "default-browser-id@npm:5.0.1" + checksum: 10c0/5288b3094c740ef3a86df9b999b04ff5ba4dee6b64e7b355c0fff5217752c8c86908d67f32f6cba9bb4f9b7b61a1b640c0a4f9e34c57e0ff3493559a625245ee + languageName: node + linkType: hard + +"default-browser@npm:^5.2.1": + version: 5.4.0 + resolution: "default-browser@npm:5.4.0" + dependencies: + bundle-name: "npm:^4.1.0" + default-browser-id: "npm:^5.0.0" + checksum: 10c0/a49ddd0c7b1a319163f64a5fc68ebb45a98548ea23a3155e04518f026173d85cfa2f451b646366c36c8f70b01e4cb773e23d1d22d2c61d8b84e5fbf151b4b609 + languageName: node + linkType: hard + "define-data-property@npm:^1.0.1, define-data-property@npm:^1.1.4": version: 1.1.4 resolution: "define-data-property@npm:1.1.4" @@ -6383,6 +6601,13 @@ __metadata: languageName: node linkType: hard +"define-lazy-prop@npm:^3.0.0": + version: 3.0.0 + resolution: "define-lazy-prop@npm:3.0.0" + checksum: 10c0/5ab0b2bf3fa58b3a443140bbd4cd3db1f91b985cc8a246d330b9ac3fc0b6a325a6d82bddc0b055123d745b3f9931afeea74a5ec545439a1630b9c8512b0eeb49 + languageName: node + linkType: hard + "define-properties@npm:^1.1.3, define-properties@npm:^1.2.1": version: 1.2.1 resolution: "define-properties@npm:1.2.1" @@ -6401,6 +6626,15 @@ __metadata: languageName: node linkType: hard +"delegated-events@npm:^1.1.2": + version: 1.1.2 + resolution: "delegated-events@npm:1.1.2" + dependencies: + selector-set: "npm:^1.1.5" + checksum: 10c0/b295a6d6c6cef4b9312bfd4132ac3a1255f3c2fadf3692a04cf7ddf8f0d472bfce9de06323faa75e922d20e5674d45022e1a5378b8500cd7d573ffa0cf7ca602 + languageName: node + linkType: hard + "denque@npm:^2.1.0": version: 2.1.0 resolution: "denque@npm:2.1.0" @@ -6408,7 +6642,7 @@ __metadata: languageName: node linkType: hard -"depd@npm:2.0.0": +"depd@npm:^2.0.0, depd@npm:~2.0.0": version: 2.0.0 resolution: "depd@npm:2.0.0" checksum: 10c0/58bd06ec20e19529b06f7ad07ddab60e504d9e0faca4bd23079fac2d279c3594334d736508dc350e06e510aba5e22e4594483b3a6562ce7c17dd797f4cc4ad2c @@ -6422,13 +6656,6 @@ __metadata: languageName: node linkType: hard -"destroy@npm:1.2.0": - version: 1.2.0 - resolution: "destroy@npm:1.2.0" - checksum: 10c0/bd7633942f57418f5a3b80d5cb53898127bcf53e24cdf5d5f4396be471417671f0fee48a4ebe9a1e9defbde2a31280011af58a57e090ff822f589b443ed4e643 - languageName: node - linkType: hard - "detect-it@npm:^4.0.1": version: 4.0.1 resolution: "detect-it@npm:4.0.1" @@ -6495,16 +6722,7 @@ __metadata: languageName: node linkType: hard -"dom-helpers@npm:^3.4.0": - version: 3.4.0 - resolution: "dom-helpers@npm:3.4.0" - dependencies: - "@babel/runtime": "npm:^7.1.2" - checksum: 10c0/1d2d3e4eadac2c4f4c8c7470a737ab32b7ec28237c4d094ea967ec3184168fd12452196fcc424a5d7860b6176117301aeaecba39467bf1a6e8492a8e5c9639d1 - languageName: node - linkType: hard - -"dom-helpers@npm:^5.0.1, dom-helpers@npm:^5.2.0": +"dom-helpers@npm:^5.0.1, dom-helpers@npm:^5.1.4, dom-helpers@npm:^5.2.0": version: 5.2.1 resolution: "dom-helpers@npm:5.2.1" dependencies: @@ -6524,10 +6742,10 @@ __metadata: languageName: node linkType: hard -"dotenv@npm:^16.0.3": - version: 16.5.0 - resolution: "dotenv@npm:16.5.0" - checksum: 10c0/5bc94c919fbd955bf0ba44d33922a1e93d1078e64a1db5c30faeded1d996e7a83c55332cb8ea4fae5a9ca4d0be44cbceb95c5811e70f9f095298df09d1997dd9 +"dotenv@npm:^16.0.3, dotenv@npm:^16.4.2": + version: 16.6.1 + resolution: "dotenv@npm:16.6.1" + checksum: 10c0/15ce56608326ea0d1d9414a5c8ee6dcf0fffc79d2c16422b4ac2268e7e2d76ff5a572d37ffe747c377de12005f14b3cc22361e79fc7f1061cce81f77d2c973dc languageName: node linkType: hard @@ -6567,10 +6785,10 @@ __metadata: languageName: node linkType: hard -"electron-to-chromium@npm:^1.5.218": - version: 1.5.222 - resolution: "electron-to-chromium@npm:1.5.222" - checksum: 10c0/a81eb8d2b171236884faf9b5dd382c66d9250283032cb89a3e555d788bf3956f7f4f6bf7bf30b3daf9e5c945ef837bfcd1be21b3f41cfe186ed2f25da13c9af3 +"electron-to-chromium@npm:^1.5.263": + version: 1.5.266 + resolution: "electron-to-chromium@npm:1.5.266" + checksum: 10c0/74ada92ada1ace76ec5b7da8a9cc2d7f03db122a64ac8e12ae30eba3e358ffec443c0c5265bc6edcdeebfa73f449b21c361080c064eb1eec437db2d71fc03248 languageName: node linkType: hard @@ -6631,14 +6849,14 @@ __metadata: languageName: node linkType: hard -"encodeurl@npm:~1.0.2": - version: 1.0.2 - resolution: "encodeurl@npm:1.0.2" - checksum: 10c0/f6c2387379a9e7c1156c1c3d4f9cb7bb11cf16dd4c1682e1f6746512564b053df5781029b6061296832b59fb22f459dbe250386d217c2f6e203601abb2ee0bec +"empathic@npm:^2.0.0": + version: 2.0.0 + resolution: "empathic@npm:2.0.0" + checksum: 10c0/7d3b14b04a93b35c47bcc950467ec914fd241cd9acc0269b0ea160f13026ec110f520c90fae64720fde72cc1757b57f3f292fb606617b7fccac1f4d008a76506 languageName: node linkType: hard -"encodeurl@npm:~2.0.0": +"encodeurl@npm:^2.0.0": version: 2.0.0 resolution: "encodeurl@npm:2.0.0" checksum: 10c0/5d317306acb13e6590e28e27924c754163946a2480de11865c991a3a7eed4315cd3fba378b543ca145829569eefe9b899f3d84bb09870f675ae60bc924b01ceb @@ -6698,6 +6916,17 @@ __metadata: languageName: node linkType: hard +"errno@npm:^0.1.1": + version: 0.1.8 + resolution: "errno@npm:0.1.8" + dependencies: + prr: "npm:~1.0.1" + bin: + errno: cli.js + checksum: 10c0/83758951967ec57bf00b5f5b7dc797e6d65a6171e57ea57adcf1bd1a0b477fd9b5b35fae5be1ff18f4090ed156bce1db749fe7e317aac19d485a5d150f6a4936 + languageName: node + linkType: hard + "error-ex@npm:^1.3.1": version: 1.3.2 resolution: "error-ex@npm:1.3.2" @@ -6864,46 +7093,36 @@ __metadata: languageName: node linkType: hard -"esbuild-register@npm:^3.5.0": - version: 3.6.0 - resolution: "esbuild-register@npm:3.6.0" - dependencies: - debug: "npm:^4.3.4" - peerDependencies: - esbuild: ">=0.12 <1" - checksum: 10c0/77193b7ca32ba9f81b35ddf3d3d0138efb0b1429d71b39480cfee932e1189dd2e492bd32bf04a4d0bc3adfbc7ec7381ceb5ffd06efe35f3e70904f1f686566d5 - languageName: node - linkType: hard - -"esbuild@npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0, esbuild@npm:^0.25.0": - version: 0.25.5 - resolution: "esbuild@npm:0.25.5" - dependencies: - "@esbuild/aix-ppc64": "npm:0.25.5" - "@esbuild/android-arm": "npm:0.25.5" - "@esbuild/android-arm64": "npm:0.25.5" - "@esbuild/android-x64": "npm:0.25.5" - "@esbuild/darwin-arm64": "npm:0.25.5" - "@esbuild/darwin-x64": "npm:0.25.5" - "@esbuild/freebsd-arm64": "npm:0.25.5" - "@esbuild/freebsd-x64": "npm:0.25.5" - "@esbuild/linux-arm": "npm:0.25.5" - "@esbuild/linux-arm64": "npm:0.25.5" - "@esbuild/linux-ia32": "npm:0.25.5" - "@esbuild/linux-loong64": "npm:0.25.5" - "@esbuild/linux-mips64el": "npm:0.25.5" - "@esbuild/linux-ppc64": "npm:0.25.5" - "@esbuild/linux-riscv64": "npm:0.25.5" - "@esbuild/linux-s390x": "npm:0.25.5" - "@esbuild/linux-x64": "npm:0.25.5" - "@esbuild/netbsd-arm64": "npm:0.25.5" - "@esbuild/netbsd-x64": "npm:0.25.5" - "@esbuild/openbsd-arm64": "npm:0.25.5" - "@esbuild/openbsd-x64": "npm:0.25.5" - "@esbuild/sunos-x64": "npm:0.25.5" - "@esbuild/win32-arm64": "npm:0.25.5" - "@esbuild/win32-ia32": "npm:0.25.5" - "@esbuild/win32-x64": "npm:0.25.5" +"esbuild@npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0": + version: 0.27.2 + resolution: "esbuild@npm:0.27.2" + dependencies: + "@esbuild/aix-ppc64": "npm:0.27.2" + "@esbuild/android-arm": "npm:0.27.2" + "@esbuild/android-arm64": "npm:0.27.2" + "@esbuild/android-x64": "npm:0.27.2" + "@esbuild/darwin-arm64": "npm:0.27.2" + "@esbuild/darwin-x64": "npm:0.27.2" + "@esbuild/freebsd-arm64": "npm:0.27.2" + "@esbuild/freebsd-x64": "npm:0.27.2" + "@esbuild/linux-arm": "npm:0.27.2" + "@esbuild/linux-arm64": "npm:0.27.2" + "@esbuild/linux-ia32": "npm:0.27.2" + "@esbuild/linux-loong64": "npm:0.27.2" + "@esbuild/linux-mips64el": "npm:0.27.2" + "@esbuild/linux-ppc64": "npm:0.27.2" + "@esbuild/linux-riscv64": "npm:0.27.2" + "@esbuild/linux-s390x": "npm:0.27.2" + "@esbuild/linux-x64": "npm:0.27.2" + "@esbuild/netbsd-arm64": "npm:0.27.2" + "@esbuild/netbsd-x64": "npm:0.27.2" + "@esbuild/openbsd-arm64": "npm:0.27.2" + "@esbuild/openbsd-x64": "npm:0.27.2" + "@esbuild/openharmony-arm64": "npm:0.27.2" + "@esbuild/sunos-x64": "npm:0.27.2" + "@esbuild/win32-arm64": "npm:0.27.2" + "@esbuild/win32-ia32": "npm:0.27.2" + "@esbuild/win32-x64": "npm:0.27.2" dependenciesMeta: "@esbuild/aix-ppc64": optional: true @@ -6947,6 +7166,8 @@ __metadata: optional: true "@esbuild/openbsd-x64": optional: true + "@esbuild/openharmony-arm64": + optional: true "@esbuild/sunos-x64": optional: true "@esbuild/win32-arm64": @@ -6957,27 +7178,113 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 10c0/aba8cbc11927fa77562722ed5e95541ce2853f67ad7bdc40382b558abc2e0ec57d92ffb820f082ba2047b4ef9f3bc3da068cdebe30dfd3850cfa3827a78d604e + checksum: 10c0/cf83f626f55500f521d5fe7f4bc5871bec240d3deb2a01fbd379edc43b3664d1167428738a5aad8794b35d1cca985c44c375b1cd38a2ca613c77ced2c83aafcd languageName: node linkType: hard -"escalade@npm:^3.1.1, escalade@npm:^3.2.0": - version: 3.2.0 - resolution: "escalade@npm:3.2.0" - checksum: 10c0/ced4dd3a78e15897ed3be74e635110bbf3b08877b0a41be50dcb325ee0e0b5f65fc2d50e9845194d7c4633f327e2e1c6cce00a71b617c5673df0374201d67f65 - languageName: node - linkType: hard - -"escape-html@npm:^1.0.3, escape-html@npm:~1.0.3": - version: 1.0.3 - resolution: "escape-html@npm:1.0.3" - checksum: 10c0/524c739d776b36c3d29fa08a22e03e8824e3b2fd57500e5e44ecf3cc4707c34c60f9ca0781c0e33d191f2991161504c295e98f68c78fe7baa6e57081ec6ac0a3 - languageName: node - linkType: hard - -"escape-string-regexp@npm:^4.0.0": - version: 4.0.0 - resolution: "escape-string-regexp@npm:4.0.0" +"esbuild@npm:^0.25.0": + version: 0.25.5 + resolution: "esbuild@npm:0.25.5" + dependencies: + "@esbuild/aix-ppc64": "npm:0.25.5" + "@esbuild/android-arm": "npm:0.25.5" + "@esbuild/android-arm64": "npm:0.25.5" + "@esbuild/android-x64": "npm:0.25.5" + "@esbuild/darwin-arm64": "npm:0.25.5" + "@esbuild/darwin-x64": "npm:0.25.5" + "@esbuild/freebsd-arm64": "npm:0.25.5" + "@esbuild/freebsd-x64": "npm:0.25.5" + "@esbuild/linux-arm": "npm:0.25.5" + "@esbuild/linux-arm64": "npm:0.25.5" + "@esbuild/linux-ia32": "npm:0.25.5" + "@esbuild/linux-loong64": "npm:0.25.5" + "@esbuild/linux-mips64el": "npm:0.25.5" + "@esbuild/linux-ppc64": "npm:0.25.5" + "@esbuild/linux-riscv64": "npm:0.25.5" + "@esbuild/linux-s390x": "npm:0.25.5" + "@esbuild/linux-x64": "npm:0.25.5" + "@esbuild/netbsd-arm64": "npm:0.25.5" + "@esbuild/netbsd-x64": "npm:0.25.5" + "@esbuild/openbsd-arm64": "npm:0.25.5" + "@esbuild/openbsd-x64": "npm:0.25.5" + "@esbuild/sunos-x64": "npm:0.25.5" + "@esbuild/win32-arm64": "npm:0.25.5" + "@esbuild/win32-ia32": "npm:0.25.5" + "@esbuild/win32-x64": "npm:0.25.5" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/aba8cbc11927fa77562722ed5e95541ce2853f67ad7bdc40382b558abc2e0ec57d92ffb820f082ba2047b4ef9f3bc3da068cdebe30dfd3850cfa3827a78d604e + languageName: node + linkType: hard + +"escalade@npm:^3.1.1, escalade@npm:^3.2.0": + version: 3.2.0 + resolution: "escalade@npm:3.2.0" + checksum: 10c0/ced4dd3a78e15897ed3be74e635110bbf3b08877b0a41be50dcb325ee0e0b5f65fc2d50e9845194d7c4633f327e2e1c6cce00a71b617c5673df0374201d67f65 + languageName: node + linkType: hard + +"escape-html@npm:^1.0.3": + version: 1.0.3 + resolution: "escape-html@npm:1.0.3" + checksum: 10c0/524c739d776b36c3d29fa08a22e03e8824e3b2fd57500e5e44ecf3cc4707c34c60f9ca0781c0e33d191f2991161504c295e98f68c78fe7baa6e57081ec6ac0a3 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^4.0.0": + version: 4.0.0 + resolution: "escape-string-regexp@npm:4.0.0" checksum: 10c0/9497d4dd307d845bd7f75180d8188bb17ea8c151c1edbf6b6717c100e104d629dc2dfb687686181b0f4b7d732c7dfdc4d5e7a8ff72de1b0ca283a75bbb3a9cd9 languageName: node linkType: hard @@ -7045,11 +7352,11 @@ __metadata: linkType: hard "eslint-plugin-formatjs@npm:^5.3.1": - version: 5.4.0 - resolution: "eslint-plugin-formatjs@npm:5.4.0" + version: 5.4.2 + resolution: "eslint-plugin-formatjs@npm:5.4.2" dependencies: - "@formatjs/icu-messageformat-parser": "npm:2.11.2" - "@formatjs/ts-transformer": "npm:3.14.0" + "@formatjs/icu-messageformat-parser": "npm:2.11.4" + "@formatjs/ts-transformer": "npm:3.14.2" "@types/eslint": "npm:^9.6.1" "@types/picomatch": "npm:^3" "@typescript-eslint/utils": "npm:^8.27.0" @@ -7059,7 +7366,7 @@ __metadata: unicode-emoji-utils: "npm:^1.2.0" peerDependencies: eslint: ^9.23.0 - checksum: 10c0/5c74a53988df68ffed4e68bb58a4ee75cdcd92b7d94f699e2edbcdd8c2c45930f500c7211da0a4616714d7d83bbbdc105328e5aacf0c9c7582a78fdfc9fa2b55 + checksum: 10c0/46bee038c54a7da58647eaccee6c956857ad81499b60f814914921b26852e6bf9b0efabb9556d7aab0e151ba349dcf949dc5f14a356b80bc5e63f338e7f7cc7f languageName: node linkType: hard @@ -7092,23 +7399,27 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-jsdoc@npm:^54.0.0": - version: 54.0.0 - resolution: "eslint-plugin-jsdoc@npm:54.0.0" +"eslint-plugin-jsdoc@npm:^61.1.11": + version: 61.4.1 + resolution: "eslint-plugin-jsdoc@npm:61.4.1" dependencies: - "@es-joy/jsdoccomment": "npm:~0.52.0" + "@es-joy/jsdoccomment": "npm:~0.76.0" + "@es-joy/resolve.exports": "npm:1.2.0" are-docs-informative: "npm:^0.0.2" comment-parser: "npm:1.4.1" - debug: "npm:^4.4.1" + debug: "npm:^4.4.3" escape-string-regexp: "npm:^4.0.0" espree: "npm:^10.4.0" esquery: "npm:^1.6.0" + html-entities: "npm:^2.6.0" + object-deep-merge: "npm:^2.0.0" parse-imports-exports: "npm:^0.2.4" - semver: "npm:^7.7.2" + semver: "npm:^7.7.3" spdx-expression-parse: "npm:^4.0.0" + to-valid-identifier: "npm:^1.0.0" peerDependencies: eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 - checksum: 10c0/cf0a388fc670ababe26f9584c467bc8c1592aa83affcf16118d8181c186a6d8f02a8ea65250766b45168fca5cb879a6af66e8457cdb98f0f923bd927572e2de5 + checksum: 10c0/564f89bad71dcdbf6a45c27d16113333a5251f97a60bcc0e7346ea1b19dc1258991e1f585c89a2978e279288be2e180dde58c57f63cd49ac3db6604a5d4c581c languageName: node linkType: hard @@ -7148,12 +7459,18 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-react-hooks@npm:^5.2.0": - version: 5.2.0 - resolution: "eslint-plugin-react-hooks@npm:5.2.0" +"eslint-plugin-react-hooks@npm:^7.0.1": + version: 7.0.1 + resolution: "eslint-plugin-react-hooks@npm:7.0.1" + dependencies: + "@babel/core": "npm:^7.24.4" + "@babel/parser": "npm:^7.24.4" + hermes-parser: "npm:^0.25.1" + zod: "npm:^3.25.0 || ^4.0.0" + zod-validation-error: "npm:^3.5.0 || ^4.0.0" peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 - checksum: 10c0/1c8d50fa5984c6dea32470651807d2922cc3934cf3425e78f84a24c2dfd972e7f019bee84aefb27e0cf2c13fea0ac1d4473267727408feeb1c56333ca1489385 + checksum: 10c0/1e711d1a9d1fa9cfc51fa1572500656577201199c70c795c6a27adfc1df39e5c598f69aab6aa91117753d23cc1f11388579a2bed14921cf9a4efe60ae8618496 languageName: node linkType: hard @@ -7185,15 +7502,15 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-storybook@npm:^9.0.4": - version: 9.1.1 - resolution: "eslint-plugin-storybook@npm:9.1.1" +"eslint-plugin-storybook@npm:^10.0.2": + version: 10.1.4 + resolution: "eslint-plugin-storybook@npm:10.1.4" dependencies: "@typescript-eslint/utils": "npm:^8.8.1" peerDependencies: eslint: ">=8" - storybook: ^9.1.1 - checksum: 10c0/4cf80aa078633021b153a3a5b790a39c9919b5fa7203727c15d8ae066e75d6e134d7d718e66a6a5db9815275f32942a2deae1979aeb36be2543572507faced2c + storybook: ^10.1.4 + checksum: 10c0/d68a0244318a386877de12d22ce309725f9f7057002f5182fa50fc965e26ced7d051432d7495046f13ee5803634ce53ef9e58cfe2866c3251f2ff94f8ab50e74 languageName: node linkType: hard @@ -7222,22 +7539,21 @@ __metadata: linkType: hard "eslint@npm:^9.23.0": - version: 9.32.0 - resolution: "eslint@npm:9.32.0" + version: 9.39.1 + resolution: "eslint@npm:9.39.1" dependencies: - "@eslint-community/eslint-utils": "npm:^4.2.0" + "@eslint-community/eslint-utils": "npm:^4.8.0" "@eslint-community/regexpp": "npm:^4.12.1" - "@eslint/config-array": "npm:^0.21.0" - "@eslint/config-helpers": "npm:^0.3.0" - "@eslint/core": "npm:^0.15.0" + "@eslint/config-array": "npm:^0.21.1" + "@eslint/config-helpers": "npm:^0.4.2" + "@eslint/core": "npm:^0.17.0" "@eslint/eslintrc": "npm:^3.3.1" - "@eslint/js": "npm:9.32.0" - "@eslint/plugin-kit": "npm:^0.3.4" + "@eslint/js": "npm:9.39.1" + "@eslint/plugin-kit": "npm:^0.4.1" "@humanfs/node": "npm:^0.16.6" "@humanwhocodes/module-importer": "npm:^1.0.1" "@humanwhocodes/retry": "npm:^0.4.2" "@types/estree": "npm:^1.0.6" - "@types/json-schema": "npm:^7.0.15" ajv: "npm:^6.12.4" chalk: "npm:^4.0.0" cross-spawn: "npm:^7.0.6" @@ -7267,7 +7583,7 @@ __metadata: optional: true bin: eslint: bin/eslint.js - checksum: 10c0/e8a23924ec5f8b62e95483002ca25db74e25c23bd9c6d98a9f656ee32f820169bee3bfdf548ec728b16694f198b3db857d85a49210ee4a035242711d08fdc602 + checksum: 10c0/59b2480639404ba24578ca480f973683b87b7aac8aa7e349240474a39067804fd13cd8b9cb22fee074170b8c7c563b57bab703ec0f0d3f81ea017e5d2cad299d languageName: node linkType: hard @@ -7347,7 +7663,7 @@ __metadata: languageName: node linkType: hard -"etag@npm:~1.8.1": +"etag@npm:^1.8.1": version: 1.8.1 resolution: "etag@npm:1.8.1" checksum: 10c0/12be11ef62fb9817314d790089a0a49fae4e1b50594135dcb8076312b7d7e470884b5100d249b28c18581b7fd52f8b485689ffae22a11ed9ec17377a33a08f84 @@ -7361,10 +7677,10 @@ __metadata: languageName: node linkType: hard -"expect-type@npm:^1.2.1": - version: 1.2.1 - resolution: "expect-type@npm:1.2.1" - checksum: 10c0/b775c9adab3c190dd0d398c722531726cdd6022849b4adba19dceab58dda7e000a7c6c872408cd73d665baa20d381eca36af4f7b393a4ba60dd10232d1fb8898 +"expect-type@npm:^1.2.2": + version: 1.2.2 + resolution: "expect-type@npm:1.2.2" + checksum: 10c0/6019019566063bbc7a690d9281d920b1a91284a4a093c2d55d71ffade5ac890cf37a51e1da4602546c4b56569d2ad2fc175a2ccee77d1ae06cb3af91ef84f44b languageName: node linkType: hard @@ -7375,56 +7691,53 @@ __metadata: languageName: node linkType: hard -"express@npm:^4.18.2": - version: 4.21.2 - resolution: "express@npm:4.21.2" - dependencies: - accepts: "npm:~1.3.8" - array-flatten: "npm:1.1.1" - body-parser: "npm:1.20.3" - content-disposition: "npm:0.5.4" - content-type: "npm:~1.0.4" - cookie: "npm:0.7.1" - cookie-signature: "npm:1.0.6" - debug: "npm:2.6.9" - depd: "npm:2.0.0" - encodeurl: "npm:~2.0.0" - escape-html: "npm:~1.0.3" - etag: "npm:~1.8.1" - finalhandler: "npm:1.3.1" - fresh: "npm:0.5.2" - http-errors: "npm:2.0.0" - merge-descriptors: "npm:1.0.3" - methods: "npm:~1.1.2" - on-finished: "npm:2.4.1" - parseurl: "npm:~1.3.3" - path-to-regexp: "npm:0.1.12" - proxy-addr: "npm:~2.0.7" - qs: "npm:6.13.0" - range-parser: "npm:~1.2.1" - safe-buffer: "npm:5.2.1" - send: "npm:0.19.0" - serve-static: "npm:1.16.2" - setprototypeof: "npm:1.2.0" - statuses: "npm:2.0.1" - type-is: "npm:~1.6.18" - utils-merge: "npm:1.0.1" - vary: "npm:~1.1.2" - checksum: 10c0/38168fd0a32756600b56e6214afecf4fc79ec28eca7f7a91c2ab8d50df4f47562ca3f9dee412da7f5cea6b1a1544b33b40f9f8586dbacfbdada0fe90dbb10a1f +"express@npm:^5.1.0": + version: 5.2.1 + resolution: "express@npm:5.2.1" + dependencies: + accepts: "npm:^2.0.0" + body-parser: "npm:^2.2.1" + content-disposition: "npm:^1.0.0" + content-type: "npm:^1.0.5" + cookie: "npm:^0.7.1" + cookie-signature: "npm:^1.2.1" + debug: "npm:^4.4.0" + depd: "npm:^2.0.0" + encodeurl: "npm:^2.0.0" + escape-html: "npm:^1.0.3" + etag: "npm:^1.8.1" + finalhandler: "npm:^2.1.0" + fresh: "npm:^2.0.0" + http-errors: "npm:^2.0.0" + merge-descriptors: "npm:^2.0.0" + mime-types: "npm:^3.0.0" + on-finished: "npm:^2.4.1" + once: "npm:^1.4.0" + parseurl: "npm:^1.3.3" + proxy-addr: "npm:^2.0.7" + qs: "npm:^6.14.0" + range-parser: "npm:^1.2.1" + router: "npm:^2.2.0" + send: "npm:^1.1.0" + serve-static: "npm:^2.2.0" + statuses: "npm:^2.0.1" + type-is: "npm:^2.0.1" + vary: "npm:^1.1.2" + checksum: 10c0/45e8c841ad188a41402ddcd1294901e861ee0819f632fb494f2ed344ef9c43315d294d443fb48d594e6586a3b779785120f43321417adaef8567316a55072949 languageName: node linkType: hard "fake-indexeddb@npm:^6.0.1": - version: 6.2.2 - resolution: "fake-indexeddb@npm:6.2.2" - checksum: 10c0/5ad98f05beb22d8591af1bcf8500d1a92d9a17b3e2c380dfa669770b4fecdbadc1ccd9c8ba5429a92a30fd2562f6ab24238992522a8574ffd365d7b809677f0a + version: 6.2.5 + resolution: "fake-indexeddb@npm:6.2.5" + checksum: 10c0/6c5e2fe84a61daa06d7ad63699d1041fe61847f15f92db12415634b3db94f363a64be9e08a3c3c4434af9c3c0132086b85c4d5dc5e8e06edae1e7daf70ce1f3c languageName: node linkType: hard -"fast-copy@npm:^3.0.2": - version: 3.0.2 - resolution: "fast-copy@npm:3.0.2" - checksum: 10c0/02e8b9fd03c8c024d2987760ce126456a0e17470850b51e11a1c3254eed6832e4733ded2d93316c82bc0b36aeb991ad1ff48d1ba95effe7add7c3ab8d8eb554a +"fast-copy@npm:^4.0.0": + version: 4.0.1 + resolution: "fast-copy@npm:4.0.1" + checksum: 10c0/5c0ccddc68cb8f071d7806681f4bb87741cc07ed3153bba49adc456607c6d38854bf921c318feba402442056f7babcdc435052f431f19b2c7c6cbeefe9ae5841 languageName: node linkType: hard @@ -7435,7 +7748,7 @@ __metadata: languageName: node linkType: hard -"fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.2, fast-glob@npm:^3.3.3": +"fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.3": version: 3.3.3 resolution: "fast-glob@npm:3.3.3" dependencies: @@ -7462,13 +7775,6 @@ __metadata: languageName: node linkType: hard -"fast-redact@npm:^3.1.1": - version: 3.3.0 - resolution: "fast-redact@npm:3.3.0" - checksum: 10c0/d81562510681e9ba6404ee5d3838ff5257a44d2f80937f5024c099049ff805437d0fae0124458a7e87535cc9dcf4de305bb075cab8f08d6c720bbc3447861b4e - languageName: node - linkType: hard - "fast-safe-stringify@npm:^2.1.1": version: 2.1.1 resolution: "fast-safe-stringify@npm:2.1.1" @@ -7525,12 +7831,12 @@ __metadata: languageName: node linkType: hard -"file-entry-cache@npm:^10.1.4": - version: 10.1.4 - resolution: "file-entry-cache@npm:10.1.4" +"file-entry-cache@npm:^11.1.1": + version: 11.1.1 + resolution: "file-entry-cache@npm:11.1.1" dependencies: - flat-cache: "npm:^6.1.13" - checksum: 10c0/78a7d6b257c620374a8fc5280f14acffc7bd5cb5d39a5bd3509c640f17209f5194eff6e3b476d19db7cfbe9f97abe85ec8d33260f7ed94225efb2a95a68841a6 + flat-cache: "npm:^6.1.19" + checksum: 10c0/aa639f5dd578f63984a941f34b112180a4bd9d091d03970752437958158932957fc576861b9fbcf4d6eceaeb0779ad5359befdc321cc1bac59aa6f56f6b1d205 languageName: node linkType: hard @@ -7561,18 +7867,17 @@ __metadata: languageName: node linkType: hard -"finalhandler@npm:1.3.1": - version: 1.3.1 - resolution: "finalhandler@npm:1.3.1" - dependencies: - debug: "npm:2.6.9" - encodeurl: "npm:~2.0.0" - escape-html: "npm:~1.0.3" - on-finished: "npm:2.4.1" - parseurl: "npm:~1.3.3" - statuses: "npm:2.0.1" - unpipe: "npm:~1.0.0" - checksum: 10c0/d38035831865a49b5610206a3a9a9aae4e8523cbbcd01175d0480ffbf1278c47f11d89be3ca7f617ae6d94f29cf797546a4619cd84dd109009ef33f12f69019f +"finalhandler@npm:^2.1.0": + version: 2.1.0 + resolution: "finalhandler@npm:2.1.0" + dependencies: + debug: "npm:^4.4.0" + encodeurl: "npm:^2.0.0" + escape-html: "npm:^1.0.3" + on-finished: "npm:^2.4.1" + parseurl: "npm:^1.3.3" + statuses: "npm:^2.0.1" + checksum: 10c0/da0bbca6d03873472ee890564eb2183f4ed377f25f3628a0fc9d16dac40bed7b150a0d82ebb77356e4c6d97d2796ad2dba22948b951dddee2c8768b0d1b9fb1f languageName: node linkType: hard @@ -7593,17 +7898,6 @@ __metadata: languageName: node linkType: hard -"find-up@npm:^7.0.0": - version: 7.0.0 - resolution: "find-up@npm:7.0.0" - dependencies: - locate-path: "npm:^7.2.0" - path-exists: "npm:^5.0.0" - unicorn-magic: "npm:^0.1.0" - checksum: 10c0/e6ee3e6154560bc0ab3bc3b7d1348b31513f9bdf49a5dd2e952495427d559fa48cdf33953e85a309a323898b43fa1bfbc8b80c880dfc16068384783034030008 - languageName: node - linkType: hard - "flat-cache@npm:^4.0.0": version: 4.0.1 resolution: "flat-cache@npm:4.0.1" @@ -7614,14 +7908,14 @@ __metadata: languageName: node linkType: hard -"flat-cache@npm:^6.1.13": - version: 6.1.14 - resolution: "flat-cache@npm:6.1.14" +"flat-cache@npm:^6.1.19": + version: 6.1.19 + resolution: "flat-cache@npm:6.1.19" dependencies: - cacheable: "npm:^2.0.1" + cacheable: "npm:^2.2.0" flatted: "npm:^3.3.3" - hookified: "npm:^1.12.0" - checksum: 10c0/e17eda47414b4742bc557650788f18255068621afb66b23dfc6a47b3ef3e6c366ede7329e71406bf331ef6f4a3b243040803eb175560b4ceb204779066ba6e92 + hookified: "npm:^1.13.0" + checksum: 10c0/80c2d3c6ff2b7327920dacf2ab57e70ba4e865120209e41295689f029fcec17a6b49892ded7ce758d968d2b097fa2f9ab4e52923d971f3cdc90af9faba4680fd languageName: node linkType: hard @@ -7651,13 +7945,13 @@ __metadata: languageName: node linkType: hard -"foreground-child@npm:^3.1.0": - version: 3.1.1 - resolution: "foreground-child@npm:3.1.1" +"foreground-child@npm:^3.1.0, foreground-child@npm:^3.3.1": + version: 3.3.1 + resolution: "foreground-child@npm:3.3.1" dependencies: - cross-spawn: "npm:^7.0.0" + cross-spawn: "npm:^7.0.6" signal-exit: "npm:^4.0.1" - checksum: 10c0/9700a0285628abaeb37007c9a4d92bd49f67210f09067638774338e146c8e9c825c5c877f072b2f75f41dc6a2d0be8664f79ffc03f6576649f54a84fb9b47de0 + checksum: 10c0/8986e4af2430896e65bc2788d6679067294d6aee9545daefc84923a0a4b399ad9c7a3ea7bd8c0b2b80fdf4a92de4c69df3f628233ff3224260e9c1541a9e9ed3 languageName: node linkType: hard @@ -7681,28 +7975,17 @@ __metadata: languageName: node linkType: hard -"fraction.js@npm:^4.3.7": - version: 4.3.7 - resolution: "fraction.js@npm:4.3.7" - checksum: 10c0/df291391beea9ab4c263487ffd9d17fed162dbb736982dee1379b2a8cc94e4e24e46ed508c6d278aded9080ba51872f1bc5f3a5fd8d7c74e5f105b508ac28711 - languageName: node - linkType: hard - -"fresh@npm:0.5.2": - version: 0.5.2 - resolution: "fresh@npm:0.5.2" - checksum: 10c0/c6d27f3ed86cc5b601404822f31c900dd165ba63fff8152a3ef714e2012e7535027063bc67ded4cb5b3a49fa596495d46cacd9f47d6328459cf570f08b7d9e5a +"fraction.js@npm:^5.3.4": + version: 5.3.4 + resolution: "fraction.js@npm:5.3.4" + checksum: 10c0/f90079fe9bfc665e0a07079938e8ff71115bce9462f17b32fc283f163b0540ec34dc33df8ed41bb56f028316b04361b9a9995b9ee9258617f8338e0b05c5f95a languageName: node linkType: hard -"fs-extra@npm:^11.3.0": - version: 11.3.0 - resolution: "fs-extra@npm:11.3.0" - dependencies: - graceful-fs: "npm:^4.2.0" - jsonfile: "npm:^6.0.1" - universalify: "npm:^2.0.0" - checksum: 10c0/5f95e996186ff45463059feb115a22fb048bdaf7e487ecee8a8646c78ed8fdca63630e3077d4c16ce677051f5e60d3355a06f3cd61f3ca43f48cc58822a44d0a +"fresh@npm:^2.0.0": + version: 2.0.0 + resolution: "fresh@npm:2.0.0" + checksum: 10c0/0557548194cb9a809a435bf92bcfbc20c89e8b5eb38861b73ced36750437251e39a111fc3a18b98531be9dd91fe1411e4969f229dc579ec0251ce6c5d4900bbc languageName: node linkType: hard @@ -7883,7 +8166,7 @@ __metadata: languageName: node linkType: hard -"glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2": +"glob-parent@npm:^5.1.2": version: 5.1.2 resolution: "glob-parent@npm:5.1.2" dependencies: @@ -7901,9 +8184,9 @@ __metadata: languageName: node linkType: hard -"glob@npm:^10.0.0, glob@npm:^10.2.2, glob@npm:^10.4.1": - version: 10.4.5 - resolution: "glob@npm:10.4.5" +"glob@npm:^10.0.0, glob@npm:^10.2.2": + version: 10.5.0 + resolution: "glob@npm:10.5.0" dependencies: foreground-child: "npm:^3.1.0" jackspeak: "npm:^3.1.2" @@ -7913,7 +8196,23 @@ __metadata: path-scurry: "npm:^1.11.1" bin: glob: dist/esm/bin.mjs - checksum: 10c0/19a9759ea77b8e3ca0a43c2f07ecddc2ad46216b786bb8f993c445aee80d345925a21e5280c7b7c6c59e860a0154b84e4b2b60321fea92cd3c56b4a7489f160e + checksum: 10c0/100705eddbde6323e7b35e1d1ac28bcb58322095bd8e63a7d0bef1a2cdafe0d0f7922a981b2b48369a4f8c1b077be5c171804534c3509dfe950dde15fbe6d828 + languageName: node + linkType: hard + +"glob@npm:^11.0.1": + version: 11.1.0 + resolution: "glob@npm:11.1.0" + dependencies: + foreground-child: "npm:^3.3.1" + jackspeak: "npm:^4.1.1" + minimatch: "npm:^10.1.1" + minipass: "npm:^7.1.2" + package-json-from-dist: "npm:^1.0.0" + path-scurry: "npm:^2.0.0" + bin: + glob: dist/esm/bin.mjs + checksum: 10c0/1ceae07f23e316a6fa74581d9a74be6e8c2e590d2f7205034dd5c0435c53f5f7b712c2be00c3b65bf0a49294a1c6f4b98cd84c7637e29453b5aa13b79f1763a2 languageName: node linkType: hard @@ -7959,9 +8258,9 @@ __metadata: linkType: hard "globals@npm:^16.0.0": - version: 16.3.0 - resolution: "globals@npm:16.3.0" - checksum: 10c0/c62dc20357d1c0bf2be4545d6c4141265d1a229bf1c3294955efb5b5ef611145391895e3f2729f8603809e81b30b516c33e6c2597573844449978606aad6eb38 + version: 16.5.0 + resolution: "globals@npm:16.5.0" + checksum: 10c0/615241dae7851c8012f5aa0223005b1ed6607713d6813de0741768bd4ddc39353117648f1a7086b4b0fa45eae733f1c0a0fe369aa4e543bb63f8de8990178ea9 languageName: node linkType: hard @@ -8010,7 +8309,7 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.6": +"graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.6": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 @@ -8079,6 +8378,15 @@ __metadata: languageName: node linkType: hard +"hashery@npm:^1.2.0": + version: 1.2.0 + resolution: "hashery@npm:1.2.0" + dependencies: + hookified: "npm:^1.13.0" + checksum: 10c0/57905ae4bcb12faedf222b1f39cc05424ae2a2bba1f613b9c582a4e5012b8361c14a25a5a0c16da7eca70ee8338ad2924d6b9566667014c927a36d4b90ad5b72 + languageName: node + linkType: hard + "hasown@npm:^2.0.2": version: 2.0.2 resolution: "hasown@npm:2.0.2" @@ -8102,6 +8410,22 @@ __metadata: languageName: node linkType: hard +"hermes-estree@npm:0.25.1": + version: 0.25.1 + resolution: "hermes-estree@npm:0.25.1" + checksum: 10c0/48be3b2fa37a0cbc77a112a89096fa212f25d06de92781b163d67853d210a8a5c3784fac23d7d48335058f7ed283115c87b4332c2a2abaaccc76d0ead1a282ac + languageName: node + linkType: hard + +"hermes-parser@npm:^0.25.1": + version: 0.25.1 + resolution: "hermes-parser@npm:0.25.1" + dependencies: + hermes-estree: "npm:0.25.1" + checksum: 10c0/3abaa4c6f1bcc25273f267297a89a4904963ea29af19b8e4f6eabe04f1c2c7e9abd7bfc4730ddb1d58f2ea04b6fee74053d8bddb5656ec6ebf6c79cc8d14202c + languageName: node + linkType: hard + "history@npm:^4.10.1, history@npm:^4.9.0": version: 4.10.1 resolution: "history@npm:4.10.1" @@ -8125,10 +8449,10 @@ __metadata: languageName: node linkType: hard -"hookified@npm:^1.12.0, hookified@npm:^1.12.1": - version: 1.12.1 - resolution: "hookified@npm:1.12.1" - checksum: 10c0/fe8d74ee49d1f79677dcdff7606eeb731f7a7dc59f61ec2141a11e3bb94ff6532f870649b900fa9f68568f410c504a338d8732e4d1abe61b426e645c37862e50 +"hookified@npm:^1.12.2, hookified@npm:^1.13.0": + version: 1.13.0 + resolution: "hookified@npm:1.13.0" + checksum: 10c0/26718a60385ab95f20173323c175a23b06efcc1fac613c51714c9c38038c7395ed52d3bea660840c8362d92dc38022ae4469c2a531728f6116f4df53f70505e7 languageName: node linkType: hard @@ -8141,6 +8465,13 @@ __metadata: languageName: node linkType: hard +"html-entities@npm:^2.6.0": + version: 2.6.0 + resolution: "html-entities@npm:2.6.0" + checksum: 10c0/7c8b15d9ea0cd00dc9279f61bab002ba6ca8a7a0f3c36ed2db3530a67a9621c017830d1d2c1c65beb9b8e3436ea663e9cf8b230472e0e413359399413b27c8b7 + languageName: node + linkType: hard + "html-escaper@npm:^2.0.0": version: 2.0.2 resolution: "html-escaper@npm:2.0.2" @@ -8162,16 +8493,16 @@ __metadata: languageName: node linkType: hard -"http-errors@npm:2.0.0": - version: 2.0.0 - resolution: "http-errors@npm:2.0.0" +"http-errors@npm:^2.0.0, http-errors@npm:~2.0.1": + version: 2.0.1 + resolution: "http-errors@npm:2.0.1" dependencies: - depd: "npm:2.0.0" - inherits: "npm:2.0.4" - setprototypeof: "npm:1.2.0" - statuses: "npm:2.0.1" - toidentifier: "npm:1.0.1" - checksum: 10c0/fc6f2715fe188d091274b5ffc8b3657bd85c63e969daa68ccb77afb05b071a4b62841acb7a21e417b5539014dff2ebf9550f0b14a9ff126f2734a7c1387f8e19 + depd: "npm:~2.0.0" + inherits: "npm:~2.0.4" + setprototypeof: "npm:~1.2.0" + statuses: "npm:~2.0.2" + toidentifier: "npm:~1.0.1" + checksum: 10c0/fb38906cef4f5c83952d97661fe14dc156cb59fe54812a42cd448fa57b5c5dfcb38a40a916957737bd6b87aab257c0648d63eb5b6a9ca9f548e105b6072712d4 languageName: node linkType: hard @@ -8211,21 +8542,30 @@ __metadata: languageName: node linkType: hard -"iconv-lite@npm:0.4.24": - version: 0.4.24 - resolution: "iconv-lite@npm:0.4.24" +"iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2, iconv-lite@npm:^0.6.3": + version: 0.6.3 + resolution: "iconv-lite@npm:0.6.3" dependencies: - safer-buffer: "npm:>= 2.1.2 < 3" - checksum: 10c0/c6886a24cc00f2a059767440ec1bc00d334a89f250db8e0f7feb4961c8727118457e27c495ba94d082e51d3baca378726cd110aaf7ded8b9bbfd6a44760cf1d4 + safer-buffer: "npm:>= 2.1.2 < 3.0.0" + checksum: 10c0/98102bc66b33fcf5ac044099d1257ba0b7ad5e3ccd3221f34dd508ab4070edff183276221684e1e0555b145fce0850c9f7d2b60a9fcac50fbb4ea0d6e845a3b1 languageName: node linkType: hard -"iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2": - version: 0.6.3 - resolution: "iconv-lite@npm:0.6.3" +"iconv-lite@npm:^0.7.0, iconv-lite@npm:~0.7.0": + version: 0.7.0 + resolution: "iconv-lite@npm:0.7.0" dependencies: safer-buffer: "npm:>= 2.1.2 < 3.0.0" - checksum: 10c0/98102bc66b33fcf5ac044099d1257ba0b7ad5e3ccd3221f34dd508ab4070edff183276221684e1e0555b145fce0850c9f7d2b60a9fcac50fbb4ea0d6e845a3b1 + checksum: 10c0/2382400469071c55b6746c531eed5fa4d033e5db6690b7331fb2a5f59a30d7a9782932e92253db26df33c1cf46fa200a3fbe524a2a7c62037c762283f188ec2f + languageName: node + linkType: hard + +"icss-utils@npm:^5.0.0, icss-utils@npm:^5.1.0": + version: 5.1.0 + resolution: "icss-utils@npm:5.1.0" + peerDependencies: + postcss: ^8.1.0 + checksum: 10c0/39c92936fabd23169c8611d2b5cc39e39d10b19b0d223352f20a7579f75b39d5f786114a6b8fc62bee8c5fed59ba9e0d38f7219a4db383e324fb3061664b043d languageName: node linkType: hard @@ -8264,6 +8604,15 @@ __metadata: languageName: node linkType: hard +"image-size@npm:~0.5.0": + version: 0.5.5 + resolution: "image-size@npm:0.5.5" + bin: + image-size: bin/image-size.js + checksum: 10c0/655204163af06732f483a9fe7cce9dff4a29b7b2e88f5c957a5852e8143fa750f5e54b1955a2ca83de99c5220dbd680002d0d4e09140b01433520f4d5a0b1f4c + languageName: node + linkType: hard + "immer@npm:^10.0.3": version: 10.0.3 resolution: "immer@npm:10.0.3" @@ -8326,7 +8675,7 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2, inherits@npm:2.0.4": +"inherits@npm:2, inherits@npm:~2.0.4": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 @@ -8358,15 +8707,15 @@ __metadata: languageName: node linkType: hard -"intl-messageformat@npm:10.7.16, intl-messageformat@npm:^10.7.16": - version: 10.7.16 - resolution: "intl-messageformat@npm:10.7.16" +"intl-messageformat@npm:10.7.18, intl-messageformat@npm:^10.7.16": + version: 10.7.18 + resolution: "intl-messageformat@npm:10.7.18" dependencies: - "@formatjs/ecma402-abstract": "npm:2.3.4" + "@formatjs/ecma402-abstract": "npm:2.3.6" "@formatjs/fast-memoize": "npm:2.2.7" - "@formatjs/icu-messageformat-parser": "npm:2.11.2" + "@formatjs/icu-messageformat-parser": "npm:2.11.4" tslib: "npm:^2.8.0" - checksum: 10c0/537735bf6439f0560f132895d117df6839957ac04cdd58d861f6da86803d40bfc19059e3d341ddb8de87214b73a6329b57f9acdb512bb0f745dcf08729507b9b + checksum: 10c0/d54da9987335cb2bca26246304cea2ca6b1cb44ca416d6b28f3aa62b11477c72f7ce0bf3f11f5d236ceb1842bdc3378a926e606496d146fde18783ec92c314e1 languageName: node linkType: hard @@ -8380,10 +8729,10 @@ __metadata: linkType: hard "ioredis@npm:^5.3.2": - version: 5.7.0 - resolution: "ioredis@npm:5.7.0" + version: 5.8.2 + resolution: "ioredis@npm:5.8.2" dependencies: - "@ioredis/commands": "npm:^1.3.0" + "@ioredis/commands": "npm:1.4.0" cluster-key-slot: "npm:^1.1.0" debug: "npm:^4.3.4" denque: "npm:^2.1.0" @@ -8392,7 +8741,7 @@ __metadata: redis-errors: "npm:^1.2.0" redis-parser: "npm:^3.0.0" standard-as-callback: "npm:^2.1.0" - checksum: 10c0/c63c521a953bfaf29f8c8871b122af38e439328336fa238f83bfbb066556f64daf69ed7a4ec01fc7b9ee1f0862059dd188b8c684150125d362d36642399b30ee + checksum: 10c0/305e385f811d49908899e32c2de69616cd059f909afd9e0a53e54f596b1a5835ee3449bfc6a3c49afbc5a2fd27990059e316cc78f449c94024957bd34c826d88 languageName: node linkType: hard @@ -8446,15 +8795,6 @@ __metadata: languageName: node linkType: hard -"is-binary-path@npm:~2.1.0": - version: 2.1.0 - resolution: "is-binary-path@npm:2.1.0" - dependencies: - binary-extensions: "npm:^2.0.0" - checksum: 10c0/a16eaee59ae2b315ba36fad5c5dcaf8e49c3e27318f8ab8fa3cdb8772bf559c8d1ba750a589c2ccb096113bb64497084361a25960899cb6172a6925ab6123d38 - languageName: node - linkType: hard - "is-boolean-object@npm:^1.2.1": version: 1.2.2 resolution: "is-boolean-object@npm:1.2.2" @@ -8520,6 +8860,15 @@ __metadata: languageName: node linkType: hard +"is-docker@npm:^3.0.0": + version: 3.0.0 + resolution: "is-docker@npm:3.0.0" + bin: + is-docker: cli.js + checksum: 10c0/d2c4f8e6d3e34df75a5defd44991b6068afad4835bb783b902fa12d13ebdb8f41b2a199dcb0b5ed2cb78bfee9e4c0bbdb69c2d9646f4106464674d3e697a5856 + languageName: node + linkType: hard + "is-extglob@npm:^2.1.1": version: 2.1.1 resolution: "is-extglob@npm:2.1.1" @@ -8561,7 +8910,7 @@ __metadata: languageName: node linkType: hard -"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3, is-glob@npm:~4.0.1": +"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3": version: 4.0.3 resolution: "is-glob@npm:4.0.3" dependencies: @@ -8570,6 +8919,17 @@ __metadata: languageName: node linkType: hard +"is-inside-container@npm:^1.0.0": + version: 1.0.0 + resolution: "is-inside-container@npm:1.0.0" + dependencies: + is-docker: "npm:^3.0.0" + bin: + is-inside-container: cli.js + checksum: 10c0/a8efb0e84f6197e6ff5c64c52890fa9acb49b7b74fed4da7c95383965da6f0fa592b4dbd5e38a79f87fc108196937acdbcd758fcefc9b140e479b39ce1fcd1cd + languageName: node + linkType: hard + "is-map@npm:^2.0.3": version: 2.0.3 resolution: "is-map@npm:2.0.3" @@ -8636,6 +8996,13 @@ __metadata: languageName: node linkType: hard +"is-promise@npm:^4.0.0": + version: 4.0.0 + resolution: "is-promise@npm:4.0.0" + checksum: 10c0/ebd5c672d73db781ab33ccb155fb9969d6028e37414d609b115cc534654c91ccd061821d5b987eefaa97cf4c62f0b909bb2f04db88306de26e91bfe8ddc01503 + languageName: node + linkType: hard + "is-regex@npm:^1.2.1": version: 1.2.1 resolution: "is-regex@npm:1.2.1" @@ -8741,6 +9108,13 @@ __metadata: languageName: node linkType: hard +"is-what@npm:^3.14.1": + version: 3.14.1 + resolution: "is-what@npm:3.14.1" + checksum: 10c0/4b770b85454c877b6929a84fd47c318e1f8c2ff70fd72fd625bc3fde8e0c18a6e57345b6e7aa1ee9fbd1c608d27cfe885df473036c5c2e40cd2187250804a2c7 + languageName: node + linkType: hard + "is-wsl@npm:^2.2.0": version: 2.2.0 resolution: "is-wsl@npm:2.2.0" @@ -8750,6 +9124,15 @@ __metadata: languageName: node linkType: hard +"is-wsl@npm:^3.1.0": + version: 3.1.0 + resolution: "is-wsl@npm:3.1.0" + dependencies: + is-inside-container: "npm:^1.0.0" + checksum: 10c0/d3317c11995690a32c362100225e22ba793678fe8732660c6de511ae71a0ff05b06980cf21f98a6bf40d7be0e9e9506f859abe00a1118287d63e53d0a3d06947 + languageName: node + linkType: hard + "isarray@npm:0.0.1": version: 0.0.1 resolution: "isarray@npm:0.0.1" @@ -8807,13 +9190,13 @@ __metadata: languageName: node linkType: hard -"istanbul-reports@npm:^3.1.7": - version: 3.1.7 - resolution: "istanbul-reports@npm:3.1.7" +"istanbul-reports@npm:^3.2.0": + version: 3.2.0 + resolution: "istanbul-reports@npm:3.2.0" dependencies: html-escaper: "npm:^2.0.0" istanbul-lib-report: "npm:^3.0.0" - checksum: 10c0/a379fadf9cf8dc5dfe25568115721d4a7eb82fbd50b005a6672aff9c6989b20cc9312d7865814e0859cd8df58cbf664482e1d3604be0afde1f7fc3ccc1394a51 + checksum: 10c0/d596317cfd9c22e1394f22a8d8ba0303d2074fe2e971887b32d870e4b33f8464b10f8ccbe6847808f7db485f084eba09e6c2ed706b3a978e4b52f07085b8f9bc languageName: node linkType: hard @@ -8844,6 +9227,15 @@ __metadata: languageName: node linkType: hard +"jackspeak@npm:^4.1.1": + version: 4.1.1 + resolution: "jackspeak@npm:4.1.1" + dependencies: + "@isaacs/cliui": "npm:^8.0.2" + checksum: 10c0/84ec4f8e21d6514db24737d9caf65361511f75e5e424980eebca4199f400874f45e562ac20fa8aeb1dd20ca2f3f81f0788b6e9c3e64d216a5794fd6f30e0e042 + languageName: node + linkType: hard + "jake@npm:^10.8.5": version: 10.8.7 resolution: "jake@npm:10.8.7" @@ -8880,37 +9272,37 @@ __metadata: linkType: hard "js-yaml@npm:^4.1.0": - version: 4.1.0 - resolution: "js-yaml@npm:4.1.0" + version: 4.1.1 + resolution: "js-yaml@npm:4.1.1" dependencies: argparse: "npm:^2.0.1" bin: js-yaml: bin/js-yaml.js - checksum: 10c0/184a24b4eaacfce40ad9074c64fd42ac83cf74d8c8cd137718d456ced75051229e5061b8633c3366b8aada17945a7a356b337828c19da92b51ae62126575018f + checksum: 10c0/561c7d7088c40a9bb53cc75becbfb1df6ae49b34b5e6e5a81744b14ae8667ec564ad2527709d1a6e7d5e5fa6d483aa0f373a50ad98d42fde368ec4a190d4fae7 languageName: node linkType: hard -"jsdoc-type-pratt-parser@npm:~4.1.0": - version: 4.1.0 - resolution: "jsdoc-type-pratt-parser@npm:4.1.0" - checksum: 10c0/7700372d2e733a32f7ea0a1df9cec6752321a5345c11a91b2ab478a031a426e934f16d5c1f15c8566c7b2c10af9f27892a29c2c789039f595470e929a4aa60ea +"jsdoc-type-pratt-parser@npm:~6.10.0": + version: 6.10.0 + resolution: "jsdoc-type-pratt-parser@npm:6.10.0" + checksum: 10c0/8ea395df0cae0e41d4bdba5f8d81b8d3e467fe53d1e4182a5d4e653235a5f17d60ed137343d68dbc74fa10e767f1c58fb85b1f6d5489c2cf16fc7216cc6d3e1a languageName: node linkType: hard "jsdom@npm:^27.0.0": - version: 27.0.0 - resolution: "jsdom@npm:27.0.0" + version: 27.3.0 + resolution: "jsdom@npm:27.3.0" dependencies: - "@asamuzakjp/dom-selector": "npm:^6.5.4" - cssstyle: "npm:^5.3.0" + "@acemir/cssom": "npm:^0.9.28" + "@asamuzakjp/dom-selector": "npm:^6.7.6" + cssstyle: "npm:^5.3.4" data-urls: "npm:^6.0.0" - decimal.js: "npm:^10.5.0" + decimal.js: "npm:^10.6.0" html-encoding-sniffer: "npm:^4.0.0" http-proxy-agent: "npm:^7.0.2" https-proxy-agent: "npm:^7.0.6" is-potential-custom-element-name: "npm:^1.0.1" - parse5: "npm:^7.3.0" - rrweb-cssom: "npm:^0.8.0" + parse5: "npm:^8.0.0" saxes: "npm:^6.0.0" symbol-tree: "npm:^3.2.4" tough-cookie: "npm:^6.0.0" @@ -8918,15 +9310,15 @@ __metadata: webidl-conversions: "npm:^8.0.0" whatwg-encoding: "npm:^3.1.1" whatwg-mimetype: "npm:^4.0.0" - whatwg-url: "npm:^15.0.0" - ws: "npm:^8.18.2" + whatwg-url: "npm:^15.1.0" + ws: "npm:^8.18.3" xml-name-validator: "npm:^5.0.0" peerDependencies: canvas: ^3.0.0 peerDependenciesMeta: canvas: optional: true - checksum: 10c0/cc977bd0f48f92b275166b3e64622d83c2073dc309b790ed806246365985743295a7735bc8519a186ccffd42d1f2c16a0fa52a4ea79d2b329a948756db64ade1 + checksum: 10c0/b022ed8f6ce175afd97fbd42eb65b03b2be3b23df86cf87f018b6d2e757682fe8348e719a14780d6fa3fe8a65e531ba71b38db80f312818a32b77f01e31f267e languageName: node linkType: hard @@ -8981,16 +9373,16 @@ __metadata: languageName: node linkType: hard -"json-stable-stringify@npm:^1.1.1": - version: 1.2.1 - resolution: "json-stable-stringify@npm:1.2.1" +"json-stable-stringify@npm:^1.3.0": + version: 1.3.0 + resolution: "json-stable-stringify@npm:1.3.0" dependencies: call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.3" + call-bound: "npm:^1.0.4" isarray: "npm:^2.0.5" jsonify: "npm:^0.0.1" object-keys: "npm:^1.1.1" - checksum: 10c0/e623e7ce89282f089d56454087edb717357e8572089b552fbc6980fb7814dc3943f7d0e4f1a19429a36ce9f4428b6c8ee6883357974457aaaa98daba5adebeea + checksum: 10c0/8b3ff19e4c23c0ad591a49bc3a015d89a538db787d12fe9c4072e1d64d8cfa481f8c37719c629c3d84e848847617bf49f5fee894cf1d25959ab5b67e1c517f31 languageName: node linkType: hard @@ -9053,13 +9445,6 @@ __metadata: languageName: node linkType: hard -"keycode@npm:^2.1.7": - version: 2.2.1 - resolution: "keycode@npm:2.2.1" - checksum: 10c0/e38ecbdc62f57e18ef9f7ad88aefded84e05b89115a40eea3081a7002d7c055765ddb5f5c3e2dd36ac2b2ab3901053c8286c9db082fd807b3abeb7e44034d96a - languageName: node - linkType: hard - "keyv@npm:^4.5.4": version: 4.5.4 resolution: "keyv@npm:4.5.4" @@ -9069,12 +9454,12 @@ __metadata: languageName: node linkType: hard -"keyv@npm:^5.5.1": - version: 5.5.2 - resolution: "keyv@npm:5.5.2" +"keyv@npm:^5.5.4": + version: 5.5.4 + resolution: "keyv@npm:5.5.4" dependencies: "@keyv/serialize": "npm:^1.1.1" - checksum: 10c0/b0a224210e8bbc4a5913535aa7cc8552809dc81ad67311cc78a2ccfe5485b82b23b8e28ea3a1202e8352c18dd2a0b12b49cda5bb933958d2dcf88042db54c9d0 + checksum: 10c0/8dad7f61022c6348c4c691a19468b7c238198252edbd3cc08277d95253c137af7ce5ffd763b6ffded4a75cbe03dc3134f1adcd3dd26c5767c2c9c254e3b39001 languageName: node linkType: hard @@ -9131,6 +9516,41 @@ __metadata: languageName: node linkType: hard +"less@npm:^4.2.0": + version: 4.4.2 + resolution: "less@npm:4.4.2" + dependencies: + copy-anything: "npm:^2.0.1" + errno: "npm:^0.1.1" + graceful-fs: "npm:^4.1.2" + image-size: "npm:~0.5.0" + make-dir: "npm:^2.1.0" + mime: "npm:^1.4.1" + needle: "npm:^3.1.0" + parse-node-version: "npm:^1.0.1" + source-map: "npm:~0.6.0" + tslib: "npm:^2.3.0" + dependenciesMeta: + errno: + optional: true + graceful-fs: + optional: true + image-size: + optional: true + make-dir: + optional: true + mime: + optional: true + needle: + optional: true + source-map: + optional: true + bin: + lessc: bin/lessc + checksum: 10c0/f8b796e45ef171adc390b5250f3018922cd046c256181dd9d4cbcbbdc5d6de7cb88c8327741c10eff7ff76421cd826fd95a664ea1b88fbf6f31742428d4a2dab + languageName: node + linkType: hard + "leven@npm:^3.1.0": version: 3.1.0 resolution: "leven@npm:3.1.0" @@ -9148,6 +9568,13 @@ __metadata: languageName: node linkType: hard +"lilconfig@npm:^2.0.5": + version: 2.1.0 + resolution: "lilconfig@npm:2.1.0" + checksum: 10c0/64645641aa8d274c99338e130554abd6a0190533c0d9eb2ce7ebfaf2e05c7d9961f3ffe2bfa39efd3b60c521ba3dd24fa236fe2775fc38501bf82bf49d4678b8 + languageName: node + linkType: hard + "lines-and-columns@npm:^1.1.6": version: 1.2.4 resolution: "lines-and-columns@npm:1.2.4" @@ -9155,26 +9582,26 @@ __metadata: languageName: node linkType: hard -"lint-staged@npm:^16.0.0": - version: 16.2.0 - resolution: "lint-staged@npm:16.2.0" +"lint-staged@npm:^16.2.6": + version: 16.2.7 + resolution: "lint-staged@npm:16.2.7" dependencies: - commander: "npm:14.0.1" - listr2: "npm:9.0.4" - micromatch: "npm:4.0.8" - nano-spawn: "npm:1.0.3" - pidtree: "npm:0.6.0" - string-argv: "npm:0.3.2" - yaml: "npm:2.8.1" + commander: "npm:^14.0.2" + listr2: "npm:^9.0.5" + micromatch: "npm:^4.0.8" + nano-spawn: "npm:^2.0.0" + pidtree: "npm:^0.6.0" + string-argv: "npm:^0.3.2" + yaml: "npm:^2.8.1" bin: lint-staged: bin/lint-staged.js - checksum: 10c0/0903eea526d390fd04ec10c2b6c3c3accdff53647d1918d3c30236d46fa7714a4a109d1aeda7364ecc689728c7b188bc430911b170834228fe0010d938c9a631 + checksum: 10c0/9a677c21a8112d823ae5bc565ba2c9e7b803786f2a021c46827a55fe44ed59def96edb24fc99c06a2545cdbbf366022ad82addcb3bf60c712f3b98ef92069717 languageName: node linkType: hard -"listr2@npm:9.0.4": - version: 9.0.4 - resolution: "listr2@npm:9.0.4" +"listr2@npm:^9.0.5": + version: 9.0.5 + resolution: "listr2@npm:9.0.5" dependencies: cli-truncate: "npm:^5.0.0" colorette: "npm:^2.0.20" @@ -9182,7 +9609,7 @@ __metadata: log-update: "npm:^6.1.0" rfdc: "npm:^1.4.1" wrap-ansi: "npm:^9.0.0" - checksum: 10c0/69feca532f5b3317112a74bc7589ad29f98ccfbe1a582bdab556d536978b094e5841b94069e01cf59ea919684dfb68218754526ddd317b1dc829ab57f7450e45 + checksum: 10c0/46448d1ba0addc9d71aeafd05bb8e86ded9641ccad930ac302c2bd2ad71580375604743e18586fcb8f11906edf98e8e17fca75ba0759947bf275d381f68e311d languageName: node linkType: hard @@ -9195,12 +9622,10 @@ __metadata: languageName: node linkType: hard -"locate-path@npm:^7.2.0": - version: 7.2.0 - resolution: "locate-path@npm:7.2.0" - dependencies: - p-locate: "npm:^6.0.0" - checksum: 10c0/139e8a7fe11cfbd7f20db03923cacfa5db9e14fa14887ea121345597472b4a63c1a42a8a5187defeeff6acf98fd568da7382aa39682d38f0af27433953a97751 +"lodash.camelcase@npm:^4.3.0": + version: 4.3.0 + resolution: "lodash.camelcase@npm:4.3.0" + checksum: 10c0/fcba15d21a458076dd309fce6b1b4bf611d84a0ec252cb92447c948c533ac250b95d2e00955801ebc367e5af5ed288b996d75d37d2035260a937008e14eaf432 languageName: node linkType: hard @@ -9300,10 +9725,10 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^11.1.0": - version: 11.2.1 - resolution: "lru-cache@npm:11.2.1" - checksum: 10c0/6f0e6b27f368d5e464e7813bd5b0af8f9a81a3a7ce2f40509841fdef07998b2588869f3e70edfbdb3bf705857f7bb21cca58fb01e1a1dc2440a83fcedcb7e8d8 +"lru-cache@npm:^11.0.0, lru-cache@npm:^11.2.2, lru-cache@npm:^11.2.4": + version: 11.2.4 + resolution: "lru-cache@npm:11.2.4" + checksum: 10c0/4a24f9b17537619f9144d7b8e42cd5a225efdfd7076ebe7b5e7dc02b860a818455201e67fbf000765233fe7e339d3c8229fc815e9b58ee6ede511e07608c19b2 languageName: node linkType: hard @@ -9334,23 +9759,33 @@ __metadata: languageName: node linkType: hard -"magic-string@npm:^0.30.0, magic-string@npm:^0.30.17, magic-string@npm:~0.30.11": - version: 0.30.17 - resolution: "magic-string@npm:0.30.17" +"magic-string@npm:^0.30.0, magic-string@npm:^0.30.17, magic-string@npm:^0.30.21, magic-string@npm:~0.30.11": + version: 0.30.21 + resolution: "magic-string@npm:0.30.21" dependencies: - "@jridgewell/sourcemap-codec": "npm:^1.5.0" - checksum: 10c0/16826e415d04b88378f200fe022b53e638e3838b9e496edda6c0e086d7753a44a6ed187adc72d19f3623810589bf139af1a315541cd6a26ae0771a0193eaf7b8 + "@jridgewell/sourcemap-codec": "npm:^1.5.5" + checksum: 10c0/299378e38f9a270069fc62358522ddfb44e94244baa0d6a8980ab2a9b2490a1d03b236b447eee309e17eb3bddfa482c61259d47960eb018a904f0ded52780c4a languageName: node linkType: hard -"magicast@npm:^0.3.5": - version: 0.3.5 - resolution: "magicast@npm:0.3.5" +"magicast@npm:^0.5.1": + version: 0.5.1 + resolution: "magicast@npm:0.5.1" dependencies: - "@babel/parser": "npm:^7.25.4" - "@babel/types": "npm:^7.25.4" - source-map-js: "npm:^1.2.0" - checksum: 10c0/a6cacc0a848af84f03e3f5bda7b0de75e4d0aa9ddce5517fd23ed0f31b5ddd51b2d0ff0b7e09b51f7de0f4053c7a1107117edda6b0732dca3e9e39e6c5a68c64 + "@babel/parser": "npm:^7.28.5" + "@babel/types": "npm:^7.28.5" + source-map-js: "npm:^1.2.1" + checksum: 10c0/a00bbf3688b9b3e83c10b3bfe3f106cc2ccbf20c4f2dc1c9020a10556dfe0a6a6605a445ee8e86a6e2b484ec519a657b5e405532684f72678c62e4c0d32f962c + languageName: node + linkType: hard + +"make-dir@npm:^2.1.0": + version: 2.1.0 + resolution: "make-dir@npm:2.1.0" + dependencies: + pify: "npm:^4.0.1" + semver: "npm:^5.6.0" + checksum: 10c0/ada869944d866229819735bee5548944caef560d7a8536ecbc6536edca28c72add47cc4f6fc39c54fb25d06b58da1f8994cf7d9df7dadea047064749efc085d8 languageName: node linkType: hard @@ -9417,10 +9852,10 @@ __metadata: languageName: node linkType: hard -"media-typer@npm:0.3.0": - version: 0.3.0 - resolution: "media-typer@npm:0.3.0" - checksum: 10c0/d160f31246907e79fed398470285f21bafb45a62869dc469b1c8877f3f064f5eabc4bcc122f9479b8b605bc5c76187d7871cf84c4ee3ecd3e487da1993279928 +"media-typer@npm:^1.1.0": + version: 1.1.0 + resolution: "media-typer@npm:1.1.0" + checksum: 10c0/7b4baa40b25964bb90e2121ee489ec38642127e48d0cc2b6baa442688d3fde6262bfdca86d6bbf6ba708784afcac168c06840c71facac70e390f5f759ac121b9 languageName: node linkType: hard @@ -9438,10 +9873,10 @@ __metadata: languageName: node linkType: hard -"merge-descriptors@npm:1.0.3": - version: 1.0.3 - resolution: "merge-descriptors@npm:1.0.3" - checksum: 10c0/866b7094afd9293b5ea5dcd82d71f80e51514bed33b4c4e9f516795dc366612a4cbb4dc94356e943a8a6914889a914530badff27f397191b9b75cda20b6bae93 +"merge-descriptors@npm:^2.0.0": + version: 2.0.0 + resolution: "merge-descriptors@npm:2.0.0" + checksum: 10c0/95389b7ced3f9b36fbdcf32eb946dc3dd1774c2fdf164609e55b18d03aa499b12bd3aae3a76c1c7185b96279e9803525550d3eb292b5224866060a288f335cb3 languageName: node linkType: hard @@ -9452,14 +9887,7 @@ __metadata: languageName: node linkType: hard -"methods@npm:~1.1.2": - version: 1.1.2 - resolution: "methods@npm:1.1.2" - checksum: 10c0/bdf7cc72ff0a33e3eede03708c08983c4d7a173f91348b4b1e4f47d4cdbf734433ad971e7d1e8c77247d9e5cd8adb81ea4c67b0a2db526b758b2233d7814b8b2 - languageName: node - linkType: hard - -"micromatch@npm:4.0.8, micromatch@npm:^4.0.5, micromatch@npm:^4.0.8": +"micromatch@npm:^4.0.5, micromatch@npm:^4.0.8": version: 4.0.8 resolution: "micromatch@npm:4.0.8" dependencies: @@ -9476,7 +9904,14 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:^2.1.12, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": +"mime-db@npm:^1.54.0": + version: 1.54.0 + resolution: "mime-db@npm:1.54.0" + checksum: 10c0/8d907917bc2a90fa2df842cdf5dfeaf509adc15fe0531e07bb2f6ab15992416479015828d6a74200041c492e42cce3ebf78e5ce714388a0a538ea9c53eece284 + languageName: node + linkType: hard + +"mime-types@npm:^2.1.12": version: 2.1.35 resolution: "mime-types@npm:2.1.35" dependencies: @@ -9485,7 +9920,16 @@ __metadata: languageName: node linkType: hard -"mime@npm:1.6.0": +"mime-types@npm:^3.0.0, mime-types@npm:^3.0.1": + version: 3.0.1 + resolution: "mime-types@npm:3.0.1" + dependencies: + mime-db: "npm:^1.54.0" + checksum: 10c0/bd8c20d3694548089cf229016124f8f40e6a60bbb600161ae13e45f793a2d5bb40f96bbc61f275836696179c77c1d6bf4967b2a75e0a8ad40fe31f4ed5be4da5 + languageName: node + linkType: hard + +"mime@npm:^1.4.1": version: 1.6.0 resolution: "mime@npm:1.6.0" bin: @@ -9508,6 +9952,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:^10.1.1": + version: 10.1.1 + resolution: "minimatch@npm:10.1.1" + dependencies: + "@isaacs/brace-expansion": "npm:^5.0.0" + checksum: 10c0/c85d44821c71973d636091fddbfbffe62370f5ee3caf0241c5b60c18cd289e916200acb2361b7e987558cd06896d153e25d505db9fc1e43e6b4b6752e2702902 + languageName: node + linkType: hard + "minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" @@ -9634,42 +10087,33 @@ __metadata: languageName: node linkType: hard -"ms@npm:2.0.0": - version: 2.0.0 - resolution: "ms@npm:2.0.0" - checksum: 10c0/f8fda810b39fd7255bbdc451c46286e549794fcc700dc9cd1d25658bbc4dc2563a5de6fe7c60f798a16a60c6ceb53f033cb353f493f0cf63e5199b702943159d - languageName: node - linkType: hard - -"ms@npm:2.1.3, ms@npm:^2.1.1, ms@npm:^2.1.3": +"ms@npm:^2.1.1, ms@npm:^2.1.3": version: 2.1.3 resolution: "ms@npm:2.1.3" checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 languageName: node linkType: hard -"msw-storybook-addon@npm:^2.0.5": - version: 2.0.5 - resolution: "msw-storybook-addon@npm:2.0.5" +"msw-storybook-addon@npm:^2.0.6": + version: 2.0.6 + resolution: "msw-storybook-addon@npm:2.0.6" dependencies: is-node-process: "npm:^1.0.1" peerDependencies: msw: ^2.0.0 - checksum: 10c0/3f26fd4a8a6b1b4da165a8940eca4da2e175a69036a1c85c07ec1952fbb595252db689c4380d8f88ec1cfaa66a6696e90ef0c26b2d1bf17c30092b81247d1d40 + checksum: 10c0/e108e8bf247326f5475c1914fdcfaaa6603f1e46f0920ed40d1f1725e2c2f62698658ff8b7d75aee0a0ea1fc0a9714fd6caaf5278426b6dcefd5da381f462c8e languageName: node linkType: hard -"msw@npm:^2.10.2": - version: 2.11.3 - resolution: "msw@npm:2.11.3" +"msw@npm:^2.12.1": + version: 2.12.1 + resolution: "msw@npm:2.12.1" dependencies: - "@bundled-es-modules/cookie": "npm:^2.0.1" - "@bundled-es-modules/statuses": "npm:^1.0.1" "@inquirer/confirm": "npm:^5.0.0" - "@mswjs/interceptors": "npm:^0.39.1" + "@mswjs/interceptors": "npm:^0.40.0" "@open-draft/deferred-promise": "npm:^2.2.0" - "@types/cookie": "npm:^0.6.0" "@types/statuses": "npm:^2.0.4" + cookie: "npm:^1.0.2" graphql: "npm:^16.8.1" headers-polyfill: "npm:^4.0.2" is-node-process: "npm:^1.2.0" @@ -9677,6 +10121,7 @@ __metadata: path-to-regexp: "npm:^6.3.0" picocolors: "npm:^1.1.1" rettime: "npm:^0.7.0" + statuses: "npm:^2.0.2" strict-event-emitter: "npm:^0.5.1" tough-cookie: "npm:^6.0.0" type-fest: "npm:^4.26.1" @@ -9689,7 +10134,7 @@ __metadata: optional: true bin: msw: cli/index.js - checksum: 10c0/847cb0e66152328d898474cd12e2ea0b9c11ed997b3a1129018a99afd7ff1134e45aed7158d0a57a1052b7a80b241e91952285f915142bd70c905316dbe9a49d + checksum: 10c0/822f4fc0cb2bdade39a67045d56b32fc7b15f30814a64c637a3c55d99358a4c1d61ed00d21fafafbbee320ad600e5a048d938b195e0cef5c59e016a040595176 languageName: node linkType: hard @@ -9700,10 +10145,10 @@ __metadata: languageName: node linkType: hard -"nano-spawn@npm:1.0.3": - version: 1.0.3 - resolution: "nano-spawn@npm:1.0.3" - checksum: 10c0/ea18857e493710a50ded333dd71677953bd9bd9e6a17ade74af957763c50a9a02205ef31bc0d6784f5b3ad82db3d9f47531e9baac2acf01118f9b7c35bd9d5de +"nano-spawn@npm:^2.0.0": + version: 2.0.0 + resolution: "nano-spawn@npm:2.0.0" + checksum: 10c0/d00f9b5739f86e28cb732ffd774793e110810cded246b8393c75c4f22674af47f98ee37b19f022ada2d8c9425f800e841caa0662fbff4c0930a10e39339fb366 languageName: node linkType: hard @@ -9732,10 +10177,15 @@ __metadata: languageName: node linkType: hard -"negotiator@npm:0.6.3": - version: 0.6.3 - resolution: "negotiator@npm:0.6.3" - checksum: 10c0/3ec9fd413e7bf071c937ae60d572bc67155262068ed522cf4b3be5edbe6ddf67d095ec03a3a14ebf8fc8e95f8e1d61be4869db0dbb0de696f6b837358bd43fc2 +"needle@npm:^3.1.0": + version: 3.3.1 + resolution: "needle@npm:3.3.1" + dependencies: + iconv-lite: "npm:^0.6.3" + sax: "npm:^1.2.4" + bin: + needle: bin/needle + checksum: 10c0/233b9315d47b735867d03e7a018fb665ee6cacf3a83b991b19538019cf42b538a3e85ca745c840b4c5e9a0ffdca76472f941363bf7c166214ae8cbc650fd4d39 languageName: node linkType: hard @@ -9810,10 +10260,10 @@ __metadata: languageName: node linkType: hard -"node-releases@npm:^2.0.21": - version: 2.0.21 - resolution: "node-releases@npm:2.0.21" - checksum: 10c0/0eb94916eeebbda9d51da6a9ea47428a12b2bb0dd94930c949632b0c859356abf53b2e5a2792021f96c5fda4f791a8e195f2375b78ae7dba8d8bc3141baa1469 +"node-releases@npm:^2.0.27": + version: 2.0.27 + resolution: "node-releases@npm:2.0.27" + checksum: 10c0/f1e6583b7833ea81880627748d28a3a7ff5703d5409328c216ae57befbced10ce2c991bea86434e8ec39003bd017f70481e2e5f8c1f7e0a7663241f81d6e00e2 languageName: node linkType: hard @@ -9828,7 +10278,7 @@ __metadata: languageName: node linkType: hard -"normalize-path@npm:^3.0.0, normalize-path@npm:~3.0.0": +"normalize-path@npm:^3.0.0": version: 3.0.0 resolution: "normalize-path@npm:3.0.0" checksum: 10c0/e008c8142bcc335b5e38cf0d63cfd39d6cf2d97480af9abdbe9a439221fd4d749763bab492a8ee708ce7a194bb00c9da6d0a115018672310850489137b3da046 @@ -9849,6 +10299,13 @@ __metadata: languageName: node linkType: hard +"object-deep-merge@npm:^2.0.0": + version: 2.0.0 + resolution: "object-deep-merge@npm:2.0.0" + checksum: 10c0/69e8741131ad49fa8720fb96007a3c82dca1119b5d874151d2ecbcc3b44ccd46e8553c7a30b0abcba752c099ba361bbba97f33a68c9ae54c57eed7be116ffc97 + languageName: node + linkType: hard + "object-inspect@npm:^1.13.3, object-inspect@npm:^1.13.4": version: 1.13.4 resolution: "object-inspect@npm:1.13.4" @@ -9924,6 +10381,13 @@ __metadata: languageName: node linkType: hard +"obug@npm:^2.1.1": + version: 2.1.1 + resolution: "obug@npm:2.1.1" + checksum: 10c0/59dccd7de72a047e08f8649e94c1015ec72f94eefb6ddb57fb4812c4b425a813bc7e7cd30c9aca20db3c59abc3c85cc7a62bb656a968741d770f4e8e02bc2e78 + languageName: node + linkType: hard + "on-exit-leak-free@npm:^2.1.0": version: 2.1.2 resolution: "on-exit-leak-free@npm:2.1.2" @@ -9931,7 +10395,7 @@ __metadata: languageName: node linkType: hard -"on-finished@npm:2.4.1": +"on-finished@npm:^2.4.1": version: 2.4.1 resolution: "on-finished@npm:2.4.1" dependencies: @@ -9958,7 +10422,19 @@ __metadata: languageName: node linkType: hard -"open@npm:^8.0.0, open@npm:^8.0.4": +"open@npm:^10.2.0": + version: 10.2.0 + resolution: "open@npm:10.2.0" + dependencies: + default-browser: "npm:^5.2.1" + define-lazy-prop: "npm:^3.0.0" + is-inside-container: "npm:^1.0.0" + wsl-utils: "npm:^0.1.0" + checksum: 10c0/5a36d0c1fd2f74ce553beb427ca8b8494b623fc22c6132d0c1688f246a375e24584ea0b44c67133d9ab774fa69be8e12fbe1ff12504b1142bd960fb09671948f + languageName: node + linkType: hard + +"open@npm:^8.0.0": version: 8.4.2 resolution: "open@npm:8.4.2" dependencies: @@ -10019,15 +10495,6 @@ __metadata: languageName: node linkType: hard -"p-limit@npm:^4.0.0": - version: 4.0.0 - resolution: "p-limit@npm:4.0.0" - dependencies: - yocto-queue: "npm:^1.0.0" - checksum: 10c0/a56af34a77f8df2ff61ddfb29431044557fcbcb7642d5a3233143ebba805fc7306ac1d448de724352861cb99de934bc9ab74f0d16fe6a5460bdbdf938de875ad - languageName: node - linkType: hard - "p-locate@npm:^5.0.0": version: 5.0.0 resolution: "p-locate@npm:5.0.0" @@ -10037,16 +10504,7 @@ __metadata: languageName: node linkType: hard -"p-locate@npm:^6.0.0": - version: 6.0.0 - resolution: "p-locate@npm:6.0.0" - dependencies: - p-limit: "npm:^4.0.0" - checksum: 10c0/d72fa2f41adce59c198270aa4d3c832536c87a1806e0f69dffb7c1a7ca998fb053915ca833d90f166a8c082d3859eabfed95f01698a3214c20df6bb8de046312 - languageName: node - linkType: hard - -"p-map@npm:^7.0.2, p-map@npm:^7.0.3": +"p-map@npm:^7.0.2": version: 7.0.3 resolution: "p-map@npm:7.0.3" checksum: 10c0/46091610da2b38ce47bcd1d8b4835a6fa4e832848a6682cf1652bc93915770f4617afc844c10a77d1b3e56d2472bb2d5622353fa3ead01a7f42b04fc8e744a5c @@ -10060,6 +10518,13 @@ __metadata: languageName: node linkType: hard +"page-lifecycle@npm:^0.1.2": + version: 0.1.2 + resolution: "page-lifecycle@npm:0.1.2" + checksum: 10c0/509dbbc2ad2000dffcf591f66ab13d80fb1dba9337d85c76269173f7a5c3959b5a876e3bfb1e4494f6b932c1dc02a0b5824ebd452ab1a7204d4abdf498cb27c5 + languageName: node + linkType: hard + "parent-module@npm:^1.0.0": version: 1.0.1 resolution: "parent-module@npm:1.0.1" @@ -10090,6 +10555,13 @@ __metadata: languageName: node linkType: hard +"parse-node-version@npm:^1.0.1": + version: 1.0.1 + resolution: "parse-node-version@npm:1.0.1" + checksum: 10c0/999cd3d7da1425c2e182dce82b226c6dc842562d3ed79ec47f5c719c32a7f6c1a5352495b894fc25df164be7f2ede4224758255da9902ddef81f2b77ba46bb2c + languageName: node + linkType: hard + "parse-statements@npm:1.0.11": version: 1.0.11 resolution: "parse-statements@npm:1.0.11" @@ -10097,16 +10569,16 @@ __metadata: languageName: node linkType: hard -"parse5@npm:^7.3.0": - version: 7.3.0 - resolution: "parse5@npm:7.3.0" +"parse5@npm:^8.0.0": + version: 8.0.0 + resolution: "parse5@npm:8.0.0" dependencies: entities: "npm:^6.0.0" - checksum: 10c0/7fd2e4e247e85241d6f2a464d0085eed599a26d7b0a5233790c49f53473232eb85350e8133344d9b3fd58b89339e7ad7270fe1f89d28abe50674ec97b87f80b5 + checksum: 10c0/8279892dcd77b2f2229707f60eb039e303adf0288812b2a8fd5acf506a4d432da833c6c5d07a6554bef722c2367a81ef4a1f7e9336564379a7dba3e798bf16b3 languageName: node linkType: hard -"parseurl@npm:~1.3.3": +"parseurl@npm:^1.3.3": version: 1.3.3 resolution: "parseurl@npm:1.3.3" checksum: 10c0/90dd4760d6f6174adb9f20cf0965ae12e23879b5f5464f38e92fce8073354341e4b3b76fa3d878351efe7d01e617121955284cfd002ab087fba1a0726ec0b4f5 @@ -10127,13 +10599,6 @@ __metadata: languageName: node linkType: hard -"path-exists@npm:^5.0.0": - version: 5.0.0 - resolution: "path-exists@npm:5.0.0" - checksum: 10c0/b170f3060b31604cde93eefdb7392b89d832dfbc1bed717c9718cbe0f230c1669b7e75f87e19901da2250b84d092989a0f9e44d2ef41deb09aa3ad28e691a40a - languageName: node - linkType: hard - "path-is-absolute@npm:^1.0.0": version: 1.0.1 resolution: "path-is-absolute@npm:1.0.1" @@ -10165,10 +10630,13 @@ __metadata: languageName: node linkType: hard -"path-to-regexp@npm:0.1.12": - version: 0.1.12 - resolution: "path-to-regexp@npm:0.1.12" - checksum: 10c0/1c6ff10ca169b773f3bba943bbc6a07182e332464704572962d277b900aeee81ac6aa5d060ff9e01149636c30b1f63af6e69dd7786ba6e0ddb39d4dee1f0645b +"path-scurry@npm:^2.0.0": + version: 2.0.1 + resolution: "path-scurry@npm:2.0.1" + dependencies: + lru-cache: "npm:^11.0.0" + minipass: "npm:^7.1.2" + checksum: 10c0/2a16ed0e81fbc43513e245aa5763354e25e787dab0d539581a6c3f0f967461a159ed6236b2559de23aa5b88e7dc32b469b6c47568833dd142a4b24b4f5cd2620 languageName: node linkType: hard @@ -10188,6 +10656,13 @@ __metadata: languageName: node linkType: hard +"path-to-regexp@npm:^8.0.0": + version: 8.3.0 + resolution: "path-to-regexp@npm:8.3.0" + checksum: 10c0/ee1544a73a3f294a97a4c663b0ce71bbf1621d732d80c9c9ed201b3e911a86cb628ebad691b9d40f40a3742fe22011e5a059d8eed2cf63ec2cb94f6fb4efe67c + languageName: node + linkType: hard + "path-type@npm:^4.0.0": version: 4.0.0 resolution: "path-type@npm:4.0.0" @@ -10304,14 +10779,14 @@ __metadata: languageName: node linkType: hard -"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.2.2, picomatch@npm:^2.3.1": +"picomatch@npm:^2.2.2, picomatch@npm:^2.3.1": version: 2.3.1 resolution: "picomatch@npm:2.3.1" checksum: 10c0/26c02b8d06f03206fc2ab8d16f19960f2ff9e81a658f831ecb656d8f17d9edc799e8364b1f4a7873e89d9702dff96204be0fa26fe4181f6843f040f819dac4be languageName: node linkType: hard -"pidtree@npm:0.6.0": +"pidtree@npm:^0.6.0": version: 0.6.0 resolution: "pidtree@npm:0.6.0" bin: @@ -10320,6 +10795,13 @@ __metadata: languageName: node linkType: hard +"pify@npm:^4.0.1": + version: 4.0.1 + resolution: "pify@npm:4.0.1" + checksum: 10c0/6f9d404b0d47a965437403c9b90eca8bb2536407f03de165940e62e72c8c8b75adda5516c6b9b23675a5877cc0bcac6bdfb0ef0e39414cd2476d5495da40e7cf + languageName: node + linkType: hard + "pino-abstract-transport@npm:^2.0.0": version: 2.0.0 resolution: "pino-abstract-transport@npm:2.0.0" @@ -10329,38 +10811,47 @@ __metadata: languageName: node linkType: hard -"pino-http@npm:^10.0.0": - version: 10.5.0 - resolution: "pino-http@npm:10.5.0" +"pino-abstract-transport@npm:^3.0.0": + version: 3.0.0 + resolution: "pino-abstract-transport@npm:3.0.0" + dependencies: + split2: "npm:^4.0.0" + checksum: 10c0/4486e1b9508110aaf963d07741ac98d660b974dd51d8ad42077d215118e27cda20c64da46c07c926898d52540aab7c6b9c37dc0f5355c203bb1d6a72b5bd8d6c + languageName: node + linkType: hard + +"pino-http@npm:^11.0.0": + version: 11.0.0 + resolution: "pino-http@npm:11.0.0" dependencies: get-caller-file: "npm:^2.0.5" - pino: "npm:^9.0.0" + pino: "npm:^10.0.0" pino-std-serializers: "npm:^7.0.0" process-warning: "npm:^5.0.0" - checksum: 10c0/17597d653a4088f7faed4d58500a5ef51d4d05247307696760006313c33c1d23177af98fb902e15b8e2cd92d81306c884673f841ba5b9bf0c064802f3c0bd775 + checksum: 10c0/75110c7a7f1b1c4eadfbff3b87599ef9d100c20c3ffd19541f0cb37cd11285a3f221bb90d33df0772ed94a3f7d5b2007bd7365c5f5525cff2178d4deef140ee5 languageName: node linkType: hard "pino-pretty@npm:^13.0.0": - version: 13.1.1 - resolution: "pino-pretty@npm:13.1.1" + version: 13.1.3 + resolution: "pino-pretty@npm:13.1.3" dependencies: colorette: "npm:^2.0.7" dateformat: "npm:^4.6.3" - fast-copy: "npm:^3.0.2" + fast-copy: "npm:^4.0.0" fast-safe-stringify: "npm:^2.1.1" help-me: "npm:^5.0.0" joycon: "npm:^3.1.1" minimist: "npm:^1.2.6" on-exit-leak-free: "npm:^2.1.0" - pino-abstract-transport: "npm:^2.0.0" + pino-abstract-transport: "npm:^3.0.0" pump: "npm:^3.0.0" secure-json-parse: "npm:^4.0.0" sonic-boom: "npm:^4.0.1" strip-json-comments: "npm:^5.0.2" bin: pino-pretty: bin.js - checksum: 10c0/845c07afd3d73cb96ad2049cfa7fca12b8280a51e30d6db8b490857690637556bb8e7f05b2fa640b3e4a7edd9b1369110042d670fda743ef98fe3be29876c8c7 + checksum: 10c0/36fa382521a893290c8f6a5b2ddc28dfb87fda1d161adb6b97d80bf7d24184970d0a7eab6f8ee45c39aff4b2ec3b2e533c756899798adc270010f34ba4411063 languageName: node linkType: hard @@ -10371,12 +10862,12 @@ __metadata: languageName: node linkType: hard -"pino@npm:^9.0.0": - version: 9.11.0 - resolution: "pino@npm:9.11.0" +"pino@npm:^10.0.0": + version: 10.1.0 + resolution: "pino@npm:10.1.0" dependencies: + "@pinojs/redact": "npm:^0.4.0" atomic-sleep: "npm:^1.0.0" - fast-redact: "npm:^3.1.1" on-exit-leak-free: "npm:^2.1.0" pino-abstract-transport: "npm:^2.0.0" pino-std-serializers: "npm:^7.0.0" @@ -10388,31 +10879,49 @@ __metadata: thread-stream: "npm:^3.0.0" bin: pino: bin.js - checksum: 10c0/ba908f95b61fa2c2d6c432e1f39a4394cc0dbf356c4f8837bd9c07538d749699b78204a5557e6050870f2988c25c3f0b6a88693d4bd185ebeef57d75a3b25e38 + checksum: 10c0/49c1dd80d5f99f02bde1acf2f60cef7686948a937f751f6cb368c2868c7e82e54aeabac63a34587e16019965cbf0eb6e609edf92c439a98a0a4fcb0add277eaf languageName: node linkType: hard -"playwright-core@npm:1.55.0": - version: 1.55.0 - resolution: "playwright-core@npm:1.55.0" +"pixelmatch@npm:7.1.0": + version: 7.1.0 + resolution: "pixelmatch@npm:7.1.0" + dependencies: + pngjs: "npm:^7.0.0" + bin: + pixelmatch: bin/pixelmatch + checksum: 10c0/ff069f92edaa841ac9b58b0ab74e1afa1f3b5e770eea0218c96bac1da4e752f5f6b79a0f9c4ba6b02afb955d39b8c78bcc3cc884f8122b67a1f2efbbccbe1a73 + languageName: node + linkType: hard + +"playwright-core@npm:1.57.0": + version: 1.57.0 + resolution: "playwright-core@npm:1.57.0" bin: playwright-core: cli.js - checksum: 10c0/c39d6aa30e7a4e73965942ca5e13405ae05c9cb49f755a35f04248c864c0b24cf662d9767f1797b3ec48d1cf4e54774dce4a19c16534bd5cfd2aa3da81c9dc3a + checksum: 10c0/798e35d83bf48419a8c73de20bb94d68be5dde68de23f95d80a0ebe401e3b83e29e3e84aea7894d67fa6c79d2d3d40cc5bcde3e166f657ce50987aaa2421b6a9 languageName: node linkType: hard -"playwright@npm:^1.54.1": - version: 1.55.0 - resolution: "playwright@npm:1.55.0" +"playwright@npm:^1.57.0": + version: 1.57.0 + resolution: "playwright@npm:1.57.0" dependencies: fsevents: "npm:2.3.2" - playwright-core: "npm:1.55.0" + playwright-core: "npm:1.57.0" dependenciesMeta: fsevents: optional: true bin: playwright: cli.js - checksum: 10c0/51605b7e57a5650e57972c5fdfc09d7a9934cca1cbee5beacca716fa801e25cb5bb7c1663de90c22b300fde884e5545a2b13a0505a93270b660687791c478304 + checksum: 10c0/ab03c99a67b835bdea9059f516ad3b6e42c21025f9adaa161a4ef6bc7ca716dcba476d287140bb240d06126eb23f889a8933b8f5f1f1a56b80659d92d1358899 + languageName: node + linkType: hard + +"pngjs@npm:^7.0.0": + version: 7.0.0 + resolution: "pngjs@npm:7.0.0" + checksum: 10c0/0d4c7a0fd476a9c33df7d0a2a73e1d56537628a668841f6995c2bca070cf30819f9254a64363266bc14ef2fee47659dd3b4f2b18eec7ab65143015139f497b38 languageName: node linkType: hard @@ -10618,6 +11127,24 @@ __metadata: languageName: node linkType: hard +"postcss-load-config@npm:^3.1.4": + version: 3.1.4 + resolution: "postcss-load-config@npm:3.1.4" + dependencies: + lilconfig: "npm:^2.0.5" + yaml: "npm:^1.10.2" + peerDependencies: + postcss: ">=8.0.9" + ts-node: ">=9.0.0" + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + checksum: 10c0/7d2cc6695c2fc063e4538316d651a687fdb55e48db453ff699de916a6ee55ab68eac2b120c28a6b8ca7aa746a588888351b810a215b5cd090eabea62c5762ede + languageName: node + linkType: hard + "postcss-logical@npm:^8.1.0": version: 8.1.0 resolution: "postcss-logical@npm:8.1.0" @@ -10636,6 +11163,39 @@ __metadata: languageName: node linkType: hard +"postcss-modules-extract-imports@npm:^3.0.0": + version: 3.1.0 + resolution: "postcss-modules-extract-imports@npm:3.1.0" + peerDependencies: + postcss: ^8.1.0 + checksum: 10c0/402084bcab376083c4b1b5111b48ec92974ef86066f366f0b2d5b2ac2b647d561066705ade4db89875a13cb175b33dd6af40d16d32b2ea5eaf8bac63bd2bf219 + languageName: node + linkType: hard + +"postcss-modules-local-by-default@npm:^4.0.4": + version: 4.2.0 + resolution: "postcss-modules-local-by-default@npm:4.2.0" + dependencies: + icss-utils: "npm:^5.0.0" + postcss-selector-parser: "npm:^7.0.0" + postcss-value-parser: "npm:^4.1.0" + peerDependencies: + postcss: ^8.1.0 + checksum: 10c0/b0b83feb2a4b61f5383979d37f23116c99bc146eba1741ca3cf1acca0e4d0dbf293ac1810a6ab4eccbe1ee76440dd0a9eb2db5b3bba4f99fc1b3ded16baa6358 + languageName: node + linkType: hard + +"postcss-modules-scope@npm:^3.1.1": + version: 3.2.1 + resolution: "postcss-modules-scope@npm:3.2.1" + dependencies: + postcss-selector-parser: "npm:^7.0.0" + peerDependencies: + postcss: ^8.1.0 + checksum: 10c0/bd2d81f79e3da0ef6365b8e2c78cc91469d05b58046b4601592cdeef6c4050ed8fe1478ae000a1608042fc7e692cb51fecbd2d9bce3f4eace4d32e883ffca10b + languageName: node + linkType: hard + "postcss-nesting@npm:^13.0.2": version: 13.0.2 resolution: "postcss-nesting@npm:13.0.2" @@ -10690,8 +11250,8 @@ __metadata: linkType: hard "postcss-preset-env@npm:^10.1.5": - version: 10.4.0 - resolution: "postcss-preset-env@npm:10.4.0" + version: 10.5.0 + resolution: "postcss-preset-env@npm:10.5.0" dependencies: "@csstools/postcss-alpha-function": "npm:^1.0.1" "@csstools/postcss-cascade-layers": "npm:^5.0.2" @@ -10720,21 +11280,23 @@ __metadata: "@csstools/postcss-nested-calc": "npm:^4.0.0" "@csstools/postcss-normalize-display-values": "npm:^4.0.0" "@csstools/postcss-oklab-function": "npm:^4.0.12" + "@csstools/postcss-position-area-property": "npm:^1.0.0" "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" "@csstools/postcss-random-function": "npm:^2.0.1" "@csstools/postcss-relative-color-syntax": "npm:^3.0.12" "@csstools/postcss-scope-pseudo-class": "npm:^4.0.1" "@csstools/postcss-sign-functions": "npm:^1.1.4" "@csstools/postcss-stepped-value-functions": "npm:^4.0.9" + "@csstools/postcss-system-ui-font-family": "npm:^1.0.0" "@csstools/postcss-text-decoration-shorthand": "npm:^4.0.3" "@csstools/postcss-trigonometric-functions": "npm:^4.0.9" "@csstools/postcss-unset-value": "npm:^4.0.0" - autoprefixer: "npm:^10.4.21" - browserslist: "npm:^4.26.0" + autoprefixer: "npm:^10.4.22" + browserslist: "npm:^4.28.0" css-blank-pseudo: "npm:^7.0.1" css-has-pseudo: "npm:^7.0.3" css-prefers-color-scheme: "npm:^10.0.0" - cssdb: "npm:^8.4.2" + cssdb: "npm:^8.5.2" postcss-attribute-case-insensitive: "npm:^7.0.1" postcss-clamp: "npm:^4.1.0" postcss-color-functional-notation: "npm:^7.0.12" @@ -10762,7 +11324,7 @@ __metadata: postcss-selector-not: "npm:^8.0.1" peerDependencies: postcss: ^8.4 - checksum: 10c0/3c081a66ebde19ae2f915f4eb103b85097085799b43103e5dd1699ed807bd54c80d633c7d4b525badaf21e9d0b217e6ca169ee306e2b720bb70b7414ad375387 + checksum: 10c0/4e9881478b465e8eb7493c1240cb2df8523944135728672e8feeb8bb3f6a48b00d67d007ee8fbdcee648ab9ebdfca10a7591f42e3c6b9076cbf7f355f8ad1574 languageName: node linkType: hard @@ -10832,14 +11394,14 @@ __metadata: languageName: node linkType: hard -"postcss-value-parser@npm:^4.2.0": +"postcss-value-parser@npm:^4.1.0, postcss-value-parser@npm:^4.2.0": version: 4.2.0 resolution: "postcss-value-parser@npm:4.2.0" checksum: 10c0/f4142a4f56565f77c1831168e04e3effd9ffcc5aebaf0f538eee4b2d465adfd4b85a44257bb48418202a63806a7da7fe9f56c330aebb3cac898e46b4cbf49161 languageName: node linkType: hard -"postcss@npm:^8.5.6": +"postcss@npm:^8.0.0, postcss@npm:^8.4.35, postcss@npm:^8.5.6": version: 8.5.6 resolution: "postcss@npm:8.5.6" dependencies: @@ -10888,11 +11450,11 @@ __metadata: linkType: hard "prettier@npm:^3.3.3": - version: 3.6.2 - resolution: "prettier@npm:3.6.2" + version: 3.7.4 + resolution: "prettier@npm:3.7.4" bin: prettier: bin/prettier.cjs - checksum: 10c0/488cb2f2b99ec13da1e50074912870217c11edaddedeadc649b1244c749d15ba94e846423d062e2c4c9ae683e2d65f754de28889ba06e697ac4f988d44f45812 + checksum: 10c0/9675d2cd08eacb1faf1d1a2dbfe24bfab6a912b059fc9defdb380a408893d88213e794a40a2700bd29b140eb3172e0b07c852853f6e22f16f3374659a1a13389 languageName: node linkType: hard @@ -10965,7 +11527,7 @@ __metadata: languageName: node linkType: hard -"prop-types@npm:^15.5.10, prop-types@npm:^15.5.4, prop-types@npm:^15.6.0, prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": +"prop-types@npm:^15.5.10, prop-types@npm:^15.6.0, prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": version: 15.8.1 resolution: "prop-types@npm:15.8.1" dependencies: @@ -10976,7 +11538,7 @@ __metadata: languageName: node linkType: hard -"proxy-addr@npm:~2.0.7": +"proxy-addr@npm:^2.0.7": version: 2.0.7 resolution: "proxy-addr@npm:2.0.7" dependencies: @@ -10993,6 +11555,13 @@ __metadata: languageName: node linkType: hard +"prr@npm:~1.0.1": + version: 1.0.1 + resolution: "prr@npm:1.0.1" + checksum: 10c0/5b9272c602e4f4472a215e58daff88f802923b84bc39c8860376bb1c0e42aaf18c25d69ad974bd06ec6db6f544b783edecd5502cd3d184748d99080d68e4be5f + languageName: node + linkType: hard + "pump@npm:^3.0.0": version: 3.0.0 resolution: "pump@npm:3.0.0" @@ -11017,12 +11586,21 @@ __metadata: languageName: node linkType: hard -"qs@npm:6.13.0": - version: 6.13.0 - resolution: "qs@npm:6.13.0" +"qified@npm:^0.5.2": + version: 0.5.2 + resolution: "qified@npm:0.5.2" + dependencies: + hookified: "npm:^1.13.0" + checksum: 10c0/4234ba1c0d3b14f31752a39f6788b2490cebad57abd1ce0cee0e04d2fe04a234463785d47559d364affe4d1578aad06fa2fd67b87044688708bf56d4a18ce44a + languageName: node + linkType: hard + +"qs@npm:^6.14.0": + version: 6.14.0 + resolution: "qs@npm:6.14.0" dependencies: - side-channel: "npm:^1.0.6" - checksum: 10c0/62372cdeec24dc83a9fb240b7533c0fdcf0c5f7e0b83343edd7310f0ab4c8205a5e7c56406531f2e47e1b4878a3821d652be4192c841de5b032ca83619d8f860 + side-channel: "npm:^1.1.0" + checksum: 10c0/8ea5d91bf34f440598ee389d4a7d95820e3b837d3fd9f433871f7924801becaa0cd3b3b4628d49a7784d06a8aea9bc4554d2b6d8d584e2d221dc06238a42909c languageName: node linkType: hard @@ -11049,22 +11627,22 @@ __metadata: languageName: node linkType: hard -"range-parser@npm:~1.2.1": +"range-parser@npm:^1.2.1": version: 1.2.1 resolution: "range-parser@npm:1.2.1" checksum: 10c0/96c032ac2475c8027b7a4e9fe22dc0dfe0f6d90b85e496e0f016fbdb99d6d066de0112e680805075bd989905e2123b3b3d002765149294dce0c1f7f01fcc2ea0 languageName: node linkType: hard -"raw-body@npm:2.5.2": - version: 2.5.2 - resolution: "raw-body@npm:2.5.2" +"raw-body@npm:^3.0.1": + version: 3.0.2 + resolution: "raw-body@npm:3.0.2" dependencies: - bytes: "npm:3.1.2" - http-errors: "npm:2.0.0" - iconv-lite: "npm:0.4.24" - unpipe: "npm:1.0.0" - checksum: 10c0/b201c4b66049369a60e766318caff5cb3cc5a900efd89bdac431463822d976ad0670912c931fdbdcf5543207daf6f6833bca57aa116e1661d2ea91e12ca692c4 + bytes: "npm:~3.1.2" + http-errors: "npm:~2.0.1" + iconv-lite: "npm:~0.7.0" + unpipe: "npm:~1.0.0" + checksum: 10c0/d266678d08e1e7abea62c0ce5864344e980fa81c64f6b481e9842c5beaed2cdcf975f658a3ccd67ad35fc919c1f6664ccc106067801850286a6cbe101de89f29 languageName: node linkType: hard @@ -11118,19 +11696,6 @@ __metadata: languageName: node linkType: hard -"react-event-listener@npm:^0.6.0": - version: 0.6.6 - resolution: "react-event-listener@npm:0.6.6" - dependencies: - "@babel/runtime": "npm:^7.2.0" - prop-types: "npm:^15.6.0" - warning: "npm:^4.0.1" - peerDependencies: - react: ^16.3.0 - checksum: 10c0/07ca093d74b7963cb6048953517f881a0fdee9b485d31dabd49814cda51543eee20e714dd423e25946984b0ac26dbcb80aaf1211b0170e9b0cfa92fa85b0984e - languageName: node - linkType: hard - "react-fast-compare@npm:^3.1.1": version: 3.2.2 resolution: "react-fast-compare@npm:3.2.2" @@ -11175,16 +11740,16 @@ __metadata: linkType: hard "react-intl@npm:^7.1.10": - version: 7.1.11 - resolution: "react-intl@npm:7.1.11" + version: 7.1.14 + resolution: "react-intl@npm:7.1.14" dependencies: - "@formatjs/ecma402-abstract": "npm:2.3.4" - "@formatjs/icu-messageformat-parser": "npm:2.11.2" - "@formatjs/intl": "npm:3.1.6" + "@formatjs/ecma402-abstract": "npm:2.3.6" + "@formatjs/icu-messageformat-parser": "npm:2.11.4" + "@formatjs/intl": "npm:3.1.8" "@types/hoist-non-react-statics": "npm:^3.3.1" "@types/react": "npm:16 || 17 || 18 || 19" hoist-non-react-statics: "npm:^3.3.2" - intl-messageformat: "npm:10.7.16" + intl-messageformat: "npm:10.7.18" tslib: "npm:^2.8.0" peerDependencies: react: 16 || 17 || 18 || 19 @@ -11192,7 +11757,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10c0/f20770fb7bcce7a67acec70b9183f5320b7f3f9bbcb263ca8f4787817297674d1be158687f94d1e2803a9c8696d4f93dd86a28898aba8bc5197e858313e3dd06 + checksum: 10c0/b4361427ea05b4c9e7d87635a323854ca871710e01cd2a46b5da70b34b78a50661c04b2065258f3f49be134ca414c429c804bc34edc277784a9ffa0c04a30b04 languageName: node linkType: hard @@ -11277,10 +11842,10 @@ __metadata: languageName: node linkType: hard -"react-refresh@npm:^0.17.0": - version: 0.17.0 - resolution: "react-refresh@npm:0.17.0" - checksum: 10c0/002cba940384c9930008c0bce26cac97a9d5682bc623112c2268ba0c155127d9c178a9a5cc2212d560088d60dfd503edd808669a25f9b377f316a32361d0b23c +"react-refresh@npm:^0.18.0": + version: 0.18.0 + resolution: "react-refresh@npm:0.18.0" + checksum: 10c0/34a262f7fd803433a534f50deb27a148112a81adcae440c7d1cbae7ef14d21ea8f2b3d783e858cb7698968183b77755a38b4d4b5b1d79b4f4689c2f6d358fff2 languageName: node linkType: hard @@ -11301,21 +11866,6 @@ __metadata: languageName: node linkType: hard -"react-router-scroll-4@npm:^1.0.0-beta.1": - version: 1.0.0-beta.2 - resolution: "react-router-scroll-4@npm:1.0.0-beta.2" - dependencies: - scroll-behavior: "npm:^0.9.1" - warning: "npm:^3.0.0" - peerDependencies: - prop-types: ^15.6.0 - react: ^15.0.0 || ^16.0.0 - react-dom: ^15.0.0 || ^16.0.0 - react-router-dom: ^4.0 - checksum: 10c0/ad195b7359fd3146530cf299ec437f0a619c577b2cacfb2c76a156d3cd9d5d3e97af56e17c300c37ca8c485041e93124fe63f0c86db6aea468caf838281e62cb - languageName: node - linkType: hard - "react-router@npm:5.3.4, react-router@npm:^5.3.4": version: 5.3.4 resolution: "react-router@npm:5.3.4" @@ -11388,49 +11938,6 @@ __metadata: languageName: node linkType: hard -"react-swipeable-views-core@npm:^0.14.1": - version: 0.14.1 - resolution: "react-swipeable-views-core@npm:0.14.1" - dependencies: - "@babel/runtime": "npm:7.0.0" - warning: "npm:^4.0.1" - peerDependencies: - react: ^15.3.0 || ^16.0.0 || ^17.0.0 - checksum: 10c0/4da08493dad34f8498b66c596c1f40cd9a62e4968e71d00c79a26bf45e25ba3a3323e690641121cc8e7fd9c695e7da37fe781fa3f69e7d2b60fff3bfe4621426 - languageName: node - linkType: hard - -"react-swipeable-views-utils@npm:^0.14.1": - version: 0.14.1 - resolution: "react-swipeable-views-utils@npm:0.14.1" - dependencies: - "@babel/runtime": "npm:7.0.0" - keycode: "npm:^2.1.7" - prop-types: "npm:^15.6.0" - react-event-listener: "npm:^0.6.0" - react-swipeable-views-core: "npm:^0.14.1" - shallow-equal: "npm:^1.2.1" - peerDependencies: - react: ^15.3.0 || ^16.0.0 || ^17.0.0 - checksum: 10c0/f9a9930e7df9dab9cd0f5344c7cf0423cc8aff4728436eb7dc13588186fbd1a557c8250b6329ec09242f3f6d2700d33e1155ab191ce8de48aa860fad6fb1814b - languageName: node - linkType: hard - -"react-swipeable-views@npm:^0.14.0": - version: 0.14.1 - resolution: "react-swipeable-views@npm:0.14.1" - dependencies: - "@babel/runtime": "npm:7.0.0" - prop-types: "npm:^15.5.4" - react-swipeable-views-core: "npm:^0.14.1" - react-swipeable-views-utils: "npm:^0.14.1" - warning: "npm:^4.0.1" - peerDependencies: - react: ^15.3.0 || ^16.0.0 || ^17.0.0 - checksum: 10c0/7ee6d19cc33172e0846835eafd4b24ece0f26aed5cc9bbe80d3ec7c8d22f327443df8b2d50ddb4b837e1d6fbb40744abc41f62cdcab8e941e0663a952d627a15 - languageName: node - linkType: hard - "react-test-renderer@npm:^18.2.0": version: 18.3.1 resolution: "react-test-renderer@npm:18.3.1" @@ -11508,15 +12015,6 @@ __metadata: languageName: node linkType: hard -"readdirp@npm:~3.6.0": - version: 3.6.0 - resolution: "readdirp@npm:3.6.0" - dependencies: - picomatch: "npm:^2.2.1" - checksum: 10c0/6fa848cf63d1b82ab4e985f4cf72bd55b7dcfd8e0a376905804e48c3634b7e749170940ba77b32804d5fe93b3cc521aa95a8d7e7d725f830da6d93f3669ce66b - languageName: node - linkType: hard - "real-require@npm:^0.2.0": version: 0.2.0 resolution: "real-require@npm:0.2.0" @@ -11629,13 +12127,6 @@ __metadata: languageName: node linkType: hard -"regenerator-runtime@npm:^0.12.0": - version: 0.12.1 - resolution: "regenerator-runtime@npm:0.12.1" - checksum: 10c0/dbefbb38f5d6d55261aec1182d7c97ac93f2eec6ef9c756d9656eb5152f5cb3f8d873df020ddb4a3a8126c2ac1a3c2e534413cffe18d8f89b1c2a480a0a38601 - languageName: node - linkType: hard - "regenerator-runtime@npm:^0.13.3": version: 0.13.11 resolution: "regenerator-runtime@npm:0.13.11" @@ -11724,6 +12215,20 @@ __metadata: languageName: node linkType: hard +"reserved-identifiers@npm:^1.0.0": + version: 1.2.0 + resolution: "reserved-identifiers@npm:1.2.0" + checksum: 10c0/b82651b12e6c608e80463c3753d275bc20fd89294d0415f04e670aeec3611ae3582ddc19e8fedd497e7d0bcbfaddab6a12823ec86e855b1e6a245e0a734eb43d + languageName: node + linkType: hard + +"reserved-words@npm:^0.1.2": + version: 0.1.2 + resolution: "reserved-words@npm:0.1.2" + checksum: 10c0/88360388d88f4b36c1f5d47f8d596936dbf950bddd642c04ce940f1dab1fa58ef6fec23f5fab81a1bfe5cd0f223b2b635311496fcf0ef3db93ad4dfb6d7be186 + languageName: node + linkType: hard + "resolve-from@npm:^4.0.0": version: 4.0.0 resolution: "resolve-from@npm:4.0.0" @@ -11852,8 +12357,8 @@ __metadata: linkType: hard "rollup-plugin-visualizer@npm:^6.0.3": - version: 6.0.3 - resolution: "rollup-plugin-visualizer@npm:6.0.3" + version: 6.0.5 + resolution: "rollup-plugin-visualizer@npm:6.0.5" dependencies: open: "npm:^8.0.0" picomatch: "npm:^4.0.2" @@ -11869,11 +12374,11 @@ __metadata: optional: true bin: rollup-plugin-visualizer: dist/bin/cli.js - checksum: 10c0/595d68936a6338744e8facd165fceedf7f2ebedc44863e640e725198001ed62948cc4a5d8403aa74e679de92957e4def3b1dffc4a9f8de71e4245929566553a3 + checksum: 10c0/3824626e97d5033fbb3aa1bbe93c8c17a8569bc47e33c941bde6b90404f2cae70b26fec1b623bd393c3e076338014196c91726ed2c96218edc67e1f21676f7ef languageName: node linkType: hard -"rollup@npm:^2.43.1": +"rollup@npm:^2.79.2": version: 2.79.2 resolution: "rollup@npm:2.79.2" dependencies: @@ -11888,29 +12393,29 @@ __metadata: linkType: hard "rollup@npm:^4.43.0": - version: 4.46.2 - resolution: "rollup@npm:4.46.2" - dependencies: - "@rollup/rollup-android-arm-eabi": "npm:4.46.2" - "@rollup/rollup-android-arm64": "npm:4.46.2" - "@rollup/rollup-darwin-arm64": "npm:4.46.2" - "@rollup/rollup-darwin-x64": "npm:4.46.2" - "@rollup/rollup-freebsd-arm64": "npm:4.46.2" - "@rollup/rollup-freebsd-x64": "npm:4.46.2" - "@rollup/rollup-linux-arm-gnueabihf": "npm:4.46.2" - "@rollup/rollup-linux-arm-musleabihf": "npm:4.46.2" - "@rollup/rollup-linux-arm64-gnu": "npm:4.46.2" - "@rollup/rollup-linux-arm64-musl": "npm:4.46.2" - "@rollup/rollup-linux-loongarch64-gnu": "npm:4.46.2" - "@rollup/rollup-linux-ppc64-gnu": "npm:4.46.2" - "@rollup/rollup-linux-riscv64-gnu": "npm:4.46.2" - "@rollup/rollup-linux-riscv64-musl": "npm:4.46.2" - "@rollup/rollup-linux-s390x-gnu": "npm:4.46.2" - "@rollup/rollup-linux-x64-gnu": "npm:4.46.2" - "@rollup/rollup-linux-x64-musl": "npm:4.46.2" - "@rollup/rollup-win32-arm64-msvc": "npm:4.46.2" - "@rollup/rollup-win32-ia32-msvc": "npm:4.46.2" - "@rollup/rollup-win32-x64-msvc": "npm:4.46.2" + version: 4.46.4 + resolution: "rollup@npm:4.46.4" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.46.4" + "@rollup/rollup-android-arm64": "npm:4.46.4" + "@rollup/rollup-darwin-arm64": "npm:4.46.4" + "@rollup/rollup-darwin-x64": "npm:4.46.4" + "@rollup/rollup-freebsd-arm64": "npm:4.46.4" + "@rollup/rollup-freebsd-x64": "npm:4.46.4" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.46.4" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.46.4" + "@rollup/rollup-linux-arm64-gnu": "npm:4.46.4" + "@rollup/rollup-linux-arm64-musl": "npm:4.46.4" + "@rollup/rollup-linux-loongarch64-gnu": "npm:4.46.4" + "@rollup/rollup-linux-ppc64-gnu": "npm:4.46.4" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.46.4" + "@rollup/rollup-linux-riscv64-musl": "npm:4.46.4" + "@rollup/rollup-linux-s390x-gnu": "npm:4.46.4" + "@rollup/rollup-linux-x64-gnu": "npm:4.46.4" + "@rollup/rollup-linux-x64-musl": "npm:4.46.4" + "@rollup/rollup-win32-arm64-msvc": "npm:4.46.4" + "@rollup/rollup-win32-ia32-msvc": "npm:4.46.4" + "@rollup/rollup-win32-x64-msvc": "npm:4.46.4" "@types/estree": "npm:1.0.8" fsevents: "npm:~2.3.2" dependenciesMeta: @@ -11958,14 +12463,27 @@ __metadata: optional: true bin: rollup: dist/bin/rollup - checksum: 10c0/f428497fe119fe7c4e34f1020d45ba13e99b94c9aa36958d88823d932b155c9df3d84f53166f3ee913ff68ea6c7599a9ab34861d88562ad9d8420f64ca5dad4c + checksum: 10c0/17871534544bd19ec9b5bc1d82a8509addbdb7ee0dd865f20352a8b5695e7b9288af842cd50187bed9fece61b0f1d7b7ff43cf070265d3a2e7d8348497e3ba1e languageName: node linkType: hard -"rrweb-cssom@npm:^0.8.0": - version: 0.8.0 - resolution: "rrweb-cssom@npm:0.8.0" - checksum: 10c0/56f2bfd56733adb92c0b56e274c43f864b8dd48784d6fe946ef5ff8d438234015e59ad837fc2ad54714b6421384141c1add4eb569e72054e350d1f8a50b8ac7b +"router@npm:^2.2.0": + version: 2.2.0 + resolution: "router@npm:2.2.0" + dependencies: + debug: "npm:^4.4.0" + depd: "npm:^2.0.0" + is-promise: "npm:^4.0.0" + parseurl: "npm:^1.3.3" + path-to-regexp: "npm:^8.0.0" + checksum: 10c0/3279de7450c8eae2f6e095e9edacbdeec0abb5cb7249c6e719faa0db2dba43574b4fff5892d9220631c9abaff52dd3cad648cfea2aaace845e1a071915ac8867 + languageName: node + linkType: hard + +"run-applescript@npm:^7.0.0": + version: 7.1.0 + resolution: "run-applescript@npm:7.1.0" + checksum: 10c0/ab826c57c20f244b2ee807704b1ef4ba7f566aa766481ae5922aac785e2570809e297c69afcccc3593095b538a8a77d26f2b2e9a1d9dffee24e0e039502d1a03 languageName: node linkType: hard @@ -12026,16 +12544,16 @@ __metadata: languageName: node linkType: hard -"safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0": +"safer-buffer@npm:>= 2.1.2 < 3.0.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" checksum: 10c0/7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4 languageName: node linkType: hard -"sass@npm:^1.62.1": - version: 1.93.0 - resolution: "sass@npm:1.93.0" +"sass@npm:^1.62.1, sass@npm:^1.70.0": + version: 1.97.0 + resolution: "sass@npm:1.97.0" dependencies: "@parcel/watcher": "npm:^2.4.1" chokidar: "npm:^4.0.0" @@ -12046,7 +12564,21 @@ __metadata: optional: true bin: sass: sass.js - checksum: 10c0/51dcb4e65a69f97b4c200ee154ca45f81b748a45f8ef0ec3236b774bb143590a9304038e9ab09f809f734d4edb3add96a0a690b2e8451ff66b9f57c469b2685e + checksum: 10c0/4c0e2596131054089beeeb6e98f7318beb909c4775187690656cbb85a45153d04eecd970e6a4431fe47dc94f9749cf8d58c9c5e59055d2cae39f4887a4bb6104 + languageName: node + linkType: hard + +"sax@npm:^1.2.4": + version: 1.4.1 + resolution: "sax@npm:1.4.1" + checksum: 10c0/6bf86318a254c5d898ede6bd3ded15daf68ae08a5495a2739564eb265cd13bcc64a07ab466fb204f67ce472bb534eb8612dac587435515169593f4fffa11de7c + languageName: node + linkType: hard + +"sax@npm:~1.3.0": + version: 1.3.0 + resolution: "sax@npm:1.3.0" + checksum: 10c0/599dbe0ba9d8bd55e92d920239b21d101823a6cedff71e542589303fa0fa8f3ece6cf608baca0c51be846a2e88365fac94a9101a9c341d94b98e30c4deea5bea languageName: node linkType: hard @@ -12075,13 +12607,14 @@ __metadata: languageName: node linkType: hard -"scroll-behavior@npm:^0.9.1": - version: 0.9.12 - resolution: "scroll-behavior@npm:0.9.12" +"scroll-behavior@npm:^0.11.0": + version: 0.11.0 + resolution: "scroll-behavior@npm:0.11.0" dependencies: - dom-helpers: "npm:^3.4.0" + dom-helpers: "npm:^5.1.4" invariant: "npm:^2.2.4" - checksum: 10c0/4f438c48b93a1dcc2ab51a18670fac6f5ce41885291d8aa13251b4a187be9d0c6dd518ee974eb52ac9bbe227b9811c2615ecca73192a1a190b78dfdadb9c2cf2 + page-lifecycle: "npm:^0.1.2" + checksum: 10c0/c54010c9fdd9fc360fd7887ecf64f16972f9557ac679723709612cd54fc4778c7433ab46a9637933179ef31471f78e2591fb35351dc0e15537fecf1c8c89d32c languageName: node linkType: hard @@ -12092,6 +12625,22 @@ __metadata: languageName: node linkType: hard +"selector-set@npm:^1.1.5": + version: 1.1.5 + resolution: "selector-set@npm:1.1.5" + checksum: 10c0/4835907846eb8496c2cc4e5ce48355cce1ef3b55e82789542739dcdc71ebfb756e133d1d7f7ec9f0cf4b1c11fc0375a1d3b99a482d9c973493ca85a6d4b012ab + languageName: node + linkType: hard + +"semver@npm:^5.6.0": + version: 5.7.2 + resolution: "semver@npm:5.7.2" + bin: + semver: bin/semver + checksum: 10c0/e4cf10f86f168db772ae95d86ba65b3fd6c5967c94d97c708ccb463b778c2ee53b914cd7167620950fc07faf5a564e6efe903836639e512a1aa15fbc9667fa25 + languageName: node + linkType: hard + "semver@npm:^6.3.1": version: 6.3.1 resolution: "semver@npm:6.3.1" @@ -12101,33 +12650,31 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.6.0, semver@npm:^7.6.2, semver@npm:^7.7.1, semver@npm:^7.7.2": - version: 7.7.2 - resolution: "semver@npm:7.7.2" +"semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.6.0, semver@npm:^7.6.2, semver@npm:^7.7.1, semver@npm:^7.7.3": + version: 7.7.3 + resolution: "semver@npm:7.7.3" bin: semver: bin/semver.js - checksum: 10c0/aca305edfbf2383c22571cb7714f48cadc7ac95371b4b52362fb8eeffdfbc0de0669368b82b2b15978f8848f01d7114da65697e56cd8c37b0dab8c58e543f9ea + checksum: 10c0/4afe5c986567db82f44c8c6faef8fe9df2a9b1d98098fc1721f57c696c4c21cebd572f297fc21002f81889492345b8470473bc6f4aff5fb032a6ea59ea2bc45e languageName: node linkType: hard -"send@npm:0.19.0": - version: 0.19.0 - resolution: "send@npm:0.19.0" +"send@npm:^1.1.0, send@npm:^1.2.0": + version: 1.2.0 + resolution: "send@npm:1.2.0" dependencies: - debug: "npm:2.6.9" - depd: "npm:2.0.0" - destroy: "npm:1.2.0" - encodeurl: "npm:~1.0.2" - escape-html: "npm:~1.0.3" - etag: "npm:~1.8.1" - fresh: "npm:0.5.2" - http-errors: "npm:2.0.0" - mime: "npm:1.6.0" - ms: "npm:2.1.3" - on-finished: "npm:2.4.1" - range-parser: "npm:~1.2.1" - statuses: "npm:2.0.1" - checksum: 10c0/ea3f8a67a8f0be3d6bf9080f0baed6d2c51d11d4f7b4470de96a5029c598a7011c497511ccc28968b70ef05508675cebff27da9151dd2ceadd60be4e6cf845e3 + debug: "npm:^4.3.5" + encodeurl: "npm:^2.0.0" + escape-html: "npm:^1.0.3" + etag: "npm:^1.8.1" + fresh: "npm:^2.0.0" + http-errors: "npm:^2.0.0" + mime-types: "npm:^3.0.1" + ms: "npm:^2.1.3" + on-finished: "npm:^2.4.1" + range-parser: "npm:^1.2.1" + statuses: "npm:^2.0.1" + checksum: 10c0/531bcfb5616948d3468d95a1fd0adaeb0c20818ba4a500f439b800ca2117971489e02074ce32796fd64a6772ea3e7235fe0583d8241dbd37a053dc3378eff9a5 languageName: node linkType: hard @@ -12140,15 +12687,15 @@ __metadata: languageName: node linkType: hard -"serve-static@npm:1.16.2": - version: 1.16.2 - resolution: "serve-static@npm:1.16.2" +"serve-static@npm:^2.2.0": + version: 2.2.0 + resolution: "serve-static@npm:2.2.0" dependencies: - encodeurl: "npm:~2.0.0" - escape-html: "npm:~1.0.3" - parseurl: "npm:~1.3.3" - send: "npm:0.19.0" - checksum: 10c0/528fff6f5e12d0c5a391229ad893910709bc51b5705962b09404a1d813857578149b8815f35d3ee5752f44cd378d0f31669d4b1d7e2d11f41e08283d5134bd1f + encodeurl: "npm:^2.0.0" + escape-html: "npm:^1.0.3" + parseurl: "npm:^1.3.3" + send: "npm:^1.2.0" + checksum: 10c0/30e2ed1dbff1984836cfd0c65abf5d3f3f83bcd696c99d2d3c97edbd4e2a3ff4d3f87108a7d713640d290a7b6fe6c15ddcbc61165ab2eaad48ea8d3b52c7f913 languageName: node linkType: hard @@ -12189,20 +12736,13 @@ __metadata: languageName: node linkType: hard -"setprototypeof@npm:1.2.0": +"setprototypeof@npm:~1.2.0": version: 1.2.0 resolution: "setprototypeof@npm:1.2.0" checksum: 10c0/68733173026766fa0d9ecaeb07f0483f4c2dc70ca376b3b7c40b7cda909f94b0918f6c5ad5ce27a9160bdfb475efaa9d5e705a11d8eaae18f9835d20976028bc languageName: node linkType: hard -"shallow-equal@npm:^1.2.1": - version: 1.2.1 - resolution: "shallow-equal@npm:1.2.1" - checksum: 10c0/51e03abadd97c9ebe590547d92db9148446962a3f23a3a0fb1ba2fccab80af881eef0ff1f8ccefd3f066c0bc5a4c8ca53706194813b95c8835fa66448a843a26 - languageName: node - linkType: hard - "shebang-command@npm:^2.0.0": version: 2.0.0 resolution: "shebang-command@npm:2.0.0" @@ -12254,7 +12794,7 @@ __metadata: languageName: node linkType: hard -"side-channel@npm:^1.0.6, side-channel@npm:^1.1.0": +"side-channel@npm:^1.1.0": version: 1.1.0 resolution: "side-channel@npm:1.1.0" dependencies: @@ -12281,14 +12821,14 @@ __metadata: languageName: node linkType: hard -"sirv@npm:^3.0.1": - version: 3.0.1 - resolution: "sirv@npm:3.0.1" +"sirv@npm:^3.0.2": + version: 3.0.2 + resolution: "sirv@npm:3.0.2" dependencies: "@polka/url": "npm:^1.0.0-next.24" mrmime: "npm:^2.0.0" totalist: "npm:^3.0.0" - checksum: 10c0/7cf64b28daa69b15f77b38b0efdd02c007b72bb3ec5f107b208ebf59f01b174ef63a1db3aca16d2df925501831f4c209be6ece3302b98765919ef5088b45bf80 + checksum: 10c0/5930e4397afdb14fbae13751c3be983af4bda5c9aadec832607dc2af15a7162f7d518c71b30e83ae3644b9a24cea041543cc969e5fe2b80af6ce8ea3174b2d04 languageName: node linkType: hard @@ -12381,7 +12921,7 @@ __metadata: languageName: node linkType: hard -"source-map-js@npm:>=0.6.2 <2.0.0, source-map-js@npm:^1.0.1, source-map-js@npm:^1.2.0, source-map-js@npm:^1.2.1": +"source-map-js@npm:>=0.6.2 <2.0.0, source-map-js@npm:^1.0.1, source-map-js@npm:^1.0.2, source-map-js@npm:^1.2.1": version: 1.2.1 resolution: "source-map-js@npm:1.2.1" checksum: 10c0/7bda1fc4c197e3c6ff17de1b8b2c20e60af81b63a52cb32ec5a5d67a20a7d42651e2cb34ebe93833c5a2a084377e17455854fee3e21e7925c64a51b6a52b0faf @@ -12412,17 +12952,17 @@ __metadata: languageName: node linkType: hard -"source-map@npm:^0.6.0, source-map@npm:~0.6.1": +"source-map@npm:^0.6.0, source-map@npm:~0.6.0, source-map@npm:~0.6.1": version: 0.6.1 resolution: "source-map@npm:0.6.1" checksum: 10c0/ab55398007c5e5532957cb0beee2368529618ac0ab372d789806f5718123cc4367d57de3904b4e6a4170eb5a0b0f41373066d02ca0735a0c4d75c7d328d3e011 languageName: node linkType: hard -"source-map@npm:^0.7.4": - version: 0.7.4 - resolution: "source-map@npm:0.7.4" - checksum: 10c0/dc0cf3768fe23c345ea8760487f8c97ef6fca8a73c83cd7c9bf2fde8bc2c34adb9c0824d6feb14bc4f9e37fb522e18af621543f1289038a66ac7586da29aa7dc +"source-map@npm:^0.7.3, source-map@npm:^0.7.4": + version: 0.7.6 + resolution: "source-map@npm:0.7.6" + checksum: 10c0/59f6f05538539b274ba771d2e9e32f6c65451982510564438e048bc1352f019c6efcdc6dd07909b1968144941c14015c2c7d4369fb7c4d7d53ae769716dcc16c languageName: node linkType: hard @@ -12540,24 +13080,17 @@ __metadata: languageName: node linkType: hard -"statuses@npm:2.0.1": - version: 2.0.1 - resolution: "statuses@npm:2.0.1" - checksum: 10c0/34378b207a1620a24804ce8b5d230fea0c279f00b18a7209646d5d47e419d1cc23e7cbf33a25a1e51ac38973dc2ac2e1e9c647a8e481ef365f77668d72becfd0 - languageName: node - linkType: hard - -"statuses@npm:^2.0.1": +"statuses@npm:^2.0.1, statuses@npm:^2.0.2, statuses@npm:~2.0.2": version: 2.0.2 resolution: "statuses@npm:2.0.2" checksum: 10c0/a9947d98ad60d01f6b26727570f3bcceb6c8fa789da64fe6889908fe2e294d57503b14bf2b5af7605c2d36647259e856635cd4c49eab41667658ec9d0080ec3f languageName: node linkType: hard -"std-env@npm:^3.9.0": - version: 3.9.0 - resolution: "std-env@npm:3.9.0" - checksum: 10c0/4a6f9218aef3f41046c3c7ecf1f98df00b30a07f4f35c6d47b28329bc2531eef820828951c7d7b39a1c5eb19ad8a46e3ddfc7deb28f0a2f3ceebee11bab7ba50 +"std-env@npm:^3.10.0": + version: 3.10.0 + resolution: "std-env@npm:3.10.0" + checksum: 10c0/1814927a45004d36dde6707eaf17552a546769bc79a6421be2c16ce77d238158dfe5de30910b78ec30d95135cc1c59ea73ee22d2ca170f8b9753f84da34c427f languageName: node linkType: hard @@ -12571,21 +13104,21 @@ __metadata: languageName: node linkType: hard -"storybook@npm:^9.1.1": - version: 9.1.7 - resolution: "storybook@npm:9.1.7" +"storybook@npm:^10.0.5": + version: 10.1.10 + resolution: "storybook@npm:10.1.10" dependencies: "@storybook/global": "npm:^5.0.0" + "@storybook/icons": "npm:^2.0.0" "@testing-library/jest-dom": "npm:^6.6.3" "@testing-library/user-event": "npm:^14.6.1" "@vitest/expect": "npm:3.2.4" - "@vitest/mocker": "npm:3.2.4" "@vitest/spy": "npm:3.2.4" - better-opn: "npm:^3.0.2" - esbuild: "npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0" - esbuild-register: "npm:^3.5.0" + esbuild: "npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0" + open: "npm:^10.2.0" recast: "npm:^0.23.5" semver: "npm:^7.6.2" + use-sync-external-store: "npm:^1.5.0" ws: "npm:^8.18.0" peerDependencies: prettier: ^2 || ^3 @@ -12593,8 +13126,8 @@ __metadata: prettier: optional: true bin: - storybook: ./bin/index.cjs - checksum: 10c0/daf3fb47ada1368604db7a1bc53775a5ceb485cd103351670e74d3378cb7bc1e396f72530a4821f3919207fb55ce7904ce894f55566844f9a14279553a8bd534 + storybook: ./dist/bin/dispatcher.js + checksum: 10c0/beff5472ee86a995cbde2789b2aabd941f823e31ca6957bb4434cb8ee3d3703cf1248e44f4b4d402416a52bfee94677e74f233cc906487901e831e8ab610defa languageName: node linkType: hard @@ -12605,7 +13138,7 @@ __metadata: languageName: node linkType: hard -"string-argv@npm:0.3.2": +"string-argv@npm:^0.3.2": version: 0.3.2 resolution: "string-argv@npm:0.3.2" checksum: 10c0/75c02a83759ad1722e040b86823909d9a2fc75d15dd71ec4b537c3560746e33b5f5a07f7332d1e3f88319909f82190843aa2f0a0d8c8d591ec08e93d5b8dec82 @@ -12819,15 +13352,6 @@ __metadata: languageName: node linkType: hard -"strip-literal@npm:^3.0.0": - version: 3.0.0 - resolution: "strip-literal@npm:3.0.0" - dependencies: - js-tokens: "npm:^9.0.1" - checksum: 10c0/d81657f84aba42d4bbaf2a677f7e7f34c1f3de5a6726db8bc1797f9c0b303ba54d4660383a74bde43df401cf37cce1dff2c842c55b077a4ceee11f9e31fba828 - languageName: node - linkType: hard - "stylelint-config-prettier-scss@npm:^1.0.0": version: 1.0.0 resolution: "stylelint-config-prettier-scss@npm:1.0.0" @@ -12840,62 +13364,62 @@ __metadata: languageName: node linkType: hard -"stylelint-config-recommended-scss@npm:^15.0.1": - version: 15.0.1 - resolution: "stylelint-config-recommended-scss@npm:15.0.1" +"stylelint-config-recommended-scss@npm:^16.0.1": + version: 16.0.2 + resolution: "stylelint-config-recommended-scss@npm:16.0.2" dependencies: postcss-scss: "npm:^4.0.9" - stylelint-config-recommended: "npm:^16.0.0" - stylelint-scss: "npm:^6.12.0" + stylelint-config-recommended: "npm:^17.0.0" + stylelint-scss: "npm:^6.12.1" peerDependencies: postcss: ^8.3.3 - stylelint: ^16.16.0 + stylelint: ^16.24.0 peerDependenciesMeta: postcss: optional: true - checksum: 10c0/8c5854e143145241dbff3d921298eb59e837aa695c0e6d7f08acf75de81f3f8307d39a931781bf8ac7cbe6bf9079a402fee89566206e9cfb1d728ef6b6486890 + checksum: 10c0/d4e30a881e248d8b039347bf967526f6afe6d6a07f18e2747e14568de32273e819ba478be7a61a0dd63178931b4e891050a34e73d296ab533aa434209a7f3146 languageName: node linkType: hard -"stylelint-config-recommended@npm:^16.0.0": - version: 16.0.0 - resolution: "stylelint-config-recommended@npm:16.0.0" +"stylelint-config-recommended@npm:^17.0.0": + version: 17.0.0 + resolution: "stylelint-config-recommended@npm:17.0.0" peerDependencies: - stylelint: ^16.16.0 - checksum: 10c0/b2b4ea2633a606a0f686521aa5e8908810c9dd21fd4525c86b34213de1e362b445fd5472b6e5ff251d46f999e2ca2c6c704f2efc1c08d5a532084427f4e1c9d8 + stylelint: ^16.23.0 + checksum: 10c0/49e5d1c0f58197b2c5585b85fad814fed9bdec44c9870368c46a762664c5ff158c1145b6337456ae194409d692992b5b87421d62880422f71d8a3360417f5ad1 languageName: node linkType: hard -"stylelint-config-standard-scss@npm:^15.0.1": - version: 15.0.1 - resolution: "stylelint-config-standard-scss@npm:15.0.1" +"stylelint-config-standard-scss@npm:^16.0.0": + version: 16.0.0 + resolution: "stylelint-config-standard-scss@npm:16.0.0" dependencies: - stylelint-config-recommended-scss: "npm:^15.0.1" - stylelint-config-standard: "npm:^38.0.0" + stylelint-config-recommended-scss: "npm:^16.0.1" + stylelint-config-standard: "npm:^39.0.0" peerDependencies: postcss: ^8.3.3 - stylelint: ^16.18.0 + stylelint: ^16.23.1 peerDependenciesMeta: postcss: optional: true - checksum: 10c0/85b4c85a9ecd97176ac104fb4590cd48047b6253b830d08749c024752b9bc8871bbf69eca592769d69cd4c6e3f90005960630f1c2cdaf85dbfabdb5621ecc55f + checksum: 10c0/eb77f23824c5d649b193cb71d7f9b538b32b8cc1769451b2993270361127243d4011baf891ec265711b8e34e69ce28acb57ab6c3947b51fa3713ac26f4276439 languageName: node linkType: hard -"stylelint-config-standard@npm:^38.0.0": - version: 38.0.0 - resolution: "stylelint-config-standard@npm:38.0.0" +"stylelint-config-standard@npm:^39.0.0": + version: 39.0.1 + resolution: "stylelint-config-standard@npm:39.0.1" dependencies: - stylelint-config-recommended: "npm:^16.0.0" + stylelint-config-recommended: "npm:^17.0.0" peerDependencies: - stylelint: ^16.18.0 - checksum: 10c0/8b52c7b7d6287c7495a8fe3a681e07ea9478374e7e66b28d61779072d46cd5b845530b2410df7496a008a8efafe834fb46cf07792f4cf57f996e39f24a801b90 + stylelint: ^16.23.0 + checksum: 10c0/70a9862a2cedcc2a1807bd92fc91c40877270cf8a39576b91ae056d6de51d3b68104b26f71056ff22461b4319e9ec988d009abf10ead513b2ec15569d82e865a languageName: node linkType: hard -"stylelint-scss@npm:^6.12.0": - version: 6.12.0 - resolution: "stylelint-scss@npm:6.12.0" +"stylelint-scss@npm:^6.12.1": + version: 6.12.1 + resolution: "stylelint-scss@npm:6.12.1" dependencies: css-tree: "npm:^3.0.1" is-plain-object: "npm:^5.0.0" @@ -12907,28 +13431,29 @@ __metadata: postcss-value-parser: "npm:^4.2.0" peerDependencies: stylelint: ^16.0.2 - checksum: 10c0/c0ba314badd22118047e374febf8dabac56bd351d612ed9c9fc2da5dc760996c2768605aa8d4e483cf0b0fe649c35ae5a003c8a872ee5bec1bbc2d8d45673ff5 + checksum: 10c0/9a0903d34be3c75a72bef32402899db5f6b94c0823c5944fdf1acb2c3dc61c1f70fbb322558f8cb7e42dd01ed5e0dec22ed298f03b7bacc9f467c28330acae71 languageName: node linkType: hard "stylelint@npm:^16.19.1": - version: 16.24.0 - resolution: "stylelint@npm:16.24.0" + version: 16.26.1 + resolution: "stylelint@npm:16.26.1" dependencies: "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-syntax-patches-for-csstree": "npm:^1.0.19" "@csstools/css-tokenizer": "npm:^3.0.4" "@csstools/media-query-list-parser": "npm:^4.0.3" "@csstools/selector-specificity": "npm:^5.0.0" - "@dual-bundle/import-meta-resolve": "npm:^4.1.0" + "@dual-bundle/import-meta-resolve": "npm:^4.2.1" balanced-match: "npm:^2.0.0" colord: "npm:^2.9.3" cosmiconfig: "npm:^9.0.0" css-functions-list: "npm:^3.2.3" css-tree: "npm:^3.1.0" - debug: "npm:^4.4.1" + debug: "npm:^4.4.3" fast-glob: "npm:^3.3.3" fastest-levenshtein: "npm:^1.0.16" - file-entry-cache: "npm:^10.1.4" + file-entry-cache: "npm:^11.1.1" global-modules: "npm:^2.0.0" globby: "npm:^11.1.0" globjoin: "npm:^0.1.4" @@ -12955,7 +13480,7 @@ __metadata: write-file-atomic: "npm:^5.0.1" bin: stylelint: bin/stylelint.mjs - checksum: 10c0/f694bfa86b8030d71e0ebd7eb815e927869481325af9a096d574d60d459944d3f11c295a5ed8885ae3d090aa1ab9431698fba1b7b99cea9884f7c762433a2891 + checksum: 10c0/3805dfe868abdcc5a62e5726eebe5e950432cfadfc5b47c2f103ef4dede8ee1eb8a1247c9ceb01a1739c0aba68865d79899d33a707256365bb2004664524908b languageName: node linkType: hard @@ -12966,6 +13491,21 @@ __metadata: languageName: node linkType: hard +"stylus@npm:^0.62.0": + version: 0.62.0 + resolution: "stylus@npm:0.62.0" + dependencies: + "@adobe/css-tools": "npm:~4.3.1" + debug: "npm:^4.3.2" + glob: "npm:^7.1.6" + sax: "npm:~1.3.0" + source-map: "npm:^0.7.3" + bin: + stylus: bin/stylus + checksum: 10c0/62afe3a6d781f66d7d283e8218dc1a15530d7d89fc2f09457a723975b2073e96e0d32c61d7f0dd1bd2686aae4ab6cc6933dc85e1b72eebab8aa30167bd16962b + languageName: node + linkType: hard + "substring-trie@npm:^1.0.2": version: 1.0.2 resolution: "substring-trie@npm:1.0.2" @@ -13096,16 +13636,16 @@ __metadata: languageName: node linkType: hard -"tesseract.js-core@npm:^6.0.0": - version: 6.0.0 - resolution: "tesseract.js-core@npm:6.0.0" - checksum: 10c0/c04be8bbaa296be658664496754f21e857bdffff84113f08adf02f03a1f84596d68b3542ed2fda4a6dc138abb84b09b30ab07c04ee5950879e780876d343955f +"tesseract.js-core@npm:^7.0.0": + version: 7.0.0 + resolution: "tesseract.js-core@npm:7.0.0" + checksum: 10c0/c1afee9f8aecf994bc4714fd879e57d04b995849345532872bdc3d8c82a59c4ebbb0acde14d2b24e6a3aec27cafee3d18931f2744496d603ae36241290108e17 languageName: node linkType: hard -"tesseract.js@npm:^6.0.0": - version: 6.0.1 - resolution: "tesseract.js@npm:6.0.1" +"tesseract.js@npm:^7.0.0": + version: 7.0.0 + resolution: "tesseract.js@npm:7.0.0" dependencies: bmp-js: "npm:^0.1.0" idb-keyval: "npm:^6.2.0" @@ -13113,21 +13653,10 @@ __metadata: node-fetch: "npm:^2.6.9" opencollective-postinstall: "npm:^2.0.3" regenerator-runtime: "npm:^0.13.3" - tesseract.js-core: "npm:^6.0.0" - wasm-feature-detect: "npm:^1.2.11" + tesseract.js-core: "npm:^7.0.0" + wasm-feature-detect: "npm:^1.8.0" zlibjs: "npm:^0.3.1" - checksum: 10c0/1d73bb1fbc00c8629756d9594989d8bbfabda657a8cad84922ad68eb0f073148c82845bf71a882e5d2427a46edb5a470356864e60562c7a8442bddd70251435a - languageName: node - linkType: hard - -"test-exclude@npm:^7.0.1": - version: 7.0.1 - resolution: "test-exclude@npm:7.0.1" - dependencies: - "@istanbuljs/schema": "npm:^0.1.2" - glob: "npm:^10.4.1" - minimatch: "npm:^9.0.4" - checksum: 10c0/6d67b9af4336a2e12b26a68c83308c7863534c65f27ed4ff7068a56f5a58f7ac703e8fc80f698a19bb154fd8f705cdf7ec347d9512b2c522c737269507e7b263 + checksum: 10c0/daf5b153a9a06e0ab3365b33f4cc323e375d3a8b86a7df98031c19047623451aa3bfb293c295b0ebecd5c0781e42e43469d93ed4e7ba8a868b7a457120e609a1 languageName: node linkType: hard @@ -13168,10 +13697,10 @@ __metadata: languageName: node linkType: hard -"tinyexec@npm:^0.3.2": - version: 0.3.2 - resolution: "tinyexec@npm:0.3.2" - checksum: 10c0/3efbf791a911be0bf0821eab37a3445c2ba07acc1522b1fa84ae1e55f10425076f1290f680286345ed919549ad67527d07281f1c19d584df3b74326909eb1f90 +"tinyexec@npm:^1.0.2": + version: 1.0.2 + resolution: "tinyexec@npm:1.0.2" + checksum: 10c0/1261a8e34c9b539a9aae3b7f0bb5372045ff28ee1eba035a2a059e532198fe1a182ec61ac60fa0b4a4129f0c4c4b1d2d57355b5cb9aa2d17ac9454ecace502ee languageName: node linkType: hard @@ -13185,13 +13714,6 @@ __metadata: languageName: node linkType: hard -"tinypool@npm:^1.1.1": - version: 1.1.1 - resolution: "tinypool@npm:1.1.1" - checksum: 10c0/bf26727d01443061b04fa863f571016950888ea994ba0cd8cba3a1c51e2458d84574341ab8dbc3664f1c3ab20885c8cf9ff1cc4b18201f04c2cde7d317fff69b - languageName: node - linkType: hard - "tinyrainbow@npm:^2.0.0": version: 2.0.0 resolution: "tinyrainbow@npm:2.0.0" @@ -13199,6 +13721,13 @@ __metadata: languageName: node linkType: hard +"tinyrainbow@npm:^3.0.3": + version: 3.0.3 + resolution: "tinyrainbow@npm:3.0.3" + checksum: 10c0/1e799d35cd23cabe02e22550985a3051dc88814a979be02dc632a159c393a998628eacfc558e4c746b3006606d54b00bcdea0c39301133956d10a27aa27e988c + languageName: node + linkType: hard + "tinyspy@npm:^4.0.3": version: 4.0.3 resolution: "tinyspy@npm:4.0.3" @@ -13233,7 +13762,17 @@ __metadata: languageName: node linkType: hard -"toidentifier@npm:1.0.1": +"to-valid-identifier@npm:^1.0.0": + version: 1.0.0 + resolution: "to-valid-identifier@npm:1.0.0" + dependencies: + "@sindresorhus/base62": "npm:^1.0.0" + reserved-identifiers: "npm:^1.0.0" + checksum: 10c0/569b49f43b5aaaa20677e67f0f1cdcff344855149934cfb80c793c7ac7c30e191b224bc81cab40fb57641af9ca73795c78053c164a2addc617671e2d22c13a4a + languageName: node + linkType: hard + +"toidentifier@npm:~1.0.1": version: 1.0.1 resolution: "toidentifier@npm:1.0.1" checksum: 10c0/93937279934bd66cc3270016dd8d0afec14fb7c94a05c72dc57321f8bd1fa97e5bea6d1f7c89e728d077ca31ea125b78320a616a6c6cd0e6b9cb94cb864381c1 @@ -13272,12 +13811,12 @@ __metadata: languageName: node linkType: hard -"tr46@npm:^5.1.1": - version: 5.1.1 - resolution: "tr46@npm:5.1.1" +"tr46@npm:^6.0.0": + version: 6.0.0 + resolution: "tr46@npm:6.0.0" dependencies: punycode: "npm:^2.3.1" - checksum: 10c0/ae270e194d52ec67ebd695c1a42876e0f19b96e4aca2ab464ab1d9d17dc3acd3e18764f5034c93897db73421563be27c70c98359c4501136a497e46deda5d5ec + checksum: 10c0/83130df2f649228aa91c17754b66248030a3af34911d713b5ea417066fa338aa4bc8668d06bd98aa21a2210f43fc0a3db8b9099e7747fb5830e40e39a6a1058e languageName: node linkType: hard @@ -13341,7 +13880,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.4.0, tslib@npm:^2.8.0": +"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.3.0, tslib@npm:^2.4.0, tslib@npm:^2.8.0": version: 2.8.1 resolution: "tslib@npm:2.8.1" checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 @@ -13397,13 +13936,14 @@ __metadata: languageName: node linkType: hard -"type-is@npm:~1.6.18": - version: 1.6.18 - resolution: "type-is@npm:1.6.18" +"type-is@npm:^2.0.1": + version: 2.0.1 + resolution: "type-is@npm:2.0.1" dependencies: - media-typer: "npm:0.3.0" - mime-types: "npm:~2.1.24" - checksum: 10c0/a23daeb538591b7efbd61ecf06b6feb2501b683ffdc9a19c74ef5baba362b4347e42f1b4ed81f5882a8c96a3bfff7f93ce3ffaf0cbbc879b532b04c97a55db9d + content-type: "npm:^1.0.5" + media-typer: "npm:^1.1.0" + mime-types: "npm:^3.0.0" + checksum: 10c0/7f7ec0a060b16880bdad36824ab37c26019454b67d73e8a465ed5a3587440fbe158bc765f0da68344498235c877e7dbbb1600beccc94628ed05599d667951b99 languageName: node linkType: hard @@ -13460,58 +14000,67 @@ __metadata: languageName: node linkType: hard -"typescript-eslint@npm:^8.28.0, typescript-eslint@npm:^8.29.1": - version: 8.38.0 - resolution: "typescript-eslint@npm:8.38.0" +"typescript-eslint@npm:^8.28.0, typescript-eslint@npm:^8.45.0": + version: 8.48.1 + resolution: "typescript-eslint@npm:8.48.1" dependencies: - "@typescript-eslint/eslint-plugin": "npm:8.38.0" - "@typescript-eslint/parser": "npm:8.38.0" - "@typescript-eslint/typescript-estree": "npm:8.38.0" - "@typescript-eslint/utils": "npm:8.38.0" + "@typescript-eslint/eslint-plugin": "npm:8.48.1" + "@typescript-eslint/parser": "npm:8.48.1" + "@typescript-eslint/typescript-estree": "npm:8.48.1" + "@typescript-eslint/utils": "npm:8.48.1" peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/486b9862ee08f7827d808a2264ce03b58087b11c4c646c0da3533c192a67ae3fcb4e68d7a1e69d0f35a1edc274371a903a50ecfe74012d5eaa896cb9d5a81e0b - languageName: node - linkType: hard - -"typescript@npm:^5.6.0": - version: 5.8.2 - resolution: "typescript@npm:5.8.2" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10c0/5c4f6fbf1c6389b6928fe7b8fcd5dc73bb2d58cd4e3883f1d774ed5bd83b151cbac6b7ecf11723de56d4676daeba8713894b1e9af56174f2f9780ae7848ec3c6 + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/10b501bf69b14edd09d652b33e4a5dfad0498f2943992a433006933e384cdc5815217b2990801796ddf946d2ef4971d9a16c98c7cfbba41f6aa31b245ad057ac languageName: node linkType: hard -"typescript@npm:~5.7.3": - version: 5.7.3 - resolution: "typescript@npm:5.7.3" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10c0/b7580d716cf1824736cc6e628ab4cd8b51877408ba2be0869d2866da35ef8366dd6ae9eb9d0851470a39be17cbd61df1126f9e211d8799d764ea7431d5435afa +"typescript-plugin-css-modules@npm:^5.2.0": + version: 5.2.0 + resolution: "typescript-plugin-css-modules@npm:5.2.0" + dependencies: + "@types/postcss-modules-local-by-default": "npm:^4.0.2" + "@types/postcss-modules-scope": "npm:^3.0.4" + dotenv: "npm:^16.4.2" + icss-utils: "npm:^5.1.0" + less: "npm:^4.2.0" + lodash.camelcase: "npm:^4.3.0" + postcss: "npm:^8.4.35" + postcss-load-config: "npm:^3.1.4" + postcss-modules-extract-imports: "npm:^3.0.0" + postcss-modules-local-by-default: "npm:^4.0.4" + postcss-modules-scope: "npm:^3.1.1" + reserved-words: "npm:^0.1.2" + sass: "npm:^1.70.0" + source-map-js: "npm:^1.0.2" + stylus: "npm:^0.62.0" + tsconfig-paths: "npm:^4.2.0" + peerDependencies: + typescript: ">=4.0.0" + dependenciesMeta: + stylus: + optional: true + checksum: 10c0/7cd024f7145c0a29d9b78f2fb49c42cdf1747b50a43391f9993132ba42a727266f9b544fd868d905d5352e0a8676a19ae7a9aa56d516cc819c3ab39d66aa25e4 languageName: node linkType: hard -"typescript@patch:typescript@npm%3A^5.6.0#optional!builtin": - version: 5.8.2 - resolution: "typescript@patch:typescript@npm%3A5.8.2#optional!builtin::version=5.8.2&hash=5786d5" +"typescript@npm:^5.6.0, typescript@npm:~5.9.0": + version: 5.9.3 + resolution: "typescript@npm:5.9.3" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10c0/5448a08e595cc558ab321e49d4cac64fb43d1fa106584f6ff9a8d8e592111b373a995a1d5c7f3046211c8a37201eb6d0f1566f15cdb7a62a5e3be01d087848e2 + checksum: 10c0/6bd7552ce39f97e711db5aa048f6f9995b53f1c52f7d8667c1abdc1700c68a76a308f579cd309ce6b53646deb4e9a1be7c813a93baaf0a28ccd536a30270e1c5 languageName: node linkType: hard -"typescript@patch:typescript@npm%3A~5.7.3#optional!builtin": - version: 5.7.3 - resolution: "typescript@patch:typescript@npm%3A5.7.3#optional!builtin::version=5.7.3&hash=5786d5" +"typescript@patch:typescript@npm%3A^5.6.0#optional!builtin, typescript@patch:typescript@npm%3A~5.9.0#optional!builtin": + version: 5.9.3 + resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10c0/6fd7e0ed3bf23a81246878c613423730c40e8bdbfec4c6e4d7bf1b847cbb39076e56ad5f50aa9d7ebd89877999abaee216002d3f2818885e41c907caaa192cc4 + checksum: 10c0/ad09fdf7a756814dce65bc60c1657b40d44451346858eea230e10f2e95a289d9183b6e32e5c11e95acc0ccc214b4f36289dcad4bf1886b0adb84d711d336a430 languageName: node linkType: hard @@ -13588,13 +14137,6 @@ __metadata: languageName: node linkType: hard -"unicorn-magic@npm:^0.1.0": - version: 0.1.0 - resolution: "unicorn-magic@npm:0.1.0" - checksum: 10c0/e4ed0de05b0a05e735c7d8a2930881e5efcfc3ec897204d5d33e7e6247f4c31eac92e383a15d9a6bccb7319b4271ee4bea946e211bf14951fec6ff2cbbb66a92 - languageName: node - linkType: hard - "unique-filename@npm:^4.0.0": version: 4.0.0 resolution: "unique-filename@npm:4.0.0" @@ -13629,20 +14171,22 @@ __metadata: languageName: node linkType: hard -"unpipe@npm:1.0.0, unpipe@npm:~1.0.0": +"unpipe@npm:~1.0.0": version: 1.0.0 resolution: "unpipe@npm:1.0.0" checksum: 10c0/193400255bd48968e5c5383730344fbb4fa114cdedfab26e329e50dd2d81b134244bb8a72c6ac1b10ab0281a58b363d06405632c9d49ca9dfd5e90cbd7d0f32c languageName: node linkType: hard -"unplugin@npm:^1.3.1": - version: 1.16.1 - resolution: "unplugin@npm:1.16.1" +"unplugin@npm:^2.3.5": + version: 2.3.10 + resolution: "unplugin@npm:2.3.10" dependencies: - acorn: "npm:^8.14.0" + "@jridgewell/remapping": "npm:^2.3.5" + acorn: "npm:^8.15.0" + picomatch: "npm:^4.0.3" webpack-virtual-modules: "npm:^0.6.2" - checksum: 10c0/dd5f8c5727d0135847da73cf03fb199107f1acf458167034886fda3405737dab871ad3926431b4f70e1e82cdac482ac1383cea4019d782a68515c8e3e611b6cc + checksum: 10c0/29dcd738772aeff91c6f0154f156c95c58a37a4674fcb7cc34d6868af763834f0f447a1c3af074818c0c5602baead49bd3b9399a13f0425d69a00a527e58ddda languageName: node linkType: hard @@ -13727,9 +14271,9 @@ __metadata: languageName: node linkType: hard -"update-browserslist-db@npm:^1.1.3": - version: 1.1.3 - resolution: "update-browserslist-db@npm:1.1.3" +"update-browserslist-db@npm:^1.2.0": + version: 1.2.2 + resolution: "update-browserslist-db@npm:1.2.2" dependencies: escalade: "npm:^3.2.0" picocolors: "npm:^1.1.1" @@ -13737,7 +14281,7 @@ __metadata: browserslist: ">= 4.21.0" bin: update-browserslist-db: cli.js - checksum: 10c0/682e8ecbf9de474a626f6462aa85927936cdd256fe584c6df2508b0df9f7362c44c957e9970df55dfe44d3623807d26316ea2c7d26b80bb76a16c56c37233c32 + checksum: 10c0/39c3ea08b397ffc8dc3a1c517f5c6ed5cc4179b5e185383dab9bf745879623c12062a2e6bf4f9427cc59389c7bfa0010e86858b923c1e349e32fdddd9b043bb2 languageName: node linkType: hard @@ -13794,12 +14338,12 @@ __metadata: languageName: node linkType: hard -"use-sync-external-store@npm:^1.4.0": - version: 1.4.0 - resolution: "use-sync-external-store@npm:1.4.0" +"use-sync-external-store@npm:^1.4.0, use-sync-external-store@npm:^1.5.0": + version: 1.6.0 + resolution: "use-sync-external-store@npm:1.6.0" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - checksum: 10c0/ec011a5055962c0f6b509d6e78c0b143f8cd069890ae370528753053c55e3b360d3648e76cfaa854faa7a59eb08d6c5fb1015e60ffde9046d32f5b2a295acea5 + checksum: 10c0/35e1179f872a53227bdf8a827f7911da4c37c0f4091c29b76b1e32473d1670ebe7bcd880b808b7549ba9a5605c233350f800ffab963ee4a4ee346ee983b6019b languageName: node linkType: hard @@ -13820,19 +14364,12 @@ __metadata: languageName: node linkType: hard -"utils-merge@npm:1.0.1": - version: 1.0.1 - resolution: "utils-merge@npm:1.0.1" - checksum: 10c0/02ba649de1b7ca8854bfe20a82f1dfbdda3fb57a22ab4a8972a63a34553cf7aa51bc9081cf7e001b035b88186d23689d69e71b510e610a09a4c66f68aa95b672 - languageName: node - linkType: hard - -"uuid@npm:^11.0.0": - version: 11.1.0 - resolution: "uuid@npm:11.1.0" +"uuid@npm:^13.0.0": + version: 13.0.0 + resolution: "uuid@npm:13.0.0" bin: - uuid: dist/esm/bin/uuid - checksum: 10c0/34aa51b9874ae398c2b799c88a127701408cd581ee89ec3baa53509dd8728cbb25826f2a038f9465f8b7be446f0fbf11558862965b18d21c993684297628d4d3 + uuid: dist-node/bin/uuid + checksum: 10c0/950e4c18d57fef6c69675344f5700a08af21e26b9eff2bf2180427564297368c538ea11ac9fb2e6528b17fc3966a9fd2c5049361b0b63c7d654f3c550c9b3d67 languageName: node linkType: hard @@ -13843,28 +14380,13 @@ __metadata: languageName: node linkType: hard -"vary@npm:^1, vary@npm:~1.1.2": +"vary@npm:^1, vary@npm:^1.1.2": version: 1.1.2 resolution: "vary@npm:1.1.2" checksum: 10c0/f15d588d79f3675135ba783c91a4083dcd290a2a5be9fcb6514220a1634e23df116847b1cc51f66bfb0644cf9353b2abb7815ae499bab06e46dd33c1a6bf1f4f languageName: node linkType: hard -"vite-node@npm:3.2.4": - version: 3.2.4 - resolution: "vite-node@npm:3.2.4" - dependencies: - cac: "npm:^6.7.14" - debug: "npm:^4.4.1" - es-module-lexer: "npm:^1.7.0" - pathe: "npm:^2.0.3" - vite: "npm:^5.0.0 || ^6.0.0 || ^7.0.0-0" - bin: - vite-node: vite-node.mjs - checksum: 10c0/6ceca67c002f8ef6397d58b9539f80f2b5d79e103a18367288b3f00a8ab55affa3d711d86d9112fce5a7fa658a212a087a005a045eb8f4758947dd99af2a6c6b - languageName: node - linkType: hard - "vite-plugin-manifest-sri@npm:^0.2.0": version: 0.2.0 resolution: "vite-plugin-manifest-sri@npm:0.2.0" @@ -13873,38 +14395,23 @@ __metadata: linkType: hard "vite-plugin-pwa@npm:^1.0.2": - version: 1.0.3 - resolution: "vite-plugin-pwa@npm:1.0.3" + version: 1.2.0 + resolution: "vite-plugin-pwa@npm:1.2.0" dependencies: debug: "npm:^4.3.6" pretty-bytes: "npm:^6.1.1" tinyglobby: "npm:^0.2.10" - workbox-build: "npm:^7.3.0" - workbox-window: "npm:^7.3.0" + workbox-build: "npm:^7.4.0" + workbox-window: "npm:^7.4.0" peerDependencies: "@vite-pwa/assets-generator": ^1.0.0 vite: ^3.1.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 - workbox-build: ^7.3.0 - workbox-window: ^7.3.0 + workbox-build: ^7.4.0 + workbox-window: ^7.4.0 peerDependenciesMeta: "@vite-pwa/assets-generator": optional: true - checksum: 10c0/03fc24bd12ae4a4130979da4877e3dabddf13d7d6ff2bd68e5e8497a2643b8874a78e6c2502874277ddf2f28593d0a3b025d78af2335bdcc5d2966295784fd46 - languageName: node - linkType: hard - -"vite-plugin-static-copy@npm:^3.1.1": - version: 3.1.2 - resolution: "vite-plugin-static-copy@npm:3.1.2" - dependencies: - chokidar: "npm:^3.6.0" - fs-extra: "npm:^11.3.0" - p-map: "npm:^7.0.3" - picocolors: "npm:^1.1.1" - tinyglobby: "npm:^0.2.14" - peerDependencies: - vite: ^5.0.0 || ^6.0.0 || ^7.0.0 - checksum: 10c0/1a65f4c9d291cc27483a5b225b1ac5610edc3aa2f13fa3a76a77327874c83bbee52e1011ee0bf5b0168b9b7b974213d49fe800e44af398cfbcb6607814b45c5b + checksum: 10c0/d037591fc6a44b9a97f45b2452f691fce34c1c8ece4721d4f1a068f6dd7c30991b63c090a2af87d5b78d351bf5f5eee9d9dbbfc4bb39a77d3bc2ab7d2ca5df6b languageName: node linkType: hard @@ -13921,9 +14428,9 @@ __metadata: languageName: node linkType: hard -"vite-tsconfig-paths@npm:^5.1.4": - version: 5.1.4 - resolution: "vite-tsconfig-paths@npm:5.1.4" +"vite-tsconfig-paths@npm:^6.0.0": + version: 6.0.3 + resolution: "vite-tsconfig-paths@npm:6.0.3" dependencies: debug: "npm:^4.1.1" globrex: "npm:^0.1.2" @@ -13933,13 +14440,13 @@ __metadata: peerDependenciesMeta: vite: optional: true - checksum: 10c0/6228f23155ea25d92b1e1702284cf8dc52ad3c683c5ca691edd5a4c82d2913e7326d00708cef1cbfde9bb226261df0e0a12e03ef1d43b6a92d8f02b483ef37e3 + checksum: 10c0/75cfe470f1ec0e776b2aec1d2e71316d5e1214f485fce7daaed4e4789d6f667881fb85d98129b6463a5b70c7524ef258b401c4871ed8b6318ac45cc892ee778a languageName: node linkType: hard -"vite@npm:^5.0.0 || ^6.0.0 || ^7.0.0-0, vite@npm:^7.1.1": - version: 7.1.7 - resolution: "vite@npm:7.1.7" +"vite@npm:^6.0.0 || ^7.0.0, vite@npm:^7.1.1": + version: 7.2.7 + resolution: "vite@npm:7.2.7" dependencies: esbuild: "npm:^0.25.0" fdir: "npm:^6.5.0" @@ -13988,53 +14495,56 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: 10c0/3f6bd61a65aaa81368f4dda804f0e23b103664724218ccb5a0b1a0c7e284df498107b57ced951dc40ae4c5d472435bc8fb5c836414e729ee7e102809eaf6ff80 + checksum: 10c0/0c502d9eb898d9c05061dbd8fd199f280b524bbb4c12ab5f88c7b12779947386684a269e4dd0aa424aa35bcd857f1aa44aadb9ea764702a5043af433052455b5 languageName: node linkType: hard -"vitest@npm:^3.2.4": - version: 3.2.4 - resolution: "vitest@npm:3.2.4" +"vitest@npm:^4.0.5": + version: 4.0.15 + resolution: "vitest@npm:4.0.15" dependencies: - "@types/chai": "npm:^5.2.2" - "@vitest/expect": "npm:3.2.4" - "@vitest/mocker": "npm:3.2.4" - "@vitest/pretty-format": "npm:^3.2.4" - "@vitest/runner": "npm:3.2.4" - "@vitest/snapshot": "npm:3.2.4" - "@vitest/spy": "npm:3.2.4" - "@vitest/utils": "npm:3.2.4" - chai: "npm:^5.2.0" - debug: "npm:^4.4.1" - expect-type: "npm:^1.2.1" - magic-string: "npm:^0.30.17" + "@vitest/expect": "npm:4.0.15" + "@vitest/mocker": "npm:4.0.15" + "@vitest/pretty-format": "npm:4.0.15" + "@vitest/runner": "npm:4.0.15" + "@vitest/snapshot": "npm:4.0.15" + "@vitest/spy": "npm:4.0.15" + "@vitest/utils": "npm:4.0.15" + es-module-lexer: "npm:^1.7.0" + expect-type: "npm:^1.2.2" + magic-string: "npm:^0.30.21" + obug: "npm:^2.1.1" pathe: "npm:^2.0.3" - picomatch: "npm:^4.0.2" - std-env: "npm:^3.9.0" + picomatch: "npm:^4.0.3" + std-env: "npm:^3.10.0" tinybench: "npm:^2.9.0" - tinyexec: "npm:^0.3.2" - tinyglobby: "npm:^0.2.14" - tinypool: "npm:^1.1.1" - tinyrainbow: "npm:^2.0.0" - vite: "npm:^5.0.0 || ^6.0.0 || ^7.0.0-0" - vite-node: "npm:3.2.4" + tinyexec: "npm:^1.0.2" + tinyglobby: "npm:^0.2.15" + tinyrainbow: "npm:^3.0.3" + vite: "npm:^6.0.0 || ^7.0.0" why-is-node-running: "npm:^2.3.0" peerDependencies: "@edge-runtime/vm": "*" - "@types/debug": ^4.1.12 - "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 - "@vitest/browser": 3.2.4 - "@vitest/ui": 3.2.4 + "@opentelemetry/api": ^1.9.0 + "@types/node": ^20.0.0 || ^22.0.0 || >=24.0.0 + "@vitest/browser-playwright": 4.0.15 + "@vitest/browser-preview": 4.0.15 + "@vitest/browser-webdriverio": 4.0.15 + "@vitest/ui": 4.0.15 happy-dom: "*" jsdom: "*" peerDependenciesMeta: "@edge-runtime/vm": optional: true - "@types/debug": + "@opentelemetry/api": optional: true "@types/node": optional: true - "@vitest/browser": + "@vitest/browser-playwright": + optional: true + "@vitest/browser-preview": + optional: true + "@vitest/browser-webdriverio": optional: true "@vitest/ui": optional: true @@ -14044,7 +14554,7 @@ __metadata: optional: true bin: vitest: vitest.mjs - checksum: 10c0/5bf53ede3ae6a0e08956d72dab279ae90503f6b5a05298a6a5e6ef47d2fd1ab386aaf48fafa61ed07a0ebfe9e371772f1ccbe5c258dd765206a8218bf2eb79eb + checksum: 10c0/fd57913dbcba81b67ca67bae37f0920f2785a60939a9029a82ebb843253f7a67f93f2c959cb90bb23a57055c0256ec0a6059ec9a10c129e8912c09b6e407242b languageName: node linkType: hard @@ -14057,16 +14567,7 @@ __metadata: languageName: node linkType: hard -"warning@npm:^3.0.0": - version: 3.0.0 - resolution: "warning@npm:3.0.0" - dependencies: - loose-envify: "npm:^1.0.0" - checksum: 10c0/6a2a56ab3139d3927193d926a027e74e1449fa47cc692feea95f8a81a4bb5b7f10c312def94cce03f3b58cb26ba3247858e75d17d596451d2c483a62e8204705 - languageName: node - linkType: hard - -"warning@npm:^4.0.1, warning@npm:^4.0.3": +"warning@npm:^4.0.3": version: 4.0.3 resolution: "warning@npm:4.0.3" dependencies: @@ -14075,7 +14576,7 @@ __metadata: languageName: node linkType: hard -"wasm-feature-detect@npm:^1.2.11": +"wasm-feature-detect@npm:^1.8.0": version: 1.8.0 resolution: "wasm-feature-detect@npm:1.8.0" checksum: 10c0/2cb43e91bbf7aa7c121bc76b3133de3ab6dc4f482acc1d2dc46c528e8adb7a51c72df5c2aacf1d219f113c04efd1706f18274d5790542aa5dd49e0644e3ee665 @@ -14126,13 +14627,13 @@ __metadata: languageName: node linkType: hard -"whatwg-url@npm:^15.0.0": - version: 15.0.0 - resolution: "whatwg-url@npm:15.0.0" +"whatwg-url@npm:^15.0.0, whatwg-url@npm:^15.1.0": + version: 15.1.0 + resolution: "whatwg-url@npm:15.1.0" dependencies: - tr46: "npm:^5.1.1" + tr46: "npm:^6.0.0" webidl-conversions: "npm:^8.0.0" - checksum: 10c0/7b4f633fcd8fea6d588fb5694a9c4631382b41d31270e1fb3b755923f2c267d456ea1c7122124689e430b6a4e73c7e43e0731f833b33bfedcfffa2a7b4efbfc0 + checksum: 10c0/40c49b47044787c87486aaaa5b504da122820661c45ae20ab466c62595ed03c64be7c10c1d180d028949a393cd455db14144966a68359cd37fe6417e3426d128 languageName: node linkType: hard @@ -14270,28 +14771,28 @@ __metadata: languageName: node linkType: hard -"workbox-background-sync@npm:7.3.0": - version: 7.3.0 - resolution: "workbox-background-sync@npm:7.3.0" +"workbox-background-sync@npm:7.4.0": + version: 7.4.0 + resolution: "workbox-background-sync@npm:7.4.0" dependencies: idb: "npm:^7.0.1" - workbox-core: "npm:7.3.0" - checksum: 10c0/cc982d62702847fb16c4ef372a8bd243348a80c2d5da1649a860b0187b45060a799a65582c2d36f1a32e31d5d68dedcb037698c41d3b2f171ea5d54d73453cf1 + workbox-core: "npm:7.4.0" + checksum: 10c0/024dfad37c9ca28480857aaf7c0dd2e2f2b2ea416e100b51260eaf28f14ca5a558d0e12b1e95f862fec04df54432c090fa29582ab88b43d8778a4c821f21d13f languageName: node linkType: hard -"workbox-broadcast-update@npm:7.3.0": - version: 7.3.0 - resolution: "workbox-broadcast-update@npm:7.3.0" +"workbox-broadcast-update@npm:7.4.0": + version: 7.4.0 + resolution: "workbox-broadcast-update@npm:7.4.0" dependencies: - workbox-core: "npm:7.3.0" - checksum: 10c0/25007acd3e845b5ca1f4c9ac9888ce661431723f7419cfa56b3029b6c56cbeca24902dae015c42a2d6f554f956274743e331d03ceeb4b0e3879cb7b908d0e82f + workbox-core: "npm:7.4.0" + checksum: 10c0/9e96d38cb1cfaccf72a37beeed7d36a66db8bacaa09e584bfa15f6b129aee5026c521320b4b68fa762f4f6ba0229f11c6ec6f7bc039419c8d1f1a9ed9f2a37b5 languageName: node linkType: hard -"workbox-build@npm:^7.3.0": - version: 7.3.0 - resolution: "workbox-build@npm:7.3.0" +"workbox-build@npm:^7.4.0": + version: 7.4.0 + resolution: "workbox-build@npm:7.4.0" dependencies: "@apideck/better-ajv-errors": "npm:^0.3.1" "@babel/core": "npm:^7.24.4" @@ -14306,157 +14807,157 @@ __metadata: common-tags: "npm:^1.8.0" fast-json-stable-stringify: "npm:^2.1.0" fs-extra: "npm:^9.0.1" - glob: "npm:^7.1.6" + glob: "npm:^11.0.1" lodash: "npm:^4.17.20" pretty-bytes: "npm:^5.3.0" - rollup: "npm:^2.43.1" + rollup: "npm:^2.79.2" source-map: "npm:^0.8.0-beta.0" stringify-object: "npm:^3.3.0" strip-comments: "npm:^2.0.1" tempy: "npm:^0.6.0" upath: "npm:^1.2.0" - workbox-background-sync: "npm:7.3.0" - workbox-broadcast-update: "npm:7.3.0" - workbox-cacheable-response: "npm:7.3.0" - workbox-core: "npm:7.3.0" - workbox-expiration: "npm:7.3.0" - workbox-google-analytics: "npm:7.3.0" - workbox-navigation-preload: "npm:7.3.0" - workbox-precaching: "npm:7.3.0" - workbox-range-requests: "npm:7.3.0" - workbox-recipes: "npm:7.3.0" - workbox-routing: "npm:7.3.0" - workbox-strategies: "npm:7.3.0" - workbox-streams: "npm:7.3.0" - workbox-sw: "npm:7.3.0" - workbox-window: "npm:7.3.0" - checksum: 10c0/cb396f9c2a53429d1e11b4c1da2e21c9e1c98473ce15f20ae53277e47bd7ccbcb3f1f843694e588bb70b12d9332faafd098ca05b93abb0293d373f38a8de3ca8 + workbox-background-sync: "npm:7.4.0" + workbox-broadcast-update: "npm:7.4.0" + workbox-cacheable-response: "npm:7.4.0" + workbox-core: "npm:7.4.0" + workbox-expiration: "npm:7.4.0" + workbox-google-analytics: "npm:7.4.0" + workbox-navigation-preload: "npm:7.4.0" + workbox-precaching: "npm:7.4.0" + workbox-range-requests: "npm:7.4.0" + workbox-recipes: "npm:7.4.0" + workbox-routing: "npm:7.4.0" + workbox-strategies: "npm:7.4.0" + workbox-streams: "npm:7.4.0" + workbox-sw: "npm:7.4.0" + workbox-window: "npm:7.4.0" + checksum: 10c0/673fb05a0b24bb5534667a8d7c42304e3a1394071d73c9ccbfec881e36a66c98ed59eba92e21669a2758289c79e76a72930077a611f20ba803cdc0d0a24da6b2 languageName: node linkType: hard -"workbox-cacheable-response@npm:7.3.0": - version: 7.3.0 - resolution: "workbox-cacheable-response@npm:7.3.0" +"workbox-cacheable-response@npm:7.4.0": + version: 7.4.0 + resolution: "workbox-cacheable-response@npm:7.4.0" dependencies: - workbox-core: "npm:7.3.0" - checksum: 10c0/192c8a8878c53a205c55398bac78f2c32c0f36e55c95cab282d8a716ddf2fa72563afaed690d34d3438cc8df5fb0df4d98dcb2d93cc6d67c69a9ae592f7bf246 + workbox-core: "npm:7.4.0" + checksum: 10c0/4d4fabf3cbd7b2b0505e62ec653f933de40d4fa7600e49ac15cdf223e86b9e70580927f53c6ef27ddd027b655829918b90a77a3675593017c001811a122c6591 languageName: node linkType: hard -"workbox-core@npm:7.3.0": - version: 7.3.0 - resolution: "workbox-core@npm:7.3.0" - checksum: 10c0/b7dce640cd9665ed207f65f5b08a50e2e24e5599790c6ea4fec987539b9d2ef81765d8c5f94acfee3a8a45d5ade8e1a4ebd0b8847a1471302ef75a5b93c7bd04 +"workbox-core@npm:7.4.0": + version: 7.4.0 + resolution: "workbox-core@npm:7.4.0" + checksum: 10c0/ae7c762df084b57d3d10f42b83a508a5c0bec5663de4c9cefc2b5c2893922a816f451cf8ec45ee76d204a3a6e90ee52b6c550a6cba9109d9bb644da9e98fb9e8 languageName: node linkType: hard -"workbox-expiration@npm:7.3.0, workbox-expiration@npm:^7.3.0": - version: 7.3.0 - resolution: "workbox-expiration@npm:7.3.0" +"workbox-expiration@npm:7.4.0, workbox-expiration@npm:^7.3.0": + version: 7.4.0 + resolution: "workbox-expiration@npm:7.4.0" dependencies: idb: "npm:^7.0.1" - workbox-core: "npm:7.3.0" - checksum: 10c0/6040d72122ece901becfcc59974586e9cc9b6309840b83b652c9f9aafe32ff89783404a431cadf6f888f80e5371252820e425ced499742964d6d68687f6fad1a + workbox-core: "npm:7.4.0" + checksum: 10c0/d4f5e96e6d58d74a1be7d8c842e68e9fc2d4a22664f629ec71570039a1876e033ca1e54d68eccded83d901999e48fb388c1f807b14f706b9b83c7b3ab5815cf8 languageName: node linkType: hard -"workbox-google-analytics@npm:7.3.0": - version: 7.3.0 - resolution: "workbox-google-analytics@npm:7.3.0" +"workbox-google-analytics@npm:7.4.0": + version: 7.4.0 + resolution: "workbox-google-analytics@npm:7.4.0" dependencies: - workbox-background-sync: "npm:7.3.0" - workbox-core: "npm:7.3.0" - workbox-routing: "npm:7.3.0" - workbox-strategies: "npm:7.3.0" - checksum: 10c0/5317a4bcc01f1aa87480f9708d7d382c15fb37d6119e71e0a2909dfd683f6060b5cc4f7b016a81fc67098f51a5d0cfd1cda20e228f2f3778ee3caf649b59996b + workbox-background-sync: "npm:7.4.0" + workbox-core: "npm:7.4.0" + workbox-routing: "npm:7.4.0" + workbox-strategies: "npm:7.4.0" + checksum: 10c0/34a5ad1ea6fd6c65d5eb78b8c4bcf30b2624099b1c7c2e8f6a2af7135dcfcff00531d042f99b45960dcc42185b0f9c4b62c02a38325429fa15f060f30df61e51 languageName: node linkType: hard -"workbox-navigation-preload@npm:7.3.0": - version: 7.3.0 - resolution: "workbox-navigation-preload@npm:7.3.0" +"workbox-navigation-preload@npm:7.4.0": + version: 7.4.0 + resolution: "workbox-navigation-preload@npm:7.4.0" dependencies: - workbox-core: "npm:7.3.0" - checksum: 10c0/69e4d43c68c06889987e9fa437995378b0632c83bad8c7044b4ed812b05b94b3a4aa8700ea4c26b2ecf68ee6858e94ff41dfa3279815c1bc385ac19c0edfb200 + workbox-core: "npm:7.4.0" + checksum: 10c0/9999d78683247dcbf1ca1e18664b3ecb19e8330c0ff9328403a513051d65eb3fc6578504dcc324fe603c7fb6d3d1054de1f55b4d2b9765236298cfebebefabe9 languageName: node linkType: hard -"workbox-precaching@npm:7.3.0": - version: 7.3.0 - resolution: "workbox-precaching@npm:7.3.0" +"workbox-precaching@npm:7.4.0": + version: 7.4.0 + resolution: "workbox-precaching@npm:7.4.0" dependencies: - workbox-core: "npm:7.3.0" - workbox-routing: "npm:7.3.0" - workbox-strategies: "npm:7.3.0" - checksum: 10c0/15c4c5cf5dfec684711ce3536bbfa6873f7af16b712d02ded81d3ff490ea4097e46602705548f5872c49f06e3516fd69f17e72a7fc60631ff6d68460e48f7648 + workbox-core: "npm:7.4.0" + workbox-routing: "npm:7.4.0" + workbox-strategies: "npm:7.4.0" + checksum: 10c0/df1d3ac590f56418ae80595c6c6a718f15ce3d6b9622ef43058d9d56f7374d95202ef2932e5ef5410d949f4f4366f5eccaf7956c32f579846fcc4bd83578c246 languageName: node linkType: hard -"workbox-range-requests@npm:7.3.0": - version: 7.3.0 - resolution: "workbox-range-requests@npm:7.3.0" +"workbox-range-requests@npm:7.4.0": + version: 7.4.0 + resolution: "workbox-range-requests@npm:7.4.0" dependencies: - workbox-core: "npm:7.3.0" - checksum: 10c0/d48e1484866442864d66b1891c4965b71e997a83a7634f11452ec1a73a30a5e642e6a95d5cff45578bef4dec7a5f57bc598aeedb6189d17ca210e2c5f2898244 + workbox-core: "npm:7.4.0" + checksum: 10c0/7b945fab877bfc226fa8b7aa8e87b1d2179565314eff424672bfa70c43eec5dce99fe79109f548b6c6b054629f4464fb73085c3357f3c52cea6befe8d36f4fb4 languageName: node linkType: hard -"workbox-recipes@npm:7.3.0": - version: 7.3.0 - resolution: "workbox-recipes@npm:7.3.0" +"workbox-recipes@npm:7.4.0": + version: 7.4.0 + resolution: "workbox-recipes@npm:7.4.0" dependencies: - workbox-cacheable-response: "npm:7.3.0" - workbox-core: "npm:7.3.0" - workbox-expiration: "npm:7.3.0" - workbox-precaching: "npm:7.3.0" - workbox-routing: "npm:7.3.0" - workbox-strategies: "npm:7.3.0" - checksum: 10c0/c8146ece4247cbcbefba36a14f2cb65b5f74b2412f64cfc7955ff75ff653857161a1f1d94c987fbae4812f5b770eedcf99af965e512cc375fbc7fb5421bdc99c + workbox-cacheable-response: "npm:7.4.0" + workbox-core: "npm:7.4.0" + workbox-expiration: "npm:7.4.0" + workbox-precaching: "npm:7.4.0" + workbox-routing: "npm:7.4.0" + workbox-strategies: "npm:7.4.0" + checksum: 10c0/2de53c679af2879921861d817b2f9b6a682aff0d871d38542519c16902ccfdfe7d5bf456f1e3a1f2fdb86824795f1794fec39a5b342a0d9866fccfd0e02a93a7 languageName: node linkType: hard -"workbox-routing@npm:7.3.0, workbox-routing@npm:^7.3.0": - version: 7.3.0 - resolution: "workbox-routing@npm:7.3.0" +"workbox-routing@npm:7.4.0, workbox-routing@npm:^7.3.0": + version: 7.4.0 + resolution: "workbox-routing@npm:7.4.0" dependencies: - workbox-core: "npm:7.3.0" - checksum: 10c0/8ac1824211d0fbe0e916ecb2c2427bcb0ef8783f9225d8114fe22e6c326f2d8a040a089bead58064e8b096ec95abe070c04cd7353dd8830dba3ab8d608a053aa + workbox-core: "npm:7.4.0" + checksum: 10c0/62a2a3f24a5c54fc716e9275ccd032c5e35cf645d3bcf670c91531a2a8308025767a4ff799d36483eab50c007647748ab309ea1af76847ebcd78203ef4069ae6 languageName: node linkType: hard -"workbox-strategies@npm:7.3.0, workbox-strategies@npm:^7.3.0": - version: 7.3.0 - resolution: "workbox-strategies@npm:7.3.0" +"workbox-strategies@npm:7.4.0, workbox-strategies@npm:^7.3.0": + version: 7.4.0 + resolution: "workbox-strategies@npm:7.4.0" dependencies: - workbox-core: "npm:7.3.0" - checksum: 10c0/50f3c28b46b54885a9461ad6559010d9abb2a7e35e0128d05c268f3ea0a96b1a747934758121d0e821f7af63946d9db8f4d2d7e0146f12555fb05c768e6b82bb + workbox-core: "npm:7.4.0" + checksum: 10c0/c25771af342ce10aee006d749e7adba4e77e7379b39cd96dbd05557f0f4968ff1ac88001022fb588ce2c105e5dfded321ce9f14344291c19ab753db37e6611f8 languageName: node linkType: hard -"workbox-streams@npm:7.3.0": - version: 7.3.0 - resolution: "workbox-streams@npm:7.3.0" +"workbox-streams@npm:7.4.0": + version: 7.4.0 + resolution: "workbox-streams@npm:7.4.0" dependencies: - workbox-core: "npm:7.3.0" - workbox-routing: "npm:7.3.0" - checksum: 10c0/2ae541343d187eb7a50da2cfd74051f15771d1ddd1cad6856ffd530f7cccdb8eed9a8af94ff7540b710fef73eeec37d652123ae42b0206fbbd0679dc25e66ff4 + workbox-core: "npm:7.4.0" + workbox-routing: "npm:7.4.0" + checksum: 10c0/213ac3cebb6f499fd2bedf28ae75523c8193ca6c7ace797227347775811dba80551dd645fc0bf04fb1b36eea2cf030e4ce3648d886cc0e8017363bd3806c84f3 languageName: node linkType: hard -"workbox-sw@npm:7.3.0": - version: 7.3.0 - resolution: "workbox-sw@npm:7.3.0" - checksum: 10c0/9ae275e31dd5ec51245773b6d90fda16d0b7f70d59f3a71aec732814b5aedf08aedc7fcce57739e7e89d9e1479ef97e3a202a542a511d732cf5e8b5d1c293870 +"workbox-sw@npm:7.4.0": + version: 7.4.0 + resolution: "workbox-sw@npm:7.4.0" + checksum: 10c0/f87198a9f40da34e13f896ea308c5feedf9caf3ba10b2d40c301850396edf17cdba060775b224fc07697356858816af9912997b751041a715b6934ab14b35300 languageName: node linkType: hard -"workbox-window@npm:7.3.0, workbox-window@npm:^7.3.0": - version: 7.3.0 - resolution: "workbox-window@npm:7.3.0" +"workbox-window@npm:7.4.0, workbox-window@npm:^7.3.0, workbox-window@npm:^7.4.0": + version: 7.4.0 + resolution: "workbox-window@npm:7.4.0" dependencies: "@types/trusted-types": "npm:^2.0.2" - workbox-core: "npm:7.3.0" - checksum: 10c0/dbda33c4761ec40051cfe6e3f1701b2381b4f3b191f7a249c32f683503ea35cf8b42d1f99df5ba3b693fac78705d8ed0c191488bdd178c525d1291d0161ec8ff + workbox-core: "npm:7.4.0" + checksum: 10c0/6013d1019fe3bbb06d8e572b6a4ccfec6fc0879448337997c193fde08738c2fa420a4dca385b00220ef1feac3539e82b01896c85f77cd8c45cff379e7a6cb1d5 languageName: node linkType: hard @@ -14521,7 +15022,7 @@ __metadata: languageName: node linkType: hard -"ws@npm:^8.12.1, ws@npm:^8.18.0, ws@npm:^8.18.2": +"ws@npm:^8.12.1, ws@npm:^8.18.0, ws@npm:^8.18.3": version: 8.18.3 resolution: "ws@npm:8.18.3" peerDependencies: @@ -14536,6 +15037,15 @@ __metadata: languageName: node linkType: hard +"wsl-utils@npm:^0.1.0": + version: 0.1.0 + resolution: "wsl-utils@npm:0.1.0" + dependencies: + is-wsl: "npm:^3.1.0" + checksum: 10c0/44318f3585eb97be994fc21a20ddab2649feaf1fbe893f1f866d936eea3d5f8c743bec6dc02e49fbdd3c0e69e9b36f449d90a0b165a4f47dd089747af4cf2377 + languageName: node + linkType: hard + "xml-name-validator@npm:^5.0.0": version: 5.0.0 resolution: "xml-name-validator@npm:5.0.0" @@ -14585,7 +15095,14 @@ __metadata: languageName: node linkType: hard -"yaml@npm:2.8.1": +"yaml@npm:^1.10.0, yaml@npm:^1.10.2": + version: 1.10.2 + resolution: "yaml@npm:1.10.2" + checksum: 10c0/5c28b9eb7adc46544f28d9a8d20c5b3cb1215a886609a2fd41f51628d8aaa5878ccd628b755dbcd29f6bb4921bd04ffbc6dcc370689bb96e594e2f9813d2605f + languageName: node + linkType: hard + +"yaml@npm:^2.8.1": version: 2.8.1 resolution: "yaml@npm:2.8.1" bin: @@ -14594,13 +15111,6 @@ __metadata: languageName: node linkType: hard -"yaml@npm:^1.10.0": - version: 1.10.2 - resolution: "yaml@npm:1.10.2" - checksum: 10c0/5c28b9eb7adc46544f28d9a8d20c5b3cb1215a886609a2fd41f51628d8aaa5878ccd628b755dbcd29f6bb4921bd04ffbc6dcc370689bb96e594e2f9813d2605f - languageName: node - linkType: hard - "yargs-parser@npm:^21.1.1": version: 21.1.1 resolution: "yargs-parser@npm:21.1.1" @@ -14630,13 +15140,6 @@ __metadata: languageName: node linkType: hard -"yocto-queue@npm:^1.0.0": - version: 1.2.1 - resolution: "yocto-queue@npm:1.2.1" - checksum: 10c0/5762caa3d0b421f4bdb7a1926b2ae2189fc6e4a14469258f183600028eb16db3e9e0306f46e8ebf5a52ff4b81a881f22637afefbef5399d6ad440824e9b27f9f - languageName: node - linkType: hard - "yoctocolors-cjs@npm:^2.1.2": version: 2.1.2 resolution: "yoctocolors-cjs@npm:2.1.2" @@ -14650,3 +15153,19 @@ __metadata: checksum: 10c0/2d110bfcb0f8b8dbf225423f6556da9c5bca95c8b849c1218983676158a24b5cd0350357e0c4d504e27f8c7e18d471d9712576f35114a81a51bcf83453f02beb languageName: node linkType: hard + +"zod-validation-error@npm:^3.5.0 || ^4.0.0": + version: 4.0.2 + resolution: "zod-validation-error@npm:4.0.2" + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + checksum: 10c0/0ccfec48c46de1be440b719cd02044d4abb89ed0e14c13e637cd55bf29102f67ccdba373f25def0fc7130e5f15025be4d557a7edcc95d5a3811599aade689e1b + languageName: node + linkType: hard + +"zod@npm:^3.25.0 || ^4.0.0": + version: 4.1.12 + resolution: "zod@npm:4.1.12" + checksum: 10c0/b64c1feb19e99d77075261eaf613e0b2be4dfcd3551eff65ad8b4f2a079b61e379854d066f7d447491fcf193f45babd8095551a9d47973d30b46b6d8e2c46774 + languageName: node + linkType: hard