#
# 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 2 of the License, or (at your option) any later
-# version.
+# 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.
+# Koha is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License along
-# with Koha; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+# You should have received a copy of the GNU General Public License
+# along with Koha; if not, see <http://www.gnu.org/licenses>.
-use strict;
-use warnings;
+use Modern::Perl;
use C4::Context;
# WARNING: Any other tested YAML library fails to work properly in this
use YAML::Syck qw( Dump LoadFile );
use Locale::PO;
use FindBin qw( $Bin );
+use File::Basename;
+use File::Find;
+use File::Path qw( make_path );
+use File::Slurp;
+use File::Spec;
+use File::Temp qw( tempdir );
+use Template::Parser;
+use PPI;
$YAML::Syck::ImplicitTyping = 1;
+
+# Default file header for .po syspref files
+my $default_pref_po_header = Locale::PO->new(-msgid => '', -msgstr =>
+ "Project-Id-Version: PACKAGE VERSION\\n" .
+ "PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\\n" .
+ "Last-Translator: FULL NAME <EMAIL\@ADDRESS>\\n" .
+ "Language-Team: Koha Translate List <koha-translate\@lists.koha-community.org>\\n" .
+ "MIME-Version: 1.0\\n" .
+ "Content-Type: text/plain; charset=UTF-8\\n" .
+ "Content-Transfer-Encoding: 8bit\\n" .
+ "Plural-Forms: nplurals=2; plural=(n > 1);\\n"
+);
+
+
sub set_lang {
my ($self, $lang) = @_;
$self->{verbose} = $verbose;
$self->{process} = "$Bin/tmpl_process3.pl " . ($verbose ? '' : '-q');
$self->{path_po} = "$Bin/po";
- $self->{po} = {};
+ $self->{po} = { '' => $default_pref_po_header };
+ $self->{domain} = 'Koha';
+ $self->{cp} = `which cp`;
+ $self->{msgmerge} = `which msgmerge`;
+ $self->{msgfmt} = `which msgfmt`;
+ $self->{msginit} = `which msginit`;
+ $self->{xgettext} = `which xgettext`;
+ $self->{sed} = `which sed`;
+ chomp $self->{cp};
+ chomp $self->{msgmerge};
+ chomp $self->{msgfmt};
+ chomp $self->{msginit};
+ chomp $self->{xgettext};
+ chomp $self->{sed};
+
+ unless ($self->{xgettext}) {
+ die "Missing 'xgettext' executable. Have you installed the gettext package?\n";
+ }
# Get all .pref file names
opendir my $fh, $self->{path_pref_en};
- my @pref_files = grep { /.pref/ } readdir($fh);
+ my @pref_files = grep { /\.pref$/ } readdir($fh);
close $fh;
$self->{pref_files} = \@pref_files;
# Get all available language codes
opendir $fh, $self->{path_po};
- my @langs = map { ($_) =~ /(.*)-i-opac/ }
- grep { $_ =~ /.*-opac-/ } readdir($fh);
+ my @langs = map { ($_) =~ /(.*)-pref/ }
+ grep { $_ =~ /.*-pref/ } readdir($fh);
closedir $fh;
$self->{langs} = \@langs;
# Map for both interfaces opac/intranet
- $self->{interface} = {
- opac => {
- dir => $context->config('opachtdocs') . '/prog',
- suffix => '-i-opac-t-prog-v-3006000.po',
- },
- intranet => {
+ my $opachtdocs = $context->config('opachtdocs');
+ $self->{interface} = [
+ {
+ name => 'Intranet prog UI',
dir => $context->config('intrahtdocs') . '/prog',
- suffix => '-i-staff-t-prog-v-3006000.po',
+ suffix => '-staff-prog.po',
+ },
+ ];
+
+ # OPAC themes
+ opendir my $dh, $context->config('opachtdocs');
+ for my $theme ( grep { not /^\.|lib|xslt/ } readdir($dh) ) {
+ push @{$self->{interface}}, {
+ name => "OPAC $theme",
+ dir => "$opachtdocs/$theme",
+ suffix => "-opac-$theme.po",
+ };
+ }
+
+ # MARC flavours (hardcoded list)
+ for ( "MARC21", "UNIMARC", "NORMARC" ) {
+ # search for strings on staff & opac marc files
+ my $dirs = $context->config('intrahtdocs') . '/prog';
+ opendir $fh, $context->config('opachtdocs');
+ for ( grep { not /^\.|\.\.|lib$|xslt/ } readdir($fh) ) {
+ $dirs .= ' ' . "$opachtdocs/$_";
}
- };
+ push @{$self->{interface}}, {
+ name => "$_",
+ dir => $dirs,
+ suffix => "-marc-$_.po",
+ };
+ }
bless $self, $class;
}
}
}
}
- elsif ( $element && $pref_name ) {
+ elsif ( $element ) {
$self->po_append( $self->{file} . "#$pref_name# $element", $comment );
}
}
}
}
}
- elsif ( $element && $pref_name ) {
+ elsif ( $element ) {
my $id = $self->{file} . "#$pref_name# $element";
my $text = $self->get_trans_text( $id );
$p->[$i] = $text if $text;
sub save_po {
my $self = shift;
+
+ # Create file header if it doesn't already exist
+ my $po = $self->{po};
+ $po->{''} ||= $default_pref_po_header;
+
# Write .po entries into a file put in Koha standard po directory
- Locale::PO->save_file_fromhash( $self->po_filename, $self->{po} );
- print "Saved in file: ", $self->po_filename, "\n" if $self->{verbose};
+ Locale::PO->save_file_fromhash( $self->po_filename, $po );
+ say "Saved in file: ", $self->po_filename if $self->{verbose};
}
my $id = $self->{file} . " $section";
my $text = $self->get_trans_text($id);
my $nsection = $text ? $text : $section;
- $ntab->{$nsection} = $tab_content->{$section};
+ if( exists $ntab->{$nsection} ) {
+ # When translations collide (see BZ 18634)
+ push @{$ntab->{$nsection}}, @{$tab_content->{$section}};
+ } else {
+ $ntab->{$nsection} = $tab_content->{$section};
+ }
}
$pref->{$tab} = $ntab;
}
sub install_tmpl {
- my $self = shift;
- print "Install templates\n" if $self->{verbose};
- while ( my ($interface, $tmpl) = each %{$self->{interface}} ) {
- print
- " Install templates '$interface\n",
- " From: $tmpl->{dir}/en/\n",
- " To : $tmpl->{dir}/$self->{lang}\n",
- " With: $self->{path_po}/$self->{lang}$tmpl->{suffix}\n"
+ my ($self, $files) = @_;
+ say "Install templates" if $self->{verbose};
+ for my $trans ( @{$self->{interface}} ) {
+ my @t_dirs = split(" ", $trans->{dir});
+ for my $t_dir ( @t_dirs ) {
+ my @files = @$files;
+ my @nomarc = ();
+ print
+ " Install templates '$trans->{name}'\n",
+ " From: $t_dir/en/\n",
+ " To : $t_dir/$self->{lang}\n",
+ " With: $self->{path_po}/$self->{lang}$trans->{suffix}\n"
if $self->{verbose};
- my $lang_dir = "$tmpl->{dir}/$self->{lang}";
- mkdir $lang_dir unless -d $lang_dir;
- system
- "$self->{process} install " .
- "-i $tmpl->{dir}/en/ " .
- "-o $tmpl->{dir}/$self->{lang} ".
- "-s $self->{path_po}/$self->{lang}$tmpl->{suffix} -r"
+
+ my $trans_dir = "$t_dir/en/";
+ my $lang_dir = "$t_dir/$self->{lang}";
+ $lang_dir =~ s|/en/|/$self->{lang}/|;
+ mkdir $lang_dir unless -d $lang_dir;
+ # if installing MARC po file, only touch corresponding files
+ my $marc = ( $trans->{name} =~ /MARC/ )?"-m \"$trans->{name}\"":""; # for MARC translations
+ # if not installing MARC po file, ignore all MARC files
+ @nomarc = ( 'marc21', 'unimarc', 'normarc' ) if ( $trans->{name} !~ /MARC/ ); # hardcoded MARC variants
+
+ system
+ "$self->{process} install " .
+ "-i $trans_dir " .
+ "-o $lang_dir ".
+ "-s $self->{path_po}/$self->{lang}$trans->{suffix} -r " .
+ "$marc " .
+ ( @files ? ' -f ' . join ' -f ', @files : '') .
+ ( @nomarc ? ' -n ' . join ' -n ', @nomarc : '');
+ }
}
}
sub update_tmpl {
- my $self = shift;
+ my ($self, $files) = @_;
- print "Update templates\n" if $self->{verbose};
- while ( my ($interface, $tmpl) = each %{$self->{interface}} ) {
+ say "Update templates" if $self->{verbose};
+ for my $trans ( @{$self->{interface}} ) {
+ my @files = @$files;
+ my @nomarc = ();
print
- " Update templates '$interface'\n",
- " From: $tmpl->{dir}/en/\n",
- " To : $self->{path_po}/$self->{lang}$tmpl->{suffix}\n"
+ " Update templates '$trans->{name}'\n",
+ " From: $trans->{dir}/en/\n",
+ " To : $self->{path_po}/$self->{lang}$trans->{suffix}\n"
if $self->{verbose};
- my $lang_dir = "$tmpl->{dir}/$self->{lang}";
- mkdir $lang_dir unless -d $lang_dir;
+
+ my $trans_dir = join("/en/ -i ",split(" ",$trans->{dir}))."/en/"; # multiple source dirs
+ # if processing MARC po file, only use corresponding files
+ my $marc = ( $trans->{name} =~ /MARC/ )?"-m \"$trans->{name}\"":""; # for MARC translations
+ # if not processing MARC po file, ignore all MARC files
+ @nomarc = ( 'marc21', 'unimarc', 'normarc' ) if ( $trans->{name} !~ /MARC/ ); # hardcoded MARC variants
+
system
"$self->{process} update " .
- "-i $tmpl->{dir}/en/ " .
- "-s $self->{path_po}/$self->{lang}$tmpl->{suffix} -r"
+ "-i $trans_dir " .
+ "-s $self->{path_po}/$self->{lang}$trans->{suffix} -r " .
+ "$marc " .
+ ( @files ? ' -f ' . join ' -f ', @files : '') .
+ ( @nomarc ? ' -n ' . join ' -n ', @nomarc : '');
}
}
sub create_prefs {
my $self = shift;
+ if ( -e $self->po_filename ) {
+ say "Preferences .po file already exists. Delete it if you want to recreate it.";
+ return;
+ }
$self->get_po_from_prefs();
$self->save_po();
}
sub create_tmpl {
- my $self = shift;
+ my ($self, $files) = @_;
- print "Create templates\n" if $self->{verbose};
- while ( my ($interface, $tmpl) = each %{$self->{interface}} ) {
+ say "Create templates\n" if $self->{verbose};
+ for my $trans ( @{$self->{interface}} ) {
+ my @files = @$files;
+ my @nomarc = ();
print
- " Create templates .po files for '$interface'\n",
- " From: $tmpl->{dir}/en/\n",
- " To : $self->{path_po}/$self->{lang}$tmpl->{suffix}\n"
+ " Create templates .po files for '$trans->{name}'\n",
+ " From: $trans->{dir}/en/\n",
+ " To : $self->{path_po}/$self->{lang}$trans->{suffix}\n"
if $self->{verbose};
+
+ my $trans_dir = join("/en/ -i ",split(" ",$trans->{dir}))."/en/"; # multiple source dirs
+ # if processing MARC po file, only use corresponding files
+ my $marc = ( $trans->{name} =~ /MARC/ )?"-m \"$trans->{name}\"":""; # for MARC translations
+ # if not processing MARC po file, ignore all MARC files
+ @nomarc = ( 'marc21', 'unimarc', 'normarc' ) if ( $trans->{name} !~ /MARC/ ); # hardcoded MARC variants
+
system
"$self->{process} create " .
- "-i $tmpl->{dir}/en/ " .
- "-s $self->{path_po}/$self->{lang}$tmpl->{suffix} -r"
+ "-i $trans_dir " .
+ "-s $self->{path_po}/$self->{lang}$trans->{suffix} -r " .
+ "$marc " .
+ ( @files ? ' -f ' . join ' -f ', @files : '') .
+ ( @nomarc ? ' -n ' . join ' -n ', @nomarc : '');
+ }
+}
+
+sub locale_name {
+ my $self = shift;
+
+ my ($language, $region, $country) = split /-/, $self->{lang};
+ $country //= $region;
+ my $locale = $language;
+ if ($country && length($country) == 2) {
+ $locale .= '_' . $country;
}
+
+ return $locale;
}
+sub create_messages {
+ my $self = shift;
-sub install {
+ my $pot = "$Bin/$self->{domain}.pot";
+ my $po = "$self->{path_po}/$self->{lang}-messages.po";
+
+ unless ( -f $pot ) {
+ $self->extract_messages();
+ }
+
+ say "Create messages ($self->{lang})" if $self->{verbose};
+ my $locale = $self->locale_name();
+ system "$self->{msginit} -i $pot -o $po -l $locale --no-translator";
+
+ # If msginit failed to correctly set Plural-Forms, set a default one
+ system "$self->{sed} --in-place $po "
+ . "--expression='s/Plural-Forms: nplurals=INTEGER; plural=EXPRESSION/Plural-Forms: nplurals=2; plural=(n != 1)/'";
+}
+
+sub update_messages {
+ my $self = shift;
+
+ my $pot = "$Bin/$self->{domain}.pot";
+ my $po = "$self->{path_po}/$self->{lang}-messages.po";
+
+ unless ( -f $pot ) {
+ $self->extract_messages();
+ }
+
+ if ( -f $po ) {
+ say "Update messages ($self->{lang})" if $self->{verbose};
+ system "$self->{msgmerge} --quiet -U $po $pot";
+ } else {
+ $self->create_messages();
+ }
+}
+
+sub extract_messages_from_templates {
+ my ($self, $tempdir, $type, @files) = @_;
+
+ my $htdocs = $type eq 'intranet' ? 'intrahtdocs' : 'opachtdocs';
+ my $dir = $self->{context}->config($htdocs);
+ my @keywords = qw(t tx tn txn tnx tp tpx tnp tnpx);
+ my $parser = Template::Parser->new();
+
+ foreach my $file (@files) {
+ say "Extract messages from $file" if $self->{verbose};
+ my $template = read_file(File::Spec->catfile($dir, $file));
+
+ # No need to process a file that doesn't use the i18n.inc file.
+ next unless $template =~ /i18n\.inc/;
+
+ my $data = $parser->parse($template);
+ unless ($data) {
+ warn "Error at $file : " . $parser->error();
+ next;
+ }
+
+ my $destfile = $type eq 'intranet' ?
+ File::Spec->catfile($tempdir, 'koha-tmpl', 'intranet-tmpl', $file) :
+ File::Spec->catfile($tempdir, 'koha-tmpl', 'opac-tmpl', $file);
+
+ make_path(dirname($destfile));
+ open my $fh, '>', $destfile;
+
+ my @blocks = ($data->{BLOCK}, values %{ $data->{DEFBLOCKS} });
+ foreach my $block (@blocks) {
+ my $document = PPI::Document->new(\$block);
+
+ # [% t('foo') %] is compiled to
+ # $output .= $stash->get(['t', ['foo']]);
+ # We try to find all nodes corresponding to keyword (here 't')
+ my $nodes = $document->find(sub {
+ my ($topnode, $element) = @_;
+
+ # Filter out non-valid keywords
+ return 0 unless ($element->isa('PPI::Token::Quote::Single'));
+ return 0 unless (grep {$element->content eq qq{'$_'}} @keywords);
+
+ # keyword (e.g. 't') should be the first element of the arrayref
+ # passed to $stash->get()
+ return 0 if $element->sprevious_sibling;
+
+ return 0 unless $element->snext_sibling
+ && $element->snext_sibling->snext_sibling
+ && $element->snext_sibling->snext_sibling->isa('PPI::Structure::Constructor');
+
+ # Check that it's indeed a call to $stash->get()
+ my $statement = $element->statement->parent->statement->parent->statement;
+ return 0 unless grep { $_->isa('PPI::Token::Symbol') && $_->content eq '$stash' } $statement->children;
+ return 0 unless grep { $_->isa('PPI::Token::Operator') && $_->content eq '->' } $statement->children;
+ return 0 unless grep { $_->isa('PPI::Token::Word') && $_->content eq 'get' } $statement->children;
+
+ return 1;
+ });
+
+ next unless $nodes;
+
+ # Write the Perl equivalent of calls to t* functions family, so
+ # xgettext can extract the strings correctly
+ foreach my $node (@$nodes) {
+ my @args = map {
+ $_->significant && !$_->isa('PPI::Token::Operator') ? $_->content : ()
+ } $node->snext_sibling->snext_sibling->find_first('PPI::Statement')->children;
+
+ my $keyword = $node->content;
+ $keyword =~ s/^'t(.*)'$/__$1/;
+
+ # Only keep required args to have a clean output
+ my @required_args = shift @args;
+ push @required_args, shift @args if $keyword =~ /n/;
+ push @required_args, shift @args if $keyword =~ /p/;
+
+ say $fh "$keyword(" . join(', ', @required_args) . ");";
+ }
+
+ }
+
+ close $fh;
+ }
+
+ return $tempdir;
+}
+
+sub extract_messages {
+ my $self = shift;
+
+ say "Extract messages into POT file" if $self->{verbose};
+
+ my $intranetdir = $self->{context}->config('intranetdir');
+ my $opacdir = $self->{context}->config('opacdir');
+
+ # Find common ancestor directory
+ my @intranetdirs = File::Spec->splitdir($intranetdir);
+ my @opacdirs = File::Spec->splitdir($opacdir);
+ my @basedirs;
+ while (@intranetdirs and @opacdirs) {
+ my ($dir1, $dir2) = (shift @intranetdirs, shift @opacdirs);
+ last if $dir1 ne $dir2;
+ push @basedirs, $dir1;
+ }
+ my $basedir = File::Spec->catdir(@basedirs);
+
+ my @files_to_scan;
+ my @directories_to_scan = ('.');
+ my @blacklist = map { File::Spec->catdir(@intranetdirs, $_) } qw(blib koha-tmpl skel tmp t);
+ while (@directories_to_scan) {
+ my $dir = shift @directories_to_scan;
+ opendir DIR, File::Spec->catdir($basedir, $dir) or die "Unable to open $dir: $!";
+ foreach my $entry (readdir DIR) {
+ next if $entry =~ /^\./;
+ my $relentry = File::Spec->catfile($dir, $entry);
+ my $abspath = File::Spec->catfile($basedir, $relentry);
+ if (-d $abspath and not grep /^$relentry$/, @blacklist) {
+ push @directories_to_scan, $relentry;
+ } elsif (-f $abspath and $relentry =~ /\.(pl|pm)$/) {
+ push @files_to_scan, $relentry;
+ }
+ }
+ }
+
+ my $intrahtdocs = $self->{context}->config('intrahtdocs');
+ my $opachtdocs = $self->{context}->config('opachtdocs');
+
+ my @intranet_tt_files;
+ find(sub {
+ if ($File::Find::dir =~ m|/en/| && $_ =~ m/\.(tt|inc)$/) {
+ my $filename = $File::Find::name;
+ $filename =~ s|^$intrahtdocs/||;
+ push @intranet_tt_files, $filename;
+ }
+ }, $intrahtdocs);
+
+ my @opac_tt_files;
+ find(sub {
+ if ($File::Find::dir =~ m|/en/| && $_ =~ m/\.(tt|inc)$/) {
+ my $filename = $File::Find::name;
+ $filename =~ s|^$opachtdocs/||;
+ push @opac_tt_files, $filename;
+ }
+ }, $opachtdocs);
+
+ my $tempdir = tempdir('Koha-translate-XXXX', TMPDIR => 1, CLEANUP => 1);
+ $self->extract_messages_from_templates($tempdir, 'intranet', @intranet_tt_files);
+ $self->extract_messages_from_templates($tempdir, 'opac', @opac_tt_files);
+
+ @intranet_tt_files = map { File::Spec->catfile('koha-tmpl', 'intranet-tmpl', $_) } @intranet_tt_files;
+ @opac_tt_files = map { File::Spec->catfile('koha-tmpl', 'opac-tmpl', $_) } @opac_tt_files;
+ my @tt_files = grep { -e File::Spec->catfile($tempdir, $_) } @intranet_tt_files, @opac_tt_files;
+
+ push @files_to_scan, @tt_files;
+
+ my $xgettext_cmd = "$self->{xgettext} --force-po -L Perl --from-code=UTF-8 "
+ . "--package-name=Koha --package-version='' "
+ . "-k -k__ -k__x -k__n:1,2 -k__nx:1,2 -k__xn:1,2 -k__p:1c,2 "
+ . "-k__px:1c,2 -k__np:1c,2,3 -k__npx:1c,2,3 -kN__ -kN__n:1,2 "
+ . "-kN__p:1c,2 -kN__np:1c,2,3 "
+ . "-o $Bin/$self->{domain}.pot -D $tempdir -D $basedir";
+ $xgettext_cmd .= " $_" foreach (@files_to_scan);
+
+ if (system($xgettext_cmd) != 0) {
+ die "system call failed: $xgettext_cmd";
+ }
+
+ my $replace_charset_cmd = "$self->{sed} --in-place " .
+ "$Bin/$self->{domain}.pot " .
+ "--expression='s/charset=CHARSET/charset=UTF-8/'";
+ if (system($replace_charset_cmd) != 0) {
+ die "system call failed: $replace_charset_cmd";
+ }
+}
+
+sub install_messages {
+ my ($self) = @_;
+
+ my $locale = $self->locale_name();
+ my $modir = "$self->{path_po}/$locale/LC_MESSAGES";
+ my $pofile = "$self->{path_po}/$self->{lang}-messages.po";
+ my $mofile = "$modir/$self->{domain}.mo";
+
+ if ( not -f $pofile ) {
+ $self->create_messages();
+ }
+ say "Install messages ($locale)" if $self->{verbose};
+ make_path($modir);
+ system "$self->{msgfmt} -o $mofile $pofile";
+}
+
+sub remove_pot {
my $self = shift;
+
+ unlink "$Bin/$self->{domain}.pot";
+}
+
+sub install {
+ my ($self, $files) = @_;
return unless $self->{lang};
- $self->install_tmpl() unless $self->{pref_only};
+ $self->install_tmpl($files) unless $self->{pref_only};
$self->install_prefs();
+ $self->install_messages();
+ $self->remove_pot();
}
sub get_all_langs {
my $self = shift;
opendir( my $dh, $self->{path_po} );
- my @files = grep { $_ =~ /-i-opac-t-prog-v-3006000.po$/ }
+ my @files = grep { $_ =~ /-pref.po$/ }
readdir $dh;
- @files = map { $_ =~ s/-i-opac-t-prog-v-3006000.po$//; $_ } @files;
+ @files = map { $_ =~ s/-pref.po$//; $_ } @files;
}
sub update {
- my $self = shift;
+ my ($self, $files) = @_;
my @langs = $self->{lang} ? ($self->{lang}) : $self->get_all_langs();
for my $lang ( @langs ) {
$self->set_lang( $lang );
- $self->update_tmpl() unless $self->{pref_only};
+ $self->update_tmpl($files) unless $self->{pref_only};
$self->update_prefs();
+ $self->update_messages();
}
+ $self->remove_pot();
}
sub create {
- my $self = shift;
+ my ($self, $files) = @_;
return unless $self->{lang};
- $self->create_tmpl() unless $self->{pref_only};
+ $self->create_tmpl($files) unless $self->{pref_only};
$self->create_prefs();
+ $self->create_messages();
+ $self->remove_pot();
}
=item translate create F<lang>
-Create 3 .po files in F<po> subdirectory: (1) from opac pages templates, (2)
-intranet templates, and (3) from preferences.
+Create 4 kinds of .po files in F<po> subdirectory:
+(1) one from each theme on opac pages templates,
+(2) intranet templates,
+(3) preferences, and
+(4) one for each MARC dialect.
+
=over
-=item F<lang>-opac.po
+=item F<lang>-opac-{theme}.po
Contains extracted text from english (en) OPAC templates found in
-<KOHA_ROOT>/koha-tmpl/opac-tmpl/prog/en/ directory.
+<KOHA_ROOT>/koha-tmpl/opac-tmpl/{theme}/en/ directory.
-=item F<lang>-intranet.po
+=item F<lang>-staff-prog.po
Contains extracted text from english (en) intranet templates found in
<KOHA_ROOT>/koha-tmpl/intranet-tmpl/prog/en/ directory.
located in <KOHA_ROOT>/koha-tmpl/intranet-tmpl/prog/en/admin/preferences
directory.
+=item F<lang>-marc-{MARC}.po
+
+Contains extracted text from english (en) files from opac and intranet,
+related with MARC dialects.
+
=back
=item pref-trans update F<lang>