Localize navigation

Change-Id: Id3e423bbf51916cdff737e9188b6104696a0f5b5
diff --git a/kalamar.dict b/kalamar.dict
index d846aab..b88a0d2 100644
--- a/kalamar.dict
+++ b/kalamar.dict
@@ -80,6 +80,21 @@
           'kustvakt' => 'de/doc/korap/kustvakt'
         }
       }
+    },
+    Nav => {
+      'ql' => 'Anfragesprachen',
+      '#segments' => 'Einfache Segmente',
+      '#complex' => 'Komplexe Segmente',
+      '#spans' => 'Span Segmente',
+      '#paradigmatic-operators' => 'Paradigmatische Operatoren',
+      '#syntagmatic-operators' => 'Syntagmatische Operatoren',
+      '#class-operators' => 'Klassen Operatoren',
+      'regexp' => 'Reguläre Ausdrücke',
+      'data' => 'Daten',
+      'corpus' => 'Korpora',
+      'annotation' => 'Annotationen',
+      '#default-foundries' => 'Standard Foundries',
+      'faq' => 'Häufig gestellte Fragen'
     }
   },
   -en => {
@@ -138,14 +153,37 @@
     Template => {
       doc => {
         ql => {
-#          'annis' => 'doc/ql/annis',
-#          'cosmas-2' => 'doc/ql/cosmas-2',
-#          'cql' => 'doc/ql/cql',
-          'poliqarp-plus' => 'doc/ql/poliqarp-plus',
-#          'regexp' => 'doc/ql/regexp',
-#          'wildcards' => 'doc/ql/wildcards'
+          'poliqarp-plus' => 'doc/ql/poliqarp-plus'
+        },
+        api => {
+          'koralquery' => 'doc/api/koralquery'
+        },
+        data => {
+          'annotation' => 'doc/data/annotation'
+        },
+        korap => {
+          'kalamar' => 'doc/korap/kalamar',
+          'karang' => 'doc/korap/karang',
+          'koral' => 'doc/korap/koral',
+          'krill' => 'doc/korap/krill',
+          'kustvakt' => 'doc/korap/kustvakt'
         }
       }
+    },
+    Nav => {
+      'ql' => 'Query Languages',
+      '#segments' => 'Simple Segments',
+      '#complex' => 'Complex Segments',
+      '#spans' => 'Span Segments',
+      '#paradigmatic-operators' => 'Paradigmatic Operators',
+      '#syntagmatic-operators' => 'Syntagmatic Operators',
+      '#class-operators' => 'Class Operators',
+      'regexp' => 'Regular Expressions',
+      'data' => 'Data',
+      'corpus' => 'Corpora',
+      'annotation' => 'Annotations',
+      '#default-foundries' => 'Default Foundries',
+      'faq' => 'F.A.Q.'
     }
   }
 };
diff --git a/lib/Kalamar/Plugin/KalamarHelpers.pm b/lib/Kalamar/Plugin/KalamarHelpers.pm
index 8ae6c1e..a091a28 100644
--- a/lib/Kalamar/Plugin/KalamarHelpers.pm
+++ b/lib/Kalamar/Plugin/KalamarHelpers.pm
@@ -18,7 +18,7 @@
 
       my $base = $c->url_for('index');
       if ($base->path->parts->[0]) {
-	$base->path->trailing_slash(1);
+        $base->path->trailing_slash(1);
       };
 
       # If there is a different base - append this as a base
@@ -26,11 +26,12 @@
 
       $url->fragment($scope);
 
-      return $c->tag('object',
-	data => $url,
-	type => 'image/svg+xml',
-	alt  => $c->loc('korap_overview'),
-	id   => 'overview'
+      return $c->tag(
+        'object',
+        data => $url,
+        type => 'image/svg+xml',
+        alt  => $c->loc('korap_overview'),
+        id   => 'overview'
       );
     }
   );
@@ -48,17 +49,17 @@
       ($page, my $fragment) = split '#', $page;
 
       my $url = $c->url_with(
-	'doc',
-	scope => $scope,
-	page => $page
+        'doc',
+        scope => $scope,
+        page => $page
       );
 
       $url->fragment($fragment) if $fragment;
 
       return $c->link_to(
-	$title,
-	$url,
-	class => 'doc-link'
+        $title,
+        $url,
+        class => 'doc-link'
       );
     }
   );
