]> git.vanrenterghem.biz Git - git.ikiwiki.info.git/blobdiff - IkiWiki/Plugin/po.pm
(no commit message)
[git.ikiwiki.info.git] / IkiWiki / Plugin / po.pm
index 434d9cf4f1a640436b0c6818fae22fbac1070fc9..903a9500f5dc8482b52860ff6b212dbb296d641b 100644 (file)
@@ -10,7 +10,12 @@ use warnings;
 use strict;
 use IkiWiki 3.00;
 use Encode;
-use Locale::Po4a::Common qw(nowrapi18n !/.*/);
+eval q{use Locale::Po4a::Common qw(nowrapi18n !/.*/)};
+if ($@) {
+       print STDERR gettext("warning: Old po4a detected! Recommend upgrade to 0.35.")."\n";
+       eval q{use Locale::Po4a::Common qw(!/.*/)};
+       die $@ if $@;
+}
 use Locale::Po4a::Chooser;
 use Locale::Po4a::Po;
 use File::Basename;
@@ -46,16 +51,20 @@ sub import {
        hook(type => "formbuilder_setup", id => "po", call => \&formbuilder_setup, last => 1);
        hook(type => "formbuilder", id => "po", call => \&formbuilder);
 
-       $origsubs{'bestlink'}=\&IkiWiki::bestlink;
-       inject(name => "IkiWiki::bestlink", call => \&mybestlink);
-       $origsubs{'beautify_urlpath'}=\&IkiWiki::beautify_urlpath;
-       inject(name => "IkiWiki::beautify_urlpath", call => \&mybeautify_urlpath);
-       $origsubs{'targetpage'}=\&IkiWiki::targetpage;
-       inject(name => "IkiWiki::targetpage", call => \&mytargetpage);
-       $origsubs{'urlto'}=\&IkiWiki::urlto;
-       inject(name => "IkiWiki::urlto", call => \&myurlto);
-       $origsubs{'cgiurl'}=\&IkiWiki::cgiurl;
-       inject(name => "IkiWiki::cgiurl", call => \&mycgiurl);
+       if (! %origsubs) {
+               $origsubs{'bestlink'}=\&IkiWiki::bestlink;
+               inject(name => "IkiWiki::bestlink", call => \&mybestlink);
+               $origsubs{'beautify_urlpath'}=\&IkiWiki::beautify_urlpath;
+               inject(name => "IkiWiki::beautify_urlpath", call => \&mybeautify_urlpath);
+               $origsubs{'targetpage'}=\&IkiWiki::targetpage;
+               inject(name => "IkiWiki::targetpage", call => \&mytargetpage);
+               $origsubs{'urlto'}=\&IkiWiki::urlto;
+               inject(name => "IkiWiki::urlto", call => \&myurlto);
+               $origsubs{'cgiurl'}=\&IkiWiki::cgiurl;
+               inject(name => "IkiWiki::cgiurl", call => \&mycgiurl);
+               $origsubs{'rootpage'}=\&IkiWiki::rootpage;
+               inject(name => "IkiWiki::rootpage", call => \&myrootpage);
+       }
 }
 
 
@@ -78,7 +87,8 @@ sub getsetup () {
        return
                plugin => {
                        safe => 0,
-                       rebuild => 1,
+                       rebuild => 1, # format plugin
+                       section => "format",
                },
                po_master_language => {
                        type => "string",
@@ -94,7 +104,7 @@ sub getsetup () {
                        type => "string",
                        example => {
                                'fr' => 'Français',
-                               'es' => 'Castellano',
+                               'es' => 'Español',
                                'de' => 'Deutsch'
                        },
                        description => "slave languages (PO files)",
@@ -125,6 +135,7 @@ sub checkconfig () {
                                      $field, 'po'));
                }
        }
