Lines 1-277
Link Here
|
1 |
[% USE raw %] |
1 |
[% USE JSON.Escape %] |
2 |
[% USE Asset %] |
|
|
3 |
[% SET footerjs = 1 %] |
2 |
[% SET footerjs = 1 %] |
4 |
[% INCLUDE 'doc-head-open.inc' %] |
3 |
[% INCLUDE 'doc-head-open.inc' %] |
5 |
<title>Localization › Koha</title> |
4 |
<title>Localization › Koha</title> |
6 |
[% INCLUDE 'doc-head-close.inc' popup => 1 %] |
5 |
[% INCLUDE 'doc-head-close.inc' %] |
7 |
<style>#localization { margin-top: 1em; }</style> |
6 |
<style> |
|
|
7 |
#localization { margin-top: 1em; } |
8 |
|
9 |
.add-translation-block { |
10 |
margin-bottom: 5px; |
11 |
} |
12 |
|
13 |
.add-translation-block form { |
14 |
display: inline-flex; |
15 |
align-items: center; |
16 |
} |
17 |
code { |
18 |
background-color: lightgrey; |
19 |
font-weight: bold; |
20 |
display: inline-block; |
21 |
padding: 2px 4px; |
22 |
} |
23 |
</style> |
8 |
</head> |
24 |
</head> |
9 |
|
25 |
|
10 |
<body id="admin_localization" class="admin"> |
26 |
<body id="admin_localization" class="admin"> |
11 |
<div class="container-fluid"> |
27 |
[% INCLUDE 'header.inc' %] |
12 |
<div class="row"> |
28 |
[% INCLUDE 'prefs-admin-search.inc' %] |
13 |
<div class="col-sm-12"> |
|
|
14 |
<form id="add_translation" method="post"> |
15 |
<input type="hidden" name="entity" value="[% entity | html %]" /> |
16 |
<input type="hidden" name="code" value="[% code | html %]" /> |
17 |
<input type="hidden" name="interface" value="[% interface_side | html %]" /> |
18 |
<fieldset class="rows clearfix"> |
19 |
<ol> |
20 |
<li> |
21 |
<span class="label">Authorized value:</span> |
22 |
[% code | html %] |
23 |
</li> |
24 |
<li> |
25 |
<label for="lang">Language:</label> |
26 |
<select name="lang" id="lang"> |
27 |
[% FOR language IN languages %] |
28 |
[% FOR sublanguage IN language.sublanguages_loop %] |
29 |
[% IF language.plural %] |
30 |
<option value="[% sublanguage.rfc4646_subtag | html %]">[% sublanguage.native_description | html %] [% sublanguage.region_description | html %] ([% sublanguage.rfc4646_subtag | html %])</option> |
31 |
[% ELSE %] |
32 |
<option value="[% sublanguage.rfc4646_subtag | html %]">[% sublanguage.native_description | html %] ([% sublanguage.rfc4646_subtag | html %])</option> |
33 |
[% END %] |
34 |
[% END %] |
35 |
[% END %] |
36 |
</select> |
37 |
</li> |
38 |
<li> |
39 |
<label for="translation">Translation:</label> |
40 |
<input type="text" size="40" name="translation" id="translation" /> |
41 |
</li> |
42 |
<li> |
43 |
<span class="label"> </span> |
44 |
<input type="submit" value="Add" /> |
45 |
</li> |
46 |
</ol> |
47 |
</fieldset> |
48 |
</form> |
49 |
</div> <!-- /.col-sm-12 --> |
50 |
</div> <!-- /.row --> |
51 |
|
29 |
|
52 |
<div class="row"> |
30 |
<div id="breadcrumbs"> |
53 |
<div class="col-sm-12"> |
31 |
<a href="/cgi-bin/koha/mainpage.pl">Home</a> |
54 |
<div id="messages"></div> |
32 |
› |
55 |
</div> <!-- /.col-sm-12 --> |
33 |
<a href="/cgi-bin/koha/admin/admin-home.pl">Administration</a> |
56 |
</div> <!-- /.row --> |
34 |
› |
|
|
35 |
Localization |
36 |
</div> |
37 |
|
38 |
<div class="main container-fluid"> |
57 |
|
39 |
|
58 |
<div class="row"> |
40 |
<div class="row"> |
59 |
<div class="col-sm-12"> |
41 |
<div class="col-sm-10 col-sm-push-2"> |
60 |
<table id="localization"> |
42 |
<main> |
61 |
<thead> |
43 |
<h2>Localization</h2> |
62 |
<tr> |
44 |
|
63 |
<th>Id</th> |
45 |
<table id="localization"> |
64 |
<th>Entity</th> |
46 |
<thead> |
65 |
<th>Code</th> |
47 |
<tr> |
66 |
<th>Language</th> |
48 |
<th>Context</th> |
67 |
<th>Translation</th> |
49 |
<th>Source</th> |
68 |
<th class="NoSort"> </th> |
50 |
<th>Translations</th> |
69 |
</tr> |
51 |
</tr> |
70 |
</thead> |
52 |
</thead> |
71 |
<tbody> |
53 |
<tbody> |
72 |
[% FOR t IN translations %] |
54 |
</tbody> |
73 |
<tr id="row_id_[% t.id | html %]" data-id="[% t.id | html %]"> |
55 |
</table> |
74 |
<td>[% t.id | html %]</td> |
56 |
</main> |
75 |
<td>[% t.entity | html %]</td> |
57 |
</div> |
76 |
<td>[% t.code | html %]</td> |
58 |
|
77 |
<td class="lang">[% t.lang | html %]</td> |
59 |
<div class="col-sm-2 col-sm-pull-10"> |
78 |
<td class="translation" contenteditable="true">[% t.translation | html %]</td> |
60 |
<aside> |
79 |
<td class="actions"><a href="#" class="delete"><i class="fa fa-trash"></i> Delete</a></td> |
61 |
[% INCLUDE 'admin-menu.inc' %] |
80 |
</tr> |
62 |
</aside> |
81 |
[% END %] |
63 |
</div> |
82 |
</tbody> |
64 |
</div> |
83 |
</table> |
65 |
</div> |
84 |
</div> <!-- /.col-sm-12 --> |
|
|
85 |
</div> <!-- /.row --> |
86 |
</div> <!-- /.container-fluid --> |
87 |
|
66 |
|
88 |
[% MACRO jsinclude BLOCK %] |
67 |
[% MACRO jsinclude BLOCK %] |
89 |
[% INCLUDE 'datatables.inc' %] |
68 |
[% INCLUDE 'datatables.inc' %] |
90 |
<script> |
69 |
<script> |
91 |
|
70 |
|
92 |
function show_message( params ) { |
|
|
93 |
var type = params.type; |
94 |
var data = params.data; |
95 |
var messages = $("#messages"); |
96 |
var message; |
97 |
if ( type == 'success_on_update' ) { |
98 |
message = $('<div class="dialog message"></div>'); |
99 |
message.text(_("Entity %s (code %s) for lang %s has correctly been updated with '%s'").format(data.entity, data.code, data.lang, data.translation)); |
100 |
} else if ( type == 'error_on_update' ) { |
101 |
message = $('<div class="dialog alert"></div>'); |
102 |
if ( data.error_code == 'already_exists' ) { |
103 |
message.text(_("A translation already exists for this language.")); |
104 |
} else { |
105 |
message.text(_("An error occurred when updating this translation.")); |
106 |
} |
107 |
} else if ( type == 'success_on_delete' ) { |
108 |
message = $('<div class="dialog message"></div>'); |
109 |
message.text(_("The translation (id %s) has been removed successfully").format(data.id)); |
110 |
} else if ( type == 'error_on_delete' ) { |
111 |
message = $('<div class="dialog alert"></div>'); |
112 |
message.text(_("An error occurred when deleting this translation")); |
113 |
} else if ( type == 'success_on_insert' ) { |
114 |
message = $('<div class="dialog message"></div>'); |
115 |
message.text(_("Translation (id %s) has been added successfully").format(data.id)); |
116 |
} else if ( type == 'error_on_insert' ) { |
117 |
message = $('<div class="dialog alert"></div>'); |
118 |
if ( data.error_code == 'already_exists' ) { |
119 |
message.text(_("A translation already exists for this language.")); |
120 |
} else { |
121 |
message.text(_("An error occurred when adding this translation")); |
122 |
} |
123 |
} |
124 |
|
125 |
$(messages).append(message); |
126 |
|
127 |
setTimeout(function(){ |
128 |
message.hide() |
129 |
}, 3000); |
130 |
} |
131 |
|
132 |
function send_update_request( data, cell ) { |
71 |
function send_update_request( data, cell ) { |
133 |
$.ajax({ |
72 |
$.ajax({ |
134 |
data: data, |
73 |
data: data, |
135 |
type: 'PUT', |
74 |
type: 'PUT', |
136 |
url: '/cgi-bin/koha/svc/localization', |
75 |
url: '/cgi-bin/koha/svc/localization', |
137 |
success: function (data) { |
76 |
success: function (data) { |
138 |
if ( data.error ) { |
77 |
$("#localization").DataTable().draw(false); |
139 |
$(cell).css('background-color', '#FF0000'); |
|
|
140 |
show_message({ type: 'error_on_update', data: data }); |
141 |
} else if ( data.is_changed == 1 ) { |
142 |
$(cell).css('background-color', '#00FF00'); |
143 |
show_message({ type: 'success_on_update', data: data }); |
144 |
} |
145 |
|
146 |
if ( $(cell).hasClass('lang') ) { |
147 |
$(cell).text(data.lang) |
148 |
} else if ( $(cell).hasClass('translation') ) { |
149 |
$(cell).text(data.translation) |
150 |
} |
151 |
}, |
152 |
error: function (data) { |
153 |
$(cell).css('background-color', '#FF9090'); |
154 |
if ( $(cell).hasClass('lang') ) { |
155 |
$(cell).text(data.lang) |
156 |
} else if ( $(cell).hasClass('translation') ) { |
157 |
$(cell).text(data.translation) |
158 |
} |
159 |
show_message({ type: 'error_on_update', data: data }); |
160 |
}, |
161 |
}); |
162 |
} |
163 |
|
164 |
function send_delete_request( id, cell ) { |
165 |
$.ajax({ |
166 |
type: 'DELETE', |
167 |
url: '/cgi-bin/koha/svc/localization/?id='+id, |
168 |
success: function (data) { |
169 |
$("#localization").DataTable().row( '#row_id_' + id ).remove().draw(); |
170 |
show_message({ type: 'success_on_delete', data: data }); |
171 |
}, |
172 |
error: function (data) { |
173 |
$(cell).css('background-color', '#FF9090'); |
174 |
show_message({ type: 'error_on_delete', data: data }); |
175 |
}, |
78 |
}, |
176 |
}); |
79 |
}); |
177 |
} |
80 |
} |
178 |
|
81 |
|
179 |
$(document).ready(function() { |
82 |
$(document).ready(function() { |
180 |
$(".dialog").hide(); |
|
|
181 |
|
182 |
var table = $("#localization").DataTable($.extend(true, {}, dataTablesDefaults, { |
83 |
var table = $("#localization").DataTable($.extend(true, {}, dataTablesDefaults, { |
183 |
"dom": 't', |
84 |
autoWidth: true, |
184 |
"columnDefs": [ |
85 |
serverSide: true, |
185 |
{ 'sortable': false, 'targets': [ 'NoSort' ] } |
86 |
ordering: false, |
186 |
], |
87 |
ajax: { |
187 |
'bPaginate': false, |
|
|
188 |
'autoWidth': false, |
189 |
})); |
190 |
|
191 |
var languages_select = $('<select name="lang" id="lang"></select>'); |
192 |
[% FOR language IN languages %] |
193 |
[% FOR sublanguage IN language.sublanguages_loop %] |
194 |
var option; |
195 |
[% IF language.plural %] |
196 |
option = $('<option value="[% sublanguage.rfc4646_subtag | html %]">[% sublanguage.native_description | html %] [% sublanguage.region_description | html %] ([% sublanguage.rfc4646_subtag | html %])</option>'); |
197 |
$(languages_select).append(option); |
198 |
[% ELSE %] |
199 |
option = $('<option value="[% sublanguage.rfc4646_subtag | html %]">[% sublanguage.native_description | html %] ([% sublanguage.rfc4646_subtag | html %])</option>'); |
200 |
[% END %] |
201 |
$(languages_select).append(option); |
202 |
[% END %] |
203 |
[% END %] |
204 |
|
205 |
$("td.translation").on('focus', function(){ |
206 |
$(this).css('background-color', ''); |
207 |
}); |
208 |
$("td.lang").on('click', function(){ |
209 |
var td = this; |
210 |
var lang = $(td).text(); |
211 |
$(td).css('background-color', ''); |
212 |
var my_select = $(languages_select).clone(); |
213 |
$(my_select).find('option[value="' + lang + '"]').attr('selected', 'selected'); |
214 |
$(my_select).on('click', function(e){ |
215 |
e.stopPropagation(); |
216 |
}); |
217 |
$(my_select).on('change', function(){ |
218 |
var tr = $(this).parent().parent(); |
219 |
var id = $(tr).data('id'); |
220 |
var lang = $(this).find('option:selected').val(); |
221 |
var data = "id=" + encodeURIComponent(id) + "&lang=" + encodeURIComponent(lang); |
222 |
send_update_request( data, td ); |
223 |
}); |
224 |
$(my_select).on('blur', function(){ |
225 |
$(td).html(lang); |
226 |
}); |
227 |
$(this).html(my_select); |
228 |
}); |
229 |
|
230 |
$("td.translation").on('blur', function(){ |
231 |
var tr = $(this).parent(); |
232 |
var id = $(tr).data('id'); |
233 |
var translation = $(this).text(); |
234 |
var data = "id=" + encodeURIComponent(id) + "&translation=" + encodeURIComponent(translation); |
235 |
send_update_request( data, this ); |
236 |
}); |
237 |
|
238 |
$("body").on("click", "a.delete", function(e){ |
239 |
e.preventDefault(); |
240 |
if ( confirm(_("Are you sure you want to delete this translation?")) ) { |
241 |
var td = $(this).parent(); |
242 |
var tr = $(td).parent(); |
243 |
var id = $(tr).data('id'); |
244 |
send_delete_request( id, td ); |
245 |
} |
246 |
}); |
247 |
|
248 |
$("#add_translation").on('submit', function(e){ |
249 |
e.preventDefault(); |
250 |
var entity = $(this).find('input[name="entity"]').val(); |
251 |
var code = $(this).find('input[name="code"]').val(); |
252 |
var lang = $(this).find('select[name="lang"] option:selected').val(); |
253 |
var translation = $(this).find('input[name="translation"]').val(); |
254 |
var data = "entity=" + encodeURIComponent(entity) + "&code=" + encodeURIComponent(code) + "&lang=" + encodeURIComponent(lang) + "&translation=" + encodeURIComponent(translation); |
255 |
$.ajax({ |
256 |
data: data, |
257 |
type: 'POST', |
258 |
url: '/cgi-bin/koha/svc/localization', |
88 |
url: '/cgi-bin/koha/svc/localization', |
259 |
success: function (data) { |
89 |
}, |
260 |
if ( data.error ) { |
90 |
columns: [ |
261 |
show_message({ type: 'error_on_insert', data: data }); |
91 |
{ |
262 |
} else { |
92 |
name: 'context', |
263 |
var new_row = table.row.add( [ data.id, data.entity, data.code, data.lang, data.translation, "<a href=\"#\" class=\"delete\"><i class=\"fa fa-trash\"></i> Delete</a>" ] ).draw().node(); |
93 |
data: 'context', |
264 |
$( new_row ).attr("id", "row_id_" + data.id ).data("id", data.id ); |
94 |
}, |
265 |
show_message({ type: 'success_on_insert', data: data }); |
95 |
{ |
266 |
} |
96 |
name: 'source', |
|
|
97 |
data: 'source', |
267 |
}, |
98 |
}, |
268 |
error: function (data) { |
99 |
{ |
269 |
show_message({ type: 'error_on_insert', data: data }); |
100 |
name: 'targets', |
|
|
101 |
data: 'l10n_targets', |
102 |
render: function (data, type, row, meta) { |
103 |
const languages = [% languages.json %]; |
104 |
const translations = Object.fromEntries(data.map(translation => [translation.language, translation])); |
105 |
let output = ''; |
106 |
for (const language of languages) { |
107 |
const div = $('<div>'); |
108 |
div.addClass('add-translation-block'); |
109 |
const languageCode = $('<code>').text(language.rfc4646_subtag); |
110 |
const translationSpan = $('<span>'); |
111 |
if (translations[language.rfc4646_subtag]) { |
112 |
const translation = translations[language.rfc4646_subtag].translation; |
113 |
translationSpan.text(translation); |
114 |
} else { |
115 |
languageCode.html('<del>' + languageCode.text() + '</del>'); |
116 |
} |
117 |
const translateLink = $('<a>'); |
118 |
translateLink.text('Translate'); |
119 |
translateLink.addClass('add-translation'); |
120 |
translateLink.attr('href', '#'); |
121 |
translateLink.attr('data-l10n-source-id', row.l10n_source_id); |
122 |
translateLink.attr('data-language', language.rfc4646_subtag); |
123 |
translateLink.attr('data-source', row.source); |
124 |
if (translations[language.rfc4646_subtag]) { |
125 |
translateLink.attr('data-translation', translations[language.rfc4646_subtag].translation); |
126 |
} |
127 |
|
128 |
div.append(languageCode, ' ', translationSpan, ' ', translateLink); |
129 |
|
130 |
output += ' ' + div[0].outerHTML; |
131 |
} |
132 |
|
133 |
return output; |
134 |
}, |
270 |
}, |
135 |
}, |
|
|
136 |
], |
137 |
})); |
138 |
|
139 |
$('#localization').on('click', '.add-translation', function (ev) { |
140 |
ev.preventDefault(); |
141 |
const form = $('<form>'); |
142 |
const textarea = $('<textarea>').attr('rows', '1'); |
143 |
textarea.text(ev.target.getAttribute('data-translation')); |
144 |
form.append(textarea); |
145 |
form.append('<button type="submit">Save</button>'); |
146 |
const l10n_source_id = ev.target.getAttribute('data-l10n-source-id'); |
147 |
const language = ev.target.getAttribute('data-language'); |
148 |
form.on('submit', function (ev) { |
149 |
ev.preventDefault(); |
150 |
send_update_request({ |
151 |
id: l10n_source_id, |
152 |
lang: language, |
153 |
translation: textarea.val(), |
154 |
}); |
271 |
}); |
155 |
}); |
|
|
156 |
$(ev.target).hide(); |
157 |
$(ev.target).after(form); |
272 |
}); |
158 |
}); |
273 |
|
|
|
274 |
}); |
159 |
}); |
275 |
</script> |
160 |
</script> |
276 |
[% END %] |
161 |
[% END %] |
277 |
[% INCLUDE 'popup-bottom.inc' %] |
162 |
[% INCLUDE 'intranet-bottom.inc' %] |