Merge branch 'main' into glitch-soc/merge-upstream

Conflicts:
- `app/views/admin/announcements/edit.html.haml`:
  Upstream change too close to theming-related glitch-soc change.
  Ported upstream changes.
- `app/views/admin/announcements/new.html.haml`
  Upstream change too close to theming-related glitch-soc change.
  Ported upstream changes.
main
Claire 2 months ago
commit 32c70d2f09
  1. 138
      .github/workflows/test-chart.yml
  2. 1065
      AUTHORS.md
  3. 1
      CHANGELOG.md
  4. 2
      Gemfile
  5. 2
      app/controllers/api/base_controller.rb
  6. 2
      app/controllers/api/v2/admin/accounts_controller.rb
  7. 2
      app/controllers/api/v2/filters/keywords_controller.rb
  8. 2
      app/controllers/api/v2/filters/statuses_controller.rb
  9. 2
      app/javascript/mastodon/actions/filters.js
  10. 6
      app/javascript/mastodon/components/account.js
  11. 2
      app/javascript/mastodon/components/admin/Trends.js
  12. 10
      app/javascript/mastodon/components/hashtag.js
  13. 2
      app/javascript/mastodon/components/icon.js
  14. 40
      app/javascript/mastodon/components/permalink.js
  15. 8
      app/javascript/mastodon/components/status.js
  16. 35
      app/javascript/mastodon/components/status_content.js
  17. 1
      app/javascript/mastodon/features/account/components/featured_tags.js
  18. 2
      app/javascript/mastodon/features/account/components/header.js
  19. 2
      app/javascript/mastodon/features/account_gallery/components/media_item.js
  20. 8
      app/javascript/mastodon/features/account_timeline/components/moved_note.js
  21. 10
      app/javascript/mastodon/features/compose/components/navigation_bar.js
  22. 2
      app/javascript/mastodon/features/compose/components/reply_indicator.js
  23. 4
      app/javascript/mastodon/features/direct_timeline/components/conversation.js
  24. 6
      app/javascript/mastodon/features/directory/components/account_card.js
  25. 6
      app/javascript/mastodon/features/follow_recommendations/components/account.js
  26. 6
      app/javascript/mastodon/features/follow_requests/components/account_authorize.js
  27. 6
      app/javascript/mastodon/features/notifications/components/follow_request.js
  28. 6
      app/javascript/mastodon/features/notifications/components/notification.js
  29. 2
      app/javascript/mastodon/features/picture_in_picture/components/footer.js
  30. 4
      app/javascript/mastodon/features/status/components/detailed_status.js
  31. 4
      app/javascript/mastodon/features/ui/components/boost_modal.js
  32. 5
      app/javascript/mastodon/features/ui/components/header.js
  33. 72
      app/javascript/mastodon/locales/ca.json
  34. 14
      app/javascript/mastodon/locales/cy.json
  35. 22
      app/javascript/mastodon/locales/de.json
  36. 34
      app/javascript/mastodon/locales/eo.json
  37. 10
      app/javascript/mastodon/locales/es-AR.json
  38. 2
      app/javascript/mastodon/locales/es.json
  39. 4
      app/javascript/mastodon/locales/fi.json
  40. 4
      app/javascript/mastodon/locales/gl.json
  41. 6
      app/javascript/mastodon/locales/ja.json
  42. 10
      app/javascript/mastodon/locales/lv.json
  43. 6
      app/javascript/mastodon/locales/ml.json
  44. 4
      app/javascript/mastodon/locales/nl.json
  45. 2
      app/javascript/mastodon/locales/nn.json
  46. 26
      app/javascript/mastodon/locales/no.json
  47. 4
      app/javascript/mastodon/locales/pl.json
  48. 80
      app/javascript/mastodon/locales/sk.json
  49. 6
      app/javascript/mastodon/locales/sq.json
  50. 14
      app/javascript/mastodon/locales/th.json
  51. 2
      app/javascript/mastodon/locales/tr.json
  52. 17
      app/javascript/styles/mastodon/components.scss
  53. 3
      app/javascript/styles/mastodon/tables.scss
  54. 10
      app/models/account_filter.rb
  55. 2
      app/models/admin/action_log_filter.rb
  56. 4
      app/models/admin/appeal_filter.rb
  57. 2
      app/models/admin/status_filter.rb
  58. 2
      app/models/announcement_filter.rb
  59. 2
      app/models/concerns/domain_normalizable.rb
  60. 2
      app/models/custom_emoji_filter.rb
  61. 4
      app/models/instance_filter.rb
  62. 2
      app/models/invite_filter.rb
  63. 1
      app/models/media_attachment.rb
  64. 12
      app/models/relationship_filter.rb
  65. 4
      app/models/report_filter.rb
  66. 2
      app/models/rule.rb
  67. 2
      app/models/trends/preview_card_filter.rb
  68. 4
      app/models/trends/preview_card_provider_filter.rb
  69. 2
      app/models/trends/status_filter.rb
  70. 2
      app/services/import_service.rb
  71. 2
      app/views/admin/announcements/edit.html.haml
  72. 2
      app/views/admin/announcements/new.html.haml
  73. 2
      app/views/admin/settings/registrations/show.html.haml
  74. 15
      chart/.helmignore
  75. 2
      chart/Chart.yaml
  76. 2
      chart/README.md
  77. 25
      chart/dev-values.yaml
  78. 6
      chart/templates/configmap-env.yaml
  79. 14
      chart/templates/cronjob-media-remove.yaml
  80. 14
      chart/templates/deployment-sidekiq.yaml
  81. 14
      chart/templates/job-assets-precompile.yaml
  82. 14
      chart/templates/job-chewy-upgrade.yaml
  83. 14
      chart/templates/job-db-migrate.yaml
  84. 6
      chart/values.yaml
  85. 8
      config/initializers/paperclip.rb
  86. 4
      config/locales/activerecord.ru.yml
  87. 12
      config/locales/af.yml
  88. 5
      config/locales/ar.yml
  89. 4
      config/locales/ast.yml
  90. 4
      config/locales/bg.yml
  91. 2
      config/locales/br.yml
  92. 6
      config/locales/ca.yml
  93. 4
      config/locales/ckb.yml
  94. 4
      config/locales/co.yml
  95. 20
      config/locales/cs.yml
  96. 1
      config/locales/cy.yml
  97. 6
      config/locales/da.yml
  98. 24
      config/locales/de.yml
  99. 2
      config/locales/devise.th.yml
  100. 4
      config/locales/doorkeeper.ca.yml
  101. Some files were not shown because too many files have changed in this diff Show More