@@ -87,11 +88,11 @@
       my $scope = shift;
       my $url;
       if ($page) {
-	$url = $c->url_for('doc', page => $page, scope => $scope);
-	$url->path->canonicalize;
+        $url = $c->url_for('doc', page => $page, scope => $scope);
+        $url->path->canonicalize;
       }
       else {
-	$url = $c->url_for('doc_start');
+        $url = $c->url_for('doc_start');
       };
       return $c->link_to($cb->($c), $url);
     }
@@ -111,71 +112,73 @@
       # Embed all link tags
       foreach (@$items) {
 
-	my ($active, $url) = 0;
+        my ($active, $url) = 0;
 
-	# There is a fragment!
-	if (index($_->{id}, '#') == 0) {
+        # There is a fragment!
+        if (index($_->{id}, '#') == 0) {
 
-	  my $part_scope = scalar($scope);
-	  $part_scope =~ s!\/([^\/]+)$!!;
-	  my $page = $1;
-	  my $id = $_->{id};
-	  $id =~ s/^#//;
+          my $part_scope = scalar($scope);
+          $part_scope =~ s!\/([^\/]+)$!!;
+          my $page = $1;
+          my $id = $_->{id};
+          $id =~ s/^#//;
 
-	  $url = $c->url_with(
-	    'doc',
-	    'scope' => $part_scope,
-	    'page' => $page
-	  );
+          $url = $c->url_with(
+            'doc',
+            'scope' => $part_scope,
+            'page' => $page
+          );
 
-	  $url->fragment($id);
-	}
+          $url->fragment($id);
+        }
 
-	# There is no fragment
-	else {
+        # There is no fragment
+        else {
 
-	  # The item is active
-	  if ($c->stash('page') && $c->stash('page') eq $_->{id}) {
-	    $active = 1;
-	  };
+          # The item is active
+          if ($c->stash('page') && $c->stash('page') eq $_->{id}) {
+            $active = 1;
+          };
 
-	  # Generate url with query parameter inheritance
-	  $url = $c->url_with(
-	    'doc',
-	    'scope' => $scope,
-	    'page' => $_->{id}
-	  );
+          # Generate url with query parameter inheritance
+          $url = $c->url_with(
+            'doc',
+            'scope' => $scope,
+            'page' => $_->{id}
+          );
 
-	  # Canonicalize (for empty scopes)
-	  $url->path->canonicalize;
-	};
+          # Canonicalize (for empty scopes)
+          $url->path->canonicalize;
+        };
 
-	my @classes;
-	push(@classes, $_->{'class'}) if $_->{'class'};
-	push(@classes, 'active') if $active;
+        my @classes;
+        push(@classes, $_->{'class'}) if $_->{'class'};
+        push(@classes, 'active') if $active;
 
 
-	# New list item
-	$html .= '<li';
-	if (@classes) {
-	  $html .= ' class="' . join(' ', @classes) . '"';
-	};
-	$html .= '>';
+        # New list item
+        $html .= '<li';
+        if (@classes) {
+          $html .= ' class="' . join(' ', @classes) . '"';
+        };
+        $html .= '>';
 
+        # Translate title
+        my $title = $c->loc('Nav_' . $_->{id}, $_->{title});
 
-	# Generate link
-	$html .= $c->link_to($_->{title}, $url);
+        # Generate link
+        $html .= $c->link_to($title, $url);
 
-	# Set sub entries
-	if ($_->{items} && ref($_->{items}) eq 'ARRAY') {
-	  $html .= "\n";
-	  my $subscope = $scope ? scalar($scope) . '/' . $_->{id} : $_->{id};
-	  $html .= $c->doc_navi($subscope, $_->{items});
-	  $html .= "</li>\n";
-	}
-	else {
-	  $html .= "</li>\n";
-	};
+        # Set sub entries
+        if ($_->{items} && ref($_->{items}) eq 'ARRAY') {
+          $html .= "\n";
+          my $subscope = $scope ? scalar($scope) . '/' . $_->{id} : $_->{id};
+          $html .= $c->doc_navi($subscope, $_->{items});
+          $html .= "</li>\n";
+        }
+        else {
+          $html .= "</li>\n";
+        };
       };
       return $html . "</ul>\n";
     }
