Unverified Commit 3550470c authored by David Yip's avatar David Yip
Browse files

Merge remote-tracking branch 'origin/master' into gs-master

  Conflicts:
 	app/javascript/mastodon/locales/en.json
 	app/javascript/mastodon/locales/ja.json
 	app/javascript/mastodon/locales/pl.json

The above conflicts appear to be a text conflict introduced by
glitch-soc's additional level of columns (i.e. moving a bunch of columns
under the Misc option).  They were resolved via accept-ours.
parents a641d1b5 00512ecf
...@@ -118,13 +118,23 @@ rules: ...@@ -118,13 +118,23 @@ rules:
jsx-a11y/accessible-emoji: warn jsx-a11y/accessible-emoji: warn
jsx-a11y/alt-text: warn jsx-a11y/alt-text: warn
jsx-a11y/anchor-has-content: warn jsx-a11y/anchor-has-content: warn
jsx-a11y/anchor-is-valid:
- warn
- components:
- Link
- NavLink
specialLink:
- to
aspect:
- noHref
- invalidHref
- preferButton
jsx-a11y/aria-activedescendant-has-tabindex: warn jsx-a11y/aria-activedescendant-has-tabindex: warn
jsx-a11y/aria-props: warn jsx-a11y/aria-props: warn
jsx-a11y/aria-proptypes: warn jsx-a11y/aria-proptypes: warn
jsx-a11y/aria-role: warn jsx-a11y/aria-role: warn
jsx-a11y/aria-unsupported-elements: warn jsx-a11y/aria-unsupported-elements: warn
jsx-a11y/heading-has-content: warn jsx-a11y/heading-has-content: warn
jsx-a11y/href-no-hash: warn
jsx-a11y/html-has-lang: warn jsx-a11y/html-has-lang: warn
jsx-a11y/iframe-has-title: warn jsx-a11y/iframe-has-title: warn
jsx-a11y/img-redundant-alt: warn jsx-a11y/img-redundant-alt: warn
......
import React from 'react';
import Motion from '../features/ui/util/optional_motion';
import spring from 'react-motion/lib/spring';
import PropTypes from 'prop-types';
const Collapsable = ({ fullHeight, isVisible, children }) => (
<Motion defaultStyle={{ opacity: !isVisible ? 0 : 100, height: isVisible ? fullHeight : 0 }} style={{ opacity: spring(!isVisible ? 0 : 100), height: spring(!isVisible ? 0 : fullHeight) }}>
{({ opacity, height }) => (
<div style={{ height: `${height}px`, overflow: 'hidden', opacity: opacity / 100, display: Math.floor(opacity) === 0 ? 'none' : 'block' }}>
{children}
</div>
)}
</Motion>
);
Collapsable.propTypes = {
fullHeight: PropTypes.number.isRequired,
isVisible: PropTypes.bool.isRequired,
children: PropTypes.node.isRequired,
};
export default Collapsable;
...@@ -7,7 +7,6 @@ import ReplyIndicatorContainer from '../containers/reply_indicator_container'; ...@@ -7,7 +7,6 @@ import ReplyIndicatorContainer from '../containers/reply_indicator_container';
import AutosuggestTextarea from '../../../components/autosuggest_textarea'; import AutosuggestTextarea from '../../../components/autosuggest_textarea';
import UploadButtonContainer from '../containers/upload_button_container'; import UploadButtonContainer from '../containers/upload_button_container';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages, injectIntl } from 'react-intl';
import Collapsable from '../../../components/collapsable';
import SpoilerButtonContainer from '../containers/spoiler_button_container'; import SpoilerButtonContainer from '../containers/spoiler_button_container';
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container'; import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
import SensitiveButtonContainer from '../containers/sensitive_button_container'; import SensitiveButtonContainer from '../containers/sensitive_button_container';
...@@ -160,17 +159,15 @@ export default class ComposeForm extends ImmutablePureComponent { ...@@ -160,17 +159,15 @@ export default class ComposeForm extends ImmutablePureComponent {
<div className='compose-form'> <div className='compose-form'>
<WarningContainer /> <WarningContainer />
<Collapsable isVisible={this.props.spoiler} fullHeight={50}>
<div className='spoiler-input'>
<label>
<span style={{ display: 'none' }}>{intl.formatMessage(messages.spoiler_placeholder)}</span>
<input placeholder={intl.formatMessage(messages.spoiler_placeholder)} value={this.props.spoiler_text} onChange={this.handleChangeSpoilerText} onKeyDown={this.handleKeyDown} type='text' className='spoiler-input__input' id='cw-spoiler-input' />
</label>
</div>
</Collapsable>
<ReplyIndicatorContainer /> <ReplyIndicatorContainer />
<div className={`spoiler-input ${this.props.spoiler ? 'spoiler-input--visible' : ''}`}>
<label>
<span style={{ display: 'none' }}>{intl.formatMessage(messages.spoiler_placeholder)}</span>
<input placeholder={intl.formatMessage(messages.spoiler_placeholder)} value={this.props.spoiler_text} onChange={this.handleChangeSpoilerText} onKeyDown={this.handleKeyDown} type='text' className='spoiler-input__input' id='cw-spoiler-input' />
</label>
</div>
<div className='compose-form__autosuggest-wrapper'> <div className='compose-form__autosuggest-wrapper'>
<AutosuggestTextarea <AutosuggestTextarea
ref={this.setAutosuggestTextarea} ref={this.setAutosuggestTextarea}
......
...@@ -4,7 +4,7 @@ import { fetchTrends } from '../../../actions/trends'; ...@@ -4,7 +4,7 @@ import { fetchTrends } from '../../../actions/trends';
const mapStateToProps = state => ({ const mapStateToProps = state => ({
results: state.getIn(['search', 'results']), results: state.getIn(['search', 'results']),
trends: state.get('trends'), trends: state.getIn(['trends', 'items']),
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({
......
import classNames from 'classnames';
import React from 'react';
import ImmutablePureComponent from 'react-immutable-pure-component';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { FormattedMessage, defineMessages } from 'react-intl';
import Hashtag from '../../../components/hashtag';
import { Link } from 'react-router-dom';
const messages = defineMessages({
refresh_trends: { id: 'trends.refresh', defaultMessage: 'Refresh' },
});
export default class Trends extends ImmutablePureComponent {
static defaultProps = {
loading: false,
};
static propTypes = {
trends: ImmutablePropTypes.list,
loading: PropTypes.bool.isRequired,
showTrends: PropTypes.bool.isRequired,
fetchTrends: PropTypes.func.isRequired,
toggleTrends: PropTypes.func.isRequired,
};
componentDidMount () {
setTimeout(() => this.props.fetchTrends(), 5000);
}
handleRefreshTrends = () => {
this.props.fetchTrends();
}
handleToggle = () => {
this.props.toggleTrends(!this.props.showTrends);
}
render () {
const { intl, trends, loading, showTrends } = this.props;
if (!trends || trends.size < 1) {
return null;
}
return (
<div className='getting-started__trends'>
<div className='column-header__wrapper'>
<h1 className='column-header'>
<button>
<i className='fa fa-fire fa-fw' />
<FormattedMessage id='trends.header' defaultMessage='Trending now' />
</button>
<div className='column-header__buttons'>
{showTrends && <button onClick={this.handleRefreshTrends} className='column-header__button' title={intl.formatMessage(messages.refresh_trends)} aria-label={intl.formatMessage(messages.refresh_trends)} disabled={loading}><i className={classNames('fa', 'fa-refresh', { 'fa-spin': loading })} /></button>}
<button onClick={this.handleToggle} className='column-header__button'><i className={classNames('fa', showTrends ? 'fa-chevron-down' : 'fa-chevron-up')} /></button>
</div>
</h1>
</div>
{showTrends && <div className='getting-started__scrollable'>
{trends.take(3).map(hashtag => <Hashtag key={hashtag.get('name')} hashtag={hashtag} />)}
<Link to='/trends' className='load-more'><FormattedMessage id='status.load_more' defaultMessage='Load more' /></Link>
</div>}
</div>
);
}
}
import { connect } from 'react-redux';
import { injectIntl } from 'react-intl';
import { fetchTrends } from '../../../actions/trends';
import Trends from '../components/trends';
import { changeSetting } from '../../../actions/settings';
const mapStateToProps = state => ({
trends: state.getIn(['trends', 'items']),
loading: state.getIn(['trends', 'isLoading']),
showTrends: state.getIn(['settings', 'trends', 'show']),
});
const mapDispatchToProps = dispatch => ({
fetchTrends: () => dispatch(fetchTrends()),
toggleTrends: show => dispatch(changeSetting(['trends', 'show'], show)),
});
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(Trends));
...@@ -11,9 +11,8 @@ import { me } from '../../initial_state'; ...@@ -11,9 +11,8 @@ import { me } from '../../initial_state';
import { fetchFollowRequests } from '../../actions/accounts'; import { fetchFollowRequests } from '../../actions/accounts';
import { List as ImmutableList } from 'immutable'; import { List as ImmutableList } from 'immutable';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { fetchTrends } from '../../actions/trends';
import Hashtag from '../../components/hashtag';
import NavigationBar from '../compose/components/navigation_bar'; import NavigationBar from '../compose/components/navigation_bar';
import TrendsContainer from './containers/trends_container';
const messages = defineMessages({ const messages = defineMessages({
home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' }, home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' },
...@@ -30,7 +29,6 @@ const messages = defineMessages({ ...@@ -30,7 +29,6 @@ const messages = defineMessages({
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' }, mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned toots' }, pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned toots' },
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' }, lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
refresh_trends: { id: 'trends.refresh', defaultMessage: 'Refresh' },
discover: { id: 'navigation_bar.discover', defaultMessage: 'Discover' }, discover: { id: 'navigation_bar.discover', defaultMessage: 'Discover' },
personal: { id: 'navigation_bar.personal', defaultMessage: 'Personal' }, personal: { id: 'navigation_bar.personal', defaultMessage: 'Personal' },
security: { id: 'navigation_bar.security', defaultMessage: 'Security' }, security: { id: 'navigation_bar.security', defaultMessage: 'Security' },
...@@ -39,12 +37,10 @@ const messages = defineMessages({ ...@@ -39,12 +37,10 @@ const messages = defineMessages({
const mapStateToProps = state => ({ const mapStateToProps = state => ({
myAccount: state.getIn(['accounts', me]), myAccount: state.getIn(['accounts', me]),
unreadFollowRequests: state.getIn(['user_lists', 'follow_requests', 'items'], ImmutableList()).size, unreadFollowRequests: state.getIn(['user_lists', 'follow_requests', 'items'], ImmutableList()).size,
trends: state.get('trends'),
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({
fetchFollowRequests: () => dispatch(fetchFollowRequests()), fetchFollowRequests: () => dispatch(fetchFollowRequests()),
fetchTrends: () => dispatch(fetchTrends()),
}); });
const badgeDisplay = (number, limit) => { const badgeDisplay = (number, limit) => {
...@@ -69,7 +65,6 @@ export default class GettingStarted extends ImmutablePureComponent { ...@@ -69,7 +65,6 @@ export default class GettingStarted extends ImmutablePureComponent {
fetchFollowRequests: PropTypes.func.isRequired, fetchFollowRequests: PropTypes.func.isRequired,
unreadFollowRequests: PropTypes.number, unreadFollowRequests: PropTypes.number,
unreadNotifications: PropTypes.number, unreadNotifications: PropTypes.number,
trends: ImmutablePropTypes.list,
}; };
componentDidMount () { componentDidMount () {
...@@ -78,40 +73,47 @@ export default class GettingStarted extends ImmutablePureComponent { ...@@ -78,40 +73,47 @@ export default class GettingStarted extends ImmutablePureComponent {
if (myAccount.get('locked')) { if (myAccount.get('locked')) {
fetchFollowRequests(); fetchFollowRequests();
} }
setTimeout(() => this.props.fetchTrends(), 5000);
} }
render () { render () {
const { intl, myAccount, multiColumn, unreadFollowRequests, trends } = this.props; const { intl, myAccount, multiColumn, unreadFollowRequests } = this.props;
const navItems = []; const navItems = [];
let i = 1;
let height = 0;
if (multiColumn) { if (multiColumn) {
navItems.push( navItems.push(
<ColumnSubheading key='1' text={intl.formatMessage(messages.discover)} />, <ColumnSubheading key={i++} text={intl.formatMessage(messages.discover)} />,
<ColumnLink key='2' icon='users' text={intl.formatMessage(messages.community_timeline)} to='/timelines/public/local' />, <ColumnLink key={i++} icon='users' text={intl.formatMessage(messages.community_timeline)} to='/timelines/public/local' />,
<ColumnLink key='3' icon='globe' text={intl.formatMessage(messages.public_timeline)} to='/timelines/public' />, <ColumnLink key={i++} icon='globe' text={intl.formatMessage(messages.public_timeline)} to='/timelines/public' />,
<ColumnSubheading key='8' text={intl.formatMessage(messages.personal)} /> <ColumnSubheading key={i++} text={intl.formatMessage(messages.personal)} />
); );
height += 34*2 + 48*2;
} }
navItems.push( navItems.push(
<ColumnLink key='4' icon='envelope' text={intl.formatMessage(messages.direct)} to='/timelines/direct' />, <ColumnLink key={i++} icon='envelope' text={intl.formatMessage(messages.direct)} to='/timelines/direct' />,
<ColumnLink key='5' icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />, <ColumnLink key={i++} icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />,
<ColumnLink key='6' icon='bars' text={intl.formatMessage(messages.lists)} to='/lists' /> <ColumnLink key={i++} icon='bars' text={intl.formatMessage(messages.lists)} to='/lists' />
); );
height += 48*3;
if (myAccount.get('locked')) { if (myAccount.get('locked')) {
navItems.push(<ColumnLink key='7' icon='users' text={intl.formatMessage(messages.follow_requests)} badge={badgeDisplay(unreadFollowRequests, 40)} to='/follow_requests' />); navItems.push(<ColumnLink key={i++} icon='users' text={intl.formatMessage(messages.follow_requests)} badge={badgeDisplay(unreadFollowRequests, 40)} to='/follow_requests' />);
height += 48;
} }
if (!multiColumn) { if (!multiColumn) {
navItems.push( navItems.push(
<ColumnSubheading key='9' text={intl.formatMessage(messages.settings_subheading)} />, <ColumnSubheading key={i++} text={intl.formatMessage(messages.settings_subheading)} />,
<ColumnLink key='6' icon='gears' text={intl.formatMessage(messages.preferences)} href='/settings/preferences' />, <ColumnLink key={i++} icon='gears' text={intl.formatMessage(messages.preferences)} href='/settings/preferences' />,
<ColumnLink key='6' icon='lock' text={intl.formatMessage(messages.security)} href='/auth/edit' /> <ColumnLink key={i++} icon='lock' text={intl.formatMessage(messages.security)} href='/auth/edit' />
); );
height += 34 + 48*2;
} }
return ( return (
...@@ -125,26 +127,12 @@ export default class GettingStarted extends ImmutablePureComponent { ...@@ -125,26 +127,12 @@ export default class GettingStarted extends ImmutablePureComponent {
</h1> </h1>
</div>} </div>}
<div className='getting-started__wrapper'> <div className='getting-started__wrapper' style={{ height }}>
{!multiColumn && <NavigationBar account={myAccount} />} {!multiColumn && <NavigationBar account={myAccount} />}
{navItems} {navItems}
</div> </div>
{multiColumn && trends && <div className='getting-started__trends'> {multiColumn && <TrendsContainer />}
<div className='column-header__wrapper'>
<h1 className='column-header'>
<button>
<i className='fa fa-fire fa-fw' />
<FormattedMessage id='trends.header' defaultMessage='Trending now' />
</button>
<div className='column-header__buttons'>
<button className='column-header__button' title={intl.formatMessage(messages.refresh_trends)} aria-label={intl.formatMessage(messages.refresh_trends)}><i className='fa fa-refresh' /></button>
</div>
</h1>
</div>
<div className='getting-started__scrollable'>{trends.take(3).map(hashtag => <Hashtag key={hashtag.get('name')} hashtag={hashtag} />)}</div>
</div>}
{!multiColumn && <div className='flex-spacer' />} {!multiColumn && <div className='flex-spacer' />}
......
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
import { injectIntl, defineMessages } from 'react-intl';
import Column from '../ui/components/column';
import ColumnHeader from '../../components/column_header';
import Hashtag from '../../components/hashtag';
import classNames from 'classnames';
import { fetchTrends } from '../../actions/trends';
const messages = defineMessages({
title: { id: 'trends.header', defaultMessage: 'Trending now' },
refreshTrends: { id: 'trends.refresh', defaultMessage: 'Refresh trends' },
});
const mapStateToProps = state => ({
trends: state.getIn(['trends', 'items']),
loading: state.getIn(['trends', 'isLoading']),
});
const mapDispatchToProps = dispatch => ({
fetchTrends: () => dispatch(fetchTrends()),
});
@connect(mapStateToProps, mapDispatchToProps)
@injectIntl
export default class Trends extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
trends: ImmutablePropTypes.list,
fetchTrends: PropTypes.func.isRequired,
loading: PropTypes.bool,
};
componentDidMount () {
this.props.fetchTrends();
}
handleRefresh = () => {
this.props.fetchTrends();
}
render () {
const { trends, loading, intl } = this.props;
return (
<Column>
<ColumnHeader
icon='fire'
title={intl.formatMessage(messages.title)}
extraButton={(
<button className='column-header__button' title={intl.formatMessage(messages.refreshTrends)} aria-label={intl.formatMessage(messages.refreshTrends)} onClick={this.handleRefresh}><i className={classNames('fa', 'fa-refresh', { 'fa-spin': loading })} /></button>
)}
/>
<div className='scrollable'>
{trends && trends.map(hashtag => <Hashtag key={hashtag.get('name')} hashtag={hashtag} />)}
</div>
</Column>
);
}
}
...@@ -42,6 +42,7 @@ import { ...@@ -42,6 +42,7 @@ import {
Mutes, Mutes,
PinnedStatuses, PinnedStatuses,
Lists, Lists,
Trends,
} from './util/async-components'; } from './util/async-components';
import { HotKeys } from 'react-hotkeys'; import { HotKeys } from 'react-hotkeys';
import { me } from '../../initial_state'; import { me } from '../../initial_state';
...@@ -154,6 +155,7 @@ class SwitchingColumnsArea extends React.PureComponent { ...@@ -154,6 +155,7 @@ class SwitchingColumnsArea extends React.PureComponent {
<WrappedRoute path='/pinned' component={PinnedStatuses} content={children} /> <WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />
<WrappedRoute path='/search' component={Compose} content={children} componentParams={{ isSearchPage: true }} /> <WrappedRoute path='/search' component={Compose} content={children} componentParams={{ isSearchPage: true }} />
<WrappedRoute path='/trends' component={Trends} content={children} />
<WrappedRoute path='/statuses/new' component={Compose} content={children} /> <WrappedRoute path='/statuses/new' component={Compose} content={children} />
<WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} /> <WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
......
...@@ -129,3 +129,7 @@ export function EmbedModal () { ...@@ -129,3 +129,7 @@ export function EmbedModal () {
export function ListEditor () { export function ListEditor () {
return import(/* webpackChunkName: "features/list_editor" */'../../list_editor'); return import(/* webpackChunkName: "features/list_editor" */'../../list_editor');
} }
export function Trends () {
return import(/* webpackChunkName: "features/trends" */'../../trends');
}
...@@ -58,7 +58,6 @@ ...@@ -58,7 +58,6 @@
"column_header.pin": "تدبيس", "column_header.pin": "تدبيس",
"column_header.show_settings": "عرض الإعدادات", "column_header.show_settings": "عرض الإعدادات",
"column_header.unpin": "فك التدبيس", "column_header.unpin": "فك التدبيس",
"column_subheading.navigation": "التصفح",
"column_subheading.settings": "الإعدادات", "column_subheading.settings": "الإعدادات",
"compose_form.direct_message_warning": "لن يَظهر هذا التبويق إلا للمستخدمين المذكورين.", "compose_form.direct_message_warning": "لن يَظهر هذا التبويق إلا للمستخدمين المذكورين.",
"compose_form.direct_message_warning_learn_more": "Learn more", "compose_form.direct_message_warning_learn_more": "Learn more",
...@@ -112,11 +111,10 @@ ...@@ -112,11 +111,10 @@
"empty_column.public": "لا يوجد أي شيء هنا ! قم بنشر شيء ما للعامة، أو إتبع مستخدمين آخرين في الخوادم المثيلة الأخرى لملء خيط المحادثات العام", "empty_column.public": "لا يوجد أي شيء هنا ! قم بنشر شيء ما للعامة، أو إتبع مستخدمين آخرين في الخوادم المثيلة الأخرى لملء خيط المحادثات العام",
"follow_request.authorize": "ترخيص", "follow_request.authorize": "ترخيص",
"follow_request.reject": "رفض", "follow_request.reject": "رفض",
"getting_started.appsshort": "تطبيقات", "getting_started.documentation": "Documentation",
"getting_started.faq": "أسئلة وأجوبة شائعة",
"getting_started.heading": "إستعدّ للبدء", "getting_started.heading": "إستعدّ للبدء",
"getting_started.open_source_notice": "ماستدون برنامج مفتوح المصدر. يمكنك المساهمة، أو الإبلاغ عن تقارير الأخطاء، على جيت هب {github}.", "getting_started.open_source_notice": "ماستدون برنامج مفتوح المصدر. يمكنك المساهمة، أو الإبلاغ عن تقارير الأخطاء، على جيت هب {github}.",
"getting_started.userguide": "دليل المستخدم", "getting_started.terms": "Terms of service",
"home.column_settings.advanced": "متقدمة", "home.column_settings.advanced": "متقدمة",
"home.column_settings.basic": "أساسية", "home.column_settings.basic": "أساسية",
"home.column_settings.filter_regex": "تصفية حسب التعبيرات العادية", "home.column_settings.filter_regex": "تصفية حسب التعبيرات العادية",
...@@ -160,6 +158,7 @@ ...@@ -160,6 +158,7 @@
"navigation_bar.blocks": "الحسابات المحجوبة", "navigation_bar.blocks": "الحسابات المحجوبة",
"navigation_bar.community_timeline": "الخيط العام المحلي", "navigation_bar.community_timeline": "الخيط العام المحلي",
"navigation_bar.direct": "الرسائل المباشِرة", "navigation_bar.direct": "الرسائل المباشِرة",
"navigation_bar.discover": "Discover",
"navigation_bar.domain_blocks": "النطاقات المخفية", "navigation_bar.domain_blocks": "النطاقات المخفية",
"navigation_bar.edit_profile": "تعديل الملف الشخصي", "navigation_bar.edit_profile": "تعديل الملف الشخصي",
"navigation_bar.favourites": "المفضلة", "navigation_bar.favourites": "المفضلة",
...@@ -169,9 +168,11 @@ ...@@ -169,9 +168,11 @@
"navigation_bar.lists": "القوائم", "navigation_bar.lists": "القوائم",
"navigation_bar.logout": "خروج", "navigation_bar.logout": "خروج",
"navigation_bar.mutes": "الحسابات المكتومة", "navigation_bar.mutes": "الحسابات المكتومة",
"navigation_bar.personal": "Personal",
"navigation_bar.pins": "التبويقات المثبتة", "navigation_bar.pins": "التبويقات المثبتة",
"navigation_bar.preferences": "التفضيلات", "navigation_bar.preferences": "التفضيلات",
"navigation_bar.public_timeline": "الخيط العام الموحد", "navigation_bar.public_timeline": "الخيط العام الموحد",
"navigation_bar.security": "Security",
"notification.favourite": "{name} أعجب بمنشورك", "notification.favourite": "{name} أعجب بمنشورك",
"notification.follow": "{name} يتابعك", "notification.follow": "{name} يتابعك",
"notification.mention": "{name} ذكرك",