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