translation-links.pl 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. # Copyright (C) 2008, 2012 Alex Schroeder <alex@gnu.org>
  2. #
  3. # This program is free software; you can redistribute it and/or modify it under
  4. # the terms of the GNU General Public License as published by the Free Software
  5. # Foundation; either version 3 of the License, or (at your option) any later
  6. # version.
  7. #
  8. # This program is distributed in the hope that it will be useful, but WITHOUT
  9. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  10. # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
  11. # details.
  12. #
  13. # You should have received a copy of the GNU General Public License along with
  14. # this program. If not, see <http://www.gnu.org/licenses/>.
  15. use strict;
  16. use v5.10;
  17. AddModuleDescription('translation-links.pl', 'Translation Links');
  18. =head1 Translation Links
  19. This package allows Oddmuse to support translation links.
  20. The prerequisite is that you set the C<%Languages> option.
  21. Example:
  22. %Languages = ('de' => '\b(der|die|das|und|oder)\b',
  23. 'en' => '\b(the|he|she|that|this)\b');
  24. This defines the I<known languages>: de and en, in our example.
  25. The known languages should not contain any characters with a special
  26. meaning in a regular expresion, nor should it contain a space or an
  27. underscore.
  28. =head2 %TranslationLinkTarget
  29. This option maps languages to URLs. If this option is not set, all
  30. links will point to the default URL of the wiki, C<$ScriptName>.
  31. You can use this together with the namespace extension. Here's an
  32. example:
  33. $TranslationLinkTarget{en} = "$ScriptName/En";
  34. $TranslationLinkTarget{de} = "$ScriptName/De";
  35. Or you can use a setup using a different wrapper script per language:
  36. $TranslationLinkTarget{en} = "$ScriptName-en";
  37. $TranslationLinkTarget{de} = "$ScriptName-de";
  38. =head2 $TranslationLinkHelpPage
  39. When translating a page, people will be offered a link to a help page.
  40. The default page is called TranslationHelp.
  41. =cut
  42. our ($q, %Action, %Page, $OpenPageName, %Translate, %Languages, $LinkPattern, $FreeLinks, $FreeLinkPattern, $WikiLinks, @MyRules, @MyInitVariables, $FS);
  43. our ($TranslationLinkPattern, %TranslationLinkTarget,
  44. $TranslationLinkHelpPage);
  45. $TranslationLinkHelpPage = 'TranslationHelp';
  46. =head2 $TranslationLinkPattern
  47. This regular expression is formed via all the defined languages and
  48. C<$FreeLinkPattern> if C<$FreeLinks> is set and/or C<$LinkPattern> if
  49. C<$WikiLinks> is set. It matches things such as C<[[en:HomePage]]>.
  50. =cut
  51. push(@MyInitVariables, \&TranslationLinkInit);
  52. my %TranslationLinkData;
  53. sub TranslationLinkInit {
  54. $TranslationLinkPattern = '\[\[(' . join('|', keys %Languages) . '):(';
  55. $TranslationLinkPattern .= $FreeLinkPattern if $FreeLinks;
  56. $TranslationLinkPattern .= '|' if $FreeLinks or $WikiLinks;
  57. $TranslationLinkPattern .= $LinkPattern if $WikiLinks;
  58. $TranslationLinkPattern .= ')\]\]';
  59. %TranslationLinkData = ();
  60. }
  61. =head2 TranslationLinkRule
  62. The translation links will not be rendered, that is text such as
  63. C<[[en:HomePage]]> does not produce any HTML output directly. We will
  64. collect the translation links as we go, however, and store them in our
  65. C<%Page> which will be saved to disk if no HTML cache exists.
  66. =cut
  67. push(@MyRules, \&TranslationLinkRule);
  68. sub TranslationLinkRule {
  69. if (m/\G$TranslationLinkPattern/cg) {
  70. $TranslationLinkData{$1} = $2;
  71. $Page{translations} = join($FS, %TranslationLinkData);
  72. return '';
  73. }
  74. return;
  75. }
  76. =head2 Footer Links
  77. We hook into C<GetFooterLinks> and print a line of translations, as
  78. well as a link to the translate action.
  79. =cut
  80. *TranslationLinkOldGetFooterLinks = \&GetFooterLinks;
  81. *GetFooterLinks = \&TranslationLinkNewGetFooterLinks;
  82. sub TranslationLinkNewGetFooterLinks {
  83. my $html = TranslationLinkOldGetFooterLinks(@_);
  84. my ($id, $rev) = @_;
  85. if ($id and not $rev) {
  86. OpenPage($id);
  87. my $bar;
  88. my %translations;
  89. if ($Page{translations}) {
  90. %translations = split(/$FS/, $Page{translations});
  91. $bar = join(' ', map {
  92. my $url;
  93. if ($TranslationLinkTarget{$_}) {
  94. $url = $TranslationLinkTarget{$_};
  95. $url =~ s/\%s/$translations{$_}/g or $url .= $translations{$_};
  96. } else {
  97. $url = ScriptUrl($translations{$_});
  98. }
  99. $q->a({-href=>$url, -class=>"translation $_"}, T($_));
  100. } keys %translations);
  101. }
  102. my %missing;
  103. foreach (keys %Languages) {
  104. if (not $translations{$_}) {
  105. $missing{$_} = 1;
  106. }
  107. }
  108. # If the current page is autodetected to have exactly one
  109. # translation, then remove that language from the list of missing
  110. # languages.
  111. my @current = split(/,/, $Page{languages});
  112. if ($#current == 0) {
  113. delete $missing{$current[0]};
  114. }
  115. if (scalar keys %missing) {
  116. $bar .= ' ' . ScriptLink("action=translate;id=$id;missing="
  117. . join('_', sort keys %missing),
  118. T('Add Translation'), 'translation new');
  119. }
  120. $html = $q->span({-class=>'translation bar'}, $q->br(), $bar) . $html;
  121. }
  122. return $html;
  123. }
  124. =head2 Translate Action
  125. The translate action knows what page the user is trying to translate,
  126. and it knows what translations seem to be missing. By selecting the
  127. appropriate checkbox, a translation link will be added to the source
  128. page.
  129. =cut
  130. $Action{translate} = \&DoTranslationLink;
  131. sub DoTranslationLink {
  132. my $source = shift;
  133. my $target = FreeToNormal(GetParam('target', ''));
  134. my $error = ValidId($target)
  135. || ($source eq $target and T("Please provide a different page name for the translation."));
  136. my $lang = GetParam('translation', '');
  137. if (not $error and $lang) {
  138. # make sure the translation target cannot contain spam if using banning-regexps.pl
  139. if (my $rule = BannedContent(NormalToFree($source)) || BannedContent(NormalToFree($target))) {
  140. ReportError(T('Edit Denied'), '403 FORBIDDEN', undef, $q->p(T('The page contains banned text.')),
  141. $q->p(T('Contact the wiki administrator for more information.')), $q->p($rule));
  142. }
  143. OpenPage(FreeToNormal($source));
  144. Save($OpenPageName, "[[$lang:$target]]\n" . $Page{text},
  145. Tss('Added translation: %1 (%2)',
  146. NormalToFree($target), T($lang)), 1);
  147. DoEdit($target);
  148. } else {
  149. my @missing = split(/_/, GetParam('missing', ''));
  150. print GetHeader(undef, Ts('Translate %s', NormalToFree($source)));
  151. print $q->start_div({-class=>'content translate'}), GetFormStart();
  152. print $q->p(Ts('Thank you for writing a translation of %s.', $source),
  153. T('Please indicate what language you will be using.'));
  154. if (defined $q->param('target') and not $lang) {
  155. print $q->div({-class=>'message'}, $q->p(T('Language is missing')));
  156. }
  157. print $q->p(T('Suggested languages:')),
  158. $q->p($q->radio_group(-name=>'translation',
  159. -values=>\@missing,
  160. -linebreak=>'true',
  161. -labels=>\%Translate));
  162. print $q->p(Ts('Please indicate a page name for the translation of %s.',
  163. $source),
  164. Ts('More help may be available here: %s.',
  165. GetPageLink($TranslationLinkHelpPage)));
  166. if (defined $q->param('target') and $error) {
  167. print $q->div({-class=>'message'}, $q->p($error));
  168. }
  169. print $q->p($q->label({-for=>'target'}, T('Translated page:')), ' ',
  170. $q->textfield('target', '', 40),
  171. # don't use $q->hidden or you'll get encoding errors
  172. $q->input({-type=>'hidden', -name=>'id',
  173. -value=>$source}),
  174. $q->input({-type=>'hidden', -name=>'action',
  175. -value=>'translate'}),
  176. $q->input({-type=>'hidden', -name=>'missing',
  177. -value=>GetParam('missing', '')}),
  178. $q->submit('dotranslate', T('Go!')));
  179. print $q->end_form, $q->end_div();
  180. PrintFooter();
  181. }
  182. }