X-Git-Url: http://git.vanrenterghem.biz/git.ikiwiki.info.git/blobdiff_plain/8c2962ec48ae57605d6d0e297be437a97b6229ca..e0ce38dc42f461539a7c8f0594b95473d4494689:/IkiWiki/Plugin/po.pm

diff --git a/IkiWiki/Plugin/po.pm b/IkiWiki/Plugin/po.pm
index f3530faf3..13b98b739 100644
--- a/IkiWiki/Plugin/po.pm
+++ b/IkiWiki/Plugin/po.pm
@@ -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,11 +47,10 @@ 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);
-	hook(type => "editcontent", id => "po", call => \&editcontent);
 	hook(type => "formbuilder_setup", id => "po", call => \&formbuilder_setup, last => 1);
 	hook(type => "formbuilder", id => "po", call => \&formbuilder);
 
@@ -63,8 +65,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);
 	}
@@ -89,16 +94,13 @@ sub import {
 sub getsetup () {
 	return
 		plugin => {
-			safe => 0,
+			safe => 1,
 			rebuild => 1, # format plugin
 			section => "format",
 		},
 		po_master_language => {
 			type => "string",
-			example => {
-				'code' => 'en',
-				'name' => 'English'
-			},
+			example => "en|English",
 			description => "master language (non-PO files)",
 			safe => 1,
 			rebuild => 1,
@@ -110,7 +112,7 @@ sub getsetup () {
 				'es|Español',
 				'de|Deutsch'
 			],
-			description => "slave languages (PO files)",
+			description => "slave languages (translated via PO files) format: ll|Langname",
 			safe => 1,
 			rebuild => 1,
 		},
@@ -132,39 +134,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 +210,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";
 		}
 	}
 }
@@ -221,6 +233,8 @@ sub needsbuild () {
 	foreach my $master (keys %translations) {
 		map add_depends($_, $master), values %{otherlanguages_pages($master)};
 	}
+
+	return $needsbuild;
 }
 
 sub scan (@) {
@@ -288,9 +302,8 @@ sub filter (@) {
 	my $page = $params{page};
 	my $destpage = $params{destpage};
 	my $content = $params{content};
-	if (istranslation($page) && ! alreadyfiltered($page, $destpage)) {
+	if (istranslation($page)) {
 		$content = po_to_markup($page, $content);
-		setalreadyfiltered($page, $destpage);
 	}
 	return $content;
 }
@@ -327,6 +340,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));
 	}
@@ -356,7 +382,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});
 	}
 }
@@ -404,7 +431,7 @@ sub mydelete (@) {
 	map { deletetranslations($_) } grep istranslatablefile($_), @deleted;
 }
 
-sub change (@) {
+sub rendered (@) {
 	my @rendered=@_;
 
 	my $updated_po_files=0;
@@ -491,15 +518,6 @@ sub canrename (@) {
 	return undef;
 }
 
-# As we're previewing or saving a page, the content may have
-# changed, so tell the next filter() invocation it must not be lazy.
-sub editcontent () {
-	my %params=@_;
-
-	unsetalreadyfiltered($params{page}, $params{page});
-	return $params{content};
-}
-
 sub formbuilder_setup (@) {
 	my %params=@_;
 	my $form=$params{form};
@@ -510,7 +528,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") {
@@ -519,7 +537,7 @@ sub formbuilder_setup (@) {
 		# their buttons, which is why this hook must be run last.
 		# The canrename/canremove hooks already ensure this is forbidden
 		# at the backend level, so this is only UI sugar.
-		if (istranslation($form->field("page"))) {
+		if (istranslation(scalar $form->field("page"))) {
 			map {
 				for (my $i = 0; $i < @{$params{buttons}}; $i++) {
 					if (@{$params{buttons}}[$i] eq $_) {
@@ -599,7 +617,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$!/!;
@@ -608,23 +626,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;
@@ -633,7 +655,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
@@ -698,42 +725,6 @@ sub myisselflink ($$) {
 	return;
 }
 
-# ,----
-# | Blackboxes for private data
-# `----
-
-{
-	my %filtered;
-
-	sub alreadyfiltered($$) {
-		my $page=shift;
-		my $destpage=shift;
-
-		return exists $filtered{$page}{$destpage}
-			 && $filtered{$page}{$destpage} eq 1;
-	}
-
-	sub setalreadyfiltered($$) {
-		my $page=shift;
-		my $destpage=shift;
-
-		$filtered{$page}{$destpage}=1;
-	}
-
-	sub unsetalreadyfiltered($$) {
-		my $page=shift;
-		my $destpage=shift;
-
-		if (exists $filtered{$page}{$destpage}) {
-			delete $filtered{$page}{$destpage};
-		}
-	}
-
-	sub resetalreadyfiltered() {
-		undef %filtered;
-	}
-}
-
 # ,----
 # | Helper functions
 # `----
@@ -786,11 +777,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);
@@ -822,20 +813,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;
 }
 
@@ -849,9 +853,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;
 		}
@@ -942,10 +946,9 @@ sub refreshpofiles ($@) {
 		}
 
 		if (-e $pofile) {
-			system("msgmerge", "--previous", "-q", "-U", "--backup=none", $pofile, $potfile) == 0
-				or error("po(refreshpofiles) ".
-					 sprintf(gettext("failed to update %s"),
-						 $pofile));
+			if (! (system("msgmerge", "--previous", "-q", "-U", "--backup=none", $pofile, $potfile) == 0)) {
+				print STDERR ("po(refreshpofiles) ". sprintf(gettext("failed to update %s"), $pofile));
+			}
 		}
 		else {
 			File::Copy::syscopy($potfile,$pofile)
@@ -1006,10 +1009,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;
 }
 
@@ -1020,17 +1023,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),
 		}
@@ -1070,7 +1077,7 @@ sub deletetranslations ($) {
 			IkiWiki::rcs_remove($_);
 		}
 		else {
-			IkiWiki::prune("$config{srcdir}/$_");
+			IkiWiki::prune("$config{srcdir}/$_", $config{srcdir});
 		}
 	} @todelete;
 
@@ -1092,7 +1099,6 @@ sub commit_and_refresh ($) {
 		IkiWiki::rcs_update();
 	}
 	# Reinitialize module's private variables.
-	resetalreadyfiltered();
 	resettranslationscache();
 	flushmemoizecache();
 	# Trigger a wiki refresh.
@@ -1220,6 +1226,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;
@@ -1231,6 +1238,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
 # `----
@@ -1265,7 +1293,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 {