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