Skip to content

Commit 54f318e

Browse files
webdevinitionMarcel Diegelmannjbtronics
authored
Implemented the ability to set user-defined synonyms/labels for internal element types
* Implementiere bevorzugte Sprachauswahl und Datenquellen-Synonyme Die Spracheinstellungen/System-Settings wurden um die Möglichkeit ergänzt, bevorzugte Sprachen für die Dropdown-Menüs festzulegen. Zudem wurde ein Datenquellen-Synonymsystem implementiert, um benutzerfreundlichere Bezeichnungen anzuzeigen und zu personalisieren. * Anpassung aus Analyse * Entferne alten JSON-basierten Datenquellen-Synonym-Handler Die Verwaltung der Datenquellen-Synonyme wurde überarbeitet, um ein flexibleres und strukturiertes Konzept zu ermöglichen. Der bestehende JSON-basierte Ansatz wurde durch eine neue Service-basierte Architektur ersetzt, die eine bessere Handhabung und Erweiterbarkeit erlaubt. * Ermögliche Rückgabe aller möglichen Sprachoptionen in Verbindung mit den vom Nutzer freigeschalteten. * Removed unnecessary service definition The tag is applied via autoconfiguration * Use default translations for the NotBlank constraint * Started refactoring ElementTypeNameGenerator * Made ElementTypeNameGenerator class readonly * Modified form to work properly with new datastructure * Made the form more beautiful and space saving * Made synonym form even more space saving * Allow to define overrides for any element label there is * Use defined synonyms in ElementTypeNameGenerator * Use ElementTypeNameGenerator where possible * Register synonyms for element types as global translation parameters * Revert changes done to permission layout * Use new synonym system for admin page titles * Removed now unnecessary services * Reworked settings name and translation * Renamed all files to Synonyms * Removed unnecessary translations * Removed unnecessary translations * Fixed duplicate check * Renamed synoynms translations * Use our synonyms for permission translations * Fixed phpstan issue * Added tests --------- Co-authored-by: Marcel Diegelmann <[email protected]> Co-authored-by: Jan Böhmer <[email protected]>
1 parent 5e3bd26 commit 54f318e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1499
-330
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
3+
*
4+
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU Affero General Public License as published
8+
* by the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU Affero General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Affero General Public License
17+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
*/
19+
20+
import { Controller } from '@hotwired/stimulus';
21+
22+
export default class extends Controller {
23+
static targets = ['items'];
24+
static values = {
25+
prototype: String,
26+
prototypeName: { type: String, default: '__name__' },
27+
index: { type: Number, default: 0 },
28+
};
29+
30+
connect() {
31+
if (!this.hasIndexValue || Number.isNaN(this.indexValue)) {
32+
this.indexValue = this.itemsTarget?.children.length || 0;
33+
}
34+
}
35+
36+
add(event) {
37+
event.preventDefault();
38+
39+
const encodedProto = this.prototypeValue || '';
40+
const placeholder = this.prototypeNameValue || '__name__';
41+
if (!encodedProto || !this.itemsTarget) return;
42+
43+
const protoHtml = this._decodeHtmlAttribute(encodedProto);
44+
45+
const idx = this.indexValue;
46+
const html = protoHtml.replace(new RegExp(placeholder, 'g'), String(idx));
47+
48+
const wrapper = document.createElement('div');
49+
wrapper.innerHTML = html;
50+
const newItem = wrapper.firstElementChild;
51+
if (newItem) {
52+
this.itemsTarget.appendChild(newItem);
53+
this.indexValue = idx + 1;
54+
}
55+
}
56+
57+
remove(event) {
58+
event.preventDefault();
59+
const row = event.currentTarget.closest('.tc-item');
60+
if (row) row.remove();
61+
}
62+
63+
_decodeHtmlAttribute(str) {
64+
const tmp = document.createElement('textarea');
65+
tmp.innerHTML = str;
66+
return tmp.value || tmp.textContent || '';
67+
}
68+
}

