diff --git a/lib/default.php b/lib/default.php index 18e26ef1e9..1de401d090 100644 --- a/lib/default.php +++ b/lib/default.php @@ -355,6 +355,7 @@ $default = 'Nodeinfo' => [], 'OpenID' => [], 'OpportunisticQM' => [], + 'RemoteFollow' => [], 'ActivityPub' => [], // The order is important here (IT HAS TO COME BEFORE OSTATUS) 'OStatus' => [], 'Poll' => [], diff --git a/plugins/RemoteFollow/EVENTS.txt b/plugins/RemoteFollow/EVENTS.txt new file mode 100644 index 0000000000..64771aa0fa --- /dev/null +++ b/plugins/RemoteFollow/EVENTS.txt @@ -0,0 +1,8 @@ +RemoteFollowConnectProfile: when the plugin is "one url away" from redirecting to the remote follower instance; federation plugins must find and retrieve the url for redirection +- User $target local user to be followed +- string $profile remote follower's ID +- string|null &$url url for redirection + +RemoteFollowPullProfile: when the plugin needs to pull the remote profile from its uri; federation plugins must pull, store and retrieve the profile's local object +- string $uri the remote profile's uri +- null|Profile &$profile pulled profile \ No newline at end of file diff --git a/plugins/RemoteFollow/README.md b/plugins/RemoteFollow/README.md new file mode 100644 index 0000000000..d0992dc7c6 --- /dev/null +++ b/plugins/RemoteFollow/README.md @@ -0,0 +1,41 @@ +# RemoteFollow Plugin +(c) 2019 Free Software Foundation, Inc + +This is the README file for GNU social's ActivityPub plugin. +It includes general information about the plugin. + +## About + +This plugin adds remote-follow button support to GNU social. + +## Installation + +This plugin is enabled by default. + +## Settings + +This plugin has no settings. + +## License + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public +License along with this program, in the file "COPYING". If not, see +. + + IMPORTANT NOTE: The GNU Affero General Public License (AGPL) has + *different requirements* from the "regular" GPL. In particular, if + you make modifications to the plugin source code on your server, + you *MUST MAKE AVAILABLE* the modified version of the source code + to your users under the same license. This is a legal requirement + of using the software, and if you do not wish to share your + modifications, *YOU MAY NOT USE THIS PLUGIN*. diff --git a/plugins/RemoteFollow/RemoteFollowPlugin.php b/plugins/RemoteFollow/RemoteFollowPlugin.php new file mode 100644 index 0000000000..81f1f14d43 --- /dev/null +++ b/plugins/RemoteFollow/RemoteFollowPlugin.php @@ -0,0 +1,159 @@ +. + +/** + * Remote Follow implementation for GNU social + * + * @package GNUsocial + * @author Bruno Casteleiro + * @copyright 2019 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ + +defined('GNUSOCIAL') || die(); + +class RemoteFollowPlugin extends Plugin +{ + const PLUGIN_VERSION = '0.1.0'; + + /** + * Route/Reroute urls + * + * @param URLMapper $m + * @return void + */ + public function onRouterInitialized(URLMapper $m): void + { + // discovery + $m->connect('main/remotefollow/nickname/:nickname', + ['action' => 'RemoteFollowInit'], + ['nickname' => Nickname::DISPLAY_FMT]); + $m->connect('main/remotefollow', + ['action' => 'RemoteFollowInit']); + + // remote follow + $m->connect('main/remotefollowsub', + ['action' => 'RemoteFollowSub']); + } + + /** + * Add remote-follow button to someone's profile + * + * @param HTMLOutputter $out + * @param Profile $target + * @return bool hook return value + */ + public function onStartProfileRemoteSubscribe(HTMLOutputter $out, Profile $target): bool + { + if (common_logged_in() || !$target->isLocal()) { + return true; + } + + $out->elementStart('li', 'entity_subscribe'); + $url = common_local_url('RemoteFollowInit', ['nickname' => $target->getNickname()]); + $out->element('a', + ['href' => $url, + 'class' => 'entity_remote_subscribe'], + // TRANS: Link text for the follow button + _m('Subscribe')); + $out->elementEnd('li'); + + return true; + } + + /** + * Add remote-follow button in the subscriptions list + * + * @param Action $action + * @return bool hook return value + */ + public function onStartShowSubscriptionsContent(Action $action): bool + { + $this->showEntityRemoteSubscribe($action); + return true; + } + + /** + * Add remote-follow button to the profile subscriptions minilist + * + * @param Action $action + * @return bool hook return value + */ + public function onEndShowSubscriptionsMiniList(Action $action): bool + { + $this->showEntityRemoteSubscribe($action); + return true; + } + + /** + * Add webfinger profile link for remote subscription + */ + function onEndWebFingerProfileLinks(XML_XRD $xrd, Profile $target): bool + { + $xrd->links[] = new XML_XRD_Element_Link('http://ostatus.org/schema/1.0/subscribe', + common_local_url('RemoteFollowSub') . '?profile={uri}', + null, // type not set + true); // isTemplate + + return true; + } + + /** + * Plugin version information + * + * @param array $versions + * @return bool hook return value + */ + public function onPluginVersion(array &$versions): bool + { + $versions[] = [ + 'name' => 'RemoteFollow', + 'version' => self::PLUGIN_VERSION, + 'author' => 'Bruno Casteleiro', + 'homepage' => 'https://notabug.org/diogo/gnu-social/src/nightly/plugins/RemoteFollow', + // TRANS: Plugin description. + 'rawdescription' => _m('Add remote-follow button support to GNU social') + ]; + return true; + } + + /** + * Add remote-follow button to some required action + * + * @param Action $action + * @return void + */ + public function showEntityRemoteSubscribe(Action $action): void + { + if (!$action->getScoped() instanceof Profile) { + // not logged in + return; + } + + if ($action->getScoped()->sameAs($action->getTarget())) { + $action->elementStart('div', 'entity_actions'); + $action->elementStart('p', ['id' => 'entity_remote_subscribe', + 'class' => 'entity_subscribe']); + $action->element('a', + ['href' => common_local_url('RemoteFollowSub'), + 'class' => 'entity_remote_subscribe'], + // TRANS: Link text for link to remote subscribe. + _m('Remote')); + $action->elementEnd('p'); + $action->elementEnd('div'); + } + } +} diff --git a/plugins/RemoteFollow/actions/remotefollowinit.php b/plugins/RemoteFollow/actions/remotefollowinit.php new file mode 100644 index 0000000000..4b025b0442 --- /dev/null +++ b/plugins/RemoteFollow/actions/remotefollowinit.php @@ -0,0 +1,236 @@ +. + +/** + * Remote Follow implementation for GNU social + * + * @package GNUsocial + * @author Bruno Casteleiro + * @copyright 2019 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ + +defined('GNUSOCIAL') || die(); + +/** + * Remote-follow preparation action + * + * @category Plugin + * @package GNUsocial + * @author Bruno Casteleiro + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class RemoteFollowInitAction extends Action +{ + protected $target = null; + protected $profile = null; + + protected function prepare(array $args = []) + { + parent::prepare($args); + + if (common_logged_in()) { + // TRANS: Client error displayed when the user is logged in. + $this->clientError(_m('You can use the local subscription!')); + } + + // Local user the remote wants to follow + $nickname = $this->trimmed('nickname'); + + $this->target = User::getKV('nickname', $nickname); + if (!$this->target instanceof User) { + // TRANS: Client error displayed when targeting an invalid user. + $this->clientError(_m('No such user.')); + } + + // Webfinger or profile URL of the remote user + $this->profile = $this->trimmed('profile'); + + return true; + } + + protected function handle() + { + parent::handle(); + + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + /* Use a session token for CSRF protection. */ + $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { + // TRANS: Error displayed when the session token does not match or is not given. + $this->showForm(_m('There was a problem with your session token. '. + 'Try again, please.')); + return; + } + + $url = null; + if (Event::handle('RemoteFollowConnectProfile', [$this->target, $this->profile, &$url])) { + // use ported ostatus connect functions to find remote url + $url = self::ostatusConnect($this->target, $this->profile); + } + + if (!is_null($url)) { + common_redirect($url, 303); + } + + // TRANS: Error displayed when there is failure in connecting with the remote profile. + $this->showForm(_m('There was a problem connecting with the remote profile. '. + 'Try again, please.')); + + } else { + $this->showForm(); + } + } + + function showContent() + { + // TRANS: Form legend. %s is a nickname. + $header = sprintf(_m('Subscribe to %s'), $this->target->getNickname()); + // TRANS: Button text to subscribe to a profile. + $submit = _m('BUTTON', 'Subscribe'); + + $this->elementStart('form', + ['id' => 'form_ostatus_connect', + 'method' => 'post', + 'class' => 'form_settings', + 'action' => common_local_url('RemoteFollowInit')]); + $this->elementStart('fieldset'); + $this->element('legend', null, $header); + $this->hidden('token', common_session_token()); + + $this->elementStart('ul', 'form_data'); + $this->elementStart('li', ['id' => 'ostatus_nickname']); + + $this->input('nickname', + // TRANS: Field label. + _m('User nickname'), + $this->target->getNickname(), + // TRANS: Field title. + _m('Nickname of the user you want to follow.')); + + $this->elementEnd('li'); + $this->elementStart('li', ['id' => 'ostatus_profile']); + $this->input('profile', + // TRANS: Field label. + _m('Profile Account'), + $this->profile, + // TRANS: Tooltip for field label "Profile Account". + _m('Your account ID (e.g. user@example.com).')); + $this->elementEnd('li'); + $this->elementEnd('ul'); + $this->submit('submit', $submit); + $this->elementEnd('fieldset'); + $this->elementEnd('form'); + } + + public function showForm($err = null) + { + if ($err) { + $this->error = $err; + } + + if ($this->boolean('ajax')) { + $this->startHTML('text/xml;charset=utf-8'); + $this->elementStart('head'); + // TRANS: Form title. + $this->element('title', null, _m('TITLE','Subscribe to user')); + $this->elementEnd('head'); + $this->elementStart('body'); + $this->showContent(); + $this->elementEnd('body'); + $this->endHTML(); + } else { + $this->showPage(); + } + } + + /** + * Find remote url to finish follow interaction + * + * @param User $target local user to be followed + * @param string $remote ID of the remote subscriber + * @return string|null + */ + public static function ostatusConnect(User $target, string $remote): ?string + { + $validate = new Validate(); + $opts = ['allowed_schemes' => ['http', 'https', 'acct']]; + if ($validate->uri($remote, $opts)) { + $bits = parse_url($remote); + if ($bits['scheme'] == 'acct') { + return self::connectWebfinger($bits['path'], $target); + } else { + return self::connectProfile($remote, $target); + } + } else if (strpos($remote, '@') !== false) { + return self::connectWebfinger($remote, $target); + } + + common_log(LOG_ERR, 'Must provide a remote profile'); + return null; + } + + /** + * Find remote url to finish follow interaction from a webfinger ID + * + * @param string $acct + * @param User $target + * @return string|null + * @see ostatusConnect + */ + public static function connectWebfinger(string $acct, User $target): ?string + { + $target = common_local_url('userbyid', ['id' => $target->getID()]); + + $disco = new Discovery; + $xrd = $disco->lookup($acct); + + $link = $xrd->get('http://ostatus.org/schema/1.0/subscribe'); + if (!is_null($link)) { + // We found a URL - let's redirect! + if (!empty($link->template)) { + $url = Discovery::applyTemplate($link->template, $target); + } else { + $url = $link->href; + } + common_log(LOG_INFO, "Retrieving url $url for remote subscriber $acct"); + return $url; + } + + common_log(LOG_ERR, "Could not confirm remote profile $acct"); + return null; + } + + /** + * Find remote url to finish follow interaction from an url ID + * + * @param string $acct + * @param User $target + * @return string + * @see ostatusConnect + */ + public static function connectProfile(string $url, User $target): string + { + $target = common_local_url('userbyid', ['id' => $target->getID()]); + + // @fixme hack hack! We should look up the remote sub URL from XRDS + $suburl = preg_replace('!^(.*)/(.*?)$!', '$1/main/ostatussub', $url); + $suburl .= '?profile=' . urlencode($target); + + common_log(LOG_INFO, "Retrieving url $suburl for remote subscriber $url"); + return $suburl; + } +} \ No newline at end of file diff --git a/plugins/RemoteFollow/actions/remotefollowsub.php b/plugins/RemoteFollow/actions/remotefollowsub.php new file mode 100644 index 0000000000..a713542e52 --- /dev/null +++ b/plugins/RemoteFollow/actions/remotefollowsub.php @@ -0,0 +1,394 @@ +. + +/** + * Remote Follow implementation for GNU social + * + * @package GNUsocial + * @author Bruno Casteleiro + * @copyright 2019 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ + +defined('GNUSOCIAL') || die(); + +/** + * Remote-follow follow action + * + * @category Plugin + * @package GNUsocial + * @author Bruno Casteleiro + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class RemoteFollowSubAction extends Action +{ + protected $uri; // acct: or uri of remote entity + protected $profile; // profile of remote entity, if valid + + protected function prepare(array $args = []) + { + parent::prepare($args); + + if (!common_logged_in()) { + common_set_returnto($_SERVER['REQUEST_URI']); + if (Event::handle('RedirectToLogin', [$this, null])) { + common_redirect(common_local_url('login'), 303); + } + return false; + } + + if (!$this->profile && $this->arg('profile')) { + $this->uri = $this->trimmed('profile'); + + $profile = null; + if (!Event::handle('RemoteFollowPullProfile', [$this->uri, &$profile]) && !is_null($profile)) { + $this->profile = $profile; + } else { + // TRANS: Error displayed when there's failure in fetching the remote profile. + $this->error = _m('Sorry, we could not reach that address. ' . + 'Please make sure it is a valid address and try again later.'); + } + } + + return true; + } + + /** + * Handles the submission. + * + * @return void + */ + protected function handle(): void + { + parent::handle(); + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + $this->handlePost(); + } else { + $this->showForm(); + } + } + + /** + * Show the initial form, when we haven't yet been given a valid + * remote profile. + * + * @return void + */ + public function showInputForm(): void + { + $this->elementStart('form', ['method' => 'post', + 'id' => 'form_ostatus_sub', + 'class' => 'form_settings', + 'action' => $this->selfLink()]); + + $this->hidden('token', common_session_token()); + + $this->elementStart('fieldset', ['id' => 'settings_feeds']); + + $this->elementStart('ul', 'form_data'); + $this->elementStart('li'); + $this->input('profile', + // TRANS: Field label for a field that takes an user address. + _m('Subscribe to'), + $this->uri, + // TRANS: Tooltip for field label "Subscribe to". + _m('User\'s address, like nickname@example.com or http://example.net/nickname.')); + $this->elementEnd('li'); + $this->elementEnd('ul'); + // TRANS: Button text. + $this->submit('validate', _m('BUTTON','Continue')); + + $this->elementEnd('fieldset'); + + $this->elementEnd('form'); + } + + /** + * Show the preview-and-confirm form. We've got a valid remote + * profile and are ready to poke it! + * + * @return void + */ + public function showPreviewForm(): void + { + if (!$this->preview()) { + return; + } + + $this->elementStart('div', 'entity_actions'); + $this->elementStart('ul'); + $this->elementStart('li', 'entity_subscribe'); + $this->elementStart('form', ['method' => 'post', + 'id' => 'form_ostatus_sub', + 'class' => 'form_remote_authorize', + 'action' => $this->selfLink()]); + $this->elementStart('fieldset'); + $this->hidden('token', common_session_token()); + $this->hidden('profile', $this->uri); + $this->submit('submit', + // TRANS: Button text. + _m('BUTTON','Confirm'), + 'submit', + null, + // TRANS: Tooltip for button "Confirm". + _m('Subscribe to this user')); + $this->elementEnd('fieldset'); + $this->elementEnd('form'); + $this->elementEnd('li'); + $this->elementEnd('ul'); + $this->elementEnd('div'); + } + + /** + * Show a preview for a remote user's profile. + * + * @return bool true if we're ok to try subscribing, false otherwise + */ + public function preview(): bool + { + if ($this->scoped->isSubscribed($this->profile)) { + $this->element('div', + ['class' => 'error'], + // TRANS: Extra paragraph in remote profile view when already subscribed. + _m('You are already subscribed to this user.')); + $ok = false; + } else { + $ok = true; + } + + $avatarUrl = $this->profile->avatarUrl(AVATAR_PROFILE_SIZE); + + $this->showEntity($this->profile, + $this->profile->getUrl(), + $avatarUrl, + $this->profile->getDescription()); + return $ok; + } + + /** + * Show someone's profile. + * + * @return void + */ + public function showEntity(Profile $entity, string $profile_url, string $avatar, ?string $note): void + { + $nickname = $entity->getNickname(); + $fullname = $entity->getFullname(); + $homepage = $entity->getHomepage(); + $location = $entity->getLocation(); + + $this->elementStart('div', 'entity_profile vcard'); + $this->element('img', ['src' => $avatar, + 'class' => 'photo avatar entity_depiction', + 'width' => AVATAR_PROFILE_SIZE, + 'height' => AVATAR_PROFILE_SIZE, + 'alt' => $nickname]); + + $hasFN = ($fullname !== '') ? 'nickname' : 'fn nickname entity_nickname'; + $this->elementStart('a', ['href' => $profile_url, + 'class' => 'url '.$hasFN]); + $this->text($nickname); + $this->elementEnd('a'); + + if (!is_null($fullname)) { + $this->elementStart('div', 'fn entity_fn'); + $this->text($fullname); + $this->elementEnd('div'); + } + + if (!is_null($location)) { + $this->elementStart('div', 'label entity_location'); + $this->text($location); + $this->elementEnd('div'); + } + + if (!is_null($homepage)) { + $this->elementStart('a', ['href' => $homepage, + 'class' => 'url entity_url']); + $this->text($homepage); + $this->elementEnd('a'); + } + + if (!is_null($note)) { + $this->elementStart('div', 'note entity_note'); + $this->text($note); + $this->elementEnd('div'); + } + $this->elementEnd('div'); + } + + /** + * Redirect on successful remote follow + * + * @return void + */ + public function success(): void + { + $url = common_local_url('subscriptions', ['nickname' => $this->scoped->getNickname()]); + common_redirect($url, 303); + } + + /** + * Attempt to finalize subscription. + * + * @return void + */ + public function follow(): void + { + if ($this->scoped->isSubscribed($this->profile)) { + // TRANS: Remote subscription dialog error. + $this->showForm(_m('Already subscribed!')); + } elseif (Subscription::start($this->scoped, $this->profile)) { + $this->success(); + } else { + // TRANS: Remote subscription dialog error. + $this->showForm(_m('Remote subscription failed!')); + } + } + + /** + * Handle posts to this form + * + * @return void + */ + public function handlePost(): void + { + // CSRF protection + $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { + // TRANS: Client error displayed when the session token does not match or is not given. + $this->showForm(_m('There was a problem with your session token. '. + 'Try again, please.')); + return; + } + + if ($this->profile && $this->arg('submit')) { + $this->follow(); + return; + } + + $this->showForm(); + } + + /** + * Show the appropriate form based on our input state. + * + * @return void + */ + public function showForm(?string $err = null): void + { + if ($err) { + $this->error = $err; + } + + if ($this->boolean('ajax')) { + $this->startHTML('text/xml;charset=utf-8'); + $this->elementStart('head'); + // TRANS: Form title. + $this->element('title', null, _m('Subscribe to user')); + $this->elementEnd('head'); + $this->elementStart('body'); + $this->showContent(); + $this->elementEnd('body'); + $this->endHTML(); + } else { + $this->showPage(); + } + } + + /** + * Title of the page + * + * @return string title of the page + */ + public function title(): string + { + // TRANS: Page title for remote subscription form. + return !empty($this->uri) ? _m('Confirm') : _m('Remote subscription'); + } + + /** + * Instructions for use + * + * @return string instructions for use + */ + public function getInstructions(): string + { + // TRANS: Instructions. + return _m('You can subscribe to users from other supported sites. Paste their address or profile URI below:'); + } + + /** + * Show page notice. + * + * @return void + */ + public function showPageNotice(): void + { + if (!empty($this->error)) { + $this->element('p', 'error', $this->error); + } + } + + /** + * Content area of the page + * + * @return void + */ + public function showContent(): void + { + if ($this->profile) { + $this->showPreviewForm(); + } else { + $this->showInputForm(); + } + } + + /** + * Show javascript headers + * + * @return void + */ + public function showScripts(): void + { + parent::showScripts(); + $this->autofocus('profile'); + } + + /** + * Return url for this action + * + * @return string + */ + function selfLink(): string + { + return common_local_url('RemoteFollowSub'); + } + + /** + * Disable the send-notice form at the top of the page. + * This is really just a hack for the broken CSS in the Cloudy theme, + * I think; copying from other non-notice-navigation pages that do this + * as well. There will be plenty of others also broken. + * + * @fixme fix the cloudy theme + * @fixme do this in a more general way + */ + public function showNoticeForm(): void + { + // nop + } +} \ No newline at end of file diff --git a/plugins/RemoteFollow/locale/RemoteFollow.pot b/plugins/RemoteFollow/locale/RemoteFollow.pot new file mode 100644 index 0000000000..298a3e8035 --- /dev/null +++ b/plugins/RemoteFollow/locale/RemoteFollow.pot @@ -0,0 +1,163 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-08-25 01:23+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#. TRANS: Link text for the follow button +#: RemoteFollowPlugin.php:70 +msgid "Subscribe" +msgstr "" + +#. TRANS: Plugin description. +#: RemoteFollowPlugin.php:127 +msgid "Add remote-follow button support to GNU social" +msgstr "" + +#. TRANS: Link text for link to remote subscribe. +#: RemoteFollowPlugin:153 +msgid "Remote" +msgstr "" + +#. TRANS: Client error displayed when the user is logged in. +#: remotefollowinit.php:46 +msgid "You can use the local subscription!" +msgstr "" + +#. TRANS: Client error displayed when targeting an invalid user. +#: remotefollowinit.php:55 +msgid "No such user." +msgstr "" + +#. TRANS: Error displayed when the session token does not match or is not given. +#: remotefollowinit.php:73, remotefollowsub.php:272 +msgid "" +"There was a problem with your session token. " +"Try again, please." +msgstr "" + +#. TRANS: Error displayed when there is failure in connecting with the remote profile. +#: remotefollowinit.php:89 +msgid "" +"There was a problem connecting with the remote profile. " +"Try again, please." +msgstr "" + +#. TRANS: Form legend. %s is a nickname. +#: remotefollowinit.php:100 +msgid "Subscribe to %s" +msgstr "" + +#. TRANS: Button text to subscribe to a profile. +#: remotefollowinit.php:102 +msgctxt "BUTTON" +msgid "Subscribe" +msgstr "" + +#. TRANS: Field label. +#: remotefollowinit.php:118 +msgid "User nickname" +msgstr "" + +#. TRANS: Field title. +#: remotefollowinit.php:121 +msgid "Nickname of the user you want to follow." +msgstr "" + +#. TRANS: Field label. +#: remotefollowinit.php:127 +msgid "Profile Account" +msgstr "" + +#. TRANS: Tooltip for field label "Profile Account". +#: remotefollowinit.php:130 +msgid "Your account ID (e.g. user@example.com)." +msgstr "" + +#. TRANS: Form title. +#: remotefollowinit.php:148 +msgctxt "TITLE" +msgid "Subscribe to user" +msgstr "" + +#. TRANS: Error displayed when there's failure in fetching the remote profile. +#: remotefollowsub.php:60 +msgid "" +"Sorry, we could not reach that address. " +"Please make sure it is a valid address and try again later." +msgstr "" + +#. TRANS: Field label for a field that takes an user address. +#: remotefollowsub.php:104 +msgid "Subscribe to" +msgstr "" + +#. TRANS: Tooltip for field label "Subscribe to". +#: remotefollowsub.php:107 +msgid "User\'s address, like nickname@example.com or http://example.net/nickname." +msgstr "" + +#. TRANS: Button text. +#: remotefollowsub.php:111 +msgctxt "BUTTON" +msgid "Continue" +msgstr "" + +#. TRANS: Button text. +#: remotefollowsub.php:142 +msgctxt "BUTTON" +msgid "Confirm" +msgstr "" + +#. TRANS: Tooltip for button "Confirm". +#: remotefollowsub.php:147 +msgid "Subscribe to this user" +msgstr "" + +#. TRANS: Extra paragraph in remote profile view when already subscribed. +#: remotefollowsub.php:165 +msgid "You are already subscribed to this user." +msgstr "" + +#. TRANS: Remote subscription dialog error. +#: remotefollowsub.php:252 +msgid "Already subscribed!" +msgstr "" + +#. TRANS: Remote subscription dialog error. +#: remotefollowsub.php:257 +msgid "Remote subscription failed!" +msgstr "" + +#. TRANS: Form title. +#: remotefollowsub.php:300 +msgid "Subscribe to user" +msgstr "" + +#. TRANS: Page title for remote subscription form. +#: remotefollowsub.php:319 +msgid "Confirm" +msgstr "" + +#. TRANS: Page title for remote subscription form. +#: remotefollowsub.php:319 +msgid "Remote subscription" +msgstr "" + +#. TRANS: Instructions. +#: remotefollowsub.php:330 +msgid "You can subscribe to users from other supported sites. Paste their address or profile URI below" +msgstr "" \ No newline at end of file diff --git a/plugins/RemoteFollow/locale/en_GB/LC_MESSAGES/RemoteFollow.po b/plugins/RemoteFollow/locale/en_GB/LC_MESSAGES/RemoteFollow.po new file mode 100644 index 0000000000..9521d4d91a --- /dev/null +++ b/plugins/RemoteFollow/locale/en_GB/LC_MESSAGES/RemoteFollow.po @@ -0,0 +1,170 @@ +# Translation file for GNU social - the free software social networking platform +# Copyright (C) 2019 Free Software Foundation, Inc http://www.fsf.org +# This file is under https://www.gnu.org/licenses/agpl v3 or later +# +# Translators: +# Bruno Casteleiro , 2019 +msgid "" +msgstr "" +"Project-Id-Version: GNU social\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-08-25 01:23+0100\n" +"PO-Revision-Date: 2019-08-25 01:23+0100\n" +"Last-Translator: Bruno Casteleiro \n" +"Language-Team: English (United Kingdom) (http://www.transifex.com/gnu-social/gnu-social/language/en_GB/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: en_GB\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. TRANS: Link text for the follow button +#: RemoteFollowPlugin.php:70 +msgid "Subscribe" +msgstr "Subscribe" + +#. TRANS: Plugin description. +#: RemoteFollowPlugin.php:127 +msgid "Add remote-follow button support to GNU social" +msgstr "Add remote-follow button support to GNU social" + +#. TRANS: Link text for link to remote subscribe. +#: RemoteFollowPlugin:153 +msgid "Remote" +msgstr "Remote" + +#. TRANS: Client error displayed when the user is logged in. +#: remotefollowinit.php:46 +msgid "You can use the local subscription!" +msgstr "You can use the local subscription!" + +#. TRANS: Client error displayed when targeting an invalid user. +#: remotefollowinit.php:55 +msgid "No such user." +msgstr "No such user." + +#. TRANS: Error displayed when the session token does not match or is not given. +#: remotefollowinit.php:73, remotefollowsub.php:272 +msgid "" +"There was a problem with your session token. " +"Try again, please." +msgstr "" +"There was a problem with your session token. " +"Try again, please." + +#. TRANS: Error displayed when there is failure in connecting with the remote profile. +#: remotefollowinit.php:89 +msgid "" +"There was a problem connecting with the remote profile. " +"Try again, please." +msgstr "" +"There was a problem connecting with the remote profile. " +"Try again, please." + +#. TRANS: Form legend. %s is a nickname. +#: remotefollowinit.php:100 +msgid "Subscribe to %s" +msgstr "Subscribe to %s" + +#. TRANS: Button text to subscribe to a profile. +#: remotefollowinit.php:102 +msgctxt "BUTTON" +msgid "Subscribe" +msgstr "Subscribe" + +#. TRANS: Field label. +#: remotefollowinit.php:118 +msgid "User nickname" +msgstr "User nickname" + +#. TRANS: Field title. +#: remotefollowinit.php:121 +msgid "Nickname of the user you want to follow." +msgstr "Nickname of the user you want to follow." + +#. TRANS: Field label. +#: remotefollowinit.php:127 +msgid "Profile Account" +msgstr "Profile Account" + +#. TRANS: Tooltip for field label "Profile Account". +#: remotefollowinit.php:130 +msgid "Your account ID (e.g. user@example.com)." +msgstr "Your account ID (e.g. user@example.com)." + +#. TRANS: Form title. +#: remotefollowinit.php:148 +msgctxt "TITLE" +msgid "Subscribe to user" +msgstr "Subscribe to user" + +#. TRANS: Error displayed when there's failure in fetching the remote profile. +#: remotefollowsub.php:60 +msgid "" +"Sorry, we could not reach that address. " +"Please make sure it is a valid address and try again later." +msgstr "" +"Sorry, we could not reach that address. " +"Please make sure it is a valid address and try again later." + +#. TRANS: Field label for a field that takes an user address. +#: remotefollowsub.php:104 +msgid "Subscribe to" +msgstr "Subscribe to" + +#. TRANS: Tooltip for field label "Subscribe to". +#: remotefollowsub.php:107 +msgid "User's address, like nickname@example.com or http://example.net/nickname." +msgstr "User's address, like nickname@example.com or http://example.net/nickname." + +#. TRANS: Button text. +#: remotefollowsub.php:111 +msgctxt "BUTTON" +msgid "Continue" +msgstr "Continue" + +#. TRANS: Button text. +#: remotefollowsub.php:142 +msgctxt "BUTTON" +msgid "Confirm" +msgstr "Confirm" + +#. TRANS: Tooltip for button "Confirm". +#: remotefollowsub.php:147 +msgid "Subscribe to this user" +msgstr "Subscribe to this user" + +#. TRANS: Extra paragraph in remote profile view when already subscribed. +#: remotefollowsub.php:165 +msgid "You are already subscribed to this user." +msgstr "You are already subscribed to this user." + +#. TRANS: Remote subscription dialog error. +#: remotefollowsub.php:252 +msgid "Already subscribed!" +msgstr "Already subscribed!" + +#. TRANS: Remote subscription dialog error. +#: remotefollowsub.php:257 +msgid "Remote subscription failed!" +msgstr "Remote subscription failed!" + +#. TRANS: Form title. +#: remotefollowsub.php:300 +msgid "Subscribe to user" +msgstr "Subscribe to user" + +#. TRANS: Page title for remote subscription form. +#: remotefollowsub.php:319 +msgid "Confirm" +msgstr "Confirm" + +#. TRANS: Page title for remote subscription form. +#: remotefollowsub.php:319 +msgid "Remote subscription" +msgstr "Remote subscription" + +#. TRANS: Instructions. +#: remotefollowsub.php:330 +msgid "You can subscribe to users from other supported sites. Paste their address or profile URI below" +msgstr "You can subscribe to users from other supported sites. Paste their address or profile URI below" \ No newline at end of file diff --git a/plugins/RemoteFollow/locale/pt/LC_MESSAGES/RemoteFollow.po b/plugins/RemoteFollow/locale/pt/LC_MESSAGES/RemoteFollow.po new file mode 100644 index 0000000000..9b64cd4461 --- /dev/null +++ b/plugins/RemoteFollow/locale/pt/LC_MESSAGES/RemoteFollow.po @@ -0,0 +1,170 @@ +# Translation file for GNU social - the free software social networking platform +# Copyright (C) 2019 Free Software Foundation, Inc http://www.fsf.org +# This file is under https://www.gnu.org/licenses/agpl v3 or later +# +# Translators: +# Bruno Casteleiro , 2019 +msgid "" +msgstr "" +"Project-Id-Version: GNU social\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-08-25 01:23+0100\n" +"PO-Revision-Date: 2019-08-25 01:23+0100\n" +"Last-Translator: Bruno Casteleiro \n" +"Language-Team: Portuguese (http://www.transifex.com/gnu-social/gnu-social/language/pt/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: pt\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. TRANS: Link text for the follow button +#: RemoteFollowPlugin.php:70 +msgid "Subscribe" +msgstr "Subscrever" + +#. TRANS: Plugin description. +#: RemoteFollowPlugin.php:127 +msgid "Add remote-follow button support to GNU social" +msgstr "Adiciona suporte para o botão de subscrição remota ao GNU social" + +#. TRANS: Link text for link to remote subscribe. +#: RemoteFollowPlugin:153 +msgid "Remote" +msgstr "Remoto" + +#. TRANS: Client error displayed when the user is logged in. +#: remotefollowinit.php:46 +msgid "You can use the local subscription!" +msgstr "Pode usar a subscrição local!" + +#. TRANS: Client error displayed when targeting an invalid user. +#: remotefollowinit.php:55 +msgid "No such user." +msgstr "Utilizador inexistente." + +#. TRANS: Error displayed when the session token does not match or is not given. +#: remotefollowinit.php:73, remotefollowsub.php:272 +msgid "" +"There was a problem with your session token. " +"Try again, please." +msgstr "" +"Ocorreu um problema com o seu token de sessão. " +"Tente outra vez, por favor." + +#. TRANS: Error displayed when there is failure in connecting with the remote profile. +#: remotefollowinit.php:89 +msgid "" +"There was a problem connecting with the remote profile. " +"Try again, please." +msgstr "" +"Ocorreu um problema ao conectar com o perfil remoto. " +"Tente outra vez, por favor." + +#. TRANS: Form legend. %s is a nickname. +#: remotefollowinit.php:100 +msgid "Subscribe to %s" +msgstr "Subscrever %s" + +#. TRANS: Button text to subscribe to a profile. +#: remotefollowinit.php:102 +msgctxt "BUTTON" +msgid "Subscribe" +msgstr "Subscrever" + +#. TRANS: Field label. +#: remotefollowinit.php:118 +msgid "User nickname" +msgstr "Apelido do utilizador" + +#. TRANS: Field title. +#: remotefollowinit.php:121 +msgid "Nickname of the user you want to follow." +msgstr "Apelido do utilizador que pretende seguir." + +#. TRANS: Field label. +#: remotefollowinit.php:127 +msgid "Profile Account" +msgstr "Conta do perfil" + +#. TRANS: Tooltip for field label "Profile Account". +#: remotefollowinit.php:130 +msgid "Your account ID (e.g. user@example.com)." +msgstr "O ID da sua conta (e.g. utilizador@exemplo.com)." + +#. TRANS: Form title. +#: remotefollowinit.php:148 +msgctxt "TITLE" +msgid "Subscribe to user" +msgstr "Subscrever utilizador" + +#. TRANS: Error displayed when there's failure in fetching the remote profile. +#: remotefollowsub.php:60 +msgid "" +"Sorry, we could not reach that address. " +"Please make sure it is a valid address and try again later." +msgstr "" +"Desculpe, não conseguimos aceder a esse endereço. " +"Por favor verifique que é um endereço válido e tente outra vez mais tarde." + +#. TRANS: Field label for a field that takes an user address. +#: remotefollowsub.php:104 +msgid "Subscribe to" +msgstr "Subscrever" + +#. TRANS: Tooltip for field label "Subscribe to". +#: remotefollowsub.php:107 +msgid "User's address, like nickname@example.com or http://example.net/nickname." +msgstr "Endereço do utilizador, do tipo apelido@exemplo.com ou http://exemplo.net/apelido." + +#. TRANS: Button text. +#: remotefollowsub.php:111 +msgctxt "BUTTON" +msgid "Continue" +msgstr "Continuar" + +#. TRANS: Button text. +#: remotefollowsub.php:142 +msgctxt "BUTTON" +msgid "Confirm" +msgstr "Confirmar" + +#. TRANS: Tooltip for button "Confirm". +#: remotefollowsub.php:147 +msgid "Subscribe to this user" +msgstr "Subscrever este utilizador" + +#. TRANS: Extra paragraph in remote profile view when already subscribed. +#: remotefollowsub.php:165 +msgid "You are already subscribed to this user." +msgstr "Este utilizador ja foi subscrito." + +#. TRANS: Remote subscription dialog error. +#: remotefollowsub.php:252 +msgid "Already subscribed!" +msgstr "Já subscrito!" + +#. TRANS: Remote subscription dialog error. +#: remotefollowsub.php:257 +msgid "Remote subscription failed!" +msgstr "Subscrição remota falhou!" + +#. TRANS: Form title. +#: remotefollowsub.php:300 +msgid "Subscribe to user" +msgstr "Subscrever utilizador" + +#. TRANS: Page title for remote subscription form. +#: remotefollowsub.php:319 +msgid "Confirm" +msgstr "Confirmar" + +#. TRANS: Page title for remote subscription form. +#: remotefollowsub.php:319 +msgid "Remote subscription" +msgstr "Subscrição remota" + +#. TRANS: Instructions. +#: remotefollowsub.php:330 +msgid "You can subscribe to users from other supported sites. Paste their address or profile URI below" +msgstr "Pode subscrever utilizadores de outros sites suportados. Coloque o endereço do mesmo em baixo" \ No newline at end of file