@@ -192,13 +195,13 @@
 
       # Return tag
       b('<pre class="query tutorial" ' .
-	  qq!data-query="$q" data-query-cutoff="! .
-	    ($param{cutoff} ? 1 : 0) .
-	      '"' .
-		qq! data-query-language="$ql">! .
-		  '<code>' . $q . '</code>' .
-		    '</pre>'
-		);
+          qq!data-query="$q" data-query-cutoff="! .
+          ($param{cutoff} ? 1 : 0) .
+          '"' .
+          qq! data-query-language="$ql">! .
+          '<code>' . $q . '</code>' .
+          '</pre>'
+        );
     }
   );
 
@@ -210,14 +213,14 @@
 
       # Test port is defined in the stash
       if (defined $c->stash('kalamar.test_port')) {
-	return $c->stash('kalamar.test_port');
+        return $c->stash('kalamar.test_port');
       };
 
       # Check the port
       if ($c->req->url->to_abs->port == 6666 ||
-	    $c->app->mode =~ m/^development|test$/) {
-	$c->stash('kalamar.test_port' => 1);
-	return 1;
+            $c->app->mode =~ m/^development|test$/) {
+        $c->stash('kalamar.test_port' => 1);
+        return 1;
       };
 
       # No test port
diff --git a/t/docnavi.t b/t/docnavi.t
index a9dc149..122956f 100644
--- a/t/docnavi.t
+++ b/t/docnavi.t
@@ -1,8 +1,8 @@
 use Mojo::Base -strict;
-use lib '../lib', 'lib';
 use Mojolicious::Lite;
 use Test::More;
 use Test::Mojo;
+use utf8;
 
 my $t = Test::Mojo->new;
 my $app = $t->app;
@@ -16,6 +16,23 @@
 # Load plugin to test
 $app->plugin('KalamarHelpers');
 
+my $languages = [qw/en de/];
+$app->plugin('Localize' => {
+  dict => {
+    Nav => {
+      _ => sub { $languages },
+      -en => {
+        faq => 'F.A.Q.',
+        '#default-foundries' => 'Default Foundries',
+      },
+      de => {
+        faq => 'Häufige Fragen',
+        '#default-foundries' => 'Standard Foundries'
+      }
+    }
+  }
+});
+
 my $navi = [
   {
     id => 'korap',
@@ -50,8 +67,8 @@
     title => 'KorAP',
     items => [
       {
-	id => 'krill',
-	title => 'Krill',
+        id => 'krill',
+        title => 'Krill',
       }
     ]
   },
@@ -72,12 +89,12 @@
     title => 'KorAP',
     items => [
       {
-	id => 'krill',
-	title => 'Krill',
+        id => 'krill',
+        title => 'Krill',
       },
       {
-	id => 'koral',
-	title => 'Koral'
+        id => 'koral',
+        title => 'Koral'
       }
     ]
   },
@@ -86,22 +103,22 @@
     id => 'ql',
     items => [
       {
-	title => 'Cosmas II',
-	id => 'cosmas2'
+        title => 'Cosmas II',
+        id => 'cosmas2'
       },
       {
-	'title' => 'Poliqarp+',
-	'id' => 'poliqarp-plus',
-	items => [
-	  {
-	    "title" => "Simple Segments",
-	    "id" => "#segments"
-	  },
-	  {
-	    "title" => "Complex Segments",
-	    "id" => "#complex"
-	  }
-	]
+        'title' => 'Poliqarp+',
+        'id' => 'poliqarp-plus',
+        items => [
+          {
+            "title" => "Simple Segments",
+            "id" => "#segments"
+          },
+          {
+            "title" => "Complex Segments",
+            "id" => "#complex"
+          }
+        ]
       }
     ]
   },
@@ -155,12 +172,12 @@
     title => 'KorAP',
     items => [
       {
-	id => 'krill',
-	title => 'Krill',
+        id => 'krill',
+        title => 'Krill',
       },
       {
-	id => 'koral',
-	title => 'Koral'
+        id => 'koral',
+        title => 'Koral'
       }
     ]
   },
