diff --git a/config/packages/translation.yaml b/config/packages/translation.yaml index cbc1cd7e..a3f529e3 100644 --- a/config/packages/translation.yaml +++ b/config/packages/translation.yaml @@ -1,7 +1,7 @@ framework: default_locale: 'en' # Just enable the locales we need for performance reasons. - enabled_locale: '%partdb.locale_menu%' + enabled_locale: ['en', 'de', 'it', 'fr', 'ru', 'ja', 'cs', 'da', 'zh', 'pl'] translator: default_path: '%kernel.project_dir%/translations' fallbacks: diff --git a/config/packages/twig.yaml b/config/packages/twig.yaml index 674aa317..78956026 100644 --- a/config/packages/twig.yaml +++ b/config/packages/twig.yaml @@ -7,7 +7,7 @@ twig: globals: allow_email_pw_reset: '%partdb.users.email_pw_reset%' - locale_menu: '%partdb.locale_menu%' + location_settings: '@App\Settings\SystemSettings\LocalizationSettings' attachment_manager: '@App\Services\Attachments\AttachmentManager' label_profile_dropdown_helper: '@App\Services\LabelSystem\LabelProfileDropdownHelper' error_page_admin_email: '%partdb.error_pages.admin_email%' @@ -20,4 +20,4 @@ twig: when@test: twig: - strict_variables: true \ No newline at end of file + strict_variables: true diff --git a/config/parameters.yaml b/config/parameters.yaml index 5b40899d..03ccde15 100644 --- a/config/parameters.yaml +++ b/config/parameters.yaml @@ -8,7 +8,6 @@ parameters: # This is used as workaround for places where we can not access the settings directly (like the 2FA application names) partdb.title: '%env(string:settings:customization:instanceName)%' # The title shown inside of Part-DB (e.g. in the navbar and on homepage) - partdb.locale_menu: ['en', 'de', 'it', 'fr', 'ru', 'ja', 'cs', 'da', 'zh', 'pl'] # The languages that are shown in user drop down menu partdb.default_uri: '%env(string:DEFAULT_URI)%' # The default URI to use for the Part-DB instance (e.g. https://part-db.example.com/). This is used for generating links in emails diff --git a/config/services.yaml b/config/services.yaml index 17611cea..a74bf090 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -188,6 +188,13 @@ services: $fontDirectory: '%kernel.project_dir%/var/dompdf/fonts/' $tmpDirectory: '%kernel.project_dir%/var/dompdf/tmp/' + #################################################################################################################### + # Twig Extensions + #################################################################################################################### + + App\Twig\DataSourceNameExtension: + tags: [ 'twig.extension' ] + #################################################################################################################### # Part info provider system #################################################################################################################### diff --git a/docs/configuration.md b/docs/configuration.md index d4b21781..5ab376ec 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -262,8 +262,6 @@ command `bin/console cache:clear`. The following options are available: -* `partdb.locale_menu`: The codes of the languages, which should be shown in the language chooser menu (the one with the - user icon in the navbar). The first language in the list will be the default language. * `partdb.gdpr_compliance`: When set to true (default value), IP addresses which are saved in the database will be anonymized, by removing the last byte of the IP. This is required by the GDPR (General Data Protection Regulation) in the EU. diff --git a/src/Controller/ToolsController.php b/src/Controller/ToolsController.php index d78aff62..5d353615 100644 --- a/src/Controller/ToolsController.php +++ b/src/Controller/ToolsController.php @@ -61,7 +61,7 @@ class ToolsController extends AbstractController 'default_timezone' => $settings->system->localization->timezone, 'default_currency' => $settings->system->localization->baseCurrency, 'default_theme' => $settings->system->customization->theme, - 'enabled_locales' => $this->getParameter('partdb.locale_menu'), + 'enabled_locales' => array_column($settings->system->localization->preferredLanguages, 'value'), 'demo_mode' => $this->getParameter('partdb.demo_mode'), 'use_gravatar' => $settings->system->privacy->useGravatar, 'gdpr_compliance' => $this->getParameter('partdb.gdpr_compliance'), diff --git a/src/Form/Type/DataSourceJsonType.php b/src/Form/Type/DataSourceJsonType.php new file mode 100644 index 00000000..7d067b41 --- /dev/null +++ b/src/Form/Type/DataSourceJsonType.php @@ -0,0 +1,103 @@ +settings->dataSourceSynonyms; + } + + foreach ($dataSources as $key => $label) { + $initialData = $existingData[$key] ?? $defaultValues[$key] ?? '{}'; + + $builder->add($key, TextareaType::class, [ + 'label' => $label, + 'required' => false, + 'data' => $initialData, + 'attr' => [ + 'rows' => 3, + 'style' => 'font-family: monospace;', + 'placeholder' => sprintf('%s translations in JSON format', ucfirst($key)), + ], + 'constraints' => [ + new Assert\Callback(function ($value, $context) { + if ($value && !static::isValidJson($value)) { + $context->buildViolation('The field must contain valid JSON.')->addViolation(); + } + }), + ], + ]); + } + + $builder->addEventListener(FormEvents::SUBMIT, function (FormEvent $event) use ($defaultValues) { + $data = $event->getData(); + + if (!$data) { + $event->setData($defaultValues); + return; + } + + foreach ($defaultValues as $key => $defaultValue) { + if (empty($data[$key])) { + $data[$key] = $defaultValue; + } else { + $decodedValue = json_decode($data[$key], true); + if (json_last_error() === JSON_ERROR_NONE) { + $data[$key] = json_encode($decodedValue, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); + } + } + } + + $event->setData($data); + }); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_sources' => [], + 'default_values' => [], + ]); + + $resolver->setAllowedTypes('data_sources', 'array'); + $resolver->setAllowedTypes('default_values', 'array'); + } + + /** + * Validates if a string is a valid JSON format. + * + * @param string $json + * @return bool + */ + private static function isValidJson(string $json): bool + { + json_decode($json); + return json_last_error() === JSON_ERROR_NONE; + } +} diff --git a/src/Form/Type/LocaleSelectType.php b/src/Form/Type/LocaleSelectType.php index d47fb57f..b87932d1 100644 --- a/src/Form/Type/LocaleSelectType.php +++ b/src/Form/Type/LocaleSelectType.php @@ -23,7 +23,7 @@ declare(strict_types=1); namespace App\Form\Type; -use Symfony\Component\DependencyInjection\Attribute\Autowire; +use App\Settings\SystemSettings\LocalizationSettings; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\LocaleType; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -35,7 +35,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver; class LocaleSelectType extends AbstractType { - public function __construct(#[Autowire(param: 'partdb.locale_menu')] private readonly array $preferred_languages) + public function __construct(private LocalizationSettings $localizationSetting) { } @@ -47,7 +47,7 @@ class LocaleSelectType extends AbstractType public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ - 'preferred_choices' => $this->preferred_languages, + 'preferred_choices' => array_column($this->localizationSetting->preferredLanguages, 'value'), ]); } } diff --git a/src/Services/Trees/ToolsTreeBuilder.php b/src/Services/Trees/ToolsTreeBuilder.php index 036797f6..1f8becb7 100644 --- a/src/Services/Trees/ToolsTreeBuilder.php +++ b/src/Services/Trees/ToolsTreeBuilder.php @@ -37,6 +37,7 @@ use App\Entity\UserSystem\Group; use App\Entity\UserSystem\User; use App\Helpers\Trees\TreeViewNode; use App\Services\Cache\UserCacheKeyGenerator; +use App\Settings\BehaviorSettings\DataSourceSynonymsSettings; use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Contracts\Cache\ItemInterface; @@ -49,8 +50,14 @@ use Symfony\Contracts\Translation\TranslatorInterface; */ class ToolsTreeBuilder { - public function __construct(protected TranslatorInterface $translator, protected UrlGeneratorInterface $urlGenerator, protected TagAwareCacheInterface $cache, protected UserCacheKeyGenerator $keyGenerator, protected Security $security) - { + public function __construct( + protected TranslatorInterface $translator, + protected UrlGeneratorInterface $urlGenerator, + protected TagAwareCacheInterface $cache, + protected UserCacheKeyGenerator $keyGenerator, + protected Security $security, + protected DataSourceSynonymsSettings $dataSourceSynonymsSettings, + ) { } /** @@ -138,7 +145,7 @@ class ToolsTreeBuilder $this->translator->trans('info_providers.search.title'), $this->urlGenerator->generate('info_providers_search') ))->setIcon('fa-treeview fa-fw fa-solid fa-cloud-arrow-down'); - + $nodes[] = (new TreeViewNode( $this->translator->trans('info_providers.bulk_import.manage_jobs'), $this->urlGenerator->generate('bulk_info_provider_manage') @@ -165,37 +172,37 @@ class ToolsTreeBuilder } if ($this->security->isGranted('read', new Category())) { $nodes[] = (new TreeViewNode( - $this->translator->trans('tree.tools.edit.categories'), + $this->getTranslatedDataSourceOrSynonym('category', 'tree.tools.edit.categories', $this->translator->getLocale()), $this->urlGenerator->generate('category_new') ))->setIcon('fa-fw fa-treeview fa-solid fa-tags'); } if ($this->security->isGranted('read', new Project())) { $nodes[] = (new TreeViewNode( - $this->translator->trans('tree.tools.edit.projects'), + $this->getTranslatedDataSourceOrSynonym('project', 'tree.tools.edit.projects', $this->translator->getLocale()), $this->urlGenerator->generate('project_new') ))->setIcon('fa-fw fa-treeview fa-solid fa-archive'); } if ($this->security->isGranted('read', new Supplier())) { $nodes[] = (new TreeViewNode( - $this->translator->trans('tree.tools.edit.suppliers'), + $this->getTranslatedDataSourceOrSynonym('supplier', 'tree.tools.edit.suppliers', $this->translator->getLocale()), $this->urlGenerator->generate('supplier_new') ))->setIcon('fa-fw fa-treeview fa-solid fa-truck'); } if ($this->security->isGranted('read', new Manufacturer())) { $nodes[] = (new TreeViewNode( - $this->translator->trans('tree.tools.edit.manufacturer'), + $this->getTranslatedDataSourceOrSynonym('manufacturer', 'tree.tools.edit.manufacturer', $this->translator->getLocale()), $this->urlGenerator->generate('manufacturer_new') ))->setIcon('fa-fw fa-treeview fa-solid fa-industry'); } if ($this->security->isGranted('read', new StorageLocation())) { $nodes[] = (new TreeViewNode( - $this->translator->trans('tree.tools.edit.storelocation'), + $this->getTranslatedDataSourceOrSynonym('storagelocation', 'tree.tools.edit.storelocation', $this->translator->getLocale()), $this->urlGenerator->generate('store_location_new') ))->setIcon('fa-fw fa-treeview fa-solid fa-cube'); } if ($this->security->isGranted('read', new Footprint())) { $nodes[] = (new TreeViewNode( - $this->translator->trans('tree.tools.edit.footprint'), + $this->getTranslatedDataSourceOrSynonym('footprint', 'tree.tools.edit.footprint', $this->translator->getLocale()), $this->urlGenerator->generate('footprint_new') ))->setIcon('fa-fw fa-treeview fa-solid fa-microchip'); } @@ -303,4 +310,24 @@ class ToolsTreeBuilder return $nodes; } + + protected function getTranslatedDataSourceOrSynonym(string $dataSource, string $translationKey, string $locale): string + { + $currentTranslation = $this->translator->trans($translationKey); + + $synonyms = $this->dataSourceSynonymsSettings->getSynonymsAsArray(); + + // Call alternatives from DataSourcesynonyms (if available) + if (!empty($synonyms[$dataSource][$locale])) { + $alternativeTranslation = $synonyms[$dataSource][$locale]; + + // Use alternative translation when it deviates from the standard translation + if ($alternativeTranslation !== $currentTranslation) { + return $alternativeTranslation; + } + } + + // Otherwise return the standard translation + return $currentTranslation; + } } diff --git a/src/Services/Trees/TreeViewGenerator.php b/src/Services/Trees/TreeViewGenerator.php index 73ffa5ba..4b30cb18 100644 --- a/src/Services/Trees/TreeViewGenerator.php +++ b/src/Services/Trees/TreeViewGenerator.php @@ -38,6 +38,7 @@ use App\Repository\StructuralDBElementRepository; use App\Services\Cache\ElementCacheTagGenerator; use App\Services\Cache\UserCacheKeyGenerator; use App\Services\EntityURLGenerator; +use App\Settings\BehaviorSettings\DataSourceSynonymsSettings; use App\Settings\BehaviorSettings\SidebarSettings; use Doctrine\ORM\EntityManagerInterface; use InvalidArgumentException; @@ -67,6 +68,7 @@ class TreeViewGenerator protected TranslatorInterface $translator, private readonly UrlGeneratorInterface $router, private readonly SidebarSettings $sidebarSettings, + protected DataSourceSynonymsSettings $dataSourceSynonymsSettings, ) { $this->rootNodeEnabled = $this->sidebarSettings->rootNodeEnabled; $this->rootNodeExpandedByDefault = $this->sidebarSettings->rootNodeExpanded; @@ -212,13 +214,15 @@ class TreeViewGenerator protected function entityClassToRootNodeString(string $class): string { + $locale = $this->translator->getLocale(); + return match ($class) { - Category::class => $this->translator->trans('category.labelp'), - StorageLocation::class => $this->translator->trans('storelocation.labelp'), - Footprint::class => $this->translator->trans('footprint.labelp'), - Manufacturer::class => $this->translator->trans('manufacturer.labelp'), - Supplier::class => $this->translator->trans('supplier.labelp'), - Project::class => $this->translator->trans('project.labelp'), + Category::class => $this->getTranslatedOrSynonym('category', $locale), + StorageLocation::class => $this->getTranslatedOrSynonym('storelocation', $locale), + Footprint::class => $this->getTranslatedOrSynonym('footprint', $locale), + Manufacturer::class => $this->getTranslatedOrSynonym('manufacturer', $locale), + Supplier::class => $this->getTranslatedOrSynonym('supplier', $locale), + Project::class => $this->getTranslatedOrSynonym('project', $locale), default => $this->translator->trans('tree.root_node.text'), }; } @@ -274,4 +278,24 @@ class TreeViewGenerator return $repo->getGenericNodeTree($parent); //@phpstan-ignore-line }); } + + protected function getTranslatedOrSynonym(string $key, string $locale): string + { + $currentTranslation = $this->translator->trans($key . '.labelp'); + + $synonyms = $this->dataSourceSynonymsSettings->getSynonymsAsArray(); + + // Call alternatives from DataSourcesynonyms (if available) + if (!empty($synonyms[$key][$locale])) { + $alternativeTranslation = $synonyms[$key][$locale]; + + // Use alternative translation when it deviates from the standard translation + if ($alternativeTranslation !== $currentTranslation) { + return $alternativeTranslation; + } + } + + // Otherwise return the standard translation + return $currentTranslation; + } } diff --git a/src/Settings/BehaviorSettings/DataSourceSynonymsSettings.php b/src/Settings/BehaviorSettings/DataSourceSynonymsSettings.php new file mode 100644 index 00000000..74b9a2a1 --- /dev/null +++ b/src/Settings/BehaviorSettings/DataSourceSynonymsSettings.php @@ -0,0 +1,73 @@ + '{"en":"", "de":""}']), + options: ['type' => StringType::class], + formType: DataSourceJsonType::class, + formOptions: [ + 'required' => false, + 'data_sources' => [ + 'category' => new TM("settings.behavior.data_source_synonyms.category"), + 'storagelocation' => new TM("settings.behavior.data_source_synonyms.storagelocation"), + 'footprint' => new TM("settings.behavior.data_source_synonyms.footprint"), + 'manufacturer' => new TM("settings.behavior.data_source_synonyms.manufacturer"), + 'supplier' => new TM("settings.behavior.data_source_synonyms.supplier"), + 'project' => new TM("settings.behavior.data_source_synonyms.project"), + ], + 'default_values' => [ + 'category' => '{"en":"Categories", "de":"Kategorien"}', + 'storagelocation' => '{"en":"Storage locations", "de":"Lagerorte"}', + 'footprint' => '{"en":"Footprints", "de":"Footprints"}', + 'manufacturer' => '{"en":"Manufacturers", "de":"Hersteller"}', + 'supplier' => '{"en":"Suppliers", "de":"Lieferanten"}', + 'project' => '{"en":"Projects", "de":"Projekte"}', + ], + ], + )] + #[Assert\Type('array')] + public array $dataSourceSynonyms = [ + 'category' => '{"en":"Categories", "de":"Kategorien"}', + 'storagelocation' => '{"en":"Storage locations", "de":"Lagerorte"}', + 'footprint' => '{"en":"Footprints", "de":"Footprints"}', + 'manufacturer' => '{"en":"Manufacturers", "de":"Hersteller"}', + 'supplier' => '{"en":"Suppliers", "de":"Lieferanten"}', + 'project' => '{"en":"Projects", "de":"Projekte"}', + ]; + + /** + * Get the synonyms data as a structured array. + * + * @return array> The data source synonyms parsed from JSON to array. + */ + public function getSynonymsAsArray(): array + { + $result = []; + foreach ($this->dataSourceSynonyms as $key => $jsonString) { + $result[$key] = json_decode($jsonString, true, 512, JSON_THROW_ON_ERROR) ?? []; + } + + return $result; + } + +} diff --git a/src/Settings/SystemSettings.php b/src/Settings/SystemSettings.php index 83d00afc..a81f5871 100644 --- a/src/Settings/SystemSettings.php +++ b/src/Settings/SystemSettings.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace App\Settings; +use App\Settings\BehaviorSettings\DataSourceSynonymsSettings; use App\Settings\SystemSettings\AttachmentsSettings; use App\Settings\SystemSettings\CustomizationSettings; use App\Settings\SystemSettings\HistorySettings; @@ -37,6 +38,9 @@ class SystemSettings #[EmbeddedSettings()] public ?LocalizationSettings $localization = null; + #[EmbeddedSettings] + public ?DataSourceSynonymsSettings $dataSourceSynonyms = null; + #[EmbeddedSettings()] public ?CustomizationSettings $customization = null; @@ -48,4 +52,4 @@ class SystemSettings #[EmbeddedSettings()] public ?HistorySettings $history = null; -} \ No newline at end of file +} diff --git a/src/Settings/SystemSettings/LocalizationSettings.php b/src/Settings/SystemSettings/LocalizationSettings.php index 434a4e69..bc52f488 100644 --- a/src/Settings/SystemSettings/LocalizationSettings.php +++ b/src/Settings/SystemSettings/LocalizationSettings.php @@ -26,6 +26,8 @@ namespace App\Settings\SystemSettings; use App\Form\Type\LocaleSelectType; use App\Settings\SettingsIcon; use Jbtronics\SettingsBundle\Metadata\EnvVarMode; +use Jbtronics\SettingsBundle\ParameterTypes\ArrayType; +use Jbtronics\SettingsBundle\ParameterTypes\EnumType; use Jbtronics\SettingsBundle\Settings\Settings; use Jbtronics\SettingsBundle\Settings\SettingsParameter; use Jbtronics\SettingsBundle\Settings\SettingsTrait; @@ -60,4 +62,19 @@ class LocalizationSettings envVar: "string:BASE_CURRENCY", envVarMode: EnvVarMode::OVERWRITE )] public string $baseCurrency = 'EUR'; -} \ No newline at end of file + + /** @var PreferredLocales[] */ + #[SettingsParameter(ArrayType::class, + label: new TM("settings.system.localization.preferred_languages"), + description: new TM("settings.system.localization.preferred_languages.help"), + options: ['type' => EnumType::class, 'options' => ['class' => PreferredLocales::class]], + formType: \Symfony\Component\Form\Extension\Core\Type\EnumType::class, + formOptions: ['class' => PreferredLocales::class, 'multiple' => true, 'ordered' => true] + )] + #[Assert\NotBlank()] + #[Assert\Unique()] + #[Assert\All([new Assert\Type(PreferredLocales::class)])] + public array $preferredLanguages = [PreferredLocales::EN, PreferredLocales::DE, + PreferredLocales::IT, PreferredLocales::FR, PreferredLocales::RU, PreferredLocales::JA, + PreferredLocales::CS, PreferredLocales::DA, PreferredLocales::ZH, PreferredLocales::PL]; +} diff --git a/src/Settings/SystemSettings/PreferredLocales.php b/src/Settings/SystemSettings/PreferredLocales.php new file mode 100644 index 00000000..1fe38a54 --- /dev/null +++ b/src/Settings/SystemSettings/PreferredLocales.php @@ -0,0 +1,37 @@ +. + */ + +declare(strict_types=1); + +namespace App\Settings\SystemSettings; + +enum PreferredLocales: string +{ + case EN = 'en'; + case DE = 'de'; + case IT = 'it'; + case FR = 'fr'; + case RU = 'ru'; + case JA = 'ja'; + case CS = 'cs'; + case DA = 'da'; + case ZH = 'zh'; + case PL = 'pl'; +} diff --git a/src/Twig/DataSourceNameExtension.php b/src/Twig/DataSourceNameExtension.php new file mode 100644 index 00000000..d0d8b4b5 --- /dev/null +++ b/src/Twig/DataSourceNameExtension.php @@ -0,0 +1,43 @@ +translator = $translator; + $this->dataSourceSynonyms = $dataSourceSynonymsSettings->getSynonymsAsArray(); + } + + public function getFunctions(): array + { + return [ + new TwigFunction('get_data_source_name', [$this, 'getDataSourceName']), + ]; + } + + /** + * Based on the locale and data source names, gives the right synonym value back or the default translator value. + */ + public function getDataSourceName(string $dataSourceName, string $defaultKey): string + { + $locale = $this->translator->getLocale(); + + // Use alternative dataSource synonym (if available) + if (isset($this->dataSourceSynonyms[$dataSourceName][$locale])) { + return $this->dataSourceSynonyms[$dataSourceName][$locale]; + } + + // Otherwise return the standard translation + return $this->translator->trans($defaultKey); + } +} diff --git a/templates/_turbo_control.html.twig b/templates/_turbo_control.html.twig index 4c178038..46637587 100644 --- a/templates/_turbo_control.html.twig +++ b/templates/_turbo_control.html.twig @@ -22,9 +22,9 @@
- {% for locale in locale_menu %} + {% for locale in location_settings.preferredLanguages %} - {{ locale|language_name }} ({{ locale|upper }}) + app.request.query.all|merge(app.request.attributes.get('_route_params'))|merge({'_locale': locale.value})) }}"> + {{ locale.value|language_name }} ({{ locale.value|upper }}) {% endfor %} -
\ No newline at end of file + diff --git a/templates/admin/category_admin.html.twig b/templates/admin/category_admin.html.twig index 5811640b..f1fe7663 100644 --- a/templates/admin/category_admin.html.twig +++ b/templates/admin/category_admin.html.twig @@ -1,7 +1,9 @@ {% extends "admin/base_admin.html.twig" %} {% block card_title %} - {% trans %}category.labelp{% endtrans %} + {% set dataSourceName = get_data_source_name('category', 'category.labelp') %} + {% set translatedSource = 'category.labelp'|trans %} + {% if dataSourceName != translatedSource %}{{ 'datasource.synonym'|trans({'%name%': translatedSource, '%synonym%': dataSourceName}) }}{% else %}{{ translatedSource }}{% endif %} {% endblock %} {% block additional_pills %} diff --git a/templates/admin/footprint_admin.html.twig b/templates/admin/footprint_admin.html.twig index a2c3e4af..a6acbe84 100644 --- a/templates/admin/footprint_admin.html.twig +++ b/templates/admin/footprint_admin.html.twig @@ -1,7 +1,9 @@ {% extends "admin/base_admin.html.twig" %} {% block card_title %} - {% trans %}footprint.labelp{% endtrans %} + {% set dataSourceName = get_data_source_name('footprint', 'footprint.labelp') %} + {% set translatedSource = 'footprint.labelp'|trans %} + {% if dataSourceName != translatedSource %}{{ 'datasource.synonym'|trans({'%name%': translatedSource, '%synonym%': dataSourceName}) }}{% else %}{{ translatedSource }}{% endif %} {% endblock %} {% block master_picture_block %} diff --git a/templates/admin/manufacturer_admin.html.twig b/templates/admin/manufacturer_admin.html.twig index 5db892c0..3ce9a124 100644 --- a/templates/admin/manufacturer_admin.html.twig +++ b/templates/admin/manufacturer_admin.html.twig @@ -1,7 +1,9 @@ {% extends "admin/base_company_admin.html.twig" %} {% block card_title %} - {% trans %}manufacturer.caption{% endtrans %} + {% set dataSourceName = get_data_source_name('manufacturer', 'manufacturer.caption') %} + {% set translatedSource = 'manufacturer.caption'|trans %} + {% if dataSourceName != translatedSource %}{{ 'datasource.synonym'|trans({'%name%': translatedSource, '%synonym%': dataSourceName}) }}{% else %}{{ translatedSource }}{% endif %} {% endblock %} {% block edit_title %} diff --git a/templates/admin/project_admin.html.twig b/templates/admin/project_admin.html.twig index 1a995069..8066d545 100644 --- a/templates/admin/project_admin.html.twig +++ b/templates/admin/project_admin.html.twig @@ -3,7 +3,9 @@ {# @var entity App\Entity\ProjectSystem\Project #} {% block card_title %} - {% trans %}project.caption{% endtrans %} + {% set dataSourceName = get_data_source_name('project', 'project.caption') %} + {% set translatedSource = 'project.caption'|trans %} + {% if dataSourceName != translatedSource %}{{ 'datasource.synonym'|trans({'%name%': translatedSource, '%synonym%': dataSourceName}) }}{% else %}{{ translatedSource }}{% endif %} {% endblock %} {% block edit_title %} diff --git a/templates/admin/storelocation_admin.html.twig b/templates/admin/storelocation_admin.html.twig index c93339dc..1e60eeea 100644 --- a/templates/admin/storelocation_admin.html.twig +++ b/templates/admin/storelocation_admin.html.twig @@ -2,7 +2,9 @@ {% import "label_system/dropdown_macro.html.twig" as dropdown %} {% block card_title %} - {% trans %}storelocation.labelp{% endtrans %} + {% set dataSourceName = get_data_source_name('storagelocation', 'storelocation.labelp') %} + {% set translatedSource = 'storelocation.labelp'|trans %} + {% if dataSourceName != translatedSource %}{{ 'datasource.synonym'|trans({'%name%': translatedSource, '%synonym%': dataSourceName}) }}{% else %}{{ translatedSource }}{% endif %} {% endblock %} {% block additional_controls %} diff --git a/templates/admin/supplier_admin.html.twig b/templates/admin/supplier_admin.html.twig index ce38a5ca..b5cf7b23 100644 --- a/templates/admin/supplier_admin.html.twig +++ b/templates/admin/supplier_admin.html.twig @@ -1,7 +1,9 @@ {% extends "admin/base_company_admin.html.twig" %} {% block card_title %} - {% trans %}supplier.caption{% endtrans %} + {% set dataSourceName = get_data_source_name('supplier', 'supplier.caption') %} + {% set translatedSource = 'supplier.caption'|trans %} + {% if dataSourceName != translatedSource %}{{ 'datasource.synonym'|trans({'%name%': translatedSource, '%synonym%': dataSourceName}) }}{% else %}{{ translatedSource }}{% endif %} {% endblock %} {% block additional_panes %} diff --git a/templates/components/tree_macros.html.twig b/templates/components/tree_macros.html.twig index 366d42fe..e82cd3b4 100644 --- a/templates/components/tree_macros.html.twig +++ b/templates/components/tree_macros.html.twig @@ -1,13 +1,15 @@ {% macro sidebar_dropdown() %} + {% set currentLocale = app.request.locale %} + {# Format is [mode, route, label, show_condition] #} {% set data_sources = [ - ['categories', path('tree_category_root'), 'category.labelp', is_granted('@categories.read') and is_granted('@parts.read')], - ['locations', path('tree_location_root'), 'storelocation.labelp', is_granted('@storelocations.read') and is_granted('@parts.read')], - ['footprints', path('tree_footprint_root'), 'footprint.labelp', is_granted('@footprints.read') and is_granted('@parts.read')], - ['manufacturers', path('tree_manufacturer_root'), 'manufacturer.labelp', is_granted('@manufacturers.read') and is_granted('@parts.read')], - ['suppliers', path('tree_supplier_root'), 'supplier.labelp', is_granted('@suppliers.read') and is_granted('@parts.read')], - ['projects', path('tree_device_root'), 'project.labelp', is_granted('@projects.read')], - ['tools', path('tree_tools'), 'tools.label', true], + ['categories', path('tree_category_root'), 'category.labelp', is_granted('@categories.read') and is_granted('@parts.read'), 'category'], + ['locations', path('tree_location_root'), 'storelocation.labelp', is_granted('@storelocations.read') and is_granted('@parts.read'), 'storagelocation'], + ['footprints', path('tree_footprint_root'), 'footprint.labelp', is_granted('@footprints.read') and is_granted('@parts.read'), 'footprint'], + ['manufacturers', path('tree_manufacturer_root'), 'manufacturer.labelp', is_granted('@manufacturers.read') and is_granted('@parts.read'), 'manufacturer'], + ['suppliers', path('tree_supplier_root'), 'supplier.labelp', is_granted('@suppliers.read') and is_granted('@parts.read'), 'supplier'], + ['projects', path('tree_device_root'), 'project.labelp', is_granted('@projects.read'), 'project'], + ['tools', path('tree_tools'), 'tools.label', true, 'tool'], ] %} @@ -18,9 +20,9 @@ {% for source in data_sources %} {% if source[3] %} {# show_condition #} -
  • + >{{ get_data_source_name(source[4], source[2]) }} {% endif %} {% endfor %} {% endmacro %} @@ -61,4 +63,4 @@
    -{% endmacro %} \ No newline at end of file +{% endmacro %} diff --git a/templates/form/permission_layout.html.twig b/templates/form/permission_layout.html.twig index 166147b4..3c7f799e 100644 --- a/templates/form/permission_layout.html.twig +++ b/templates/form/permission_layout.html.twig @@ -6,12 +6,34 @@
    {% else %} - {{ form.vars.label | trans }} + def{{ form.vars.label | trans }} {% endif %} @@ -110,4 +132,4 @@ {% endfor %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/translations/messages.cs.xlf b/translations/messages.cs.xlf index 1f234450..ae29c6da 100644 --- a/translations/messages.cs.xlf +++ b/translations/messages.cs.xlf @@ -12669,6 +12669,72 @@ Vezměte prosím na vědomí, že se nemůžete vydávat za uživatele se zakáz <b>Upozorňujeme, že při změně této hodnoty nedochází k převodu měn. Změna výchozí měny po přidání informací o cenách tedy povede k nesprávným cenám!</b> + + + settings.system.localization.preferred_languages + Preferované jazyky + + + + + settings.system.localization.preferred_languages.help + Jazyky, které se zobrazují v uživatelské rozbalovací nabídce + + + + + settings.system.data_source_synonyms + Synonyma zdrojů dat + + + + + settings.system.data_source_synonyms.configuration + Zdroj + + + + + settings.system.data_source_synonyms.configuration.help + Definujte vlastní synonyma pro dané zdroje dat. Očekává se formát JSON s vašimi preferovanými jazykovými ISO kódy. Příklad: %format%. + + + + + settings.behavior.data_source_synonyms.category + Kategorie + + + + + settings.behavior.data_source_synonyms.storagelocation + Skladové umístění + + + + + settings.behavior.data_source_synonyms.footprint + Pouzdro + + + + + settings.behavior.data_source_synonyms.manufacturer + Výrobce + + + + + settings.behavior.data_source_synonyms.supplier + Dodavatel + + + + + settings.behavior.data_source_synonyms.project + Projekt + + settings.system.privacy @@ -13479,5 +13545,11 @@ Vezměte prosím na vědomí, že se nemůžete vydávat za uživatele se zakáz Minimální šířka náhledu (px) + + + datasource.synonym + %name% (Váš synonymum: %synonym%) + + diff --git a/translations/messages.da.xlf b/translations/messages.da.xlf index d7258986..e33a9035 100644 --- a/translations/messages.da.xlf +++ b/translations/messages.da.xlf @@ -12196,5 +12196,11 @@ Bemærk venligst, at du ikke kan kopiere fra deaktiveret bruger. Hvis du prøver Du forsøgte at fjerne/tilføje en mængde sat til nul! Der blev ikke foretaget nogen handling. + + + datasource.synonym + %name% (Dit synonym: %synonym%) + + diff --git a/translations/messages.de.xlf b/translations/messages.de.xlf index 06326a21..44d62d4e 100644 --- a/translations/messages.de.xlf +++ b/translations/messages.de.xlf @@ -12749,6 +12749,72 @@ Bitte beachten Sie, dass Sie sich nicht als deaktivierter Benutzer ausgeben kön <b>Bitte beachten Sie, dass die Währungen bei einer Änderung dieses Wertes nicht umgerechnet werden. Wenn Sie also die Basiswährung ändern, nachdem Sie bereits Preisinformationen hinzugefügt haben, führt dies zu falschen Preisen!</b> + + + settings.system.localization.preferred_languages + Bevorzugte Sprachen + + + + + settings.system.localization.preferred_languages.help + Die Sprachen, die im Dropdown-Menü der Nutzer angezeigt werden + + + + + settings.system.data_source_synonyms + Datenquellen-Synonyme + + + + + settings.system.data_source_synonyms.configuration + Quelle + + + + + settings.system.data_source_synonyms.configuration.help + Definieren Sie Ihre eigenen Synonyme für die angegebenen Datenquellen. Erwartet wird ein JSON-Format mit Ihren bevorzugten Sprache-ISO-Codes. Beispiel: %format%. + + + + + settings.behavior.data_source_synonyms.category + Kategorie + + + + + settings.behavior.data_source_synonyms.storagelocation + Lagerort + + + + + settings.behavior.data_source_synonyms.footprint + Footprint + + + + + settings.behavior.data_source_synonyms.manufacturer + Hersteller + + + + + settings.behavior.data_source_synonyms.supplier + Lieferant + + + + + settings.behavior.data_source_synonyms.project + Projekt + + settings.system.privacy @@ -14189,5 +14255,11 @@ Bitte beachten Sie, dass Sie sich nicht als deaktivierter Benutzer ausgeben kön Maximale Anzahl von Zuordnungen erreicht + + + datasource.synonym + %name% (Ihr Synonym: %synonym%) + + diff --git a/translations/messages.el.xlf b/translations/messages.el.xlf index cc17d9be..86e7737f 100644 --- a/translations/messages.el.xlf +++ b/translations/messages.el.xlf @@ -1535,5 +1535,11 @@ Επεξεργασία + + + datasource.synonym + %name% (Το συνώνυμό σας: %synonym%) + + diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index a2ec2f65..2b4f000b 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -12750,6 +12750,72 @@ Please note, that you can not impersonate a disabled user. If you try you will g <b>Please note that the currencies are not converted, when changing this value. So changing the default currency after you already added price information, will result in wrong prices!</b> + + + settings.system.localization.preferred_languages + Preferred languages + + + + + settings.system.localization.preferred_languages.help + The languages that are shown in user drop down menu + + + + + settings.system.data_source_synonyms + Data source synonyms + + + + + settings.system.data_source_synonyms.configuration + Source + + + + + settings.system.data_source_synonyms.configuration.help + Define your own synonyms for the given data sources. Expected in JSON-format with your preferred language iso-codes. Example: %format%. + + + + + settings.behavior.data_source_synonyms.category + Category + + + + + settings.behavior.data_source_synonyms.storagelocation + Storage location + + + + + settings.behavior.data_source_synonyms.footprint + Footprint + + + + + settings.behavior.data_source_synonyms.manufacturer + Manufacturer + + + + + settings.behavior.data_source_synonyms.supplier + Supplier + + + + + settings.behavior.data_source_synonyms.project + Project + + settings.system.privacy @@ -14190,5 +14256,11 @@ Please note, that you can not impersonate a disabled user. If you try you will g Maximum number of mappings reached + + + datasource.synonym + %name% (Your synonym: %synonym%) + + diff --git a/translations/messages.es.xlf b/translations/messages.es.xlf index fce38e52..83c54fcb 100644 --- a/translations/messages.es.xlf +++ b/translations/messages.es.xlf @@ -12368,5 +12368,11 @@ Por favor ten en cuenta que no puedes personificar a un usuario deshabilitado. S Este componente contiene más de un stock. Cambie la ubicación manualmente para seleccionar el stock deseado. + + + datasource.synonym + %name% (Tu sinónimo: %synonym%) + + diff --git a/translations/messages.fr.xlf b/translations/messages.fr.xlf index 292dbafa..a010463f 100644 --- a/translations/messages.fr.xlf +++ b/translations/messages.fr.xlf @@ -6947,7 +6947,7 @@ Si vous avez fait cela de manière incorrecte ou si un ordinateur n'est plus fia company.edit.address.placeholder - Ex. 99 exemple de rue + Ex. 99 exemple de rue exemple de ville @@ -9097,5 +9097,11 @@ exemple de ville Si vous avez des questions à propos de Part-DB , rendez vous sur <a href="%href%" class="link-external" target="_blank">Github</a> + + + datasource.synonym + %name% (Votre synonyme : %synonym%) + + diff --git a/translations/messages.it.xlf b/translations/messages.it.xlf index 828304eb..d98cd2a2 100644 --- a/translations/messages.it.xlf +++ b/translations/messages.it.xlf @@ -12370,5 +12370,11 @@ Notare che non è possibile impersonare un utente disattivato. Quando si prova a Questo componente contiene più di uno stock. Cambia manualmente la posizione per selezionare quale stock scegliere. + + + datasource.synonym + %name% (Il tuo sinonimo: %synonym%) + + diff --git a/translations/messages.ja.xlf b/translations/messages.ja.xlf index 4becc319..e91f4f97 100644 --- a/translations/messages.ja.xlf +++ b/translations/messages.ja.xlf @@ -8834,5 +8834,11 @@ Exampletown Part-DBについての質問は、<a href="%href%" class="link-external" target="_blank">GitHub</a> にスレッドがあります。 + + + datasource.synonym + %name% (あなたの同義語: %synonym%) + + diff --git a/translations/messages.nl.xlf b/translations/messages.nl.xlf index 760533d7..eb1597ba 100644 --- a/translations/messages.nl.xlf +++ b/translations/messages.nl.xlf @@ -724,5 +724,11 @@ Weet u zeker dat u wilt doorgaan? + + + datasource.synonym + %name% (Uw synoniem: %synonym%) + + diff --git a/translations/messages.pl.xlf b/translations/messages.pl.xlf index b769e273..7d135d2a 100644 --- a/translations/messages.pl.xlf +++ b/translations/messages.pl.xlf @@ -12223,5 +12223,11 @@ Należy pamiętać, że nie możesz udawać nieaktywnych użytkowników. Jeśli Wygenerowany kod + + + datasource.synonym + %name% (Twój synonim: %synonym%) + + diff --git a/translations/messages.ru.xlf b/translations/messages.ru.xlf index 62570acb..4acf1ac1 100644 --- a/translations/messages.ru.xlf +++ b/translations/messages.ru.xlf @@ -731,7 +731,7 @@ user.edit.tfa.disable_tfa_message - Это выключит <b>все активные двухфакторной способы аутентификации пользователя</b>и удалит <b>резервные коды</b>! + Это выключит <b>все активные двухфакторной способы аутентификации пользователя</b>и удалит <b>резервные коды</b>! <br> Пользователь должен будет снова настроить все методы двухфакторной аутентификации и распечатать новые резервные коды! <br><br> <b>Делайте это только в том случае, если вы абсолютно уверены в личности пользователя (обращающегося за помощью), в противном случае учетная запись может быть взломана злоумышленником!</b> @@ -3740,7 +3740,7 @@ tfa_backup.reset_codes.confirm_message - Это удалит все предыдущие коды и создаст набор новых. Это не может быть отменено. + Это удалит все предыдущие коды и создаст набор новых. Это не может быть отменено. Не забудьте распечатать новы кода и хранить их в безопасном месте! @@ -12323,5 +12323,11 @@ Профиль сохранен! + + + datasource.synonym + %name% (Ваш синоним: %synonym%) + + diff --git a/translations/messages.zh.xlf b/translations/messages.zh.xlf index 668c32f2..bc76efb3 100644 --- a/translations/messages.zh.xlf +++ b/translations/messages.zh.xlf @@ -12208,5 +12208,11 @@ Element 3 成功创建 %COUNT% 个元素。 + + + datasource.synonym + %name% (您的同义词: %synonym%) + +