po plugin: implemented po_link_to=current
[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
14 sub import {
15         hook(type => "getsetup", id => "po", call => \&getsetup);
16         hook(type => "checkconfig", id => "po", call => \&checkconfig);
17         hook(type => "targetpage", id => "po", call => \&targetpage);
18         hook(type => "tweakurlpath", id => "po", call => \&tweakurlpath);
19         hook(type => "tweakbestlink", id => "po", call => \&tweakbestlink);
20         hook(type => "filter", id => "po", call => \&filter);
21         hook(type => "preprocess", id => "translatable", call => \&preprocess_translatable);
22         hook(type => "htmlize", id => "po", call => \&htmlize);
23 }
24
25 sub getsetup () { #{{{
26         return
27                 plugin => {
28                         safe => 0,
29                         rebuild => 1, # format plugin
30                 },
31                 po_master_language => {
32                         type => "string",
33                         example => {
34                                 'code' => 'en',
35                                 'name' => 'English'
36                         },
37                         description => "master language (non-PO files)",
38                         safe => 1,
39                         rebuild => 1,
40                 },
41                 po_slave_languages => {
42                         type => "string",
43                         example => {'fr' => { 'name' => 'Fran├žais' },
44                                     'es' => { 'name' => 'Castellano' },
45                                     'de' => { 'name' => 'Deutsch' },
46                         },
47                         description => "slave languages (PO files)",
48                         safe => 1,
49                         rebuild => 1,
50                 },
51                 po_link_to => {
52                         type => "string",
53                         example => "current",
54                         description => "internal linking behavior (default/current/negotiated)",
55                         safe => 1,
56                         rebuild => 1,
57                 },
58 } #}}}
59
60 sub checkconfig () { #{{{
61         foreach my $field (qw{po_master_language po_slave_languages}) {
62                 if (! exists $config{$field} || ! defined $config{$field}) {
63                         error(sprintf(gettext("Must specify %s"), $field));
64                 }
65         }
66         if (! exists $config{po_link_to} ||
67             ! defined $config{po_link_to}) {
68             $config{po_link_to}="default";
69         }
70         if ($config{po_link_to} eq "negotiated" && ! $config{usedirs}) {
71                 error(gettext("po_link_to=negotiated requires usedirs to be set"));
72         }
73 } #}}}
74
75 sub targetpage (@) { #{{{
76         my %params = @_;
77         my $page=$params{page};
78         my $ext=$params{ext};
79
80         if (pagespec_match($page,"istranslation()")) {
81                 my ($masterpage, $lang) = ($page =~ /(.*)[.]([a-z]{2})$/);
82                 if (! $config{usedirs} || $page eq 'index') {
83                         return $masterpage . "." . $lang . "." . $ext;
84                 }
85                 else {
86                         return $masterpage . "/index." . $lang . "." . $ext;
87                 }
88         }
89         elsif (pagespec_match($page,"istranslatable()")) {
90                 if (! $config{usedirs} || $page eq 'index') {
91                         return $page . "." . $config{po_master_language}{code} . "." . $ext;
92                 }
93                 else {
94                         return $page . "/index." . $config{po_master_language}{code} . "." . $ext;
95                 }
96         }
97         return;
98 } #}}}
99
100 sub tweakurlpath ($) { #{{{
101         my %params = @_;
102         my $url=$params{url};
103         if ($config{po_link_to} eq "negotiated") {
104                 $url =~ s!/index.$config{po_master_language}{code}.$config{htmlext}$!/!;
105         }
106         return $url;
107 } #}}}
108
109 sub tweakbestlink ($$) { #{{{
110         my %params = @_;
111         my $page=$params{page};
112         my $link=$params{link};
113         if ($config{po_link_to} eq "current" && pagespec_match($link, "istranslatable()")) {
114                 if (pagespec_match($page, "istranslation()")) {
115                         my ($masterpage, $curlang) = ($page =~ /(.*)[.]([a-z]{2})$/);
116                         return $link . "." . $curlang;
117                 }
118         }
119         return $link;
120 } #}}}
121
122 # We use filter to convert PO to the master page's type,
123 # since other plugins should not work on PO files
124 sub filter (@) { #{{{
125         my %params = @_;
126         my $page = $params{page};
127         my $content = decode_utf8(encode_utf8($params{content}));
128
129         # decide if this is a PO file that should be converted into a translated document,
130         # and perform various sanity checks
131         if (! pagespec_match($page, "istranslation()")) {
132                 return $content;
133         }
134
135         my ($masterpage, $lang) = ($page =~ /(.*)[.]([a-z]{2})$/);
136         my $file=srcfile(exists $params{file} ? $params{file} : $IkiWiki::pagesources{$page});
137         my $masterfile = srcfile($pagesources{$masterpage});
138         my (@pos,@masters);
139         push @pos,$file;
140         push @masters,$masterfile;
141         my %options = (
142                         "markdown" => (pagetype($masterfile) eq 'mdwn') ? 1 : 0,
143                         );
144         my $doc=Locale::Po4a::Chooser::new('text',%options);
145         $doc->process(
146                 'po_in_name'    => \@pos,
147                 'file_in_name'  => \@masters,
148                 'file_in_charset'  => 'utf-8',
149                 'file_out_charset' => 'utf-8',
150         ) or error("[po/filter:$file]: failed to translate");
151         my ($percent,$hit,$queries) = $doc->stats();
152         my $tmpfh = File::Temp->new(TEMPLATE => "/tmp/ikiwiki-po-filter-out.XXXXXXXXXX");
153         my $tmpout = $tmpfh->filename;
154         $doc->write($tmpout) or error("[po/filter:$file] could not write $tmpout");
155         $content = readfile($tmpout) or error("[po/filter:$file] could not read $tmpout");
156         return $content;
157 } #}}}
158
159 sub preprocess_translatable (@) { #{{{
160         my %params = @_;
161         my $match = exists $params{match} ? $params{match} : $params{page};
162
163         $pagestate{$params{page}}{po_translatable}{$match}=1;
164
165         return "" if ($params{silent} && IkiWiki::yesno($params{silent}));
166         return sprintf(gettext("pages %s set as translatable"), $params{match});
167
168 } #}}}
169
170 sub htmlize (@) { #{{{
171         my %params=@_;
172         my $page = $params{page};
173         my $content = $params{content};
174         my ($masterpage, $lang) = ($page =~ /(.*)[.]([a-z]{2})$/);
175         my $masterfile = srcfile($pagesources{$masterpage});
176
177         # force content to be htmlize'd as if it was the same type as the master page
178         return IkiWiki::htmlize($page, $page, pagetype($masterfile), $content);
179 } #}}}
180
181 package IkiWiki::PageSpec;
182 use warnings;
183 use strict;
184 use IkiWiki 2.00;
185
186 sub match_istranslation ($;@) { #{{{
187         my $page=shift;
188         my $wanted=shift;
189
190         my %params=@_;
191         my $file=exists $params{file} ? $params{file} : $pagesources{$page};
192         if (! defined $file) {
193                 return IkiWiki::FailReason->new("no file specified");
194         }
195
196         if (! defined pagetype($file) || ! pagetype($file) eq 'po') {
197                 return IkiWiki::FailReason->new("is not a PO file");
198         }
199
200         my ($masterpage, $lang) = ($page =~ /(.*)[.]([a-z]{2})$/);
201         if (! defined $masterpage || ! defined $lang
202             || ! (length($masterpage) > 0) || ! (length($lang) > 0)) {
203                 return IkiWiki::FailReason->new("is not named like a translation file");
204         }
205
206         if (! defined $pagesources{$masterpage}) {
207                 return IkiWiki::FailReason->new("the master page does not exist");
208         }
209
210         if (! defined $config{po_slave_languages}{$lang}) {
211                 return IkiWiki::FailReason->new("language $lang is not supported");
212         }
213
214         return IkiWiki::SuccessReason->new("page $page is a translation");
215 } #}}}
216
217 sub match_istranslatable ($;@) { #{{{
218         my $page=shift;
219         my $wanted=shift;
220
221         my %params=@_;
222         my $file=exists $params{file} ? $params{file} : $pagesources{$page};
223         if (! defined $file) {
224                 return IkiWiki::FailReason->new("no file specified");
225         }
226
227         if (defined pagetype($file) && pagetype($file) eq 'po') {
228                 return IkiWiki::FailReason->new("is a PO file");
229         }
230         if ($file =~ /\.pot$/) {
231                 return IkiWiki::FailReason->new("is a POT file");
232         }
233
234         foreach my $registering_page (keys %pagestate) {
235                 if (exists $pagestate{$registering_page}{po_translatable}) {
236                         foreach my $pagespec (sort keys %{$pagestate{$registering_page}{po_translatable}}) {
237                                 if (pagespec_match($page, $pagespec, location => $registering_page)) {
238                                         return IkiWiki::SuccessReason->new("is set as translatable on $registering_page");
239                                 }
240                         }
241                 }
242         }
243
244         return IkiWiki::FailReason->new("is not set as translatable");
245 } #}}}
246
247 1