]> sipb.mit.edu Git - ikiwiki.git/blob - IkiWiki/Plugin/po.pm
88985cd1208fb77a75657f91f35b3ae31345a9a1
[ikiwiki.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;
6
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;
14
15 my %translations;
16 memoize("istranslatable");
17 memoize("_istranslation");
18
19 sub import {
20         hook(type => "getsetup", id => "po", call => \&getsetup);
21         hook(type => "checkconfig", id => "po", call => \&checkconfig);
22         hook(type => "scan", id => "po", call => \&scan);
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 }
30
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 } #}}}
74
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 } #}}}
94
95 sub scan (@) { #{{{
96         my %params=@_;
97         my $page=$params{page};
98         # let's build %translations, using istranslation's
99         # side-effect, so that we can consider it is complete at
100         # preprocess time
101         istranslation($page);
102 } #}}}
103
104 sub targetpage (@) { #{{{
105         my %params = @_;
106         my $page=$params{page};
107         my $ext=$params{ext};
108
109         if (istranslation($page)) {
110                 my ($masterpage, $lang) = ($page =~ /(.*)[.]([a-z]{2})$/);
111                 if (! $config{usedirs} || $page eq 'index') {
112                         return $masterpage . "." . $lang . "." . $ext;
113                 }
114                 else {
115                         return $masterpage . "/index." . $lang . "." . $ext;
116                 }
117         }
118         elsif (istranslatable($page)) {
119                 if (! $config{usedirs} || $page eq 'index') {
120                         return $page . "." . $config{po_master_language}{code} . "." . $ext;
121                 }
122                 else {
123                         return $page . "/index." . $config{po_master_language}{code} . "." . $ext;
124                 }
125         }
126         return;
127 } #}}}
128
129 sub tweakurlpath ($) { #{{{
130         my %params = @_;
131         my $url=$params{url};
132         if ($config{po_link_to} eq "negotiated") {
133                 $url =~ s!/index.$config{po_master_language}{code}.$config{htmlext}$!/!;
134         }
135         return $url;
136 } #}}}
137
138 sub tweakbestlink ($$) { #{{{
139         my %params = @_;
140         my $page=$params{page};
141         my $link=$params{link};
142         if ($config{po_link_to} eq "current"
143             && istranslatable($link)
144             && istranslation($page)) {
145                 my ($masterpage, $curlang) = ($page =~ /(.*)[.]([a-z]{2})$/);
146                 return $link . "." . $curlang;
147         }
148         return $link;
149 } #}}}
150
151 our %filtered;
152 # We use filter to convert PO to the master page's type,
153 # since other plugins should not work on PO files
154 sub filter (@) { #{{{
155         my %params = @_;
156         my $page = $params{page};
157         my $destpage = $params{destpage};
158         my $content = decode_utf8(encode_utf8($params{content}));
159
160         # decide if this is a PO file that should be converted into a translated document,
161         # and perform various sanity checks
162         if (! istranslation($page) || $filtered{$page}{$destpage}) {
163                 return $content;
164         }
165
166         my ($masterpage, $lang) = ($page =~ /(.*)[.]([a-z]{2})$/);
167         my $file=srcfile(exists $params{file} ? $params{file} : $IkiWiki::pagesources{$page});
168         my $masterfile = srcfile($pagesources{$masterpage});
169         my (@pos,@masters);
170         push @pos,$file;
171         push @masters,$masterfile;
172         my %options = (
173                         "markdown" => (pagetype($masterfile) eq 'mdwn') ? 1 : 0,
174                         );
175         my $doc=Locale::Po4a::Chooser::new('text',%options);
176         $doc->process(
177                 'po_in_name'    => \@pos,
178                 'file_in_name'  => \@masters,
179                 'file_in_charset'  => 'utf-8',
180                 'file_out_charset' => 'utf-8',
181         ) or error("[po/filter:$file]: failed to translate");
182         my ($percent,$hit,$queries) = $doc->stats();
183         my $tmpfh = File::Temp->new(TEMPLATE => "/tmp/ikiwiki-po-filter-out.XXXXXXXXXX");
184         my $tmpout = $tmpfh->filename;
185         $doc->write($tmpout) or error("[po/filter:$file] could not write $tmpout");
186         $content = readfile($tmpout) or error("[po/filter:$file] could not read $tmpout");
187         $filtered{$page}{$destpage}=1;
188         return $content;
189 } #}}}
190
191 sub htmlize (@) { #{{{
192         my %params=@_;
193         my $page = $params{page};
194         my $content = $params{content};
195         my ($masterpage, $lang) = ($page =~ /(.*)[.]([a-z]{2})$/);
196         my $masterfile = srcfile($pagesources{$masterpage});
197
198         # force content to be htmlize'd as if it was the same type as the master page
199         return IkiWiki::htmlize($page, $page, pagetype($masterfile), $content);
200 } #}}}
201
202 sub otherlanguages ($) { #{{{
203         my $page=shift;
204         my @ret;
205         if (istranslatable($page)) {
206                 foreach my $lang (sort keys %{$translations{$page}}) {
207                         push @ret, {
208                                 url => urlto($translations{$page}{$lang}, $page),
209                                 code => $lang,
210                                 language => $config{po_slave_languages}{$lang},
211                                 master => 0,
212                         };
213                 }
214         }
215         elsif (istranslation($page)) {
216                 my ($masterpage, $curlang) = ($page =~ /(.*)[.]([a-z]{2})$/);
217                 push @ret, {
218                         url => urlto($masterpage, $page),
219                         code => $config{po_master_language}{code},
220                         language => $config{po_master_language}{name},
221                         master => 1,
222                 };
223                 foreach my $lang (sort keys %{$translations{$masterpage}}) {
224                         push @ret, {
225                                 url => urlto($translations{$masterpage}{$lang}, $page),
226                                 code => $lang,
227                                 language => $config{po_slave_languages}{$lang},
228                                 master => 0,
229                         } unless ($lang eq $curlang);
230                 }
231         }
232         return @ret;
233 } #}}}
234
235 sub pagetemplate (@) { #{{{
236         my %params=@_;
237         my $page=$params{page};
238         my $template=$params{template};
239
240         if ($template->query(name => "otherlanguages")) {
241                 $template->param(otherlanguages => [otherlanguages($page)]);
242         }
243 } # }}}
244
245 sub istranslatable ($) { #{{{
246         my $page=shift;
247         my $file=$pagesources{$page};
248
249         if (! defined $file
250             || (defined pagetype($file) && pagetype($file) eq 'po')
251             || $file =~ /\.pot$/) {
252                 return 0;
253         }
254         return pagespec_match($page, $config{po_translatable_pages});
255 } #}}}
256
257 sub _istranslation ($) { #{{{
258         my $page=shift;
259         my $file=$pagesources{$page};
260         if (! defined $file) {
261                 return IkiWiki::FailReason->new("no file specified");
262         }
263
264         if (! defined $file
265             || ! defined pagetype($file)
266             || ! pagetype($file) eq 'po'
267             || $file =~ /\.pot$/) {
268                 return 0;
269         }
270
271         my ($masterpage, $lang) = ($page =~ /(.*)[.]([a-z]{2})$/);
272         if (! defined $masterpage || ! defined $lang
273             || ! (length($masterpage) > 0) || ! (length($lang) > 0)
274             || ! defined $pagesources{$masterpage}
275             || ! defined $config{po_slave_languages}{$lang}) {
276                 return 0;
277         }
278
279         return istranslatable($masterpage);
280 } #}}}
281
282 sub istranslation ($) { #{{{
283         my $page=shift;
284         if (_istranslation($page)) {
285                 my ($masterpage, $lang) = ($page =~ /(.*)[.]([a-z]{2})$/);
286                 $translations{$masterpage}{$lang}=$page unless exists $translations{$masterpage}{$lang};
287                 return 1;
288         }
289         return 0;
290 } #}}}
291
292 package IkiWiki::PageSpec;
293 use warnings;
294 use strict;
295 use IkiWiki 2.00;
296
297 sub match_istranslation ($;@) { #{{{
298         my $page=shift;
299         if (IkiWiki::Plugin::po::istranslation($page)) {
300                 return IkiWiki::SuccessReason->new("is a translation page");
301         }
302         else {
303                 return IkiWiki::FailReason->new("is not a translation page");
304         }
305 } #}}}
306
307 sub match_istranslatable ($;@) { #{{{
308         my $page=shift;
309         if (IkiWiki::Plugin::po::istranslatable($page)) {
310                 return IkiWiki::SuccessReason->new("is set as translatable in po_translatable_pages");
311         }
312         else {
313                 return IkiWiki::FailReason->new("is not set as translatable in po_translatable_pages");
314         }
315 } #}}}
316
317 1