Skip to content

Commit fba80d2

Browse files
authored
Allow to add and delete messages in web ui (#31)
* Cleanup * Do not depractate the catalogue fetcher * Cleanup * Fixes * Make sure we can add translations with WebUI * Added a delete button * Code style
1 parent c290610 commit fba80d2

File tree

7 files changed

+184
-24
lines changed

7 files changed

+184
-24
lines changed

Controller/WebUIController.php

Lines changed: 53 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,31 +17,36 @@
1717
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
1818
use Symfony\Component\Intl\Intl;
1919
use Symfony\Component\Translation\MessageCatalogue;
20-
use Symfony\Component\Translation\Translator;
2120
use Translation\Bundle\Exception\MessageValidationException;
2221
use Translation\Bundle\Model\WebUiMessage;
22+
use Translation\Bundle\Service\StorageService;
2323
use Translation\Common\Exception\StorageException;
2424
use Translation\Bundle\Model\CatalogueMessage;
25+
use Translation\Common\Model\Message;
2526

2627
/**
2728
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
2829
*/
2930
class WebUIController extends Controller
3031
{
32+
/**
33+
* Show a dashboard for the configuration.
34+
*
35+
* @param string|null $configName
36+
*
37+
* @return Response
38+
*/
3139
public function indexAction($configName = null)
3240
{
3341
$config = $this->getConfiguration($configName);
42+
$localeMap = $this->getLocale2LanguageMap();
43+
$catalogues = $this->get('php_translation.catalogue_fetcher')->getCatalogues(array_keys($localeMap), [$config['output_dir']]);
3444

35-
$configuedLocales = $this->getParameter('php_translation.locales');
36-
$allLocales = Intl::getLocaleBundle()->getLocaleNames('en');
37-
$locales = [];
38-
foreach ($configuedLocales as $l) {
39-
$locales[$l] = $allLocales[$l];
40-
}
41-
$catalogues = $this->get('php_translation.catalogue_fetcher')->getCatalogues($configuedLocales, [$config['output_dir']]);
4245
$catalogueSize = [];
4346
$maxDomainSize = [];
4447
$maxCatalogueSize = 1;
48+
49+
// For each catalogue (or locale)
4550
/** @var MessageCatalogue $catalogue */
4651
foreach ($catalogues as $catalogue) {
4752
$locale = $catalogue->getLocale();
@@ -66,26 +71,29 @@ public function indexAction($configName = null)
6671
'catalogueSize' => $catalogueSize,
6772
'maxDomainSize' => $maxDomainSize,
6873
'maxCatalogueSize' => $maxCatalogueSize,
69-
'locales' => $locales,
74+
'localeMap' => $localeMap,
7075
'configName' => $configName,
7176
'configNames' => $this->get('php_translation.configuration_manager')->getNames(),
7277
]);
7378
}
7479

7580
/**
76-
* @param $locale
77-
* @param $domain
81+
* Show a catalogue.
82+
*
83+
* @param string $configName
84+
* @param string $locale
85+
* @param string $domain
7886
*
7987
* @return Response
8088
*/
8189
public function showAction($configName, $locale, $domain)
8290
{
8391
$config = $this->getConfiguration($configName);
8492
$locales = $this->getParameter('php_translation.locales');
85-
/** @var Translator $translator */
86-
$catalogues = $this->get('php_translation.catalogue_fetcher')->getCatalogues($locales, [$config['output_dir']]);
93+
94+
// Get a catalogue manager and load it with all the catalogues
8795
$catalogueManager = $this->get('php_translation.catalogue_manager');
88-
$catalogueManager->load($catalogues);
96+
$catalogueManager->load($this->get('php_translation.catalogue_fetcher')->getCatalogues($locales, [$config['output_dir']]));
8997

9098
/** @var CatalogueMessage[] $messages */
9199
$messages = $catalogueManager->getMessages($locale, $domain);
@@ -106,21 +114,24 @@ public function showAction($configName, $locale, $domain)
106114

107115
/**
108116
* @param Request $request
117+
* @param string $configName
118+
* @param string $locale
109119
* @param string $domain
110120
*
111121
* @return Response
112122
*/
113123
public function createAction(Request $request, $configName, $locale, $domain)
114124
{
115-
$storage = $this->get('php_translation.storage.file.'.$configName);
125+
/** @var StorageService $storage */
126+
$storage = $this->get('php_translation.storage.'.$configName);
116127
try {
117128
$message = $this->getMessage($request, ['Create']);
118129
} catch (MessageValidationException $e) {
119130
return new Response($e->getMessage(), 400);
120131
}
121132

122133
try {
123-
$storage->set($locale, $domain, $message->getKey(), $message->getMessage());
134+
$storage->create(new Message($message->getKey(), $domain, $locale, $message->getMessage()));
124135
} catch (StorageException $e) {
125136
throw new BadRequestHttpException(sprintf(
126137
'Key "%s" does already exist for "%s" on domain "%s".',
@@ -130,7 +141,9 @@ public function createAction(Request $request, $configName, $locale, $domain)
130141
), $e);
131142
}
132143

133-
return new Response('Translation created');
144+
return $this->render('TranslationBundle:WebUI:create.html.twig', [
145+
'message' => $message,
146+
]);
134147
}
135148

136149
/**
@@ -149,7 +162,9 @@ public function editAction(Request $request, $configName, $locale, $domain)
149162
return new Response($e->getMessage(), 400);
150163
}
151164

152-
$this->get('php_translation.storage.file.'.$configName)->update($locale, $domain, $message->getKey(), $message->getMessage());
165+
/** @var StorageService $storage */
166+
$storage = $this->get('php_translation.storage.'.$configName);
167+
$storage->update(new Message($message->getKey(), $domain, $locale, $message->getMessage()));
153168

154169
return new Response('Translation updated');
155170
}
@@ -170,7 +185,9 @@ public function deleteAction(Request $request, $configName, $locale, $domain)
170185
return new Response($e->getMessage(), 400);
171186
}
172187

