View | Details | Raw Unified | Return to bug 14100
Collapse All | Expand All

(-)a/C4/Koha.pm (-3 / +20 lines)
Lines 254-268 sub GetItemTypes { Link Here
254
    my ( %params ) = @_;
254
    my ( %params ) = @_;
255
    my $style = defined( $params{'style'} ) ? $params{'style'} : 'hash';
255
    my $style = defined( $params{'style'} ) ? $params{'style'} : 'hash';
256
256
257
    my $language = C4::Languages::getlanguage();
258
    $language =~ s|^(.*)-.*$|$1|;
257
    # returns a reference to a hash of references to itemtypes...
259
    # returns a reference to a hash of references to itemtypes...
258
    my %itemtypes;
260
    my %itemtypes;
259
    my $dbh   = C4::Context->dbh;
261
    my $dbh   = C4::Context->dbh;
260
    my $query = qq|
262
    my $query = q|
261
        SELECT *
263
        SELECT
264
               itemtypes.itemtype,
265
               itemtypes.description,
266
               itemtypes.rentalcharge,
267
               itemtypes.gstrate,
268
               itemtypes.notforloan,
269
               itemtypes.imageurl,
270
               itemtypes.summary,
271
               itemtypes.checkinmsg,
272
               itemtypes.checkinmsgtype,
273
               itemtypes.sip_media_type,
274
               COALESCE( localization.translation, itemtypes.description ) AS translated_description
262
        FROM   itemtypes
275
        FROM   itemtypes
276
        LEFT JOIN localization ON itemtypes.itemtype = localization.code
277
            AND localization.entity = 'itemtypes'
278
            AND localization.lang = ?
279
        ORDER BY itemtype
263
    |;
280
    |;
264
    my $sth = $dbh->prepare($query);
281
    my $sth = $dbh->prepare($query);
265
    $sth->execute;
282
    $sth->execute( $language );
266
283
267
    if ( $style eq 'hash' ) {
284
    if ( $style eq 'hash' ) {
268
        while ( my $IT = $sth->fetchrow_hashref ) {
285
        while ( my $IT = $sth->fetchrow_hashref ) {
(-)a/Koha/Localization.pm (+13 lines)
Line 0 Link Here
1
package Koha::Localization;
2
3
use Modern::Perl;
4
5
use Koha::Database;
6
7
use base qw(Koha::Object);
8
9
sub type {
10
    return 'Localization';
11
}
12
13
1;
(-)a/Koha/Localizations.pm (+19 lines)
Line 0 Link Here
1
package Koha::Localizations;
2
3
use Modern::Perl;
4
5
use Koha::Database;
6
7
use Koha::Localization;
8
9
use base qw(Koha::Objects);
10
11
sub type {
12
    return 'Localization';
13
}
14
15
sub object_class {
16
    return 'Koha::Localization';
17
}
18
19
1;
(-)a/admin/itemtypes.pl (-16 / +24 lines)
Lines 52-70 use C4::Context; Link Here
52
use C4::Auth;
52
use C4::Auth;
53
use C4::Output;
53
use C4::Output;
54
54
55
sub StringSearch {
56
    my ( $searchstring, $type ) = @_;
57
    my $dbh = C4::Context->dbh;
58
    $searchstring =~ s/\'/\\\'/g;
59
    my @data = split( ' ', $searchstring );
60
    my $sth = $dbh->prepare(
61
        "SELECT * FROM itemtypes WHERE (description LIKE ?) ORDER BY itemtype"
62
	);
63
    $sth->execute("$data[0]%");
64
    return $sth->fetchall_arrayref({});		# return ref-to-array of ref-to-hashes
65
								# like [ fetchrow_hashref(), fetchrow_hashref() ... ]
66
}
67
68
my $input       = new CGI;
55
my $input       = new CGI;
69
my $searchfield = $input->param('description');
56
my $searchfield = $input->param('description');
70
my $script_name = "/cgi-bin/koha/admin/itemtypes.pl";
57
my $script_name = "/cgi-bin/koha/admin/itemtypes.pl";
Lines 100-107 if ( $op eq 'add_form' ) { Link Here
100
    #---- if primkey exists, it's a modify action, so read values to modify...
87
    #---- if primkey exists, it's a modify action, so read values to modify...
101
    my $data;
88
    my $data;
102
    if ($itemtype) {
89
    if ($itemtype) {
103
        my $sth = $dbh->prepare("select * from itemtypes where itemtype=?");
90
        my $sth = $dbh->prepare(q|
104
        $sth->execute($itemtype);
91
            SELECT
92
                   itemtypes.itemtype,
93
                   itemtypes.description,
94
                   itemtypes.rentalcharge,
95
                   itemtypes.gstrate,
96
                   itemtypes.notforloan,
97
                   itemtypes.imageurl,
98
                   itemtypes.summary,
99
                   itemtypes.checkinmsg,
100
                   itemtypes.checkinmsgtype,
101
                   itemtypes.sip_media_type,
102
                   COALESCE( localization.translation, itemtypes.description ) AS translated_description
103
            FROM   itemtypes
104
            LEFT JOIN localization ON itemtypes.itemtype = localization.code
105
                AND localization.entity='itemtypes'
106
                AND localization.lang = ?
107
            WHERE itemtype = ?
108
        |);
109
        my $language = C4::Languages::getlanguage();
110
        $language =~ s|^(.*)-.*$|$1|;
111
        $sth->execute($language, $itemtype);
105
        $data = $sth->fetchrow_hashref;
112
        $data = $sth->fetchrow_hashref;
106
    }
113
    }
107
114
Lines 245-251 elsif ( $op eq 'delete_confirmed' ) { Link Here
245
################## DEFAULT ##################################
252
################## DEFAULT ##################################
246
}
253
}
247
else {    # DEFAULT
254
else {    # DEFAULT
248
    my ($results) = StringSearch( $searchfield, 'web' );
255
    my $results = C4::Koha::GetItemTypes( style => 'array' );
256
    warn Data::Dumper::Dumper $results;
249
    my @loop;
257
    my @loop;
250
    foreach my $itemtype ( @{$results} ) {
258
    foreach my $itemtype ( @{$results} ) {
251
        $itemtype->{imageurl} = getitemtypeimagelocation( 'intranet', $itemtype->{imageurl} );
259
        $itemtype->{imageurl} = getitemtypeimagelocation( 'intranet', $itemtype->{imageurl} );
(-)a/admin/localization.pl (+54 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
use Modern::Perl;
4
5
use C4::Auth;
6
use C4::Output;
7
8
use Koha::Localization;
9
use Koha::Localizations;
10
11
use CGI qw( -utf8 );
12
13
my $query = new CGI;
14
15
my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
16
    {
17
        template_name   => "admin/localization.tt",
18
        authnotrequired => 0,
19
        flagsrequired   => { parameters => 'parameters_remaining_permissions' },
20
        query           => $query,
21
        type            => "intranet",
22
        debug           => 1,
23
    }
24
);
25
26
my $entity = $query->param('entity');
27
my $code = $query->param('code');
28
my $rs = Koha::Localizations->search({ entity => $entity, code => $code });
29
my @translations;
30
while ( my $s = $rs->next ) {
31
    push @translations, {
32
        id => $s->localization_id,
33
        entity => $s->entity,
34
        code => $s->code,
35
        lang => $s->lang,
36
        translation => $s->translation,
37
    }
38
}
39
40
my $languages = [ map {
41
    {
42
        lang => $_->{subtag},
43
        description => ($_->{language_description} or $_->{description} or $_->{language}),
44
    }
45
} @{ C4::Languages::getAllLanguages() } ];
46
47
$template->param(
48
    translations => \@translations,
49
    languages => $languages,
50
    entity => $entity,
51
    code => $code,
52
);
53
54
output_html_with_http_headers $query, $cookie, $template->output;
(-)a/installer/data/mysql/atomicupdate/Bug_14100-add_table_localization.sql (+8 lines)
Line 0 Link Here
1
CREATE TABLE `localization` (
2
      localization_id int(11) NOT NULL AUTO_INCREMENT,
3
      entity varchar(16) COLLATE utf8_unicode_ci NOT NULL,
4
      code varchar(64) COLLATE utf8_unicode_ci NOT NULL,
5
      lang varchar(25) COLLATE utf8_unicode_ci NOT NULL,
6
      translation text COLLATE utf8_unicode_ci,
7
      PRIMARY KEY (localization_id)
8
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
(-)a/installer/data/mysql/kohastructure.sql (+14 lines)
Lines 3532-3537 CREATE TABLE discharges ( Link Here
3532
  CONSTRAINT borrower_discharges_ibfk1 FOREIGN KEY (borrower) REFERENCES borrowers (borrowernumber) ON DELETE CASCADE ON UPDATE CASCADE
3532
  CONSTRAINT borrower_discharges_ibfk1 FOREIGN KEY (borrower) REFERENCES borrowers (borrowernumber) ON DELETE CASCADE ON UPDATE CASCADE
3533
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
3533
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
3534
3534
3535
--
3536
-- Table structure for table 'localization'
3537
--
3538
3539
DROP TABLE IF EXISTS localization;
3540
CREATE TABLE `localization` (
3541
      localization_id int(11) NOT NULL AUTO_INCREMENT,
3542
      entity varchar(16) COLLATE utf8_unicode_ci NOT NULL,
3543
      code varchar(64) COLLATE utf8_unicode_ci NOT NULL,
3544
      lang varchar(25) COLLATE utf8_unicode_ci NOT NULL, --could be a foreign key
3545
      translation text COLLATE utf8_unicode_ci,
3546
      PRIMARY KEY (localization_id)
3547
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
3548
3535
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
3549
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
3536
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
3550
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
3537
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
3551
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/itemtypes.tt (-3 / +6 lines)
Lines 20-25 Data deleted Link Here
20
</title>
20
</title>
21
[% INCLUDE 'doc-head-close.inc' %]
21
[% INCLUDE 'doc-head-close.inc' %]
22
<link rel="stylesheet" type="text/css" href="[% themelang %]/css/datatables.css" />
22
<link rel="stylesheet" type="text/css" href="[% themelang %]/css/datatables.css" />
23
[% INCLUDE 'greybox.inc' %]
23
[% INCLUDE 'datatables.inc' %]
24
[% INCLUDE 'datatables.inc' %]
24
<script type="text/javascript">
25
<script type="text/javascript">
25
//<![CDATA[
26
//<![CDATA[
Lines 108-114 Item types administration Link Here
108
      </li>
109
      </li>
109
  [% END %]
110
  [% END %]
110
      <li>
111
      <li>
111
          <label for="description" class="required">Description: </label><input type="text" id="description" name="description" size="48" value="[% description |html %]" required="required" /> <span class="required">Required</span></li>
112
          <label for="description" class="required">Description: </label><input type="text" id="description" name="description" size="48" value="[% description |html %]" required="required" /> <span class="required">Required</span>
113
          <a href="/cgi-bin/koha/admin/localization.pl?entity=itemtypes&code=[% itemtype %]" title="Translate" rel="gb_page_center[600,500]"><i class="icon-edit"></i> Translate for other languages</a>
114
      </li>
112
     [% IF ( noItemTypeImages ) %]
115
     [% IF ( noItemTypeImages ) %]
113
	 <li><span class="label">Image: </span>Item type images are disabled. To enable them, turn off the <a href="/cgi-bin/koha/admin/preferences.pl?op=search&amp;searchfield=noItemTypeImages">noItemTypeImages system preference</a></li></ol>
116
	 <li><span class="label">Image: </span>Item type images are disabled. To enable them, turn off the <a href="/cgi-bin/koha/admin/preferences.pl?op=search&amp;searchfield=noItemTypeImages">noItemTypeImages system preference</a></li></ol>
114
	 [% ELSE %]</ol>
117
	 [% ELSE %]</ol>
Lines 237-243 Item types administration Link Here
237
			<td>[% itemtype %]</td>
240
			<td>[% itemtype %]</td>
238
		</tr>
241
		</tr>
239
242
240
	<tr><th scope="row">Description</th><td>[% description %]</td></tr>
243
	<tr><th scope="row">Description</th><td>[% translated_description %]</td></tr>
241
	<tr><th scope="row">Loan length</th><td>[% loanlength %]</td></tr>
244
	<tr><th scope="row">Loan length</th><td>[% loanlength %]</td></tr>
242
<tr><th scope="row">Rental charge</th><td>[% rentalcharge %]</td></tr></table>
245
<tr><th scope="row">Rental charge</th><td>[% rentalcharge %]</td></tr></table>
243
		<form action="[% script_name %]" method="post">
246
		<form action="[% script_name %]" method="post">
Lines 273-279 Item types administration Link Here
273
        [% loo.itemtype %]
276
        [% loo.itemtype %]
274
      </a>
277
      </a>
275
    </td>
278
    </td>
276
    <td>[% loo.description %]</td>
279
    <td>[% loo.translated_description %]</td>
277
    <td>[% IF ( loo.notforloan ) %]Yes[% ELSE %]&nbsp;[% END %]</td>
280
    <td>[% IF ( loo.notforloan ) %]Yes[% ELSE %]&nbsp;[% END %]</td>
278
    <td>
281
    <td>
279
    [% UNLESS ( loo.notforloan ) %]
282
    [% UNLESS ( loo.notforloan ) %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/localization.tt (+206 lines)
Line 0 Link Here
1
[% INCLUDE 'doc-head-open.inc' %]
2
<title>Koha &rsaquo; Localization</title>
3
[% INCLUDE 'doc-head-close.inc' %]
4
<link rel="stylesheet" type="text/css" href="[% themelang %]/css/datatables.css" />
5
[% INCLUDE 'greybox.inc' %]
6
[% INCLUDE 'datatables.inc' %]
7
<script type="text/javascript">
8
//<![CDATA[
9
10
    function show_message( params ) {
11
        var type = params.type;
12
        var data = params.data;
13
        var messages = $("#messages");
14
        var message;
15
        if ( type == 'success_on_update' ) {
16
            message = $('<div class="dialog message"></div>');
17
            message.text("Entity %s (code %s) for lang %s has correctly been updated with '%s'".format(data.entity, data.code, data.lang, data.translation));
18
        } else if ( type == 'error_on_update' ) {
19
            message = $('<div class="dialog alert"></div>');
20
            message.text("An error occurred when updating this translation");
21
        } else if ( type == 'success_on_delete' ) {
22
            message = $('<div class="dialog message"></div>');
23
            message.text("The translation (id %s) has been removed successfully".format(data.id));
24
        } else if ( type == 'error_on_delete' ) {
25
            message = $('<div class="dialog alert"></div>');
26
            message.text("An error occurred when deleting this translation");
27
        } else if ( type == 'success_on_insert' ) {
28
            message = $('<div class="dialog message"></div>');
29
            message.text("Translation (id %s) has been added successfully".format(data.id));
30
        } else if ( type == 'error_on_insert' ) {
31
            message = $('<div class="dialog alert"></div>');
32
            message.text("An error occurred when adding this translation");
33
        }
34
35
        $(messages).append(message);
36
37
        setTimeout(function(){
38
            message.hide()
39
        }, 3000);
40
    }
41
42
    function send_update_request( data, cell ) {
43
        $.ajax({
44
            data: data,
45
            type: 'PUT',
46
            url: '/cgi-bin/koha/svc/localization',
47
            success: function (data) {
48
                if ( data.is_changed ) {
49
                    $(cell).css('background-color', '#00FF00');
50
                    show_message({ type: 'success_on_update', data: data });
51
                }
52
                if ( $(cell).hasClass('lang') ) {
53
                    $(cell).text(data.lang)
54
                } else if ( $(cell).hasClass('translation') ) {
55
                    $(cell).text(data.translation)
56
                }
57
            },
58
            error: function (data) {
59
                $(cell).css('background-color', '#FF0000');
60
                if ( $(cell).hasClass('lang') ) {
61
                    $(cell).text(data.lang)
62
                } else if ( $(cell).hasClass('translation') ) {
63
                    $(cell).text(data.translation)
64
                }
65
                show_message({ type: 'error_on_update', data: data });
66
            },
67
        });
68
    }
69
70
    function send_delete_request( id, cell ) {
71
        $.ajax({
72
            type: 'DELETE',
73
            url: '/cgi-bin/koha/svc/localization/?id='+id,
74
            success: function (data) {
75
                $("#localization").DataTable().row( '#row_id_' + id ).remove().draw();
76
                show_message({ type: 'success_on_delete', data: data });
77
            },
78
            error: function (data) {
79
                $(cell).css('background-color', '#FF0000');
80
                show_message({ type: 'error_on_delete', data: data });
81
            },
82
        });
83
    }
84
85
    $(document).ready(function() {
86
        $(".dialog").hide();
87
88
        var table = $("#localization").DataTable($.extend(true, {}, dataTablesDefaults, {
89
            'bPaginate': false,
90
        }));
91
92
        var languages_select = $('<select name="lang"></select>');
93
        [% FOR language IN languages %]
94
            var option = $('<option value="[% language.lang %]">[% language.description %]</option>');
95
            $(languages_select).append(option);
96
        [% END %]
97
98
        $("td.translation").on('focus', function(){
99
            $(this).css('background-color', '');
100
        });
101
        $("td.lang").on('click', function(){
102
            var td = this;
103
            var lang = $(td).text();
104
            $(td).css('background-color', '');
105
            var my_select = $(languages_select).clone();
106
            $(my_select).find('option[value="' + lang + '"]').attr('selected', 'selected');
107
            $(my_select).on('click', function(e){
108
                e.stopPropagation();
109
            });
110
            $(my_select).on('change', function(){
111
                var tr = $(this).parent().parent();
112
                var id = $(tr).data('id');
113
                var lang = $(this).find('option:selected').val();
114
                var data = "id=" + encodeURIComponent(id) + "&lang=" + encodeURIComponent(lang);
115
                send_update_request( data, td );
116
            });
117
            $(my_select).on('blur', function(){
118
                $(td).html(lang);
119
            });
120
            $(this).html(my_select);
121
        });
122
123
        $("td.translation").on('blur', function(){
124
            var tr = $(this).parent();
125
            var id = $(tr).data('id');
126
            var translation = $(this).text();
127
            var data = "id=" + encodeURIComponent(id) + "&translation=" + encodeURIComponent(translation);
128
            send_update_request( data, this );
129
        });
130
131
        $("a.delete").on('click', function(){
132
            if ( confirm(_("Are you sure you want to delete this translation?")) ) {
133
                var td = $(this).parent();
134
                var tr = $(td).parent();
135
                var id = $(tr).data('id');
136
                send_delete_request( id, td );
137
            }
138
        });
139
140
        $("#add_translation").on('submit', function(e){
141
            e.preventDefault();
142
            var entity = $(this).find('input[name="entity"]').val();
143
            var code = $(this).find('input[name="code"]').val();
144
            var lang = $(this).find('select[name="lang"] option:selected').val();
145
            var translation = $(this).find('input[name="translation"]').val();
146
            var data = "entity=" + encodeURIComponent(entity) + "&code=" + encodeURIComponent(code) + "&lang=" + encodeURIComponent(lang) + "&translation=" + encodeURIComponent(translation);
147
            $.ajax({
148
                data: data,
149
                type: 'POST',
150
                url: '/cgi-bin/koha/svc/localization',
151
                success: function (data) {
152
                    // FIXME Should append the delete link
153
                    table.row.add( [ data.id, data.entity, data.code, data.lang, data.translation, "" ] ).draw();
154
                    show_message({ type: 'success_on_insert', data: data });
155
                },
156
                error: function (data) {
157
                    show_message({ type: 'error_on_insert', data: data });
158
                },
159
            });
160
        });
161
162
     });
163
//]]>
164
</script>
165
</head>
166
<body id="admin_localization" class="admin">
167
<div id="main">
168
<table id="localization">
169
    <thead>
170
        <tr>
171
            <th>Id</th>
172
            <th>Entity</th>
173
            <th>Code</th>
174
            <th>Lang</th>
175
            <th>Translation</th>
176
            <th></th>
177
        </tr>
178
    </thead>
179
    <tbody>
180
        [% FOR t IN translations %]
181
        <tr id="row_id_[% t.id %]" data-id="[% t.id %]">
182
            <td>[% t.id %]</td>
183
            <td>[% t.entity %]</td>
184
            <td>[% t.code %]</td>
185
            <td class="lang">[% t.lang %]</td>
186
            <td class="translation" contenteditable="true">[% t.translation %]</td>
187
            <td><a class="delete" title="Delete this translation"><i class="icon-remove"></i></a</td>
188
        </tr>
189
        [% END %]
190
    </tbody>
191
</table>
192
<form id="add_translation" method="post">
193
    <input type="hidden" name="entity" value="[% entity %]" />
194
    <input type="hidden" name="code" value="[% code %]" />
195
    Lang: <select name="lang">
196
        [% FOR language IN languages %]
197
            <option value="[% language.lang %]">[% language.description %]</option>
198
        [% END %]
199
    </select>
200
    Translation: <input type="text" name="translation" />
201
    <input type="submit" value="Add" />
202
</form>
203
<div id="messages"></div>
204
</div>
205
</body>
206
</html>
(-)a/svc/localization (-1 / +92 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/perl
2
3
use Modern::Perl;
4
5
use C4::Service;
6
use Koha::Localizations;
7
8
our ( $query, $response ) = C4::Service->init( parameters => 'parameters_remaining_permissions' );
9
10
sub get_translations {
11
    my $rs = Koha::Localizations->search({ type => $query->param('type'), code => $query->param('code') });
12
    my @translations;
13
    while ( my $s = $rs->next ) {
14
        push @translations, {
15
            id => $s->localization_id,
16
            type => $s->type,
17
            code => $s->code,
18
            lang => $s->lang,
19
            translation => $s->translation,
20
        }
21
    }
22
    $response->param( translations => \@translations );
23
    C4::Service->return_success( $response );
24
}
25
26
sub update_translation {
27
    my $id = $query->param('id');
28
    my $translation = $query->param('translation');
29
    my $lang = $query->param('lang');
30
    my $localization = Koha::Localizations->find( $id );
31
32
    if ( defined $lang and $localization->lang ne $lang ) {
33
        $localization->lang( $lang )
34
    }
35
    if ( defined $translation and $localization->translation ne $translation ) {
36
        $localization->translation( $translation )
37
    }
38
    my $is_changed;
39
    if ( $localization->is_changed ) {
40
        $is_changed = 1;
41
        $localization->store;
42
    }
43
    $response->param(
44
        id          => $localization->localization_id,
45
        entity      => $localization->entity,
46
        code        => $localization->code,
47
        lang        => $localization->lang,
48
        translation => $localization->translation,
49
        is_changed  => $is_changed,
50
    );
51
    C4::Service->return_success( $response );
52
}
53
54
sub add_translation {
55
    my $entity = $query->param('entity');
56
    my $code = $query->param('code');
57
    my $lang = $query->param('lang');
58
    my $translation = $query->param('translation');
59
    my $localization = Koha::Localization->new(
60
        {
61
            entity => $entity,
62
            code => $code,
63
            lang => $lang,
64
            translation => $translation,
65
        }
66
    );
67
    $localization->store;
68
    $localization->localization_id;
69
    $response->param(
70
        id          => $localization->localization_id,
71
        entity      => $localization->entity,
72
        code        => $localization->code,
73
        lang        => $localization->lang,
74
        translation => $localization->translation,
75
    );
76
77
    C4::Service->return_success( $response );
78
}
79
80
sub delete_translation {
81
    my $id = $query->param('id');
82
    Koha::Localizations->find($id)->delete;
83
    $response->param( id => $id );
84
    C4::Service->return_success( $response );
85
}
86
87
C4::Service->dispatch(
88
    [ 'GET /', [ 'id' ], \&get_translations ],
89
    [ 'PUT /', [ 'id' ], \&update_translation ],
90
    [ 'POST /', [ 'entity', 'code', 'lang', 'translation' ], \&add_translation ],
91
    [ 'DELETE /', ['id'],  \&delete_translation ],
92
);

Return to bug 14100