@ -0,0 +1,138 @@
# This is a GitHub workflow defining a set of jobs with a set of steps.
# ref: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions
#
name: Test chart
on:
pull_request:
paths:
- "chart/**"
- "!**.md"
- ".github/workflows/test-chart.yml"
push:
paths:
- "chart/**"
- "!**.md"
- ".github/workflows/test-chart.yml"
branches-ignore:
- "dependabot/**"
workflow_dispatch:
permissions:
contents: read
defaults:
run:
working-directory: chart
jobs:
lint-templates:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "3.x"
- name: Install dependencies (yamllint)
run: pip install yamllint
- run: helm dependency update
- name: helm lint
run: |
helm lint . \
--values dev-values.yaml
- name: helm template
run: |
helm template . \
--values dev-values.yaml \
--output-dir rendered-templates
- name: yamllint (only on templates we manage)
run: |
rm -rf rendered-templates/mastodon/charts
yamllint rendered-templates \
--config-data "{rules: {indentation: {spaces: 2}, line-length: disable}}"
# This job helps us validate that rendered templates are valid k8s resources
# against a k8s api-server, via "helm template --validate", but also that a
# basic configuration can be used to successfully startup mastodon.
#
test-install:
runs-on: ubuntu-22.04
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
include:
# k3s-channel reference: https://update.k3s.io/v1-release/channels
- k3s-channel: latest
- k3s-channel: stable
# This represents the oldest configuration we test against.
#
# The k8s version chosen is based on the oldest still supported k8s
# version among two managed k8s services, GKE, EKS.
# - GKE: https://endoflife.date/google-kubernetes-engine
# - EKS: https://endoflife.date/amazon-eks
#
# The helm client's version can influence what helper functions is
# available for use in the templates, currently we need v3.6.0 or
# higher.
#
- k3s-channel: v1.21
helm-version: v3.6.0
steps:
- uses: actions/checkout@v3
# This action starts a k8s cluster with NetworkPolicy enforcement and
# installs both kubectl and helm.
#
# ref: https://github.com/jupyterhub/action-k3s-helm#readme
#
- uses: jupyterhub/action-k3s-helm@v3
with:
k3s-channel: ${{ matrix.k3s-channel }}
helm-version: ${{ matrix.helm-version }}
metrics-enabled: false
traefik-enabled: false
docker-enabled: false
- run: helm dependency update
# Validate rendered helm templates against the k8s api-server
- name: helm template --validate
run: |
helm template --validate mastodon . \
--values dev-values.yaml
- name: helm install
run: |
helm install mastodon . \
--values dev-values.yaml \
--timeout 10m
# This actions provides a report about the state of the k8s cluster,
# providing logs etc on anything that has failed and workloads marked as
# important.
#
# ref: https://github.com/jupyterhub/action-k8s-namespace-report#readme
#
- name: Kubernetes namespace report
uses: jupyterhub/action-k8s-namespace-report@v1
if: always()
with:
important-workloads: >-
deploy/mastodon-sidekiq
deploy/mastodon-streaming
deploy/mastodon-web
job/mastodon-assets-precompile
job/mastodon-chewy-upgrade
job/mastodon-create-admin
job/mastodon-db-migrate

File diff suppressed because it is too large Load Diff