@@ -169,23 +186,23 @@
     id => 'ql',
     items => [
       {
-	title => 'Cosmas II',
-	id => 'cosmas2'
+        title => 'Cosmas II',
+        id => 'cosmas2'
       },
       {
-	'title' => 'Poliqarp+',
-	'id' => 'poliqarp-plus',
-	'class' => 'folded',
-	items => [
-	  {
-	    "title" => "Simple Segments",
-	    "id" => "#segments"
-	  },
-	  {
-	    "title" => "Complex Segments",
-	    "id" => "#complex"
-	  }
-	]
+        'title' => 'Poliqarp+',
+        'id' => 'poliqarp-plus',
+        'class' => 'folded',
+        items => [
+          {
+            "title" => "Simple Segments",
+            "id" => "#segments"
+          },
+          {
+            "title" => "Complex Segments",
+            "id" => "#complex"
+          }
+        ]
       }
     ]
   },
@@ -205,6 +222,45 @@
      'Path matches doc/ql/poliqarp-plus#complex');
 like($render, qr!class="folded active".*?Poliqarp\+!, 'Active and folded value for Poliqarp+');
 
+
+# Test for translations
+$navi = [
+  {
+    id => 'korap',
+    title => 'KorAP',
+    items => [
+      {
+        id => 'krill',
+        title => 'Krill',
+      }
+    ]
+  },
+  {
+    id => 'faq',
+    title => 'F.A.Q.'
+  }
+];
+
+$render = $app->doc_navi($navi);
+like($render, qr!/doc/korap!, 'Path matches doc/korap');
+like($render, qr!/doc/korap/krill!, 'Path matches korap/krill');
+like($render, qr!<a href="/doc/korap/krill">Krill</a>!,
+     'Path matches korap/krill');
+like($render, qr!<a href="/doc/faq">F\.A\.Q\.</a>!,
+     'Path matches FAQ');
+
+# Change preferred language
+$languages = [qw/de en/];
+
+$render = $app->doc_navi($navi);
+like($render, qr!/doc/korap!, 'Path matches doc/korap');
+like($render, qr!/doc/korap/krill!, 'Path matches korap/krill');
+like($render, qr!<a href="/doc/korap/krill">Krill</a>!,
+     'Path matches korap/krill');
+like($render, qr!<a href="/doc/faq">Häufige Fragen</a>!,
+     'Path matches FAQ');
+
+
 done_testing;
 
 __END__
diff --git a/templates/doc/navigation.json b/templates/doc/navigation.json
index 931fa7f..12e4683 100644
--- a/templates/doc/navigation.json
+++ b/templates/doc/navigation.json
@@ -4,24 +4,24 @@
     "id" : "korap",
     "items" : [
       {
-	"title" : "Kalamar",
-	"id" : "kalamar"
+        "title" : "Kalamar",
+        "id" : "kalamar"
       },
       {
-	"title" : "Kustvakt",
-	"id" : "kustvakt"
+        "title" : "Kustvakt",
+        "id" : "kustvakt"
       },
       {
-	"title" : "Koral",
-	"id" : "koral"
+        "title" : "Koral",
+        "id" : "koral"
       },
       {
-	"title" : "Krill",
-	"id" : "krill"
+        "title" : "Krill",
+        "id" : "krill"
       },
       {
-	"title" : "Karang",
-	"id" : "karang"
+        "title" : "Karang",
+        "id" : "karang"
       }
     ]
   },
