]> git.vanrenterghem.biz Git - git.ikiwiki.info.git/commitdiff
Merge commit 'upstream/po' into prv/po
authorintrigeri <intrigeri@boum.org>
Thu, 6 Nov 2008 11:28:31 +0000 (12:28 +0100)
committerintrigeri <intrigeri@boum.org>
Thu, 6 Nov 2008 11:28:31 +0000 (12:28 +0100)
Conflicts:

IkiWiki/Plugin/po.pm
doc/plugins/po.mdwn

Signed-off-by: intrigeri <intrigeri@boum.org>
1  2 
IkiWiki/Plugin/po.pm
doc/plugins/po.mdwn

diff --combined IkiWiki/Plugin/po.pm
index 38fc7527bcd668e765baa002e1a3f6bbef53d219,ac56041cfbe00f00e0c371ba3cd8b41cb3ed1637..7861523dad8076122515dd688a3aac307692c288
@@@ -19,37 -19,36 +19,38 @@@ use Memoize
  my %translations;
  our %filtered;
  
 -## FIXME: makes some test cases cry once every two tries; this may be
 -## related to the artificial way the testsuite is run, or not.
 -# memoize("istranslatable");
  memoize("_istranslation");
  memoize("percenttranslated");
 +# FIXME: memoizing istranslatable() makes some test cases fail once every
 +# two tries; this may be related to the artificial way the testsuite is
 +# run, or not.
 +# memoize("istranslatable");
  
  # backup references to subs that will be overriden
  my %origsubs;
- $origsubs{'bestlink'}=\&IkiWiki::bestlink;
- $origsubs{'beautify_urlpath'}=\&IkiWiki::beautify_urlpath;
- $origsubs{'targetpage'}=\&IkiWiki::targetpage;
  
