From ab56d5f2fef6fa61412d74f0ed659e653a93504f Mon Sep 17 00:00:00 2001 From: bernhard Date: Sat, 25 Nov 2023 17:59:27 +0100 Subject: [PATCH] Issue #205: Add support for translated country names Also show the flag in the country selection. Load language packs for the most popular languages --- Kernel/Config/Files/XML/Framework.xml | 7 ++ Kernel/Modules/AdminCustomerCompany.pm | 38 ++++---- Kernel/System/ReferenceData.pm | 126 +++++++++++++++++++------ bin/otobo.CheckModules.pl | 19 ++++ cpanfile | 69 ++++++++++++++ cpanfile.docker | 30 ++++++ 6 files changed, 244 insertions(+), 45 deletions(-) diff --git a/Kernel/Config/Files/XML/Framework.xml b/Kernel/Config/Files/XML/Framework.xml index 80ff438639..d9ff302859 100644 --- a/Kernel/Config/Files/XML/Framework.xml +++ b/Kernel/Config/Files/XML/Framework.xml @@ -3098,6 +3098,13 @@ You can log in via the following URL: + + Translate the country names in the country selection. The CLDR country codes will be stored. Needs Locale::CLDR and the relevant language packs. + Core::ReferenceData + + 0 + + Enables performance log (to log the page response time). It will affect the system performance. Frontend::Module###AdminPerformanceLog must be enabled. Core::PerformanceLog diff --git a/Kernel/Modules/AdminCustomerCompany.pm b/Kernel/Modules/AdminCustomerCompany.pm index 2d9874573f..f305d20164 100644 --- a/Kernel/Modules/AdminCustomerCompany.pm +++ b/Kernel/Modules/AdminCustomerCompany.pm @@ -16,8 +16,10 @@ package Kernel::Modules::AdminCustomerCompany; +use v5.24; use strict; use warnings; +use namespace::autoclean; # core modules use List::Util qw(any); @@ -554,6 +556,7 @@ sub _Edit { my ( $Self, %Param ) = @_; my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); + my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); $LayoutObject->Block( Name => 'Overview', @@ -571,9 +574,6 @@ sub _Edit { Data => \%Param, ); - # get config object - my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); - # send parameter ReadOnly to JS object $LayoutObject->AddJSData( Key => 'ReadOnly', @@ -661,16 +661,24 @@ sub _Edit { ); } - elsif ( $Entry->[0] =~ /^CustomerCompanyCountry/i ) { - my $OptionRequired = ''; - if ( $Entry->[4] ) { - $OptionRequired = 'Validate_Required'; - } - - # build Country string - my $CountryList = $Kernel::OM->Get('Kernel::System::ReferenceData')->CountryList(); + elsif ( $Entry->[0] =~ m/^CustomerCompanyCountry/i ) { + # build Country selection with English names $Block = 'Option'; + my $OptionRequired = $Entry->[4] ? 'Validate_Required' : ''; + my $CountryList; + if ( $ConfigObject->Get('ReferenceData::TranslatedCountryNames') ) { + + # Flag+Name => code + $CountryList = $Kernel::OM->Get('Kernel::System::ReferenceData')->TranslatedCountryList( + Language => $LayoutObject->{UserLanguage}, + ); + } + else { + + # English name => English name + $CountryList = $Kernel::OM->Get('Kernel::System::ReferenceData')->CountryList; + } $Param{Option} = $LayoutObject->BuildSelection( Data => $CountryList, PossibleNone => 1, @@ -681,14 +689,11 @@ sub _Edit { SelectedID => defined( $Param{ $Entry->[0] } ) ? $Param{ $Entry->[0] } : 1, ); } - elsif ( $Entry->[0] =~ /^ValidID/i ) { - my $OptionRequired = ''; - if ( $Entry->[4] ) { - $OptionRequired = 'Validate_Required'; - } + elsif ( $Entry->[0] =~ m/^ValidID/i ) { # build ValidID string $Block = 'Option'; + my $OptionRequired = $Entry->[4] ? 'Validate_Required' : ''; $Param{Option} = $LayoutObject->BuildSelection( Data => { $ValidObject->ValidList(), }, Name => $Entry->[0], @@ -754,6 +759,7 @@ sub _Edit { } } } + return 1; } diff --git a/Kernel/System/ReferenceData.pm b/Kernel/System/ReferenceData.pm index 077a7d41ad..36d9e18d14 100644 --- a/Kernel/System/ReferenceData.pm +++ b/Kernel/System/ReferenceData.pm @@ -19,6 +19,8 @@ package Kernel::System::ReferenceData; use v5.24; use strict; use warnings; +use namespace::autoclean; +use utf8; # core modules @@ -27,19 +29,44 @@ use Locale::Country qw(all_country_names); # OTOBO modules -our @ObjectDependencies = ( - 'Kernel::Config', - 'Kernel::System::Log', +our @ObjectDependencies = qw( + Kernel::Config + Kernel::System::Log + Kernel::System::Main ); +=for stopwords da CLDR + =head1 NAME Kernel::System::ReferenceData - ReferenceData lib =head1 DESCRIPTION -Contains reference data. For now, this is limited to just a list of ISO country -codes. +Contains reference data. For now, this is limited to: + +=over 4 + +=item ISO 3166-1 country codes and English names + +Currently there are 249 officially assigned codes. +Retired codes are not included. + +=item Translated country names from the Unicode CLDR + +The keys are two letter country codes. The codes are made up of: + +=over 4 + +=item the 249 officially assigned ISO 3166-1 codes + +=item exceptional reservations: Ascension Island, Clipperton Island, Diego Garcia, Ceuta and Melilla, Canary Islands, and Tristan da Cunha + +=item user-assigned temporary country code: Kosovo + +=back + +=back =head1 PUBLIC INTERFACE @@ -55,20 +82,21 @@ sub new { my ( $Type, %Param ) = @_; # allocate new hash for object - my $Self = {}; - bless( $Self, $Type ); - - return $Self; + return bless {}, $Type; } =head2 CountryList() return a list of countries as a hash reference. The countries are based on ISO -3166-2 and are provided by the Perl module Locale::Code::Country, or optionally +3166-1 and are provided by the Perl module Locale::Code::Country, or optionally from the SysConfig setting ReferenceData::OwnCountryList. - my $CountryList = $ReferenceDataObject->CountryList( - Result => 'CODE', # optional: returns CODE => Country pairs conform ISO 3166-2. + my $CountryName2Name = $ReferenceDataObject->CountryList() + +or + + my $CountryName2Code = $ReferenceDataObject->CountryList() + Result => 'CODE', # optional: returns CODE => Country pairs conform ISO 3166-1 alpha-2. ); =cut @@ -76,23 +104,19 @@ from the SysConfig setting ReferenceData::OwnCountryList. sub CountryList { my ( $Self, %Param ) = @_; - if ( !defined $Param{Result} || $Param{Result} ne 'CODE' ) { - $Param{Result} = undef; - } - - my $Countries = $Kernel::OM->Get('Kernel::Config')->Get('ReferenceData::OwnCountryList'); + # Determine whether the values of the result should be the codes + my $ReturnCodes = ( $Param{Result} // '' ) eq 'CODE'; - if ( $Param{Result} && $Countries ) { + my $OwnCountries = $Kernel::OM->Get('Kernel::Config')->Get('ReferenceData::OwnCountryList'); + if ($OwnCountries) { # return Code => Country pairs from SysConfig - return $Countries; - } - elsif ($Countries) { + return $OwnCountries if $ReturnCodes; # return Country => Country pairs from SysConfig - my %CountryJustNames = map { $_ => $_ } values %$Countries; + my %CountryName2Name = map { $_ => $_ } values $OwnCountries->%*; - return \%CountryJustNames; + return \%CountryName2Name; } # get the country list from Locale::Country @@ -105,21 +129,65 @@ sub CountryList { ); } - if ( $Param{Result} ) { + if ($ReturnCodes) { # return Code => Country pairs from ISO list - my %Countries; + my %CountryName2Code; for my $Country (@CountryNames) { - $Countries{$Country} = country2code( $Country, 1 ); + $CountryName2Code{$Country} = country2code( $Country, 1 ); } - return \%Countries; + return \%CountryName2Code; } # return Country => Country pairs from ISO list - my %CountryNames = map { $_ => $_ } @CountryNames; + my %CountryName2Name = map { $_ => $_ } @CountryNames; + + return \%CountryName2Name; +} + +=head2 TranslatedCountryList() + +returns a mapping of translated country names to two letter country codes. +The translated country name are prepended by their flag. +The data is provided by L. + + my $CountryName2Code = $ReferenceDataObject->TranslatedCountryList( + Language => 'de', + ); + +=cut + +sub TranslatedCountryList { + my ( $Self, %Param ) = @_; + + my $MainObject = $Kernel::OM->Get('Kernel::System::Main'); + + # Locale::CLDR is not required for OTOBO + return {} unless $MainObject->Require('Locale::CLDR'); + + # TODO: Check whether $Param{Language} is supported + + # For getting the country flags. + # See https://en.wikipedia.org/wiki/Regional_indicator_symbol + # This indicators are in a sequence: ord(🇩) = ord('🇦') - ord('A') + ord('D') + my $Base = ord('🇦') - ord('A'); + my %Letter2Indicator = map + { $_ => chr( $Base + ord($_) ) } + ( 'A' .. 'Z' ); + + my $Locale = Locale::CLDR->new( language_id => $Param{Language} ); + my $AllRegions = $Locale->all_regions(); # includes regions like '001' => World + my %Code2Name; + for my $Code ( grep { length $_ == 2 } keys $AllRegions->%* ) { + my $Flag = + join '', + map { $Letter2Indicator{$_} } + split //, $Code; + $Code2Name{$Code} = "$AllRegions->{$Code} $Flag"; + } - return \%CountryNames; + return \%Code2Name; } 1; diff --git a/bin/otobo.CheckModules.pl b/bin/otobo.CheckModules.pl index ae43e74921..1650ebaec7 100755 --- a/bin/otobo.CheckModules.pl +++ b/bin/otobo.CheckModules.pl @@ -1202,6 +1202,25 @@ =head1 DESCRIPTION }, ); +# Add CLDR language packs. It is not decided yet whether all 50 languages are added. +# So let's first go for the languages that have a translation quote of more than 80%. +# See https://translate.otobo.org/projects/otobo10/otobo/. +for my $Code (qw(De Nb Es Zh Pt Ar Hu Sr Ko Ru)) { + push @NeededModules, + { + Module => "Locale::CLDR::Locales::$Code", + Features => ['div:cldr'], + VersionRequired => '0.34.3', + Comment => 'localisation from the CLDR project', + InstTypes => { + aptget => undef, # not in any Debian package + emerge => undef, + zypper => undef, + ports => undef, + }, + }; +} + # Sanity check. for my $Module (@NeededModules) { die 'Module must be set!' unless defined $Module->{Module}; diff --git a/cpanfile b/cpanfile index c872f6dbc6..349833d449 100644 --- a/cpanfile +++ b/cpanfile @@ -203,6 +203,36 @@ feature 'div:cldr', 'Support for feature div:cldr' => sub { # localisation from the CLDR project requires 'Locale::CLDR', ">= 0.34.3"; + # localisation from the CLDR project + requires 'Locale::CLDR::Locales::De', ">= 0.34.3"; + + # localisation from the CLDR project + requires 'Locale::CLDR::Locales::Nb', ">= 0.34.3"; + + # localisation from the CLDR project + requires 'Locale::CLDR::Locales::Es', ">= 0.34.3"; + + # localisation from the CLDR project + requires 'Locale::CLDR::Locales::Zh', ">= 0.34.3"; + + # localisation from the CLDR project + requires 'Locale::CLDR::Locales::Pt', ">= 0.34.3"; + + # localisation from the CLDR project + requires 'Locale::CLDR::Locales::Ar', ">= 0.34.3"; + + # localisation from the CLDR project + requires 'Locale::CLDR::Locales::Hu', ">= 0.34.3"; + + # localisation from the CLDR project + requires 'Locale::CLDR::Locales::Sr', ">= 0.34.3"; + + # localisation from the CLDR project + requires 'Locale::CLDR::Locales::Ko', ">= 0.34.3"; + + # localisation from the CLDR project + requires 'Locale::CLDR::Locales::Ru', ">= 0.34.3"; + }; feature 'div:hanextra', 'Support for feature div:hanextra' => sub { @@ -356,6 +386,12 @@ feature 'optional', 'Support for feature optional' => sub { # localisation from the CLDR project requires 'Locale::CLDR', ">= 0.34.3"; + # localisation from the CLDR project + requires 'Locale::CLDR::Locales::De', ">= 0.34.3"; + + # localisation from the CLDR project + requires 'Locale::CLDR::Locales::Fr', ">= 0.34.3"; + # Required for Generic Interface XSLT mapping module. requires 'XML::LibXSLT'; @@ -395,6 +431,39 @@ feature 'optional', 'Support for feature optional' => sub { # module for manipulating .po entries requires 'Locale::PO'; +<<<<<<< HEAD +======= + # localisation from the CLDR project + requires 'Locale::CLDR::Locales::De', ">= 0.34.3"; + + # localisation from the CLDR project + requires 'Locale::CLDR::Locales::Nb', ">= 0.34.3"; + + # localisation from the CLDR project + requires 'Locale::CLDR::Locales::Es', ">= 0.34.3"; + + # localisation from the CLDR project + requires 'Locale::CLDR::Locales::Zh', ">= 0.34.3"; + + # localisation from the CLDR project + requires 'Locale::CLDR::Locales::Pt', ">= 0.34.3"; + + # localisation from the CLDR project + requires 'Locale::CLDR::Locales::Ar', ">= 0.34.3"; + + # localisation from the CLDR project + requires 'Locale::CLDR::Locales::Hu', ">= 0.34.3"; + + # localisation from the CLDR project + requires 'Locale::CLDR::Locales::Sr', ">= 0.34.3"; + + # localisation from the CLDR project + requires 'Locale::CLDR::Locales::Ko', ">= 0.34.3"; + + # localisation from the CLDR project + requires 'Locale::CLDR::Locales::Ru', ">= 0.34.3"; + +>>>>>>> 714e86bcc9 (mmmmmmmmmm) }; feature 'performance:redis', 'Support for feature performance:redis' => sub { diff --git a/cpanfile.docker b/cpanfile.docker index 53da9fe2b9..6b7ab8fcc7 100644 --- a/cpanfile.docker +++ b/cpanfile.docker @@ -197,6 +197,36 @@ requires 'Const::Fast'; # localisation from the CLDR project requires 'Locale::CLDR', ">= 0.34.3"; + # localisation from the CLDR project + requires 'Locale::CLDR::Locales::De', ">= 0.34.3"; + + # localisation from the CLDR project + requires 'Locale::CLDR::Locales::Nb', ">= 0.34.3"; + + # localisation from the CLDR project + requires 'Locale::CLDR::Locales::Es', ">= 0.34.3"; + + # localisation from the CLDR project + requires 'Locale::CLDR::Locales::Zh', ">= 0.34.3"; + + # localisation from the CLDR project + requires 'Locale::CLDR::Locales::Pt', ">= 0.34.3"; + + # localisation from the CLDR project + requires 'Locale::CLDR::Locales::Ar', ">= 0.34.3"; + + # localisation from the CLDR project + requires 'Locale::CLDR::Locales::Hu', ">= 0.34.3"; + + # localisation from the CLDR project + requires 'Locale::CLDR::Locales::Sr', ">= 0.34.3"; + + # localisation from the CLDR project + requires 'Locale::CLDR::Locales::Ko', ">= 0.34.3"; + + # localisation from the CLDR project + requires 'Locale::CLDR::Locales::Ru', ">= 0.34.3"; + # }; # Feature 'div:hanextra' is not needed for Docker