@ -185,6 +185,7 @@ Some of the features in this release have been funded through the [NGI0 Discover
- Fix `CDN_HOST` not being used in some asset URLs ([tribela](https://github.com/mastodon/mastodon/pull/18662))
- Fix `CAS_DISPLAY_NAME`, `SAML_DISPLAY_NAME` and `OIDC_DISPLAY_NAME` being ignored ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18568))
- Fix various typos in comments throughout the codebase ([luzpaz](https://github.com/mastodon/mastodon/pull/18604))
- Fix CSV upload no longer breaks if an server domain includes UTF-8 characters ([HamptonMakes]())
### Security

@ -92,7 +92,7 @@ gem 'tty-prompt', '~> 0.23', require: false
gem 'twitter-text', '~> 3.1.0'
gem 'tzinfo-data', '~> 1.2022'
gem 'webpacker', '~> 5.4'
gem 'webpush', git: 'https://github.com/ClearlyClaire/webpush.git', ref: 'f14a4d52e201128b1b00245d11b6de80d6cfdcd9'
gem 'webpush', github: 'ClearlyClaire/webpush', ref: 'f14a4d52e201128b1b00245d11b6de80d6cfdcd9'
gem 'webauthn', '~> 2.5'
gem 'json-ld'

@ -57,7 +57,7 @@ class Api::BaseController < ApplicationController
render json: { error: I18n.t('errors.429') }, status: 429
end
rescue_from ActionController::ParameterMissing do |e|
rescue_from ActionController::ParameterMissing, Mastodon::InvalidParameterError do |e|
render json: { error: e.to_s }, status: 400
end

@ -33,7 +33,7 @@ class Api::V2::Admin::AccountsController < Api::V1::Admin::AccountsController
end
def filter_params
params.permit(*FILTER_PARAMS)
params.permit(*FILTER_PARAMS, role_ids: [])
end
def pagination_params(core_params)

@ -1,6 +1,6 @@
# frozen_string_literal: true
class Api::V1::Filters::KeywordsController < Api::BaseController
class Api::V2::Filters::KeywordsController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:filters' }, only: [:index, :show]
before_action -> { doorkeeper_authorize! :write, :'write:filters' }, except: [:index, :show]
before_action :require_user!

@ -1,6 +1,6 @@
# frozen_string_literal: true
class Api::V1::Filters::StatusesController < Api::BaseController
class Api::V2::Filters::StatusesController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:filters' }, only: [:index, :show]
before_action -> { doorkeeper_authorize! :write, :'write:filters' }, except: [:index, :show]
before_action :require_user!

@ -43,7 +43,7 @@ export const fetchFilters = () => (dispatch, getState) => {
export const createFilterStatus = (params, onSuccess, onFail) => (dispatch, getState) => {
dispatch(createFilterStatusRequest());
api(getState).post(`/api/v1/filters/${params.filter_id}/statuses`, params).then(response => {
api(getState).post(`/api/v2/filters/${params.filter_id}/statuses`, params).then(response => {
dispatch(createFilterStatusSuccess(response.data));
if (onSuccess) onSuccess();
}).catch(error => {

@ -3,13 +3,13 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import Avatar from './avatar';
import DisplayName from './display_name';
import Permalink from './permalink';
import IconButton from './icon_button';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { me } from '../initial_state';
import RelativeTimestamp from './relative_timestamp';
import Skeleton from 'mastodon/components/skeleton';
import { Link } from 'react-router-dom';
const messages = defineMessages({
follow: { id: 'account.follow', defaultMessage: 'Follow' },
@ -140,11 +140,11 @@ class Account extends ImmutablePureComponent {
return (
<div className='account'>
<div className='account__wrapper'>
<Permalink key={account.get('id')} className='account__display-name' title={account.get('acct')} href={account.get('url')} to={`/@${account.get('acct')}`}>
<Link key={account.get('id')} className='account__display-name' title={account.get('acct')} to={`/@${account.get('acct')}`}>
<div className='account__avatar-wrapper'><Avatar account={account} size={size} /></div>
{mute_expires_at}
<DisplayName account={account} />
</Permalink>
</Link>
<div className='account__relationship'>
{buttons}

@ -50,7 +50,7 @@ export default class Trends extends React.PureComponent {
<Hashtag
key={hashtag.name}
name={hashtag.name}
href={`/admin/tags/${hashtag.id}`}
to={`/admin/tags/${hashtag.id}`}
people={hashtag.history[0].accounts * 1 + hashtag.history[1].accounts * 1}
uses={hashtag.history[0].uses * 1 + hashtag.history[1].uses * 1}
history={hashtag.history.reverse().map(day => day.uses)}

@ -4,7 +4,7 @@ import { Sparklines, SparklinesCurve } from 'react-sparklines';
import { FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import Permalink from './permalink';
import { Link } from 'react-router-dom';
import ShortNumber from 'mastodon/components/short_number';
import Skeleton from 'mastodon/components/skeleton';
import classNames from 'classnames';
@ -53,7 +53,6 @@ export const accountsCountRenderer = (displayNumber, pluralReady) => (
export const ImmutableHashtag = ({ hashtag }) => (
<Hashtag
name={hashtag.get('name')}
href={hashtag.get('url')}
to={`/tags/${hashtag.get('name')}`}
people={hashtag.getIn(['history', 0, 'accounts']) * 1 + hashtag.getIn(['history', 1, 'accounts']) * 1}
history={hashtag.get('history').reverse().map((day) => day.get('uses')).toArray()}
@ -64,12 +63,12 @@ ImmutableHashtag.propTypes = {
hashtag: ImmutablePropTypes.map.isRequired,
};
const Hashtag = ({ name, href, to, people, uses, history, className, description, withGraph }) => (
const Hashtag = ({ name, to, people, uses, history, className, description, withGraph }) => (
<div className={classNames('trends__item', className)}>
<div className='trends__item__name'>
<Permalink href={href} to={to}>
<Link to={to}>
{name ? <React.Fragment>#<span>{name}</span></React.Fragment> : <Skeleton width={50} />}
</Permalink>
</Link>
{description ? (
<span>{description}</span>
@ -98,7 +97,6 @@ const Hashtag = ({ name, href, to, people, uses, history, className, description
Hashtag.propTypes = {
name: PropTypes.string,
href: PropTypes.string,
to: PropTypes.string,
people: PropTypes.number,
description: PropTypes.node,

@ -14,7 +14,7 @@ export default class Icon extends React.PureComponent {
const { id, className, fixedWidth, ...other } = this.props;
return (
<i role='img' className={classNames('fa', `fa-${id}`, className, { 'fa-fw': fixedWidth })} {...other} />
<i className={classNames('fa', `fa-${id}`, className, { 'fa-fw': fixedWidth })} {...other} />
);
}

@ -1,40 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
export default class Permalink extends React.PureComponent {
static contextTypes = {
router: PropTypes.object,
};
static propTypes = {
className: PropTypes.string,
href: PropTypes.string.isRequired,
to: PropTypes.string.isRequired,
children: PropTypes.node,
onInterceptClick: PropTypes.func,
};
handleClick = e => {
if (this.props.onInterceptClick && this.props.onInterceptClick()) {
e.preventDefault();
return;
}
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault();
this.context.router.history.push(this.props.to);
}
}
render () {
const { href, children, className, onInterceptClick, ...other } = this.props;
return (
<a target='_blank' href={href} onClick={this.handleClick} {...other} className={`permalink${className ? ' ' + className : ''}`}>
{children}
</a>
);
}
}

@ -378,7 +378,7 @@ class Status extends ImmutablePureComponent {
prepend = (
<div className='status__prepend'>
<div className='status__prepend-icon-wrapper'><Icon id='retweet' className='status__prepend-icon' fixedWidth /></div>
<FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <a onClick={this.handlePrependAccountClick} data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} className='status__display-name muted'><bdi><strong dangerouslySetInnerHTML={display_name_html} /></bdi></a> }} />
<FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <a onClick={this.handlePrependAccountClick} data-id={status.getIn(['account', 'id'])} href={`/@${status.getIn(['account', 'acct'])}`} className='status__display-name muted'><bdi><strong dangerouslySetInnerHTML={display_name_html} /></bdi></a> }} />
</div>
);
@ -392,7 +392,7 @@ class Status extends ImmutablePureComponent {
prepend = (
<div className='status__prepend'>
<div className='status__prepend-icon-wrapper'><Icon id='reply' className='status__prepend-icon' fixedWidth /></div>
<FormattedMessage id='status.replied_to' defaultMessage='Replied to {name}' values={{ name: <a onClick={this.handlePrependAccountClick} data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} className='status__display-name muted'><bdi><strong dangerouslySetInnerHTML={display_name_html} /></bdi></a> }} />
<FormattedMessage id='status.replied_to' defaultMessage='Replied to {name}' values={{ name: <a onClick={this.handlePrependAccountClick} data-id={status.getIn(['account', 'id'])} href={`/@${status.getIn(['account', 'acct'])}`} className='status__display-name muted'><bdi><strong dangerouslySetInnerHTML={display_name_html} /></bdi></a> }} />
</div>
);
}
@ -511,12 +511,12 @@ class Status extends ImmutablePureComponent {
<div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), muted: this.props.muted })} data-id={status.get('id')}>
<div className='status__info'>
<a onClick={this.handleClick} href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener noreferrer'>
<a onClick={this.handleClick} href={`/@${status.getIn(['account', 'acct'])}\/${status.get('id')}`} className='status__relative-time' target='_blank' rel='noopener noreferrer'>
<span className='status__visibility-icon'><Icon id={visibilityIcon.icon} title={visibilityIcon.text} /></span>
<RelativeTimestamp timestamp={status.get('created_at')} />{status.get('edited_at') && <abbr title={intl.formatMessage(messages.edited, { date: intl.formatDate(status.get('edited_at'), { hour12: false, year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }) })}> *</abbr>}
</a>
<a onClick={this.handleAccountClick} href={status.getIn(['account', 'url'])} title={status.getIn(['account', 'acct'])} className='status__display-name' target='_blank' rel='noopener noreferrer'>
<a onClick={this.handleAccountClick} href={`/@${status.getIn(['account', 'acct'])}`} title={status.getIn(['account', 'acct'])} className='status__display-name' target='_blank' rel='noopener noreferrer'>
<div className='status__avatar'>
{statusAvatar}
</div>

@ -2,13 +2,13 @@ import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import { FormattedMessage, injectIntl } from 'react-intl';
import Permalink from './permalink';
import { Link } from 'react-router-dom';
import classnames from 'classnames';
import PollContainer from 'mastodon/containers/poll_container';
import Icon from 'mastodon/components/icon';
import { autoPlayGif, languages as preloadedLanguages, translationEnabled } from 'mastodon/initial_state';
const MAX_HEIGHT = 642; // 20px * 32 (+ 2px padding at the top)
const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top)
class TranslateButton extends React.PureComponent {
@ -77,38 +77,45 @@ class StatusContent extends React.PureComponent {
return;
}
const { status, onCollapsedToggle } = this.props;
const links = node.querySelectorAll('a');
let link, mention;
for (var i = 0; i < links.length; ++i) {
let link = links[i];
link = links[i];
if (link.classList.contains('status-link')) {
continue;
}
link.classList.add('status-link');
let mention = this.props.status.get('mentions').find(item => link.href === item.get('url'));
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')}`);
} 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.slice(1)}`);
} else {
link.setAttribute('title', link.href);
link.classList.add('unhandled-link');
}
}
if (this.props.status.get('collapsed', null) === null) {
let collapsed =
this.props.collapsable
&& this.props.onClick
&& node.clientHeight > MAX_HEIGHT
&& this.props.status.get('spoiler_text').length === 0;
if (status.get('collapsed', null) === null && onCollapsedToggle) {
const { collapsable, onClick } = this.props;
if(this.props.onCollapsedToggle) this.props.onCollapsedToggle(collapsed);
const collapsed =
collapsable
&& onClick
&& node.clientHeight > MAX_HEIGHT
&& status.get('spoiler_text').length === 0;
this.props.status.set('collapsed', collapsed);
onCollapsedToggle(collapsed);
}
}
@ -242,9 +249,9 @@ class StatusContent extends React.PureComponent {
let mentionsPlaceholder = '';
const mentionLinks = status.get('mentions').map(item => (
<Permalink to={`/@${item.get('acct')}`} href={item.get('url')} key={item.get('id')} className='mention'>
<Link to={`/@${item.get('acct')}`} key={item.get('id')} className='mention'>
@<span>{item.get('username')}</span>
</Permalink>
</Link>
)).reduce((aggregate, item) => [...aggregate, item, ' '], []);
const toggleText = hidden ? <FormattedMessage id='status.show_more' defaultMessage='Show more' /> : <FormattedMessage id='status.show_less' defaultMessage='Show less' />;

@ -39,7 +39,6 @@ class FeaturedTags extends ImmutablePureComponent {
<Hashtag
key={featuredTag.get('name')}
name={featuredTag.get('name')}
href={featuredTag.get('url')}
to={`/@${account.get('acct')}/tagged/${featuredTag.get('name')}`}
uses={featuredTag.get('statuses_count') * 1}
withGraph={false}

@ -314,8 +314,6 @@ class Header extends ImmutablePureComponent {
<Avatar account={suspended || hidden ? undefined : account} size={90} />
</a>
<div className='spacer' />
{!suspended && (
<div className='account__header__tabs__buttons'>
{!hidden && (

@ -129,7 +129,7 @@ export default class MediaItem extends ImmutablePureComponent {
return (
<div className='account-gallery__item' style={{ width, height }}>
<a className='media-gallery__item-thumbnail' href={status.get('url')} onClick={this.handleClick} title={title} target='_blank' rel='noopener noreferrer'>
<a className='media-gallery__item-thumbnail' href={`/@${status.getIn(['account', 'acct'])}\/${status.get('id')}`} onClick={this.handleClick} title={title} target='_blank' rel='noopener noreferrer'>
<Blurhash
hash={attachment.get('blurhash')}
className={classNames('media-gallery__preview', { 'media-gallery__preview--hidden': visible && loaded })}

@ -4,7 +4,7 @@ import { FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import AvatarOverlay from '../../../components/avatar_overlay';
import DisplayName from '../../../components/display_name';
import Permalink from 'mastodon/components/permalink';
import { Link } from 'react-router-dom';
export default class MovedNote extends ImmutablePureComponent {
@ -23,12 +23,12 @@ export default class MovedNote extends ImmutablePureComponent {
</div>
<div className='moved-account-banner__action'>
<Permalink href={to.get('url')} to={`/@${to.get('acct')}`} className='detailed-status__display-name'>
<Link to={`/@${to.get('acct')}`} className='detailed-status__display-name'>
<div className='detailed-status__display-avatar'><AvatarOverlay account={to} friend={from} /></div>
<DisplayName account={to} />
</Permalink>
</Link>
<Permalink href={to.get('url')} to={`/@${to.get('acct')}`} className='button'><FormattedMessage id='account.go_to_profile' defaultMessage='Go to profile' /></Permalink>
<Link to={`/@${to.get('acct')}`} className='button'><FormattedMessage id='account.go_to_profile' defaultMessage='Go to profile' /></Link>
</div>
</div>
);

@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ActionBar from './action_bar';
import Avatar from '../../../components/avatar';
import Permalink from '../../../components/permalink';
import { Link } from 'react-router-dom';
import IconButton from '../../../components/icon_button';
import { FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
@ -19,15 +19,15 @@ export default class NavigationBar extends ImmutablePureComponent {
render () {
return (
<div className='navigation-bar'>
<Permalink href={this.props.account.get('url')} to={`/@${this.props.account.get('acct')}`}>
<Link to={`/@${this.props.account.get('acct')}`}>
<span style={{ display: 'none' }}>{this.props.account.get('acct')}</span>
<Avatar account={this.props.account} size={46} />
</Permalink>
</Link>
<div className='navigation-bar__profile'>
<Permalink href={this.props.account.get('url')} to={`/@${this.props.account.get('acct')}`}>
<Link to={`/@${this.props.account.get('acct')}`}>
<strong className='navigation-bar__profile-account'>@{this.props.account.get('acct')}</strong>
</Permalink>
</Link>
<a href='/settings/profile' className='navigation-bar__profile-edit'><FormattedMessage id='navigation_bar.edit_profile' defaultMessage='Edit profile' /></a>
</div>

@ -50,7 +50,7 @@ class ReplyIndicator extends ImmutablePureComponent {
<div className='reply-indicator__header'>
<div className='reply-indicator__cancel'><IconButton title={intl.formatMessage(messages.cancel)} icon='times' onClick={this.handleClick} inverted /></div>
<a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='reply-indicator__display-name'>
<a href={`/@${status.getIn(['account', 'acct'])}`} onClick={this.handleAccountClick} className='reply-indicator__display-name'>
<div className='reply-indicator__display-avatar'><Avatar account={status.get('account')} size={24} /></div>
<DisplayName account={status.get('account')} />
</a>

@ -7,7 +7,7 @@ import AttachmentList from 'mastodon/components/attachment_list';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container';
import AvatarComposite from 'mastodon/components/avatar_composite';
import Permalink from 'mastodon/components/permalink';
import { Link } from 'react-router-dom';
import IconButton from 'mastodon/components/icon_button';
import RelativeTimestamp from 'mastodon/components/relative_timestamp';
import { HotKeys } from 'react-hotkeys';
@ -133,7 +133,7 @@ class Conversation extends ImmutablePureComponent {
menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDelete });
const names = accounts.map(a => <Permalink to={`/@${a.get('acct')}`} href={a.get('url')} key={a.get('id')} title={a.get('acct')}><bdi><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: a.get('display_name_html') }} /></bdi></Permalink>).reduce((prev, cur) => [prev, ', ', cur]);
const names = accounts.map(a => <Link to={`/@${a.get('acct')}`} key={a.get('id')} title={a.get('acct')}><bdi><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: a.get('display_name_html') }} /></bdi></Link>).reduce((prev, cur) => [prev, ', ', cur]);
const handlers = {
reply: this.handleReply,

@ -6,7 +6,7 @@ import { connect } from 'react-redux';
import { makeGetAccount } from 'mastodon/selectors';
import Avatar from 'mastodon/components/avatar';
import DisplayName from 'mastodon/components/display_name';
import Permalink from 'mastodon/components/permalink';
import { Link } from 'react-router-dom';
import Button from 'mastodon/components/button';
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
import { autoPlayGif, me, unfollowModal } from 'mastodon/initial_state';
@ -169,7 +169,7 @@ class AccountCard extends ImmutablePureComponent {
return (
<div className='account-card'>
<Permalink href={account.get('url')} to={`/@${account.get('acct')}`} className='account-card__permalink'>
<Link to={`/@${account.get('acct')}`} className='account-card__permalink'>
<div className='account-card__header'>
<img
src={
@ -183,7 +183,7 @@ class AccountCard extends ImmutablePureComponent {
<div className='account-card__title__avatar'><Avatar account={account} size={56} /></div>
<DisplayName account={account} />
</div>
</Permalink>
</Link>
{account.get('note').length > 0 && (
<div

@ -6,7 +6,7 @@ import { connect } from 'react-redux';
import { makeGetAccount } from 'mastodon/selectors';
import Avatar from 'mastodon/components/avatar';
import DisplayName from 'mastodon/components/display_name';
import Permalink from 'mastodon/components/permalink';
import { Link } from 'react-router-dom';
import IconButton from 'mastodon/components/icon_button';
import { injectIntl, defineMessages } from 'react-intl';
import { followAccount, unfollowAccount } from 'mastodon/actions/accounts';
@ -66,13 +66,13 @@ class Account extends ImmutablePureComponent {
return (
<div className='account follow-recommendations-account'>
<div className='account__wrapper'>
<Permalink className='account__display-name account__display-name--with-note' title={account.get('acct')} href={account.get('url')} to={`/@${account.get('acct')}`}>
<Link className='account__display-name account__display-name--with-note' title={account.get('acct')} to={`/@${account.get('acct')}`}>
<div className='account__avatar-wrapper'><Avatar account={account} size={36} /></div>
<DisplayName account={account} />
<div className='account__note'>{getFirstSentence(account.get('note_plain'))}</div>
</Permalink>
</Link>
<div className='account__relationship'>
{button}

@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import Permalink from '../../../components/permalink';
import { Link } from 'react-router-dom';
import Avatar from '../../../components/avatar';
import DisplayName from '../../../components/display_name';
import IconButton from '../../../components/icon_button';
@ -30,10 +30,10 @@ class AccountAuthorize extends ImmutablePureComponent {
return (
<div className='account-authorize__wrapper'>
<div className='account-authorize'>
<Permalink href={account.get('url')} to={`/@${account.get('acct')}`} className='detailed-status__display-name'>
<Link to={`/@${account.get('acct')}`} className='detailed-status__display-name'>
<div className='account-authorize__avatar'><Avatar account={account} size={48} /></div>
<DisplayName account={account} />
</Permalink>
</Link>
<div className='account__header__content translate' dangerouslySetInnerHTML={content} />
</div>

@ -3,7 +3,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import Avatar from 'mastodon/components/avatar';
import DisplayName from 'mastodon/components/display_name';
import Permalink from 'mastodon/components/permalink';
import { Link } from 'react-router-dom';
import IconButton from 'mastodon/components/icon_button';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
@ -42,10 +42,10 @@ class FollowRequest extends ImmutablePureComponent {
return (
<div className='account'>
<div className='account__wrapper'>
<Permalink key={account.get('id')} className='account__display-name' title={account.get('acct')} href={account.get('url')} to={`/@${account.get('acct')}`}>
<Link key={account.get('id')} className='account__display-name' title={account.get('acct')} to={`/@${account.get('acct')}`}>
<div className='account__avatar-wrapper'><Avatar account={account} size={36} /></div>
<DisplayName account={account} />
</Permalink>
</Link>
<div className='account__relationship'>
<IconButton title={intl.formatMessage(messages.authorize)} icon='check' onClick={onAuthorize} />

@ -10,7 +10,7 @@ import AccountContainer from 'mastodon/containers/account_container';
import Report from './report';
import FollowRequestContainer from '../containers/follow_request_container';
import Icon from 'mastodon/components/icon';
import Permalink from 'mastodon/components/permalink';
import { Link } from 'react-router-dom';
import classNames from 'classnames';
const messages = defineMessages({
@ -378,7 +378,7 @@ class Notification extends ImmutablePureComponent {
const targetAccount = report.get('target_account');
const targetDisplayNameHtml = { __html: targetAccount.get('display_name_html') };
const targetLink = <bdi><Permalink className='notification__display-name' href={targetAccount.get('url')} title={targetAccount.get('acct')} to={`/@${targetAccount.get('acct')}`} dangerouslySetInnerHTML={targetDisplayNameHtml} /></bdi>;
const targetLink = <bdi><Link className='notification__display-name' title={targetAccount.get('acct')} to={`/@${targetAccount.get('acct')}`} dangerouslySetInnerHTML={targetDisplayNameHtml} /></bdi>;
return (
<HotKeys handlers={this.getHandlers()}>
@ -403,7 +403,7 @@ class Notification extends ImmutablePureComponent {
const { notification } = this.props;
const account = notification.get('account');
const displayNameHtml = { __html: account.get('display_name_html') };
const link = <bdi><Permalink className='notification__display-name' href={account.get('url')} title={account.get('acct')} to={`/@${account.get('acct')}`} dangerouslySetInnerHTML={displayNameHtml} /></bdi>;
const link = <bdi><Link className='notification__display-name' href={`/@${account.get('acct')}`} title={account.get('acct')} to={`/@${account.get('acct')}`} dangerouslySetInnerHTML={displayNameHtml} /></bdi>;
switch(notification.get('type')) {
case 'follow':

@ -184,7 +184,7 @@ class Footer extends ImmutablePureComponent {
<IconButton className='status__action-bar-button' title={replyTitle} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} counter={status.get('replies_count')} obfuscateCount />
<IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} counter={status.get('reblogs_count')} />
<IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} counter={status.get('favourites_count')} />
{withOpenButton && <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.open)} icon='external-link' onClick={this.handleOpenClick} href={status.get('url')} />}
{withOpenButton && <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.open)} icon='external-link' onClick={this.handleOpenClick} href={`/@${status.getIn(['account', 'acct'])}\/${status.get('id')}`} />}
</div>
);
}

@ -261,7 +261,7 @@ class DetailedStatus extends ImmutablePureComponent {
return (
<div style={outerStyle}>
<div ref={this.setRef} className={classNames('detailed-status', `detailed-status-${status.get('visibility')}`, { compact })}>
<a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='detailed-status__display-name'>
<a href={`/@${status.getIn(['account', 'acct'])}`} onClick={this.handleAccountClick} className='detailed-status__display-name'>
<div className='detailed-status__display-avatar'><Avatar account={status.get('account')} size={46} /></div>
<DisplayName account={status.get('account')} localDomain={this.props.domain} />
</a>
@ -276,7 +276,7 @@ class DetailedStatus extends ImmutablePureComponent {
{media}
<div className='detailed-status__meta'>
<a className='detailed-status__datetime' href={status.get('url')} target='_blank' rel='noopener noreferrer'>
<a className='detailed-status__datetime' href={`/@${status.getIn(['account', 'acct'])}\/${status.get('id')}`} target='_blank' rel='noopener noreferrer'>
<FormattedDate value={new Date(status.get('created_at'))} hour12={false} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' />
</a>{edited}{visibilityLink}{applicationLink}{reblogLink} · {favouriteLink}
</div>

@ -98,12 +98,12 @@ class BoostModal extends ImmutablePureComponent {
<div className='boost-modal__container'>
<div className={classNames('status', `status-${status.get('visibility')}`, 'light')}>
<div className='status__info'>
<a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener noreferrer'>
<a href={`/@${status.getIn(['account', 'acct'])}\/${status.get('id')}`} className='status__relative-time' target='_blank' rel='noopener noreferrer'>
<span className='status__visibility-icon'><Icon id={visibilityIcon.icon} title={visibilityIcon.text} /></span>
<RelativeTimestamp timestamp={status.get('created_at')} />
</a>
<a onClick={this.handleAccountClick} href={status.getIn(['account', 'url'])} className='status__display-name'>
<a onClick={this.handleAccountClick} href={`/@${status.getIn(['account', 'acct'])}`} className='status__display-name'>
<div className='status__avatar'>
<Avatar account={status.get('account')} size={48} />
</div>

@ -4,16 +4,15 @@ import { Link, withRouter } from 'react-router-dom';
import { FormattedMessage } from 'react-intl';
import { registrationsOpen, me } from 'mastodon/initial_state';
import Avatar from 'mastodon/components/avatar';
import Permalink from 'mastodon/components/permalink';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
const Account = connect(state => ({
account: state.getIn(['accounts', me]),
}))(({ account }) => (
<Permalink href={account.get('url')} to={`/@${account.get('acct')}`} title={account.get('acct')}>
<Link to={`/@${account.get('acct')}`} title={account.get('acct')}>
<Avatar account={account} size={35} />
</Permalink>
</Link>
));
export default @withRouter

@ -1,14 +1,14 @@
{
"about.blocks": "Servidors moderats",
"about.contact": "Contacte:",
"about.disclaimer": "Mastodon és un programari lliure de codi obert i una marca comercial de Mastodon gGmbH.",
"about.domain_blocks.no_reason_available": "Motiu no disponible",
"about.disclaimer": "Mastodon és programari lliure de codi obert i una marca comercial de Mastodon gGmbH.",
"about.domain_blocks.no_reason_available": "No és disponible el motiu",
"about.domain_blocks.preamble": "En general, Mastodon et permet veure el contingut i interaccionar amb els usuaris de qualsevol altre servidor del fedivers. Aquestes són les excepcions que s'han fet en aquest servidor particular.",
"about.domain_blocks.silenced.explanation": "Generalment no veuràs perfils ni contingut d'aquest servidor, a menys que el cerquis explícitament o optis per ell seguint-lo.",
"about.domain_blocks.silenced.explanation": "Generalment no veuràs perfils ni contingut d'aquest servidor, a menys que el cerquis explícitament o optis per seguir-lo.",
"about.domain_blocks.silenced.title": "Limitat",
"about.domain_blocks.suspended.explanation": "No es processaran, emmagatzemaran ni s'intercanviaran dades d'aquest servidor, fent impossible qualsevol interacció o comunicació amb els usuaris d'aquest servidor.",
"about.domain_blocks.suspended.explanation": "No es processaran, emmagatzemaran ni intercanviaran dades d'aquest servidor, fent impossible qualsevol interacció o comunicació amb els seus usuaris.",
"about.domain_blocks.suspended.title": "Suspès",
"about.not_available": "Aquesta informació no s'ha fet disponible en aquest servidor.",
"about.not_available": "Aquesta informació no és disponible en aquest servidor.",
"about.powered_by": "Xarxa social descentralitzada impulsada per {mastodon}",
"about.rules": "Normes del servidor",
"account.account_note_header": "Nota",
@ -19,19 +19,19 @@
"account.block_domain": "Bloqueja el domini {domain}",
"account.blocked": "Bloquejat",
"account.browse_more_on_origin_server": "Navega més en el perfil original",
"account.cancel_follow_request": "Retirar la sol·licitud de seguiment",
"account.cancel_follow_request": "Retira la sol·licitud de seguiment",
"account.direct": "Envia missatge directe a @{name}",
"account.disable_notifications": "No em notifiquis les publicacions de @{name}",
"account.domain_blocked": "Domini bloquejat",
"account.domain_blocked": "Domini blocat",
"account.edit_profile": "Edita el perfil",
"account.enable_notifications": "Notificam les publicacions de @{name}",
"account.enable_notifications": "Notifica'm les publicacions de @{name}",
"account.endorse": "Recomana en el perfil",
"account.featured_tags.last_status_at": "Última publicació el {date}",
"account.featured_tags.last_status_never": "Cap publicació",
"account.featured_tags.last_status_never": "No hi ha publicacions",
"account.featured_tags.title": "Etiquetes destacades de: {name}",
"account.follow": "Segueix",
"account.followers": "Seguidors",
"account.followers.empty": "Ningú segueix aquest usuari encara.",
"account.followers.empty": "Encara ningú no segueix aquest usuari.",
"account.followers_counter": "{count, plural, one {{counter} Seguidor} other {{counter} Seguidors}}",
"account.following": "Seguint",
"account.following_counter": "{count, plural, other {{counter} Seguint}}",
@ -52,25 +52,25 @@
"account.open_original_page": "Obre la pàgina original",
"account.posts": "Publicacions",
"account.posts_with_replies": "Publicacions i respostes",
"account.report": "Informa sobre @{name}",
"account.requested": "Esperant aprovació. Fes clic per cancel·lar la petició de seguiment",
"account.report": "Informa quant a @{name}",
"account.requested": "S'està esperant l'aprovació. Feu clic per a cancel·lar la petició de seguiment",
"account.share": "Comparteix el perfil de @{name}",
"account.show_reblogs": "Mostra els impulsos de @{name}",
"account.statuses_counter": "{count, plural, one {{counter} Publicació} other {{counter} Publicacions}}",
"account.unblock": "Desbloqueja @{name}",
"account.unblock_domain": "Desbloqueja el domini {domain}",
"account.unblock_short": "Desbloquejar",
"account.unendorse": "No recomanar en el perfil",
"account.unfollow": "Deixar de seguir",
"account.unblock_short": "Desbloqueja",
"account.unendorse": "No recomanis en el perfil",
"account.unfollow": "Deixa de seguir",
"account.unmute": "Deixar de silenciar @{name}",
"account.unmute_notifications": "Activar notificacions de @{name}",
"account.unmute_notifications": "Activa les notificacions de @{name}",
"account.unmute_short": "Deixa de silenciar",
"account_note.placeholder": "Clica per afegir-hi una nota",
"admin.dashboard.daily_retention": "Ràtio de retenció d'usuaris nous, per dia, després del registre",
"admin.dashboard.monthly_retention": "Ràtio de retenció d'usuaris nous, per mes, després del registre",
"admin.dashboard.daily_retention": "Ràtio de retenció d'usuaris nous per dia, després del registre",
"admin.dashboard.monthly_retention": "Ràtio de retenció d'usuaris nous per mes, després del registre",
"admin.dashboard.retention.average": "Mitjana",
"admin.dashboard.retention.cohort": "Mes del registre",
"admin.dashboard.retention.cohort_size": "Nous usuaris",
"admin.dashboard.retention.cohort": "Mes de registre",
"admin.dashboard.retention.cohort_size": "Usuaris nous",
"alert.rate_limited.message": "Si us plau, torna-ho a provar després de {retry_time, time, medium}.",
"alert.rate_limited.title": "Límit de freqüència",
"alert.unexpected.message": "S'ha produït un error inesperat.",
@ -79,21 +79,21 @@
"attachments_list.unprocessed": "(sense processar)",
"audio.hide": "Amaga l'àudio",
"autosuggest_hashtag.per_week": "{count} per setmana",
"boost_modal.combo": "Pots prémer {combo} per evitar-ho el pròxim cop",
"bundle_column_error.copy_stacktrace": "Copiar l'informe d'error",
"bundle_column_error.error.body": "No s'ha pogut renderitzar la pàgina sol·licitada. Podría ser degut a un error en el nostre codi o un problema de compatibilitat del navegador.",
"boost_modal.combo": "Podeu prémer {combo} per a evitar-ho el pròxim cop",
"bundle_column_error.copy_stacktrace": "Copia l'informe d'error",
"bundle_column_error.error.body": "No s'ha pogut renderitzar la pàgina sol·licitada. Podria ser per un error en el nostre codi o per un problema de compatibilitat del navegador.",
"bundle_column_error.error.title": "Oh, no!",
"bundle_column_error.network.body": "Hi ha hagut un error al intentar carregar aquesta pàgina. Això podria ser degut a un problem temporal amb la teva connexió a internet o amb aquest servidor.",
"bundle_column_error.network.title": "Error de xarxa",
"bundle_column_error.retry": "Tornar-ho a provar",
"bundle_column_error.network.body": "Hi ha hagut un error en intentar carregar aquesta pàgina. Això podria ser per un problema temporal amb la teva connexió a internet o amb aquest servidor.",
"bundle_column_error.network.title": "Error de connexió",
"bundle_column_error.retry": "Torna-ho a provar",
"bundle_column_error.return": "Torna a Inici",
"bundle_column_error.routing.body": "No es pot trobar la pàgina sol·licitada. Estàs segur que la URL de la barra d'adreces és correcte?",
"bundle_column_error.routing.body": "No es pot trobar la pàgina sol·licitada. Segur que la URL de la barra d'adreces és correcta?",
"bundle_column_error.routing.title": "404",
"bundle_modal_error.close": "Tanca",
"bundle_modal_error.message": "S'ha produït un error en carregar aquest component.",
"bundle_modal_error.retry": "Tornar-ho a provar",
"closed_registrations.other_server_instructions": "Donat que Mastodon és descentralitzat, pots crear un compte en un altre servidor i encara interactuar amb aquest.",
"closed_registrations_modal.description": "Crear un compte a {domain} no és possible ara mateix però, si us plau, tingues en compte que no necessites específicament un compte a {domain} per a usar Mastodon.",
"bundle_modal_error.retry": "Torna-ho a provar",
"closed_registrations.other_server_instructions": "Com que Mastodon és descentralitzat, pots crear un compte en un altre servidor i seguir interactuant amb aquest.",
"closed_registrations_modal.description": "No es pot crear un compte a {domain} ara mateix, però tingueu en compte que no necessiteu específicament un compte a {domain} per a usar Mastodon.",
"closed_registrations_modal.find_another_server": "Troba un altre servidor",
"closed_registrations_modal.preamble": "Mastodon és descentralitzat per tant no importa on tinguis el teu compte, seràs capaç de seguir i interactuar amb tothom des d'aquest servidor. Fins i tot pots tenir el compte en el teu propi servidor!",
"closed_registrations_modal.title": "Registrant-se a Mastodon",
@ -130,7 +130,7 @@
"compose_form.hashtag_warning": "Aquesta publicació no es mostrarà en cap etiqueta, ja que no està llistada. Només les publicacions públiques es poden cercar per etiqueta.",
"compose_form.lock_disclaimer": "El teu compte no està {locked}. Tothom pot seguir-te i veure les publicacions de només per a seguidors.",
"compose_form.lock_disclaimer.lock": "bloquejat",
"compose_form.placeholder": "Què et passa pel cap?",
"compose_form.placeholder": "Què tens en ment?",
"compose_form.poll.add_option": "Afegir una opció",
"compose_form.poll.duration": "Durada de l'enquesta",
"compose_form.poll.option_placeholder": "Opció {number}",
@ -210,7 +210,7 @@
"empty_column.account_timeline": "No hi ha publicacions aquí!",
"empty_column.account_unavailable": "Perfil no disponible",
"empty_column.blocks": "Encara no has bloquejat cap usuari.",
"empty_column.bookmarked_statuses": "Encara no has marcat com publicació com a preferida. Quan en marquis una apareixerà aquí.",
"empty_column.bookmarked_statuses": "Encara no has marcat cap publicació com a preferida. Quan en marquis una, apareixerà aquí.",
"empty_column.community": "La línia de temps local és buida. Escriu alguna cosa públicament per posar-ho tot en marxa!",
"empty_column.direct": "Encara no tens missatges directes. Quan n'enviïs o en rebis, es mostraran aquí.",
"empty_column.domain_blocks": "Encara no hi ha dominis bloquejats.",
@ -257,7 +257,7 @@
"filter_modal.title.status": "Filtra una publicació",
"follow_recommendations.done": "Fet",
"follow_recommendations.heading": "Segueix a la gent de la que t'agradaria veure les seves publicacions! Aquí hi ha algunes recomanacions.",
"follow_recommendations.lead": "Les publicacions del usuaris que segueixes es mostraran en ordre cronològic en la teva línia de temps Inici. No tinguis por en cometre errors, pots fàcilment deixar de seguir-los en qualsevol moment!",
"follow_recommendations.lead": "Les publicacions dels usuaris que segueixes es mostraran en ordre cronològic en la teva línia de temps Inici. No tinguis por en cometre errors, pots fàcilment deixar de seguir-los en qualsevol moment!",
"follow_request.authorize": "Autoritza",
"follow_request.reject": "Rebutja",
"follow_requests.unlocked_explanation": "Tot i que el teu compte no està bloquejat, el personal de {domain} ha pensat que és possible que vulguis revisar les sol·licituds de seguiment d’aquests comptes manualment.",
@ -294,7 +294,7 @@
"interaction_modal.on_this_server": "En aquest servidor",
"interaction_modal.other_server_instructions": "Copia i enganxa aquest enllaç en el camp de cerca de la teva aplicació Mastodon preferida o en l'interfície web del teu servidor Mastodon.",
"interaction_modal.preamble": "Donat que Mastodon és descentralitzat, pots fer servir el teu compte existent a un altre servidor Mastodon o plataforma compatible si és que no tens compte en aquest.",
"interaction_modal.title.favourite": "Afavoreix la publicació de {name}",
"interaction_modal.title.favourite": "Marca la publicació de {name}",
"interaction_modal.title.follow": "Segueix {name}",
"interaction_modal.title.reblog": "Impulsa la publicació de {name}",
"interaction_modal.title.reply": "Respon a la publicació de {name}",
@ -310,7 +310,7 @@
"keyboard_shortcuts.direct": "per obrir la columna de missatges directes",
"keyboard_shortcuts.down": "Mou-lo avall en la llista",
"keyboard_shortcuts.enter": "Obrir publicació",
"keyboard_shortcuts.favourite": "Afavoreix la publicació",
"keyboard_shortcuts.favourite": "Marca la publicació",
"keyboard_shortcuts.favourites": "Obre la llista de preferits",
"keyboard_shortcuts.federated": "Obre la línia de temps federada",
"keyboard_shortcuts.heading": "Dreceres de teclat",
@ -542,7 +542,7 @@
"status.admin_account": "Obre l'interfície de moderació per a @{name}",
"status.admin_status": "Obrir aquesta publicació a la interfície de moderació",
"status.block": "Bloqueja @{name}",
"status.bookmark": "Afavoreix",
"status.bookmark": "Marca",
"status.cancel_reblog_private": "Desfés l'impuls",
"status.cannot_reblog": "Aquesta publicació no es pot impulsar",
"status.copy": "Copia l'enllaç a la publicació",

@ -2,7 +2,7 @@
"about.blocks": "Gweinyddion sy'n cael eu cymedroli",
"about.contact": "Cyswllt:",
"about.disclaimer": "Mae Mastodon yn feddalwedd rhydd, cod agored ac o dan hawlfraint Mastodon gGmbH.",
"about.domain_blocks.no_reason_available": "Reason not available",
"about.domain_blocks.no_reason_available": "Rheswm ddim ar gael",
"about.domain_blocks.preamble": "Yn gyffredinol, mae Mastodon yn caniatáu i chi weld cynnwys gan unrhyw weinyddwr arall yn y ffederasiwn a rhyngweithio â hi. Dyma'r eithriadau a wnaed ar y gweinydd penodol hwn.",
"about.domain_blocks.silenced.explanation": "Yn gyffredinol, fyddwch chi ddim yn gweld proffiliau a chynnwys o'r gweinydd hwn, oni bai eich bod yn chwilio'n benodol amdano neu yn ymuno drwy ei ddilyn.",
"about.domain_blocks.silenced.title": "Tawelwyd",
@ -28,7 +28,7 @@
"account.endorse": "Arddangos ar fy mhroffil",
"account.featured_tags.last_status_at": "Y cofnod diwethaf ar {date}",
"account.featured_tags.last_status_never": "Dim postiadau",
"account.featured_tags.title": "{name}'s featured hashtags",
"account.featured_tags.title": "hashnodau dan sylw {name}",
"account.follow": "Dilyn",
"account.followers": "Dilynwyr",
"account.followers.empty": "Does neb yn dilyn y defnyddiwr hwn eto.",
@ -49,7 +49,7 @@
"account.mute": "Tawelu @{name}",
"account.mute_notifications": "Cuddio hysbysiadau o @{name}",
"account.muted": "Distewyd",
"account.open_original_page": "Open original page",
"account.open_original_page": "Agor y dudalen wreiddiol",
"account.posts": "Postiadau",
"account.posts_with_replies": "Postiadau ac atebion",
"account.report": "Adrodd @{name}",
@ -95,7 +95,7 @@
"closed_registrations.other_server_instructions": "Gan fod Mastodon yn ddatganoledig, gallwch greu cyfrif ar weinydd arall a dal i ryngweithio gyda hwn.",
"closed_registrations_modal.description": "Ar hyn o bryd nid yw'n bosib creu cyfrif ar {domain}, ond cadwch mewn cof nad oes raid i chi gael cyfrif yn benodol ar {domain} i ddefnyddio Mastodon.",