Merge pull request #3025 from ClearlyClaire/glitch-soc/merge-upstream

Merge upstream changes up to e8270e2807
This commit is contained in:
Claire 2025-04-01 22:46:36 +02:00 committed by GitHub
commit a26a32cd37
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
46 changed files with 1233 additions and 980 deletions

View file

@ -1,13 +0,0 @@
/build/**
/coverage/**
/db/**
/lib/**
/log/**
/node_modules/**
/nonobox/**
/public/**
!/public/embed.js
/spec/**
/tmp/**
/vendor/**
!.eslintrc.js

View file

@ -1,384 +0,0 @@
// @ts-check
const { defineConfig } = require('eslint-define-config');
module.exports = defineConfig({
root: true,
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'plugin:jsx-a11y/recommended',
'plugin:import/recommended',
'plugin:promise/recommended',
'plugin:jsdoc/recommended',
],
env: {
browser: true,
node: true,
es6: true,
},
parser: '@typescript-eslint/parser',
plugins: [
'react',
'jsx-a11y',
'import',
'promise',
'@typescript-eslint',
'formatjs',
],
parserOptions: {
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 2021,
requireConfigFile: false,
babelOptions: {
configFile: false,
presets: ['@babel/react', '@babel/env'],
},
},
settings: {
react: {
version: 'detect',
},
'import/ignore': [
'node_modules',
'\\.(css|scss|json)$',
],
'import/resolver': {
typescript: {},
},
},
rules: {
'consistent-return': 'error',
'dot-notation': 'error',
eqeqeq: ['error', 'always', { 'null': 'ignore' }],
'indent': ['error', 2],
'jsx-quotes': ['error', 'prefer-single'],
'semi': ['error', 'always'],
'no-catch-shadow': 'error',
'no-console': [
'warn',
{
allow: [
'error',
'warn',
],
},
],
'no-empty': ['error', { "allowEmptyCatch": true }],
'no-restricted-properties': [
'error',
{ property: 'substring', message: 'Use .slice instead of .substring.' },
{ property: 'substr', message: 'Use .slice instead of .substr.' },
],
'no-restricted-syntax': [
'error',
{
// eslint-disable-next-line no-restricted-syntax
selector: 'Literal[value=/•/], JSXText[value=/•/]',
// eslint-disable-next-line no-restricted-syntax
message: "Use '·' (middle dot) instead of '•' (bullet)",
},
],
'no-unused-expressions': 'error',
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{
vars: 'all',
args: 'after-used',
destructuredArrayIgnorePattern: '^_',
ignoreRestSiblings: true,
},
],
'valid-typeof': 'error',
'react/jsx-filename-extension': ['error', { extensions: ['.jsx', 'tsx'] }],
'react/jsx-boolean-value': 'error',
'react/display-name': 'off',
'react/jsx-fragments': ['error', 'syntax'],
'react/jsx-equals-spacing': 'error',
'react/jsx-no-bind': 'error',
'react/jsx-no-useless-fragment': 'error',
'react/jsx-no-target-blank': ['error', { allowReferrer: true }],
'react/jsx-tag-spacing': 'error',
'react/jsx-uses-react': 'off', // not needed with new JSX transform
'react/jsx-wrap-multilines': 'error',
'react/react-in-jsx-scope': 'off', // not needed with new JSX transform
'react/self-closing-comp': 'error',
// recommended values found in https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/v6.8.0/src/index.js#L46
'jsx-a11y/click-events-have-key-events': 'off',
'jsx-a11y/label-has-associated-control': 'off',
'jsx-a11y/media-has-caption': 'off',
'jsx-a11y/no-autofocus': 'off',
// recommended rule is:
// 'jsx-a11y/no-interactive-element-to-noninteractive-role': [
// 'error',
// {
// tr: ['none', 'presentation'],
// canvas: ['img'],
// },
// ],
'jsx-a11y/no-interactive-element-to-noninteractive-role': 'off',
// recommended rule is:
// 'jsx-a11y/no-noninteractive-tabindex': [
// 'error',
// {
// tags: [],
// roles: ['tabpanel'],
// allowExpressionValues: true,
// },
// ],
'jsx-a11y/no-noninteractive-tabindex': 'off',
// recommended is full 'error'
'jsx-a11y/no-static-element-interactions': [
'warn',
{
handlers: [
'onClick',
],
},
],
// See https://github.com/import-js/eslint-plugin-import/blob/v2.29.1/config/recommended.js
'import/extensions': [
'error',
'always',
{
js: 'never',
jsx: 'never',
mjs: 'never',
ts: 'never',
tsx: 'never',
},
],
'import/first': 'error',
'import/newline-after-import': 'error',
'import/no-anonymous-default-export': 'error',
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: [
'.eslintrc.js',
'config/webpack/**',
'app/javascript/mastodon/performance.js',
'app/javascript/mastodon/test_setup.js',
'app/javascript/**/__tests__/**',
],
},
],
'import/no-amd': 'error',
'import/no-commonjs': 'error',
'import/no-import-module-exports': 'error',
'import/no-relative-packages': 'error',
'import/no-self-import': 'error',
'import/no-useless-path-segments': 'error',
'import/no-webpack-loader-syntax': 'error',
'import/order': [
'error',
{
alphabetize: { order: 'asc' },
'newlines-between': 'always',
groups: [
'builtin',
'external',
'internal',
'parent',
['index', 'sibling'],
'object',
],
pathGroups: [
// React core packages
{
pattern: '{react,react-dom,react-dom/client,prop-types}',
group: 'builtin',
position: 'after',
},
// I18n
{
pattern: '{react-intl,intl-messageformat}',
group: 'builtin',
position: 'after',
},
// Common React utilities
{
pattern: '{classnames,react-helmet,react-router,react-router-dom}',
group: 'external',
position: 'before',
},
// Immutable / Redux / data store
{
pattern: '{immutable,@reduxjs/toolkit,react-redux,react-immutable-proptypes,react-immutable-pure-component}',
group: 'external',
position: 'before',
},
// Internal packages
{
pattern: '{mastodon/**}',
group: 'internal',
position: 'after',
},
{
pattern: '{flavours/glitch-soc/**}',
group: 'internal',
position: 'after',
},
],
pathGroupsExcludedImportTypes: [],
},
],
// Forbid imports from vanilla in glitch flavour
'import/no-restricted-paths': [
'error',
{
zones: [{
target: 'app/javascript/flavours/glitch/',
from: 'app/javascript/mastodon/',
message: 'Import from /flavours/glitch/ instead'
}]
}
],
'promise/always-return': 'off',
'promise/catch-or-return': [
'error',
{
allowFinally: true,
},
],
'promise/no-callback-in-promise': 'off',
'promise/no-nesting': 'off',
'promise/no-promise-in-callback': 'off',
'formatjs/blocklist-elements': 'error',
'formatjs/enforce-default-message': ['error', 'literal'],
'formatjs/enforce-description': 'off', // description values not currently used
'formatjs/enforce-id': 'off', // Explicit IDs are used in the project
'formatjs/enforce-placeholders': 'off', // Issues in short_number.jsx
'formatjs/enforce-plural-rules': 'error',
'formatjs/no-camel-case': 'off', // disabledAccount is only non-conforming
'formatjs/no-complex-selectors': 'error',
'formatjs/no-emoji': 'error',
'formatjs/no-id': 'off', // IDs are used for translation keys
'formatjs/no-invalid-icu': 'error',
'formatjs/no-literal-string-in-jsx': 'off', // Should be looked at, but mainly flagging punctuation outside of strings
'formatjs/no-multiple-whitespaces': 'error',
'formatjs/no-offset': 'error',
'formatjs/no-useless-message': 'error',
'formatjs/prefer-formatted-message': 'error',
'formatjs/prefer-pound-in-plural': 'error',
'jsdoc/check-types': 'off',
'jsdoc/no-undefined-types': 'off',
'jsdoc/require-jsdoc': 'off',
'jsdoc/require-param-description': 'off',
'jsdoc/require-property-description': 'off',
'jsdoc/require-returns-description': 'off',
'jsdoc/require-returns': 'off',
},
overrides: [
{
files: [
'.eslintrc.js',
'*.config.js',
'.*rc.js',
'ide-helper.js',
'config/webpack/**/*',
'config/formatjs-formatter.js',
],
env: {
commonjs: true,
},
parserOptions: {
sourceType: 'script',
},
rules: {
'import/no-commonjs': 'off',
},
},
{
files: [
'**/*.ts',
'**/*.tsx',
],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/strict-type-checked',
'plugin:@typescript-eslint/stylistic-type-checked',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'plugin:jsx-a11y/recommended',
'plugin:import/recommended',
'plugin:import/typescript',
'plugin:promise/recommended',
'plugin:jsdoc/recommended-typescript',
],
parserOptions: {
projectService: true,
tsconfigRootDir: __dirname,
},
rules: {
// Disable formatting rules that have been enabled in the base config
'indent': 'off',
// This is not needed as we use noImplicitReturns, which handles this in addition to understanding types
'consistent-return': 'off',
'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
'@typescript-eslint/consistent-type-definitions': ['warn', 'interface'],
'@typescript-eslint/consistent-type-exports': 'error',
'@typescript-eslint/consistent-type-imports': 'error',
"@typescript-eslint/prefer-nullish-coalescing": ['error', { ignorePrimitives: { boolean: true } }],
"@typescript-eslint/no-restricted-imports": [
"warn",
{
"name": "react-redux",
"importNames": ["useSelector", "useDispatch"],
"message": "Use typed hooks `useAppDispatch` and `useAppSelector` instead."
}
],
"@typescript-eslint/restrict-template-expressions": ['warn', { allowNumber: true }],
'jsdoc/require-jsdoc': 'off',
// Those rules set stricter rules for TS files
// to enforce better practices when converting from JS
'import/no-default-export': 'warn',
'react/prefer-stateless-function': 'warn',
'react/function-component-definition': ['error', { namedComponents: 'arrow-function' }],
'react/jsx-uses-react': 'off', // not needed with new JSX transform
'react/react-in-jsx-scope': 'off', // not needed with new JSX transform
'react/prop-types': 'off',
},
},
{
files: [
'**/__tests__/*.js',
'**/__tests__/*.jsx',
],
env: {
jest: true,
},
}
],
});

