summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cpanfile2
-rwxr-xr-xlib/Travelynx.pm45
-rw-r--r--lib/Travelynx/Command/database.pm22
-rw-r--r--lib/Travelynx/Controller/Account.pm29
-rw-r--r--lib/Travelynx/Helper/Locales.pm22
-rwxr-xr-xlib/Travelynx/Model/Journeys.pm2
-rw-r--r--lib/Travelynx/Model/Users.pm19
-rw-r--r--share/locales/de_DE.po198
-rw-r--r--share/locales/en_GB.po180
-rw-r--r--templates/_checked_in.html.ep8
-rw-r--r--templates/_public_status_card.html.ep14
-rw-r--r--templates/_wagons.html.ep8
-rw-r--r--templates/account.html.ep37
-rw-r--r--templates/landingpage.html.ep4
-rw-r--r--templates/language.html.ep36
-rw-r--r--templates/layouts/default.html.ep14
-rw-r--r--templates/login.html.ep10
-rw-r--r--templates/register.html.ep18
18 files changed, 611 insertions, 57 deletions
diff --git a/cpanfile b/cpanfile
index 04083c7..6fccfd1 100644
--- a/cpanfile
+++ b/cpanfile
@@ -9,6 +9,8 @@ requires 'GIS::Distance::Fast';
requires 'IO::Socket::Socks', '>= 0.64';
requires 'IO::Socket::SSL', '>= 2.009';
requires 'List::UtilsBy';
+requires 'Locale::Maketext';
+requires 'Locale::Maketext::Lexicon';
requires 'Math::Polygon';
requires 'MIME::Entity';
requires 'Mojolicious';
diff --git a/lib/Travelynx.pm b/lib/Travelynx.pm
index 7dba658..33a8328 100755
--- a/lib/Travelynx.pm
+++ b/lib/Travelynx.pm
@@ -26,6 +26,7 @@ use Travelynx::Helper::DBRIS;
use Travelynx::Helper::EFA;
use Travelynx::Helper::HAFAS;
use Travelynx::Helper::IRIS;
+use Travelynx::Helper::Locales;
use Travelynx::Helper::MOTIS;
use Travelynx::Helper::Sendmail;
use Travelynx::Helper::Traewelling;
@@ -157,6 +158,14 @@ sub startup {
}
);
+ $self->hook(
+ 'before_render' => sub {
+ my ($self) = @_;
+
+ $self->stash( loc_handle => $self->loc_handle );
+ }
+ );
+
$self->attr(
cache_iris_main => sub {
my ($self) = @_;
@@ -412,6 +421,40 @@ sub startup {
);
$self->helper(
+ loc_handle => sub {
+ my ($self) = @_;
+
+ my @languages;
+ if ( $self->is_user_authenticated
+ and @{ $self->current_user->{languages} } )
+ {
+ @languages = @{ $self->current_user->{languages} };
+ }
+ elsif ( my $languages = $self->req->headers->accept_language ) {
+ for my $lang ( split( qr{ \s* , \s* }x, $languages ) ) {
+ if ( $lang =~ m{ ^ de }x ) {
+ push( @languages, 'de-DE' );
+ }
+ elsif ( $lang =~ m{ ^ en }x ) {
+ push( @languages, 'en-GB' );
+ }
+ }
+ }
+
+ # de-DE is our fall-back language and thus always appended
+ return Travelynx::Helper::Locales->get_handle( @languages,
+ 'de-DE' );
+ }
+ );
+
+ $self->helper(
+ 'L' => sub {
+ my ( $self, @args ) = @_;
+ $self->stash('loc_handle')->maketext(@args);
+ }
+ );
+
+ $self->helper(
'now' => sub {
return DateTime->now( time_zone => 'Europe/Berlin' );
}
@@ -3067,6 +3110,7 @@ sub startup {
$authed_r->get('/account/hooks')->to('account#webhook');
$authed_r->get('/account/traewelling')->to('traewelling#settings');
$authed_r->get('/account/insight')->to('account#insight');
+ $authed_r->get('/account/language')->to('account#change_language');
$authed_r->get('/ajax/status_card.html')->to('traveling#status_card');
$authed_r->get( '/cancelled' => [ format => [ 'html', 'json' ] ] )
->to( 'traveling#cancelled', format => undef );
@@ -3097,6 +3141,7 @@ sub startup {
$authed_r->post('/account/hooks')->to('account#webhook');
$authed_r->post('/account/traewelling')->to('traewelling#settings');
$authed_r->post('/account/insight')->to('account#insight');
+ $authed_r->post('/account/language')->to('account#change_language');
$authed_r->post('/account/select_backend')->to('account#change_backend');
$authed_r->post('/checkin/add')->to('traveling#add_intransit_form');
$authed_r->post('/journey/add')->to('traveling#add_journey_form');
diff --git a/lib/Travelynx/Command/database.pm b/lib/Travelynx/Command/database.pm
index 34efde6..009da30 100644
--- a/lib/Travelynx/Command/database.pm
+++ b/lib/Travelynx/Command/database.pm
@@ -3359,6 +3359,28 @@ qq{select distinct checkout_station_id from in_transit where backend_id = 0;}
}
);
},
+
+ # v66 -> v67
+ # Add language settings to profile
+ sub {
+ my ($db) = @_;
+ $db->query(
+ qq{
+ drop view users_with_backend;
+ alter table users add column language varchar(128);
+ update schema_version set version = 67;
+ create view users_with_backend as select
+ users.id as id, users.name as name, status, public_level,
+ language, email, password, registered_at, last_seen,
+ deletion_requested, deletion_notified, use_history,
+ accept_follows, notifications, profile, backend_id, iris,
+ hafas, efa, dbris, motis, backend.name as backend_name
+ from users
+ left join backends as backend on users.backend_id = backend.id
+ ;
+ }
+ );
+ },
);
sub sync_stations {
diff --git a/lib/Travelynx/Controller/Account.pm b/lib/Travelynx/Controller/Account.pm
index bf1eac2..8121f0a 100644
--- a/lib/Travelynx/Controller/Account.pm
+++ b/lib/Travelynx/Controller/Account.pm
@@ -874,6 +874,35 @@ sub webhook {
$self->render( 'webhooks', hook => $hook );
}
+sub change_language {
+ my ($self) = @_;
+
+ my $action = $self->req->param('action');
+ my $language = $self->req->param('language');
+
+ if ( $action and $action eq 'save' ) {
+ if ( $self->validation->csrf_protect->has_error('csrf_token') ) {
+ $self->render(
+ 'bad_request',
+ csrf => 1,
+ status => 400
+ );
+ return;
+ }
+ $self->users->set_language(
+ uid => $self->current_user->{id},
+ language => $language,
+ );
+ $self->flash( success => 'language' );
+ $self->redirect_to('account');
+ }
+ else {
+ my @languages = @{ $self->current_user->{languages} };
+ $self->param( language => $languages[0] // q{} );
+ $self->render('language');
+ }
+}
+
sub change_mail {
my ($self) = @_;
diff --git a/lib/Travelynx/Helper/Locales.pm b/lib/Travelynx/Helper/Locales.pm
new file mode 100644
index 0000000..12e95d1
--- /dev/null
+++ b/lib/Travelynx/Helper/Locales.pm
@@ -0,0 +1,22 @@
+package Travelynx::Helper::Locales;
+
+use strict;
+use warnings;
+
+use base qw(Locale::Maketext);
+
+our %lexicon = (
+ _AUTO => 1,
+);
+
+use Locale::Maketext::Lexicon {
+ _decode => 1,
+ '*' => [ Gettext => 'share/locales/*.po' ],
+};
+
+sub init {
+ my ($self) = @_;
+ return $self->SUPER::init( @_[ 1 .. $#_ ] );
+}
+
+1;
diff --git a/lib/Travelynx/Model/Journeys.pm b/lib/Travelynx/Model/Journeys.pm
index b07511a..5e6195f 100755
--- a/lib/Travelynx/Model/Journeys.pm
+++ b/lib/Travelynx/Model/Journeys.pm
@@ -50,6 +50,8 @@ sub epoch_to_dt {
);
}
+# TODO turn into a travelynx helper called from templates so that
+# loc_handle is available for localization
sub min_to_human {
my ( $self, $minutes ) = @_;
diff --git a/lib/Travelynx/Model/Users.pm b/lib/Travelynx/Model/Users.pm
index be9e80b..3ef7f33 100644
--- a/lib/Travelynx/Model/Users.pm
+++ b/lib/Travelynx/Model/Users.pm
@@ -216,6 +216,14 @@ sub set_backend {
);
}
+sub set_language {
+ my ( $self, %opt ) = @_;
+ $opt{db} //= $self->{pg}->db;
+
+ $opt{db}
+ ->update( 'users', { language => $opt{language} }, { id => $opt{uid} } );
+}
+
sub set_privacy {
my ( $self, %opt ) = @_;
my $db = $opt{db} // $self->{pg}->db;
@@ -413,7 +421,7 @@ sub get {
my $user = $db->select(
'users_with_backend',
- 'id, name, status, public_level, email, '
+ 'id, name, status, public_level, email, language, '
. 'accept_follows, notifications, '
. 'extract(epoch from registered_at) as registered_at_ts, '
. 'extract(epoch from last_seen) as last_seen_ts, '
@@ -423,10 +431,11 @@ sub get {
)->hash;
if ($user) {
return {
- id => $user->{id},
- name => $user->{name},
- status => $user->{status},
- notifications => $user->{notifications},
+ id => $user->{id},
+ name => $user->{name},
+ languages => [ split( qr{[|]}, $user->{language} // q{} ) ],
+ status => $user->{status},
+ notifications => $user->{notifications},
accept_follows => $user->{accept_follows} == 2 ? 1 : 0,
accept_follow_requests => $user->{accept_follows} == 1 ? 1 : 0,
default_visibility => $user->{public_level} & 0x7f,
diff --git a/share/locales/de_DE.po b/share/locales/de_DE.po
new file mode 100644
index 0000000..7d86a42
--- /dev/null
+++ b/share/locales/de_DE.po
@@ -0,0 +1,198 @@
+msgid ""
+msgstr ""
+"Language: de-DE\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+
+#
+# Global Strings
+#
+
+msgid "button.register"
+msgstr "Registrieren"
+
+msgid "button.login"
+msgstr "Anmelden"
+
+msgid "button.logout"
+msgstr "Abmelden"
+
+msgid "footer.imprint"
+msgstr "Impressum"
+
+msgid "footer.privacy"
+msgstr "Datenschutz"
+
+msgid "footer.legend"
+msgstr "Legende"
+
+msgid "footer.colour-scheme"
+msgstr "Farbschema"
+
+msgid "footer.colour-scheme.light"
+msgstr "hell"
+
+msgid "footer.colour-scheme.dark"
+msgstr "dunkel"
+
+msgid "footer.colour-scheme.auto"
+msgstr "automatisch"
+
+#
+# Templates
+#
+
+# account.html.ep
+
+msgid "account.account"
+msgstr "Account"
+
+msgid "account.name"
+msgstr "Name"
+
+msgid "account.mail"
+msgstr "E-Mail"
+
+msgid "account.password"
+msgstr "Passwort"
+
+msgid "account.language"
+msgstr "Sprache"
+
+msgid "account.connections"
+msgstr "Verbindungen"
+
+msgid "account.connections.enabled"
+msgstr "Vorschläge aktiv"
+
+msgid "account.connections.disabled"
+msgstr "Vorschläge deaktiviert"
+
+msgid "account.visibility"
+msgstr "Sichtbarkeit"
+
+msgid "account.interaction"
+msgstr "Interaktion"
+
+msgid "account.interaction.accept-follows"
+msgstr "Accounts können dir direkt folgen"
+
+msgid "account.interaction.accept-follow-requests"
+msgstr "Accounts können dir auf Anfrage folgen"
+
+msgid "account.interaction.one"
+msgstr "eine"
+
+msgid "account.interaction.open-request"
+msgstr "offene Anfrage"
+
+msgid "account.interaction.open-requests"
+msgstr "offene Anfragen"
+
+msgid "account.interaction.disabled"
+msgstr "Accounts können dir nicht folgen"
+
+# login.html.ep
+
+msgid "login.accept-tos-pre"
+msgstr "Mit der Anmeldung stimmst du den"
+
+msgid "login.tos"
+msgstr "Nutzungsbedingungen"
+
+msgid "login.accept-tos-post"
+msgstr "zu."
+
+msgid "login.forgot-password"
+msgstr "Passwort vergessen"
+
+msgid "login.registration-disabled"
+msgstr "Diese Instanz erlaubt derzeit keine Registrierung neuer Accounts"
+
+# register.html.ep
+
+msgid "register.name"
+msgstr "Name (alphanumerisch)"
+
+msgid "register.mail"
+msgstr "E-Mail-Adresse"
+
+msgid "register.password"
+msgstr "Passwort"
+
+msgid "register.repeat-password"
+msgstr "Passwort wiederholen"
+
+msgid "register.accept-tos-pre"
+msgstr "Mit deiner Registrierung stimmst du den"
+
+msgid "register.tos"
+msgstr "Nutzungsbedingungen"
+
+msgid "register.accept-tos-post"
+msgstr "zu."
+
+msgid "register.expect-confirmation-link"
+msgstr "Nach der Registrierung wird ein für 48 Stunden gültiger Bestätigungslink an die angegebene Mail-Adresse geschickt. Eine Anmeldung ist erst nach Bestätigung der Mail-Adresse möglich."
+
+msgid "register.why-mail"
+msgstr "Die Mail-Adresse wird ausschließlich zur Bestätigung der Anmeldung, für die „Passwort vergessen“-Funktionalität und für wichtige Informationen über den Account verwendet und nicht an Dritte weitergegeben."
+
+msgid "register.privacy-pre"
+msgstr "Die"
+
+msgid "register.privacy"
+msgstr "Datenschutzerklärung"
+
+msgid "register.privacy-post"
+msgstr "beschreibt weitere erhobene Daten sowie deren Zweck und Speicherfristen."
+
+msgid "register.account-deletion"
+msgstr "Accounts werden nach einem Jahr ohne Aktivität per E-Mail über die bevorstehende Löschung informiert und nach vier weiteren Wochen ohne Aktivität automatisch gelöscht."
+
+msgid "register.disclaimer"
+msgstr "Bitte beachten: Travelynx ist ein privat betriebenes Projekt ohne Verfügbarkeitsgarantie. Unangekündigte Downtimes oder eine kurzfristige Einstellung dieser Seite sind nicht vorgesehen, aber möglich."
+
+# _public_status_card.html.ep
+
+msgid "status.is-checked-in"
+msgstr "ist unterwegs"
+
+msgid "status.is-not-checked-in"
+msgstr "ist gerade nicht eingecheckt"
+
+msgid "status.share"
+msgstr "Teilen"
+
+msgid "status.arrival-in"
+msgstr "Ankunft in"
+
+msgid "status.arrival-soon"
+msgstr "Ankunft in weniger als einer Minute"
+
+msgid "status.arrival-unknown"
+msgstr "Ankunft unbekannt"
+
+msgid "status.arrived"
+msgstr "Ziel erreicht"
+
+msgid "status.carriages"
+msgstr "Wagen"
+
+msgid "status.route"
+msgstr "Route"
+
+# _wagons.html.ep
+
+msgid "wagons.name-as-type"
+msgstr "als"
+
+msgid "wagons.from"
+msgstr "von"
+
+msgid "wagons.to"
+msgstr "nach"
+
+msgid "wagons.carriage"
+msgstr "Wagen"
diff --git a/share/locales/en_GB.po b/share/locales/en_GB.po
new file mode 100644
index 0000000..47983a3
--- /dev/null
+++ b/share/locales/en_GB.po
@@ -0,0 +1,180 @@
+msgid ""
+msgstr ""
+"Language: en-GB\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+
+#
+# Global Strings
+#
+
+msgid "button.register"
+msgstr "Register"
+
+msgid "button.login"
+msgstr "Login"
+
+msgid "button.logout"
+msgstr "Logout"
+
+msgid "footer.imprint"
+msgstr "Imprint"
+
+msgid "footer.privacy"
+msgstr "Privacy"
+
+msgid "footer.legend"
+msgstr "Legend"
+
+msgid "footer.colour-scheme"
+msgstr "Display Mode"
+
+msgid "footer.colour-scheme.light"
+msgstr "light"
+
+msgid "footer.colour-scheme.dark"
+msgstr "dark"
+
+msgid "footer.colour-scheme.auto"
+msgstr "auto"
+
+#
+# Templates
+#
+
+# account.html.ep
+
+msgid "account.account"
+msgstr "Account"
+
+msgid "account.name"
+msgstr "Name"
+
+msgid "account.mail"
+msgstr "E-Mail"
+
+msgid "account.password"
+msgstr "Password"
+
+msgid "account.language"
+msgstr "Language"
+
+msgid "account.connections"
+msgstr "Connections"
+
+msgid "account.connections.enabled"
+msgstr "Suggestions enabled"
+
+msgid "account.connections.disabled"
+msgstr "Suggestions disabled"
+
+msgid "account.visibility"
+msgstr "Visibility"
+
+msgid "account.interaction"
+msgstr "Interaction"
+
+msgid "account.interaction.accept-follows"
+msgstr "Accounts may follow you"
+
+msgid "account.interaction.accept-follow-requests"
+msgstr "Accounts may send follow requests"
+
+msgid "account.interaction.one"
+msgstr "one"
+
+msgid "account.interaction.open-request"
+msgstr "open request"
+
+msgid "account.interaction.open-requests"
+msgstr "open requests"
+
+msgid "account.interaction.disabled"
+msgstr "Accounts cannot follow you"
+
+# login.html.ep
+
+msgid "login.tos"
+msgstr "terms of use"
+
+msgid "login.accept-tos-pre"
+msgstr "By logging in, you accept the"
+
+msgid "login.accept-tos-post"
+msgstr " "
+
+msgid "login.forgot-password"
+msgstr "Forgot password"
+
+msgid "login.registration-disabled"
+msgstr "This instance does not allow registration of new accounts at the moment"
+
+# register.html.ep
+
+msgid "register.name"
+msgstr "Name (alphanumeric)"
+
+msgid "register.mail"
+msgstr "E-Mail address"
+
+msgid "register.password"
+msgstr "Password"
+
+msgid "register.repeat-password"
+msgstr "Repeat password"
+
+msgid "register.tos"
+msgstr "terms of use"
+
+msgid "register.accept-tos-pre"
+msgstr "By submitting this registration form, you accept the"
+
+msgid "register.accept-tos-post"
+msgstr " "
+
+msgid "register.expect-confirmation-link"
+msgstr "After submitting the registration, a confirmation link will be sent to the provided E-Mail address. Logging into the new travelynx account is only possible after following that link. The link is valid for 48 hours."
+
+# _checked_in, _public_status_card.html.ep
+
+msgid "status.is-checked-in"
+msgstr "is in transit"
+
+msgid "status.is-not-checked-in"
+msgstr "ist not in transit right now"
+
+msgid "status.share"
+msgstr "Share"
+
+msgid "status.arrival-in"
+msgstr "Arrival in"
+
+msgid "status.arrival-soon"
+msgstr "Arrival in less than one minute"
+
+msgid "status.arrival-unknown"
+msgstr "Arrival unknown"
+
+msgid "status.arrived"
+msgstr "Arrived"
+
+msgid "status.carriages"
+msgstr "Carriages"
+
+msgid "status.route"
+msgstr "Route"
+
+# _wagons.html.ep
+
+msgid "wagons.name-as-type"
+msgstr "running as"
+
+msgid "wagons.from"
+msgstr "from"
+
+msgid "wagons.to"
+msgstr "towards"
+
+msgid "wagons.carriage"
+msgstr "Carriage"
diff --git a/templates/_checked_in.html.ep b/templates/_checked_in.html.ep
index e1cefe6..c798393 100644
--- a/templates/_checked_in.html.ep
+++ b/templates/_checked_in.html.ep
@@ -32,13 +32,13 @@
% }
% elsif (defined $journey->{arrival_countdown}) {
% if ($journey->{arrival_countdown} > 60) {
- Ankunft in <%= journeys->min_to_human(int($journey->{arrival_countdown} / 60)) %>
+ <%= L('status.arrival-in') %> <%= journeys->min_to_human(int($journey->{arrival_countdown} / 60)) %>
% }
% elsif ($journey->{arrival_countdown} > 0) {
- Ankunft in weniger als einer Minute
+ %= L('status.arrival-soon')
% }
% else {
- Ziel erreicht
+ %= L('status.arrived')
% }
% if ($journey->{arrival_countdown} < (60 * 15) and $journey->{arr_platform}) {
% if ($journey->{arr_direction} and $journey->{arr_direction} eq 'r') {
@@ -311,7 +311,7 @@
data-url="<%= url_for('/status')->to_abs->scheme('https') %>/<%= $user->{name} %>/<%= $journey->{sched_departure}->epoch %>?token=<%= $journey->{dep_eva} %>-<%= $journey->{timestamp}->epoch % 337 %>"
% }
>
- <i class="material-icons left" aria-hidden="true">share</i> Teilen
+ <i class="material-icons left" aria-hidden="true">share</i> <%= L('status.share') %>
</a>
% }
% else {
diff --git a/templates/_public_status_card.html.ep b/templates/_public_status_card.html.ep
index 32b193a..73840b3 100644
--- a/templates/_public_status_card.html.ep
+++ b/templates/_public_status_card.html.ep
@@ -11,7 +11,7 @@
<a href="/status/<%= $name %>"><%= $name %></a>: <%= include '_format_train', journey => $journey %>
% }
% else {
- <a href="/p/<%= $name %>"><%= $name %></a> ist unterwegs
+ <a href="/p/<%= $name %>"><%= $name %></a> <%= L('status.is-checked-in') %>
% }
<i class="material-icons right"><%= visibility_icon($journey->{effective_visibility_str}) %></i>
% if (not $journey->{extra_data}{rt}) {
@@ -41,10 +41,10 @@
% }
% elsif (defined $journey->{arrival_countdown}) {
% if ($journey->{arrival_countdown} > 60) {
- Ankunft in <%= journeys->min_to_human(int($journey->{arrival_countdown} / 60)) %>
+ <%= L('status.arrival-in') %> <%= journeys->min_to_human(int($journey->{arrival_countdown} / 60)) %>
% }
% elsif ($journey->{arrival_countdown} > 0) {
- Ankunft in weniger als einer Minute
+ %= L('status.arrival-soon')
% }
% else {
Ziel erreicht
@@ -54,7 +54,7 @@
% }
% }
% elsif ($journey->{arr_name}) {
- Ankunft in mehr als zwei Stunden
+ %= L('status.arrival-unknown')
% }
</div>
<div class="progress" style="height: 1ex;">
@@ -216,14 +216,14 @@
% }
% else {
<div class="wagons" style="margin-top: 2ex;">
- Wagen:<br/>
+ <%= L('status.carriages') %>:<br/>
%= include '_wagons', wagongroups => $journey->{wagongroups};
</div>
% }
% }
% if (not stash('from_timeline')) {
<div style="margin-top: 2ex;">
- Route:<br/>
+ <%= L('status.route') %>:<br/>
% my $before = 1;
% my $within = 0;
% my $at_startstop = 0;
@@ -280,7 +280,7 @@
<span class="card-title">Aktuell nicht eingecheckt</span>
% }
% else {
- <span class="card-title"><a href="/p/<%= $name %>"><%= $name %></a> ist gerade nicht eingecheckt</span>
+ <span class="card-title"><a href="/p/<%= $name %>"><%= $name %></a> <%= L('status.is-not-checked-in') %></span>
% }
<div>
% if ($journey->{arr_name}) {
diff --git a/templates/_wagons.html.ep b/templates/_wagons.html.ep
index 4090f11..926aac1 100644
--- a/templates/_wagons.html.ep
+++ b/templates/_wagons.html.ep
@@ -7,12 +7,12 @@
% elsif ($wagon_number and my $group_name = app->ice_name->{$wagon_number}) {
„<%= $group_name %>“
% }
- als <b><%= $wagongroup->{type} // $journey->{type} %> <%= $wagongroup->{no} %></b>
+ <%= L('wagons.name-as-type') %> <b><%= $wagongroup->{type} // $journey->{type} %> <%= $wagongroup->{no} %></b>
% if ($wagongroup->{from}) {
- von <b><%= $wagongroup->{from} %></b>
+ <%= L('wagons.from') %> <b><%= $wagongroup->{from} %></b>
% }
% if ($wagongroup->{to}) {
- nach <b><%= $wagongroup->{to} %></b>
+ <%= L('wagons.to') %> <b><%= $wagongroup->{to} %></b>
% }
<br/>
% for my $wagon (@{$wagongroup->{wagons}}) {
@@ -24,7 +24,7 @@
% }
%= $wagon->{type}
% if ($wagon->{number}) {
- – Wagen <%= $wagon->{number} %>
+ – <%= L('wagons.carriage') %> <%= $wagon->{number} %>
% }
<br/>
% }
diff --git a/templates/account.html.ep b/templates/account.html.ep
index e4bf38d..837f219 100644
--- a/templates/account.html.ep
+++ b/templates/account.html.ep
@@ -16,6 +16,9 @@
% elsif ($success eq 'password') {
<span class="card-title">Passwort geändert</span>
% }
+ % elsif ($success eq 'language') {
+ <span class="card-title">Sprache geändert</span>
+ % }
% elsif ($success eq 'privacy') {
<span class="card-title">Einstellungen zu öffentlichen Account-Daten geändert</span>
% }
@@ -46,34 +49,38 @@
% my $use_history = users->use_history(uid => $acc->{id});
<div class="row">
<div class="col s12">
- <h2>Account</h2>
+ <h2><%= L('account.account') %></h2>
<table class="striped">
<tr>
- <th scope="row">Name</th>
+ <th scope="row"><%= L('account.name') %></th>
<td><a href="/account/name"><i class="material-icons">edit</i></a><%= $acc->{name} %></td>
</tr>
<tr>
- <th scope="row">Mail</th>
+ <th scope="row"><%= L('account.mail') %></th>
<td><a href="/account/mail"><i class="material-icons">edit</i></a><%= $acc->{email} %></td>
</tr>
<tr>
- <th scope="row">Passwort</th>
+ <th scope="row"><%= L('account.password') %></th>
<td><a href="/account/password"><i class="material-icons">edit</i></a></td>
</tr>
<tr>
- <th scope="row">Verbindungen</th>
+ <th scope="row"><%= L('account.language') %></th>
+ <td><a href="/account/language"><i class="material-icons">edit</i></a><%= $acc->{languages}[0] // q{} %></td>
+ </tr>
+ <tr>
+ <th scope="row"><%= L('account.connections') %></th>
<td>
<a href="/account/insight"><i class="material-icons">edit</i></a>
% if ($use_history & 0x03) {
- Vorschläge aktiv
+ %= L('account.connections.enabled')
% }
% else {
- <span style="color: #999999;">Vorschläge deaktiviert</span>
+ <span style="color: #999999;"><%= L('account.connections.disabled') %></span>
% }
</td>
</tr>
<tr>
- <th scope="row">Sichtbarkeit</th>
+ <th scope="row"><%= L('account.visibility') %></th>
<td>
<a href="/account/privacy"><i class="material-icons">edit</i></a>
<i class="material-icons">check</i><i class="material-icons"><%= visibility_icon($acc->{default_visibility_str}) %></i>
@@ -81,23 +88,23 @@
</td>
</tr>
<tr>
- <th scope="row">Interaktion</th>
+ <th scope="row"><%= L('account.interaction') %></th>
<td>
<a href="/account/social"><i class="material-icons">edit</i></a>
% if ($acc->{accept_follows}) {
- <span>Accounts können dir direkt folgen</span>
+ <span><%= L('account.interaction.accept-follows') %></span>
% }
% elsif ($acc->{accept_follow_requests}) {
- <span>Accounts können dir auf Anfrage folgen
+ <span><%= L('account.interaction.accept-follow-requests') %>
% if ($num_rx_follow_requests == 1) {
- – <a href="/account/social/follow-requests-received"><strong>eine</strong> offene Anfrage</a>
+ – <a href="/account/social/follow-requests-received"><strong><%= L('account.interaction.one') %></strong> <%= L('account.interaction.open-request') %></a>
% } elsif ($num_rx_follow_requests) {
- – <a href="/account/social/follow-requests-received"><strong><%= $num_rx_follow_requests %></strong> offene Anfragen</a>
+ – <a href="/account/social/follow-requests-received"><strong><%= $num_rx_follow_requests %></strong> <%= L('account.interaction.open-requests') %></a>
% }
</span>
% }
% else {
- <span style="color: #999999;">Accounts können dir nicht folgen</span>
+ <span style="color: #999999;"><%= L('account.interaction.disabled') %></span>
% }
</td>
</tr>
@@ -163,7 +170,7 @@
%= form_for 'logout' => begin
%= csrf_field
<button class="btn waves-effect waves-light" type="submit" name="action" value="logout">
- Abmelden
+ %= L('button.logout')
</button>
%= end
</div>
diff --git a/templates/landingpage.html.ep b/templates/landingpage.html.ep
index 5ca0e9e..9c2ccde 100644
--- a/templates/landingpage.html.ep
+++ b/templates/landingpage.html.ep
@@ -144,9 +144,9 @@
</div>
<div class="col s10 m10 l6 center-align">
% if (not app->config->{registration}{disabled}) {
- <a href="/register" class="waves-effect waves-light btn"><i class="material-icons left" aria-hidden="true">add</i>Registrieren</a>
+ <a href="/register" class="waves-effect waves-light btn"><i class="material-icons left" aria-hidden="true">add</i><%= L('button.register') %></a>
% }
- <a href="/login" class="waves-effect waves-light btn"><i class="material-icons left" aria-hidden="true">account_circle</i>Anmelden</a>
+ <a href="/login" class="waves-effect waves-light btn"><i class="material-icons left" aria-hidden="true">account_circle</i><%= L('button.login') %></a>
</div>
<div class="col s1 m1 l3">
</div>
diff --git a/templates/language.html.ep b/templates/language.html.ep
new file mode 100644
index 0000000..75df054
--- /dev/null
+++ b/templates/language.html.ep
@@ -0,0 +1,36 @@
+<h1>Sprache</h1>
+%= form_for '/account/language' => (method => 'POST') => begin
+ %= csrf_field
+ <div class="row">
+ <div class="input-field col s12">
+ <div>
+ <label>
+ %= radio_button language => 'de-DE'
+ <span>de-DE: Deutsch (hochdeutsch)</span>
+ </label>
+ </div>
+ </div>
+ </div>
+ <div class="row">
+ <div class="input-field col s12">
+ <div>
+ <label>
+ %= radio_button language => 'en-GB'
+ <span>en-GB: English (Great Britain)</span>
+ </label>
+ </div>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col s3 m3 l3">
+ </div>
+ <div class="col s6 m6 l6 center-align">
+ <button class="btn waves-effect waves-light" type="submit" name="action" value="save">
+ Speichern
+ <i class="material-icons right">send</i>
+ </button>
+ </div>
+ <div class="col s3 m3 l3">
+ </div>
+ </div>
+%= end
diff --git a/templates/layouts/default.html.ep b/templates/layouts/default.html.ep
index 2279789..ec3b5e0 100644
--- a/templates/layouts/default.html.ep
+++ b/templates/layouts/default.html.ep
@@ -131,21 +131,21 @@
<div class="col s12 center-align grey-text">
<a href="/about">travelynx</a> v<%= $version // '???' %>
<span style="margin-left: 0.5em; margin-right: 0.5em;">–</span>
- <a href="/impressum">Impressum</a>
+ <a href="/impressum"><%= L('footer.imprint') %></a>
<span style="margin-left: 0.5em; margin-right: 0.5em;">–</span>
- <a href="/impressum">Datenschutz</a>
+ <a href="/impressum"><%= L('footer.privacy') %></a>
<span style="margin-left: 0.5em; margin-right: 0.5em;">–</span>
- <a href="/legend">Legende</a>
+ <a href="/legend"><%= L('footer.legend') %></a>
</div>
</div>
<div class="row">
<div class="col s12 center-align grey-text config">
- Farbschema:
- <a onClick="javascript:setTheme('light')">hell</a>
+ <%= L('footer.colour-scheme') %>:
+ <a onClick="javascript:setTheme('light')"><%= L('footer.colour-scheme.light') %></a>
·
- <a onClick="javascript:setTheme('dark')">dunkel</a>
+ <a onClick="javascript:setTheme('dark')"><%= L('footer.colour-scheme.dark') %></a>
·
- <a onClick="javascript:setTheme('default')">automatisch</a>
+ <a onClick="javascript:setTheme('default')"><%= L('footer.colour-scheme.auto') %></a>
</div>
</div>
</div>
diff --git a/templates/login.html.ep b/templates/login.html.ep
index 3a9cc1f..21f14d3 100644
--- a/templates/login.html.ep
+++ b/templates/login.html.ep
@@ -75,7 +75,9 @@
</div>
<div class="row">
<div class="col s12 m12 l12">
- Mit der Anmeldung stimmst du den <a href="/tos">Nutzungsbedingungen</a> zu.
+ %= L('login.accept-tos-pre')
+ <a href="/tos"><%= L('login.tos') %></a>
+ %= L('login.accept-tos-post')
</div>
</div>
<div class="row">
@@ -83,7 +85,7 @@
</div>
<div class="col s6 m6 l6 center-align">
<button class="btn waves-effect waves-light" type="submit" name="action" value="login">
- Anmelden
+ %= L('button.login')
<i class="material-icons right">send</i>
</button>
</div>
@@ -95,7 +97,7 @@
</div>
<div class="col s6 m6 l6 center-align">
<a href="/recover">
- Passwort vergessen
+ %= L('login.forgot-password')
</a>
</div>
<div class="col s3 m3 l3">
@@ -104,7 +106,7 @@
% if (app->config->{registration}{disabled}) {
<div class="row" style="margin-top: 2em;">
<div class="col s12 center-align">
- <em>Diese Instanz erlaubt derzeit keine Registrierung neuer Accounts</em>
+ <em><%= L('login.registration-disabled') %></em>
</div>
</div>
% }
diff --git a/templates/register.html.ep b/templates/register.html.ep
index f9a486a..e7064da 100644
--- a/templates/register.html.ep
+++ b/templates/register.html.ep
@@ -8,27 +8,29 @@
<div class="input-field col l6 m12 s12">
<i class="material-icons prefix">account_circle</i>
%= text_field 'user', id => 'account', class => 'validate', required => undef, pattern => '[0-9a-zA-Z_-]+', maxlength => 60, autocomplete => 'username'
- <label for="account">Name (alphanumerisch)</label>
+ <label for="account"><%= L('register.name') %></label>
</div>
<div class="input-field col l6 m12 s12">
<i class="material-icons prefix">email</i>
%= email_field 'email', id => 'email', class => 'validate', required => undef, maxlength => 250
- <label for="email">Mail-Adresse</label>
+ <label for="email"><%= L('register.mail') %></label>
</div>
<div class="input-field col l6 m12 s12">
<i class="material-icons prefix">lock</i>
%= password_field 'password', id => 'password', class => 'validate', required => undef, minlength => 8, maxlength => 10000, autocomplete => 'new-password'
- <label for="password">Passwort</label>
+ <label for="password"><%= L('register.password') %></label>
</div>
<div class="input-field col l6 m12 s12">
<i class="material-icons prefix">lock</i>
%= password_field 'password2', id => 'password2', class => 'validate', required => undef, minlength => 8, maxlength => 10000, autocomplete => 'new-password'
- <label for="password2">Passwort wiederholen</label>
+ <label for="password2"><%= L('register.repeat-password') %></label>
</div>
</div>
<div class="row">
<div class="col s12 m12 l12">
- Mit deiner Registrierung stimmst du den <a href="/tos">Nutzungsbedingungen</a> zu.
+ %= L('register.accept-tos-pre')
+ <a href="/tos"><%= L('register.tos') %></a>
+ %= L('register.accept-tos-post')
</div>
</div>
<div class="row">
@@ -36,7 +38,7 @@
</div>
<div class="col s6 m6 l6 center-align">
<button class="btn waves-effect waves-light" type="submit" name="action" value="register">
- Registrieren
+ %= L('button.register')
<i class="material-icons right">send</i>
</button>
</div>
@@ -47,9 +49,7 @@
<div class="row">
<div class="col s12">
<p>
- Nach der Registrierung wird ein für 48 Stunden gültiger
- Bestätigungslink an die angegebene Mail-Adresse geschickt. Eine
- Anmeldung ist erst nach Bestätigung der Mail-Adresse möglich.
+ %= L('register.expect-confirmation-link')
</p>
<p>
Die Mail-Adresse wird ausschließlich zur Bestätigung der Anmeldung,