config/packages/translation.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
framework:
22
default_locale: 'en'
33
# Just enable the locales we need for performance reasons.
4-
enabled_locale: '%partdb.locale_menu%'
4+
enabled_locale: ['en', 'de', 'it', 'fr', 'ru', 'ja', 'cs', 'da', 'zh', 'pl']
55
translator:
66
default_path: '%kernel.project_dir%/translations'
77
fallbacks:

config/packages/twig.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
twig:
22
default_path: '%kernel.project_dir%/templates'
3-
form_themes: ['bootstrap_5_horizontal_layout.html.twig', 'form/extended_bootstrap_layout.html.twig', 'form/permission_layout.html.twig', 'form/filter_types_layout.html.twig']
3+
form_themes: ['bootstrap_5_horizontal_layout.html.twig', 'form/extended_bootstrap_layout.html.twig', 'form/permission_layout.html.twig', 'form/filter_types_layout.html.twig', 'form/synonyms_collection.html.twig']
44

55
paths:
66
'%kernel.project_dir%/assets/css': css
@@ -20,4 +20,4 @@ twig:
2020

2121
when@test:
2222
twig:
23-
strict_variables: true
23+
strict_variables: true

config/permissions.yaml

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
1818

1919
parts: # e.g. this maps to perms_parts in User/Group database
2020
group: "data"
21-
label: "perm.parts"
21+
label: "{{part}}"
2222
operations: # Here are all possible operations are listed => the op name is mapped to bit value
2323
read:
2424
label: "perm.read"
@@ -71,7 +71,7 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
7171

7272

7373
storelocations: &PART_CONTAINING
74-
label: "perm.storelocations"
74+
label: "{{storage_location}}"
7575
group: "data"
7676
operations:
7777
read:
@@ -103,39 +103,39 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
103103

104104
footprints:
105105
<<: *PART_CONTAINING
106-
label: "perm.part.footprints"
106+
label: "{{footprint}}"
107107

108108
categories:
109109
<<: *PART_CONTAINING
110-
label: "perm.part.categories"
110+
label: "{{category}}"
111111

112112
suppliers:
113113
<<: *PART_CONTAINING
114-
label: "perm.part.supplier"
114+
label: "{{supplier}}"
115115

116116
manufacturers:
117117
<<: *PART_CONTAINING
118-
label: "perm.part.manufacturers"
118+
label: "{{manufacturer}}"
119119

120120
projects:
121121
<<: *PART_CONTAINING
122-
label: "perm.projects"
122+
label: "{{project}}"
123123

124124
attachment_types:
125125
<<: *PART_CONTAINING
126-
label: "perm.part.attachment_types"
126+
label: "{{attachment_type}}"
127127

128128
currencies:
129129
<<: *PART_CONTAINING
130-
label: "perm.currencies"
130+
label: "{{currency}}"
131131

132132
measurement_units:
133133
<<: *PART_CONTAINING
134-
label: "perm.measurement_units"
134+
label: "{{measurement_unit}}"
135135

136136
part_custom_states:
137137
<<: *PART_CONTAINING
138-
label: "perm.part_custom_states"
138+
label: "{{part_custom_state}}"
139139

140140
tools:
141141
label: "perm.part.tools"
@@ -377,4 +377,4 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
377377
manage_tokens:
378378
label: "perm.api.manage_tokens"
379379
alsoSet: ['access_api']
380-
apiTokenRole: ROLE_API_FULL
380+
apiTokenRole: ROLE_API_FULL

src/Controller/SettingsController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public function systemSettings(Request $request, TagAwareCacheInterface $cache):
6464
$this->settingsManager->save($settings);
6565

6666
//It might be possible, that the tree settings have changed, so clear the cache
67-
$cache->invalidateTags(['tree_treeview', 'sidebar_tree_update']);
67+
$cache->invalidateTags(['tree_tools', 'tree_treeview', 'sidebar_tree_update', 'synonyms']);
6868

