Bugzilla – Attachment 175078 Details for
Bug 38136
Refactor database translations (alternative)
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 38136: Refactor database translations (alternative)
Bug-38136-Refactor-database-translations-alternati.patch (text/plain), 63.17 KB, created by
Julian Maurice
on 2024-12-03 14:18:47 UTC
(
hide
)
Description:
Bug 38136: Refactor database translations (alternative)
Filename:
MIME Type:
Creator:
Julian Maurice
Created:
2024-12-03 14:18:47 UTC
Size:
63.17 KB
patch
obsolete
>From b958c9489d7253551bad245f8d5ceecec335b40c Mon Sep 17 00:00:00 2001 >From: Julian Maurice <julian.maurice@biblibre.com> >Date: Wed, 9 Oct 2024 22:03:16 +0200 >Subject: [PATCH] Bug 38136: Refactor database translations (alternative) > >This has the same goals of bug 24975 but with a different approach. > >Bug 24975 tried the "POT/PO files as database tables" approach but >it appeared that is not what best fits our needs. > >Instead of a "source text / translated text" mapping, we need >translations to be attached to a specific "object" (or table row), which >has the added benefit of keeping translations unaffected when the >original string changes. It may or may not be what you want but at least >this avoids the problem where fixing an error in the original string >forces users to re-translate everything. > >We also need to be able to translate all translatable properties of a >particular entity. > >The solution proposed here is to add a DBIC component that will add >several DBIC relations to the `localization` table: >* a relation named `localizations` that will return all translations for > all translatable properties for all languages. >* for each translatable property: > * a relation named `{$property}_localizations` that will return all > translations for a given property, for all languages > * a relation named `{$property}_localization` that will return the > translation for a given property, for the current language > >To enable translations for a particular entity, we will need to: >* add two method calls in the "result class" corresponding to the > localizable object type >* in the admin interface, add a link to the the translation popup. > Unlike bug 24975 there is not a page where you can translate > everything. I don't know if it is really useful (probably better to > translate itemtypes when you are on the itemtype page). It can be > added later if needed. > >To give an example, this is what needs to be added manually in >Koha/Schema/Result/Itemtype.pm: > > __PACKAGE__->load_components('+Koha::DBIx::Class::Localization'); > __PACKAGE__->localization_add_relationships( > 'itemtype', # the PK column > 'description' # a list of translatable columns > ); > >(see POD in Koha/Schema/Component/Localization.pm for more information) > >Final notes: >* I wanted this patch to have as few changes as possible so I kept > methods like `search_with_localization`, `children_with_localization` > even if I believe they can be removed (replaced by search/children) > Localizations do not need to be prefetched/joined unless you want to > sort on them. > For the same reason, $itemtype->unblessed now always return a > `translated_description` key. > This can be fixed in followup patches >* There is cache involved (see > Koha::Schema::Component::Localization::localization). > Localizations in cache are grouped by object type and language (keys > look like 'KOHA:localization:Itemtype:en') >* By keeping a single table for all translations of all entities, we > cannot have data integrity at the RDBMS level, but automatic deletion > of translations is handled by the DBIC `cascade_delete` feature, so > integrity should be fine as long as no raw SQL update/delete operation > is involved. >--- > C4/Biblio.pm | 19 +- > Koha/ItemType.pm | 91 ++------ > Koha/ItemTypes.pm | 34 ++- > Koha/Localization.pm | 73 ------ > Koha/Localizations.pm | 34 --- > Koha/Object.pm | 27 +++ > Koha/Schema/Component/Localization.pm | 214 ++++++++++++++++++ > Koha/Schema/Result/Itemtype.pm | 22 +- > Koha/Schema/Result/ItemtypeLocalization.pm | 32 --- > Koha/Schema/Result/ItemtypesLocalization.pm | 124 ++++++++++ > Koha/Schema/Result/Localization.pm | 108 --------- > Koha/UI/Form/Builder/Item.pm | 9 +- > admin/itemtypes.pl | 1 - > admin/localization.pl | 34 +-- > .../data/mysql/atomicupdate/bug-38136.pl | 22 ++ > installer/data/mysql/kohastructure.sql | 3 +- > .../prog/en/includes/localization-link.inc | 1 + > .../prog/en/modules/admin/itemtypes.tt | 2 +- > .../prog/en/modules/admin/localization.tt | 79 ++++--- > .../prog/js/fetch/localization-api-client.js | 21 +- > svc/localization | 143 ++++++------ > .../Koha/Filter/ExpandCodedFields.t | 3 +- > t/db_dependent/Koha/Item.t | 12 +- > t/db_dependent/Koha/ItemTypes.t | 24 +- > .../Koha/Template/Plugin/ItemTypes.t | 17 +- > t/db_dependent/api/v1/item_types.t | 27 +-- > 26 files changed, 628 insertions(+), 548 deletions(-) > delete mode 100644 Koha/Localization.pm > delete mode 100644 Koha/Localizations.pm > create mode 100644 Koha/Schema/Component/Localization.pm > delete mode 100644 Koha/Schema/Result/ItemtypeLocalization.pm > create mode 100644 Koha/Schema/Result/ItemtypesLocalization.pm > delete mode 100644 Koha/Schema/Result/Localization.pm > create mode 100644 installer/data/mysql/atomicupdate/bug-38136.pl > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/includes/localization-link.inc > >diff --git a/C4/Biblio.pm b/C4/Biblio.pm >index 6c62b21311..f9bcb1f0d5 100644 >--- a/C4/Biblio.pm >+++ b/C4/Biblio.pm >@@ -99,6 +99,7 @@ use C4::Linker; > use C4::OAI::Sets; > > use Koha::Logger; >+use Koha::Cache::Memory::Lite; > use Koha::Caches; > use Koha::ClassSources; > use Koha::Authority::Types; >@@ -1473,17 +1474,15 @@ sub GetAuthorisedValueDesc { > > #---- itemtypes > if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "itemtypes" ) { >- my $lang = C4::Languages::getlanguage; >- $lang //= 'en'; >- $cache_key = 'itemtype:description:' . $lang; >- my $itypes = $cache->get_from_cache( $cache_key, { unsafe => 1 } ); >- if ( !$itypes ) { >- $itypes = >- { map { $_->itemtype => $_->translated_description } >- Koha::ItemTypes->search()->as_list }; >- $cache->set_in_cache( $cache_key, $itypes ); >+ my $memory_cache = Koha::Cache::Memory::Lite->get_instance(); >+ my $cache_key = 'GetAuthorisedValueDesc:itemtypes'; >+ my $itemtypes = $memory_cache->get_from_cache($cache_key); >+ unless ($itemtypes) { >+ $itemtypes = { map { $_->itemtype => $_ } Koha::ItemTypes->as_list }; >+ $memory_cache->set_in_cache($cache_key, $itemtypes); > } >- return $itypes->{$value}; >+ my $itemtype = $itemtypes->{$value}; >+ return $itemtype ? $itemtype->translated_description : undef; > } > > if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "cn_source" ) { >diff --git a/Koha/ItemType.pm b/Koha/ItemType.pm >index f4c0c4aee9..414013b1db 100644 >--- a/Koha/ItemType.pm >+++ b/Koha/ItemType.pm >@@ -19,15 +19,11 @@ use Modern::Perl; > > use C4::Koha qw( getitemtypeimagelocation ); > use C4::Languages; >-use Koha::Caches; > use Koha::Database; > use Koha::CirculationRules; >-use Koha::Localizations; > > use base qw(Koha::Object Koha::Object::Limit::Library); > >-my $cache = Koha::Caches->get_instance(); >- > =head1 NAME > > Koha::ItemType - Koha Item type Object class >@@ -36,48 +32,6 @@ Koha::ItemType - Koha Item type Object class > > =head2 Class methods > >-=cut >- >-=head3 store >- >-ItemType specific store to ensure relevant caches are flushed on change >- >-=cut >- >-sub store { >- my ($self) = @_; >- >- my $flush = 0; >- >- if ( !$self->in_storage ) { >- $flush = 1; >- } else { >- my $self_from_storage = $self->get_from_storage; >- $flush = 1 if ( $self_from_storage->description ne $self->description ); >- } >- >- $self = $self->SUPER::store; >- >- if ($flush) { >- my $key = "itemtype:description:en"; >- $cache->clear_from_cache($key); >- } >- >- return $self; >-} >- >-=head2 delete >- >-ItemType specific C<delete> to clear relevant caches on delete. >- >-=cut >- >-sub delete { >- my $self = shift @_; >- $cache->clear_from_cache('itemtype:description:en'); >- $self->SUPER::delete(@_); >-} >- > =head3 image_location > > =cut >@@ -93,26 +47,8 @@ sub image_location { > > sub translated_description { > my ( $self, $lang ) = @_; >- if ( my $translated_description = eval { $self->get_column('translated_description') } ) { >- >- # If the value has already been fetched (eg. from sarch_with_localization), >- # do not search for it again >- # Note: This is a bit hacky but should be fast >- return $translated_description >- ? $translated_description >- : $self->description; >- } >- $lang ||= C4::Languages::getlanguage; >- my $translated_description = Koha::Localizations->search( >- { >- code => $self->itemtype, >- entity => 'itemtypes', >- lang => $lang >- } >- )->next; >- return $translated_description >- ? $translated_description->translation >- : $self->description; >+ >+ my $localization = $self->localization('description', $lang || C4::Languages::getlanguage()); > } > > =head3 translated_descriptions >@@ -121,19 +57,14 @@ sub translated_description { > > sub translated_descriptions { > my ($self) = @_; >- my @translated_descriptions = Koha::Localizations->search( >- { >- entity => 'itemtypes', >- code => $self->itemtype, >- } >- )->as_list; >+ > return [ > map { > { > lang => $_->lang, > translation => $_->translation, > } >- } @translated_descriptions >+ } $self->_result->description_localizations > ]; > } > >@@ -235,9 +166,23 @@ sub to_api_mapping { > rentalcharge_hourly => 'hourly_rental_charge', > rentalcharge_hourly_calendar => 'hourly_rental_charge_calendar', > bookable_itemtype => 'bookable_itemtype', >+ >+ # TODO Remove after having updated all code using unblessed translated_description >+ translated_description => undef, > }; > } > >+# TODO Remove after having updated all code using unblessed translated_description >+sub unblessed { >+ my ($self) = @_; >+ >+ my $unblessed = $self->SUPER::unblessed(); >+ >+ $unblessed->{translated_description} = $self->translated_description; >+ >+ return $unblessed; >+} >+ > =head2 Internal methods > > =head3 _type >diff --git a/Koha/ItemTypes.pm b/Koha/ItemTypes.pm >index 13caa648a8..fc138a6478 100644 >--- a/Koha/ItemTypes.pm >+++ b/Koha/ItemTypes.pm >@@ -33,8 +33,6 @@ Koha::ItemTypes - Koha ItemType Object set class > > =head2 Class methods > >-=cut >- > =head3 search_with_localization > > my $itemtypes = Koha::ItemTypes->search_with_localization >@@ -44,22 +42,22 @@ my $itemtypes = Koha::ItemTypes->search_with_localization > sub search_with_localization { > my ( $self, $params, $attributes ) = @_; > >- my $language = C4::Languages::getlanguage(); >- $Koha::Schema::Result::Itemtype::LANGUAGE = $language; >- $attributes->{order_by} = 'translated_description' unless exists $attributes->{order_by}; >- $attributes->{join} = 'localization'; >- $attributes->{'+select'} = [ >- { >- coalesce => [qw( localization.translation me.description )], >- -as => 'translated_description' >- } >- ]; >- if(defined $params->{branchcode}) { >- my $branchcode = delete $params->{branchcode}; >- $self->search_with_library_limits( $params, $attributes, $branchcode ); >- } else { >- $self->SUPER::search( $params, $attributes ); >- } >+ return $self->search( $params, $attributes )->order_by_translated_description; >+} >+ >+=head3 order_by_translated_description >+ >+=cut >+ >+sub order_by_translated_description { >+ my ($self) = @_; >+ >+ my $attributes = { >+ join => 'description_localization', >+ order_by => \['COALESCE(description_localization.translation, me.description)'], >+ }; >+ >+ return $self->search( {}, $attributes ); > } > > =head2 Internal methods >diff --git a/Koha/Localization.pm b/Koha/Localization.pm >deleted file mode 100644 >index 90201b8870..0000000000 >--- a/Koha/Localization.pm >+++ /dev/null >@@ -1,73 +0,0 @@ >-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, see <http://www.gnu.org/licenses>. >- >-use Modern::Perl; >- >-use Koha::Database; >- >-use base qw(Koha::Object); >- >-my $cache = Koha::Caches->get_instance(); >- >-=head1 NAME >- >-Koha::Localization - Koha Localization type Object class >- >-=head1 API >- >-=head2 Class methods >- >-=cut >- >-=head3 store >- >-Localization specific store to ensure relevant caches are flushed on change >- >-=cut >- >-sub store { >- my ($self) = @_; >- $self = $self->SUPER::store; >- >- if ($self->entity eq 'itemtypes') { >- my $key = "itemtype:description:".$self->lang; >- $cache->clear_from_cache($key); >- } >- >- return $self; >-} >- >-=head2 delete >- >-Localization specific C<delete> to clear relevant caches on delete. >- >-=cut >- >-sub delete { >- my $self = shift @_; >- if ($self->entity eq 'itemtypes') { >- my $key = "itemtype:description:".$self->lang; >- $cache->clear_from_cache($key); >- } >- $self->SUPER::delete(@_); >-} >- >-sub _type { >- return 'Localization'; >-} >- >-1; >diff --git a/Koha/Localizations.pm b/Koha/Localizations.pm >deleted file mode 100644 >index 8720334acd..0000000000 >--- a/Koha/Localizations.pm >+++ /dev/null >@@ -1,34 +0,0 @@ >-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, see <http://www.gnu.org/licenses>. >- >-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; >diff --git a/Koha/Object.pm b/Koha/Object.pm >index 8e254623bd..19e97d0fdd 100644 >--- a/Koha/Object.pm >+++ b/Koha/Object.pm >@@ -956,6 +956,33 @@ sub unblessed_all_relateds { > return \%data; > } > >+=head3 localization >+ >+Returns a localized (translated) value of the property, or the original >+property value if no translation exist >+ >+ $localization = $object->localization($property, $lang); >+ >+C<$property> is the property name. Often this will correspond to an SQL column name >+ >+C<$lang> is the language code (for instance: 'en-GB') >+ >+=cut >+ >+sub localization { >+ my ($self, $property, $lang) = @_; >+ >+ my $result = $self->_result; >+ if ($result->can('localization')) { >+ if (my $localization = $result->localization($property, $lang)) { >+ return $localization; >+ } >+ } >+ >+ return $result->get_column($property); >+} >+ >+ > =head3 $object->_result(); > > Returns the internal DBIC Row object >diff --git a/Koha/Schema/Component/Localization.pm b/Koha/Schema/Component/Localization.pm >new file mode 100644 >index 0000000000..6abce2f862 >--- /dev/null >+++ b/Koha/Schema/Component/Localization.pm >@@ -0,0 +1,214 @@ >+package Koha::Schema::Component::Localization; >+ >+=head1 NAME >+ >+Koha::Schema::Component::Localization >+ >+=head1 SYNOPSIS >+ >+ package Koha::Schema::Result::SomeTable; >+ >+ # ... generated code ... >+ >+ __PACKAGE__->load_components('+Koha::Schema::Component::Localization'); >+ >+ __PACKAGE__->localization_add_relationships( >+ 'some_table_localizations', >+ 'some_table_id' => 'some_table_id', >+ 'first_column', >+ 'second_column', >+ # ... >+ ); >+ >+ package main; >+ >+ my $rows = $schema->resultset('SomeTable'); >+ my $row = $rows->first; >+ $row->localizations->search($cond); >+ $row->create_related('first_column_localizations', { lang => $lang, translation => $translation }) >+ >+ while (my $row = $rows->next) >+ # first call will fetch all localizations for the current language and >+ # the result will be cached, next calls will not execute a query >+ $row->localization('first_column', $lang); >+ >+ # no query executed >+ $row->localization('second_column', $lang); >+ } >+ >+=head1 DESCRIPTION >+ >+This is a DBIx::Class component that helps to manage database localizations by >+adding several relationships and methods to a "Result Class" >+ >+This can handle several localizable columns (also referred as "properties") per >+table >+ >+To add database localizations to an existing database table, you need to: >+ >+=over >+ >+=item * Create a new table with: >+ >+=over >+ >+=item * An auto incremented column as primary key >+ >+=item * A foreign key column referencing the existing table >+ >+=item * 3 string (varchar or text) columns named 'property', 'lang', >+'translation' >+ >+=item * A unique key comprising the foreign key column, 'property' and 'lang' >+ >+=back >+ >+=item * Regenerate the DBIx::Class schema with >+misc/devel/update_dbix_class_files.pl >+ >+=item * Add calls to load_components and localization_add_relationships at the >+end of the result class >+ >+=back >+ >+This will give you a relationship named 'localizations' through which you can >+access all localizations of a particular table row. >+ >+And for every property, you will have: >+ >+=over >+ >+=item * a "has_many" relationship named <property>_localizations, giving access >+to all localizations of a particular table row for this particular property >+ >+=item * a "might_have" relationship named <property>_localization, giving >+access to the localization of a particular table row for this particular >+property and for the current language (uses C4::Languages::getlanguage) >+ >+=back >+ >+The "row" object will also gain a method C<localization($property, $lang)> >+which returns a specific translation and uses cache to avoid executing lots of >+queries >+ >+=cut >+ >+use Modern::Perl; >+use Carp; >+ >+use base qw(DBIx::Class); >+ >+=head2 localization_add_relationships >+ >+Add relationships to the localization table >+ >+=cut >+ >+sub localization_add_relationships { >+ my ($class, $pk_column, @properties) = @_; >+ >+ my $rel_class = 'Koha::Schema::Result::Localization'; >+ my $source_name = $class =~ s/.*:://r; >+ >+ $class->has_many( >+ 'localizations', >+ $rel_class, >+ sub { >+ my ($args) = @_; >+ >+ return ( >+ { >+ "$args->{foreign_alias}.code" => { -ident => "$args->{self_alias}.$pk_column" }, >+ "$args->{foreign_alias}.entity" => $source_name, >+ }, >+ !$args->{self_result_object} ? () : { >+ "$args->{foreign_alias}.code" => $args->{self_result_object}->get_column($pk_column), >+ "$args->{foreign_alias}.entity" => $source_name, >+ }, >+ ); >+ }, >+ { cascade_copy => 0, cascade_delete => 1, cascade_update => 0 }, >+ ); >+ >+ foreach my $property (@properties) { >+ $class->might_have( >+ $property . '_localization', >+ $rel_class, >+ sub { >+ my ($args) = @_; >+ >+ # Not a 'use' because we don't want to load C4::Languages (and >+ # thus C4::Context) while loading the schema >+ require C4::Languages; >+ my $lang = C4::Languages::getlanguage(); >+ >+ return ( >+ { >+ "$args->{foreign_alias}.code" => { -ident => "$args->{self_alias}.$pk_column" }, >+ "$args->{foreign_alias}.entity" => $source_name, >+ "$args->{foreign_alias}.property" => $property, >+ "$args->{foreign_alias}.lang" => $lang, >+ }, >+ !$args->{self_result_object} ? () : { >+ "$args->{foreign_alias}.code" => $args->{self_result_object}->get_column($pk_column), >+ "$args->{foreign_alias}.entity" => $source_name, >+ "$args->{foreign_alias}.property" => $property, >+ "$args->{foreign_alias}.lang" => $lang, >+ }, >+ ); >+ }, >+ { cascade_copy => 0, cascade_delete => 0, cascade_update => 0 }, >+ ); >+ >+ $class->has_many( >+ $property . '_localizations', >+ $rel_class, >+ sub { >+ my ($args) = @_; >+ >+ return ( >+ { >+ "$args->{foreign_alias}.code" => { -ident => "$args->{self_alias}.$pk_column" }, >+ "$args->{foreign_alias}.entity" => $source_name, >+ "$args->{foreign_alias}.property" => $property, >+ }, >+ !$args->{self_result_object} ? () : { >+ "$args->{foreign_alias}.code" => $args->{self_result_object}->get_column($pk_column), >+ "$args->{foreign_alias}.entity" => $source_name, >+ "$args->{foreign_alias}.property" => $property, >+ }, >+ ); >+ }, >+ { cascade_copy => 0, cascade_delete => 0, cascade_update => 0 }, >+ ); >+ } >+} >+ >+sub localization { >+ my ($self, $property, $lang) = @_; >+ >+ my $result_source = $self->result_source; >+ >+ my $cache = Koha::Caches->get_instance('localization'); >+ my $cache_key = sprintf('%s:%s', $result_source->source_name, $lang); >+ my $localizations_map = $cache->get_from_cache($cache_key); >+ unless ($localizations_map) { >+ $localizations_map = {}; >+ >+ my $localizations = $result_source->schema->resultset('Localization')->search({ lang => $lang }); >+ while (my $localization = $localizations->next) { >+ my $fk = $localization->get_column('code'); >+ my $localization_key = sprintf('%s:%s', $fk, $localization->property); >+ $localizations_map->{$localization_key} = $localization->translation; >+ } >+ >+ $cache->set_in_cache($cache_key, $localizations_map); >+ } >+ >+ my ($pk) = $self->id; >+ my $localization_key = sprintf('%s:%s', $pk, $property); >+ >+ return $localizations_map->{$localization_key}; >+} >+ >+1; >diff --git a/Koha/Schema/Result/Itemtype.pm b/Koha/Schema/Result/Itemtype.pm >index 46aa24926d..a29f154722 100644 >--- a/Koha/Schema/Result/Itemtype.pm >+++ b/Koha/Schema/Result/Itemtype.pm >@@ -344,8 +344,8 @@ __PACKAGE__->has_many( > ); > > >-# Created by DBIx::Class::Schema::Loader v0.07051 @ 2024-10-25 13:25:14 >-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:jd0dYE700dpg1IiRnfbcEg >+# Created by DBIx::Class::Schema::Loader v0.07052 @ 2024-12-03 14:37:10 >+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:l3ycM1L5bmCeM0Pby8GceA > > __PACKAGE__->add_columns( > '+automatic_checkin' => { is_boolean => 1 }, >@@ -356,22 +356,8 @@ __PACKAGE__->add_columns( > '+bookable' => { is_boolean => 1, is_nullable => 1 }, > ); > >-# Use the ItemtypeLocalization view to create the join on localization >-our $LANGUAGE; >-__PACKAGE__->has_many( >- "localization" => "Koha::Schema::Result::ItemtypeLocalization", >- sub { >- my $args = shift; >- >- die "no lang specified!" unless $LANGUAGE; >- >- return ({ >- "$args->{self_alias}.itemtype" => { -ident => "$args->{foreign_alias}.code" }, >- "$args->{foreign_alias}.lang" => $LANGUAGE, >- }); >- >- } >-); >+__PACKAGE__->load_components('+Koha::Schema::Component::Localization'); >+__PACKAGE__->localization_add_relationships('itemtype', 'description'); > > sub koha_object_class { > 'Koha::ItemType'; >diff --git a/Koha/Schema/Result/ItemtypeLocalization.pm b/Koha/Schema/Result/ItemtypeLocalization.pm >deleted file mode 100644 >index d5966128c3..0000000000 >--- a/Koha/Schema/Result/ItemtypeLocalization.pm >+++ /dev/null >@@ -1,32 +0,0 @@ >-package Koha::Schema::Result::ItemtypeLocalization; >- >-use base 'DBIx::Class::Core'; >- >-use Modern::Perl; >- >-__PACKAGE__->table_class('DBIx::Class::ResultSource::View'); >- >-__PACKAGE__->table('itemtype_localizations'); >-__PACKAGE__->result_source_instance->is_virtual(1); >-__PACKAGE__->result_source_instance->view_definition( >- "SELECT localization_id, code, lang, translation FROM localization WHERE entity='itemtypes'" >-); >- >-__PACKAGE__->add_columns( >- "localization_id", >- { data_type => "integer", is_auto_increment => 1, is_nullable => 0 }, >- "code", >- { data_type => "varchar", is_nullable => 0, size => 64 }, >- "lang", >- { data_type => "varchar", is_nullable => 0, size => 25 }, >- "translation", >- { data_type => "text", is_nullable => 1 }, >-); >- >-__PACKAGE__->belongs_to( >- "itemtype", >- "Koha::Schema::Result::Itemtype", >- { code => 'itemtype' } >-); >- >-1; >diff --git a/Koha/Schema/Result/ItemtypesLocalization.pm b/Koha/Schema/Result/ItemtypesLocalization.pm >new file mode 100644 >index 0000000000..d7c2721b69 >--- /dev/null >+++ b/Koha/Schema/Result/ItemtypesLocalization.pm >@@ -0,0 +1,124 @@ >+use utf8; >+package Koha::Schema::Result::ItemtypesLocalization; >+ >+# Created by DBIx::Class::Schema::Loader >+# DO NOT MODIFY THE FIRST PART OF THIS FILE >+ >+=head1 NAME >+ >+Koha::Schema::Result::ItemtypesLocalization >+ >+=cut >+ >+use strict; >+use warnings; >+ >+use base 'DBIx::Class::Core'; >+ >+=head1 TABLE: C<itemtypes_localizations> >+ >+=cut >+ >+__PACKAGE__->table("itemtypes_localizations"); >+ >+=head1 ACCESSORS >+ >+=head2 itemtypes_localizations_id >+ >+ data_type: 'integer' >+ is_auto_increment: 1 >+ is_nullable: 0 >+ >+=head2 itemtype >+ >+ data_type: 'varchar' >+ is_foreign_key: 1 >+ is_nullable: 0 >+ size: 10 >+ >+=head2 property >+ >+ data_type: 'varchar' >+ is_nullable: 0 >+ size: 100 >+ >+=head2 lang >+ >+ data_type: 'varchar' >+ is_nullable: 0 >+ size: 25 >+ >+=head2 translation >+ >+ data_type: 'mediumtext' >+ is_nullable: 1 >+ >+=cut >+ >+__PACKAGE__->add_columns( >+ "itemtypes_localizations_id", >+ { data_type => "integer", is_auto_increment => 1, is_nullable => 0 }, >+ "itemtype", >+ { data_type => "varchar", is_foreign_key => 1, is_nullable => 0, size => 10 }, >+ "property", >+ { data_type => "varchar", is_nullable => 0, size => 100 }, >+ "lang", >+ { data_type => "varchar", is_nullable => 0, size => 25 }, >+ "translation", >+ { data_type => "mediumtext", is_nullable => 1 }, >+); >+ >+=head1 PRIMARY KEY >+ >+=over 4 >+ >+=item * L</itemtypes_localizations_id> >+ >+=back >+ >+=cut >+ >+__PACKAGE__->set_primary_key("itemtypes_localizations_id"); >+ >+=head1 UNIQUE CONSTRAINTS >+ >+=head2 C<itemtype_property_lang> >+ >+=over 4 >+ >+=item * L</itemtype> >+ >+=item * L</property> >+ >+=item * L</lang> >+ >+=back >+ >+=cut >+ >+__PACKAGE__->add_unique_constraint("itemtype_property_lang", ["itemtype", "property", "lang"]); >+ >+=head1 RELATIONS >+ >+=head2 itemtype >+ >+Type: belongs_to >+ >+Related object: L<Koha::Schema::Result::Itemtype> >+ >+=cut >+ >+__PACKAGE__->belongs_to( >+ "itemtype", >+ "Koha::Schema::Result::Itemtype", >+ { itemtype => "itemtype" }, >+ { is_deferrable => 1, on_delete => "CASCADE", on_update => "CASCADE" }, >+); >+ >+ >+# Created by DBIx::Class::Schema::Loader v0.07052 @ 2024-10-08 14:32:00 >+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:9xi1V/qaD/R9u3KHcW7saA >+ >+ >+# You can replace this text with custom code or comments, and it will be preserved on regeneration >+1; >diff --git a/Koha/Schema/Result/Localization.pm b/Koha/Schema/Result/Localization.pm >deleted file mode 100644 >index 1919258b36..0000000000 >--- a/Koha/Schema/Result/Localization.pm >+++ /dev/null >@@ -1,108 +0,0 @@ >-use utf8; >-package Koha::Schema::Result::Localization; >- >-# Created by DBIx::Class::Schema::Loader >-# DO NOT MODIFY THE FIRST PART OF THIS FILE >- >-=head1 NAME >- >-Koha::Schema::Result::Localization >- >-=cut >- >-use strict; >-use warnings; >- >-use base 'DBIx::Class::Core'; >- >-=head1 TABLE: C<localization> >- >-=cut >- >-__PACKAGE__->table("localization"); >- >-=head1 ACCESSORS >- >-=head2 localization_id >- >- data_type: 'integer' >- is_auto_increment: 1 >- is_nullable: 0 >- >-=head2 entity >- >- data_type: 'varchar' >- is_nullable: 0 >- size: 16 >- >-=head2 code >- >- data_type: 'varchar' >- is_nullable: 0 >- size: 64 >- >-=head2 lang >- >- data_type: 'varchar' >- is_nullable: 0 >- size: 25 >- >-could be a foreign key >- >-=head2 translation >- >- data_type: 'mediumtext' >- is_nullable: 1 >- >-=cut >- >-__PACKAGE__->add_columns( >- "localization_id", >- { data_type => "integer", is_auto_increment => 1, is_nullable => 0 }, >- "entity", >- { data_type => "varchar", is_nullable => 0, size => 16 }, >- "code", >- { data_type => "varchar", is_nullable => 0, size => 64 }, >- "lang", >- { data_type => "varchar", is_nullable => 0, size => 25 }, >- "translation", >- { data_type => "mediumtext", is_nullable => 1 }, >-); >- >-=head1 PRIMARY KEY >- >-=over 4 >- >-=item * L</localization_id> >- >-=back >- >-=cut >- >-__PACKAGE__->set_primary_key("localization_id"); >- >-=head1 UNIQUE CONSTRAINTS >- >-=head2 C<entity_code_lang> >- >-=over 4 >- >-=item * L</entity> >- >-=item * L</code> >- >-=item * L</lang> >- >-=back >- >-=cut >- >-__PACKAGE__->add_unique_constraint("entity_code_lang", ["entity", "code", "lang"]); >- >- >-# Created by DBIx::Class::Schema::Loader v0.07049 @ 2021-01-21 13:39:29 >-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:Elbup2i+1JON+xa38uzd6A >- >- >-# You can replace this text with custom code or comments, and it will be preserved on regeneration >-1; >diff --git a/Koha/UI/Form/Builder/Item.pm b/Koha/UI/Form/Builder/Item.pm >index 228ba982e6..15fe3bf5f2 100644 >--- a/Koha/UI/Form/Builder/Item.pm >+++ b/Koha/UI/Form/Builder/Item.pm >@@ -213,14 +213,11 @@ sub generate_subfield_form { > } > elsif ( $subfield->{authorised_value} eq "itemtypes" ) { > push @authorised_values, ""; >- my $itemtypes; >+ my $itemtypes = Koha::ItemTypes->new; > if ($branch_limit) { >- $itemtypes = Koha::ItemTypes->search_with_localization( >- { branchcode => $branch_limit } ); >- } >- else { >- $itemtypes = Koha::ItemTypes->search_with_localization; >+ $itemtypes = $itemtypes->search_with_library_limits({}, {}, $branch_limit); > } >+ $itemtypes = $itemtypes->order_by_translated_description; > while ( my $itemtype = $itemtypes->next ) { > push @authorised_values, $itemtype->itemtype; > $authorised_lib{ $itemtype->itemtype } = >diff --git a/admin/itemtypes.pl b/admin/itemtypes.pl >index 7eceef6b1a..42f1fa2a0d 100755 >--- a/admin/itemtypes.pl >+++ b/admin/itemtypes.pl >@@ -31,7 +31,6 @@ use C4::Auth qw( get_template_and_user ); > use C4::Output qw( output_html_with_http_headers ); > use Koha::ItemTypes; > use Koha::ItemType; >-use Koha::Localizations; > > my $input = CGI->new; > my $searchfield = $input->param('description'); >diff --git a/admin/localization.pl b/admin/localization.pl >index 58205d8df7..2440b11a26 100755 >--- a/admin/localization.pl >+++ b/admin/localization.pl >@@ -21,8 +21,9 @@ use Modern::Perl; > use C4::Auth qw( get_template_and_user ); > use C4::Output qw( output_html_with_http_headers ); > >-use Koha::Localization; >-use Koha::Localizations; >+use Koha::Database; >+ >+my $schema = Koha::Database->schema; > > use CGI qw( -utf8 ); > >@@ -36,18 +37,20 @@ my ( $template, $borrowernumber, $cookie ) = get_template_and_user( > } > ); > >-my $entity = $query->param('entity'); >-my $code = $query->param('code'); >-my $rs = Koha::Localizations->search( { entity => $entity, code => $code } ); >+my $source = $query->param('source'); >+my $object_id = $query->param('object_id'); >+my $property = $query->param('property'); >+ >+my $row = $schema->resultset($source)->find($object_id); >+ > 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 $localizations = $row->localizations->search({ property => $property }); >+while ( my $localization = $localizations->next ) { >+ push @translations, { >+ localization_id => $localization->id, >+ lang => $localization->lang, >+ translation => $localization->translation, >+ }; > } > > my $translated_languages = C4::Languages::getTranslatedLanguages( 'intranet', C4::Context->preference('template') ); >@@ -55,8 +58,9 @@ my $translated_languages = C4::Languages::getTranslatedLanguages( 'intranet', C4 > $template->param( > translations => \@translations, > languages => $translated_languages, >- entity => $entity, >- code => $code, >+ source => $source, >+ object_id => $object_id, >+ property => $property, > ); > > output_html_with_http_headers $query, $cookie, $template->output; >diff --git a/installer/data/mysql/atomicupdate/bug-38136.pl b/installer/data/mysql/atomicupdate/bug-38136.pl >new file mode 100644 >index 0000000000..1113b6e559 >--- /dev/null >+++ b/installer/data/mysql/atomicupdate/bug-38136.pl >@@ -0,0 +1,22 @@ >+use Modern::Perl; >+use Koha::Installer::Output qw(say_warning say_failure say_success say_info); >+ >+return { >+ bug_number => '38136', >+ description => 'Add localization.property', >+ up => sub { >+ my ($args) = @_; >+ my ( $dbh, $out ) = @$args{qw(dbh out)}; >+ >+ unless (column_exists('localization', 'property')) { >+ $dbh->do("alter table `localization` add `property` varchar(100) null after `code`"); >+ $dbh->do("update `localization` set `property` = 'description', entity = 'Itemtype'"); >+ $dbh->do("alter table `localization` modify `property` varchar(100) not null"); >+ $dbh->do("alter table `localization` drop key `entity_code_lang`"); >+ $dbh->do("alter table `localization` add unique key `entity_code_property_lang` (`entity`, `code`, `property`, `lang`)"); >+ >+ say_success($out, 'Added column localization.property and updated localization.entity values'); >+ } >+ >+ }, >+}; >diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql >index d368659571..6fc19c06c2 100644 >--- a/installer/data/mysql/kohastructure.sql >+++ b/installer/data/mysql/kohastructure.sql >@@ -4406,10 +4406,11 @@ CREATE TABLE `localization` ( > `localization_id` int(11) NOT NULL AUTO_INCREMENT, > `entity` varchar(16) NOT NULL, > `code` varchar(64) NOT NULL, >+ `property` varchar(100) NOT NULL, > `lang` varchar(25) NOT NULL COMMENT 'could be a foreign key', > `translation` mediumtext DEFAULT NULL, > PRIMARY KEY (`localization_id`), >- UNIQUE KEY `entity_code_lang` (`entity`,`code`,`lang`) >+ UNIQUE KEY `entity_code_property_lang` (`entity`,`code`,`property`,`lang`) > ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; > /*!40101 SET character_set_client = @saved_cs_client */; > >diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/localization-link.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/localization-link.inc >new file mode 100644 >index 0000000000..e4d5c1111c >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/localization-link.inc >@@ -0,0 +1 @@ >+<a href="/cgi-bin/koha/admin/localization.pl?source=[% source | uri %]&object_id=[% object_id | uri %]&property=[% property | uri %]" rel="gb_page_center[600,500]"><i class="fa-solid fa-pencil" aria-hidden="true"></i> Translate into other languages</a> >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/itemtypes.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/itemtypes.tt >index 278d10815f..1f5f7ef5af 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/itemtypes.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/itemtypes.tt >@@ -192,7 +192,7 @@ > <label for="description" class="required">Description: </label> > <input type="text" id="description" name="description" size="48" value="[% itemtype.description | html %]" required="required" /> <span class="required">Required</span> > [% IF can_be_translated %] >- <a href="/cgi-bin/koha/admin/localization.pl?entity=itemtypes&code=[% itemtype.itemtype | uri %]" title="Translate item type [% itemtype.itemtype | html %]" rel="gb_page_center[600,500]"><i class="fa-solid fa-pencil" aria-hidden="true"></i> Translate into other languages</a> >+ [% INCLUDE 'localization-link.inc' source='Itemtype' object_id=itemtype.itemtype property='description' %] > [% END %] > </li> > <li> >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/localization.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/localization.tt >index fc500a8e31..023b9ea9cf 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/localization.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/localization.tt >@@ -18,14 +18,14 @@ > <h1>Localization</h1> > <form id="add_translation" method="get"> > [% INCLUDE 'csrf-token.inc' %] >- <input type="hidden" name="entity" value="[% entity | html %]" /> >- <input type="hidden" name="code" value="[% code | html %]" /> >- <input type="hidden" name="interface" value="[% interface_side | html %]" /> >+ <input type="hidden" name="source" value="[% source | html %]" /> >+ <input type="hidden" name="object_id" value="[% object_id | html %]" /> >+ <input type="hidden" name="property" value="[% property | html %]" /> > <fieldset class="rows clearfix"> > <ol> > <li> > <span class="label">Authorized value:</span> >- [% code | html %] >+ [% object_id | html %] > </li> > <li> > <label for="lang">Language:</label> >@@ -66,9 +66,6 @@ > <table id="localization"> > <thead> > <tr> >- <th>Id</th> >- <th>Entity</th> >- <th>Code</th> > <th>Language</th> > <th>Translation</th> > <th class="NoSort"> </th> >@@ -76,10 +73,7 @@ > </thead> > <tbody> > [% FOR t IN translations %] >- <tr id="row_id_[% t.id | html %]" data-id="[% t.id | html %]"> >- <td>[% t.id | html %]</td> >- <td>[% t.entity | html %]</td> >- <td>[% t.code | html %]</td> >+ <tr id="row_id_[% t.localization_id | html %]" data-id="[% t.localization_id | html %]"> > <td class="lang">[% t.lang | html %]</td> > <td class="translation" contenteditable="true">[% t.translation | html %]</td> > <td class="actions"><a href="#" class="delete"><i class="fa fa-trash-can"></i> Delete</a></td> >@@ -102,7 +96,7 @@ > var message; > if ( type == 'success_on_update' ) { > message = $('<div class="alert alert-info"></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)); >+ message.text(_("Translation for lang %s has correctly been updated with '%s'").format(data.lang, data.translation)); > } else if ( type == 'error_on_update' ) { > message = $('<div class="alert alert-warning"></div>'); > if ( data.error_code == 'already_exists' ) { >@@ -112,13 +106,13 @@ > } > } else if ( type == 'success_on_delete' ) { > message = $('<div class="alert alert-info"></div>'); >- message.text(_("The translation (id %s) has been removed successfully").format(data.id)); >+ message.text(_("The translation has been removed successfully")); > } else if ( type == 'error_on_delete' ) { > message = $('<div class="alert alert-warning"></div>'); > message.text(_("An error occurred when deleting this translation")); > } else if ( type == 'success_on_insert' ) { > message = $('<div class="alert alert-info"></div>'); >- message.text(_("Translation (id %s) has been added successfully").format(data.id)); >+ message.text(_("Translation has been added successfully")); > } else if ( type == 'error_on_insert' ) { > message = $('<div class="alert alert-warning"></div>'); > if ( data.error_code == 'already_exists' ) { >@@ -135,14 +129,18 @@ > }, 3000); > } > >- function send_update_request( data, cell ) { >+ function send_update_request( _data, cell ) { >+ const form = document.forms.add_translation; >+ const source = form.elements.source.value; >+ const object_id = form.elements.object_id.value; >+ const data = Object.assign({}, _data, { source, object_id }); > const client = APIClient.localization; > client.localizations.update(data).then( > success => { > if ( success.error ) { > $(cell).css('background-color', '#FF0000'); > show_message({ type: 'error_on_update', data: success }); >- } else if ( success.is_changed == 1 ) { >+ } else { > $(cell).css('background-color', '#00FF00'); > show_message({ type: 'success_on_update', data: success }); > } >@@ -166,12 +164,18 @@ > ); > } > >- function send_delete_request( id, cell ) { >+ function send_delete_request( localization_id, cell ) { >+ const form = document.forms.add_translation; >+ const source = form.elements.source.value; >+ const object_id = form.elements.object_id.value; >+ const property = form.elements.property.value; >+ const data = { source, object_id, property, localization_id }; >+ > const client = APIClient.localization; >- client.localizations.delete(id).then( >+ client.localizations.delete(data).then( > success => { >- $("#localization").DataTable().row( '#row_id_' + id ).remove().draw(); >- show_message({ type: 'success_on_delete', data: {id} }); >+ $("#localization").DataTable().row( '#row_id_' + localization_id ).remove().draw(); >+ show_message({ type: 'success_on_delete', data: {localization_id} }); > }, > error => { > $(cell).css('background-color', '#FF9090'); >@@ -212,6 +216,11 @@ > }); > $("td.lang").on('click', function(){ > var td = this; >+ if (td.childElementCount > 0) { >+ // do nothing if there is already something there (like a select for instance) >+ return; >+ } >+ > var lang = $(td).text(); > $(td).css('background-color', ''); > var my_select = $(languages_select).clone(); >@@ -221,10 +230,9 @@ > }); > $(my_select).on('change', function(){ > var tr = $(this).parent().parent(); >- var id = $(tr).data('id'); >+ var localization_id = $(tr).data('id'); > var lang = $(this).find('option:selected').val(); >- var translation = $(this).text(); >- send_update_request( {id, lang, translation}, td ); >+ send_update_request( {localization_id, lang}, td ); > }); > $(my_select).on('blur', function(){ > $(td).html(lang); >@@ -234,10 +242,9 @@ > > $("td.translation").on('blur', function(){ > var tr = $(this).parent(); >- var id = $(tr).data('id'); >- var lang = $(tr).find('td.lang').text(); >+ var localization_id = $(tr).data('id'); > var translation = $(this).text(); >- send_update_request( {id, lang, translation}, this ); >+ send_update_request( {localization_id, translation}, this ); > }); > > $("body").on("click", "a.delete", function(e){ >@@ -252,20 +259,24 @@ > > $("#add_translation").on('submit', function(e){ > e.preventDefault(); >- let localization = { >- entity: $(this).find('input[name="entity"]').val(), >- code: $(this).find('input[name="code"]').val(), >- lang: $(this).find('select[name="lang"] option:selected').val(), >- translation: $(this).find('input[name="translation"]').val(), >- }; >+ >+ const form = this; >+ const source = form.elements.source.value; >+ const object_id = form.elements.object_id.value; >+ const property = form.elements.property.value; >+ const lang = form.elements.lang.value >+ const translation = form.elements.translation.value >+ >+ let localization = { source, object_id, property, lang, translation }; > const client = APIClient.localization; > client.localizations.create(localization).then( > success => { > if ( success.error ) { > show_message({ type: 'error_on_insert', data: success }); > } else { >- var new_row = table.row.add( [ success.id, success.entity, success.code, success.lang, success.translation, "<a href=\"#\" class=\"delete\"><i class=\"fa fa-trash-can\"></i> Delete</a>" ] ).draw().node(); >- $( new_row ).attr("id", "row_id_" + success.id ).data("id", success.id ); >+ let delete_str = _("Delete"); >+ var new_row = table.row.add( [ success.lang, success.translation, "<a href=\"#\" class=\"delete\"><i class=\"fa fa-trash-can\"></i> %s</a>".format(escape_str(delete_str)) ] ).draw().node(); >+ $( new_row ).attr("id", "row_id_" + success.localization_id ).data("id", success.localization_id ); > show_message({ type: 'success_on_insert', data: success }); > } > }, >diff --git a/koha-tmpl/intranet-tmpl/prog/js/fetch/localization-api-client.js b/koha-tmpl/intranet-tmpl/prog/js/fetch/localization-api-client.js >index 4fbede4c67..c6d81f16b9 100644 >--- a/koha-tmpl/intranet-tmpl/prog/js/fetch/localization-api-client.js >+++ b/koha-tmpl/intranet-tmpl/prog/js/fetch/localization-api-client.js >@@ -13,12 +13,7 @@ export class LocalizationAPIClient extends HttpClient { > create: localization => > this.post({ > endpoint: "", >- body: "entity=%s&code=%s&lang=%s&translation=%s".format( >- encodeURIComponent(localization.entity), >- encodeURIComponent(localization.code), >- encodeURIComponent(localization.lang), >- encodeURIComponent(localization.translation) >- ), >+ body: (new URLSearchParams(localization)).toString(), > headers: { > "Content-Type": > "application/x-www-form-urlencoded;charset=utf-8", >@@ -27,23 +22,15 @@ export class LocalizationAPIClient extends HttpClient { > update: localization => > this.put({ > endpoint: "", >- body: "id=%s&lang=%s&translation=%s".format( >- encodeURIComponent(localization.id), >- encodeURIComponent(localization.lang), >- encodeURIComponent(localization.translation) >- ), >+ body: (new URLSearchParams(localization)).toString(), > headers: { > "Content-Type": > "application/x-www-form-urlencoded;charset=utf-8", > }, > }), >- delete: id => >+ delete: localization => > this.delete({ >- endpoint: "/?id=%s".format(id), >- headers: { >- "Content-Type": >- "application/x-www-form-urlencoded;charset=utf-8", >- }, >+ endpoint: "?" + (new URLSearchParams(localization)).toString(), > }), > }; > } >diff --git a/svc/localization b/svc/localization >index 6eed60632c..d07d3b2635 100755 >--- a/svc/localization >+++ b/svc/localization >@@ -2,103 +2,114 @@ > > use Modern::Perl; > use Encode qw( encode ); >+use Try::Tiny; >+use JSON qw( to_json ); > > use C4::Service; >-use Koha::Localizations; >+use Koha::Caches; >+use Koha::Database; >+use C4::Output qw( output_with_http_headers ); > > our ( $query, $response ) = C4::Service->init( parameters => 'manage_itemtypes' ); > >-sub get_translations { >- my $rs = Koha::Localizations->search({ entity => $query->param('entity'), code => $query->param('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, >- } >- } >- $response->param( translations => \@translations ); >- C4::Service->return_success( $response ); >-} >- > sub update_translation { >- my $id = $query->param('id'); >- my $translation = $query->param('translation'); >+ my $source = $query->param('source'); >+ my $localization_id = $query->param('localization_id'); >+ my $object_id = $query->param('object_id'); > my $lang = $query->param('lang'); >+ my $translation = $query->param('translation'); > >- 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 %params; >- my $is_changed; >- if ( $localization->is_changed ) { >- $is_changed = 1; >- unless ( Koha::Localizations->search( { entity => $localization->entity, code => $localization->code, lang => $lang, localization_id => { '!=' => $localization->localization_id }, } )->count ) { >- $localization->store; >- } else { >- $params{error} = 1; >- $params{error_code} = 'already_exists'; >+ my $schema = Koha::Database->schema; >+ my $row = $schema->resultset($source)->find($object_id); >+ if ($row) { >+ my $localization = $row->localizations->find($localization_id); >+ if ($localization) { >+ try { >+ my $original_lang = $localization->lang; >+ $localization->lang($lang) if $lang; >+ $localization->translation($translation) if $translation; >+ $localization->update(); >+ Koha::Caches->get_instance('localization')->clear_from_cache("$source:$original_lang"); >+ Koha::Caches->get_instance('localization')->clear_from_cache("$source:$lang") if $lang; >+ } catch { >+ $localization->discard_changes(); >+ $response->param(error => 1, error_code => 'already_exists'); >+ }; > } >+ >+ $response->param( >+ lang => $localization->lang, >+ translation => $localization->translation, >+ ); > } >- $response->param( >- %params, >- 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 $source = $query->param('source'); >+ my $localization_id = $query->param('localization_id'); >+ my $object_id = $query->param('object_id'); >+ my $property = $query->param('property'); > my $lang = $query->param('lang'); > my $translation = $query->param('translation'); > >- unless ( Koha::Localizations->search({entity => $entity, code => $code, lang => $lang, })->count ) { >- my $localization = Koha::Localization->new( >+ my $schema = Koha::Database->schema; >+ my $row = $schema->resultset($source)->find($object_id); >+ try { >+ my $localization = $row->create_related( >+ "${property}_localizations", > { >- entity => $entity, >- code => $code, >+ entity => $source, > lang => $lang, > translation => $translation, > } > ); >- $localization->store; >+ Koha::Caches->get_instance('localization')->clear_from_cache("$source:$lang"); >+ > $response->param( >- id => $localization->localization_id, >- entity => $localization->entity, >- code => $localization->code, >- lang => $localization->lang, >- translation => $localization->translation, >+ lang => $localization->lang, >+ translation => $localization->translation, >+ localization_id => $localization->id, > ); >+ } catch { >+ $response->param(error => 1, error_code => 'already_exists'); >+ }; > >- } else { >- $response->param( error => 1, error_code => 'already_exists', ); >- } > C4::Service->return_success( $response ); > } > > sub delete_translation { >- my $id = $query->param('id'); >- Koha::Localizations->find($id)->delete; >- $response->param( id => $id ); >+ my $source = $query->param('source'); >+ my $object_id = $query->param('object_id'); >+ my $localization_id = $query->param('localization_id'); >+ >+ my $schema = Koha::Database->schema; >+ my $row = $schema->resultset($source)->find($object_id); >+ if ($row && $row->can('localizations')) { >+ my $localization = $row->localizations->find($localization_id); >+ >+ unless ( $localization ) { >+ my $json = to_json ( { errors => 'Not found' } ); >+ output_with_http_headers $query, undef, $json, 'js', '404 Not Found'; >+ exit; >+ } >+ >+ if ($localization) { >+ $localization->delete(); >+ Koha::Caches->get_instance('localization')->clear_from_cache("$source:" . $localization->lang); >+ } >+ >+ $response->param( >+ localization_id => $localization_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 ], >+ [ 'PUT /', [], \&update_translation ], >+ [ 'POST /', [], \&add_translation ], >+ [ 'DELETE /', [], \&delete_translation ], > ); >diff --git a/t/db_dependent/Koha/Filter/ExpandCodedFields.t b/t/db_dependent/Koha/Filter/ExpandCodedFields.t >index ac33dc9a14..234b8cde5e 100755 >--- a/t/db_dependent/Koha/Filter/ExpandCodedFields.t >+++ b/t/db_dependent/Koha/Filter/ExpandCodedFields.t >@@ -68,10 +68,11 @@ subtest 'ExpandCodedFields tests' => sub { > $cache->clear_from_cache("MarcCodedFields-"); > # Clear GetAuthorisedValueDesc-generated cache > $cache->clear_from_cache("libraries:name"); >- $cache->clear_from_cache("itemtype:description:en"); > $cache->clear_from_cache("cn_sources:description"); > $cache->clear_from_cache("AV_descriptions:" . $av->category); > >+ Koha::Caches->get_instance('localization')->clear_from_cache('Itemtype:en'); >+ > C4::Biblio::ModBiblio( $record, $biblio->biblionumber ); > $biblio = Koha::Biblios->find( $biblio->biblionumber); > $record = $biblio->metadata->record; >diff --git a/t/db_dependent/Koha/Item.t b/t/db_dependent/Koha/Item.t >index 7d115cdb3a..82df6e8902 100755 >--- a/t/db_dependent/Koha/Item.t >+++ b/t/db_dependent/Koha/Item.t >@@ -1950,10 +1950,11 @@ subtest 'columns_to_str' => sub { > $cache->clear_from_cache("MarcStructure-1-"); > $cache->clear_from_cache("MarcSubfieldStructure-"); > $cache->clear_from_cache("libraries:name"); >- $cache->clear_from_cache("itemtype:description:en"); > $cache->clear_from_cache("cn_sources:description"); > $cache->clear_from_cache("AV_descriptions:LOST"); > >+ Koha::Caches->get_instance('localization')->clear_from_cache('Itemtype:en'); >+ > # Creating subfields 'é', 'è' that are not linked with a kohafield > Koha::MarcSubfieldStructures->search( > { >@@ -2034,10 +2035,11 @@ subtest 'columns_to_str' => sub { > $cache->clear_from_cache("MarcStructure-1-"); > $cache->clear_from_cache("MarcSubfieldStructure-"); > $cache->clear_from_cache("libraries:name"); >- $cache->clear_from_cache("itemtype:description:en"); > $cache->clear_from_cache("cn_sources:description"); > $cache->clear_from_cache("AV_descriptions:LOST"); > >+ Koha::Caches->get_instance('localization')->clear_from_cache('Itemtype:en'); >+ > $schema->storage->txn_rollback; > }; > >@@ -2054,10 +2056,11 @@ subtest 'strings_map() tests' => sub { > $cache->clear_from_cache("MarcStructure-1-"); > $cache->clear_from_cache("MarcSubfieldStructure-"); > $cache->clear_from_cache("libraries:name"); >- $cache->clear_from_cache("itemtype:description:en"); > $cache->clear_from_cache("cn_sources:description"); > $cache->clear_from_cache("AV_descriptions:LOST"); > >+ Koha::Caches->get_instance('localization')->clear_from_cache('Itemtype:en'); >+ > # Recreating subfields just to be sure tests will be ok > # 1 => av (LOST) > # 3 => no link >@@ -2240,9 +2243,10 @@ subtest 'strings_map() tests' => sub { > $cache->clear_from_cache("MarcStructure-1-"); > $cache->clear_from_cache("MarcSubfieldStructure-"); > $cache->clear_from_cache("libraries:name"); >- $cache->clear_from_cache("itemtype:description:en"); > $cache->clear_from_cache("cn_sources:description"); > >+ Koha::Caches->get_instance('localization')->clear_from_cache('Itemtype:en'); >+ > $schema->storage->txn_rollback; > }; > >diff --git a/t/db_dependent/Koha/ItemTypes.t b/t/db_dependent/Koha/ItemTypes.t >index d62cf05e48..09073e320a 100755 >--- a/t/db_dependent/Koha/ItemTypes.t >+++ b/t/db_dependent/Koha/ItemTypes.t >@@ -26,6 +26,7 @@ use t::lib::TestBuilder; > > use C4::Calendar qw( new ); > use Koha::Biblioitems; >+use Koha::Caches; > use Koha::Libraries; > use Koha::Database; > use Koha::DateUtils qw(dt_from_string);; >@@ -66,30 +67,29 @@ my $child3 = $builder->build_object({ > } > }); > >-Koha::Localization->new( >+$child1->_result->localizations->create( > { >- entity => 'itemtypes', >- code => $child1->itemtype, >+ property => 'description', > lang => 'en', > translation => 'b translated itemtype desc' > } >-)->store; >-Koha::Localization->new( >+); >+$child2->_result->localizations->create( > { >- entity => 'itemtypes', >- code => $child2->itemtype, >+ property => 'description', > lang => 'en', > translation => 'a translated itemtype desc' > } >-)->store; >-Koha::Localization->new( >+); >+$child3->_result->localizations->create( > { >- entity => 'something_else', >- code => $child2->itemtype, >+ property => 'description', > lang => 'en', > translation => 'another thing' > } >-)->store; >+); >+ >+Koha::Caches->get_instance('localization')->flush_all(); > > my $type = Koha::ItemTypes->find($child1->itemtype); > ok( defined($type), 'first result' ); >diff --git a/t/db_dependent/Koha/Template/Plugin/ItemTypes.t b/t/db_dependent/Koha/Template/Plugin/ItemTypes.t >index 59726397e5..cfc0c23b67 100755 >--- a/t/db_dependent/Koha/Template/Plugin/ItemTypes.t >+++ b/t/db_dependent/Koha/Template/Plugin/ItemTypes.t >@@ -19,6 +19,7 @@ use Modern::Perl; > use Test::More tests => 10; > > use C4::Context; >+use Koha::Caches; > use Koha::Database; > use Koha::ItemTypes; > >@@ -52,14 +53,13 @@ my $itemtypeA = $builder->build_object( > } > } > ); >-Koha::Localization->new( >+$itemtypeA->_result->localizations->create( > { >- entity => 'itemtypes', >- code => $itemtypeA->itemtype, >+ property => 'description', > lang => 'en', > translation => 'Translated itemtype A' > } >-)->store; >+); > my $itemtypeB = $builder->build_object( > { > class => 'Koha::ItemTypes', >@@ -69,14 +69,13 @@ my $itemtypeB = $builder->build_object( > } > } > ); >-Koha::Localization->new( >+$itemtypeB->_result->localizations->create( > { >- entity => 'itemtypes', >- code => $itemtypeB->itemtype, >+ property => 'description', > lang => 'en', > translation => 'Translated itemtype B' > } >-)->store; >+); > my $itemtypeC = $builder->build_object( > { > class => 'Koha::ItemTypes', >@@ -87,6 +86,8 @@ my $itemtypeC = $builder->build_object( > } > ); > >+Koha::Caches->get_instance('localization')->flush_all(); >+ > my $GetDescriptionA1 = $plugin->GetDescription($itemtypeA->itemtype); > is($GetDescriptionA1, "Translated itemtype A", "ItemType without parent - GetDescription without want parent"); > my $GetDescriptionA2 = $plugin->GetDescription($itemtypeA->itemtype, 1); >diff --git a/t/db_dependent/api/v1/item_types.t b/t/db_dependent/api/v1/item_types.t >index 3196bbbb3b..9589f00ff1 100755 >--- a/t/db_dependent/api/v1/item_types.t >+++ b/t/db_dependent/api/v1/item_types.t >@@ -25,6 +25,7 @@ use t::lib::Mocks; > > use Mojo::JSON qw(encode_json); > >+use Koha::Caches; > use Koha::ItemTypes; > use Koha::Database; > >@@ -66,29 +67,23 @@ subtest 'list() tests' => sub { > } > ); > >- my $en = $builder->build_object( >+ $item_type->_result->localizations->create( > { >- class => 'Koha::Localizations', >- value => { >- entity => 'itemtypes', >- code => $item_type->id, >- lang => 'en', >- translation => 'English word "test"', >- } >+ property => 'description', >+ lang => 'en', >+ translation => 'English word "test"', > } > ); >- my $sv = $builder->build_object( >+ $item_type->_result->localizations->create( > { >- class => 'Koha::Localizations', >- value => { >- entity => 'itemtypes', >- code => $item_type->id, >- lang => 'sv_SE', >- translation => 'Swedish word "test"', >- } >+ property => 'description', >+ lang => 'sv_SE', >+ translation => 'Swedish word "test"', > } > ); > >+ Koha::Caches->get_instance('localization')->flush_all(); >+ > my $librarian = $builder->build_object( > { > class => 'Koha::Patrons', >-- >2.39.2
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 38136
:
172585
|
172804
|
173195
|
173196
|
173197
|
173198
|
174578
|
174579
|
174580
|
174581
|
174598
|
174599
|
175077
|
175078
|
175082
|
175083