Bugzilla – Attachment 172585 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), 56.48 KB, created by
Julian Maurice
on 2024-10-10 12:05:41 UTC
(
hide
)
Description:
Bug 38136: Refactor database translations (alternative)
Filename:
MIME Type:
Creator:
Julian Maurice
Created:
2024-10-10 12:05:41 UTC
Size:
56.48 KB
patch
obsolete
>From d598d9498811d761ff3f6b55ec7f7fe4c41219ec 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. > >The solution proposed here is to store translations in separate tables, >one table for each object type (so, one for itemtypes, another for >authorised values, ...). > >While it may seem redundant and tedious to add another table with the >same structure for each localizable object type, this has the benefit of >being able to add foreign key constraints and thus having integrity >checks "for free". > >Also this patch adds all the necessary tools so that the tedium of >localizing a new object type is reduced to a minimum. In summary, we >will only need to: >* create the table structure (atomicupdate, kohastructure.sql) >* 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( > 'itemtypes_localizations', > 'itemtype' => 'itemtype', > 'description' > ); > >(see POD in Koha/DBIx/Class/Localization.pm for more information) > >Final notes: >* If having several localization tables is really a problem, I believe > the current implementation can be changed easily to use a single table > (just need to change the dbic relationship conditions) >* 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::DBIx::Class::Localization::localization). Localizations in cache > are grouped by object type and language (keys look like > 'KOHA:localization:Itemtype:en') >--- > Koha/DBIx/Class/Localization.pm | 200 ++++++++++++++++++ > Koha/ItemType.pm | 88 ++------ > Koha/ItemTypes.pm | 34 ++- > Koha/Localization.pm | 73 ------- > Koha/Localizations.pm | 34 --- > Koha/Object.pm | 27 +++ > Koha/Schema/Result/Itemtype.pm | 39 ++-- > Koha/Schema/Result/ItemtypeLocalization.pm | 32 --- > Koha/Schema/Result/ItemtypesLocalization.pm | 124 +++++++++++ > Koha/Schema/Result/Localization.pm | 108 ---------- > admin/itemtypes.pl | 1 - > admin/localization.pl | 34 +-- > .../data/mysql/atomicupdate/bug-38136.pl | 36 ++++ > installer/data/mysql/kohastructure.sql | 17 ++ > .../prog/en/includes/localization-link.inc | 1 + > .../prog/en/modules/admin/itemtypes.tt | 2 +- > .../prog/en/modules/admin/localization.tt | 71 ++++--- > .../prog/js/fetch/localization-api-client.js | 21 +- > svc/localization | 127 ++++++----- > t/db_dependent/Koha/ItemTypes.t | 24 +-- > .../Koha/Template/Plugin/ItemTypes.t | 17 +- > t/db_dependent/api/v1/item_types.t | 27 +-- > 22 files changed, 618 insertions(+), 519 deletions(-) > create mode 100644 Koha/DBIx/Class/Localization.pm > delete mode 100644 Koha/Localization.pm > delete mode 100644 Koha/Localizations.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/Koha/DBIx/Class/Localization.pm b/Koha/DBIx/Class/Localization.pm >new file mode 100644 >index 0000000000..cfc09502ae >--- /dev/null >+++ b/Koha/DBIx/Class/Localization.pm >@@ -0,0 +1,200 @@ >+package Koha::DBIx::Class::Localization; >+ >+=head1 NAME >+ >+Koha::DBIx::Class::Localization >+ >+=head1 SYNOPSIS >+ >+ package Koha::Schema::Result::SomeTable; >+ >+ # ... generated code ... >+ >+ __PACKAGE__->load_components('+Koha::DBIx::Class::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 base qw(DBIx::Class); >+ >+sub localization_add_relationships { >+ my ($class, $rel_name, $fk_column, $pk_column, @properties) = @_; >+ >+ my $rel_info = $class->relationship_info($rel_name); >+ >+ $class->has_many( >+ 'localizations', >+ $rel_info->{class}, >+ { "foreign.$fk_column" => "self.$pk_column" }, >+ { cascade_copy => 0, cascade_delete => 0, cascade_update => 0 }, >+ ); >+ >+ foreach my $property (@properties) { >+ $class->might_have( >+ $property . "_localization", >+ $rel_info->{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}.$fk_column" => { -ident => "$args->{self_alias}.$pk_column" }, >+ "$args->{foreign_alias}.property" => $property, >+ "$args->{foreign_alias}.lang" => $lang, >+ }, >+ !$args->{self_result_object} ? () : { >+ "$args->{foreign_alias}.$fk_column" => $args->{self_result_object}->get_column($pk_column), >+ "$args->{foreign_alias}.property" => $property, >+ "$args->{foreign_alias}.lang" => $lang, >+ }, >+ !$args->{foreign_values} ? () : { >+ "$args->{foreign_alias}.$fk_column" => $args->{foreign_values}->{$pk_column}, >+ } >+ ); >+ }, >+ { cascade_copy => 0, cascade_delete => 0, cascade_update => 0 }, >+ ); >+ >+ $class->has_many( >+ $property . '_localizations', >+ $rel_info->{class}, >+ sub { >+ my ($args) = @_; >+ >+ return ( >+ { >+ "$args->{foreign_alias}.$fk_column" => { -ident => "$args->{self_alias}.$pk_column" }, >+ "$args->{foreign_alias}.property" => $property, >+ }, >+ !$args->{self_result_object} ? () : { >+ "$args->{foreign_alias}.$fk_column" => $args->{self_result_object}->get_column($pk_column), >+ "$args->{foreign_alias}.property" => $property, >+ }, >+ !$args->{foreign_values} ? () : { >+ "$args->{foreign_alias}.$fk_column" => $args->{foreign_values}->{$pk_column}, >+ } >+ ); >+ }, >+ { 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 $rel_info = $self->relationship_info('localizations'); >+ my $rel_source = $rel_info->{class} =~ s/.*:://r; >+ >+ my ($fk_column) = map { s/^foreign\.//r } keys %{ $rel_info->{cond} }; >+ >+ my $localizations = $result_source->schema->resultset($rel_source)->search({ lang => $lang }); >+ while (my $localization = $localizations->next) { >+ my $fk = $localization->get_column($fk_column); >+ my $localization_key = sprintf('%s:%s', $fk, $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/ItemType.pm b/Koha/ItemType.pm >index fb88518c25..0f352c84e6 100644 >--- a/Koha/ItemType.pm >+++ b/Koha/ItemType.pm >@@ -22,7 +22,6 @@ use C4::Languages; > use Koha::Caches; > use Koha::Database; > use Koha::CirculationRules; >-use Koha::Localizations; > > use base qw(Koha::Object Koha::Object::Limit::Library); > >@@ -36,48 +35,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 +50,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 +60,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 > ]; > } > >@@ -234,9 +168,23 @@ sub to_api_mapping { > rentalcharge_daily_calendar => 'daily_rental_charge_calendar', > rentalcharge_hourly => 'hourly_rental_charge', > rentalcharge_hourly_calendar => 'hourly_rental_charge_calendar', >+ >+ # 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 ad34bbc948..0f6b50e67c 100644 >--- a/Koha/Object.pm >+++ b/Koha/Object.pm >@@ -907,6 +907,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/Result/Itemtype.pm b/Koha/Schema/Result/Itemtype.pm >index 22880fdb43..95340ed4a2 100644 >--- a/Koha/Schema/Result/Itemtype.pm >+++ b/Koha/Schema/Result/Itemtype.pm >@@ -283,6 +283,21 @@ __PACKAGE__->has_many( > { cascade_copy => 0, cascade_delete => 0 }, > ); > >+=head2 itemtypes_localizations >+ >+Type: has_many >+ >+Related object: L<Koha::Schema::Result::ItemtypesLocalization> >+ >+=cut >+ >+__PACKAGE__->has_many( >+ "itemtypes_localizations", >+ "Koha::Schema::Result::ItemtypesLocalization", >+ { "foreign.itemtype" => "self.itemtype" }, >+ { cascade_copy => 0, cascade_delete => 0 }, >+); >+ > =head2 old_reserves > > Type: has_many >@@ -334,8 +349,8 @@ __PACKAGE__->has_many( > ); > > >-# Created by DBIx::Class::Schema::Loader v0.07051 @ 2024-05-08 14:38:07 >-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:9NChRQA4eBUqHpLXUFmOuw >+# Created by DBIx::Class::Schema::Loader v0.07052 @ 2024-10-09 21:42:51 >+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:+/gmrTCXAvhf12UGXmC3yg > > __PACKAGE__->add_columns( > '+automatic_checkin' => { is_boolean => 1 }, >@@ -345,21 +360,11 @@ __PACKAGE__->add_columns( > '+rentalcharge_hourly_calendar' => { is_boolean => 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::DBIx::Class::Localization'); >+__PACKAGE__->localization_add_relationships( >+ 'itemtypes_localizations', >+ 'itemtype' => 'itemtype', >+ 'description' > ); > > sub koha_object_class { >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/admin/itemtypes.pl b/admin/itemtypes.pl >index 684f9377db..390bb4ce00 100755 >--- a/admin/itemtypes.pl >+++ b/admin/itemtypes.pl >@@ -32,7 +32,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..0585f03087 >--- /dev/null >+++ b/installer/data/mysql/atomicupdate/bug-38136.pl >@@ -0,0 +1,36 @@ >+use Modern::Perl; >+use Koha::Installer::Output qw(say_warning say_failure say_success say_info); >+ >+return { >+ bug_number => '38136', >+ description => 'Add itemtypes_localizations table', >+ up => sub { >+ my ($args) = @_; >+ my ( $dbh, $out ) = @$args{qw(dbh out)}; >+ >+ unless ( TableExists('itemtypes_localizations') ) { >+ $dbh->do( " >+ CREATE TABLE `itemtypes_localizations` ( >+ `itemtypes_localizations_id` int(11) NOT NULL AUTO_INCREMENT, >+ `itemtype` varchar(10) NOT NULL, >+ `property` varchar(100) NOT NULL, >+ `lang` varchar(25) NOT NULL, >+ `translation` mediumtext DEFAULT NULL, >+ PRIMARY KEY (`itemtypes_localizations_id`), >+ UNIQUE KEY `itemtype_property_lang` (`itemtype`, `property`, `lang`), >+ CONSTRAINT `itemtypes_localizations_itemtype_fk` >+ FOREIGN KEY (`itemtype`) REFERENCES `itemtypes` (`itemtype`) >+ ON DELETE CASCADE ON UPDATE CASCADE >+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; >+ " ); >+ >+ if ( TableExists('localization') ) { >+ $dbh->do( " >+ INSERT INTO `itemtypes_localizations` (`itemtype`, `property`, `lang`, `translation`) >+ SELECT `code`, 'description', `lang`, `translation` FROM `localization` WHERE `entity` = 'itemtypes' >+ " ); >+ $dbh->do('DROP TABLE localization'); >+ } >+ } >+ }, >+}; >diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql >index 31f0828a24..34c813fc90 100644 >--- a/installer/data/mysql/kohastructure.sql >+++ b/installer/data/mysql/kohastructure.sql >@@ -4166,6 +4166,23 @@ CREATE TABLE `itemtypes_branches` ( > ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; > /*!40101 SET character_set_client = @saved_cs_client */; > >+-- >+-- Table structure for table `itemtypes_localizations` >+-- >+ >+CREATE TABLE `itemtypes_localizations` ( >+ `itemtypes_localizations_id` int(11) NOT NULL AUTO_INCREMENT, >+ `itemtype` varchar(10) NOT NULL, >+ `property` varchar(100) NOT NULL, >+ `lang` varchar(25) NOT NULL, >+ `translation` mediumtext DEFAULT NULL, >+ PRIMARY KEY (`itemtypes_localizations_id`), >+ UNIQUE KEY `itemtype_property_lang` (`itemtype`, `property`, `lang`), >+ CONSTRAINT `itemtypes_localizations_itemtype_fk` >+ FOREIGN KEY (`itemtype`) REFERENCES `itemtypes` (`itemtype`) >+ ON DELETE CASCADE ON UPDATE CASCADE >+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; >+ > -- > -- Table structure for table `keyboard_shortcuts` > -- >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 dfb27e10e9..82d67269a8 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..2ff364fb66 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/localization.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/localization.tt >@@ -18,9 +18,9 @@ > <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> >@@ -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'); >@@ -221,10 +225,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 +237,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 +254,23 @@ > > $("#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 ); >+ var new_row = table.row.add( [ 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.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..6dd2c5c1d0 100755 >--- a/svc/localization >+++ b/svc/localization >@@ -2,103 +2,102 @@ > > use Modern::Perl; > use Encode qw( encode ); >+use Try::Tiny; > > use C4::Service; >-use Koha::Localizations; >+use Koha::Caches; >+use Koha::Database; > > 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:$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, > 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, > ); >+ } 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); >+ 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/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
|
179978
|
180096
|
180136