6969
$this->addFlash('success', t('settings.flash.saved'));
7070
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<?php
2+
/*
3+
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
4+
*
5+
* Copyright (C) 2019 - 2025 Jan Böhmer (https://github.com/jbtronics)
6+
*
7+
* This program is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU Affero General Public License as published
9+
* by the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU Affero General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Affero General Public License
18+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
19+
*/
20+
21+
declare(strict_types=1);
22+
23+
24+
namespace App\EventListener;
25+
26+
use App\Services\ElementTypeNameGenerator;
27+
use App\Services\ElementTypes;
28+
use Symfony\Component\DependencyInjection\Attribute\Autowire;
29+
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
30+
use Symfony\Component\HttpKernel\Event\RequestEvent;
31+
use Symfony\Component\Translation\Translator;
32+
use Symfony\Contracts\Cache\CacheInterface;
33+
use Symfony\Contracts\Cache\ItemInterface;
34+
use Symfony\Contracts\Cache\TagAwareCacheInterface;
35+
use Symfony\Contracts\Translation\TranslatorInterface;
36+
37+
#[AsEventListener]
38+
readonly class RegisterSynonymsAsTranslationParametersListener
39+
{
40+
private Translator $translator;
41+
42+
public function __construct(
43+
#[Autowire(service: 'translator.default')] TranslatorInterface $translator,
44+
private TagAwareCacheInterface $cache,
45+
private ElementTypeNameGenerator $typeNameGenerator)
46+
{
47+
if (!$translator instanceof Translator) {
48+
throw new \RuntimeException('Translator must be an instance of Symfony\Component\Translation\Translator or this listener cannot be used.');
49+
}
50+
$this->translator = $translator;
51+
}
52+
53+
public function getSynonymPlaceholders(): array
54+
{
55+
return $this->cache->get('partdb_synonym_placeholders', function (ItemInterface $item) {
56+
$item->tag('synonyms');
57+
58+
59+
$placeholders = [];
60+
61+
//Generate a placeholder for each element type
62+
foreach (ElementTypes::cases() as $elementType) {
63+
//We have a placeholder for singular
64+
$placeholders['{' . $elementType->value . '}'] = $this->typeNameGenerator->typeLabel($elementType);
65+
//We have a placeholder for plural
66+
$placeholders['{{' . $elementType->value . '}}'] = $this->typeNameGenerator->typeLabelPlural($elementType);
67+
68+
//And we have lowercase versions for both
69+
$placeholders['[' . $elementType->value . ']'] = mb_strtolower($this->typeNameGenerator->typeLabel($elementType));
70+
$placeholders['[[' . $elementType->value . ']]'] = mb_strtolower($this->typeNameGenerator->typeLabelPlural($elementType));
71+
}
72+
73+
return $placeholders;
74+
});
75+
}
76+
77+
public function __invoke(RequestEvent $event): void
78+
{
79+
//If we already added the parameters, skip adding them again
80+
if (isset($this->translator->getGlobalParameters()['@@partdb_synonyms_registered@@'])) {
81+
return;
82+
}
83+
84+
//Register all placeholders for synonyms
85+
$placeholders = $this->getSynonymPlaceholders();
86+
foreach ($placeholders as $key => $value) {
87+
$this->translator->addGlobalParameter($key, $value);
88+
}
89+
90+
//Register the marker parameter to avoid double registration
91+
$this->translator->addGlobalParameter('@@partdb_synonyms_registered@@', 'registered');
92+
}
93+
}

src/Form/Type/LanguageMenuEntriesType.php renamed to src/Form/Settings/LanguageMenuEntriesType.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,11 @@
2121
declare(strict_types=1);
2222

2323

24-
namespace App\Form\Type;
24+
namespace App\Form\Settings;
2525

2626
use Symfony\Component\DependencyInjection\Attribute\Autowire;
2727
use Symfony\Component\Form\AbstractType;
2828
use Symfony\Component\Form\Extension\Core\Type\LanguageType;
29-
use Symfony\Component\Form\Extension\Core\Type\LocaleType;
3029
use Symfony\Component\Intl\Languages;
3130
use Symfony\Component\OptionsResolver\OptionsResolver;
3231

0 commit comments

Comments
 (0)