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