+ my @ret;
+ my %otherpages=%{otherlanguages($page)};
+ while (my ($lang, $otherpage) = each %otherpages) {
+ if (istranslation($page) && masterpage($page) eq $otherpage) {
+ push @ret, {
+ url => urlto_with_orig_beautiful_urlpath($otherpage, $page),
+ code => $lang,
+ language => languagename($lang),
+ master => 1,
+ };
+ }
+ else {
+ push @ret, {
+ url => urlto_with_orig_beautiful_urlpath($otherpage, $page),
+ code => $lang,
+ language => languagename($lang),
+ percent => percenttranslated($otherpage),
+ }
+ }
+ }
+ return sort {
+ return -1 if $a->{code} eq $config{po_master_language}{code};
+ return 1 if $b->{code} eq $config{po_master_language}{code};
+ return $a->{language} cmp $b->{language};
+ } @ret;
+}
+
+sub homepageurl (;$) {
+ my $page=shift;
+
+ return urlto('', $page);
+}
+
+sub deletetranslations ($) {
+ my $deletedmasterfile=shift;
+
+ my $deletedmasterpage=pagename($deletedmasterfile);
+ my @todelete;
+ map {
+ my $file = newpagefile($deletedmasterpage.'.'.$_, 'po');
+ my $absfile = "$config{srcdir}/$file";
+ if (-e $absfile && ! -l $absfile && ! -d $absfile) {
+ push @todelete, $file;
+ }
+ } keys %{$config{po_slave_languages}};
+
+ map {
+ if ($config{rcs}) {
+ IkiWiki::rcs_remove($_);
+ }
+ else {
+ IkiWiki::prune("$config{srcdir}/$_");
+ }
+ } @todelete;
+
+ if (@todelete) {
+ commit_and_refresh(
+ gettext("removed obsolete PO files"),
+ "IkiWiki::Plugin::po::deletetranslations");
+ }
+}
+
+sub commit_and_refresh ($$) {
+ my ($msg, $author) = (shift, shift);
+
+ if ($config{rcs}) {
+ IkiWiki::disable_commit_hook();
+ IkiWiki::rcs_commit_staged($msg, $author, "127.0.0.1");
+ IkiWiki::enable_commit_hook();
+ IkiWiki::rcs_update();
+ }
+ # Reinitialize module's private variables.
+ resetalreadyfiltered();
+ resettranslationscache();
+ flushmemoizecache();
+ # Trigger a wiki refresh.
+ require IkiWiki::Render;
+ # without preliminary saveindex/loadindex, refresh()
+ # complains about a lot of uninitialized variables
+ IkiWiki::saveindex();
+ IkiWiki::loadindex();
+ IkiWiki::refresh();
+ IkiWiki::saveindex();
+}
+
+# on success, returns the filtered content.
+# on error, if $nonfatal, warn and return undef; else, error out.
+sub po_to_markup ($$;$) {
+ my ($page, $content) = (shift, shift);
+ my $nonfatal = shift;
+
+ $content = '' unless defined $content;
+ $content = decode_utf8(encode_utf8($content));
+ # CRLF line terminators make poor Locale::Po4a feel bad
+ $content=~s/\r\n/\n/g;
+
+ # There are incompatibilities between some File::Temp versions
+ # (including 0.18, bundled with Lenny's perl-modules package)
+ # and others (e.g. 0.20, previously present in the archive as
+ # a standalone package): under certain circumstances, some
+ # return a relative filename, whereas others return an absolute one;
+ # we here use this module in a way that is at least compatible
+ # with 0.18 and 0.20. Beware, hit'n'run refactorers!
+ my $infile = new File::Temp(TEMPLATE => "ikiwiki-po-filter-in.XXXXXXXXXX",
+ DIR => File::Spec->tmpdir,
+ UNLINK => 1)->filename;
+ my $outfile = new File::Temp(TEMPLATE => "ikiwiki-po-filter-out.XXXXXXXXXX",
+ DIR => File::Spec->tmpdir,
+ UNLINK => 1)->filename;
+
+ my $fail = sub ($) {
+ my $msg = "po(po_to_markup) - $page : " . shift;
+ if ($nonfatal) {
+ warn $msg;
+ return undef;
+ }
+ error($msg, sub { unlink $infile, $outfile});
+ };
+
+ writefile(basename($infile), File::Spec->tmpdir, $content)
+ 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);
+ $doc->process(
+ 'po_in_name' => [ $infile ],
+ 'file_in_name' => [ $masterfile ],
+ '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));
+
+ # Unlinking should happen automatically, thanks to File::Temp,
+ # but it does not work here, probably because of the way writefile()
+ # and Locale::Po4a::write() work.
+ unlink $infile, $outfile;
+
+ return $content;
+}
+
+# returns a SuccessReason or FailReason object
+sub isvalidpo ($) {
+ my $content = shift;
+
+ # NB: we don't use po_to_markup here, since Po4a parser does
+ # not mind invalid PO content
+ $content = '' unless defined $content;
+ $content = decode_utf8(encode_utf8($content));
+
+ # There are incompatibilities between some File::Temp versions
+ # (including 0.18, bundled with Lenny's perl-modules package)
+ # and others (e.g. 0.20, previously present in the archive as
+ # a standalone package): under certain circumstances, some
+ # return a relative filename, whereas others return an absolute one;
+ # we here use this module in a way that is at least compatible
+ # with 0.18 and 0.20. Beware, hit'n'run refactorers!
+ my $infile = new File::Temp(TEMPLATE => "ikiwiki-po-isvalidpo.XXXXXXXXXX",
+ DIR => File::Spec->tmpdir,
+ UNLINK => 1)->filename;
+
+ my $fail = sub ($) {
+ my $msg = '[po/isvalidpo] ' . shift;
+ unlink $infile;
+ return IkiWiki::FailReason->new("$msg");
+ };
+
+ writefile(basename($infile), File::Spec->tmpdir, $content)
+ or return $fail->(sprintf(gettext("failed to write %s"), $infile));
+
+ my $res = (system("msgfmt", "--check", $infile, "-o", "/dev/null") == 0);
+
+ # Unlinking should happen automatically, thanks to File::Temp,
+ # but it does not work here, probably because of the way writefile()
+ # and Locale::Po4a::write() work.
+ unlink $infile;
+
+ if ($res) {
+ return IkiWiki::SuccessReason->new("valid gettext data");
+ }
+ return IkiWiki::FailReason->new("invalid gettext data, go back ".
+ "to previous page to go on with edit");
+}
+
+# ,----
+# | PageSpec's
+# `----
+
+package IkiWiki::PageSpec;
+use warnings;
+use strict;
+use IkiWiki 2.00;
+
+sub match_istranslation ($;@) {
+ my $page=shift;
+
+ if (IkiWiki::Plugin::po::istranslation($page)) {
+ return IkiWiki::SuccessReason->new("is a translation page");
+ }
+ else {
+ return IkiWiki::FailReason->new("is not a translation page");
+ }
+}
+
+sub match_istranslatable ($;@) {
+ my $page=shift;
+
+ if (IkiWiki::Plugin::po::istranslatable($page)) {
+ return IkiWiki::SuccessReason->new("is set as translatable in po_translatable_pages");
+ }
+ else {
+ return IkiWiki::FailReason->new("is not set as translatable in po_translatable_pages");
+ }
+}
+
+sub match_lang ($$;@) {
+ my $page=shift;
+ my $wanted=shift;
+
+ my $regexp=IkiWiki::glob2re($wanted);
+ my $lang=IkiWiki::Plugin::po::lang($page);
+ if ($lang!~/^$regexp$/i) {
+ return IkiWiki::FailReason->new("file language is $lang, not $wanted");
+ }
+ else {
+ return IkiWiki::SuccessReason->new("file language is $wanted");
+ }
+}
+
+sub match_currentlang ($$;@) {
+ my $page=shift;
+ shift;
+ my %params=@_;
+
+ return IkiWiki::FailReason->new("no location provided") unless exists $params{location};
+
+ my $currentlang=IkiWiki::Plugin::po::lang($params{location});
+ my $lang=IkiWiki::Plugin::po::lang($page);
+
+ if ($lang eq $currentlang) {
+ return IkiWiki::SuccessReason->new("file language is the same as current one, i.e. $currentlang");
+ }
+ else {
+ return IkiWiki::FailReason->new("file language is $lang, whereas current language is $currentlang");
+ }
+}