1#!/usr/bin/perl
2# -*- cperl-indent-level: 8; -*-
3use warnings;
4use strict;
5use File::Temp qw{tempdir};
6use utf8;
7
8BEGIN {
9	unless (eval { require Locale::Po4a::Chooser }) {
10		eval q{
11			use Test::More skip_all => "Locale::Po4a::Chooser::new is not available"
12		}
13	}
14	unless (eval { require Locale::Po4a::Po }) {
15		eval q{
16			use Test::More skip_all => "Locale::Po4a::Po::new is not available"
17		}
18	}
19}
20
21use Test::More;
22
23BEGIN { use_ok("IkiWiki"); }
24
25my $msgprefix;
26
27my $dir = tempdir("ikiwiki-test-po.XXXXXXXXXX",
28		  DIR => File::Spec->tmpdir,
29		  CLEANUP => 1);
30
31### Init
32%config=IkiWiki::defaultconfig();
33$config{srcdir} = "$dir/src";
34$config{destdir} = "$dir/dst";
35$config{destdir} = "$dir/dst";
36$config{underlaydirbase} = "/dev/null";
37$config{underlaydir} = "/dev/null";
38$config{url} = "http://example.com";
39$config{cgiurl} = "http://example.com/ikiwiki.cgi";
40$config{discussion} = 0;
41$config{po_master_language} = { code => 'en',
42				name => 'English'
43			      };
44$config{po_slave_languages} = {
45			       es => 'Castellano',
46			       fr => "Français"
47			      };
48$config{po_translatable_pages}='index or test1 or test2 or translatable or debian*';
49$config{po_link_to}='negotiated';
50IkiWiki::loadplugins();
51ok(IkiWiki::loadplugin('meta'), "meta plugin loaded");
52ok(IkiWiki::loadplugin('po'), "po plugin loaded");
53IkiWiki::checkconfig();
54
55### seed %pagesources and %pagecase
56$pagesources{'index'}='index.mdwn';
57$pagesources{'index.fr'}='index.fr.po';
58$pagesources{'index.es'}='index.es.po';
59$pagesources{'test1'}='test1.mdwn';
60$pagesources{'test1.es'}='test1.es.po';
61$pagesources{'test1.fr'}='test1.fr.po';
62$pagesources{'test2'}='test2.mdwn';
63$pagesources{'test2.es'}='test2.es.po';
64$pagesources{'test2.fr'}='test2.fr.po';
65$pagesources{'test3'}='test3.mdwn';
66$pagesources{'test3.es'}='test3.es.mdwn';
67$pagesources{'translatable'}='translatable.mdwn';
68$pagesources{'translatable.fr'}='translatable.fr.po';
69$pagesources{'translatable.es'}='translatable.es.po';
70$pagesources{'nontranslatable'}='nontranslatable.mdwn';
71$pagesources{'debian911356'}='debian911356.mdwn';
72$pagesources{'debian911356ish'}='debian911356ish.mdwn';
73$pagesources{'debian911356.fr'}='debian911356.fr.po';
74$pagesources{'debian911356ish.fr'}='debian911356ish.fr.po';
75$pagesources{'debian911356-inlined'}='debian911356-inlined.mdwn';
76$pagesources{'debian911356-inlined.fr'}='debian911356-inlined.fr.po';
77$pagesources{'templates/feedlink.tmpl'}='templates/feedlink.tmpl';
78$pagesources{'templates/inlinepage.tmpl'}='templates/inlinepage.tmpl';
79my $now=time;
80foreach my $page (keys %pagesources) {
81	$IkiWiki::pagecase{lc $page}=$page;
82	$IkiWiki::pagectime{$page}=$now;
83	$IkiWiki::pagemtime{$page}=$now;
84}
85
86### populate srcdir
87writefile('index.mdwn', $config{srcdir},
88          "[[!meta title=\"index title\"]]\n[[translatable]] [[nontranslatable]]");
89writefile('test1.mdwn', $config{srcdir},
90          "[[!meta title=\"test1 title\"]]\ntest1 content");
91writefile('test2.mdwn', $config{srcdir}, 'test2 content');
92writefile('test3.mdwn', $config{srcdir}, 'test3 content');
93writefile('translatable.mdwn', $config{srcdir}, '[[nontranslatable]]');
94writefile('nontranslatable.mdwn', $config{srcdir}, '[[/]] [[translatable]]');
95writefile('debian911356.mdwn', $config{srcdir}, <<EOF);
96Before first inline
97
98[[!inline pages="debian911356-inlined" raw="yes"]]
99
100Between inlines
101
102[[!inline pages="debian911356-inlined" raw="yes"]]
103
104After inlines
105EOF
106writefile('debian911356-inlined.mdwn', $config{srcdir}, <<EOF);
107English content
108EOF
109writefile('debian911356.fr.po', $config{srcdir}, <<EOF);
110msgid "" msgstr ""
111"MIME-Version: 1.0\\n"
112"Content-Type: text/plain; charset=UTF-8\\n"
113"Content-Transfer-Encoding: 8bit\\n"
114
115msgid "Before first inline"
116msgstr "Avant la première inline"
117
118msgid "[[!inline pages=\\"debian911356-inlined\\" raw=\\"yes\\"]]\\n"
119msgstr "[[!inline pages=\\"debian911356-inlined.fr\\" raw=\\"yes\\"]]\\n"
120
121msgid "Between inlines"
122msgstr "Entre les inlines"
123
124msgid "After inlines"
125msgstr "Après les inlines"
126EOF
127writefile('debian911356-inlined.fr.po', $config{srcdir}, <<EOF);
128msgid "English content"
129msgstr "Contenu français"
130EOF
131writefile('debian911356ish.mdwn', $config{srcdir}, <<EOF);
132Before first inline
133
134[[!inline pages="debian911356-inlined"]]
135
136Between inlines
137
138[[!inline pages="debian911356-inlined"]]
139
140After inlines
141EOF
142writefile('debian911356ish.fr.po', $config{srcdir}, <<EOF);
143msgid "" msgstr ""
144"MIME-Version: 1.0\\n"
145"Content-Type: text/plain; charset=UTF-8\\n"
146"Content-Transfer-Encoding: 8bit\\n"
147
148msgid "Before first inline"
149msgstr "Avant la première inline"
150
151msgid "[[!inline pages=\\"debian911356-inlined\\"]]\\n"
152msgstr "[[!inline pages=\\"debian911356-inlined.fr\\"]]\\n"
153
154msgid "Between inlines"
155msgstr "Entre les inlines"
156
157msgid "After inlines"
158msgstr "Après les inlines"
159EOF
160# We don't actually care what the feed links look like, so skip them
161writefile('templates/feedlink.tmpl', $config{srcdir}, <<EOF);
162<!--feedlinks-->
163EOF
164# Make inlines' appearance predictable so we can screen-scrape them
165writefile('templates/inlinepage.tmpl', $config{srcdir}, <<EOF);
166<div class="inlinecontent">
167<h6><TMPL_VAR TITLE></h6>
168<TMPL_VAR CONTENT>
169</div><!--inlinecontent-->
170EOF
171
172### istranslatable/istranslation
173# we run these tests twice because memoization attempts made them
174# succeed once every two tries...
175foreach (1, 2) {
176ok(IkiWiki::Plugin::po::istranslatable('index'), "index is translatable");
177ok(IkiWiki::Plugin::po::istranslatable('/index'), "/index is translatable");
178ok(! IkiWiki::Plugin::po::istranslatable('index.fr'), "index.fr is not translatable");
179ok(! IkiWiki::Plugin::po::istranslatable('index.es'), "index.es is not translatable");
180ok(! IkiWiki::Plugin::po::istranslatable('/index.fr'), "/index.fr is not translatable");
181ok(! IkiWiki::Plugin::po::istranslation('index'), "index is not a translation");
182ok(IkiWiki::Plugin::po::istranslation('index.fr'), "index.fr is a translation");
183ok(IkiWiki::Plugin::po::istranslation('index.es'), "index.es is a translation");
184ok(IkiWiki::Plugin::po::istranslation('/index.fr'), "/index.fr is a translation");
185ok(IkiWiki::Plugin::po::istranslatable('test1'), "test1 is translatable");
186ok(IkiWiki::Plugin::po::istranslation('test1.es'), "test1.es is a translation");
187ok(IkiWiki::Plugin::po::istranslation('test1.fr'), "test1.fr is a translation");
188ok(IkiWiki::Plugin::po::istranslatable('test2'), "test2 is translatable");
189ok(! IkiWiki::Plugin::po::istranslation('test2'), "test2 is not a translation");
190ok(! IkiWiki::Plugin::po::istranslatable('test3'), "test3 is not translatable");
191ok(! IkiWiki::Plugin::po::istranslation('test3'), "test3 is not a translation");
192}
193
194### pofiles
195
196my @pofiles = IkiWiki::Plugin::po::pofiles(srcfile("index.mdwn"));
197ok( @pofiles, "pofiles is defined");
198ok( @pofiles == 2, "pofiles has correct size");
199is_deeply(\@pofiles, ["$config{srcdir}/index.es.po", "$config{srcdir}/index.fr.po"], "pofiles content is correct");
200
201### links
202require IkiWiki::Render;
203
204sub refresh_n_scan(@) {
205	my @masterfiles_rel=@_;
206	foreach my $masterfile_rel (@masterfiles_rel) {
207		my $masterfile=srcfile($masterfile_rel);
208		IkiWiki::scan($masterfile_rel);
209		next unless IkiWiki::Plugin::po::istranslatable(pagename($masterfile_rel));
210		my @pofiles=IkiWiki::Plugin::po::pofiles($masterfile);
211		IkiWiki::Plugin::po::refreshpot($masterfile);
212		IkiWiki::Plugin::po::refreshpofiles($masterfile, @pofiles);
213		map IkiWiki::scan(IkiWiki::abs2rel($_, $config{srcdir})), @pofiles;
214	}
215}
216
217$config{po_link_to}='negotiated';
218$msgprefix="links (po_link_to=negotiated)";
219refresh_n_scan('index.mdwn', 'translatable.mdwn', 'nontranslatable.mdwn');
220is_deeply(\@{$links{'index'}}, ['translatable', 'nontranslatable'], "$msgprefix index");
221is_deeply(\@{$links{'index.es'}}, ['translatable.es', 'nontranslatable'], "$msgprefix index.es");
222is_deeply(\@{$links{'index.fr'}}, ['translatable.fr', 'nontranslatable'], "$msgprefix index.fr");
223is_deeply(\@{$links{'translatable'}}, ['nontranslatable'], "$msgprefix translatable");
224is_deeply(\@{$links{'translatable.es'}}, ['nontranslatable'], "$msgprefix translatable.es");
225is_deeply(\@{$links{'translatable.fr'}}, ['nontranslatable'], "$msgprefix translatable.fr");
226is_deeply([sort @{$links{'nontranslatable'}}], [sort('/', 'translatable', 'translatable.fr', 'translatable.es')], "$msgprefix nontranslatable");
227
228$config{po_link_to}='current';
229$msgprefix="links (po_link_to=current)";
230refresh_n_scan('index.mdwn', 'translatable.mdwn', 'nontranslatable.mdwn');
231is_deeply(\@{$links{'index'}}, ['translatable', 'nontranslatable'], "$msgprefix index");
232is_deeply(\@{$links{'index.es'}}, [ (map bestlink('index.es', $_), ('translatable.es', 'nontranslatable'))], "$msgprefix index.es");
233is_deeply(\@{$links{'index.fr'}}, [ (map bestlink('index.fr', $_), ('translatable.fr', 'nontranslatable'))], "$msgprefix index.fr");
234is_deeply(\@{$links{'translatable'}}, [bestlink('translatable', 'nontranslatable')], "$msgprefix translatable");
235is_deeply(\@{$links{'translatable.es'}}, ['nontranslatable'], "$msgprefix translatable.es");
236is_deeply(\@{$links{'translatable.fr'}}, ['nontranslatable'], "$msgprefix translatable.fr");
237is_deeply([sort @{$links{'nontranslatable'}}], [sort('/', 'translatable', 'translatable.fr', 'translatable.es')], "$msgprefix nontranslatable");
238
239### targetpage
240$config{usedirs}=0;
241$msgprefix="targetpage (usedirs=0)";
242is(targetpage('test1', 'html'), 'test1.en.html', "$msgprefix test1");
243is(targetpage('test1.fr', 'html'), 'test1.fr.html', "$msgprefix test1.fr");
244$config{usedirs}=1;
245$msgprefix="targetpage (usedirs=1)";
246is(targetpage('index', 'html'), 'index.en.html', "$msgprefix index");
247is(targetpage('index.fr', 'html'), 'index.fr.html', "$msgprefix index.fr");
248is(targetpage('test1', 'html'), 'test1/index.en.html', "$msgprefix test1");
249is(targetpage('test1.fr', 'html'), 'test1/index.fr.html', "$msgprefix test1.fr");
250is(targetpage('test3', 'html'), 'test3/index.html', "$msgprefix test3 (non-translatable page)");
251is(targetpage('test3.es', 'html'), 'test3.es/index.html', "$msgprefix test3.es (non-translatable page)");
252
253### urlto -> index
254$config{po_link_to}='current';
255$msgprefix="urlto (po_link_to=current)";
256is(urlto('', 'index'), './index.en.html', "$msgprefix index -> ''");
257is(urlto('', 'nontranslatable'), '../index.en.html', "$msgprefix nontranslatable -> ''");
258is(urlto('', 'translatable.fr'), '../index.fr.html', "$msgprefix translatable.fr -> ''");
259# when asking for a semi-absolute or absolute URL, we can't know what the
260# current language is, so for translatable pages we use the master language
261is(urlto('nontranslatable'), '/nontranslatable/', "$msgprefix 1-arg -> nontranslatable");
262is(urlto('translatable'), '/translatable/index.en.html', "$msgprefix 1-arg -> translatable");
263is(urlto('nontranslatable', undef, 1), 'http://example.com/nontranslatable/', "$msgprefix 1-arg -> nontranslatable");
264is(urlto('index', undef, 1), 'http://example.com/index.en.html', "$msgprefix 1-arg -> index");
265is(urlto('', undef, 1), 'http://example.com/index.en.html', "$msgprefix 1-arg -> ''");
266# FIXME: should these three produce the negotiatable URL instead of the master
267# language?
268is(urlto(''), '/index.en.html', "$msgprefix 1-arg -> ''");
269is(urlto('index'), '/index.en.html', "$msgprefix 1-arg -> index");
270is(urlto('translatable', undef, 1), 'http://example.com/translatable/index.en.html', "$msgprefix 1-arg -> translatable");
271
272$config{po_link_to}='negotiated';
273$msgprefix="urlto (po_link_to=negotiated)";
274is(urlto('', 'index'), './', "$msgprefix index -> ''");
275is(urlto('', 'nontranslatable'), '../', "$msgprefix nontranslatable -> ''");
276is(urlto('', 'translatable.fr'), '../', "$msgprefix translatable.fr -> ''");
277is(urlto('nontranslatable'), '/nontranslatable/', "$msgprefix 1-arg -> nontranslatable");
278is(urlto('translatable'), '/translatable/', "$msgprefix 1-arg -> translatable");
279is(urlto(''), '/', "$msgprefix 1-arg -> ''");
280is(urlto('index'), '/', "$msgprefix 1-arg -> index");
281is(urlto('nontranslatable', undef, 1), 'http://example.com/nontranslatable/', "$msgprefix 1-arg -> nontranslatable");
282is(urlto('translatable', undef, 1), 'http://example.com/translatable/', "$msgprefix 1-arg -> translatable");
283is(urlto('index', undef, 1), 'http://example.com/', "$msgprefix 1-arg -> index");
284is(urlto('', undef, 1), 'http://example.com/', "$msgprefix 1-arg -> ''");
285
286### bestlink
287$config{po_link_to}='current';
288$msgprefix="bestlink (po_link_to=current)";
289is(bestlink('test1.fr', 'test2'), 'test2.fr', "$msgprefix test1.fr -> test2");
290is(bestlink('test1.fr', 'test2.es'), 'test2.es', "$msgprefix test1.fr -> test2.es");
291$config{po_link_to}='negotiated';
292$msgprefix="bestlink (po_link_to=negotiated)";
293is(bestlink('test1.fr', 'test2'), 'test2.fr', "$msgprefix test1.fr -> test2");
294is(bestlink('test1.fr', 'test2.es'), 'test2.es', "$msgprefix test1.fr -> test2.es");
295
296### beautify_urlpath
297$config{po_link_to}='default';
298$msgprefix="beautify_urlpath (po_link_to=default)";
299is(IkiWiki::beautify_urlpath('test1/index.en.html'), './test1/index.en.html', "$msgprefix test1/index.en.html");
300is(IkiWiki::beautify_urlpath('test1/index.fr.html'), './test1/index.fr.html', "$msgprefix test1/index.fr.html");
301$config{po_link_to}='negotiated';
302$msgprefix="beautify_urlpath (po_link_to=negotiated)";
303is(IkiWiki::beautify_urlpath('test1/index.html'), './test1/', "$msgprefix test1/index.html");
304is(IkiWiki::beautify_urlpath('test1/index.en.html'), './test1/', "$msgprefix test1/index.en.html");
305is(IkiWiki::beautify_urlpath('test1/index.fr.html'), './test1/', "$msgprefix test1/index.fr.html");
306$config{po_link_to}='current';
307$msgprefix="beautify_urlpath (po_link_to=current)";
308is(IkiWiki::beautify_urlpath('test1/index.en.html'), './test1/index.en.html', "$msgprefix test1/index.en.html");
309is(IkiWiki::beautify_urlpath('test1/index.fr.html'), './test1/index.fr.html', "$msgprefix test1/index.fr.html");
310
311### re-scan
312refresh_n_scan('index.mdwn');
313is($pagestate{'index'}{meta}{title}, 'index title');
314is($pagestate{'index.es'}{meta}{title}, 'index title');
315is($pagestate{'index.fr'}{meta}{title}, 'index title');
316refresh_n_scan('test1.mdwn');
317is($pagestate{'test1'}{meta}{title}, 'test1 title');
318is($pagestate{'test1.es'}{meta}{title}, 'test1 title');
319is($pagestate{'test1.fr'}{meta}{title}, 'test1 title');
320
321### istranslatedto
322ok(IkiWiki::Plugin::po::istranslatedto('index', 'es'));
323ok(IkiWiki::Plugin::po::istranslatedto('index', 'fr'));
324ok(! IkiWiki::Plugin::po::istranslatedto('index', 'cz'));
325ok(IkiWiki::Plugin::po::istranslatedto('test1', 'es'));
326ok(IkiWiki::Plugin::po::istranslatedto('test1', 'fr'));
327ok(! IkiWiki::Plugin::po::istranslatedto('test1', 'cz'));
328ok(! IkiWiki::Plugin::po::istranslatedto('nontranslatable', 'es'));
329ok(! IkiWiki::Plugin::po::istranslatedto('nontranslatable', 'cz'));
330ok(! IkiWiki::Plugin::po::istranslatedto('test1.es', 'fr'));
331ok(! IkiWiki::Plugin::po::istranslatedto('test1.fr', 'es'));
332
333### islanguagecode
334ok(IkiWiki::Plugin::po::islanguagecode('en'));
335ok(IkiWiki::Plugin::po::islanguagecode('es'));
336ok(IkiWiki::Plugin::po::islanguagecode('arn'));
337ok(! IkiWiki::Plugin::po::islanguagecode('es_'));
338ok(! IkiWiki::Plugin::po::islanguagecode('_en'));
339
340# Actually render translated pages
341use IkiWiki::Render;
342
343my %output;
344foreach my $page (sort keys %pagesources) {
345	my $source = "$config{srcdir}/$pagesources{$page}";
346	if (-e $source) {
347		IkiWiki::scan($pagesources{$page});
348	}
349}
350
351# This is the most complicated case, so use this while we test rendering
352$config{po_link_to}='current';
353
354foreach my $page (sort keys %pagesources) {
355	my $source = "$config{srcdir}/$pagesources{$page}";
356	if (-e $source && defined IkiWiki::pagetype($pagesources{$page})) {
357		IkiWiki::scan($pagesources{$page});
358		my $content = readfile($source);
359		#print STDERR "-------------------------------------\n";
360		#print STDERR "SOURCE: $page: $content\n";
361		$content = IkiWiki::filter($page, $page, $content);
362		#print STDERR "FILTERED: $page: $content\n";
363		$content = IkiWiki::preprocess($page, $page, $content);
364		#print STDERR "PREPROCESSED: $page: $content\n";
365		$content = IkiWiki::linkify($page, $page, $content);
366		#print STDERR "LINKIFIED: $page: $content\n";
367		$content = IkiWiki::htmlize($page, $page, IkiWiki::pagetype($pagesources{$page}), $content);
368		#print STDERR "HTMLIZED: $page: $content\n";
369		IkiWiki::run_hooks(format => sub {
370			$content=shift->(
371				page => $page,
372				content => $content,
373			);
374		});
375		#print STDERR "FORMATTED: $page: $content\n";
376		$output{$page} = $content;
377	}
378}
379
380like($output{index}, qr{
381	<p>
382	<a\s+href="\./translatable/index\.en\.html">
383	translatable
384	</a>\s*
385	<a\s+href="\./nontranslatable/">
386	nontranslatable
387	</a>
388	</p>
389}sx);
390
391like($output{'index.es'}, qr{
392	<p>
393	<a\s+href="\./translatable/index\.es\.html">
394	translatable
395	</a>\s*
396	<a\s+href="\./nontranslatable/">
397	nontranslatable
398	</a>
399	</p>
400}sx);
401
402like($output{'index.fr'}, qr{
403	<p>
404	<a\s+href="\./translatable/index\.fr\.html">
405	translatable
406	</a>\s*
407	<a\s+href="\./nontranslatable/">
408	nontranslatable
409	</a>
410	</p>
411}sx);
412
413like($output{'translatable'}, qr{
414	<a\s+href="\.\./nontranslatable/">
415	nontranslatable
416	</a>
417}sx);
418
419TODO: {
420local $TODO = 'was [[/]] meant to be a link to the index?';
421unlike($output{'nontranslatable'}, qr{
422	class=.createlink.
423}sx);
424};
425like($output{'nontranslatable'}, qr{
426	<a\s+href="\.\./translatable/index\.en\.html">
427	translatable
428	</a>
429}sx);
430
431like($output{debian911356}, qr{
432	<p>Before\sfirst\sinline</p>
433	\s*
434	<p>English\scontent</p>
435	\s*
436	<p>Between\sinlines</p>
437	\s*
438	<p>English\scontent</p>
439	\s*
440	<p>After\sinlines</p>
441}sx);
442
443like($output{'debian911356.fr'}, qr{
444	<p>Avant\sla\spremière\sinline</p>
445	\s*
446	<p>Contenu\sfrançais</p>
447	\s*
448	<p>Entre\sles\sinlines</p>
449	\s*
450	<p>Contenu\sfrançais</p>
451	\s*
452	<p>Après\sles\sinlines</p>
453}sx);
454
455# Variation of Debian #911356 without using raw inlines.
456like($output{debian911356ish}, qr{
457	<p>Before\sfirst\sinline</p>
458	\s*
459	<!--feedlinks-->
460	\s*
461	<div\sclass="inlinecontent">
462	\s*
463	<h6>debian911356-inlined</h6>
464	\s*
465	<p>English\scontent</p>
466	\s*
467	</div><!--inlinecontent-->
468	\s*
469	<p>Between\sinlines</p>
470	\s*
471	<!--feedlinks-->
472	\s*
473	<div\sclass="inlinecontent">
474	\s*
475	<h6>debian911356-inlined</h6>
476	\s*
477	<p>English\scontent</p>
478	\s*
479	</div><!--inlinecontent-->
480	\s*
481	<p>After\sinlines</p>
482}sx);
483
484like($output{'debian911356ish.fr'}, qr{
485	<p>Avant\sla\spremière\sinline</p>
486	\s*
487	<!--feedlinks-->
488	\s*
489	<div\sclass="inlinecontent">
490	\s*
491	<h6>debian911356-inlined\.fr</h6>
492	\s*
493	<p>Contenu\sfrançais</p>
494	\s*
495	</div><!--inlinecontent-->
496	\s*
497	<p>Entre\sles\sinlines</p>
498	\s*
499	<!--feedlinks-->
500	\s*
501	<div\sclass="inlinecontent">
502	\s*
503	<h6>debian911356-inlined\.fr</h6>
504	\s*
505	<p>Contenu\sfrançais</p>
506	\s*
507	</div><!--inlinecontent-->
508	\s*
509	<p>Après\sles\sinlines</p>
510}sx);
511
512done_testing;
513