+       delete $config{po_slave_languages}{$config{po_master_language}{code}};;
 
        map {
                islanguagecode($_)
@@ -166,7 +177,8 @@ sub checkconfig () {
                if ($config{po_master_language}{code} ne 'en') {
                        # Add underlay containing translated source files
                        # for the master language.
-                       add_underlay("locale/$config{po_master_language}{code}/$underlay");
+                       add_underlay("locale/$config{po_master_language}{code}/$underlay")
+                               if -d "$config{underlaydirbase}/locale/$config{po_master_language}{code}/$underlay";
                }
        }
 }
@@ -199,10 +211,7 @@ sub scan (@) {
        if (istranslation($page)) {
                foreach my $destpage (@{$links{$page}}) {
                        if (istranslatable($destpage)) {
-                               # replace one occurence of $destpage in $links{$page}
-                               # (we only want to replace the one that was added by
-                               # IkiWiki::Plugin::link::scan, other occurences may be
-                               # there for other reasons)
+                               # replace the occurence of $destpage in $links{$page}
                                for (my $i=0; $i<@{$links{$page}}; $i++) {
                                        if (@{$links{$page}}[$i] eq $destpage) {
                                                @{$links{$page}}[$i] = $destpage . '.' . lang($page);
@@ -279,17 +288,16 @@ sub pagetemplate (@) {
                map add_depends($page, $_), (values %{otherlanguages($page)});
        }
        if ($config{discussion} && istranslation($page)) {
-               my $discussionlink=gettext("discussion");
-               if ($page !~ /.*\/\Q$discussionlink\E$/i &&
+               if ($page !~ /.*\/\Q$config{discussionpage}\E$/i &&
                   (length $config{cgiurl} ||
-                   exists $links{$masterpage."/".$discussionlink})) {
+                   exists $links{$masterpage."/".lc($config{discussionpage})})) {
                        $template->param('discussionlink' => htmllink(
                                $page,
                                $destpage,
-                               $masterpage . '/' . gettext("Discussion"),
+                               $masterpage . '/' . $config{discussionpage},
                                noimageinline => 1,
                                forcesubpage => 0,
-                               linktext => gettext("Discussion"),
+                               linktext => $config{discussionpage},
                ));
                }
        }
@@ -301,7 +309,10 @@ sub pagetemplate (@) {
            && $masterpage eq "index") {
                $template->param('parentlinks' => []);
        }
-} # }}}
+       if (ishomepage($page) && $template->query(name => "title")) {
+               $template->param(title => $config{wikiname});
+       }
+}
 
 # Add the renamed page translations to the list of to-be-renamed pages.
 sub renamepages (@) {
@@ -376,49 +387,49 @@ sub change (@) {
            && exists $config{meta_overrides_page_title}
            && defined $config{meta_overrides_page_title}
            && $config{meta_overrides_page_title}) {
-               debug(sprintf(gettext("re-rendering all pages to fix meta titles")));
+               debug(sprintf(gettext("rebuilding all pages to fix meta titles")));
                resetalreadyfiltered();
                require IkiWiki::Render;
                foreach my $file (@rendered) {
-                       debug(sprintf(gettext("rendering %s"), $file));
-                       IkiWiki::render($file);
+                       IkiWiki::render($file, sprintf(gettext("building %s"), $file));
                }
        }
 
        my $updated_po_files=0;
 
        # Refresh/create POT and PO files as needed.
-       # (But avoid doing so if they are in an underlay directory.)
        foreach my $file (grep {istranslatablefile($_)} @rendered) {
                my $masterfile=srcfile($file);
                my $page=pagename($file);
                my $updated_pot_file=0;
+
+               # Avoid touching underlay files.
+               next if $masterfile ne "$config{srcdir}/$file";
+
                # Only refresh POT file if it does not exist, or if
-               # $pagesources{$page} was changed: don't if only the HTML was
+               # the source was changed: don't if only the HTML was
                # refreshed, e.g. because of a dependency.
-               if ($masterfile eq "$config{srcdir}/$file" &&
-                  ((grep { $_ eq $pagesources{$page} } @origneedsbuild)
-                   || ! -e potfile($masterfile))) {
+               if ((grep { $_ eq $pagesources{$page} } @origneedsbuild) ||
+                   ! -e potfile($masterfile)) {
                        refreshpot($masterfile);
                        $updated_pot_file=1;
                }
                my @pofiles;
                foreach my $po (pofiles($masterfile)) {
-                       next if ! $updated_pot_file && -e $po;
+                       next if ! $updated_pot_file && -e $po;
                        next if grep { $po=~/\Q$_\E/ } @{$config{underlaydirs}};
                        push @pofiles, $po;
                }
                if (@pofiles) {
                        refreshpofiles($masterfile, @pofiles);
-                       map { IkiWiki::rcs_add($_) } @pofiles if $config{rcs};
+                       map { s/^\Q$config{srcdir}\E\/*//; IkiWiki::rcs_add($_) } @pofiles if $config{rcs};
                        $updated_po_files=1;
                }
        }
 
        if ($updated_po_files) {
                commit_and_refresh(
-                       gettext("updated PO files"),
-                       "IkiWiki::Plugin::po::change");
+                       gettext("updated PO files"));
        }
 }
 
@@ -525,10 +536,23 @@ sub formbuilder (@) {
        if ($form->field("do") eq "create") {
                foreach my $field ($form->field) {
                        next unless "$field" eq "type";
-                       if ($field->type eq 'select') {
-                               # remove po from the list of types
-                               my @types = grep { $_ ne 'po' } $field->options;
-                               $field->options(\@types) if @types;
+                       next unless $field->type eq 'select';
+                       my $orig_value = $field->value;
+                       # remove po from the list of types
+                       my @types = grep { $_->[0] ne 'po' } $field->options;
+                       $field->options(\@types) if @types;
+                       # favor the type of linking page's masterpage
+                       if ($orig_value eq 'po') {
+                               my ($from, $type);
+                               if (defined $form->field('from')) {
+                                       ($from)=$form->field('from')=~/$config{wiki_file_regexp}/;
+                                       $from = masterpage($from);
+                               }
+                               if (defined $from && exists $pagesources{$from}) {
+                                       $type=pagetype($pagesources{$from});
+                               }
+                               $type=$config{default_pageext} unless defined $type;
+                               $field->value($type) ;
                        }
                }
        }
@@ -543,11 +567,16 @@ sub mybestlink ($$) {
        my $page=shift;
        my $link=shift;
 
+       return $origsubs{'bestlink'}->($page, $link)
+               if defined $config{po_link_to} && $config{po_link_to} eq "default";
+
        my $res=$origsubs{'bestlink'}->(masterpage($page), $link);
+       my @caller = caller(1);
        if (length $res
-           && ($config{po_link_to} eq "current" || $config{po_link_to} eq "negotiated")
            && istranslatable($res)
-           && istranslation($page)) {
+           && istranslation($page)
+           &&  !(exists $caller[3] && defined $caller[3]
+                 && ($caller[3] eq "IkiWiki::PageSpec::match_link"))) {
                return $res . "." . lang($page);
        }
        return $res;
@@ -557,7 +586,7 @@ sub mybeautify_urlpath ($) {
        my $url=shift;
 
        my $res=$origsubs{'beautify_urlpath'}->($url);
-       if ($config{po_link_to} eq "negotiated") {
+       if (defined $config{po_link_to} && $config{po_link_to} eq "negotiated") {
                $res =~ s!/\Qindex.$config{po_master_language}{code}.$config{htmlext}\E$!/!;
                $res =~ s!/\Qindex.$config{htmlext}\E$!/!;
                map {
@@ -598,17 +627,21 @@ sub myurlto ($$;$) {
        # so that one is redirected to the just-edited page rather than to the
        # negociated translation; to prevent unnecessary fiddling with caller/inject,
        # we only do so when our beautify_urlpath would actually do what we want to
-       # avoid, i.e. when po_link_to = negotiated
+       # avoid, i.e. when po_link_to = negotiated.
+       # also avoid doing so when run by cgi_goto, so that the links on recentchanges
+       # page actually lead to the exact page they pretend to.
        if ($config{po_link_to} eq "negotiated") {
                my @caller = caller(1);
-               my $run_by_editpage = 0;
-               $run_by_editpage = 1 if (exists $caller[3] && defined $caller[3]
-                                        && $caller[3] eq "IkiWiki::cgi_editpage");
+               my $use_orig = 0;
+               $use_orig = 1 if (exists $caller[3] && defined $caller[3]
+                                && ($caller[3] eq "IkiWiki::cgi_editpage" ||
+                                    $caller[3] eq "IkiWiki::Plugin::goto::cgi_goto")
+                                );
                inject(name => "IkiWiki::beautify_urlpath", call => $origsubs{'beautify_urlpath'})
-                       if $run_by_editpage;
+                       if $use_orig;
                my $res = $origsubs{'urlto'}->($to,$from,$absolute);
                inject(name => "IkiWiki::beautify_urlpath", call => \&mybeautify_urlpath)
-                       if $run_by_editpage;
+                       if $use_orig;
                return $res;
        }
        else {
@@ -626,6 +659,22 @@ sub mycgiurl (@) {
        return $origsubs{'cgiurl'}->(%params);
 }
 
+sub myrootpage (@) {
+       my %params=@_;
+
+       my $rootpage;
+       if (exists $params{rootpage}) {
+               $rootpage=$origsubs{'bestlink'}->($params{page}, $params{rootpage});
+               if (!length $rootpage) {
+                       $rootpage=$params{rootpage};
+               }
+       }
+       else {
+               $rootpage=masterpage($params{page});
+       }
+       return $rootpage;
+}
+
 # ,----
 # | Blackboxes for private data
 # `----
@@ -681,6 +730,7 @@ sub istranslatablefile ($) {
        my $type=pagetype($file);
        return 0 if ! defined $type || $type eq 'po';
        return 0 if $file =~ /\.pot$/;
+       return 0 if ! defined $config{po_translatable_pages};
        return 1 if pagespec_match(pagename($file), $config{po_translatable_pages});
        return;
 }
@@ -798,18 +848,18 @@ sub refreshpot ($) {
        my $masterfile=shift;
 
        my $potfile=potfile($masterfile);
-       my %options = ("markdown" => (pagetype($masterfile) eq 'mdwn') ? 1 : 0);
-       my $doc=Locale::Po4a::Chooser::new('text',%options);
+       my $doc=Locale::Po4a::Chooser::new(po4a_type($masterfile),
+                                          po4a_options($masterfile));
        $doc->{TT}{utf_mode} = 1;
-       $doc->{TT}{file_in_charset} = 'utf-8';
-       $doc->{TT}{file_out_charset} = 'utf-8';
+       $doc->{TT}{file_in_charset} = 'UTF-8';
+       $doc->{TT}{file_out_charset} = 'UTF-8';
        $doc->read($masterfile);
        # let's cheat a bit to force porefs option to be passed to
        # Locale::Po4a::Po; this is undocument use of internal
        # Locale::Po4a::TransTractor's data, compulsory since this module
        # prevents us from using the porefs option.
        $doc->{TT}{po_out}=Locale::Po4a::Po->new({ 'porefs' => 'none' });
-       $doc->{TT}{po_out}->set_charset('utf-8');
+       $doc->{TT}{po_out}->set_charset('UTF-8');
        # do the actual work
        $doc->parse;
        IkiWiki::prep_writefile(basename($potfile),dirname($potfile));
@@ -827,8 +877,23 @@ sub refreshpofiles ($@) {
 
        foreach my $pofile (@pofiles) {
                IkiWiki::prep_writefile(basename($pofile),dirname($pofile));
+
+               if (! -e $pofile) {
+                       # If the po file exists in an underlay, copy it
+                       # from there.
+                       my ($pobase)=$pofile=~/^\Q$config{srcdir}\E\/?(.*)$/;
+                       foreach my $dir (@{$config{underlaydirs}}) {
+                               if (-e "$dir/$pobase") {
+                                       File::Copy::syscopy("$dir/$pobase",$pofile)
+                                               or error("po(refreshpofiles) ".
+                                                        sprintf(gettext("failed to copy underlay PO file to %s"),
+                                                                $pofile));
+                               }
+                       }
+               }
+
                if (-e $pofile) {
-                       system("msgmerge", "-q", "-U", "--backup=none", $pofile, $potfile) == 0
+                       system("msgmerge", "--previous", "-q", "-U", "--backup=none", $pofile, $potfile) == 0
                                or error("po(refreshpofiles) ".
                                         sprintf(gettext("failed to update %s"),
                                                 $pofile));
@@ -875,15 +940,13 @@ sub percenttranslated ($) {
        return gettext("N/A") unless istranslation($page);
        my $file=srcfile($pagesources{$page});
        my $masterfile = srcfile($pagesources{masterpage($page)});
-       my %options = (
-               "markdown" => (pagetype($masterfile) eq 'mdwn') ? 1 : 0,
-       );
-       my $doc=Locale::Po4a::Chooser::new('text',%options);
+       my $doc=Locale::Po4a::Chooser::new(po4a_type($masterfile),
+                                          po4a_options($masterfile));
        $doc->process(
                'po_in_name'    => [ $file ],
                'file_in_name'  => [ $masterfile ],
-               'file_in_charset'  => 'utf-8',
-               'file_out_charset' => 'utf-8',
+               'file_in_charset'  => 'UTF-8',
+               'file_out_charset' => 'UTF-8',
        ) or error("po(percenttranslated) ".
                   sprintf(gettext("failed to translate %s"), $page));
        my ($percent,$hit,$queries) = $doc->stats();
@@ -915,7 +978,7 @@ sub otherlanguagesloop ($) {
                                master => 1,
                        };
                }
-               else {
+               elsif (istranslation($otherpage)) {
                        push @ret, {
                                url => urlto_with_orig_beautiful_urlpath($otherpage, $page),
                                code => $lang,
@@ -937,6 +1000,14 @@ sub homepageurl (;$) {
        return urlto('', $page);
 }
 
+sub ishomepage ($) {
+       my $page = shift;
+
+       return 1 if $page eq 'index';
+       map { return 1 if $page eq 'index.'.$_ } keys %{$config{po_slave_languages}};
+       return undef;
+}
+
 sub deletetranslations ($) {
        my $deletedmasterfile=shift;
 
@@ -961,17 +1032,18 @@ sub deletetranslations ($) {
 
        if (@todelete) {
                commit_and_refresh(
-                       gettext("removed obsolete PO files"),
-                       "IkiWiki::Plugin::po::deletetranslations");
+                       gettext("removed obsolete PO files"));
        }
 }
 
-sub commit_and_refresh ($$) {
-       my ($msg, $author) = (shift, shift);
+sub commit_and_refresh ($) {
+       my $msg = shift;
 
        if ($config{rcs}) {
                IkiWiki::disable_commit_hook();
-               IkiWiki::rcs_commit_staged($msg, $author, "127.0.0.1");
+               IkiWiki::rcs_commit_staged(
+                       message => $msg,
+               );
                IkiWiki::enable_commit_hook();
                IkiWiki::rcs_update();
        }
@@ -989,11 +1061,8 @@ sub commit_and_refresh ($$) {
        IkiWiki::saveindex();
 }
 
-# on success, returns the filtered content.
-# on error, if $nonfatal, warn and return undef; else, error out.
-sub po_to_markup ($$;$) {
+sub po_to_markup ($$) {
        my ($page, $content) = (shift, shift);
-       my $nonfatal = shift;
 
        $content = '' unless defined $content;
        $content = decode_utf8(encode_utf8($content));
@@ -1016,10 +1085,6 @@ sub po_to_markup ($$;$) {
 
        my $fail = sub ($) {
                my $msg = "po(po_to_markup) - $page : " . shift;
-               if ($nonfatal) {
-                       warn $msg;
-                       return undef;
-               }
                error($msg, sub { unlink $infile, $outfile});
        };
 
@@ -1027,21 +1092,18 @@ sub po_to_markup ($$;$) {
                or return $fail->(sprintf(gettext("failed to write %s"), $infile));
 
        my $masterfile = srcfile($pagesources{masterpage($page)});
-       my %options = (
-               "markdown" => (pagetype($masterfile) eq 'mdwn') ? 1 : 0,
-       );
-       my $doc=Locale::Po4a::Chooser::new('text',%options);
+       my $doc=Locale::Po4a::Chooser::new(po4a_type($masterfile),
+                                          po4a_options($masterfile));
        $doc->process(
                'po_in_name'    => [ $infile ],
                'file_in_name'  => [ $masterfile ],
-               'file_in_charset'  => 'utf-8',
-               'file_out_charset' => 'utf-8',
+               'file_in_charset'  => 'UTF-8',
+               'file_out_charset' => 'UTF-8',
        ) or return $fail->(gettext("failed to translate"));
        $doc->write($outfile)
                or return $fail->(sprintf(gettext("failed to write %s"), $outfile));
 
-       $content = readfile($outfile)
-               or return $fail->(sprintf(gettext("failed to read %s"), $outfile));
+       $content = readfile($outfile);
 
        # Unlinking should happen automatically, thanks to File::Temp,
        # but it does not work here, probably because of the way writefile()
@@ -1094,6 +1156,37 @@ sub isvalidpo ($) {
                                        "to previous page to continue edit"));
 }
 
+sub po4a_type ($) {
+       my $file = shift;
+
+       my $pagetype = pagetype($file);
+       if ($pagetype eq 'html') {
+               return 'xhtml';
+        }
+       return 'text';
+}
+
+sub po4a_options($) {
+       my $file = shift;
+
+       my %options;
+       my $pagetype = pagetype($file);
+
+       if ($pagetype eq 'html') {
+               # how to disable options is not consistent across po4a modules
+               $options{includessi} = '';
+               $options{includeexternal} = 0;
+        }
+       elsif ($pagetype eq 'mdwn') {
+               $options{markdown} = 1;
+        }
+        else {
+               $options{markdown} = 0;
+        }
+
+       return %options;
+}
+
 # ,----
 # | PageSpecs
 # `----
@@ -1154,4 +1247,32 @@ sub match_currentlang ($$;@) {
        }
 }
 
+sub match_needstranslation ($$;@) {
+       my $page=shift;
+       my $wanted=shift;
+
+       if (defined $wanted && $wanted ne "") {
+               if ($wanted !~ /^\d+$/) {
+                       return IkiWiki::FailReason->new("parameter is not an integer");
+               }
+               elsif ($wanted > 100) {
+                       return IkiWiki::FailReason->new("parameter is greater than 100");
+                }
+        }
+        else {
+               $wanted=100;
+        }
+
+       my $percenttranslated=IkiWiki::Plugin::po::percenttranslated($page);
+       if ($percenttranslated eq 'N/A') {
+               return IkiWiki::FailReason->new("file is not a translatable page");
+       }
+       elsif ($percenttranslated < $wanted) {
+               return IkiWiki::SuccessReason->new("file has $percenttranslated translated");
+        }
+       else {
+               return IkiWiki::FailReason->new("file is translated enough");
+       }
+}
+
 1