diff --git a/src/Controller/AdminConfigController.php b/src/Controller/AdminConfigController.php index ccbc8b832b..f614a1c67e 100644 --- a/src/Controller/AdminConfigController.php +++ b/src/Controller/AdminConfigController.php @@ -33,7 +33,7 @@ namespace App\Controller; // use App\Core\GSEvent as Event; // use App\Util\Common; use App\Core\DB\DefaultSettings; -use App\Core\I18n; +use function App\Core\I18n\_m; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; @@ -48,14 +48,14 @@ class AdminConfigController extends AbstractController foreach (DefaultSettings::$defaults as $key => $inner) { $options[$key] = []; foreach (array_keys($inner) as $inner_key) { - $options[I18n::_m($key)][I18n::_m($inner_key)] = "{$key}:{$inner_key}"; + $options[_m($key)][_m($inner_key)] = "{$key}:{$inner_key}"; } } - $form = $this->createFormBuilder() - ->add(I18n::_m('Setting'), ChoiceType::class, ['choices' => $options]) - ->add(I18n::_m('Value'), TextType::class) - ->add('save', SubmitType::class, ['label' => I18n::_m('Set site setting')]) + $form = $this->createFormBuilder(null, ['translation_domain' => false]) + ->add(_m('Setting'), ChoiceType::class, ['choices' => $options]) + ->add(_m('Value'), TextType::class) + ->add('save', SubmitType::class, ['label' => _m('Set site setting')]) ->getForm(); $form->handleRequest($request); diff --git a/src/Core/DB/DefaultSettings.php b/src/Core/DB/DefaultSettings.php index 88173e6695..6c889cf9b3 100644 --- a/src/Core/DB/DefaultSettings.php +++ b/src/Core/DB/DefaultSettings.php @@ -32,7 +32,7 @@ namespace App\Core\DB; -use App\Core\I18n; +use App\Core\I18n\I18nHelper; use App\Util\Common; abstract class DefaultSettings @@ -47,7 +47,7 @@ abstract class DefaultSettings 'logo' => null, 'language' => 'en', 'detect_language' => true, - 'languages' => I18n::get_all_languages(), + 'languages' => I18nHelper::get_all_languages(), 'email' => $_ENV['SERVER_ADMIN'] ?? $_ENV['SOCIAL_ADMIN_EMAIL'] ?? null, 'recovery_disclose' => false, // Whether to not say that we found the email in the database, when asking for recovery 'timezone' => 'UTC', diff --git a/src/Core/GNUsocial.php b/src/Core/GNUsocial.php index 89f62c5198..8b0a9e7ab5 100644 --- a/src/Core/GNUsocial.php +++ b/src/Core/GNUsocial.php @@ -42,6 +42,7 @@ namespace App\Core; use App\Core\DB\DB; use App\Core\DB\DefaultSettings; +use App\Core\I18n\I18nHelper; use App\Core\Router\Router; use Doctrine\ORM\EntityManagerInterface; use Psr\Container\ContainerInterface; @@ -87,7 +88,7 @@ class GNUsocial implements EventSubscriberInterface { Log::setLogger($this->logger); GSEvent::setDispatcher($event_dispatcher); - I18n::setTranslator($this->translator); + I18nHelper::setTranslator($this->translator); DB::setManager($this->entity_manager); Router::setRouter($this->router); diff --git a/src/Core/I18n/I18n.php b/src/Core/I18n/I18n.php new file mode 100644 index 0000000000..448028fb86 --- /dev/null +++ b/src/Core/I18n/I18n.php @@ -0,0 +1,115 @@ +. +// }}} + +/** + * Utility functions for i18n + * + * @category I18n + * @package GNU social + * + * @author Matthew Gregg + * @author Ciaran Gultnieks + * @author Evan Prodromou + * @author Diogo Cordeiro + * @author Hugo Sales + * @copyright 2010, 2018-2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ + +namespace App\Core\I18n; + +// Locale category constants are usually predefined, but may not be +// on some systems such as Win32. +$LC_CATEGORIES = [ + 'LC_CTYPE', + 'LC_NUMERIC', + 'LC_TIME', + 'LC_COLLATE', + 'LC_MONETARY', + 'LC_MESSAGES', + 'LC_ALL', +]; +foreach ($LC_CATEGORIES as $key => $name) { + if (!defined($name)) { + define($name, $key); + } +} + +abstract class I18n +{ + // Dummy class, because the bellow function needs to be outside of + // a class, since `rfc/use-static-function` isn't implemented, so + // we'd have to use `I18n::_m`, but the autoloader still needs a class with the same name as the file +} + +/** + * Wrapper for symfony translation with smart domain detection. + * + * If calling from a plugin, this function checks which plugin it was + * being called from and uses that as text domain, which will have + * been set up during plugin initialization. + * + * Also handles plurals and contexts depending on what parameters + * are passed to it: + * + * _m($msg) -- simple message + * _m($ctx, $msg) -- message with context + * _m($msg1, $msg2, $n) -- message that can be singular or plural + * _m($ctx, $msg1, $msg2, $n) -- combination of the previous two + * + * @param string $msg + * @param extra params as described above + * + * @throws InvalidArgumentException + * + * @return string + * + * @todo add parameters + */ +function _m(string $msg /*, ...*/): string +{ + $domain = I18nHelper::_mdomain(debug_backtrace()[0]['file']); + $args = func_get_args(); + switch (count($args)) { + case 1: + // Empty parameters + return I18nHelper::$translator->trans($msg, [], $domain); + case 2: + $context = $args[0]; + $msg_single = $args[1]; + // ASCII 4 is EOT, used to separate context from string + return I18nHelper::$translator->trans($context . '\004' . $msg_single, [], $domain); + case 3: + // '|' separates the singular from the plural version + $msg_single = $args[0]; + $msg_plural = $args[1]; + $n = $args[2]; + return I18nHelper::$translator->trans($msg_single . '|' . $msg_plural, ['%d' => $n], $domain); + case 4: + // Combine both + $context = $args[0]; + $msg_single = $args[1]; + $msg_plural = $args[2]; + $n = $args[3]; + return I18nHelper::$translator->trans($context . '\004' . $msg_single . '|' . $msg_plural, + ['%d' => $n], $domain); + default: + throw new InvalidArgumentException('Bad parameter count to _m()'); + } +} diff --git a/src/Core/I18n.php b/src/Core/I18n/I18nHelper.php similarity index 77% rename from src/Core/I18n.php rename to src/Core/I18n/I18nHelper.php index 38c158aec9..72570c5c20 100644 --- a/src/Core/I18n.php +++ b/src/Core/I18n/I18nHelper.php @@ -18,44 +18,25 @@ // }}} /** - * Utility functions for i18n + * Dynamic router loader and URLMapper interface atop Symfony's router * - * @category I18n - * @package GNU social + * Converts a path into a set of parameters, and vice versa + * + * @package GNUsocial + * @category URL * - * @author Matthew Gregg - * @author Ciaran Gultnieks - * @author Evan Prodromou - * @author Diogo Cordeiro * @author Hugo Sales - * @copyright 2010, 2018-2020 Free Software Foundation, Inc http://www.fsf.org + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ -namespace App\Core; - -// Locale category constants are usually predefined, but may not be -// on some systems such as Win32. -$LC_CATEGORIES = [ - 'LC_CTYPE', - 'LC_NUMERIC', - 'LC_TIME', - 'LC_COLLATE', - 'LC_MONETARY', - 'LC_MESSAGES', - 'LC_ALL', -]; -foreach ($LC_CATEGORIES as $key => $name) { - if (!defined($name)) { - define($name, $key); - } -} +namespace App\Core\I18n; use App\Util\Formatting; use InvalidArgumentException; use Symfony\Contracts\Translation\TranslatorInterface; -abstract class I18n +abstract class I18nHelper { public static ?TranslatorInterface $translator = null; @@ -65,15 +46,12 @@ abstract class I18n } /** - * 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 + * Looks for which plugin we've been called from to get the gettext domain; + * if not in a plugin subdirectory, we'll use the default 'Core'. * * @return string - * @private */ - public static function _mdomain(array $backtrace): string + public static function _mdomain(string $path): string { /* 0 => @@ -86,7 +64,6 @@ abstract class I18n 0 => &string 'Feeds' (length=5) */ static $cached; - $path = $backtrace[0]['file']; if (!isset($cached[$path])) { $path = Formatting::normalizePath($path); $cached[$path] = Formatting::pluginFromPath($path); @@ -237,59 +214,4 @@ abstract class I18n 'vi' => ['q' => 0.8, 'lang' => 'vi', 'name' => 'Vietnamese', 'direction' => 'ltr'], ]; } - - /** - * Wrapper for symfony translation with smart domain detection. - * - * If calling from a plugin, this function checks which plugin it was - * being called from and uses that as text domain, which will have - * been set up during plugin initialization. - * - * Also handles plurals and contexts depending on what parameters - * are passed to it: - * - * _m($msg) -- simple message - * _m($ctx, $msg) -- message with context - * _m($msg1, $msg2, $n) -- message that can be singular or plural - * _m($ctx, $msg1, $msg2, $n) -- combination of the previous two - * - * @param string $msg - * @param extra params as described above - * - * @throws InvalidArgumentException - * - * @return string - * - */ - public static function _m(string $msg /*, ...*/): string - { - $domain = self::_mdomain(debug_backtrace()); - $args = func_get_args(); - switch (count($args)) { - case 1: - // Empty parameters - return self::$translator->trans($msg, [], $domain); - case 2: - $context = $args[0]; - $msg_single = $args[1]; - // ASCII 4 is EOT, used to separate context from string - return self::$translator->trans($context . '\004' . $msg_single, [], $domain); - case 3: - // '|' separates the singular from the plural version - $msg_single = $args[0]; - $msg_plural = $args[1]; - $n = $args[2]; - return self::$translator->trans($msg_single . '|' . $msg_plural, ['%d' => $n], $domain); - case 4: - // Combine both - $context = $args[0]; - $msg_single = $args[1]; - $msg_plural = $args[2]; - $n = $args[3]; - return self::$translator->trans($context . '\004' . $msg_single . '|' . $msg_plural, - ['%d' => $n], $domain); - default: - throw new InvalidArgumentException('Bad parameter count to _m()'); - } - } }