Split user language (UI translation) from locale (formatted values) (#4595)

This commit is contained in:
Kevin Papst
2024-01-30 00:09:53 +01:00
committed by GitHub
parent 12ef19df28
commit df3ca9d5a9
39 changed files with 685 additions and 881 deletions

View File

@@ -12,10 +12,9 @@ namespace App\Form\Helper;
use App\Configuration\LocaleService;
use App\Configuration\SystemConfiguration;
use App\Entity\Project;
use Symfony\Contracts\Translation\LocaleAwareInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
final class ProjectHelper implements LocaleAwareInterface
final class ProjectHelper
{
public const PATTERN_NAME = '{name}';
public const PATTERN_COMMENT = '{comment}';
@@ -33,7 +32,11 @@ final class ProjectHelper implements LocaleAwareInterface
private bool $showEnd = false;
private ?string $locale = null;
public function __construct(private SystemConfiguration $configuration, private LocaleService $localeService, private TranslatorInterface $translator)
public function __construct(
private readonly SystemConfiguration $configuration,
private readonly LocaleService $localeService,
private readonly TranslatorInterface $translator
)
{
}
@@ -42,7 +45,7 @@ final class ProjectHelper implements LocaleAwareInterface
return $this->locale ?? \Locale::getDefault();
}
public function setLocale(string $locale): void
public function setLocale(?string $locale): void
{
$this->locale = $locale;
}

View File

@@ -13,6 +13,7 @@ use App\Configuration\LocaleService;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Intl\Locales;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
@@ -20,21 +21,32 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
*/
final class LanguageType extends AbstractType
{
public function __construct(private LocaleService $localeService)
public function __construct(private readonly LocaleService $localeService)
{
}
public function configureOptions(OptionsResolver $resolver): void
{
$choices = [];
foreach ($this->localeService->getAllLocales() as $key) {
$name = ucfirst(Locales::getName($key, $key));
$choices[$name] = $key;
}
$resolver->setDefault('choices', function (Options $options) {
$choices = [];
if ($options['translated_only'] === true) {
$locales = $this->localeService->getTranslatedLocales();
} else {
$locales = $this->localeService->getAllLocales();
}
foreach ($locales as $key) {
$name = ucfirst(Locales::getName($key, $key));
$choices[$name] = $key;
}
return $choices;
});
$resolver->setDefaults([
'choices' => $choices,
'label' => 'language',
'translated_only' => false,
'choice_translation_domain' => false,
]);
}

View File

@@ -16,6 +16,8 @@ use Symfony\Component\Form\CallbackTransformer;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
final class TimePickerType extends AbstractType
@@ -40,6 +42,11 @@ final class TimePickerType extends AbstractType
]);
}
public function buildView(FormView $view, FormInterface $form, array $options): void
{
$view->vars['format'] = $options['format'];
}
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->addModelTransformer(

View File

@@ -9,30 +9,43 @@
namespace App\Form\Type;
use App\Configuration\LocaleService;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\CallbackTransformer;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* Custom form field type to select the user language.
* Custom form field type to select the user language, which is used to translate the UI.
* @extends AbstractType<string>
*/
final class UserLanguageType extends AbstractType
{
public function __construct(private UrlGeneratorInterface $router, private TranslatorInterface $translator)
public function __construct(private readonly LocaleService $localeService)
{
}
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->addModelTransformer(new CallbackTransformer(
function ($value) {
if ($value === null) {
return null;
}
return $this->localeService->getNearestTranslationLocale($value);
},
function ($value) {
return $value;
}
));
}
public function configureOptions(OptionsResolver $resolver): void
{
$route = $this->router->generate('help_locales');
$message = $this->translator->trans('user.language.help');
$moreLink = $this->translator->trans('help_locales');
$resolver->setDefaults([
'help_html' => true,
'help' => sprintf('%2$s <a href="%1$s" target="help_locales">%3$s</a>', $route, $message, $moreLink)
'label' => 'language',
'translated_only' => true,
]);
}

View File

@@ -0,0 +1,47 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* Custom form field type to select the user locale, which is used to format date/time/money/number values.
*
* @extends AbstractType<string>
*/
final class UserLocaleType extends AbstractType
{
public function __construct(
private readonly UrlGeneratorInterface $router,
private readonly TranslatorInterface $translator
)
{
}
public function configureOptions(OptionsResolver $resolver): void
{
$route = $this->router->generate('help_locales');
$moreLink = $this->translator->trans('help_locales');
$resolver->setDefaults([
'label' => 'locale',
'help_html' => true,
'help' => sprintf('<a href="%1$s" target="help_locales">%2$s</a>', $route, $moreLink)
]);
}
public function getParent(): string
{
return LanguageType::class;
}
}

View File

@@ -15,6 +15,7 @@ use App\Form\Type\AvatarType;
use App\Form\Type\MailType;
use App\Form\Type\TimezoneType;
use App\Form\Type\UserLanguageType;
use App\Form\Type\UserLocaleType;
use App\Form\Type\UserType;
use App\Form\Type\YesNoType;
use Symfony\Component\Form\AbstractType;
@@ -80,6 +81,10 @@ class UserEditType extends AbstractType
'required' => true,
]);
$builder->add('locale', UserLocaleType::class, [
'required' => true,
]);
$builder->add('timezone', TimezoneType::class, [
'required' => true,
]);