View file

@ -97,7 +97,13 @@
{
// Group all eslint-related packages with `eslint` in the same PR
matchManagers: ['npm'],
matchPackageNames: ['eslint', 'eslint-*', '@typescript-eslint/*'],
matchPackageNames: [
'eslint',
'eslint-*',
'typescript-eslint',
'@eslint/*',
'globals',
],
matchUpdateTypes: ['patch', 'minor'],
groupName: 'eslint (non-major)',
},

View file

@ -11,7 +11,7 @@ on:
- 'tsconfig.json'
- '.nvmrc'
- '.prettier*'
- '.eslint*'
- 'eslint.config.mjs'
- '**/*.js'
- '**/*.jsx'
- '**/*.ts'
@ -25,7 +25,7 @@ on:
- 'tsconfig.json'
- '.nvmrc'
- '.prettier*'
- '.eslint*'
- 'eslint.config.mjs'
- '**/*.js'
- '**/*.jsx'
- '**/*.ts'
@ -44,7 +44,7 @@ jobs:
uses: ./.github/actions/setup-javascript
- name: ESLint
run: yarn lint:js --max-warnings 0
run: yarn workspaces foreach --all --parallel run lint:js --max-warnings 0
- name: Typecheck
run: yarn typecheck

View file

@ -1,4 +1,4 @@
module.exports = {
singleQuote: true,
jsxSingleQuote: true
}
};

View file

@ -404,7 +404,7 @@ GEM
llhttp-ffi (0.5.1)
ffi-compiler (~> 1.0)
rake (~> 13.0)
logger (1.6.6)
logger (1.7.0)
lograge (0.14.0)
actionpack (>= 4)
activesupport (>= 4)
@ -446,7 +446,7 @@ GEM
net-smtp (0.5.1)
net-protocol
nio4r (2.7.4)
nokogiri (1.18.6)
nokogiri (1.18.7)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
oj (3.16.10)
@ -586,7 +586,7 @@ GEM
ox (2.14.22)
bigdecimal (>= 3.0)
parallel (1.26.3)
parser (3.3.7.3)
parser (3.3.7.4)
ast (~> 2.4.1)
racc
parslet (2.0.0)
@ -760,15 +760,15 @@ GEM
rubocop-i18n (3.2.3)
lint_roller (~> 1.1)
rubocop (>= 1.72.1)
rubocop-performance (1.24.0)
rubocop-performance (1.25.0)
lint_roller (~> 1.1)
rubocop (>= 1.72.1, < 2.0)
rubocop (>= 1.75.0, < 2.0)
rubocop-ast (>= 1.38.0, < 2.0)
rubocop-rails (2.30.3)
rubocop-rails (2.31.0)
activesupport (>= 4.2.0)
lint_roller (~> 1.1)
rack (>= 1.1)
rubocop (>= 1.72.1, < 2.0)
rubocop (>= 1.75.0, < 2.0)
rubocop-ast (>= 1.38.0, < 2.0)
rubocop-rspec (3.5.0)
lint_roller (~> 1.1)

View file

