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