[CORE][I18N] Fix the translation system

This commit is contained in:
Hugo Sales 2020-03-13 22:31:56 +00:00 committed by Hugo Sales
parent 2b9a15c1e9
commit 2215b05894
Signed by untrusted user: someonewithpc
GPG Key ID: 7D0C7EAFC9D835A0
3 changed files with 100 additions and 85 deletions

View File

@ -16,8 +16,13 @@
namespace App\Util; namespace App\Util;
class Common abstract class Common
{ {
public static function config(string $section, string $field)
{
}
public static function normalizePath(string $path): string public static function normalizePath(string $path): string
{ {
if (DIRECTORY_SEPARATOR !== '/') { if (DIRECTORY_SEPARATOR !== '/') {

View File

@ -51,25 +51,31 @@
namespace App\Util; namespace App\Util;
use Psr\Log\LoggerInterface;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\HttpKernel\KernelEvents;
use Psr\Log\LoggerInterface;
use App\Util\Log; use App\Util\Log;
use App\Util\GSEvent; use App\Util\GSEvent;
use App\Util\I18n;
class GNUsocial implements EventSubscriberInterface class GNUsocial implements EventSubscriberInterface
{ {
protected ContainerInterface $container; protected ContainerInterface $container;
protected LoggerInterface $logger; protected LoggerInterface $logger;
protected TranslatorInterface $translator;
public function __construct(ContainerInterface $container, LoggerInterface $logger) public function __construct(ContainerInterface $container,
LoggerInterface $logger,
TranslatorInterface $translator)
{ {
$this->container = $container; $this->container = $container;
$this->logger= $logger; $this->logger = $logger;
$this->translator = $translator;
} }
public function onKernelRequest(RequestEvent $event, public function onKernelRequest(RequestEvent $event,
@ -91,7 +97,6 @@ class GNUsocial implements EventSubscriberInterface
define('GNUSOCIAL_LIFECYCLE', 'dev'); define('GNUSOCIAL_LIFECYCLE', 'dev');
define('GNUSOCIAL_VERSION', GNUSOCIAL_BASE_VERSION . '-' . GNUSOCIAL_LIFECYCLE); define('GNUSOCIAL_VERSION', GNUSOCIAL_BASE_VERSION . '-' . GNUSOCIAL_LIFECYCLE);
define('GNUSOCIAL_CODENAME', 'Big bang'); define('GNUSOCIAL_CODENAME', 'Big bang');
}
/* Work internally in UTC */ /* Work internally in UTC */
date_default_timezone_set('UTC'); date_default_timezone_set('UTC');
@ -101,9 +106,12 @@ class GNUsocial implements EventSubscriberInterface
Log::setLogger($this->logger); Log::setLogger($this->logger);
GSEvent::setDispatcher($event_dispatcher); GSEvent::setDispatcher($event_dispatcher);
I18n::setTranslator($this->translator);
}
GSEvent::addHandler('test', function ($x) { GSEvent::addHandler('test', function ($x) {
Log::info("Logging from an event " . var_export($x, true)); Log::info(_m("Logging from an event " . var_export($x, true)));
}); });
return $event; return $event;

View File

@ -27,11 +27,7 @@
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
defined('GNUSOCIAL') || die(); namespace App\Util;
private Translator $translator;
public function setTranslator($trans): void { $translator = $trans; }
// Locale category constants are usually predefined, but may not be // Locale category constants are usually predefined, but may not be
// on some systems such as Win32. // on some systems such as Win32.
@ -48,6 +44,45 @@ foreach ($LC_CATEGORIES as $key => $name) {
} }
} }
use Symfony\Contracts\Translation\TranslatorInterface;
use App\Util\Common;
abstract class I18n {
public static ?TranslatorInterface $translator = null;
public static function setTranslator($trans): void { self::$translator = $trans; }
/**
* Looks for which plugin we've been called from to set the gettext domain;
* if not in a plugin subdirectory, we'll use the default 'gnusocial'.
*
* @param array $backtrace debug_backtrace() output
* @return string
* @private
*/
public static function _mdomain(array $backtrace): string
{
/*
0 =>
array
'file' => string '/var/www/mublog/plugins/FeedSub/FeedSubPlugin.php' (length=49)
'line' => int 77
'function' => string '_m' (length=2)
'args' =>
array
0 => &string 'Feeds' (length=5)
*/
static $cached;
$path = $backtrace[0]['file'];
if (!isset($cached[$path])) {
$path = common::normalizePath($path);
$cached[$path] = common::pluginFromPath($path);
}
return $cached[$path] ?? "";
}
}
/** /**
* Wrapper for symfony translation with smart domain detection. * Wrapper for symfony translation with smart domain detection.
* *
@ -68,77 +103,48 @@ foreach ($LC_CATEGORIES as $key => $name) {
* @return string * @return string
* @throws Exception * @throws Exception
*/ */
public function _m(string $msg /*, ...*/): string function _m(string $msg /*, ...*/): string
{ {
$domain = _mdomain(debug_backtrace()); $domain = I18n::_mdomain(debug_backtrace());
$args = func_get_args(); $args = func_get_args();
list($context, $msg_single, $msg_plural, $n) = $args;
switch (count($args)) { switch (count($args)) {
case 1: case 1:
// Empty parameters // Empty parameters
return $translator->trans($msg, [], $domain); return I18n::$translator->trans($msg, [], $domain);
case 2: case 2:
$context = $args[0];
$msg_single = $args[1];
// ASCII 4 is EOT, used to separate context from string // ASCII 4 is EOT, used to separate context from string
return $translator->trans($context . '\004' . $msg_single, [], $domain); return I18n::$translator->trans($context . '\004' . $msg_single, [], $domain);
case 3: case 3:
// '|' separates the singular from the plural version // '|' separates the singular from the plural version
$msg_single = $args[0]; $msg_single = $args[0];
$msg_plural = $args[1]; $msg_plural = $args[1];
$n = $args[2]; $n = $args[2];
return $translator->trans($msg_single . '|' . $msg_plural, ['%d' => $n], $domain); return I18n::$translator->trans($msg_single . '|' . $msg_plural, ['%d' => $n], $domain);
case 4: case 4:
// Combine both // Combine both
return $translator->trans($context . '\004' . $msg_single . '|' . $msg_plural, $context = $args[0];
$msg_single = $args[1];
$msg_plural = $args[2];
$n = $args[3];
return I18n::$translator->trans($context . '\004' . $msg_single . '|' . $msg_plural,
['%d' => $n], $domain); ['%d' => $n], $domain);
default: default:
throw new Exception("Bad parameter count to _m()"); throw new Exception("Bad parameter count to _m()");
} }
} }
/**
* Looks for which plugin we've been called from to set the gettext domain;
* if not in a plugin subdirectory, we'll use the default 'gnusocial'.
*
* Note: we can't return null for default domain since most of the PHP gettext
* wrapper functions turn null into "" before passing to the backend library.
*
* @param array $backtrace debug_backtrace() output
* @return string
* @private
*/
private function _mdomain(array $backtrace): string
{
/*
0 =>
array
'file' => string '/var/www/mublog/plugins/FeedSub/FeedSubPlugin.php' (length=49)
'line' => int 77
'function' => string '_m' (length=2)
'args' =>
array
0 => &string 'Feeds' (length=5)
*/
static $cached;
$path = $backtrace[0]['file'];
if (!isset($cached[$path])) {
$path = common::normalizePath($path);
$cached[$path] = common::pluginFromPath($path);
}
return $cached[$path];
}
/** /**
* Content negotiation for language codes * Content negotiation for language codes
* *
* @param string $http_accept_lang_header HTTP Accept-Language header * @param string $http_accept_lang_header HTTP Accept-Language header
* @return string language code for best language match, false otherwise * @return string language code for best language match, false otherwise
*/ */
function client_preferred_language(string $http_accept_lang_header): string function client_preferred_language(string $http_accept_lang_header): string
{ {
$client_langs = []; $client_langs = [];
$all_languages = common_config('site', 'languages'); $all_languages = Common::config('site', 'languages');
preg_match_all('"(((\S\S)-?(\S\S)?)(;q=([0-9.]+))?)\s*(,\s*|$)"', preg_match_all('"(((\S\S)-?(\S\S)?)(;q=([0-9.]+))?)\s*(,\s*|$)"',
strtolower($http_accept_lang_header), $http_langs); strtolower($http_accept_lang_header), $http_langs);
@ -203,10 +209,6 @@ function is_rtl(string $lang_value): bool
/** /**
* Get a list of all languages that are enabled in the default config * Get a list of all languages that are enabled in the default config
* *
* This should ONLY be called when setting up the default config in common.php.
* Any other attempt to get a list of languages should instead call
* common_config('site','languages')
*
* @return array mapping of language codes to language info * @return array mapping of language codes to language info
*/ */
function get_all_languages(): array function get_all_languages(): array