@ -68,7 +68,7 @@ function loaded() {
if (id) message = localeData[id];
if (!message) message = defaultMessage as string;
message ??= defaultMessage as string;
const messageFormat = new IntlMessageFormat(message, locale);
return messageFormat.format(values) as string;

View file

@ -1,4 +1,4 @@
import React from 'react';
import type React from 'react';
import { FormattedMessage } from 'react-intl';

View file

@ -1,5 +1,5 @@
import type { PropsWithChildren } from 'react';
import React from 'react';
import type React from 'react';
import { Router as OriginalRouter, useHistory } from 'react-router';

View file

@ -68,7 +68,7 @@ function loaded() {
if (id) message = localeData[id];
if (!message) message = defaultMessage as string;
message ??= defaultMessage as string;
const messageFormat = new IntlMessageFormat(message, locale);
return messageFormat.format(values) as string;

View file

@ -1,5 +1,3 @@
/* eslint-disable import/no-commonjs --
We need to use CommonJS here due to preval */
// @preval
// http://www.unicode.org/Public/emoji/5.0/emoji-test.txt
// This file contains the compressed version of the emoji data from

View file

@ -33,11 +33,8 @@ function processEmojiMapData(
shortCode?: ShortCodesToEmojiDataKey,
) {
const [native, _filename] = emojiMapData;
let filename = emojiMapData[1];
if (!filename) {
// filename name can be derived from unicodeToFilename
filename = unicodeToFilename(native);
}
// filename name can be derived from unicodeToFilename
const filename = emojiMapData[1] ?? unicodeToFilename(native);
unicodeMapping[native] = {
shortCode,
filename,

View file

@ -1,6 +1,3 @@
/* eslint-disable import/no-commonjs --
We need to use CommonJS here as its imported into a preval file (`emoji_compressed.js`) */
// taken from:
// https://github.com/twitter/twemoji/blob/47732c7/twemoji-generator.js#L848-L866
exports.unicodeToFilename = (str) => {

View file

@ -1,6 +1,3 @@
/* eslint-disable import/no-commonjs --
We need to use CommonJS here as its imported into a preval file (`emoji_compressed.js`) */
function padLeft(str, num) {
while (str.length < num) {
str = '0' + str;

View file

@ -4,7 +4,7 @@
@typescript-eslint/no-unsafe-assignment */
import type { CSSProperties } from 'react';
import React, { useState, useRef, useCallback } from 'react';
import { useState, useRef, useCallback } from 'react';
import { FormattedMessage } from 'react-intl';

View file

@ -1,4 +1,4 @@
import React from 'react';
import type React from 'react';
import { FormattedMessage } from 'react-intl';

View file

@ -1,5 +1,5 @@
import type { PropsWithChildren } from 'react';
import React from 'react';
import type React from 'react';
import { Router as OriginalRouter, useHistory } from 'react-router';

View file

@ -9,48 +9,39 @@ import { reduceMotion } from 'mastodon/initial_state';
interface UploadProgressProps {
active: boolean;
progress: number;
isProcessing: boolean;
isProcessing?: boolean;
}
export const UploadProgress: React.FC<UploadProgressProps> = ({
active,
progress,
isProcessing,
isProcessing = false,
}) => {
const styles = useSpring({
from: { width: '0%' },
to: { width: `${progress}%` },
reset: true,
immediate: reduceMotion,
immediate: reduceMotion || !active, // If this is not active, update the UI immediately.
});
if (!active) {
return null;
}
let message;
if (isProcessing) {
message = (
<FormattedMessage
id='upload_progress.processing'
defaultMessage='Processing…'
/>
);
} else {
message = (
<FormattedMessage
id='upload_progress.label'
defaultMessage='Uploading…'
/>
);
}
return (
<div className='upload-progress'>
<Icon id='upload' icon={UploadFileIcon} />
<div className='upload-progress__message'>
{message}
{isProcessing ? (
<FormattedMessage
id='upload_progress.processing'
defaultMessage='Processing…'
/>
) : (
<FormattedMessage
id='upload_progress.label'
defaultMessage='Uploading…'
/>
)}
<div className='upload-progress__backdrop'>
<animated.div className='upload-progress__tracker' style={styles} />

View file

@ -1,5 +1,3 @@
/* eslint-disable import/no-commonjs --
We need to use CommonJS here due to preval */
// @preval
// http://www.unicode.org/Public/emoji/5.0/emoji-test.txt
// This file contains the compressed version of the emoji data from

View file

@ -33,11 +33,8 @@ function processEmojiMapData(
shortCode?: ShortCodesToEmojiDataKey,
) {
const [native, _filename] = emojiMapData;
let filename = emojiMapData[1];
if (!filename) {
// filename name can be derived from unicodeToFilename
filename = unicodeToFilename(native);
}
// filename name can be derived from unicodeToFilename
const filename = emojiMapData[1] ?? unicodeToFilename(native);
unicodeMapping[native] = {
shortCode,
filename,

View file

@ -1,6 +1,3 @@
/* eslint-disable import/no-commonjs --
We need to use CommonJS here as its imported into a preval file (`emoji_compressed.js`) */
// taken from:
// https://github.com/twitter/twemoji/blob/47732c7/twemoji-generator.js#L848-L866
exports.unicodeToFilename = (str) => {

View file

@ -1,6 +1,3 @@
/* eslint-disable import/no-commonjs --
We need to use CommonJS here as its imported into a preval file (`emoji_compressed.js`) */
function padLeft(str, num) {
while (str.length < num) {
str = '0' + str;

View file

@ -911,6 +911,6 @@
"video.skip_backward": "Siirry taaksepäin",
"video.skip_forward": "Siirry eteenpäin",
"video.unmute": "Poista mykistys",
"video.volume_down": "Laske äänenvoimakkuutta",
"video.volume_up": "Nosta äänenvoimakkuutta"
"video.volume_down": "Vähennä äänenvoimakkuutta",
"video.volume_up": "Lisää äänenvoimakkuutta"
}

View file

@ -905,6 +905,12 @@
"video.expand": "동영상 확장",
"video.fullscreen": "전체화면",
"video.hide": "동영상 숨기기",
"video.mute": "음소거",
"video.pause": "일시정지",
"video.play": "재생"
"video.play": "재생",
"video.skip_backward": "뒤로 건너뛰기",
"video.skip_forward": "앞으로 건너뛰기",
"video.unmute": "음소거 해제",
"video.volume_down": "음량 감소",
"video.volume_up": "음량 증가"
}

View file

@ -697,6 +697,7 @@
"poll_button.remove_poll": "Remover enquete",
"privacy.change": "Alterar privacidade do toot",
"privacy.direct.long": "Todos mencionados na publicação",
"privacy.direct.short": "Menção privada",
"privacy.private.long": "Apenas seus seguidores",
"privacy.private.short": "Seguidores",
"privacy.public.long": "Qualquer um dentro ou fora do Mastodon",
@ -871,7 +872,9 @@
"subscribed_languages.target": "Alterar idiomas inscritos para {target}",
"tabs_bar.home": "Página inicial",
"tabs_bar.notifications": "Notificações",
"terms_of_service.effective_as_of": "Em vigor a partir de {date}",
"terms_of_service.title": "Termos de serviço",
"terms_of_service.upcoming_changes_on": "Próximas mudanças em {date}",
"time_remaining.days": "{number, plural, one {# dia restante} other {# dias restantes}}",
"time_remaining.hours": "{number, plural, one {# hora restante} other {# horas restantes}}",
"time_remaining.minutes": "{number, plural, one {# minuto restante} other {# minutos restantes}}",
@ -902,6 +905,12 @@
"video.expand": "Abrir vídeo",
"video.fullscreen": "Tela cheia",
"video.hide": "Ocultar mídia",
"video.mute": "Silenciar",
"video.pause": "Pausar",
"video.play": "Executar"
"video.play": "Executar",
"video.skip_backward": "Retroceder",
"video.skip_forward": "Avançar",
"video.unmute": "Ativar som",
"video.volume_down": "Diminuir o volume",
"video.volume_up": "Aumentar o volume"
}

View file

@ -156,7 +156,7 @@
"column.firehose": "Живая лента",
"column.follow_requests": "Запросы на подписку",
"column.home": "Главная",
"column.list_members": "Управление пользователями в списке",
"column.list_members": "Добавить или удалить из списка",
"column.lists": "Списки",
"column.mutes": "Игнорируемые пользователи",
"column.notifications": "Уведомления",
@ -173,7 +173,7 @@
"column_subheading.settings": "Настройки",
"community.column_settings.local_only": "Только локальные",
"community.column_settings.media_only": "Только с медиафайлами",
"community.column_settings.remote_only": "Только удалённые",
"community.column_settings.remote_only": "Только с других серверов",
"compose.language.change": "Изменить язык",
"compose.language.search": "Найти язык...",
"compose.published.body": "Пост опубликован.",
@ -181,7 +181,7 @@
"compose.saved.body": "Пост отредактирован.",
"compose_form.direct_message_warning_learn_more": "Узнать больше",
"compose_form.encryption_warning": "Посты в Mastodon не защищены сквозным шифрованием. Не делитесь конфиденциальной информацией через Mastodon.",
"compose_form.hashtag_warning": "Этот пост не будет виден ни под одним из хэштегов, так как он не публичный. Только публичные посты можно найти по хэштегу.",
"compose_form.hashtag_warning": "Этот пост не появится в поиске по хэштегам, так как он не обозначен как публичный. Только публичные посты можно найти по хэштегу.",
"compose_form.lock_disclaimer": "Ваша учётная запись {locked}. Любой пользователь сможет подписаться на вас и просматривать посты для подписчиков.",
"compose_form.lock_disclaimer.lock": "не закрыта",
"compose_form.placeholder": "О чём думаете?",
@ -189,8 +189,8 @@
"compose_form.poll.multiple": "Несколько вариантов ответа",
"compose_form.poll.option_placeholder": "Вариант {number}",
"compose_form.poll.single": "Один вариант ответа",
"compose_form.poll.switch_to_multiple": "Разрешить выбор нескольких вариантов",
"compose_form.poll.switch_to_single": "Переключить в режим выбора одного ответа",
"compose_form.poll.switch_to_multiple": "Переключить в режим выбора нескольких вариантов ответа",
"compose_form.poll.switch_to_single": "Переключить в режим выбора одного варианта ответа",
"compose_form.poll.type": "Тип",
"compose_form.publish": "Опубликовать",
"compose_form.publish_form": "Опубликовать",
@ -208,11 +208,11 @@
"confirmations.delete_list.message": "Вы уверены, что хотите навсегда удалить этот список?",
"confirmations.delete_list.title": "Удалить список?",
"confirmations.discard_edit_media.confirm": "Сбросить",
"confirmations.discard_edit_media.message": "У вас есть несохранённые изменения в описании мультимедиа или предпросмотре, сбросить их?",
"confirmations.discard_edit_media.message": "У вас есть несохранённые изменения, касающиеся описания медиа или области предпросмотра, сбросить их?",
"confirmations.edit.confirm": "Редактировать",
"confirmations.edit.message": "Если вы начнёте редактировать сейчас, то набираемый в данный момент пост будет стёрт. Вы уверены, что хотите продолжить?",
"confirmations.edit.title": "Стереть несохранённый черновик поста?",
"confirmations.follow_to_list.confirm": "Подписаться, а затем добавить в список",
"confirmations.follow_to_list.confirm": "Подписаться и добавить",
"confirmations.follow_to_list.message": "Чтобы добавить пользователя {name} в список, вы должны быть на него подписаны.",
"confirmations.follow_to_list.title": "Подписаться на пользователя?",
"confirmations.logout.confirm": "Выйти",
@ -224,8 +224,8 @@
"confirmations.missing_alt_text.title": "Добавить альтернативный текст?",
"confirmations.mute.confirm": "Игнорировать",
"confirmations.redraft.confirm": "Удалить и исправить",
"confirmations.redraft.message": "Вы уверены, что хотите удалить и переписать этот пост? Отметки «избранного», продвижения и ответы к оригинальному посту будут потеряны.",
"confirmations.redraft.title": "Создать пост заново?",
"confirmations.redraft.message": "Вы уверены, что хотите удалить этот пост и создать его заново? Взаимодействия, такие как добавление в избранное или продвижение, будут потеряны, а ответы к оригинальному посту перестанут на него ссылаться.",
"confirmations.redraft.title": "Удалить и создать пост заново?",
"confirmations.reply.confirm": "Ответить",
"confirmations.reply.message": "Если вы начнёте составлять ответ сейчас, то набираемый в данный момент пост будет стёрт. Вы уверены, что хотите продолжить?",
"confirmations.reply.title": "Стереть несохранённый черновик поста?",
@ -293,7 +293,7 @@
"emoji_button.search_results": "Результаты поиска",
"emoji_button.symbols": "Символы",
"emoji_button.travel": "Путешествия и места",
"empty_column.account_hides_collections": "Данный пользователь решил не предоставлять эту информацию",
"empty_column.account_hides_collections": "Пользователь предпочёл не раскрывать эту информацию",
"empty_column.account_suspended": "Учётная запись заблокирована",
"empty_column.account_timeline": "Здесь нет постов!",
"empty_column.account_unavailable": "Профиль недоступен",
@ -409,8 +409,9 @@
"home.show_announcements": "Показать объявления",
"ignore_notifications_modal.disclaimer": "Mastodon не может сообщить пользователям, что вы игнорируете их уведомления. Игнорирование уведомлений не остановит отправку самих сообщений.",
"ignore_notifications_modal.filter_instead": "Фильтровать",
"ignore_notifications_modal.filter_to_avoid_confusion": "Фильтрация помогает избежать потенциальной путаницы",
"ignore_notifications_modal.filter_to_review_separately": "Вы можете просматривать отфильтрованные уведомления отдельно",
"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": "Игнорировать уведомления",
"ignore_notifications_modal.limited_accounts_title": "Игнорировать уведомления от модерируемых учётных записей?",
"ignore_notifications_modal.new_accounts_title": "Игнорировать уведомления от новых учётных записей?",

View file

@ -113,6 +113,7 @@ function clearAll(state) {
map.set('sensitive', state.get('default_sensitive'));
map.set('language', state.get('default_language'));
map.update('media_attachments', list => list.clear());
map.set('progress', 0);
map.set('poll', null);
map.set('idempotencyKey', uuid());
});
@ -128,6 +129,7 @@ function appendMedia(state, media, file) {
map.update('media_attachments', list => list.push(media.set('unattached', true)));
map.set('is_uploading', false);
map.set('is_processing', false);
map.set('progress', 0);
map.set('resetFileKey', Math.floor((Math.random() * 0x10000)));
map.set('idempotencyKey', uuid());
map.update('pending_media_attachments', n => n - 1);
@ -296,6 +298,8 @@ const updatePoll = (state, index, value, maxOptions) => state.updateIn(['poll',
return tmp;
});
const calculateProgress = (loaded, total) => Math.min(Math.round((loaded / total) * 100), 100);
/** @type {import('@reduxjs/toolkit').Reducer<typeof initialState>} */
export const composeReducer = (state = initialState, action) => {
if (changeUploadCompose.fulfilled.match(action)) {
@ -402,15 +406,19 @@ export const composeReducer = (state = initialState, action) => {
case COMPOSE_UPLOAD_SUCCESS:
return appendMedia(state, fromJS(action.media), action.file);
case COMPOSE_UPLOAD_FAIL:
return state.set('is_uploading', false).set('is_processing', false).update('pending_media_attachments', n => n - 1);
return state
.set('is_uploading', false)
.set('is_processing', false)
.set('progress', 0)
.update('pending_media_attachments', n => n - 1);
case COMPOSE_UPLOAD_UNDO:
return removeMedia(state, action.media_id);
case COMPOSE_UPLOAD_PROGRESS:
return state.set('progress', Math.round((action.loaded / action.total) * 100));
return state.set('progress', calculateProgress(action.loaded, action.total));
case THUMBNAIL_UPLOAD_REQUEST:
return state.set('isUploadingThumbnail', true);
case THUMBNAIL_UPLOAD_PROGRESS:
return state.set('thumbnailProgress', Math.round((action.loaded / action.total) * 100));
return state.set('thumbnailProgress', calculateProgress(action.loaded, action.total));
case THUMBNAIL_UPLOAD_FAIL:
return state.set('isUploadingThumbnail', false);
case THUMBNAIL_UPLOAD_SUCCESS:

View file

@ -1,6 +1,3 @@
/* eslint-disable import/no-commonjs --
We need to use CommonJS here as its imported into a preval file (`emoji_compressed.js`) */
/* @preval */
const fs = require('fs');

View file

@ -39,7 +39,7 @@ pt-BR:
import:
attributes:
data:
malformed: Está malformado
malformed: está malformado
list_account:
attributes:
account_id:
@ -49,13 +49,19 @@ pt-BR:
attributes:
reblog:
taken: do toot já existe
terms_of_service:
attributes:
effective_date:
too_soon: está muito próxima, deve ser posterior a %{date}
user:
attributes:
date_of_birth:
below_limit: está abaixo do limite de idade
email:
blocked: usa provedor de e-mail não permitido
unreachable: parece não existir
role_id:
elevated: não pode maior que sua função atual
elevated: não pode ser maior que a sua função atual
user_role:
attributes:
permissions_as_keys:

View file

@ -14,7 +14,7 @@ pt-BR:
not_found_in_database: "%{authentication_keys} ou senha inválida."
omniauth_user_creation_failure: Erro ao criar uma conta para esta identidade.
pending: Sua conta está sendo revisada.
timeout: Sua sessão expirou. Faça login novamente para continuar.
timeout: Sua sessão expirou. Por favor, entre novamente para continuar.
unauthenticated: Você precisa entrar ou criar uma conta antes de continuar.
unconfirmed: Você precisa confirmar o seu endereço de e-mail antes de continuar.
mailer:
@ -48,12 +48,12 @@ pt-BR:
subject: 'Mastodon: Instruções para alterar senha'
title: Redefinir senha
two_factor_disabled:
explanation: O login agora é possível usando apenas o endereço eletrônico e senha.
explanation: Agora você pode entrar usando apenas seu e-mail e senha.
subject: 'Mastodon: Autenticação de dois fatores desativada'
subtitle: A autenticação de dois fatores foi desativada.
title: 2FA desativada
two_factor_enabled:
explanation: Será necessário um código gerado pelo aplicativo de autenticação TOTP para fazer login.
explanation: Um código de autenticação de dois fatores será necessário para entrar em sua conta.
subject: 'Mastodon: Autenticação de dois fatores ativada'
subtitle: A autenticação de dois fatores foi ativada para sua conta.
title: 2FA ativada
@ -75,11 +75,11 @@ pt-BR:
title: Uma das suas chaves de segurança foi excluída
webauthn_disabled:
explanation: A autenticação por chaves de segurança foi desativada para sua conta.
extra: Agora você pode fazer login usando apenas o código gerado pelo aplicativo de autenticação TOTP.
extra: Agora você pode entrar usando apenas o código de autenticação de dois fatores.
subject: 'Mastodon: Autenticação por chaves de segurança desativada'
title: Chaves de segurança desativadas
webauthn_enabled:
explanation: A autenticação por chave de segurança foi ativada para sua conta.
explanation: A autenticação por chave de segurança foi ativada.
extra: Sua chave de segurança agora pode ser usada para login.
subject: 'Mastodon: Autenticação por chaves de segurança ativada'
title: Chaves de segurança ativadas

View file

@ -61,7 +61,7 @@ pt-BR:
title: Ocorreu um erro
new:
prompt_html: "%{client_name} gostaria de permissão para acessar sua conta. <strong>Aprove esta solicitação apenas se você reconhecer e confiar nesta fonte.</strong>"
review_permissions: Rever permissões
review_permissions: Revisar permissões
title: Autorização necessária
show:
title: Copie este código de autorização e cole no aplicativo.
@ -130,7 +130,7 @@ pt-BR:
crypto: Criptografia de ponta a ponta
favourites: Favoritos
filters: Filtros
follow: Seguimentos, Silenciamentos e Bloqueios
follow: Seguidos, Silenciados e Bloqueados
follows: Seguidos
lists: Listas
media: Mídias anexadas
@ -165,7 +165,7 @@ pt-BR:
admin:write:email_domain_blocks: executar ações de moderação em domínios de e-mail bloqueados
admin:write:ip_blocks: executar ações de moderação em IPs bloqueados
admin:write:reports: executar ações de moderação em denúncias
crypto: usar criptografia de ponta-a-ponta
crypto: usar criptografia de ponta a ponta
follow: alterar o relacionamento das contas
profile: ler somente as informações do perfil da sua conta
push: receber notificações push

View file

@ -479,6 +479,16 @@ eo:
new:
title: Importi domajnblokojn
no_file: Neniu dosiero elektita
fasp:
debug:
callbacks:
delete: Forigi
providers:
delete: Forigi
name: Nomo
registrations:
confirm: Konfirmi
sign_in: Ensaluti
follow_recommendations:
description_html: "<strong>Sekvorekomendoj helpi novajn uzantojn rapide trovi interesa enhavo</strong>. Ili rekalkulitas ĉiutage lau interagoj kaj sekvantokvantoj."
language: Por la lingvo

View file

@ -473,6 +473,36 @@ ko:
new:
title: 도메인 차단 불러오기
no_file: 선택된 파일이 없습니다
fasp:
debug:
callbacks:
created_at: 생성일
delete: 삭제
ip: IP 주소
request_body: 요청 바디
title: 디버그 콜백
providers:
active: 활성
base_url: Base URL
callback: 콜백
delete: 삭제
edit: 제공자 편집
finish_registration: 등록 완료
name: 이름
providers: 제공자
public_key_fingerprint: 공개키 핑거프린트
registration_requested: 가입 요청됨
registrations:
confirm: 확인
description: FASP로부터 등록을 받았습니다. 본인이 시작하지 않았다면 거부하세요. 본인이 시작했다면 등록을 확인하기 전에 이름과 키 핑거프린트를 주의 깊게 비교하세요.
reject: 거부
title: FASP 가입 확인
save: 저장
select_capabilities: 권한 선택
sign_in: 가입
status: 상태
title: 연합우주 보조 서비스 제공자
title: FASP
follow_recommendations:
description_html: "<strong>팔로우 추천은 새 사용자들이 관심 가는 콘텐트를 빠르게 찾을 수 있도록 도와줍니다</strong>. 사용자가 개인화 된 팔로우 추천이 만들어지기 위한 충분한 상호작용을 하지 않은 경우, 이 계정들이 대신 추천 됩니다. 이들은 해당 언어에 대해 많은 관심을 갖거나 많은 로컬 팔로워를 가지고 있는 계정들을 섞어서 날마다 다시 계산 됩니다."
language: 언어 필터

View file

@ -957,10 +957,15 @@ lv:
allow: Atļaut
approved: Apstiprināts
confirm_allow: Vai tiešām atļaut atlasītos tēmturus?
confirm_disallow: Vai tiešām neatļaut atlasītās birkas?
disallow: Neatļaut
links:
allow: Atļaut saiti
allow_provider: Atļaut publicētāju
confirm_allow: Vai tiešām atļaut atlasītās saites?
confirm_allow_provider: Vai tiešām atļaut atlasītos nodrošinātājus?
confirm_disallow: Vai tiešām neatļaut atlasītās saites?
confirm_disallow_provider: Vai tiešām neatļaut atlasītos nodrošinātājus?
description_html: Šīs ir saites, kuras pašlaik bieži koplieto konti, no kuriem Tavs serveris redz ziņas. Tas var palīdzēt Taviem lietotājiem uzzināt, kas notiek pasaulē. Neviena saite netiek publiski rādīta, līdz tu apstiprini izdevēju. Tu vari arī atļaut vai noraidīt atsevišķas saites.
disallow: Neatļaut saiti
disallow_provider: Neatļaut publicētāju
@ -985,6 +990,10 @@ lv:
statuses:
allow: Ļaut veikt ierakstus
allow_account: Atļaut autoru
confirm_allow: Vai tiešām atļaut atlasītos stāvokļus?
confirm_allow_account: Vai tiešām atļaut atlasītos kontus?
confirm_disallow: Vai tiešām neatļaut atlasītos stāvokļus?
confirm_disallow_account: Vai tiešām neatļaut atlasītos kontus?
description_html: Šie ir ieraksti, par kuriem zina Tavs serveris un kuri pašlaik tiek daudz kopīgoti un pievienoti izlasēm. Tas var palīdzēt jaunajiem lietotājiem un tiem, kuri atgriežas, atrast vairāk cilvēku, kam sekot. Neviens ieraksts netiek publiski rādīts, līdz apstiprināsi autoru un ja autors ļauj savu kontu ieteikt citiem. Tu vari arī atļaut vai noraidīt atsevišķus ierakstus.
disallow: Neļaut veikt ierakstus
disallow_account: Neatļaut autoru

View file

@ -142,7 +142,7 @@ pt-BR:
only_password: Apenas senha
password_and_2fa: Senha e autenticação de dois fatores
sensitive: Sensíveis
sensitized: marcadas como sensíveis
sensitized: Marcadas como sensíveis
shared_inbox_url: Link da caixa de entrada compartilhada
show:
created_reports: Denúncias criadas
@ -309,6 +309,7 @@ pt-BR:
title: Auditar histórico
unavailable_instance: "(nome de domínio indisponível)"
announcements:
back: Voltar aos anúncios
destroyed_msg: Anúncio excluído!
edit:
title: Editar anúncio
@ -317,6 +318,8 @@ pt-BR:
new:
create: Criar anúncio
title: Novo anúncio
preview:
explanation_html: 'Esse e-mail será enviado a <strong>%{display_count} usuários</strong>. O texto a seguir será incluído ao e-mail:'
publish: Publicar
published_msg: Anúncio publicado!
scheduled_for: Agendado para %{time}
@ -475,6 +478,25 @@ pt-BR:
new:
title: Importar bloqueio de domínios
no_file: Nenhum arquivo selecionado
fasp:
debug:
callbacks:
created_at: Criado em
delete: Apagar
ip: Endereço de IP
providers:
base_url: URL Base
delete: Apagar
finish_registration: Finalizar o cadastro
name: Nome
public_key_fingerprint: Impressão digital de chave pública
registration_requested: Cadastro solicitado
registrations:
confirm: Confirmar
reject: Rejeitar
save: Salvar
sign_in: Entrar
status: Estado
follow_recommendations:
description_html: "<strong>A recomendação de contas ajuda os novos usuários a encontrar rapidamente conteúdo interessante</strong>. Quando um usuário ainda não tiver interagido o suficiente para gerar recomendações de contas, essas contas serão recomendadas. Essas recomendações são recalculadas diariamente a partir de uma lista de contas com alto engajamento e maior número de seguidores locais em uma dada língua."
language: Na língua
@ -939,6 +961,7 @@ pt-BR:
chance_to_review_html: "<strong>Os termos de serviço gerado não será publicado automaticamente.</strong>Você terá uma chance de revisar os resultados. Preencha os detalhes necessários para continuar"
explanation_html: O modelo de termos de serviço fornecido é apenas para fins informativos e não deve ser interpretado como aconselhamento jurídico sobre qualquer assunto. Consulte seu próprio advogado para esclarecer sua situação e dúvidas jurídicas específicas.
title: Configuração dos Termos de Serviço
going_live_on_html: Em vigor a partir de %{date}
history: Histórico
live: Em vigor
no_history: Ainda não há alterações registradas nos termos de serviço.
@ -1936,6 +1959,8 @@ pt-BR:
terms_of_service_changed:
agreement: Ao continuar a usar %{domain}, você concorda com estes termos. Se discordar dos termos atualizados, poderá encerrar seu acordo com %{domain} a qualquer momento excluindo sua conta.
changelog: 'Em resumo, veja o que essa atualização significa para você:'
description: 'Você recebeu este e-mail porque faremos algumas mudanças nos Termos de Serviço do %{domain}. Essas atualizações entrarão em vigor a partir de %{date}. Veja o que mudou aqui:'
description_html: Você recebeu este e-mail porque faremos algumas mudanças nos Termos de Serviço do %{domain}. Essas atualizações entrarão em vigor a partir de <strong>%{date}</strong>. <a href="%{path}" target="_blank">Veja o que mudou aqui</a>.
sign_off: A equipe do %{domain}
subject: Atualizações dos nossos termos de serviço
subtitle: Os termos de serviço do %{domain} estão mudando.

View file

@ -139,6 +139,7 @@ bg:
terms_of_service_generator:
admin_email: Правните бележки включват насрещни известия, постановления на съда, заявки за сваляне и заявки от правоохранителните органи.
arbitration_address: Може да е същото като физическия адрес горе или "неприложимо", ако се употребява имейл.
arbitration_website: Може да е уеб формуляр или "неналично", ако употребявате имейл.
choice_of_law: Град, регион, територия, щат или държава, чиито вътрешни материални права ще уреждат всички искове.
dmca_email: Може да е същият имейл, използван за "Имейл адрес за правни известия" по-горе.
domain: Неповторимо идентифициране на онлайн услугата, която предоставяте.

View file

@ -8,9 +8,9 @@ pt-BR:
display_name: Seu nome completo ou apelido.
fields: Sua página inicial, pronomes, idade ou qualquer coisa que quiser.
indexable: Suas publicações públicas podem aparecer nos resultados da pesquisa em Mastodon. As pessoas que interagiram com suas publicações podem conseguir pesquisá-las independentemente disso.
note: 'Você pode @mencionar outras pessoas ou #hashtags.'
show_collections: As pessoas poderão navegar entre os seus seguidores e seguidores. As pessoas que você segue verão que você as segue independentemente disso.
unlocked: As pessoas poderão seguir você sem solicitar aprovação. Desmarque caso você queira revisar as solicitações de seguidor e escolha se queira aceitar ou rejeitar novos seguidores.
note: 'Você pode @mencionar outras pessoas ou usar #hashtags.'
show_collections: As pessoas podem ver seus seguidores e quem você está seguindo. Os perfis que você seguir saberão que você os segue independentemente do que selecionar.
unlocked: As pessoas poderão te seguir sem solicitar aprovação. Desmarque caso você queira revisar as solicitações.
account_alias:
acct: Especifique o usuário@domínio de onde veio
account_migration:
@ -27,8 +27,8 @@ pt-BR:
disable: Impede o usuário de usar a conta, porém sem excluí-la ou suspendê-la.
none: Use isto para enviar uma advertência ao usuário, sem nenhuma outra ação.
sensitive: Marca todas as mídias do usuário como sensível.
silence: Impede o usuário de enviar postagens visualmente públicas, além de ocultar suas publicações e notificações dos que não o seguem. Ademais, fecha todas as denúncias contra esta conta.
suspend: Impede qualquer interação de ou para esta conta e exclui seu conteúdo. Reversível dentro de 30 dias. Ademais, fecha todas as denúncias contra esta conta.
silence: Impede o usuário de postar publicamente, restringe suas publicações e notificações a somente quem o segue. Encerra todas as denúncias contra esta conta.
suspend: Impede qualquer interação de ou para esta conta e exclui seu conteúdo. Reversível dentro de 30 dias. Encerra todas as denúncias contra esta conta.
warning_preset_id: Opcional. Você pode adicionar texto personalizado no final da advertência pré-definida
announcement:
all_day: Quando marcada, apenas as datas do período serão mostradas
@ -37,11 +37,11 @@ pt-BR:
starts_at: Opcional. Caso o comunicado esteja vinculado a um período específico
text: Você pode usar a sintaxe do toot. Considere o espaço que o comunicado ocupará na tela do usuário
appeal:
text: Você só pode solicitar uma revisão uma vez
text: Você só pode recorrer uma vez
defaults:
autofollow: Pessoas que criarem conta através de seu convite te seguirão automaticamente
avatar: WEBP, PNG, GIF ou JPG. No máximo %{size}. Será reduzido para %{dimensions}px
bot: Essa conta executa principalmente ações automatizadas e pode não ser monitorada
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
@ -54,7 +54,7 @@ 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_aggregate_reblogs: Não mostra novos impulsos para publicações já receberam recentemente (afeta somente os impulsos mais recentes)
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_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
@ -75,12 +75,13 @@ pt-BR:
filters:
action: Escolher qual ação executar quando uma publicação corresponder ao 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
warn: Ocultar o conteúdo filtrado por trás de um aviso mencionando o título do filtro
form_admin_settings:
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 têm a capacidade de 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.
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.
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.
@ -88,6 +89,7 @@ pt-BR:
favicon: WEBP, PNG, GIF ou JPG. Sobrescreve o favicon padrão do Mastodon com um ícone personalizado.
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
peers_api_enabled: Uma lista de nomes de domínio que este servidor encontrou no "fediverse". Nenhum dado é incluído aqui sobre se você concorda com os padroes operacionais de um determinado servidor, apenas que o seu servidor sabe disso. Esta ferramenta é utilizado por serviços que recolhem estatísticas sob as normas da federação (grupo de empresas que concordam sob paramentros operacionais específicos), em termos gerais.
profile_directory: O diretório de perfis lista todos os usuários que optaram por permitir que suas contas sejam descobertas.
require_invite_text: 'Quando o cadastro de novas contas exigir aprovação manual, tornar obrigatório, ao invés de opcional, o texto de solicitação de convite: "Por que você deseja ingressar nessa comunidade?"'
@ -132,14 +134,21 @@ pt-BR:
name: Você pode mudar a capitalização das letras, por exemplo, para torná-la mais legível
terms_of_service:
changelog: Pode ser estruturado com a sintaxe Markdown.
effective_date: Um intervalo de tempo razoável pode variar de 10 a 30 dias a partir da data em que notificar seus usuários.
text: Pode ser estruturado com a sintaxe Markdown.
terms_of_service_generator:
admin_email: Avisos legais incluem contra-notificações, ordens judiciais, solicitações de remoção e solicitações de órgãos de fiscalização.
arbitration_address: Pode ser o mesmo do endereço colocado cima, ou use "N/A" se estiver utilizando e-mail.
arbitration_website: Pode ser um formulário online, ou use "N/A" caso esteja utilizando um e-mail.
choice_of_law: Cidade, região, território ou estado cujas leis substantivas internas serão aplicáveis a todas e quaisquer reivindicações.
dmca_address: Para operadores dos EUA, utilize o endereço registrado no Diretório de Agentes Designados pela DMCA. Um endereço de Caixa Postal está disponível mediante solicitação direta; use a Solicitação de Isenção de Caixa Postal de Agente Designado pela DMCA para enviar um e-mail ao Escritório de Direitos Autorais, explicando que você é um moderador de conteúdo que trabalha em casa e teme vingança ou retaliação por suas ações, precisando usar uma Caixa Postal para remover seu endereço residencial da visualização pública.
dmca_email: Pode ser o mesmo e-mail utilizado acima em "Endereço de e-mail para avisos legais".
domain: Identificação única do serviço online que você está fornecendo.
jurisdiction: Liste o país onde quem paga as contas reside. Se for uma empresa ou outra entidade, liste o país onde ela está incorporada, e a cidade, região, território ou estado, conforme apropriado.
min_age: Não deve ter menos que a idade mínima exigida pelas suas leis locais.
user:
chosen_languages: Apenas as publicações dos idiomas selecionados serão exibidas nas linhas públicas
date_of_birth: Precisamos ter certeza de que você tem, no mínimo, %{age} anos para usar o Mastodon. Não armazenaremos essa informação.
role: A função controla quais permissões o usuário tem.
user_role:
color: Cor a ser usada para o cargo em toda a interface do usuário, como RGB no formato hexadecimal
@ -252,6 +261,7 @@ pt-BR:
name: Hashtag
filters:
actions:
blur: Oculte a mídia com um aviso
hide: Ocultar completamente
warn: Ocultar com um aviso
form_admin_settings:
@ -265,6 +275,7 @@ pt-BR:
favicon: Favicon
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
@ -330,16 +341,22 @@ pt-BR:
usable: Permitir que as publicações usem esta hashtag localmente
terms_of_service:
changelog: O que mudou?
effective_date: Data de vigência
text: Termos de Serviço
terms_of_service_generator:
admin_email: Endereço de e-mail para avisos legais.
admin_email: Endereço de e-mail para avisos legais
arbitration_address: Endereço físico para avisos de arbitragem.
arbitration_website: Site para submissão de notificações de arbitragem
choice_of_law: Lei de regência
dmca_address: Endereço físico para notificações de DMCA/direitos autorais
dmca_email: Endereço de e-mail para notificações de DMCA/direitos autorais
domain: Domínio
jurisdiction: Jurisdicação legal
jurisdiction: Jurisdição legal
min_age: Idade mínima
user:
date_of_birth_1i: Dia
date_of_birth_2i: Mês
date_of_birth_3i: Ano
role: Cargo
time_zone: Fuso horário
user_role:

419
eslint.config.mjs Normal file
View file

@ -0,0 +1,419 @@
// @ts-check
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';
import promisePlugin from 'eslint-plugin-promise';
import react from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
import globals from 'globals';
import tseslint from 'typescript-eslint';
/** @type {import('typescript-eslint').ConfigArray} */
export const baseConfig = [
js.configs.recommended,
importPlugin.flatConfigs.recommended,
jsdoc.configs['flat/recommended'],
promisePlugin.configs['flat/recommended'],
{
linterOptions: {
reportUnusedDisableDirectives: 'error',
reportUnusedInlineConfigs: 'error',
},
rules: {
'consistent-return': 'error',
'dot-notation': 'error',
eqeqeq: [
'error',
'always',
{
null: 'ignore',
},
],
'no-console': [
'warn',
{
allow: ['error', 'warn'],
},
],
'no-empty': [
'error',
{
allowEmptyCatch: true,
},
],
'no-restricted-properties': [
'error',
{
property: 'substring',
message: 'Use .slice instead of .substring.',
},
{
property: 'substr',
message: 'Use .slice instead of .substr.',
},
],
'no-unused-expressions': 'error',
'no-unused-vars': 'off',
'valid-typeof': 'error',
'import/extensions': [
'error',
'always',
{
js: 'never',
jsx: 'never',
mjs: 'never',
ts: 'never',
mts: 'never',
tsx: 'never',
},
],
'import/first': 'error',
'import/newline-after-import': 'error',
'import/no-anonymous-default-export': 'error',
'import/no-amd': 'error',
'import/no-commonjs': 'error',
'import/no-import-module-exports': 'error',
'import/no-relative-packages': 'error',
'import/no-self-import': 'error',
'import/no-useless-path-segments': 'error',
'import/order': [
'error',
{
alphabetize: {
order: 'asc',
},
'newlines-between': 'always',
groups: [
'builtin',
'external',
'internal',
'parent',
['index', 'sibling'],
'object',
],
pathGroups: [
{
pattern: '{react,react-dom,react-dom/client,prop-types}',
group: 'builtin',
position: 'after',
},
{
pattern: '{react-intl,intl-messageformat}',
group: 'builtin',
position: 'after',
},
{
pattern:
'{classnames,react-helmet,react-router,react-router-dom}',
group: 'external',
position: 'before',
},
{
pattern:
'{immutable,@reduxjs/toolkit,react-redux,react-immutable-proptypes,react-immutable-pure-component}',
group: 'external',
position: 'before',
},
{
pattern: '{mastodon/**}',
group: 'internal',
position: 'after',
},
{
pattern: '{flavours/glitch-soc/**}',
group: 'internal',
position: 'after',
},
],
pathGroupsExcludedImportTypes: [],
},
],
'jsdoc/check-types': 'off',
'jsdoc/no-undefined-types': 'off',
'jsdoc/require-jsdoc': 'off',
'jsdoc/require-param-description': 'off',
'jsdoc/require-property-description': 'off',
'jsdoc/require-returns-description': 'off',
'jsdoc/require-returns': 'off',
// Forbid imports from vanilla in glitch flavour
'import/no-restricted-paths': [
'error',
{
zones: [
{
target: 'app/javascript/flavours/glitch/',
from: 'app/javascript/mastodon/',
message: 'Import from /flavours/glitch/ instead',
},
],
},
],
'promise/always-return': 'off',
'promise/catch-or-return': [
'error',
{
allowFinally: true,
},
],
'promise/no-callback-in-promise': 'off',
'promise/no-nesting': 'off',
'promise/no-promise-in-callback': 'off',
},
},
];
export default tseslint.config([
baseConfig,
globalIgnores([
'build/**/*',
'coverage/**/*',
'db/**/*',
'lib/**/*',
'log/**/*',
'node_modules/**/*',
'public/**/*',
'!public/embed.js',
'spec/**/*',
'tmp/**/*',
'vendor/**/*',
'streaming/**/*',
]),
react.configs.flat.recommended,
react.configs.flat['jsx-runtime'],
reactHooks.configs['recommended-latest'],
jsxA11Y.flatConfigs.recommended,
importPlugin.flatConfigs.react,
// @ts-expect-error -- For some reason the formatjs package exports an empty object?
formatjs.configs.strict,
{
languageOptions: {
globals: {
...globals.browser,
},
parser: tseslint.parser,
ecmaVersion: 2021,
sourceType: 'module',
},
settings: {
react: {
version: 'detect',
},
'import/ignore': ['node_modules', '\\.(css|scss|json)$'],
'import/resolver': {
typescript: {},
},
},
rules: {
'no-restricted-syntax': [
'error',
{
// eslint-disable-next-line no-restricted-syntax
selector: 'Literal[value=/•/], JSXText[value=/•/]',
// eslint-disable-next-line no-restricted-syntax
message: "Use '·' (middle dot) instead of '•' (bullet)",
},
],
'formatjs/enforce-description': 'off', // description values not currently used
'formatjs/enforce-id': 'off', // Explicit IDs are used in the project
'formatjs/enforce-placeholders': 'off', // Issues in short_number.jsx
'formatjs/no-invalid-icu': 'error',
'formatjs/no-literal-string-in-jsx': 'off', // Should be looked at, but mainly flagging punctuation outside of strings
'formatjs/no-multiple-plurals': 'off', // Should be looked at
'jsx-a11y/click-events-have-key-events': 'off',
'jsx-a11y/label-has-associated-control': 'off',
'jsx-a11y/media-has-caption': 'off',
'jsx-a11y/no-autofocus': 'off',
'jsx-a11y/no-interactive-element-to-noninteractive-role': 'off',
'jsx-a11y/no-noninteractive-tabindex': 'off',
'jsx-a11y/no-static-element-interactions': [
'warn',
{
handlers: ['onClick'],
},
],
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: [
'eslint.config.mjs',
'config/webpack/**',
'app/javascript/mastodon/performance.js',
'app/javascript/mastodon/test_setup.js',
'app/javascript/**/__tests__/**',
],
},
],
'import/no-webpack-loader-syntax': 'error',
'react/jsx-filename-extension': [
'error',
{
extensions: ['.jsx', 'tsx'],
},
],
'react/jsx-boolean-value': 'error',
'react/display-name': 'off',
'react/jsx-fragments': ['error', 'syntax'],
'react/jsx-equals-spacing': 'error',
'react/jsx-no-bind': 'error',
'react/jsx-no-useless-fragment': 'error',
'react/jsx-no-target-blank': [
'error',
{
allowReferrer: true,
},
],
'react/jsx-tag-spacing': 'error',
'react/jsx-wrap-multilines': 'error',
'react/self-closing-comp': 'error',
},
},
{
files: [
'app/javascript/mastodon/common.js',
'app/javascript/mastodon/features/emoji/unicode_to_unified_name.js',
'app/javascript/mastodon/features/emoji/emoji_compressed.js',
'app/javascript/mastodon/features/emoji/unicode_to_filename.js',
'app/javascript/flavours/glitch/common.js',
'app/javascript/flavours/glitch/entrypoints/common.js',
'app/javascript/flavours/glitch/features/emoji/unicode_to_unified_name.js',
'app/javascript/flavours/glitch/features/emoji/emoji_compressed.js',
'app/javascript/flavours/glitch/features/emoji/unicode_to_filename.js',
'app/javascript/mastodon/service_worker/web_push_locales.js',
'**/*.config.js',
'**/.*rc.js',
'**/ide-helper.js',
'config/webpack/**/*',
'config/formatjs-formatter.js',
],
languageOptions: {
globals: {
...globals.commonjs,
...globals.node,
},
ecmaVersion: 5,
sourceType: 'commonjs',
},
rules: {
'import/no-commonjs': 'off',
},
},
{
files: ['**/*.ts', '**/*.tsx'],
extends: [
tseslint.configs.strictTypeChecked,
tseslint.configs.stylisticTypeChecked,
react.configs.flat.recommended,
react.configs.flat['jsx-runtime'],
reactHooks.configs['recommended-latest'],
jsxA11Y.flatConfigs.recommended,
importPlugin.flatConfigs.react,
importPlugin.flatConfigs.typescript,
jsdoc.configs['flat/recommended-typescript'],
],
languageOptions: {
parserOptions: {
projectService: true,
},
},
rules: {
// This is not needed as we use noImplicitReturns, which handles this in addition to understanding types
'consistent-return': 'off',
'formatjs/enforce-plural-rules': 'off',
'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
'import/no-default-export': 'warn',
'jsdoc/require-jsdoc': 'off',
'react/prefer-stateless-function': 'warn',
'react/function-component-definition': [
'error',
{
namedComponents: 'arrow-function',
},
],
'react/prop-types': 'off',
'@typescript-eslint/consistent-type-definitions': ['warn', 'interface'],
'@typescript-eslint/consistent-type-exports': 'error',
'@typescript-eslint/consistent-type-imports': 'error',
'@typescript-eslint/prefer-nullish-coalescing': [
'error',
{
ignorePrimitives: {
boolean: true,
},
},
],
'@typescript-eslint/no-restricted-imports': [
'warn',
{
name: 'react-redux',
importNames: ['useSelector', 'useDispatch'],
message:
'Use typed hooks `useAppDispatch` and `useAppSelector` instead.',
},
],
'@typescript-eslint/no-unused-vars': [
'error',
{
vars: 'all',
args: 'after-used',
destructuredArrayIgnorePattern: '^_',
ignoreRestSiblings: true,
},
],
'@typescript-eslint/restrict-template-expressions': [
'warn',
{
allowNumber: true,
},
],
},
},
{
files: ['**/__tests__/*.js', '**/__tests__/*.jsx'],
languageOptions: {
globals: {
...globals.jest,
},
},
},
]);

View file

@ -123,7 +123,14 @@ module Paperclip
end
def needs_convert?
needs_different_geometry? || needs_different_format? || needs_metadata_stripping?
strip_animations? || needs_different_geometry? || needs_different_format? || needs_metadata_stripping?
end
def strip_animations?
# Detecting whether the source image is animated across all our supported
# input file formats is not trivial, and converting unconditionally is just
# as simple for now
options[:style] == :static
end
def needs_different_geometry?

View file

@ -12,14 +12,14 @@
"scripts": {
"build:development": "cross-env RAILS_ENV=development NODE_ENV=development ./bin/webpack",
"build:production": "cross-env RAILS_ENV=production NODE_ENV=production ./bin/webpack",
"fix:js": "eslint . --ext=.js,.jsx,.ts,.tsx --cache --report-unused-disable-directives --fix",
"fix:js": "eslint . --cache --fix",
"fix:css": "stylelint --fix \"**/*.{css,scss}\"",
"fix": "yarn fix:js && yarn fix:css",
"format": "prettier --write --log-level warn .",
"format:check": "prettier --check --ignore-unknown .",
"i18n:extract": "formatjs extract 'app/javascript/**/*.{js,jsx,ts,tsx}' --ignore '**/*.d.ts' --out-file app/javascript/flavours/glitch/locales/en.json --format config/formatjs-formatter.js",
"jest": "cross-env NODE_ENV=test jest",
"lint:js": "eslint . --ext=.js,.jsx,.ts,.tsx --cache --report-unused-disable-directives",
"lint:js": "cd $INIT_CWD && eslint --cache --report-unused-disable-directives",
"lint:css": "stylelint \"**/*.{css,scss}\"",
"lint": "yarn lint:js && yarn lint:css",
"postversion": "git push --tags",
@ -145,6 +145,7 @@
"workbox-window": "^7.0.0"
},
"devDependencies": {
"@eslint/js": "^9.23.0",
"@formatjs/cli": "^6.1.1",
"@testing-library/dom": "^10.2.0",
"@testing-library/jest-dom": "^6.0.0",
@ -176,19 +177,17 @@
"@types/requestidlecallback": "^0.3.5",
"@types/webpack": "^4.41.33",
"@types/webpack-env": "^1.18.4",
"@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^8.0.0",
"babel-jest": "^29.5.0",
"eslint": "^8.41.0",
"eslint-define-config": "^2.0.0",
"eslint-import-resolver-typescript": "^3.5.5",
"eslint-plugin-formatjs": "^5.0.0",
"eslint": "^9.23.0",
"eslint-import-resolver-typescript": "^4.2.5",
"eslint-plugin-formatjs": "^5.3.1",
"eslint-plugin-import": "~2.31.0",
"eslint-plugin-jsdoc": "^50.0.0",
"eslint-plugin-jsx-a11y": "~6.10.0",
"eslint-plugin-promise": "~7.2.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-jsdoc": "^50.6.9",
"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",
"globals": "^16.0.0",
"husky": "^9.0.11",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.5.0",
@ -199,6 +198,7 @@
"stylelint-config-prettier-scss": "^1.0.0",
"stylelint-config-standard-scss": "^14.0.0",
"typescript": "^5.0.4",
"typescript-eslint": "^8.28.0",
"webpack-dev-server": "^3.11.3"
},
"resolutions": {

View file

@ -1,43 +0,0 @@
/* eslint-disable import/no-commonjs */
// @ts-check
// @ts-ignore - This needs to be a CJS file (eslint does not yet support ESM configs), and TS is complaining we use require
const { defineConfig } = require('eslint-define-config');
module.exports = defineConfig({
extends: ['../.eslintrc.js'],
env: {
browser: false,
},
parserOptions: {
project: true,
tsconfigRootDir: __dirname,
ecmaFeatures: {
jsx: false,
},
ecmaVersion: 2021,
},
rules: {
// In the streaming server we need to delete some variables to ensure
// garbage collection takes place on the values referenced by those objects;
// The alternative is to declare the variable as nullable, but then we need
// to assert it's in existence before every use, which becomes much harder
// to maintain.
'no-delete-var': 'off',
// This overrides the base configuration for this rule to pick up
// dependencies for the streaming server from the correct package.json file.
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: ['streaming/.eslintrc.cjs'],
optionalDependencies: false,
peerDependencies: false,
includeTypes: true,
packageDir: __dirname,
},
],
'import/extensions': ['error', 'always'],
},
});

View file

@ -0,0 +1,45 @@
// @ts-check
import globals from 'globals';
import tseslint from 'typescript-eslint';
// eslint-disable-next-line import/no-relative-packages -- Must import from the root
import { baseConfig } from '../eslint.config.mjs';
export default tseslint.config([
baseConfig,
{
languageOptions: {
globals: globals.node,
parser: tseslint.parser,
ecmaVersion: 2021,
sourceType: 'module',
},
settings: {
'import/ignore': ['node_modules', '\\.(json)$'],
'import/resolver': {
typescript: {},
},
},
rules: {
// In the streaming server we need to delete some variables to ensure
// garbage collection takes place on the values referenced by those objects;
// The alternative is to declare the variable as nullable, but then we need
// to assert it's in existence before every use, which becomes much harder
// to maintain.
'no-delete-var': 'off',
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: ['**/*.config.mjs'],
},
],
'import/extensions': ['error', 'always'],
},
},
]);

View file

@ -0,0 +1,7 @@
const config = {
'*': 'prettier --ignore-unknown --write',
'*.{js,ts}': 'eslint --fix',
'**/*.ts': () => 'tsc -p tsconfig.json --noEmit',
};
export default config;

View file

@ -31,14 +31,16 @@
"ws": "^8.12.1"
},
"devDependencies": {
"@eslint/js": "^9.23.0",
"@types/cors": "^2.8.16",
"@types/express": "^4.17.17",
"@types/pg": "^8.6.6",
"@types/uuid": "^10.0.0",
"@types/ws": "^8.5.9",
"eslint-define-config": "^2.0.0",
"globals": "^16.0.0",
"pino-pretty": "^13.0.0",
"typescript": "^5.0.4"
"typescript": "^5.0.4",
"typescript-eslint": "^8.28.0"
},
"optionalDependencies": {
"bufferutil": "^4.0.7",

View file

@ -8,5 +8,5 @@
"tsBuildInfoFile": "../tmp/cache/streaming/tsconfig.tsbuildinfo",
"paths": {}
},
"include": ["./*.js", "./.eslintrc.cjs"]
"include": ["./*.js"]
}

941
yarn.lock

File diff suppressed because it is too large Load diff