173-
$this->get('php_translation.storage.file.'.$configName)->delete($locale, $domain, $message->getKey());
188+
/** @var StorageService $storage */
189+
$storage = $this->get('php_translation.storage.'.$configName);
190+
$storage->delete($locale, $domain, $message->getKey());
174191

175192
return new Response('Message was deleted');
176193
}
@@ -222,4 +239,21 @@ private function getMessage(Request $request, array $validationGroups = [])
222239

223240
return $message;
224241
}
242+
243+
/**
244+
* This will return a map of our configured locales and their language name.
245+
*
246+
* @return array locale => language
247+
*/
248+
private function getLocale2LanguageMap()
249+
{
250+
$configuedLocales = $this->getParameter('php_translation.locales');
251+
$names = Intl::getLocaleBundle()->getLocaleNames('en');
252+
$map = [];
253+
foreach ($configuedLocales as $l) {
254+
$map[$l] = $names[$l];
255+
}
256+
257+
return $map;
258+
}
225259
}

Resources/public/css/webui.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@
6060
font-weight: bold;
6161
font-size: 110%
6262
}
63+
.message-delete {
64+
float: right;
65+
}
6366

6467
pre a {
6568
text-decoration: none;

Resources/public/js/webui.js

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,83 @@ function editTranslation(el) {
3131
xmlhttp.send(JSON.stringify({message: el.value, key: el.getAttribute("data-key")}));
3232
}
3333

34+
/**
35+
* Create a new translation
36+
* @param el
37+
* @param url
38+
* @returns {boolean}
39+
*/
40+
function createTranslation(el, url) {
41+
var xmlhttp = new XMLHttpRequest();
42+
var messageInput = document.getElementById('create-message');
43+
var keyInput = document.getElementById('create-key');
44+
45+
xmlhttp.onreadystatechange = function() {
46+
if (xmlhttp.readyState == XMLHttpRequest.DONE ) {
47+
var errorDiv = el.getElementsByClassName("ajax-result")[0];
48+
49+
if (xmlhttp.status == 200) {
50+
messageInput.value = "";
51+
keyInput.value = "";
52+
53+
var resultDiv = document.getElementById("new-translations");
54+
resultDiv.innerHTML = xmlhttp.responseText + resultDiv.innerHTML;
55+
}
56+
else if (xmlhttp.status == 400) {
57+
errorDiv.className += ' error';
58+
errorDiv.innerHTML = xmlhttp.responseText;
59+
}
60+
else {
61+
errorDiv.className += ' error';
62+
errorDiv.innerHTML = "Unknown error";
63+
}
64+
65+
setTimeout(function() {removeResultElement(errorDiv);}, 6000);
66+
}
67+
};
68+
69+
xmlhttp.open("POST", url, true);
70+
xmlhttp.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
71+
xmlhttp.send(JSON.stringify({message: messageInput.value, key: keyInput.value}));
72+
73+
return false;
74+
}
75+
76+
77+
/**
78+
* Delete a translation.
79+
* @param el
80+
*/
81+
function deleteTranslation(el) {
82+
var xmlhttp = new XMLHttpRequest();
83+
var messageKey = el.getAttribute("data-key");
84+
85+
xmlhttp.onreadystatechange = function() {
86+
if (xmlhttp.readyState == XMLHttpRequest.DONE ) {
87+
var row = document.getElementById(messageKey);
88+
var errorDiv = row.getElementsByClassName("ajax-result")[0];
89+
90+
if (xmlhttp.status == 200) {
91+
row.parentNode.removeChild(row);
92+
}
93+
else if (xmlhttp.status == 400) {
94+
errorDiv.className += ' error';
95+
errorDiv.innerHTML = xmlhttp.responseText;
96+
}
97+
else {
98+
errorDiv.className += ' error';
99+
errorDiv.innerHTML = "Unknown error";
100+
}
101+
102+
setTimeout(function() {removeResultElement(errorDiv);}, 6000);
103+
}
104+
};
105+
106+
xmlhttp.open("DELETE", editUrl, true);
107+
xmlhttp.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
108+
xmlhttp.send(JSON.stringify({key: messageKey}));
109+
}
110+
34111
/**
35112
* Remove the result element
36113
*
@@ -40,3 +117,17 @@ function removeResultElement(el) {
40117
el.innerHTML = '';
41118
el.className = "ajax-result";
42119
}
120+
121+
/**
122+
* Toggle visibility of an element
123+
* @param id
124+
*/
125+
function toggleElement(id) {
126+
var el = document.getElementById(id);
127+
if (el.offsetParent === null) {
128+
el.classList.add("in");
129+
} else {
130+
el.classList.remove("in");
131+
}
132+
}
133+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<div class="message row" id="{{ message.key }}">
2+
<div class="col-md-6 col-xs-12">
3+
<a class="message-key" href="#{{ message.key }}">{{ message.key }}</a>
4+
<textarea
5+
class="message-textarea"
6+
data-key="{{ message.key }}"
7+
onchange="editTranslation(this)"
8+
>{{ message.message }}</textarea>
9+
<div class="ajax-result"></div>
10+
</div>
11+
</div>

Resources/views/WebUI/index.html.twig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<div class="catalogue-list">
77
{% for cataloge in catalogues %}
88
<div class="catalouge">
9-
<h3>{{ locales[cataloge.locale] }}</h3>
9+
<h3>{{ localeMap[cataloge.locale] }}</h3>
1010
<table class="domain-table">
1111
{% for domain,messages in cataloge.all %}
1212
{% set pg = (100*messages|length/maxDomainSize[domain])|round %}

Resources/views/WebUI/show.html.twig

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,27 @@
3131

3232
<div class="container-fluid message-list">
3333

34-
<h1>Translations</h1>
34+
<h1>Translations <a href="javascript:void(0);"
35+
class="btn btn-secondary"
36+
onclick='toggleElement("create-translation")'
37+
aria-controls="create-translation">Add new</a></h1>
38+
39+
<div class="collapse" id="create-translation">
40+
<form class="form" onsubmit="return createTranslation(this, '{{ path('translation_create', {configName: configName, locale:currentLocale,domain:currentDomain}) }}')">
41+
<div class="form-group">
42+
<label for="create-key">Key</label>
43+
<input type="text" class="form-control" id="create-key" placeholder="foo.label">
44+
</div>
45+
<div class="form-group">
46+
<label for="create-message">Translation</label>
47+
<input type="email" class="form-control" id="create-message" placeholder="Lorem Ipsum">
48+
</div>
49+
<button type="submit" class="btn btn-primary">Create</button>
50+
<div class="ajax-result"></div>
51+
</form>
52+
</div>
53+
54+
<div id="new-translations"></div>
3555
{% for idx, message in messages if message.new %}
3656
{{ macro.printMessage(idx + 1, message) }}
3757
{% endfor %}
@@ -57,6 +77,8 @@
5777
<span class="text-warning" title="Obsolete">&#x26A0;</span>
5878
{% endif %}
5979
<a class="message-key" href="#{{ message.key }}">{{ message.key }}</a>
80+
<a class="message-delete" href="javascript:void(0)" data-key="{{ message.key }}" title="Delete translation" onclick='confirm("Are you sure?")?deleteTranslation(this):false;'>&#x274C;</a>
81+
6082
<textarea
6183
class="message-textarea"
6284
data-key="{{ message.key }}"

Service/CatalogueFetcher.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,10 @@
1515
use Symfony\Component\Translation\MessageCatalogue;
1616

1717
/**
18-
* Fetches catalogues from source files.
18+
* Fetches catalogues from source files. This will only work with local file storage
19+
* and the actions are read only.
1920
*
2021
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
21-
*
22-
* @deprecated I think this could be removed.. Not sure.
2322
*/
2423
class CatalogueFetcher
2524
{

0 commit comments

Comments
 (0)