@@ -30,55 +30,55 @@
     "id" : "ql",
     "items" : [
       {
-	"title" : "Cosmas II",
-	"id" : "cosmas-2"
+        "title" : "Cosmas II",
+        "id" : "cosmas-2"
       },
       {
-	"title" : "Poliqarp+",
-	"id" : "poliqarp-plus",
-	"class" : "folded",
-	"items" : [
-	  {
-	    "title" : "Simple Segments",
-	    "id" : "#segments"
-	  },
-	  {
-	    "title" : "Complex Segments",
-	    "id" : "#complex"
-	  },
-	  {
-	    "title" : "Span Segments",
-	    "id" : "#spans"
-	  },
-	  {
-	    "title" : "Paradigmatic Operators",
-	    "id" : "#paradigmatic-operators"
-	  },
-	  {
-	    "title" : "Syntagmatic Operators",
-	    "id" : "#syntagmatic-operators"
-	  },
-	  {
-	    "title" : "Class Operators",
-	    "id" : "#class-operators"
-	  }
-	]
+        "title" : "Poliqarp+",
+        "id" : "poliqarp-plus",
+        "class" : "folded",
+        "items" : [
+          {
+            "title" : "Simple Segments",
+            "id" : "#segments"
+          },
+          {
+            "title" : "Complex Segments",
+            "id" : "#complex"
+          },
+          {
+            "title" : "Span Segments",
+            "id" : "#spans"
+          },
+          {
+            "title" : "Paradigmatic Operators",
+            "id" : "#paradigmatic-operators"
+          },
+          {
+            "title" : "Syntagmatic Operators",
+            "id" : "#syntagmatic-operators"
+          },
+          {
+            "title" : "Class Operators",
+            "id" : "#class-operators"
+          }
+        ]
       },
       {
-	"title" : "Annis QL",
-	"id" : "annis"
+        "title" : "Annis QL",
+        "id" : "annis"
       },
       {
-	"title" : "CQL",
-	"id" : "cql"
+        "title" : "CQL",
+        "id" : "cql"
       },
       {
-	"title" : "Regular Expressions",
-	"id" : "regexp"
+        "title" : "Regular Expressions",
+        "id" : "regexp"
       },
       {
-	"title" : "Wildcards",
-	"id" : "wildcards"
+        "title" : "Wildcards",
+        "id" : "wildcards"
       }
     ]
   },
@@ -87,54 +87,54 @@
     "id" : "data",
     "items" : [
       {
-	"title" : "Corpora",
-	"id" : "corpus",
-	"class" : "folded",
-	"items" : [
-	  {
-	    "title" : "DeReKo",
-	    "id" : "#dereko"
-	  }
-	]
+        "title" : "Corpora",
+        "id" : "corpus",
+        "class" : "folded",
+        "items" : [
+          {
+            "title" : "DeReKo",
+            "id" : "#dereko"
+          }
+        ]
       },
       {
-	"title" : "Annotations",
-	"id" : "annotation",
-	"class" : "folded",
-	"items" : [
-	  {
-	    "title" : "Base",
-	    "id" : "#base"
-	  },
-	  {
-	    "title" : "Connexor",
-	    "id" : "#cnx"
-	  },
-	  {
-	    "title" : "CoreNLP",
-	    "id" : "#corenlp"
-	  },
-	  {
-	    "title" : "TreeTagger",
-	    "id" : "#tt"
-	  },
-	  {
-	    "title" : "Mate",
-	    "id" : "#mate"
-	  },
-	  {
-	    "title" : "OpenNLP",
-	    "id" : "#opennlp"
-	  },
-	  {
-	    "title" : "XIP",
-	    "id" : "#xip"
-	  },
-	  {
-	    "title" : "Default Foundries",
-	    "id" : "#default-foundries"
-	  }
-	]
+        "title" : "Annotations",
+        "id" : "annotation",
+        "class" : "folded",
+        "items" : [
+          {
+            "title" : "Base",
+            "id" : "#base"
+          },
+          {
+            "title" : "Connexor",
+            "id" : "#cnx"
+          },
+          {
+            "title" : "CoreNLP",
+            "id" : "#corenlp"
+          },
+          {
+            "title" : "TreeTagger",
+            "id" : "#tt"
+          },
+          {
+            "title" : "Mate",
+            "id" : "#mate"
+          },
+          {
+            "title" : "OpenNLP",
+            "id" : "#opennlp"
+          },
+          {
+            "title" : "XIP",
+            "id" : "#xip"
+          },
+          {
+            "title" : "Default Foundries",
+            "id" : "#default-foundries"
+          }
+        ]
       }
     ]
   },
@@ -143,20 +143,20 @@
     "id" : "api",
     "items" : [
       {
-	"title" : "KoralQuery",
-	"id" : "koralquery"
+        "title" : "KoralQuery",
+        "id" : "koralquery"
       },
       {
-	"title" : "Search API",
-	"id" : "search"
+        "title" : "Search API",
+        "id" : "search"
       },
       {
-	"title" : "Match API",
-	"id" : "match"
+        "title" : "Match API",
+        "id" : "match"
       },
       {
-	"title" : "User API",
-	"id" : "user"
+        "title" : "User API",
+        "id" : "user"
       }
     ]
   },