- sub import {
+ sub import { #{{{
        hook(type => "getsetup", id => "po", call => \&getsetup);
        hook(type => "checkconfig", id => "po", call => \&checkconfig);
        hook(type => "needsbuild", id => "po", call => \&needsbuild);
        hook(type => "filter", id => "po", call => \&filter);
        hook(type => "htmlize", id => "po", call => \&htmlize);
 -      hook(type => "pagetemplate", id => "po", call => \&pagetemplate);
 +      hook(type => "pagetemplate", id => "po", call => \&pagetemplate, last => 1);
 +      hook(type => "editcontent", id => "po", call => \&editcontent);
+       $origsubs{'bestlink'}=\&IkiWiki::bestlink;
        inject(name => "IkiWiki::bestlink", call => \&mybestlink);
+       $origsubs{'beautify_urlpath'}=\&IkiWiki::beautify_urlpath;
        inject(name => "IkiWiki::beautify_urlpath", call => \&mybeautify_urlpath);
+       $origsubs{'targetpage'}=\&IkiWiki::targetpage;
        inject(name => "IkiWiki::targetpage", call => \&mytargetpage);
- }
+ } #}}}
  
  sub getsetup () { #{{{
        return
                plugin => {
                        safe => 0,
-                       rebuild => 1, # format plugin
+                       rebuild => 1, # format plugin & changes html filenames
                },
                po_master_language => {
                        type => "string",
@@@ -97,11 -96,11 +98,11 @@@ sub checkconfig () { #{{
        }
        if (! exists $config{po_link_to} ||
            ! defined $config{po_link_to}) {
-           $config{po_link_to}="default";
+               $config{po_link_to}="default";
        }
        if (! exists $config{po_translatable_pages} ||
            ! defined $config{po_translatable_pages}) {
-           $config{po_translatable_pages}="";
+               $config{po_translatable_pages}="";
        }
        if ($config{po_link_to} eq "negotiated" && ! $config{usedirs}) {
                error(gettext("po_link_to=negotiated requires usedirs to be set"));
  
  sub potfile ($) { #{{{
        my $masterfile=shift;
        (my $name, my $dir, my $suffix) = fileparse($masterfile, qr/\.[^.]*/);
 -      return File::Spec->catfile($dir, $name . ".pot");
 +      $dir='' if $dir eq './';
 +      return File::Spec->catpath('', $dir, $name . ".pot");
  } #}}}
  
  sub pofile ($$) { #{{{
        my $masterfile=shift;
        my $lang=shift;
        (my $name, my $dir, my $suffix) = fileparse($masterfile, qr/\.[^.]*/);
 -      return File::Spec->catfile($dir, $name . "." . $lang . ".po");
 +      $dir='' if $dir eq './';
 +      return File::Spec->catpath('', $dir, $name . "." . $lang . ".po");
  } #}}}
  
  sub refreshpot ($) { #{{{
        my $masterfile=shift;
        my $potfile=potfile($masterfile);
        my %options = ("markdown" => (pagetype($masterfile) eq 'mdwn') ? 1 : 0);
        my $doc=Locale::Po4a::Chooser::new('text',%options);
@@@ -153,8 -153,7 +157,7 @@@ sub refreshpofiles ($@) { #{{
  
        foreach my $pofile (@pofiles) {
                if (-e $pofile) {
-                       my $cmd = "msgmerge -U --backup=none $pofile $potfile";
-                       system ($cmd) == 0
+                       system("msgmerge", "-U", "--backup=none", $pofile, $potfile) == 0
                                or error("[po/refreshpofiles:$pofile] failed to update");
                }
                else {
@@@ -175,22 -174,17 +178,22 @@@ sub needsbuild () { #{{
        # refresh/create POT and PO files as needed
        my $updated_po_files=0;
        foreach my $page (keys %pagesources) {
 -              my $pageneedsbuild = grep { $_ eq $pagesources{$page} } @$needsbuild;
                if (istranslatable($page)) {
 +                      my $pageneedsbuild = grep { $_ eq $pagesources{$page} } @$needsbuild;
 +                      my $updated_pot_file=0;
                        my $file=srcfile($pagesources{$page});
                        if ($pageneedsbuild || ! -e potfile($file)) {
                                refreshpot($file);
 +                              $updated_pot_file=1;
                        }
                        my @pofiles;
                        foreach my $lang (keys %{$config{po_slave_languages}}) {
                                my $pofile=pofile($file, $lang);
 -                              if ($pageneedsbuild || ! -e $pofile) {
 +                              my $pofile_rel=pofile($pagesources{$page}, $lang);
 +                              if ($pageneedsbuild || $updated_pot_file || ! -e $pofile) {
                                        push @pofiles, $pofile;
 +                                      push @$needsbuild, $pofile_rel
 +                                        unless grep { $_ eq $pofile_rel } @$needsbuild;
                                }
                        }
                        if (@pofiles) {
                }
        }
  
 -      # check staged changes in and trigger a wiki refresh.
 +      # check staged changes in
        if ($updated_po_files) {
                if ($config{rcs}) {
                        IkiWiki::disable_commit_hook();
                        IkiWiki::enable_commit_hook();
                        IkiWiki::rcs_update();
                }
 -              IkiWiki::refresh();
 -              IkiWiki::saveindex();
                # refresh module's private variables
                undef %filtered;
                undef %translations;
@@@ -252,9 -248,10 +255,10 @@@ sub mytargetpage ($$) { #{{
  
  sub mybeautify_urlpath ($) { #{{{
        my $url=shift;
        my $res=$origsubs{'beautify_urlpath'}->($url);
        if ($config{po_link_to} eq "negotiated") {
-               $res =~ s!/index.$config{po_master_language}{code}.$config{htmlext}$!/!;
+               $res =~ s!/\Qindex.$config{po_master_language}{code}.$config{htmlext}\E$!/!;
        }
        return $res;
  } #}}}
@@@ -273,6 -270,7 +277,7 @@@ sub urlto_with_orig_beautiful_urlpath($
  sub mybestlink ($$) { #{{{
        my $page=shift;
        my $link=shift;
        my $res=$origsubs{'bestlink'}->($page, $link);
        if (length $res) {
                if ($config{po_link_to} eq "current"
        return "";
  } #}}}
  
 -# We use filter to convert PO to the master page's type,
 -# since other plugins should not work on PO files
 +# We use filter to convert PO to the master page's format,
 +# since the rest of ikiwiki should not work on PO files.
  sub filter (@) { #{{{
        my %params = @_;
        my $page = $params{page};
        my $destpage = $params{destpage};
        my $content = decode_utf8(encode_utf8($params{content}));
  
 -      # decide if this is a PO file that should be converted into a
 -      # translated document, and perform various sanity checks
 -      if (! istranslation($page) || $filtered{$page}{$destpage}) {
 -              return $content;
 -      }
 +      return $content if ( ! istranslation($page)
 +                           || ( exists $filtered{$page}{$destpage}
 +                                && $filtered{$page}{$destpage} eq 1 ));
 +
 +      # CRLF line terminators make poor Locale::Po4a feel bad
 +      $content=~s/\r\n/\n/g;
 +
 +      # Implementation notes
 +      #
 +      # 1. Locale::Po4a reads/writes from/to files, and I'm too lazy
 +      #    to learn how to disguise a variable as a file.
 +      # 2. There are incompatibilities between some File::Temp versions
 +      #    (including 0.18, bundled with Lenny's perl-modules package)
 +      #    and others (e.g. 0.20, previously present in the archive as
 +      #    a standalone package): under certain circumstances, some
 +      #    return a relative filename, whereas others return an absolute one;
 +      #    we here use this module in a way that is at least compatible
 +      #    with 0.18 and 0.20. Beware, hit'n'run refactorers!
 +      my $infile = new File::Temp(TEMPLATE => "ikiwiki-po-filter-in.XXXXXXXXXX",
 +                                  DIR => File::Spec->tmpdir,
 +                                  UNLINK => 1)->filename;
 +      my $outfile = new File::Temp(TEMPLATE => "ikiwiki-po-filter-out.XXXXXXXXXX",
 +                                   DIR => File::Spec->tmpdir,
 +                                   UNLINK => 1)->filename;
 +
 +      writefile(basename($infile), File::Spec->tmpdir, $content);
  
        my ($masterpage, $lang) = ($page =~ /(.*)[.]([a-z]{2})$/);
 -      my $file=srcfile(exists $params{file} ? $params{file} : $IkiWiki::pagesources{$page});
        my $masterfile = srcfile($pagesources{$masterpage});
        my (@pos,@masters);
 -      push @pos,$file;
 +      push @pos,$infile;
        push @masters,$masterfile;
        my %options = (
-                       "markdown" => (pagetype($masterfile) eq 'mdwn') ? 1 : 0,
-                       );
+               "markdown" => (pagetype($masterfile) eq 'mdwn') ? 1 : 0,
+       );
        my $doc=Locale::Po4a::Chooser::new('text',%options);
        $doc->process(
                'po_in_name'    => \@pos,
                'file_in_name'  => \@masters,
                'file_in_charset'  => 'utf-8',
                'file_out_charset' => 'utf-8',
 -      ) or error("[po/filter:$file]: failed to translate");
 -      my $tmpfh = File::Temp->new(TEMPLATE => "/tmp/ikiwiki-po-filter-out.XXXXXXXXXX");
 -      my $tmpout = $tmpfh->filename;
 -      # XXX is there any way to avoid the useless write to a temp file? --Joey
 -      $doc->write($tmpout) or error("[po/filter:$file] could not write $tmpout");
 -      $content = readfile($tmpout) or error("[po/filter:$file] could not read $tmpout");
 +      ) or error("[po/filter:$infile]: failed to translate");
 +      $doc->write($outfile) or error("[po/filter:$infile] could not write $outfile");
 +      $content = readfile($outfile) or error("[po/filter:$infile] could not read $outfile");
 +
 +      # Unlinking should happen automatically, thanks to File::Temp,
 +      # but it does not work here, probably because of the way writefile()
 +      # and Locale::Po4a::write() work.
 +      unlink $infile, $outfile;
 +
        $filtered{$page}{$destpage}=1;
        return $content;
  } #}}}
  
  sub htmlize (@) { #{{{
        my %params=@_;
        my $page = $params{page};
        my $content = $params{content};
        my ($masterpage, $lang) = ($page =~ /(.*)[.]([a-z]{2})$/);
  
  sub percenttranslated ($) { #{{{
        my $page=shift;
-       return "N/A" unless (istranslation($page));
+       return gettext("N/A") unless (istranslation($page));
        my ($masterpage, $lang) = ($page =~ /(.*)[.]([a-z]{2})$/);
        my $file=srcfile($pagesources{$page});
        my $masterfile = srcfile($pagesources{$masterpage});
        push @pos,$file;
        push @masters,$masterfile;
        my %options = (
-                       "markdown" => (pagetype($masterfile) eq 'mdwn') ? 1 : 0,
-                       );
+               "markdown" => (pagetype($masterfile) eq 'mdwn') ? 1 : 0,
+       );
        my $doc=Locale::Po4a::Chooser::new('text',%options);
        $doc->process(
                'po_in_name'    => \@pos,
  
  sub otherlanguages ($) { #{{{
        my $page=shift;
        my @ret;
        if (istranslatable($page)) {
                foreach my $lang (sort keys %{$translations{$page}}) {
  
  sub pagetemplate (@) { #{{{
        my %params=@_;
 -        my $page=$params{page};
 +      my $page=$params{page};
 +      my $destpage=$params{destpage};
 +      my $template=$params{template};
 -        my $destpage=$params{destpage};
 -        my $template=$params{template};
 +      my ($masterpage, $lang) = ($page =~ /(.*)[.]([a-z]{2})$/) if istranslation($page);
  
        if (istranslation($page) && $template->query(name => "percenttranslated")) {
                $template->param(percenttranslated => percenttranslated($page));
                        }
                }
                elsif (istranslation($page)) {
 -                      my ($masterpage, $curlang) = ($page =~ /(.*)[.]([a-z]{2})$/);
                        add_depends($page, $masterpage);
                        foreach my $translation (values %{$translations{$masterpage}}) {
                                add_depends($page, $translation);
        # prevent future breakage when ikiwiki internals change.
        # Known limitations are preferred to future random bugs.
        if ($template->param('discussionlink') && istranslation($page)) {
 -              my ($masterpage, $lang) = ($page =~ /(.*)[.]([a-z]{2})$/);
                $template->param('discussionlink' => htmllink(
                                                        $page,
                                                        $destpage,
                                                        linktext => gettext("Discussion"),
                                                        ));
        }
 +      # remove broken parentlink to ./index.html on home page's translations
 +      if ($template->param('parentlinks')
 +          && istranslation($page)
 +          && $masterpage eq "index") {
 +              $template->param('parentlinks' => []);
 +      }
  } # }}}
  
 +sub editcontent () { #{{{
 +      my %params=@_;
 +      # as we're previewing or saving a page, the content may have
 +      # changed, so tell the next filter() invocation it must not be lazy
 +      if (exists $filtered{$params{page}}{$params{page}}) {
 +              delete $filtered{$params{page}}{$params{page}};
 +      }
 +      return $params{content};
 +} #}}}
 +
  sub istranslatable ($) { #{{{
        my $page=shift;
        my $file=$pagesources{$page};
  
        if (! defined $file
  
  sub _istranslation ($) { #{{{
        my $page=shift;
        my $file=$pagesources{$page};
        if (! defined $file) {
                return IkiWiki::FailReason->new("no file specified");
  
  sub istranslation ($) { #{{{
        my $page=shift;
        if (_istranslation($page)) {
                my ($masterpage, $lang) = ($page =~ /(.*)[.]([a-z]{2})$/);
                $translations{$masterpage}{$lang}=$page unless exists $translations{$masterpage}{$lang};
@@@ -537,6 -505,7 +550,7 @@@ use IkiWiki 2.00
  
  sub match_istranslation ($;@) { #{{{
        my $page=shift;
        if (IkiWiki::Plugin::po::istranslation($page)) {
                return IkiWiki::SuccessReason->new("is a translation page");
        }
  
  sub match_istranslatable ($;@) { #{{{
        my $page=shift;
        if (IkiWiki::Plugin::po::istranslatable($page)) {
                return IkiWiki::SuccessReason->new("is set as translatable in po_translatable_pages");
        }
  sub match_lang ($$;@) { #{{{
        my $page=shift;
        my $wanted=shift;
        my $regexp=IkiWiki::glob2re($wanted);
        my $lang;
        my $masterpage;
  
  sub match_currentlang ($$;@) { #{{{
        my $page=shift;
        shift;
        my %params=@_;
        my ($currentmasterpage, $currentlang, $masterpage, $lang);
diff --combined doc/plugins/po.mdwn
index f9cd0ff034b139cf21dd26751d01bc4006aa9834,a2e7b18e3b81aad9e0361101da2fe7a6cfe0307c..0a8a77a3ce429ae2e23fd7dd925cf87771ac6a91
@@@ -13,7 -13,7 +13,7 @@@ A language is chosen as the "master" on
  language is a "slave" one.
  
  A page written in the "master" language is a "master" page. It can be
- of any page type supported by ikiwiki, but PO. It does not have to be
+ of any page type supported by ikiwiki, except `po`. It does not have to be
  named a special way: migration to this plugin does not imply any page
  renaming work.
  
@@@ -22,7 -22,7 +22,7 @@@ English; if `usedirs` is enabled, it i
  `bla/page/index.en.html`, else as `bla/page.en.html`.
  
  Any translation of a "master" page into a "slave" language is called
- a "slave" page; it is written in the gettext PO format. PO is now
+ a "slave" page; it is written in the gettext PO format. `po` is now
  a page type supported by ikiwiki.
  
  Example: `bla/page.fr.po` is the PO "message catalog" used to
@@@ -56,9 -56,8 +56,8 @@@ The `po_translatable_pages` setting con
  translatable. It is a [[ikiwiki/PageSpec]], so you have lots of
  control over what kind of pages are translatable.
  
- The PO translations files are anyway not considered as being
- translatable, so you don't need to worry about excluding them
- explicitly from this [[ikiwiki/PageSpec]].
+ The `.po` files are not considered as being translatable, so you don't need to
+ worry about excluding them explicitly from this [[ikiwiki/PageSpec]].
  
  Internal links
  --------------
@@@ -180,17 -179,14 +179,14 @@@ Additional PageSpec test
  This plugin enhances the regular [[ikiwiki/PageSpec]] syntax with some
  additional tests that are documented [[here|ikiwiki/pagespec/po]].
  
- Automatic PO files update
- -------------------------
+ Automatic PO file update
+ ------------------------
  
  Committing changes to a "master" page:
  
- 1. updates the POT file, as well as the PO files for the "slave"
-    languages (this is done in the `needsbuild` hook); the updated PO
-    files are then put under version control;
- 2. triggers a refresh of the corresponding HTML slave pages (this is
-    achieved by making any "slave" page dependent on the corresponding
-    "master" page, in the `needsbuild` hook).
 -1. updates the POT file and the PO files for the supported languages;
 -   the updated PO files are then put under version control
 -2. triggers a refresh of the corresponding HTML slave pages
++1. updates the POT file and the PO files for the "slave" languages;
++   the updated PO files are then put under version control;
++2. triggers a refresh of the corresponding HTML slave pages.
  
  Also, when the plugin has just been enabled, or when a page has just
  been declared as being translatable, the needed POT and PO files are
@@@ -199,30 -195,50 +195,40 @@@ created, and the PO files are checked i
  Discussion pages
  ----------------
  
 -Discussion should happen in the language in which the pages are written for
 -real, *i.e.* the "master" one. If discussion pages are enabled, "slave" pages
 -therefore link to the "master" page's discussion page.
 +Discussion should happen in the language in which the pages are
 +written for real, *i.e.* the "master" one. If discussion pages are
 +enabled, "slave" pages therefore link to the "master" page's
 +discussion page.
  
  Translating
  -----------
  
 -One can edit the PO files using ikiwiki's CGI (a message-by-message interface
 -could also be implemented at some point).
 +One can edit the PO files using ikiwiki's CGI (a message-by-message
 +interface could also be implemented at some point).
  
- If [[tips/untrusted_git_push]] is setup, one can edit the PO files in
her preferred `$EDITOR`, without needing to be online.
+ If [[tips/untrusted_git_push]] is setup, one can edit the PO files in one's
+ preferred `$EDITOR`, without needing to be online.
  
  TODO
  ====
  
 -OTHERLANGUAGES dependencies
 ----------------------------
 -
 -Pages using `OTHERLANGUAGES` depend on any "master" and "slave" pages
 -whose status is being displayed. It is supposed to trigger dependency
 -loops, but no practical bugs were noticed yet.
 -
 -Should pages using the `OTHERLANGUAGES` template loop be declared as
 -linking to the same page in other versions? To be rigorous, they
 -should, but this may clutter the backlinks.
 -
  Security checks
  ---------------
  
  - `refreshpofiles` uses `system()`, whose args have to be checked more
    thoroughly to prevent any security issue (command injection, etc.).
+   > Always pass `system()` a list of parameters to avoid the shell.
+   > I've checked in a change fixing that. --[[Joey]]
  - `refreshpofiles` and `refreshpot` create new files; this may need
    some checks, e.g. using `IkiWiki::prep_writefile()`
+   > Yes, it would be ideal to call `prep_writefile` on each file 
+   > that they write, beforehand. This way you'd avoid symlink attacks etc to the
+   > generated po/pot files. I haven't done it, but it seems pretty trivial.
+   > --[[Joey]]
+ - Can any sort of directives be put in po files that will
+   cause mischief (ie, include other files, run commands, crash gettext,
+   whatever).
+ - Any security issues on running po4a on untrusted content?
  
  gettext/po4a rough corners
  --------------------------
    changes bla.fr.po in repo1; then pushing repo1 to repo2 triggers
    a PO update, that changes bla.fr.po in repo2; etc.; fixed in
    `629968fc89bced6727981c0a1138072631751fee`?
 -- new translations created in the web interface must get proper charset/encoding
 -  gettext metadata, else the next automatic PO update removes any non-ascii
 -  chars; possible solution: put such metadata into the Pot file, and let it
 -  propagate; should be fixed in `773de05a7a1ee68d2bed173367cf5e716884945a`, time
 -  will tell.
 +- new translations created in the web interface must get proper
 +  charset/encoding gettext metadata, else the next automatic PO update
 +  removes any non-ascii chars; possible solution: put such metadata
 +  into the Pot file, and let it propagate; should be fixed in
 +  `773de05a7a1ee68d2bed173367cf5e716884945a`, time will tell.
  
  Misc. improvements
  ------------------
  
 -### preview
 -
 -preview does not work for PO files.
 -
 -### automatic POT/PO update
 -
 -Use the `change` hook instead of `needsbuild`?
 -
  ### page titles
  
 -Use nice page titles from meta plugin in links, as inline already does. This is
 -actually a duplicate for
 -[[bugs/pagetitle_function_does_not_respect_meta_titles]], which might be fixed
 -by something like [[todo/using_meta_titles_for_parentlinks]].
 +Use nice page titles from meta plugin in links, as inline already
 +does. This is actually a duplicate for
 +[[bugs/pagetitle_function_does_not_respect_meta_titles]], which might
 +be fixed by something like [[todo/using_meta_titles_for_parentlinks]].
  
  ### websetup
  
  Which configuration settings are safe enough for websetup?
  
 -### parentlinks
 -
 -When the wiki home page is translatable, the parentlinks plugin sets
 -`./index.html` as its translations' single parent link. Ideally, the home page's
 -translations should get no parent link at all, just like the version written in
 -the master language.
 -
+ > I see no problems with `po_master_language` and `po_slave_languages`
+ > (assuming websetup handles the hashes correctly). Would not hurt to check
+ > that the values of these are legal language codes, in `checkconfig`. 
+ > `po_translatable_pages` seems entirely safe. `po_link_to` w/o usedirs
+ > causes ikiwiki to error out. If it were changed to fall back to a safe
+ > setting in this case rather than error, it would be safe.
+ > --[[Joey]]
  ### backlinks
  
 -If a given translatable `sourcepage.mdwn` links to \[[destpage]],
 -`sourcepage.LL.po` also link to \[[destpage]], and the latter has the master
 -page *and* all its translations listed in the backlinks.
 +`po_link_to = negotiated`: if a given translatable `sourcepage.mdwn`
 +links to \[[destpage]], `sourcepage.LL.po` also link to \[[destpage]],
 +and the latter has the master page *and* all its translations listed
 +in the backlinks.
 +
 +`po_link_to = current`: seems to work nicely
  
+ ### license
+ > Could you please put a copyright and license on po.pm? I assume it's
+ > GPLed as it's based on po4a-translate. --[[Joey]]
  Translation quality assurance
  -----------------------------