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
This commit is contained in:
Hypolite Petovan 2018-11-15 23:59:00 -05:00
parent 13a2068a8b
commit 5b7bb030de
21 changed files with 537 additions and 245 deletions

View File

@ -1,141 +0,0 @@
<?php
namespace Friendica\Directory\Content;
/**
* @author Hypolite Petovan <mrpetovan@gmail.com>
*/
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;
}
}

View File

@ -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' : ''
];
}

View File

@ -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;
@ -67,6 +67,7 @@ AND `account_type` = :account_type';
$vars = [
'query' => $originalQuery,
'field' => $field,
'page' => $pager->getPage(),
'itemsperpage' => $pager->getItemsPerPage(),
'count' => $count,

View File

@ -0,0 +1,9 @@
<?php
namespace Friendica\Directory\Controllers\Web;
abstract class BaseController
{
}

View File

@ -2,17 +2,17 @@
namespace Friendica\Directory\Controllers\Web;
use \Friendica\Directory\Content\Pager;
use \Friendica\Directory\Views\Widget\PopularCountries;
use \Friendica\Directory\Views\Widget\PopularTags;
use PDO;
use Friendica\Directory\Content\Pager;
use Friendica\Directory\Views\Widget\PopularCountries;
use Friendica\Directory\Views\Widget\PopularLanguages;
use Friendica\Directory\Views\Widget\PopularTags;
use Slim\Http\Request;
use Slim\Http\Response;
/**
* @author Hypolite Petovan <mrpetovan@gmail.com>
*/
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];
}
}

View File

@ -10,7 +10,7 @@ use Psr\Http\Message\ServerRequestInterface;
/**
* @author Hypolite Petovan <mrpetovan@gmail.com>
*/
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)";
@ -79,8 +90,10 @@ AND `account_type` = :account_type';
$vars = [
'query' => $originalQuery,
'field' => $field,
'fieldName' => $fieldName,
'count' => $count,
'accountTypeTabs' => $this->accountTypeTabs->render('search', $account_type, ['q' => $originalQuery]),
'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];
}
}

View File

@ -10,7 +10,7 @@ use Slim\Http\Response;
/**
* @author Hypolite Petovan <mrpetovan@gmail.com>
*/
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];
}
}

View File

@ -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(
return (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);
))->render($request, $response, $args);
}
}

View File

@ -1,19 +0,0 @@
<?php
namespace Friendica\Directory\Routes\Http;
/**
* @author Hypolite Petovan <mrpetovan@gmail.com>
*/
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);
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace Friendica\Directory\Routes\Web;
use Friendica\Directory\Controllers\Web\BaseController;
/**
* @author Hypolite Petovan <mrpetovan@gmail.com>
*/
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);
}
}

View File

@ -1,20 +1,22 @@
<?php
namespace Friendica\Directory\Routes\Http;
namespace Friendica\Directory\Routes\Web;
/**
* @author Hypolite Petovan <mrpetovan@gmail.com>
*/
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
);
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace Friendica\Directory\Routes\Web;
/**
* @author Hypolite Petovan <mrpetovan@gmail.com>
*/
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
);
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Friendica\Directory\Routes\Web;
/**
* @author Hypolite Petovan <mrpetovan@gmail.com>
*/
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
);
}
}

165
src/classes/Utils/L10n.php Normal file
View File

@ -0,0 +1,165 @@
<?php
namespace Friendica\Directory\Utils;
use Gettext\Languages\Language;
use Gettext\Translator;
/**
* @author Hypolite Petovan <mrpetovan@gmail.com>
*/
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;
}
}

View File

@ -6,6 +6,21 @@ namespace Friendica\Directory\Views;
* Zend-Escaper wrapper for Slim PHP Renderer
*
* @author Hypolite Petovan <mrpetovan@gmail.com>
*
* @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;
}
public function escapeJs(string $value): string
{
return $this->escaper->escapeJs($value);
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 context.
*
* @param string $context
* @param string $original
*
* @param array $args
* @return string
*/
function p__($context, $original, ...$args)
{
return $this->escaper->escapeHtmlAttr($value);
$text = $this->l10n->pgettext($context, $original);
if (!count($args)) {
return $text;
}
public function escapeUrl(string $value): string
return is_array($args[0]) ? strtr($text, $args[0]) : vsprintf($text, $args);
}
/**
* 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->escapeUrl($value);
$text = $this->l10n->dgettext($domain, $original);
if (!count($args)) {
return $text;
}
return is_array($args[0]) ? strtr($text, $args[0]) : vsprintf($text, $args);
}
/**
* 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)
{
$text = $this->l10n->ngettext($original, $plural, $count);
array_unshift($args, $count);
return is_array($args[1]) ? strtr($text, $args[1]) : vsprintf($text, $args);
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace Friendica\Directory\Views\Widget;
/**
* @author Hypolite Petovan <mrpetovan@gmail.com>
*/
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);
}
}

View File

@ -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

View File

@ -1,9 +1,40 @@
<?php
// Application middleware
// e.g: $app->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);

View File

@ -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');

View File

@ -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'

View File

@ -0,0 +1,12 @@
<div>
<h3><?php echo $this->__('Popular Languages')?></h3>
<ul>
<?php foreach ($languages as $language): ?>
<li>
<a href="search?field=language&q=<?php echo $this->escapeUrl($language['language']) ?>">
<?php echo $this->e(Friendica\Directory\Utils\L10n::langToString($language['language'])) ?>
</a>
</li>
<?php endforeach; ?>
</ul>
</div>