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