Bug 14100: Generic solution for language overlay - Item types
authorJonathan Druart <jonathan.druart@biblibre.com>
Thu, 30 Apr 2015 15:30:47 +0000 (17:30 +0200)
committerTomas Cohen Arazi <tomascohen@theke.io>
Tue, 27 Oct 2015 15:34:06 +0000 (12:34 -0300)
Test plan:
1/ update the Schema (misc/devel/update_dbix_class_files.pl)
2/ Translate templates for some languages (es-DE, de-DE for instance)
3/ Enable them in the pref (search for 'lang') for the staff interface
4/ Go on the item type admin page (admin/itemtypes.pl)
5/ Edit one
6/ Click on the 'translate for other languages' link
7/ You are now on the interface to translate the item type's description
in the languages you want. So translate some :)
8/ Go back on the item type list view (admin/itemtypes.pl)
9/ You should see the original description (non translated)
10/ Switch the language
11/ You should see the translated description in the correct language.
If the description is non translated, the original description is
displayed.

12/ On the different page where the item type is displayed, confirm that
the translated description appears.

Think further / Todo:
1/ Update all occurrences of the item type's description (DONE)
2/ Implement for authorised values
3/ Implement for syspref value (at least textarea)
4/ Implement for branch names
5/ Centralize all the translation on a single page in the admin area
...
N/ Implement a webservice to centralize all the translations and give
the ability to sync the item types/authorised values description with
the rest of the world (push and pull).

Signed-off-by: Josef Moravec <josef.moravec@gmail.com>
Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
19 files changed:
C4/Items.pm
C4/Koha.pm
C4/Search.pm
Koha/Localization.pm [new file with mode: 0644]
Koha/Localizations.pm [new file with mode: 0644]
Koha/Template/Plugin/ItemTypes.pm
admin/itemtypes.pl
admin/localization.pl [new file with mode: 0755]
authorities/authorities.pl
catalogue/search.pl
cataloguing/addbiblio.pl
cataloguing/additem.pl
installer/data/mysql/atomicupdate/Bug_14100-add_table_localization.sql [new file with mode: 0644]
installer/data/mysql/kohastructure.sql
koha-tmpl/intranet-tmpl/prog/en/modules/admin/itemtypes.tt
koha-tmpl/intranet-tmpl/prog/en/modules/admin/localization.tt [new file with mode: 0644]
svc/checkouts
svc/localization [new file with mode: 0755]
tools/batchMod.pl

index b1b9339..213ca4e 100644 (file)
@@ -3027,13 +3027,12 @@ sub PrepareItemrecordDisplay {
 
                         #----- itemtypes
                     } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
-                        my $sth = $dbh->prepare( "SELECT itemtype,description FROM itemtypes ORDER BY description" );
-                        $sth->execute;
+                        my $itemtypes = GetItemTypes( style => 'array' );
                         push @authorised_values, ""
                           unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
-                        while ( my ( $itemtype, $description ) = $sth->fetchrow_array ) {
-                            push @authorised_values, $itemtype;
-                            $authorised_lib{$itemtype} = $description;
+                        for my $itemtype ( @$itemtypes ) {
+                            push @authorised_values, $itemtype->{itemtype};
+                            $authorised_lib{$itemtype->{itemtype}} = $itemtype->{translated_description};
                         }
                         #---- class_sources
                     } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
