]> git.vanrenterghem.biz Git - git.ikiwiki.info.git/blob - IkiWiki/Plugin/po.pm
997f17fb39cee9060df1f926aa51f856f2f4c03f
[git.ikiwiki.info.git] / IkiWiki / Plugin / po.pm
1 #!/usr/bin/perl
2 # .po as a wiki page type
3 # inspired by the GPL'd po4a-translate,
4 # which is Copyright 2002, 2003, 2004 by Martin Quinson (mquinson#debian.org)
5 package IkiWiki::Plugin::po;
7 use warnings;
8 use strict;
9 use IkiWiki 2.00;
10 use Encode;
11 use Locale::Po4a::Chooser;
12 use File::Temp;
13 use Memoize;
15 my %translations;
16 memoize("istranslatable");
17 memoize("_istranslation");
19 sub import {
20         hook(type => "getsetup", id => "po", call => \&getsetup);
21         hook(type => "checkconfig", id => "po", call => \&checkconfig);
22         hook(type => "needsbuild", id => "po", call => \&needsbuild);
23         hook(type => "targetpage", id => "po", call => \&targetpage);
24         hook(type => "tweakurlpath", id => "po", call => \&tweakurlpath);
25         hook(type => "tweakbestlink", id => "po", call => \&tweakbestlink);
26         hook(type => "filter", id => "po", call => \&filter);
27         hook(type => "htmlize", id => "po", call => \&htmlize);
28         hook(type => "pagetemplate", id => "po", call => \&pagetemplate);
29 }
31 sub getsetup () { #{{{
32         return
33                 plugin => {
34                         safe => 0,
35                         rebuild => 1, # format plugin
36                 },
37                 po_master_language => {
38                         type => "string",
39                         example => {
40                                 'code' => 'en',
41                                 'name' => 'English'
42                         },
43                         description => "master language (non-PO files)",
44                         safe => 1,
45                         rebuild => 1,
46                 },
47                 po_slave_languages => {
48                         type => "string",
49                         example => {
50                                 'fr' => 'Français',
51                                 'es' => 'Castellano',
52                                 'de' => 'Deutsch'
53                         },
54                         description => "slave languages (PO files)",
55                         safe => 1,
56                         rebuild => 1,
57                 },
58                 po_translatable_pages => {
59                         type => "pagespec",
60                         example => "!*/Discussion",
61                         description => "PageSpec controlling which pages are translatable",
62                         link => "ikiwiki/PageSpec",
63                         safe => 1,
64                         rebuild => 1,
65                 },
66                 po_link_to => {
67                         type => "string",
68                         example => "current",
69                         description => "internal linking behavior (default/current/negotiated)",
70                         safe => 1,
71                         rebuild => 1,
72                 },
73 } #}}}
75 sub checkconfig () { #{{{
76         foreach my $field (qw{po_master_language po_slave_languages}) {
77                 if (! exists $config{$field} || ! defined $config{$field}) {
78                         error(sprintf(gettext("Must specify %s"), $field));
79                 }
80         }
81         if (! exists $config{po_link_to} ||
82             ! defined $config{po_link_to}) {
83             $config{po_link_to}="default";
84         }
85         if (! exists $config{po_translatable_pages} ||
86             ! defined $config{po_translatable_pages}) {
87             $config{po_translatable_pages}="";
88         }
89         if ($config{po_link_to} eq "negotiated" && ! $config{usedirs}) {
90                 error(gettext("po_link_to=negotiated requires usedirs to be set"));
91         }
92         push @{$config{wiki_file_prune_regexps}}, qr/\.pot$/;
93 } #}}}
95 sub needsbuild () { #{{{
96         my $needsbuild=shift;
98         # build %translations, using istranslation's side-effect
99         foreach my $page (keys %pagesources) {
100                 istranslation($page);
101         }
103 } #}}}
105 sub targetpage (@) { #{{{
106         my %params = @_;
107         my $page=$params{page};
108         my $ext=$params{ext};
110         if (istranslation($page)) {
111                 my ($masterpage, $lang) = ($page =~ /(.*)[.]([a-z]{2})$/);
112                 if (! $config{usedirs} || $page eq 'index') {
113                         return $masterpage . "." . $lang . "." . $ext;
114                 }
115                 else {
116                         return $masterpage . "/index." . $lang . "." . $ext;
117                 }
118         }
119         elsif (istranslatable($page)) {
120                 if (! $config{usedirs} || $page eq 'index') {
121                         return $page . "." . $config{po_master_language}{code} . "." . $ext;
122                 }
123                 else {
124                         return $page . "/index." . $config{po_master_language}{code} . "." . $ext;
125                 }
126         }
127         return;
128 } #}}}
130 sub tweakurlpath ($) { #{{{
131         my %params = @_;
132         my $url=$params{url};
133         if ($config{po_link_to} eq "negotiated") {
134                 $url =~ s!/index.$config{po_master_language}{code}.$config{htmlext}$!/!;
135         }
136         return $url;
137 } #}}}
139 sub tweakbestlink ($$) { #{{{
140         my %params = @_;
141         my $page=$params{page};
142         my $link=$params{link};
143         if ($config{po_link_to} eq "current"
144             && istranslatable($link)
145             && istranslation($page)) {
146                 my ($masterpage, $curlang) = ($page =~ /(.*)[.]([a-z]{2})$/);
147                 return $link . "." . $curlang;
148         }
149         return $link;
150 } #}}}
152 our %filtered;
153 # We use filter to convert PO to the master page's type,
154 # since other plugins should not work on PO files
155 sub filter (@) { #{{{
156         my %params = @_;
157         my $page = $params{page};
158         my $destpage = $params{destpage};
159         my $content = decode_utf8(encode_utf8($params{content}));
161         # decide if this is a PO file that should be converted into a translated document,
162         # and perform various sanity checks
163         if (! istranslation($page) || $filtered{$page}{$destpage}) {
164                 return $content;
165         }
167         my ($masterpage, $lang) = ($page =~ /(.*)[.]([a-z]{2})$/);
168         my $file=srcfile(exists $params{file} ? $params{file} : $IkiWiki::pagesources{$page});
169         my $masterfile = srcfile($pagesources{$masterpage});
170         my (@pos,@masters);
171         push @pos,$file;
172         push @masters,$masterfile;
173         my %options = (
174                         "markdown" => (pagetype($masterfile) eq 'mdwn') ? 1 : 0,
175                         );
176         my $doc=Locale::Po4a::Chooser::new('text',%options);
177         $doc->process(
178                 'po_in_name'    => \@pos,
179                 'file_in_name'  => \@masters,
180                 'file_in_charset'  => 'utf-8',
181                 'file_out_charset' => 'utf-8',
182         ) or error("[po/filter:$file]: failed to translate");
183         my ($percent,$hit,$queries) = $doc->stats();
184         my $tmpfh = File::Temp->new(TEMPLATE => "/tmp/ikiwiki-po-filter-out.XXXXXXXXXX");
185         my $tmpout = $tmpfh->filename;
186         $doc->write($tmpout) or error("[po/filter:$file] could not write $tmpout");
187         $content = readfile($tmpout) or error("[po/filter:$file] could not read $tmpout");
188         $filtered{$page}{$destpage}=1;
189         return $content;
190 } #}}}
192 sub htmlize (@) { #{{{
193         my %params=@_;
194         my $page = $params{page};
195         my $content = $params{content};
196         my ($masterpage, $lang) = ($page =~ /(.*)[.]([a-z]{2})$/);
197         my $masterfile = srcfile($pagesources{$masterpage});
199         # force content to be htmlize'd as if it was the same type as the master page
200         return IkiWiki::htmlize($page, $page, pagetype($masterfile), $content);
201 } #}}}
203 sub otherlanguages ($) { #{{{
204         my $page=shift;
205         my @ret;
206         if (istranslatable($page)) {
207                 foreach my $lang (sort keys %{$translations{$page}}) {
208                         push @ret, {
209                                 url => urlto($translations{$page}{$lang}, $page),
210                                 code => $lang,
211                                 language => $config{po_slave_languages}{$lang},
212                                 master => 0,
213                         };
214                 }
215         }
216         elsif (istranslation($page)) {
217                 my ($masterpage, $curlang) = ($page =~ /(.*)[.]([a-z]{2})$/);
218                 push @ret, {
219                         url => urlto($masterpage, $page),
220                         code => $config{po_master_language}{code},
221                         language => $config{po_master_language}{name},
222                         master => 1,
223                 };
224                 foreach my $lang (sort keys %{$translations{$masterpage}}) {
225                         push @ret, {
226                                 url => urlto($translations{$masterpage}{$lang}, $page),
227                                 code => $lang,
228                                 language => $config{po_slave_languages}{$lang},
229                                 master => 0,
230                         } unless ($lang eq $curlang);
231                 }
232         }
233         return @ret;
234 } #}}}
236 sub pagetemplate (@) { #{{{
237         my %params=@_;
238         my $page=$params{page};
239         my $template=$params{template};
241         if ($template->query(name => "otherlanguages")) {
242                 $template->param(otherlanguages => [otherlanguages($page)]);
243         }
244 } # }}}
246 sub istranslatable ($) { #{{{
247         my $page=shift;
248         my $file=$pagesources{$page};
250         if (! defined $file
251             || (defined pagetype($file) && pagetype($file) eq 'po')
252             || $file =~ /\.pot$/) {
253                 return 0;
254         }
255         return pagespec_match($page, $config{po_translatable_pages});
256 } #}}}
258 sub _istranslation ($) { #{{{
259         my $page=shift;
260         my $file=$pagesources{$page};
261         if (! defined $file) {
262                 return IkiWiki::FailReason->new("no file specified");
263         }
265         if (! defined $file
266             || ! defined pagetype($file)
267             || ! pagetype($file) eq 'po'
268             || $file =~ /\.pot$/) {
269                 return 0;
270         }
272         my ($masterpage, $lang) = ($page =~ /(.*)[.]([a-z]{2})$/);
273         if (! defined $masterpage || ! defined $lang
274             || ! (length($masterpage) > 0) || ! (length($lang) > 0)
275             || ! defined $pagesources{$masterpage}
276             || ! defined $config{po_slave_languages}{$lang}) {
277                 return 0;
278         }
280         return istranslatable($masterpage);
281 } #}}}
283 sub istranslation ($) { #{{{
284         my $page=shift;
285         if (_istranslation($page)) {
286                 my ($masterpage, $lang) = ($page =~ /(.*)[.]([a-z]{2})$/);
287                 $translations{$masterpage}{$lang}=$page unless exists $translations{$masterpage}{$lang};
288                 return 1;
289         }
290         return 0;
291 } #}}}
293 package IkiWiki::PageSpec;
294 use warnings;
295 use strict;
296 use IkiWiki 2.00;
298 sub match_istranslation ($;@) { #{{{
299         my $page=shift;
300         if (IkiWiki::Plugin::po::istranslation($page)) {
301                 return IkiWiki::SuccessReason->new("is a translation page");
302         }
303         else {
304                 return IkiWiki::FailReason->new("is not a translation page");
305         }
306 } #}}}
308 sub match_istranslatable ($;@) { #{{{
309         my $page=shift;
310         if (IkiWiki::Plugin::po::istranslatable($page)) {
311                 return IkiWiki::SuccessReason->new("is set as translatable in po_translatable_pages");
312         }
313         else {
314                 return IkiWiki::FailReason->new("is not set as translatable in po_translatable_pages");
315         }
316 } #}}}