use File::Spec;
use File::Temp;
use Memoize;
+use UNIVERSAL;
my %translations;
-our %filtered;
+my @origneedsbuild;
memoize("_istranslation");
memoize("percenttranslated");
hook(type => "getsetup", id => "po", call => \&getsetup);
hook(type => "checkconfig", id => "po", call => \&checkconfig);
hook(type => "needsbuild", id => "po", call => \&needsbuild);
+ hook(type => "scan", id => "po", call => \&scan, last =>1);
hook(type => "filter", id => "po", call => \&filter);
hook(type => "htmlize", id => "po", call => \&htmlize);
hook(type => "pagetemplate", id => "po", call => \&pagetemplate, last => 1);
+ hook(type => "change", id => "po", call => \&change);
hook(type => "editcontent", id => "po", call => \&editcontent);
$origsubs{'bestlink'}=\&IkiWiki::bestlink;
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);
} #}}}
sub getsetup () { #{{{
} #}}}
sub islanguagecode ($) { #{{{
- my $code=shift;
- return ($code =~ /^[a-z]{2}$/);
+ my $code=shift;
+ return ($code =~ /^[a-z]{2}$/);
} #}}}
sub checkconfig () { #{{{
error(sprintf(gettext("Must specify %s"), $field));
}
}
+ if (! (keys %{$config{po_slave_languages}})) {
+ error(gettext("At least one slave language must be defined in po_slave_languages"));
+ }
map {
islanguagecode($_)
- or error(sprintf(gettext("%s is not a valid language code"), $_));
+ or error(sprintf(gettext("%s is not a valid language code"), $_));
} ($config{po_master_language}{code}, keys %{$config{po_slave_languages}});
if (! exists $config{po_translatable_pages} ||
! defined $config{po_translatable_pages}) {
! defined $config{po_link_to}) {
$config{po_link_to}='default';
}
- elsif ($config{po_link_to} != 'default'
- && $config{po_link_to} != 'current'
- && $config{po_link_to} != 'negotiated') {
+ elsif (! grep {
+ $config{po_link_to} eq $_
+ } ('default', 'current', 'negotiated')) {
warn(sprintf(gettext('po_link_to=%s is not a valid setting, falling back to po_link_to=default'),
$config{po_link_to}));
$config{po_link_to}='default';
push @{$config{wiki_file_prune_regexps}}, qr/\.pot$/;
} #}}}
+sub otherlanguages($) { #{{{
+ my $page=shift;
+
+ my %ret;
+ if (istranslatable($page)) {
+ %ret = %{$translations{$page}};
+ }
+ elsif (istranslation($page)) {
+ my $masterpage = masterpage($page);
+ $ret{$config{po_master_language}{code}} = $masterpage;
+ foreach my $lang (sort keys %{$translations{$masterpage}}) {
+ next if $lang eq lang($page);
+ $ret{$lang} = $translations{$masterpage}{$lang};
+ }
+ }
+ return \%ret;
+} #}}}
+
sub potfile ($) { #{{{
my $masterfile=shift;
return File::Spec->catpath('', $dir, $name . "." . $lang . ".po");
} #}}}
+sub pofiles ($) { #{{{
+ my $masterfile=shift;
+ return map pofile($masterfile, $_), (keys %{$config{po_slave_languages}});
+} #}}}
+
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);
- $doc->read($masterfile);
$doc->{TT}{utf_mode} = 1;
$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}->set_charset('utf-8');
# do the actual work
$doc->parse;
+ IkiWiki::prep_writefile(basename($potfile),dirname($potfile));
$doc->writepo($potfile);
} #}}}
error("[po/refreshpofiles] POT file ($potfile) does not exist") unless (-e $potfile);
foreach my $pofile (@pofiles) {
+ IkiWiki::prep_writefile(basename($pofile),dirname($pofile));
if (-e $pofile) {
system("msgmerge", "-U", "--backup=none", $pofile, $potfile) == 0
or error("[po/refreshpofiles:$pofile] failed to update");
}
} #}}}
+sub buildtranslationscache() { #{{{
+ # use istranslation's side-effect
+ map istranslation($_), (keys %pagesources);
+} #}}}
+
+sub resettranslationscache() { #{{{
+ undef %translations;
+} #}}}
+
sub needsbuild () { #{{{
my $needsbuild=shift;
- # build %translations, using istranslation's side-effect
- foreach my $page (keys %pagesources) {
- istranslation($page);
+ # backup @needsbuild content so that change() can know whether
+ # a given master page was rendered because its source file was changed
+ @origneedsbuild=(@$needsbuild);
+
+ buildtranslationscache();
+
+ # make existing translations depend on the corresponding master page
+ foreach my $master (keys %translations) {
+ map add_depends($_, $master), values %{otherlanguages($master)};
}
+} #}}}
- # refresh/create POT and PO files as needed
- my $updated_po_files=0;
- foreach my $page (keys %pagesources) {
- if (istranslatable($page)) {
- my $pageneedsbuild = grep { $_ eq $pagesources{$page} } @$needsbuild;
- my $updated_pot_file=0;
- my $file=srcfile($pagesources{$page});
- if ($pageneedsbuild || ! -e potfile($file)) {
- refreshpot($file);
- $updated_pot_file=1;
- }
- my @pofiles;
- foreach my $lang (keys %{$config{po_slave_languages}}) {
- my $pofile=pofile($file, $lang);
- my $pofile_rel=pofile($pagesources{$page}, $lang);
- if ($pageneedsbuild || $updated_pot_file || ! -e $pofile) {
- push @pofiles, $pofile;
- push @$needsbuild, $pofile_rel
- unless grep { $_ eq $pofile_rel } @$needsbuild;
+sub scan (@) { #{{{
+ my %params=@_;
+ my $page=$params{page};
+ my $content=$params{content};
+
+ return unless UNIVERSAL::can("IkiWiki::Plugin::link", "import");
+
+ 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)
+ for (my $i=0; $i<@{$links{$page}}; $i++) {
+ if (@{$links{$page}}[$i] eq $destpage) {
+ @{$links{$page}}[$i] = $destpage . '.' . lang($page);
+ last;
+ }
}
}
- if (@pofiles) {
- refreshpofiles($file, @pofiles) ;
- map { IkiWiki::rcs_add($_); } @pofiles if ($config{rcs});
- $updated_po_files = 1;
- }
}
}
-
- # check staged changes in
- if ($updated_po_files) {
- if ($config{rcs}) {
- IkiWiki::disable_commit_hook();
- IkiWiki::rcs_commit_staged(gettext("updated PO files"),
- "refreshpofiles", "127.0.0.1");
- IkiWiki::enable_commit_hook();
- IkiWiki::rcs_update();
- }
- # refresh module's private variables
- undef %filtered;
- undef %translations;
- foreach my $page (keys %pagesources) {
- istranslation($page);
- }
- }
-
- # make existing translations depend on the corresponding master page
- foreach my $master (keys %translations) {
- foreach my $slave (values %{$translations{$master}}) {
- add_depends($slave, $master);
+ elsif (! istranslatable($page) && ! istranslation($page)) {
+ foreach my $destpage (@{$links{$page}}) {
+ if (istranslatable($destpage)) {
+ map {
+ push @{$links{$page}}, $destpage . '.' . $_;
+ } (keys %{$config{po_slave_languages}});
+ }
}
}
} #}}}
my $ext=shift;
if (istranslation($page)) {
- my ($masterpage, $lang) = ($page =~ /(.*)[.]([a-z]{2})$/);
+ my ($masterpage, $lang) = (masterpage($page), lang($page));
if (! $config{usedirs} || $masterpage eq 'index') {
return $masterpage . "." . $lang . "." . $ext;
}
return $res;
} #}}}
+sub myurlto ($$;$) { #{{{
+ my $to=shift;
+ my $from=shift;
+ my $absolute=shift;
+
+ # workaround hard-coded /index.$config{htmlext} in IkiWiki::urlto()
+ if (! length $to
+ && $config{po_link_to} eq "current"
+ && istranslation($from)
+ && istranslatable('index')) {
+ return IkiWiki::beautify_urlpath(IkiWiki::baseurl($from) . "index." . lang($from) . ".$config{htmlext}");
+ }
+ return $origsubs{'urlto'}->($to,$from,$absolute);
+} #}}}
+
sub mybestlink ($$) { #{{{
my $page=shift;
my $link=shift;
if ($config{po_link_to} eq "current"
&& istranslatable($res)
&& istranslation($page)) {
- my ($masterpage, $curlang) = ($page =~ /(.*)[.]([a-z]{2})$/);
- return $res . "." . $curlang;
+ return $res . "." . lang($page);
}
else {
return $res;
return "";
} #}}}
+# blackbox for %filtered
+{
+ my %filtered;
+
+ sub alreadyfiltered($$) { #{{{
+ my $page=shift;
+ my $destpage=shift;
+
+ return ( exists $filtered{$page}{$destpage}
+ && $filtered{$page}{$destpage} eq 1 );
+ } #}}}
+
+ sub setalreadyfiltered($$) { #{{{
+ my $page=shift;
+ my $destpage=shift;
+
+ $filtered{$page}{$destpage}=1;
+ } #}}}
+
+ sub unsetalreadyfiltered($$) { #{{{
+ my $page=shift;
+ my $destpage=shift;
+
+ if (exists $filtered{$page}{$destpage}) {
+ delete $filtered{$page}{$destpage};
+ }
+ } #}}}
+
+ sub resetalreadyfiltered() { #{{{
+ undef %filtered;
+ } #}}}
+}
+
# We use filter to convert PO to the master page's format,
# since the rest of ikiwiki should not work on PO files.
sub filter (@) { #{{{
my $content = decode_utf8(encode_utf8($params{content}));
return $content if ( ! istranslation($page)
- || ( exists $filtered{$page}{$destpage}
- && $filtered{$page}{$destpage} eq 1 ));
+ || alreadyfiltered($page, $destpage) );
# CRLF line terminators make poor Locale::Po4a feel bad
$content=~s/\r\n/\n/g;
writefile(basename($infile), File::Spec->tmpdir, $content);
- my ($masterpage, $lang) = ($page =~ /(.*)[.]([a-z]{2})$/);
- my $masterfile = srcfile($pagesources{$masterpage});
+ my $masterfile = srcfile($pagesources{masterpage($page)});
my (@pos,@masters);
push @pos,$infile;
push @masters,$masterfile;
'file_in_name' => \@masters,
'file_in_charset' => 'utf-8',
'file_out_charset' => 'utf-8',
- ) or error("[po/filter:$infile]: failed to translate");
- $doc->write($outfile) or error("[po/filter:$infile] could not write $outfile");
- $content = readfile($outfile) or error("[po/filter:$infile] could not read $outfile");
+ ) or error("[po/filter:$page]: failed to translate");
+ $doc->write($outfile) or error("[po/filter:$page] could not write $outfile");
+ $content = readfile($outfile) or error("[po/filter:$page] could not read $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;
- $filtered{$page}{$destpage}=1;
+ setalreadyfiltered($page, $destpage);
return $content;
} #}}}
my $page = $params{page};
my $content = $params{content};
- my ($masterpage, $lang) = ($page =~ /(.*)[.]([a-z]{2})$/);
- my $masterfile = srcfile($pagesources{$masterpage});
+ my $masterfile = srcfile($pagesources{masterpage($page)});
# force content to be htmlize'd as if it was the same type as the master page
return IkiWiki::htmlize($page, $page, pagetype($masterfile), $content);
sub percenttranslated ($) { #{{{
my $page=shift;
- return gettext("N/A") unless (istranslation($page));
- my ($masterpage, $lang) = ($page =~ /(.*)[.]([a-z]{2})$/);
+ return gettext("N/A") unless istranslation($page);
my $file=srcfile($pagesources{$page});
- my $masterfile = srcfile($pagesources{$masterpage});
+ my $masterfile = srcfile($pagesources{masterpage($page)});
my (@pos,@masters);
push @pos,$file;
push @masters,$masterfile;
'file_in_name' => \@masters,
'file_in_charset' => 'utf-8',
'file_out_charset' => 'utf-8',
- ) or error("[po/percenttranslated:$file]: failed to translate");
+ ) or error("[po/percenttranslated:$page]: failed to translate");
my ($percent,$hit,$queries) = $doc->stats();
return $percent;
} #}}}
-sub otherlanguages ($) { #{{{
+sub languagename ($) { #{{{
+ my $code=shift;
+
+ return $config{po_master_language}{name}
+ if $code eq $config{po_master_language}{code};
+ return $config{po_slave_languages}{$code}
+ if defined $config{po_slave_languages}{$code};
+ return;
+} #}}}
+
+sub otherlanguagesloop ($) { #{{{
my $page=shift;
my @ret;
- if (istranslatable($page)) {
- foreach my $lang (sort keys %{$translations{$page}}) {
- my $translation = $translations{$page}{$lang};
+ my %otherpages=%{otherlanguages($page)};
+ while (my ($lang, $otherpage) = each %otherpages) {
+ if (istranslation($page) && masterpage($page) eq $otherpage) {
push @ret, {
- url => urlto($translation, $page),
+ url => urlto_with_orig_beautiful_urlpath($otherpage, $page),
code => $lang,
- language => $config{po_slave_languages}{$lang},
- percent => percenttranslated($translation),
+ language => languagename($lang),
+ master => 1,
};
}
- }
- elsif (istranslation($page)) {
- my ($masterpage, $curlang) = ($page =~ /(.*)[.]([a-z]{2})$/);
- push @ret, {
- url => urlto_with_orig_beautiful_urlpath($masterpage, $page),
- code => $config{po_master_language}{code},
- language => $config{po_master_language}{name},
- master => 1,
- };
- foreach my $lang (sort keys %{$translations{$masterpage}}) {
+ else {
push @ret, {
- url => urlto($translations{$masterpage}{$lang}, $page),
+ url => urlto($otherpage, $page),
code => $lang,
- language => $config{po_slave_languages}{$lang},
- percent => percenttranslated($translations{$masterpage}{$lang}),
- } unless ($lang eq $curlang);
+ language => languagename($lang),
+ percent => percenttranslated($otherpage),
+ }
}
}
- return @ret;
+ 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 pagetemplate (@) { #{{{
my $destpage=$params{destpage};
my $template=$params{template};
- my ($masterpage, $lang) = ($page =~ /(.*)[.]([a-z]{2})$/) if istranslation($page);
+ my ($masterpage, $lang) = istranslation($page);
if (istranslation($page) && $template->query(name => "percenttranslated")) {
$template->param(percenttranslated => percenttranslated($page));
}
if ($template->query(name => "istranslation")) {
- $template->param(istranslation => istranslation($page));
+ $template->param(istranslation => scalar istranslation($page));
}
if ($template->query(name => "istranslatable")) {
$template->param(istranslatable => istranslatable($page));
}
if ($template->query(name => "otherlanguages")) {
- $template->param(otherlanguages => [otherlanguages($page)]);
- if (istranslatable($page)) {
- foreach my $translation (values %{$translations{$page}}) {
- add_depends($page, $translation);
- }
- }
- elsif (istranslation($page)) {
- add_depends($page, $masterpage);
- foreach my $translation (values %{$translations{$masterpage}}) {
- add_depends($page, $translation);
- }
- }
+ $template->param(otherlanguages => [otherlanguagesloop($page)]);
+ map add_depends($page, $_), (values %{otherlanguages($page)});
}
# Rely on IkiWiki::Render's genpage() to decide wether
# a discussion link should appear on $page; this is not
linktext => gettext("Discussion"),
));
}
- # remove broken parentlink to ./index.html on home page's translations
+ # Remove broken parentlink to ./index.html on home page's translations.
+ # It works because this hook has the "last" parameter set, to ensure it
+ # runs after parentlinks' own pagetemplate hook.
if ($template->param('parentlinks')
&& istranslation($page)
&& $masterpage eq "index") {
}
} # }}}
+sub change(@) { #{{{
+ my @rendered=@_;
+
+ my $updated_po_files=0;
+
+ # Refresh/create POT and PO files as needed.
+ foreach my $page (map pagename($_), @rendered) {
+ next unless istranslatable($page);
+ my $file=srcfile($pagesources{$page});
+ my $updated_pot_file=0;
+ # Only refresh Pot file if it does not exist, or if
+ # $pagesources{$page} was changed: don't if only the HTML was
+ # refreshed, e.g. because of a dependency.
+ if ((grep { $_ eq $pagesources{$page} } @origneedsbuild)
+ || ! -e potfile($file)) {
+ refreshpot($file);
+ $updated_pot_file=1;
+ }
+ my @pofiles;
+ map {
+ push @pofiles, $_ if ($updated_pot_file || ! -e $_);
+ } (pofiles($file));
+ if (@pofiles) {
+ refreshpofiles($file, @pofiles);
+ map { IkiWiki::rcs_add($_); } @pofiles if ($config{rcs});
+ $updated_po_files=1;
+ }
+ }
+
+ if ($updated_po_files) {
+ # Check staged changes in.
+ if ($config{rcs}) {
+ IkiWiki::disable_commit_hook();
+ IkiWiki::rcs_commit_staged(gettext("updated PO files"),
+ "IkiWiki::Plugin::po::change", "127.0.0.1");
+ IkiWiki::enable_commit_hook();
+ IkiWiki::rcs_update();
+ }
+ # Reinitialize module's private variables.
+ resetalreadyfiltered();
+ resettranslationscache();
+ # Trigger a wiki refresh.
+ require IkiWiki::Render;
+ IkiWiki::refresh();
+ IkiWiki::saveindex();
+ }
+} #}}}
+
sub editcontent () { #{{{
my %params=@_;
# as we're previewing or saving a page, the content may have
# changed, so tell the next filter() invocation it must not be lazy
- if (exists $filtered{$params{page}}{$params{page}}) {
- delete $filtered{$params{page}}{$params{page}};
- }
+ unsetalreadyfiltered($params{page}, $params{page});
return $params{content};
} #}}}
return 0;
}
- return istranslatable($masterpage);
+ return ($masterpage, $lang) if istranslatable($masterpage);
} #}}}
sub istranslation ($) { #{{{
my $page=shift;
- if (_istranslation($page)) {
- my ($masterpage, $lang) = ($page =~ /(.*)[.]([a-z]{2})$/);
+ if (1 < (my ($masterpage, $lang) = _istranslation($page))) {
$translations{$masterpage}{$lang}=$page unless exists $translations{$masterpage}{$lang};
- return 1;
+ return ($masterpage, $lang);
+ }
+ return;
+} #}}}
+
+sub masterpage ($) { #{{{
+ my $page=shift;
+
+ if ( 1 < (my ($masterpage, $lang) = _istranslation($page))) {
+ return $masterpage;
+ }
+ return $page;
+} #}}}
+
+sub lang ($) { #{{{
+ my $page=shift;
+
+ if (1 < (my ($masterpage, $lang) = _istranslation($page))) {
+ return $lang;
}
- return 0;
+ return $config{po_master_language}{code};
} #}}}
package IkiWiki::PageSpec;