|
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' %] |