Provide a UUID in edittemplate templates.
[ikiwiki.git] / IkiWiki / Plugin / po.pm
index 651c98ed7d71107f6080672c89e9493afa870d5f..6107a4a2252c256e715a3b0bd210eb28d45371b3 100644 (file)
@@ -23,12 +23,14 @@ use File::Copy;
 use File::Spec;
 use File::Temp;
 use Memoize;
-use UNIVERSAL;
 
+my ($master_language_code, $master_language_name);
 my %translations;
 my @origneedsbuild;
 my %origsubs;
 my @slavelanguages; # language codes ordered as in config po_slave_languages
+my %slavelanguages; # language code to name lookup
+my $language_code_pattern = '[a-zA-Z]+(?:_[a-zA-Z]+)?';
 
 memoize("istranslatable");
 memoize("_istranslation");
@@ -36,7 +38,8 @@ memoize("percenttranslated");
 
 sub import {
        hook(type => "getsetup", id => "po", call => \&getsetup);
-       hook(type => "checkconfig", id => "po", call => \&checkconfig);
+       hook(type => "checkconfig", id => "po", call => \&checkconfig,
+               last => 1);
        hook(type => "needsbuild", id => "po", call => \&needsbuild);
        hook(type => "scan", id => "po", call => \&scan, last => 1);
        hook(type => "filter", id => "po", call => \&filter);
@@ -44,7 +47,7 @@ sub import {
        hook(type => "pagetemplate", id => "po", call => \&pagetemplate, last => 1);
        hook(type => "rename", id => "po", call => \&renamepages, first => 1);
        hook(type => "delete", id => "po", call => \&mydelete);
-       hook(type => "change", id => "po", call => \&change);
+       hook(type => "rendered", id => "po", call => \&rendered);
        hook(type => "checkcontent", id => "po", call => \&checkcontent);
        hook(type => "canremove", id => "po", call => \&canremove);
        hook(type => "canrename", id => "po", call => \&canrename);
@@ -63,8 +66,11 @@ sub import {
                inject(name => "IkiWiki::urlto", call => \&myurlto);
                $origsubs{'cgiurl'}=\&IkiWiki::cgiurl;
                inject(name => "IkiWiki::cgiurl", call => \&mycgiurl);
-               $origsubs{'rootpage'}=\&IkiWiki::rootpage;
-               inject(name => "IkiWiki::rootpage", call => \&myrootpage);
+               if (IkiWiki->can('rootpage')) {
+                       $origsubs{'rootpage'}=\&IkiWiki::rootpage;
+                       inject(name => "IkiWiki::rootpage", call => \&myrootpage)
+                               if defined $origsubs{'rootpage'};
+               }
                $origsubs{'isselflink'}=\&IkiWiki::isselflink;
                inject(name => "IkiWiki::isselflink", call => \&myisselflink);
        }
@@ -95,10 +101,7 @@ sub getsetup () {
                },
                po_master_language => {
                        type => "string",
-                       example => {
-                               'code' => 'en',
-                               'name' => 'English'
-                       },
+                       example => "en|English",
                        description => "master language (non-PO files)",
                        safe => 1,
                        rebuild => 1,
@@ -132,39 +135,49 @@ sub getsetup () {
 }
 
 sub checkconfig () {
-       foreach my $field (qw{po_master_language}) {
-               if (! exists $config{$field} || ! defined $config{$field}) {
-                       error(sprintf(gettext("Must specify %s when using the %s plugin"),
-                                     $field, 'po'));
+       if (exists $config{po_master_language}) {
+               if (! ref $config{po_master_language}) {
+                       ($master_language_code, $master_language_name)=
+                               splitlangpair($config{po_master_language});
+               }
+               else {
+                       $master_language_code=$config{po_master_language}{code};
+                       $master_language_name=$config{po_master_language}{name};
+                       $config{po_master_language}=joinlangpair($master_language_code, $master_language_name);
                }
        }
+       if (! defined $master_language_code) {
+               $master_language_code='en';
+       }
+       if (! defined $master_language_name) {
+               $master_language_name='English';
+       }
 
        if (ref $config{po_slave_languages} eq 'ARRAY') {
-               my %slaves;
                foreach my $pair (@{$config{po_slave_languages}}) {
-                       my ($code, $name) = ( $pair =~ /^([a-z]{2})\|(.+)$/ );
-                       if (!defined $code || !defined $name) {
-                               error(sprintf(gettext("%s has invalid syntax: must use CODE|NAME"),
-                                             $pair));
+                       my ($code, $name)=splitlangpair($pair);
+                       if (defined $code && ! exists $slavelanguages{$code}) {
+                               push @slavelanguages, $code;
+                               $slavelanguages{$code} = $name;
                        }
-                       $slaves{$code} = $name;
-                       push @slavelanguages, $code;
-
                }
-               $config{po_slave_languages} = \%slaves;
        }
        elsif (ref $config{po_slave_languages} eq 'HASH') {
+               %slavelanguages=%{$config{po_slave_languages}};
                @slavelanguages = sort {
                        $config{po_slave_languages}->{$a} cmp $config{po_slave_languages}->{$b};
-               } keys %{$config{po_slave_languages}};
+               } keys %slavelanguages;
+               $config{po_slave_languages}=[
+                       map { joinlangpair($_, $slavelanguages{$_}) } @slavelanguages
+               ]
        }
 
-       delete $config{po_slave_languages}{$config{po_master_language}{code}};;
+       delete $slavelanguages{$master_language_code};
 
        map {
                islanguagecode($_)
                        or error(sprintf(gettext("%s is not a valid language code"), $_));
-       } ($config{po_master_language}{code}, @slavelanguages);
+       } ($master_language_code, @slavelanguages);
 
        if (! exists $config{po_translatable_pages} ||
            ! defined $config{po_translatable_pages}) {
@@ -198,11 +211,11 @@ sub checkconfig () {
                                if -d "$config{underlaydirbase}/po/$ll/$underlay";
                }
        
-               if ($config{po_master_language}{code} ne 'en') {
+               if ($master_language_code ne 'en') {
                        # Add underlay containing translated source files
                        # for the master language.
-                       add_underlay("locale/$config{po_master_language}{code}/$underlay")
-                               if -d "$config{underlaydirbase}/locale/$config{po_master_language}{code}/$underlay";
+                       add_underlay("locale/$master_language_code/$underlay")
+                               if -d "$config{underlaydirbase}/locale/$master_language_code/$underlay";
                }
        }
 }
@@ -329,6 +342,19 @@ sub pagetemplate (@) {
        if ($template->query(name => "istranslatable")) {
                $template->param(istranslatable => istranslatable($page));
        }
+       my $lang_code = istranslation($page) ? lang($page) : $master_language_code;
+       if ($template->query(name => "lang_code")) {
+               $template->param(lang_code => $lang_code);
+       }
+       if ($template->query(name => "html_lang_code")) {
+               $template->param(html_lang_code => htmllangcode($lang_code));
+       }
+       if ($template->query(name => "html_lang_dir")) {
+               $template->param(html_lang_dir => htmllangdir($lang_code));
+       }
+       if ($template->query(name => "lang_name")) {
+               $template->param(lang_name => languagename($lang_code));
+       }
        if ($template->query(name => "HOMEPAGEURL")) {
                $template->param(homepageurl => homepageurl($page));
        }
@@ -358,7 +384,8 @@ sub pagetemplate (@) {
            && $masterpage eq "index") {
                $template->param('parentlinks' => []);
        }
-       if (ishomepage($page) && $template->query(name => "title")) {
+       if (ishomepage($page) && $template->query(name => "title")
+           && !$template->param("title_overridden")) {
                $template->param(title => $config{wikiname});
        }
 }
@@ -406,7 +433,7 @@ sub mydelete (@) {
        map { deletetranslations($_) } grep istranslatablefile($_), @deleted;
 }
 
-sub change (@) {
+sub rendered (@) {
        my @rendered=@_;
 
        my $updated_po_files=0;
@@ -512,7 +539,7 @@ sub formbuilder_setup (@) {
        if ($form->field("do") eq "create") {
                # Warn the user: new pages must be written in master language.
                my $template=template("pocreatepage.tmpl");
-               $template->param(LANG => $config{po_master_language}{name});
+               $template->param(LANG => $master_language_name);
                $form->tmpl_param(message => $template->output);
        }
        elsif ($form->field("do") eq "edit") {
@@ -601,7 +628,7 @@ sub mybeautify_urlpath ($) {
 
        my $res=$origsubs{'beautify_urlpath'}->($url);
        if (defined $config{po_link_to} && $config{po_link_to} eq "negotiated") {
-               $res =~ s!/\Qindex.$config{po_master_language}{code}.$config{htmlext}\E$!/!;
+               $res =~ s!/\Qindex.$master_language_code.$config{htmlext}\E$!/!;
                $res =~ s!/\Qindex.$config{htmlext}\E$!/!;
                map {
                        $res =~ s!/\Qindex.$_.$config{htmlext}\E$!/!;
@@ -610,23 +637,27 @@ sub mybeautify_urlpath ($) {
        return $res;
 }
 
-sub mytargetpage ($$) {
+sub mytargetpage ($$;$) {
        my $page=shift;
        my $ext=shift;
+       my $filename=shift;
 
        if (istranslation($page) || istranslatable($page)) {
                my ($masterpage, $lang) = (masterpage($page), lang($page));
-               if (! $config{usedirs} || $masterpage eq 'index') {
+               if (defined $filename) {
+                       return $masterpage . "/" . $filename . "." . $lang . "." . $ext;
+               }
+               elsif (! $config{usedirs} || $masterpage eq 'index') {
                        return $masterpage . "." . $lang . "." . $ext;
                }
                else {
                        return $masterpage . "/index." . $lang . "." . $ext;
                }
        }
-       return $origsubs{'targetpage'}->($page, $ext);
+       return $origsubs{'targetpage'}->($page, $ext, $filename);
 }
 
-sub myurlto ($$;$) {
+sub myurlto ($;$$) {
        my $to=shift;
        my $from=shift;
        my $absolute=shift;
@@ -635,7 +666,12 @@ sub myurlto ($$;$) {
        if (! length $to
            && $config{po_link_to} eq "current"
            && istranslatable('index')) {
-               return IkiWiki::beautify_urlpath(IkiWiki::baseurl($from) . "index." . lang($from) . ".$config{htmlext}");
+               if (defined $from) {
+                       return IkiWiki::beautify_urlpath(IkiWiki::baseurl($from) . "index." . lang($from) . ".$config{htmlext}");
+               }
+               else {
+                       return $origsubs{'urlto'}->($to,$from,$absolute);
+               }
        }
        # avoid using our injected beautify_urlpath if run by cgi_editpage,
        # so that one is redirected to the just-edited page rather than to the
@@ -788,11 +824,11 @@ sub _istranslation ($) {
                         && pagetype($file) eq 'po';
        return 0 if $file =~ /\.pot$/;
 
-       my ($masterpage, $lang) = ($page =~ /(.*)[.]([a-z]{2})$/);
+       my ($masterpage, $lang) = ($page =~ /(.*)[.]($language_code_pattern)$/);
        return 0 unless defined $masterpage && defined $lang
                         && length $masterpage && length $lang
                         && defined $pagesources{$masterpage}
-                        && defined $config{po_slave_languages}{$lang};
+                        && defined $slavelanguages{$lang};
 
        return (maybe_add_leading_slash($masterpage, $hasleadingslash), $lang)
                if istranslatable($masterpage);
@@ -824,20 +860,33 @@ sub lang ($) {
        if (1 < (my ($masterpage, $lang) = _istranslation($page))) {
                return $lang;
        }
-       return $config{po_master_language}{code};
+       return $master_language_code;
+}
+
+sub htmllangcode ($) {
+       (my $lang = shift) =~ tr/_/-/;
+       return $lang;
+}
+
+sub htmllangdir ($) {
+       my $lang = shift;
+       if ($lang =~ /^(ar|fa|he)/) {
+               return 'rtl';
+       }
+       return 'ltr';
 }
 
 sub islanguagecode ($) {
        my $code=shift;
 
-       return $code =~ /^[a-z]{2}$/;
+       return $code =~ /^$language_code_pattern$/;
 }
 
 sub otherlanguage_page ($$) {
        my $page=shift;
        my $code=shift;
 
-       return masterpage($page) if $code eq $config{po_master_language}{code};
+       return masterpage($page) if $code eq $master_language_code;
        return masterpage($page) . '.' . $code;
 }
 
@@ -851,9 +900,9 @@ sub otherlanguages_codes ($) {
        return \@ret unless istranslation($page) || istranslatable($page);
        my $curlang=lang($page);
        foreach my $lang
-               ($config{po_master_language}{code}, @slavelanguages) {
+               ($master_language_code, @slavelanguages) {
                next if $lang eq $curlang;
-               if ($lang eq $config{po_master_language}{code} ||
+               if ($lang eq $master_language_code ||
                    istranslatedto(masterpage($page), $lang)) {
                        push @ret, $lang;
                }
@@ -1008,10 +1057,10 @@ sub percenttranslated ($) {
 sub languagename ($) {
        my $code=shift;
 
-       return $config{po_master_language}{name}
-               if $code eq $config{po_master_language}{code};
-       return $config{po_slave_languages}{$code}
-               if defined $config{po_slave_languages}{$code};
+       return $master_language_name
+               if $code eq $master_language_code;
+       return $slavelanguages{$code}
+               if defined $slavelanguages{$code};
        return;
 }
 
@@ -1022,17 +1071,21 @@ sub otherlanguagesloop ($) {
        if (istranslation($page)) {
                push @ret, {
                        url => urlto_with_orig_beautiful_urlpath(masterpage($page), $page),
-                       code => $config{po_master_language}{code},
-                       language => $config{po_master_language}{name},
+                       code => $master_language_code,
+                       html_code => htmllangcode($master_language_code),
+                       html_dir => htmllangdir($master_language_code),
+                       language => $master_language_name,
                        master => 1,
                };
        }
        foreach my $lang (@{otherlanguages_codes($page)}) {
-               next if $lang eq $config{po_master_language}{code};
+               next if $lang eq $master_language_code;
                my $otherpage = otherlanguage_page($page, $lang);
                push @ret, {
                        url => urlto_with_orig_beautiful_urlpath($otherpage, $page),
                        code => $lang,
+                       html_code => htmllangcode($lang),
+                       html_dir => htmllangdir($lang),
                        language => languagename($lang),
                        percent => percenttranslated($otherpage),
                }
@@ -1072,7 +1125,7 @@ sub deletetranslations ($) {
                        IkiWiki::rcs_remove($_);
                }
                else {
-                       IkiWiki::prune("$config{srcdir}/$_");
+                       IkiWiki::prune("$config{srcdir}/$_", $config{srcdir});
                }
        } @todelete;
 
@@ -1222,6 +1275,7 @@ sub po4a_options($) {
                # how to disable options is not consistent across po4a modules
                $options{includessi} = '';
                $options{includeexternal} = 0;
+               $options{ontagerror} = 'warn';
        }
        elsif ($pagetype eq 'mdwn') {
                $options{markdown} = 1;
@@ -1233,6 +1287,27 @@ sub po4a_options($) {
        return %options;
 }
 
+sub splitlangpair ($) {
+       my $pair=shift;
+
+       my ($code, $name) = ( $pair =~ /^($language_code_pattern)\|(.+)$/ );
+       if (! defined $code || ! defined $name ||
+           ! length $code || ! length $name) {
+               # not a fatal error to avoid breaking if used with web setup
+               warn sprintf(gettext("%s has invalid syntax: must use CODE|NAME"),
+                       $pair);
+       }
+
+       return $code, $name;
+}
+
+sub joinlangpair ($$) {
+       my $code=shift;
+       my $name=shift;
+
+       return "$code|$name";
+}
+
 # ,----
 # | PageSpecs
 # `----
@@ -1267,7 +1342,7 @@ sub match_lang ($$;@) {
 
        my $regexp=IkiWiki::glob2re($wanted);
        my $lang=IkiWiki::Plugin::po::lang($page);
-       if ($lang !~ /^$regexp$/i) {
+       if ($lang !~ $regexp) {
                return IkiWiki::FailReason->new("file language is $lang, not $wanted");
        }
        else {