index d0042e5..d06b8b2 100644 (file)
@@ -254,15 +254,30 @@ sub GetItemTypes {
     my ( %params ) = @_;
     my $style = defined( $params{'style'} ) ? $params{'style'} : 'hash';
 
+    my $language = C4::Languages::getlanguage();
     # returns a reference to a hash of references to itemtypes...
     my %itemtypes;
     my $dbh   = C4::Context->dbh;
-    my $query = qq|
-        SELECT *
+    my $query = q|
+        SELECT
+               itemtypes.itemtype,
+               itemtypes.description,
+               itemtypes.rentalcharge,
+               itemtypes.notforloan,
+               itemtypes.imageurl,
+               itemtypes.summary,
+               itemtypes.checkinmsg,
+               itemtypes.checkinmsgtype,
+               itemtypes.sip_media_type,
+               COALESCE( localization.translation, itemtypes.description ) AS translated_description
         FROM   itemtypes
+        LEFT JOIN localization ON itemtypes.itemtype = localization.code
+            AND localization.entity = 'itemtypes'
+            AND localization.lang = ?
+        ORDER BY itemtype
     |;
     my $sth = $dbh->prepare($query);
-    $sth->execute;
+    $sth->execute( $language );
 
     if ( $style eq 'hash' ) {
         while ( my $IT = $sth->fetchrow_hashref ) {
@@ -540,14 +555,30 @@ Defaults to intranet.
 
 sub getitemtypeinfo {
     my ($itemtype, $interface) = @_;
-    my $dbh        = C4::Context->dbh;
-    my $sth        = $dbh->prepare("select * from itemtypes where itemtype=?");
-    $sth->execute($itemtype);
-    my $res = $sth->fetchrow_hashref;
+    my $dbh      = C4::Context->dbh;
+    my $language = C4::Languages::getlanguage();
+    my $it = $dbh->selectrow_hashref(q|
+        SELECT
+               itemtypes.itemtype,
+               itemtypes.description,
+               itemtypes.rentalcharge,
+               itemtypes.notforloan,
+               itemtypes.imageurl,
+               itemtypes.summary,
+               itemtypes.checkinmsg,
+               itemtypes.checkinmsgtype,
+               itemtypes.sip_media_type,
+               COALESCE( localization.translation, itemtypes.description ) AS translated_description
+        FROM   itemtypes
+        LEFT JOIN localization ON itemtypes.itemtype = localization.code
+            AND localization.entity = 'itemtypes'
+            AND localization.lang = ?
+        WHERE itemtypes.itemtype = ?
+    |, undef, $language, $itemtype );
 
-    $res->{imageurl} = getitemtypeimagelocation( ( ( defined $interface && $interface eq 'opac' ) ? 'opac' : 'intranet' ), $res->{imageurl} );
+    $it->{imageurl} = getitemtypeimagelocation( ( ( defined $interface && $interface eq 'opac' ) ? 'opac' : 'intranet' ), $it->{imageurl} );
 
-    return $res;
+    return $it;
 }
 
 =head2 getitemtypeimagedir
index c9d2529..31e2484 100644 (file)
@@ -1873,15 +1873,9 @@ sub searchResults {
     #Build itemtype hash
     #find itemtype & itemtype image
     my %itemtypes;
-    $bsth =
-      $dbh->prepare(
-        "SELECT itemtype,description,imageurl,summary,notforloan FROM itemtypes"
-      );
-    $bsth->execute();
-    while ( my $bdata = $bsth->fetchrow_hashref ) {
-               foreach (qw(description imageurl summary notforloan)) {
-               $itemtypes{ $bdata->{'itemtype'} }->{$_} = $bdata->{$_};
-               }
+    my $itemtypes = GetItemTypes( style => 'array' );
+    for my $itemtype ( @$itemtypes ) {
+        $itemtypes{ $itemtype->{itemtype} } = $itemtype;
     }
 
     #search item field code
diff --git a/Koha/Localization.pm b/Koha/Localization.pm
new file mode 100644 (file)
index 0000000..013db3a
--- /dev/null
@@ -0,0 +1,28 @@
+package Koha::Localization;
+
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+use Modern::Perl;
+
+use Koha::Database;
+
+use base qw(Koha::Object);
+
+sub type {
+    return 'Localization';
+}
+
+1;
diff --git a/Koha/Localizations.pm b/Koha/Localizations.pm
new file mode 100644 (file)
index 0000000..bcbd8c2
--- /dev/null
@@ -0,0 +1,34 @@
+package Koha::Localizations;
+
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+use Modern::Perl;
+
+use Koha::Database;
+
+use Koha::Localization;
+
+use base qw(Koha::Objects);
+
+sub type {
+    return 'Localization';
+}
+
+sub object_class {
+    return 'Koha::Localization';
+}
+
+1;
index 91e8c4e..9c8326d 100644 (file)
@@ -27,11 +27,8 @@ use C4::Koha;
 sub GetDescription {
     my ( $self, $itemtype ) = @_;
 
-    my $query = "SELECT description FROM itemtypes WHERE itemtype = ?";
-    my $sth   = C4::Context->dbh->prepare($query);
-    $sth->execute($itemtype);
-    my $d = $sth->fetchrow_hashref();
-    return $d->{description}
+    my $itemtype = C4::Koha::getitemtypeinfo( $itemtype );
+    return $itemtype->{translated_description};
 
 }
 
index 61e59fe..2af82a3 100755 (executable)
@@ -52,18 +52,7 @@ use C4::Context;
 use C4::Auth;
 use C4::Output;
 
-sub StringSearch {
-    my ( $searchstring, $type ) = @_;
-    my $dbh = C4::Context->dbh;
-    $searchstring =~ s/\'/\\\'/g;
-    my @data = split( ' ', $searchstring );
-    my $sth = $dbh->prepare(
-        "SELECT * FROM itemtypes WHERE (description LIKE ?) ORDER BY itemtype"
-       );
-    $sth->execute("$data[0]%");
-    return $sth->fetchall_arrayref({});                # return ref-to-array of ref-to-hashes
-                                                               # like [ fetchrow_hashref(), fetchrow_hashref() ... ]
-}
+use Koha::Localizations;
 
 my $input       = new CGI;
 my $searchfield = $input->param('description');
@@ -99,8 +88,26 @@ if ( $op eq 'add_form' ) {
     #---- if primkey exists, it's a modify action, so read values to modify...
     my $data;
     if ($itemtype) {
-        my $sth = $dbh->prepare("select * from itemtypes where itemtype=?");
-        $sth->execute($itemtype);
+        my $sth = $dbh->prepare(q|
+            SELECT
+                   itemtypes.itemtype,
+                   itemtypes.description,
+                   itemtypes.rentalcharge,
+                   itemtypes.notforloan,
+                   itemtypes.imageurl,
+                   itemtypes.summary,
+                   itemtypes.checkinmsg,
+                   itemtypes.checkinmsgtype,
+                   itemtypes.sip_media_type,
+                   COALESCE( localization.translation, itemtypes.description ) AS translated_description
+            FROM   itemtypes
+            LEFT JOIN localization ON itemtypes.itemtype = localization.code
+                AND localization.entity='itemtypes'
+                AND localization.lang = ?
+            WHERE itemtype = ?
+        |);
+        my $language = C4::Languages::getlanguage();
+        $sth->execute($language, $itemtype);
         $data = $sth->fetchrow_hashref;
     }
 
@@ -259,11 +266,24 @@ elsif ( $op eq 'delete_confirmed' ) {
 }
 
 if ( $op eq 'list' ) {
-    my ($results) = StringSearch( $searchfield, 'web' );
+    my $results = C4::Koha::GetItemTypes( style => 'array' );
     my @loop;
     foreach my $itemtype ( @{$results} ) {
         $itemtype->{imageurl} = getitemtypeimagelocation( 'intranet', $itemtype->{imageurl} );
         $itemtype->{rentalcharge} = sprintf( '%.2f', $itemtype->{rentalcharge} );
+
+        my @translated_descriptions = Koha::Localizations->search(
+            {   entity => 'itemtypes',
+                code   => $itemtype->{itemtype},
+            }
+        );
+        $itemtype->{translated_descriptions} = [ map {
+            {
+                lang => $_->lang,
+                translation => $_->translation,
+            }
+        } @translated_descriptions ];
+
         push( @loop, $itemtype );
     }
 
diff --git a/admin/localization.pl b/admin/localization.pl
new file mode 100755 (executable)
index 0000000..061b979
--- /dev/null
@@ -0,0 +1,64 @@
+#!/usr/bin/perl
+
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# Koha is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Koha; if not, see <http://www.gnu.org/licenses>.
+
+
+use Modern::Perl;
+
+use C4::Auth;
+use C4::Output;
+
+use Koha::Localization;
+use Koha::Localizations;
+
+use CGI qw( -utf8 );
+
+my $query = new CGI;
+
+my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
+    {   template_name   => "admin/localization.tt",
+        authnotrequired => 0,
+        flagsrequired   => { parameters => 'parameters_remaining_permissions' },
+        query           => $query,
+        type            => "intranet",
+        debug           => 1,
+    }
+);
+
+my $entity = $query->param('entity');
+my $code   = $query->param('code');
+my $rs     = Koha::Localizations->search( { entity => $entity, code => $code } );
+my @translations;
+while ( my $s = $rs->next ) {
+    push @translations,
+      { id          => $s->localization_id,
+        entity      => $s->entity,
+        code        => $s->code,
+        lang        => $s->lang,
+        translation => $s->translation,
+      };
+}
+
+my $translated_languages = C4::Languages::getTranslatedLanguages( 'intranet', C4::Context->preference('template') );
+
+$template->param(
+    translations => \@translations,
+    languages    => $translated_languages,
+    entity       => $entity,
+    code         => $code,
+);
+
+output_html_with_http_headers $query, $cookie, $template->output;
index 16910c8..1753de6 100755 (executable)
@@ -83,23 +83,17 @@ sub build_authorized_values_list {
             push @authorised_values, $branchcode;
             $authorised_lib{$branchcode} = $branchname;
         }
-
-        #----- itemtypes
     }
     elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
-        my $sth =
-        $dbh->prepare(
-            "select itemtype,description from itemtypes order by description");
-        $sth->execute;
         push @authorised_values, ""
           unless ( $tagslib->{$tag}->{$subfield}->{mandatory}
             && ( $value || $tagslib->{$tag}->{$subfield}->{defaultvalue} ) );
-        
+
         my $itemtype;
-        
-        while ( my ( $itemtype, $description ) = $sth->fetchrow_array ) {
-            push @authorised_values, $itemtype;
-            $authorised_lib{$itemtype} = $description;
+        my $itemtypes = GetItemTypes( style => 'array' );
+        for my $itemtype ( @$itemtypes ) {
+            push @authorised_values, $itemtype->{itemtype};
+            $authorised_lib{$itemtype->{itemtype}} = $itemtype->{translated_description};
         }
         $value = $itemtype unless ($value);
 
index 43d7f29..0bd2cc6 100755 (executable)
@@ -266,7 +266,7 @@ foreach my $advanced_srch_type (@advanced_search_types) {
            my %row =(  number=>$cnt++,
                ccl => "$itype_or_itemtype,phr",
                 code => $thisitemtype,
-                description => $itemtypes->{$thisitemtype}->{'description'},
+                description => $itemtypes->{$thisitemtype}->{translated_description},
                 imageurl=> getitemtypeimagelocation( 'intranet', $itemtypes->{$thisitemtype}->{'imageurl'} ),
             );
            push @itypesloop, \%row;
index 8cf3b6a..b4784ed 100755 (executable)
@@ -184,26 +184,19 @@ sub build_authorized_values_list {
             $authorised_lib{$thisbranch} = $branches->{$thisbranch}->{'branchname'};
         }
 
-        #----- itemtypes
     }
     elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
-        my $sth =
-          $dbh->prepare(
-            "select itemtype,description from itemtypes order by description");
-        $sth->execute;
         push @authorised_values, ""
           unless ( $tagslib->{$tag}->{$subfield}->{mandatory}
             && ( $value || $tagslib->{$tag}->{$subfield}->{defaultvalue} ) );
-          
+
         my $itemtype;
-        
-        while ( my ( $itemtype, $description ) = $sth->fetchrow_array ) {
-            push @authorised_values, $itemtype;
-            $authorised_lib{$itemtype} = $description;
+        my $itemtypes = GetItemTypes( style => 'array' );
+        for my $itemtype ( @$itemtypes ) {
+            push @authorised_values, $itemtype->{itemtype};
+            $authorised_lib{$itemtype->{itemtype}} = $itemtype->{translated_description};
         }
         $value = $itemtype unless ($value);
-
-          #---- class_sources
     }
     elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
         push @authorised_values, ""
index 19279b3..b74605a 100755 (executable)
@@ -177,13 +177,12 @@ sub generate_subfield_form {
             }
             elsif ( $subfieldlib->{authorised_value} eq "itemtypes" ) {
                   push @authorised_values, "" unless ( $subfieldlib->{mandatory} );
-                  my $sth = $dbh->prepare("SELECT itemtype,description FROM itemtypes ORDER BY description");
-                  $sth->execute;
-                  while ( my ( $itemtype, $description ) = $sth->fetchrow_array ) {
-                      push @authorised_values, $itemtype;
-                      $authorised_lib{$itemtype} = $description;
+                  my $itemtypes = GetItemTypes( style => 'array' );
+                  for my $itemtype ( @$itemtypes ) {
+                      push @authorised_values, $itemtype->{itemtype};
+                      $authorised_lib{$itemtype->{itemtype}} = $itemtype->{translated_description};
                   }
-        
+
                   unless ( $value ) {
                       my $itype_sth = $dbh->prepare("SELECT itemtype FROM biblioitems WHERE biblionumber = ?");
                       $itype_sth->execute( $biblionumber );
diff --git a/installer/data/mysql/atomicupdate/Bug_14100-add_table_localization.sql b/installer/data/mysql/atomicupdate/Bug_14100-add_table_localization.sql
new file mode 100644 (file)
index 0000000..83bdb9d
--- /dev/null
@@ -0,0 +1,8 @@
+CREATE TABLE `localization` (
+      localization_id int(11) NOT NULL AUTO_INCREMENT,
+      entity varchar(16) COLLATE utf8_unicode_ci NOT NULL,
+      code varchar(64) COLLATE utf8_unicode_ci NOT NULL,
+      lang varchar(25) COLLATE utf8_unicode_ci NOT NULL,
+      translation text COLLATE utf8_unicode_ci,
+      PRIMARY KEY (localization_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
index 82a079e..8ad2205 100644 (file)
@@ -3563,6 +3563,20 @@ CREATE TABLE `additional_field_values` (
   CONSTRAINT `afv_fk` FOREIGN KEY (`field_id`) REFERENCES `additional_fields` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
 
+--
+-- Table structure for table 'localization'
+--
+
+DROP TABLE IF EXISTS localization;
+CREATE TABLE `localization` (
+      localization_id int(11) NOT NULL AUTO_INCREMENT,
+      entity varchar(16) COLLATE utf8_unicode_ci NOT NULL,
+      code varchar(64) COLLATE utf8_unicode_ci NOT NULL,
+      lang varchar(25) COLLATE utf8_unicode_ci NOT NULL, --could be a foreign key
+      translation text COLLATE utf8_unicode_ci,
+      PRIMARY KEY (localization_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+
 /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
 /*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
 /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
index 721da70..11d5cff 100644 (file)
@@ -20,6 +20,7 @@ Data deleted
 </title>
 [% INCLUDE 'doc-head-close.inc' %]
 <link rel="stylesheet" type="text/css" href="[% themelang %]/css/datatables.css" />
+[% INCLUDE 'greybox.inc' %]
 [% INCLUDE 'datatables.inc' %]
 <script type="text/javascript">
 //<![CDATA[
@@ -124,7 +125,9 @@ Item types administration
       </li>
   [% END %]
       <li>
-          <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>
+          <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>
+          <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>
+      </li>
       <li>
           <span class="label">Search category</span>
           <select id="searchcategory" name="searchcategory">
@@ -143,7 +146,6 @@ Item types administration
           </select>
           (Options are defined as the authorized values for the ITEMTYPECAT category)
       </li>
-
      [% IF ( noItemTypeImages ) %]
         <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>
         [% ELSE %]</ol>
@@ -281,7 +283,7 @@ Item types administration
                        <td>[% itemtype %]</td>
                </tr>
 
-       <tr><th scope="row">Description</th><td>[% description %]</td></tr>
+       <tr><th scope="row">Description</th><td>[% translated_description %]</td></tr>
        <tr><th scope="row">Loan length</th><td>[% loanlength %]</td></tr>
 <tr><th scope="row">Rental charge</th><td>[% rentalcharge %]</td></tr></table>
                <form action="[% script_name %]" method="post">
@@ -319,7 +321,21 @@ Item types administration
         [% loo.itemtype %]
       </a>
     </td>
-    <td>[% loo.description %]</td>
+    <td>
+        [% IF loo.translated_descriptions.size %]
+            [% loo.description %] (default)<br/>
+            [% FOR description IN loo.translated_descriptions %]
+                [% IF description.translation == loo.translated_description %]
+                    <b>[% description.translation %]</b>
+                [% ELSE %]
+                    [% description.translation %] ([% description.lang %])
+                [% END %]
+                <br/>
+            [% END %]
+        [% ELSE %]
+            [% loo.description %]
+        [% END %]
+    </td>
     <td>[% loo.searchcategory %]</td>
     <td>[% IF ( loo.notforloan ) %]Yes[% ELSE %]&nbsp;[% END %]</td>
     <td>[% IF ( loo.hideinopac ) %]Yes[% ELSE %]&nbsp;[% END %]</td>
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/localization.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/localization.tt
new file mode 100644 (file)
index 0000000..ab6ca72
--- /dev/null
@@ -0,0 +1,212 @@
+[% INCLUDE 'doc-head-open.inc' %]
+<title>Koha &rsaquo; Localization</title>
+[% INCLUDE 'doc-head-close.inc' %]
+<link rel="stylesheet" type="text/css" href="[% themelang %]/css/datatables.css" />
+[% INCLUDE 'greybox.inc' %]
+[% INCLUDE 'datatables.inc' %]
+<script type="text/javascript">
+//<![CDATA[
+
+    function show_message( params ) {
+        var type = params.type;
+        var data = params.data;
+        var messages = $("#messages");
+        var message;
+        if ( type == 'success_on_update' ) {
+            message = $('<div class="dialog message"></div>');
+            message.text("Entity %s (code %s) for lang %s has correctly been updated with '%s'".format(data.entity, data.code, data.lang, data.translation));
+        } else if ( type == 'error_on_update' ) {
+            message = $('<div class="dialog alert"></div>');
+            message.text("An error occurred when updating this translation");
+        } else if ( type == 'success_on_delete' ) {
+            message = $('<div class="dialog message"></div>');
+            message.text("The translation (id %s) has been removed successfully".format(data.id));
+        } else if ( type == 'error_on_delete' ) {
+            message = $('<div class="dialog alert"></div>');
+            message.text("An error occurred when deleting this translation");
+        } else if ( type == 'success_on_insert' ) {
+            message = $('<div class="dialog message"></div>');
+            message.text("Translation (id %s) has been added successfully".format(data.id));
+        } else if ( type == 'error_on_insert' ) {
+            message = $('<div class="dialog alert"></div>');
+            message.text("An error occurred when adding this translation");
+        }
+
+        $(messages).append(message);
+
+        setTimeout(function(){
+            message.hide()
+        }, 3000);
+    }
+
+    function send_update_request( data, cell ) {
+        $.ajax({
+            data: data,
+            type: 'PUT',
+            url: '/cgi-bin/koha/svc/localization',
+            success: function (data) {
+                if ( data.is_changed ) {
+                    $(cell).css('background-color', '#00FF00');
+                    show_message({ type: 'success_on_update', data: data });
+                }
+                if ( $(cell).hasClass('lang') ) {
+                    $(cell).text(data.lang)
+                } else if ( $(cell).hasClass('translation') ) {
+                    $(cell).text(data.translation)
+                }
+            },
+            error: function (data) {
+                $(cell).css('background-color', '#FF0000');
+                if ( $(cell).hasClass('lang') ) {
+                    $(cell).text(data.lang)
+                } else if ( $(cell).hasClass('translation') ) {
+                    $(cell).text(data.translation)
+                }
+                show_message({ type: 'error_on_update', data: data });
+            },
+        });
+    }
+
+    function send_delete_request( id, cell ) {
+        $.ajax({
+            type: 'DELETE',
+            url: '/cgi-bin/koha/svc/localization/?id='+id,
+            success: function (data) {
+                $("#localization").DataTable().row( '#row_id_' + id ).remove().draw();
+                show_message({ type: 'success_on_delete', data: data });
+            },
+            error: function (data) {
+                $(cell).css('background-color', '#FF0000');
+                show_message({ type: 'error_on_delete', data: data });
+            },
+        });
+    }
+
+    $(document).ready(function() {
+        $(".dialog").hide();
+
+        var table = $("#localization").DataTable($.extend(true, {}, dataTablesDefaults, {
+            'bPaginate': false,
+        }));
+
+        var languages_select = $('<select name="lang"></select>');
+        [% FOR language IN languages %]
+            var option = $('<option value="[% language.lang %]">[% language.description %]</option>');
+            $(languages_select).append(option);
+        [% END %]
+
+        $("td.translation").on('focus', function(){
+            $(this).css('background-color', '');
+        });
+        $("td.lang").on('click', function(){
+            var td = this;
+            var lang = $(td).text();
+            $(td).css('background-color', '');
+            var my_select = $(languages_select).clone();
+            $(my_select).find('option[value="' + lang + '"]').attr('selected', 'selected');
+            $(my_select).on('click', function(e){
+                e.stopPropagation();
+            });
+            $(my_select).on('change', function(){
+                var tr = $(this).parent().parent();
+                var id = $(tr).data('id');
+                var lang = $(this).find('option:selected').val();
+                var data = "id=" + encodeURIComponent(id) + "&lang=" + encodeURIComponent(lang);
+                send_update_request( data, td );
+            });
+            $(my_select).on('blur', function(){
+                $(td).html(lang);
+            });
+            $(this).html(my_select);
+        });
+
+        $("td.translation").on('blur', function(){
+            var tr = $(this).parent();
+            var id = $(tr).data('id');
+            var translation = $(this).text();
+            var data = "id=" + encodeURIComponent(id) + "&translation=" + encodeURIComponent(translation);
+            send_update_request( data, this );
+        });
+
+        $("a.delete").on('click', function(){
+            if ( confirm(_("Are you sure you want to delete this translation?")) ) {
+                var td = $(this).parent();
+                var tr = $(td).parent();
+                var id = $(tr).data('id');
+                send_delete_request( id, td );
+            }
+        });
+
+        $("#add_translation").on('submit', function(e){
+            e.preventDefault();
+            var entity = $(this).find('input[name="entity"]').val();
+            var code = $(this).find('input[name="code"]').val();
+            var lang = $(this).find('select[name="lang"] option:selected').val();
+            var translation = $(this).find('input[name="translation"]').val();
+            var data = "entity=" + encodeURIComponent(entity) + "&code=" + encodeURIComponent(code) + "&lang=" + encodeURIComponent(lang) + "&translation=" + encodeURIComponent(translation);
+            $.ajax({
+                data: data,
+                type: 'POST',
+                url: '/cgi-bin/koha/svc/localization',
+                success: function (data) {
+                    // FIXME Should append the delete link
+                    table.row.add( [ data.id, data.entity, data.code, data.lang, data.translation, "" ] ).draw();
+                    show_message({ type: 'success_on_insert', data: data });
+                },
+                error: function (data) {
+                    show_message({ type: 'error_on_insert', data: data });
+                },
+            });
+        });
+
+     });
+//]]>
+</script>
+</head>
+<body id="admin_localization" class="admin">
+<div id="main">
+<table id="localization">
+    <thead>
+        <tr>
+            <th>Id</th>
+            <th>Entity</th>
+            <th>Code</th>
+            <th>Lang</th>
+            <th>Translation</th>
+            <th></th>
+        </tr>
+    </thead>
+    <tbody>
+        [% FOR t IN translations %]
+        <tr id="row_id_[% t.id %]" data-id="[% t.id %]">
+            <td>[% t.id %]</td>
+            <td>[% t.entity %]</td>
+            <td>[% t.code %]</td>
+            <td class="lang">[% t.lang %]</td>
+            <td class="translation" contenteditable="true">[% t.translation %]</td>
+            <td><a class="delete" title="Delete this translation"><i class="icon-remove"></i></a</td>
+        </tr>
+        [% END %]
+    </tbody>
+</table>
+<form id="add_translation" method="post">
+    <input type="hidden" name="entity" value="[% entity %]" />
+    <input type="hidden" name="code" value="[% code %]" />
+    Lang: <select name="lang">
+        [% FOR language IN languages %]
+            [% FOR sublanguage IN language.sublanguages_loop %]
+                [% IF language.plural %]
+                    <option value="[% sublanguage.rfc4646_subtag %]">[% sublanguage.native_description %] [% sublanguage.region_description %] ([% sublanguage.rfc4646_subtag %])</option>
+                [% ELSE %]
+                    <option value="[% sublanguage.rfc4646_subtag %]">[% sublanguage.native_description %] ([% sublanguage.rfc4646_subtag %])</option>
+                [% END %]
+            [% END %]
+        [% END %]
+    </select>
+    Translation: <input type="text" name="translation" />
+    <input type="submit" value="Add" />
+</form>
+<div id="messages"></div>
+</div>
+</body>
+</html>
index cc703ae..6ecfd09 100755 (executable)
@@ -83,9 +83,7 @@ my $sql = '
         branchname,
 
         items.itype,
-        itemtype_item.description AS itype_description,
         biblioitems.itemtype,
-        itemtype_bib.description AS itemtype_description,
 
         borrowernumber,
         surname,
@@ -103,8 +101,6 @@ my $sql = '
         LEFT JOIN biblioitems USING ( biblionumber )
         LEFT JOIN borrowers USING ( borrowernumber )
         LEFT JOIN branches ON ( issues.branchcode = branches.branchcode )
-        LEFT JOIN itemtypes itemtype_bib ON ( biblioitems.itemtype = itemtype_bib.itemtype )
-        LEFT JOIN itemtypes itemtype_item ON ( items.itype = itemtype_item.itemtype )
     WHERE borrowernumber
 ';
 
@@ -145,13 +141,14 @@ while ( my $c = $sth->fetchrow_hashref() ) {
     my ( $renewals_count, $renewals_allowed, $renewals_remaining ) =
       GetRenewCount( $c->{borrowernumber}, $c->{itemnumber} );
 
+    my $itemtype = C4::Koha::getitemtypeinfo( $item_level_itypes ? $c->{itype} : $c->{itemtype} );
     my $checkout = {
         DT_RowId   => $c->{itemnumber} . '-' . $c->{borrowernumber},
         title      => $c->{title},
         author     => $c->{author},
         barcode    => $c->{barcode},
         itemtype   => $item_level_itypes ? $c->{itype} : $c->{itemtype},
-        itemtype_description => $item_level_itypes ? $c->{itype_description} : $c->{itemtype_description},
+        itemtype_description => $itemtype->{translated_description},
         location   => $c->{location} ? GetAuthorisedValueByCode( 'LOC', $c->{location} ) : q{},
         itemnotes  => $c->{itemnotes},
         branchcode => $c->{branchcode},
diff --git a/svc/localization b/svc/localization
new file mode 100755 (executable)
index 0000000..ebec0e2
--- /dev/null
@@ -0,0 +1,92 @@
+#!/usr/bin/perl
+
+use Modern::Perl;
+
+use C4::Service;
+use Koha::Localizations;
+
+our ( $query, $response ) = C4::Service->init( parameters => 'parameters_remaining_permissions' );
+
+sub get_translations {
+    my $rs = Koha::Localizations->search({ type => $query->param('type'), code => $query->param('code') });
+    my @translations;
+    while ( my $s = $rs->next ) {
+        push @translations, {
+            id => $s->localization_id,
+            type => $s->type,
+            code => $s->code,
+            lang => $s->lang,
+            translation => $s->translation,
+        }
+    }
+    $response->param( translations => \@translations );
+    C4::Service->return_success( $response );
+}
+
+sub update_translation {
+    my $id = $query->param('id');
+    my $translation = $query->param('translation');
+    my $lang = $query->param('lang');
+    my $localization = Koha::Localizations->find( $id );
+
+    if ( defined $lang and $localization->lang ne $lang ) {
+        $localization->lang( $lang )
+    }
+    if ( defined $translation and $localization->translation ne $translation ) {
+        $localization->translation( $translation )
+    }
+    my $is_changed;
+    if ( $localization->is_changed ) {
+        $is_changed = 1;
+        $localization->store;
+    }
+    $response->param(
+        id          => $localization->localization_id,
+        entity      => $localization->entity,
+        code        => $localization->code,
+        lang        => $localization->lang,
+        translation => $localization->translation,
+        is_changed  => $is_changed,
+    );
+    C4::Service->return_success( $response );
+}
+
+sub add_translation {
+    my $entity = $query->param('entity');
+    my $code = $query->param('code');
+    my $lang = $query->param('lang');
+    my $translation = $query->param('translation');
+    my $localization = Koha::Localization->new(
+        {
+            entity => $entity,
+            code => $code,
+            lang => $lang,
+            translation => $translation,
+        }
+    );
+    $localization->store;
+    $localization->localization_id;
+    $response->param(
+        id          => $localization->localization_id,
+        entity      => $localization->entity,
+        code        => $localization->code,
+        lang        => $localization->lang,
+        translation => $localization->translation,
+    );
+
+    C4::Service->return_success( $response );
+}
+
+sub delete_translation {
+    my $id = $query->param('id');
+    Koha::Localizations->find($id)->delete;
+    $response->param( id => $id );
+    C4::Service->return_success( $response );
+}
+
+C4::Service->dispatch(
+    [ 'GET /', [ 'id' ], \&get_translations ],
+    [ 'PUT /', [ 'id' ], \&update_translation ],
+    [ 'POST /', [ 'entity', 'code', 'lang', 'translation' ], \&add_translation ],
+    [ 'DELETE /', ['id'],  \&delete_translation ],
+);
index 5804936..5674717 100755 (executable)
@@ -355,14 +355,13 @@ foreach my $tag (sort keys %{$tagslib}) {
            }
         $value = "";
        }
-       elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
-           push @authorised_values, "";
-           my $sth = $dbh->prepare("select itemtype,description from itemtypes order by description");
-           $sth->execute;
-           while ( my ( $itemtype, $description ) = $sth->fetchrow_array ) {
-               push @authorised_values, $itemtype;
-               $authorised_lib{$itemtype} = $description;
-           }
+    elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
+        push @authorised_values, "";
+        my $itemtypes = GetItemTypes( style => 'array' );
+        for my $itemtype ( @$itemtypes ) {
+            push @authorised_values, $itemtype->{itemtype};
+            $authorised_lib{$itemtype->{itemtype}} = $itemtype->{translated_description};
+        }
         $value = "";
 
           #---- class_sources