From 5b7bb030de03eecc4026329279c34f827715ccb5 Mon Sep 17 00:00:00 2001 From: Hypolite Petovan Date: Thu, 15 Nov 2018 23:59:00 -0500 Subject: [PATCH] Add Internationalization - Add Utils/L10n class - Add translator functions to PHP Renderer - Refactor web controllers to prevent duplicated code - Add locale middleware - Add translation file loading - Add i18n settings --- src/classes/Content/L10n.php | 141 --------------- src/classes/Content/Pager.php | 20 +-- src/classes/Controllers/Api/Search.php | 13 +- .../Controllers/Web/BaseController.php | 9 + src/classes/Controllers/Web/Directory.php | 22 +-- src/classes/Controllers/Web/Search.php | 33 ++-- src/classes/Controllers/Web/Servers.php | 14 +- src/classes/Routes/Http/Search.php | 22 +-- src/classes/Routes/Http/Servers.php | 19 -- src/classes/Routes/Web/BaseRoute.php | 44 +++++ .../Routes/{Http => Web}/Directory.php | 12 +- src/classes/Routes/Web/Search.php | 22 +++ src/classes/Routes/Web/Servers.php | 21 +++ src/classes/Utils/L10n.php | 165 ++++++++++++++++++ src/classes/Views/PhpRenderer.php | 117 +++++++++++-- src/classes/Views/Widget/PopularLanguages.php | 36 ++++ src/dependencies.php | 6 +- src/middleware.php | 35 +++- src/routes.php | 14 +- src/settings.php | 5 + src/templates/widget/popularlanguages.phtml | 12 ++ 21 files changed, 537 insertions(+), 245 deletions(-) delete mode 100644 src/classes/Content/L10n.php create mode 100644 src/classes/Controllers/Web/BaseController.php delete mode 100644 src/classes/Routes/Http/Servers.php create mode 100644 src/classes/Routes/Web/BaseRoute.php rename src/classes/Routes/{Http => Web}/Directory.php (51%) create mode 100644 src/classes/Routes/Web/Search.php create mode 100644 src/classes/Routes/Web/Servers.php create mode 100644 src/classes/Utils/L10n.php create mode 100644 src/classes/Views/Widget/PopularLanguages.php create mode 100644 src/templates/widget/popularlanguages.phtml diff --git a/src/classes/Content/L10n.php b/src/classes/Content/L10n.php deleted file mode 100644 index 7f3970d..0000000 --- a/src/classes/Content/L10n.php +++ /dev/null @@ -1,141 +0,0 @@ - - */ -class L10n -{ - /** - * @var string - */ - private $lang; - - /** - * @var array - */ - private $strings; - - /** - * @var string - */ - private $lang_path; - - public function __construct(string $language = 'en', string $lang_path = '') - { - $this->lang = $language; - $this->lang_path = $lang_path; - - $this->loadTranslationTable(); - } - - /** - * Loads string translation table - * - * First addon strings are loaded, then globals - * - * Uses an App object shim since all the strings files refer to $a->strings - * - * @param string $lang language code to load - */ - private function loadTranslationTable(): void - { - if (file_exists($this->lang_path . '/' . $this->lang . '/strings.php')) { - $this->strings = include $this->lang_path . '/' . $this->lang . '/strings.php'; - } - } - - /** - * @brief Return the localized version of a singular/plural string with optional string interpolation - * - * This function takes two english strings as parameters, singular and plural, as - * well as a count. If a localized version exists for the current language, they - * are used instead. Discrimination between singular and plural is done using the - * localized function if any or the default one. Finally, a string interpolation - * is performed using the count as parameter. - * - * Usages: - * - L10n::tt('Like', 'Likes', $count) - * - L10n::tt("%s user deleted", "%s users deleted", count($users)) - * - * @param string $singular - * @param string $plural - * @param int $count - * @return string - */ - public function tt(string $singular, string $plural, int $count): string - { - if (!empty($this->strings[$singular])) { - $t = $this->strings[$singular]; - if (is_array($t)) { - $plural_function = 'string_plural_select_' . str_replace('-', '_', $this->lang); - if (function_exists($plural_function)) { - $i = $plural_function($count); - } else { - $i = $this->stringPluralSelectDefault($count); - } - - // for some languages there is only a single array item - if (!isset($t[$i])) { - $s = $t[0]; - } else { - $s = $t[$i]; - } - } else { - $s = $t; - } - } elseif ($this->stringPluralSelectDefault($count)) { - $s = $plural; - } else { - $s = $singular; - } - - $s = @sprintf($s, $count); - - return $s; - } - - /** - * @brief Return the localized version of the provided string with optional string interpolation - * - * This function takes a english string as parameter, and if a localized version - * exists for the current language, substitutes it before performing an eventual - * string interpolation (sprintf) with additional optional arguments. - * - * Usages: - * - L10n::t('This is an example') - * - L10n::t('URL %s returned no result', $url) - * - L10n::t('Current version: %s, new version: %s', $current_version, $new_version) - * - * @param string $s - * @param array $vars Variables to interpolate in the translation string - * @return string - */ - public function t($s, ...$vars): string - { - if (empty($s)) { - return ''; - } - - if (!empty($this->strings[$s])) { - $t = $this->strings[$s]; - $s = is_array($t) ? $t[0] : $t; - } - - if (count($vars) > 0) { - $s = sprintf($s, ...$vars); - } - - return $s; - } - - /** - * Provide a fallback which will not collide with a function defined in any language file - */ - private function stringPluralSelectDefault(int $n): bool - { - return $n != 1; - } - -} diff --git a/src/classes/Content/Pager.php b/src/classes/Content/Pager.php index e70c533..b271f7f 100644 --- a/src/classes/Content/Pager.php +++ b/src/classes/Content/Pager.php @@ -24,7 +24,7 @@ class Pager private $baseQueryString = ''; /** - * @var \Friendica\Directory\Content\L10n + * @var \Gettext\TranslatorInterface */ private $l10n; @@ -33,11 +33,11 @@ class Pager * * Guesses the page number from the GET parameter 'page'. * - * @param \Friendica\Directory\Content\L10n $l10n + * @param \Gettext\TranslatorInterface $l10n * @param \Psr\Http\Message\ServerRequestInterface $request * @param integer $itemsPerPage An optional number of items per page to override the default value */ - public function __construct(L10n $l10n, \Psr\Http\Message\ServerRequestInterface $request, int $itemsPerPage = 50) + public function __construct(\Gettext\TranslatorInterface $l10n, \Psr\Http\Message\ServerRequestInterface $request, int $itemsPerPage = 50) { $this->l10n = $l10n; $this->setQueryString($request); @@ -157,7 +157,7 @@ class Pager * @param integer $itemCount The number of displayed items on the page * @return array of links */ - public function renderMinimal(int $itemCount, string $previous_label = 'Previous', string $next_label = 'Next') + public function renderMinimal(int $itemCount) { $displayedItemCount = max(0, $itemCount); @@ -165,12 +165,12 @@ class Pager 'class' => 'pager', 'prev' => [ 'url' => $this->ensureQueryParameter($this->baseQueryString . '&page=' . ($this->getPage() - 1)), - 'text' => $this->l10n->t($previous_label), + 'text' => $this->l10n->gettext('Previous'), 'class' => 'previous' . ($this->getPage() == 1 ? ' disabled' : '') ], 'next' => [ 'url' => $this->ensureQueryParameter($this->baseQueryString . '&page=' . ($this->getPage() + 1)), - 'text' => $this->l10n->t($next_label), + 'text' => $this->l10n->gettext('Next'), 'class' => 'next' . ($displayedItemCount <= 0 ? ' disabled' : '') ] ]; @@ -208,12 +208,12 @@ class Pager if ($totalItemCount > $this->getItemsPerPage()) { $data['first'] = [ 'url' => $this->ensureQueryParameter($this->baseQueryString . '&page=1'), - 'text' => $this->l10n->t('First'), + 'text' => $this->l10n->gettext('First'), 'class' => $this->getPage() == 1 ? 'disabled' : '' ]; $data['prev'] = [ 'url' => $this->ensureQueryParameter($this->baseQueryString . '&page=' . ($this->getPage() - 1)), - 'text' => $this->l10n->t('Previous'), + 'text' => $this->l10n->gettext('Previous'), 'class' => $this->getPage() == 1 ? 'disabled' : '' ]; @@ -270,12 +270,12 @@ class Pager $data['next'] = [ 'url' => $this->ensureQueryParameter($this->baseQueryString . '&page=' . ($this->getPage() + 1)), - 'text' => $this->l10n->t('Next'), + 'text' => $this->l10n->gettext('Next'), 'class' => $this->getPage() == $lastpage ? 'disabled' : '' ]; $data['last'] = [ 'url' => $this->ensureQueryParameter($this->baseQueryString . '&page=' . $lastpage), - 'text' => $this->l10n->t('Last'), + 'text' => $this->l10n->gettext('Last'), 'class' => $this->getPage() == $lastpage ? 'disabled' : '' ]; } diff --git a/src/classes/Controllers/Api/Search.php b/src/classes/Controllers/Api/Search.php index 7cbe519..cd62c30 100644 --- a/src/classes/Controllers/Api/Search.php +++ b/src/classes/Controllers/Api/Search.php @@ -21,14 +21,14 @@ class Search */ private $profileModel; /** - * @var \Friendica\Directory\Content\L10n + * @var \Gettext\TranslatorInterface */ private $l10n; public function __construct( \Atlas\Pdo\Connection $atlas, \Friendica\Directory\Models\Profile $profileModel, - \Friendica\Directory\Content\L10n $l10n + \Gettext\TranslatorInterface $l10n ) { $this->atlas = $atlas; @@ -66,11 +66,12 @@ AND `account_type` = :account_type'; $count = $this->profileModel->getCountForDisplay($sql_where, $values); $vars = [ - 'query' => $originalQuery, - 'page' => $pager->getPage(), + 'query' => $originalQuery, + 'field' => $field, + 'page' => $pager->getPage(), 'itemsperpage' => $pager->getItemsPerPage(), - 'count' => $count, - 'profiles' => $profiles + 'count' => $count, + 'profiles' => $profiles ]; // Render index view diff --git a/src/classes/Controllers/Web/BaseController.php b/src/classes/Controllers/Web/BaseController.php new file mode 100644 index 0000000..c8a907e --- /dev/null +++ b/src/classes/Controllers/Web/BaseController.php @@ -0,0 +1,9 @@ + */ -class Directory +class Directory extends BaseController { /** * @var \Atlas\Pdo\Connection @@ -31,7 +31,7 @@ class Directory */ private $renderer; /** - * @var \Friendica\Directory\Content\L10n + * @var \Gettext\TranslatorInterface */ private $l10n; @@ -40,7 +40,7 @@ class Directory \Friendica\Directory\Models\Profile $profileModel, \Friendica\Directory\Views\Widget\AccountTypeTabs $accountTypeTabs, \Friendica\Directory\Views\PhpRenderer $renderer, - \Friendica\Directory\Content\L10n $l10n + \Gettext\TranslatorInterface $l10n ) { $this->atlas = $atlas; @@ -50,10 +50,11 @@ class Directory $this->l10n = $l10n; } - public function render(Request $request, Response $response, array $args): Response + public function render(Request $request, Response $response, array $args): array { $popularTags = new PopularTags($this->atlas, $this->renderer); $popularCountries = new PopularCountries($this->atlas, $this->renderer); + $popularLanguages = new PopularLanguages($this->atlas, $this->renderer); $pager = new Pager($this->l10n, $request, 20); @@ -69,18 +70,19 @@ class Directory $count = $this->profileModel->getCountForDisplay($condition, $values); $vars = [ - 'title' => $this->l10n->t('People'), + 'title' => $this->l10n->gettext('People'), 'profiles' => $profiles, 'pager_full' => $pager->renderFull($count), 'pager_minimal' => $pager->renderMinimal($count), 'accountTypeTabs' => $this->accountTypeTabs->render('directory', $args['account_type'] ?? ''), 'popularTags' => $popularTags->render(), 'popularCountries' => $popularCountries->render(), + 'popularLanguages' => $popularLanguages->render(), ]; $content = $this->renderer->fetch('directory.phtml', $vars); // Render index view - return $this->renderer->render($response, 'layout.phtml', ['baseUrl' => $request->getUri()->getBaseUrl(), 'content' => $content]); + return ['content' => $content]; } } diff --git a/src/classes/Controllers/Web/Search.php b/src/classes/Controllers/Web/Search.php index 248e38a..7f5d20a 100644 --- a/src/classes/Controllers/Web/Search.php +++ b/src/classes/Controllers/Web/Search.php @@ -10,7 +10,7 @@ use Psr\Http\Message\ServerRequestInterface; /** * @author Hypolite Petovan */ -class Search +class Search extends BaseController { /** * @var \Atlas\Pdo\Connection @@ -29,7 +29,7 @@ class Search */ private $accountTypeTabs; /** - * @var \Friendica\Directory\Content\L10n + * @var \Gettext\TranslatorInterface */ private $l10n; @@ -38,7 +38,7 @@ class Search \Friendica\Directory\Models\Profile $profileModel, \Friendica\Directory\Views\Widget\AccountTypeTabs $accountTypeTabs, \Friendica\Directory\Views\PhpRenderer $renderer, - \Friendica\Directory\Content\L10n $l10n + \Gettext\TranslatorInterface $l10n ) { $this->atlas = $atlas; @@ -48,7 +48,7 @@ class Search $this->l10n = $l10n; } - public function render(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args): \Slim\Http\Response + public function render(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args): array { $pager = new Pager($this->l10n, $request, 20); @@ -56,9 +56,20 @@ class Search $field = filter_input(INPUT_GET, 'field', FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW & FILTER_FLAG_STRIP_HIGH); + $fieldName = ''; + if ($field) { $query .= '%'; - $sql_where = '`' . $field . '` LIKE :query'; + $sql_where = 'p.`' . $field . '` LIKE :query'; + + switch($field) { + case 'language': $fieldName = $this->l10n->pgettext('field', 'Language'); break; + case 'locality': $fieldName = $this->l10n->pgettext('field', 'Locality'); break; + case 'region' : $fieldName = $this->l10n->pgettext('field', 'Region') ; break; + case 'country' : $fieldName = $this->l10n->pgettext('field', 'Country') ; break; + default: $fieldName = ucfirst($field); + } + } else { $sql_where = "MATCH (p.`name`, p.`pdesc`, p.`profile_url`, p.`locality`, p.`region`, p.`country`, p.`tags` ) AGAINST (:query IN BOOLEAN MODE)"; @@ -78,10 +89,12 @@ AND `account_type` = :account_type'; $count = $this->profileModel->getCountForDisplay($sql_where, $values); $vars = [ - 'query' => $originalQuery, - 'count' => $count, - 'accountTypeTabs' => $this->accountTypeTabs->render('search', $account_type, ['q' => $originalQuery]), - 'profiles' => $profiles, + 'query' => $originalQuery, + 'field' => $field, + 'fieldName' => $fieldName, + 'count' => $count, + 'accountTypeTabs' => $this->accountTypeTabs->render('search', $account_type, ['q' => $originalQuery, 'field' => $field]), + 'profiles' => $profiles, 'pager_full' => $pager->renderFull($count), 'pager_minimal' => $pager->renderMinimal($count), ]; @@ -89,6 +102,6 @@ AND `account_type` = :account_type'; $content = $this->renderer->fetch('search.phtml', $vars); // Render index view - return $this->renderer->render($response, 'layout.phtml', ['baseUrl' => $request->getUri()->getBaseUrl(), 'content' => $content, 'noNavSearch' => true]); + return ['content' => $content, 'noNavSearch' => true]; } } diff --git a/src/classes/Controllers/Web/Servers.php b/src/classes/Controllers/Web/Servers.php index 165db04..d153c5e 100644 --- a/src/classes/Controllers/Web/Servers.php +++ b/src/classes/Controllers/Web/Servers.php @@ -10,7 +10,7 @@ use Slim\Http\Response; /** * @author Hypolite Petovan */ -class Servers +class Servers extends BaseController { /** * @var \Atlas\Pdo\Connection @@ -21,7 +21,7 @@ class Servers */ private $renderer; /** - * @var \Friendica\Directory\Content\L10n + * @var \Gettext\TranslatorInterface */ private $l10n; /** @@ -32,7 +32,7 @@ class Servers public function __construct( \Atlas\Pdo\Connection $atlas, \Friendica\Directory\Views\PhpRenderer $renderer, - \Friendica\Directory\Content\L10n $l10n, + \Gettext\TranslatorInterface $l10n, \Psr\SimpleCache\CacheInterface $simplecache ) { @@ -42,7 +42,7 @@ class Servers $this->simplecache = $simplecache; } - public function render(Request $request, Response $response): Response + public function render(Request $request, Response $response): array { $stable_version = $this->simplecache->get('stable_version'); if (!$stable_version) { @@ -72,7 +72,7 @@ LIMIT :start, :limit'; foreach ($servers as $key => $server) { $servers[$key]['user_count'] = $this->atlas->fetchValue( - 'SELECT COUNT(*) FROM `profile` WHERE `available` AND `server_id` = :server_id', + 'SELECT COUNT(*) FROM `profile` WHERE `available` AND NOT `hidden` AND `server_id` = :server_id', ['server_id' => [$server['id'], PDO::PARAM_INT]] ); } @@ -85,7 +85,7 @@ AND NOT `hidden`'; $count = $this->atlas->fetchValue($stmt); $vars = [ - 'title' => $this->l10n->t('Public Servers'), + 'title' => $this->l10n->gettext('Public Servers'), 'servers' => $servers, 'pager' => $pager->renderFull($count), 'stable_version' => $stable_version, @@ -95,6 +95,6 @@ AND NOT `hidden`'; $content = $this->renderer->fetch('servers.phtml', $vars); // Render index view - return $this->renderer->render($response, 'layout.phtml', ['baseUrl' => $request->getUri()->getBaseUrl(), 'content' => $content]); + return ['content' => $content]; } } diff --git a/src/classes/Routes/Http/Search.php b/src/classes/Routes/Http/Search.php index 6bcfdb1..5b3dbc0 100644 --- a/src/classes/Routes/Http/Search.php +++ b/src/classes/Routes/Http/Search.php @@ -9,22 +9,10 @@ class Search extends BaseRoute { public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args): \Slim\Http\Response { - if ($request->getAttribute('negotiation')->getMediaType() == 'application/json') { - $controller = new \Friendica\Directory\Controllers\Api\Search( - $this->container->atlas, - $this->container->get('\Friendica\Directory\Models\Profile'), - $this->container->l10n - ); - } else { - $controller = new \Friendica\Directory\Controllers\Web\Search( - $this->container->atlas, - $this->container->get('\Friendica\Directory\Models\Profile'), - $this->container->get('\Friendica\Directory\Views\Widget\AccountTypeTabs'), - $this->container->renderer, - $this->container->l10n - ); - } - - return $controller->render($request, $response, $args); + return (new \Friendica\Directory\Controllers\Api\Search( + $this->container->atlas, + $this->container->get('\Friendica\Directory\Models\Profile'), + $this->container->l10n + ))->render($request, $response, $args); } } diff --git a/src/classes/Routes/Http/Servers.php b/src/classes/Routes/Http/Servers.php deleted file mode 100644 index f38519a..0000000 --- a/src/classes/Routes/Http/Servers.php +++ /dev/null @@ -1,19 +0,0 @@ - - */ -class Servers extends BaseRoute -{ - public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args): \Slim\Http\Response - { - return (new \Friendica\Directory\Controllers\Web\Servers( - $this->container->atlas, - $this->container->renderer, - $this->container->l10n, - $this->container->simplecache) - )->render($request, $response); - } -} diff --git a/src/classes/Routes/Web/BaseRoute.php b/src/classes/Routes/Web/BaseRoute.php new file mode 100644 index 0000000..c86fc85 --- /dev/null +++ b/src/classes/Routes/Web/BaseRoute.php @@ -0,0 +1,44 @@ + + */ +abstract class BaseRoute +{ + /** + * @var \Slim\Container + */ + protected $container; + + /** + * @var BaseController + */ + protected $controller; + + public function __construct(\Slim\Container $container) + { + $this->container = $container; + } + + public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args): \Slim\Http\Response + { + $defaults = [ + 'languages' => $this->container->settings['i18n']['locales'], + 'lang' => $request->getAttribute('locale'), + 'baseUrl' => $request->getUri()->getBaseUrl(), + 'content' => '', + 'noNavSearch' => false + ]; + + $values = $this->controller->render($request, $response, $args); + + $values = $values + $defaults; + + // Render index view + return $this->container->renderer->render($response, 'layout.phtml', $values); + } +} diff --git a/src/classes/Routes/Http/Directory.php b/src/classes/Routes/Web/Directory.php similarity index 51% rename from src/classes/Routes/Http/Directory.php rename to src/classes/Routes/Web/Directory.php index b5c76f6..ce4459c 100644 --- a/src/classes/Routes/Http/Directory.php +++ b/src/classes/Routes/Web/Directory.php @@ -1,20 +1,22 @@ */ class Directory extends BaseRoute { - public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args): \Slim\Http\Response + public function __construct(\Slim\Container $container) { - return (new \Friendica\Directory\Controllers\Web\Directory( + parent::__construct($container); + + $this->controller = new \Friendica\Directory\Controllers\Web\Directory( $this->container->atlas, $this->container->get('\Friendica\Directory\Models\Profile'), $this->container->get('\Friendica\Directory\Views\Widget\AccountTypeTabs'), $this->container->renderer, - $this->container->l10n) - )->render($request, $response, $args); + $this->container->l10n + ); } } diff --git a/src/classes/Routes/Web/Search.php b/src/classes/Routes/Web/Search.php new file mode 100644 index 0000000..655beb7 --- /dev/null +++ b/src/classes/Routes/Web/Search.php @@ -0,0 +1,22 @@ + + */ +class Search extends BaseRoute +{ + public function __construct(\Slim\Container $container) + { + parent::__construct($container); + + $this->controller = new \Friendica\Directory\Controllers\Web\Search( + $this->container->atlas, + $this->container->get('\Friendica\Directory\Models\Profile'), + $this->container->get('\Friendica\Directory\Views\Widget\AccountTypeTabs'), + $this->container->renderer, + $this->container->l10n + ); + } +} diff --git a/src/classes/Routes/Web/Servers.php b/src/classes/Routes/Web/Servers.php new file mode 100644 index 0000000..041de84 --- /dev/null +++ b/src/classes/Routes/Web/Servers.php @@ -0,0 +1,21 @@ + + */ +class Servers extends BaseRoute +{ + public function __construct(\Slim\Container $container) + { + parent::__construct($container); + + $this->controller = new \Friendica\Directory\Controllers\Web\Servers( + $this->container->atlas, + $this->container->renderer, + $this->container->l10n, + $this->container->simplecache + ); + } +} diff --git a/src/classes/Utils/L10n.php b/src/classes/Utils/L10n.php new file mode 100644 index 0000000..5439d18 --- /dev/null +++ b/src/classes/Utils/L10n.php @@ -0,0 +1,165 @@ + + */ +class L10n +{ + public static $languages = [ + 'af' => 'Afrikaans', + 'ak' => 'Akan', + 'am' => 'አማርኛ', + 'ar' => 'العربية', + 'as' => 'অসমীয়া', + 'az' => 'Azərbaycan', + 'be' => 'Беларуская', + 'bg' => 'Български', + 'bm' => 'Bamanakan', + 'bn' => 'বাংলা', + 'bo' => 'བོད་སྐད་', + 'br' => 'Brezhoneg', + 'bs' => 'Bosanski', + 'ca' => 'Català', + 'cs' => 'Čeština', + 'cy' => 'Cymraeg', + 'da' => 'Dansk', + 'de' => 'Deutsch', + 'de_AT' => 'Österreichisches Deutsch', + 'de_CH' => 'Schweizer Hochdeutsch', + 'dz' => 'རྫོང་ཁ', + 'ee' => 'Eʋegbe', + 'el' => 'Ελληνικά', + 'en' => 'English', + 'en_AU' => 'Australian English', + 'en_CA' => 'Canadian English', + 'en_GB' => 'British English', + 'en_US' => 'American English', + 'eo' => 'Esperanto', + 'es' => 'Español', + 'es_ES' => 'Español De España', + 'es_MX' => 'Español De México', + 'et' => 'Eesti', + 'eu' => 'Euskara', + 'fa' => 'فارسی', + 'ff' => 'Pulaar', + 'fi' => 'Suomi', + 'fo' => 'Føroyskt', + 'fr' => 'Français', + 'fr_CA' => 'Français Canadien', + 'fr_CH' => 'Français Suisse', + 'fy' => 'West-Frysk', + 'ga' => 'Gaeilge', + 'gd' => 'Gàidhlig', + 'gl' => 'Galego', + 'gu' => 'ગુજરાતી', + 'gv' => 'Gaelg', + 'ha' => 'Hausa', + 'he' => 'עברית', + 'hi' => 'हिन्दी', + 'hr' => 'Hrvatski', + 'hu' => 'Magyar', + 'hy' => 'Հայերեն', + 'id' => 'Bahasa Indonesia', + 'ig' => 'Igbo', + 'ii' => 'ꆈꌠꉙ', + 'is' => 'Íslenska', + 'it' => 'Italiano', + 'ja' => '日本語', + 'ka' => 'ქართული', + 'ki' => 'Gikuyu', + 'kk' => 'Қазақ Тілі', + 'kl' => 'Kalaallisut', + 'km' => 'ខ្មែរ', + 'kn' => 'ಕನ್ನಡ', + 'ko' => '한국어', + 'ks' => 'کٲشُر', + 'kw' => 'Kernewek', + 'ky' => 'Кыргызча', + 'lb' => 'Lëtzebuergesch', + 'lg' => 'Luganda', + 'ln' => 'Lingála', + 'lo' => 'ລາວ', + 'lt' => 'Lietuvių', + 'lu' => 'Tshiluba', + 'lv' => 'Latviešu', + 'mg' => 'Malagasy', + 'mk' => 'Македонски', + 'ml' => 'മലയാളം', + 'mn' => 'Монгол', + 'mr' => 'मराठी', + 'ms' => 'Bahasa Melayu', + 'mt' => 'Malti', + 'my' => 'ဗမာ', + 'nb' => 'Norsk Bokmål', + 'nd' => 'Isindebele', + 'ne' => 'नेपाली', + 'nl' => 'Nederlands', + 'nl_BE' => 'Vlaams', + 'nn' => 'Nynorsk', + 'no' => 'Norsk', + 'om' => 'Oromoo', + 'or' => 'ଓଡ଼ିଆ', + 'os' => 'Ирон', + 'pa' => 'ਪੰਜਾਬੀ', + 'pl' => 'Polski', + 'ps' => 'پښتو', + 'pt' => 'Português', + 'pt_BR' => 'Português Do Brasil', + 'pt_PT' => 'Português Europeu', + 'qu' => 'Runasimi', + 'rm' => 'Rumantsch', + 'rn' => 'Ikirundi', + 'ro' => 'Română', + 'ro_MD' => 'Moldovenească', + 'ru' => 'Русский', + 'rw' => 'Kinyarwanda', + 'se' => 'Davvisámegiella', + 'sg' => 'Sängö', + 'sh' => 'Srpskohrvatski', + 'si' => 'සිංහල', + 'sk' => 'Slovenčina', + 'sl' => 'Slovenščina', + 'sn' => 'Chishona', + 'so' => 'Soomaali', + 'sq' => 'Shqip', + 'sr' => 'Српски', + 'sv' => 'Svenska', + 'sw' => 'Kiswahili', + 'ta' => 'தமிழ்', + 'te' => 'తెలుగు', + 'th' => 'ไทย', + 'ti' => 'ትግርኛ', + 'tl' => 'Tagalog', + 'to' => 'Lea Fakatonga', + 'tr' => 'Türkçe', + 'ug' => 'ئۇيغۇرچە', + 'uk' => 'Українська', + 'ur' => 'اردو', + 'uz' => 'Oʻzbekcha', + 'vi' => 'Tiếng Việt', + 'yi' => 'ייִדיש', + 'yo' => 'Èdè Yorùbá', + 'zh' => '中文', + 'zh_Hans' => '简体中文', + 'zh_Hant' => '繁體中文', + 'zu' => 'Isizulu', + ]; + + public static function langToString($lang) + { + $found = false; + foreach(self::$languages as $key => $language) { + if (strtolower($key) == strtolower(str_replace('-', '_', $lang))) { + $found = true; + break; + } + } + + return $found ? $language : $lang; + } +} diff --git a/src/classes/Views/PhpRenderer.php b/src/classes/Views/PhpRenderer.php index 1f55912..57d97d3 100644 --- a/src/classes/Views/PhpRenderer.php +++ b/src/classes/Views/PhpRenderer.php @@ -6,6 +6,21 @@ namespace Friendica\Directory\Views; * Zend-Escaper wrapper for Slim PHP Renderer * * @author Hypolite Petovan + * + * @method string escapeHtml(string $value) + * @method string escapeHtmlAttr(string $value) + * @method string escapeCss(string $value) + * @method string escapeJs(string $value) + * @method string escapeUrl(string $value) + * @method string noop(string $original) + * @method string gettext(string $original) + * @method string ngettext(string $original, string $plural, string $value) + * @method string dngettext(string $domain, string $original, string $plural, string $value) + * @method string npgettext(string $context, string $original, string $plural, string $value) + * @method string pgettext(string $context, string $original) + * @method string dgettext(string $domain, string $original) + * @method string dpgettext(string $domain, string $context, string $original) + * @method string dnpgettext(string $domain, string $context, string $original, string $plural, string $value) */ class PhpRenderer extends \Slim\Views\PhpRenderer { @@ -14,13 +29,13 @@ class PhpRenderer extends \Slim\Views\PhpRenderer */ private $escaper; /** - * @var \Friendica\Directory\Content\L10n + * @var \Gettext\TranslatorInterface */ private $l10n; public function __construct( \Zend\Escaper\Escaper $escaper, - \Friendica\Directory\Content\L10n $l10n, + \Gettext\TranslatorInterface $l10n, string $templatePath = "", array $attributes = array() ) @@ -36,28 +51,106 @@ class PhpRenderer extends \Slim\Views\PhpRenderer return $this->escapeHtml($value); } - public function escapeHtml(string $value): string + public function __call($name, $arguments) { - return $this->escaper->escapeHtml($value); + if (method_exists($this->escaper, $name)) { + return $this->escaper->$name(...$arguments); + } elseif (method_exists($this->l10n, $name)) { + return $this->l10n->$name(...$arguments); + } else { + throw new \Exception('Unknown PhpRendere magic method: ' . $name); + } } - public function escapeCss(string $value): string + /** + * Echoes the translation of a string. + * + * Loose copy of Gettext/gettext global __() function + * + * Usages: + * - $this->__('Label') + * - $this->__('Label %s', $value) + * + * @param $original + * @param array $args + * + * @return string + */ + public function __($original, ...$args) { - return $this->escaper->escapeCss($value); + $text = $this->l10n->gettext($original); + + if (!count($args)) { + return $text; + } + + return is_array($args[0]) ? strtr($text, $args[0]) : vsprintf($text, $args); } - public function escapeJs(string $value): string + /** + * Returns the translation of a string in a specific context. + * + * @param string $context + * @param string $original + * + * @param array $args + * @return string + */ + function p__($context, $original, ...$args) { - return $this->escaper->escapeJs($value); + $text = $this->l10n->pgettext($context, $original); + + if (!count($args)) { + return $text; + } + + return is_array($args[0]) ? strtr($text, $args[0]) : vsprintf($text, $args); } - public function escapeHtmlAttr(string $value): string + /** + * Returns the translation of a string in a specific domain. + * + * @param string $domain + * @param string $original + * + * @param array $args + * @return string + */ + function d__($domain, $original, ...$args) { - return $this->escaper->escapeHtmlAttr($value); + $text = $this->l10n->dgettext($domain, $original); + + if (!count($args)) { + return $text; + } + + return is_array($args[0]) ? strtr($text, $args[0]) : vsprintf($text, $args); } - public function escapeUrl(string $value): string + /** + * Echoes the singular/plural translation of a string. + * + * Loose copy of Gettext/gettext global n__() function + * + * Usages: + * - $this->n__('Label', 'Labels', 3) + * - $this->n__('%d Label for %s', '%d Labels for %s', 3, $value) + * + * @param string $original + * @param string $plural + * @param string $count + * @param array $args + * + * @return string + */ + function n__($original, $plural, $count, ...$args) { - return $this->escaper->escapeUrl($value); + $text = $this->l10n->ngettext($original, $plural, $count); + + array_unshift($args, $count); + + return is_array($args[1]) ? strtr($text, $args[1]) : vsprintf($text, $args); } + + } diff --git a/src/classes/Views/Widget/PopularLanguages.php b/src/classes/Views/Widget/PopularLanguages.php new file mode 100644 index 0000000..0798d86 --- /dev/null +++ b/src/classes/Views/Widget/PopularLanguages.php @@ -0,0 +1,36 @@ + + */ +class PopularLanguages +{ + /** + * @var \Atlas\Pdo\Connection + */ + private $connection; + /** + * @var \Friendica\Directory\Views\PhpRenderer + */ + private $renderer; + + public function __construct(\Atlas\Pdo\Connection $connection, \Friendica\Directory\Views\PhpRenderer $renderer) + { + $this->connection = $connection; + $this->renderer = $renderer; + } + + public function render(): string + { + $stmt = 'SELECT `language`, COUNT(*) AS `total` FROM `profile` WHERE `language` IS NOT NULL GROUP BY `language` ORDER BY COUNT(`language`) DESC LIMIT 10'; + $languages = $this->connection->fetchAll($stmt); + + $vars = [ + 'languages' => $languages + ]; + + return $this->renderer->fetch('widget/popularlanguages.phtml', $vars); + } +} diff --git a/src/dependencies.php b/src/dependencies.php index 4727aa8..dbd6c6a 100644 --- a/src/dependencies.php +++ b/src/dependencies.php @@ -5,9 +5,9 @@ use Interop\Container\ContainerInterface; // DIC configuration // l10n -$container['l10n'] = function (ContainerInterface $c): Friendica\Directory\Content\L10n { - $settings = $c->get('settings')['l10n']; - return new Friendica\Directory\Content\L10n($settings['language'] ?: 'en', $settings['lang_path'] ?: ''); +$container['l10n'] = function (ContainerInterface $c): Gettext\TranslatorInterface { + $translator = new Gettext\Translator(); + return $translator; }; // simple cache diff --git a/src/middleware.php b/src/middleware.php index aa46b75..dc000a9 100644 --- a/src/middleware.php +++ b/src/middleware.php @@ -1,9 +1,40 @@ add(new \Slim\Csrf\Guard); -// configure middleware +use Boronczyk\LocalizationMiddleware; $app->add(new \Gofabian\Negotiation\NegotiationMiddleware([ 'accept' => ['text/html', 'application/json'] ])); + + +$middleware = new LocalizationMiddleware( + $container->get('settings')['i18n']['locales'], + $container->get('settings')['i18n']['default'] +); + +$middleware->setLocaleCallback(function (string $locale) use ($container) { + $langPath = $container->get('settings')['i18n']['path']; + + $translator = $container->get('l10n'); + if (is_a($translator, 'Gettext\GettextTranslator')) { + // One of them will end up working, right? + $translator->setLanguage($locale); + $translator->setLanguage($locale . '.utf8'); + $translator->setLanguage($locale . '.UTF8'); + $translator->setLanguage($locale . '.utf-8'); + $translator->setLanguage($locale . '.UTF-8'); + + $translator->loadDomain('strings', $langPath); + } else { + /** @var $translator \Gettext\Translator */ + if (file_exists($langPath . '/' . $locale . '/LC_MESSAGES/strings.mo')) { + $translator->loadTranslations(Gettext\Translations::fromMoFile($langPath . '/' . $locale . '/LC_MESSAGES/strings.mo')); + } elseif (file_exists($langPath . '/' . $locale . '/LC_MESSAGES/strings.po')) { + $translator->loadTranslations(Gettext\Translations::fromPoFile($langPath . '/' . $locale . '/LC_MESSAGES/strings.po')); + } + } +}); +$middleware->setUriParamName('lang'); + +$app->add($middleware); \ No newline at end of file diff --git a/src/routes.php b/src/routes.php index 16026f2..04474c5 100644 --- a/src/routes.php +++ b/src/routes.php @@ -9,9 +9,17 @@ use Slim\Http\Response; * @var $app \Slim\App */ -$app->get('/servers', \Friendica\Directory\Routes\Http\Servers::class); +$app->get('/servers', \Friendica\Directory\Routes\Web\Servers::class); -$app->get('/search[/{account_type}]', \Friendica\Directory\Routes\Http\Search::class)->setName('search'); +$app->get('/search[/{account_type}]', function (Request $request, Response $response, $args) { + if ($request->getAttribute('negotiation')->getMediaType() == 'application/json') { + $route = new \Friendica\Directory\Routes\Http\Search($this); + } else { + $route = new \Friendica\Directory\Routes\Web\Search($this); + } + + return $route($request, $response, $args); +})->setName('search'); $app->get('/submit', \Friendica\Directory\Routes\Http\Submit::class); @@ -26,4 +34,4 @@ $app->get('/VERSION', function (Request $request, Response $response) { return $response; }); -$app->get('/[{account_type}]', \Friendica\Directory\Routes\Http\Directory::class)->setName('directory'); +$app->get('/[{account_type}]', \Friendica\Directory\Routes\Web\Directory::class)->setName('directory'); diff --git a/src/settings.php b/src/settings.php index aa0d6d0..529660f 100644 --- a/src/settings.php +++ b/src/settings.php @@ -18,6 +18,11 @@ if (\is_readable(__DIR__ . '/../config/local.json')) { $settings = [ 'displayErrorDetails' => false, // set to false in production 'addContentLengthHeader' => false, // Allow the web server to send the content-length header + 'i18n' => [ + 'locales' => ['en', 'fr'], + 'default' => 'en', + 'path' => __DIR__ . '/lang' + ], // Escaper settings 'escaper' => [ 'encoding' => 'utf-8' diff --git a/src/templates/widget/popularlanguages.phtml b/src/templates/widget/popularlanguages.phtml new file mode 100644 index 0000000..5c06216 --- /dev/null +++ b/src/templates/widget/popularlanguages.phtml @@ -0,0 +1,12 @@ +
+

__('Popular Languages')?>

+ +