From 171e797ee7f3930165606e72036d953152d5bd76 Mon Sep 17 00:00:00 2001 From: Birte Kristina Friesel Date: Fri, 18 Jul 2025 19:55:02 +0200 Subject: Add localization support. Right now, only two languages are supported, and only a fraction of strings are already translated. There's also quite a bunch of todos left where strings are assembled in the Model, which has no access to localization functions. But that's something for iterative refinement over the next months, and (especially when it comes to adding languages and translation strings to templates) merge requests. Squashed commit of the following: commit 67d756f3bd167003907c8357126630dd7c1a3cfa Author: Birte Kristina Friesel Date: Fri Jul 18 19:53:56 2025 +0200 more translations commit 8cb0d65e70e42180419a5dd7634d332e65488dd4 Author: Birte Kristina Friesel Date: Fri Jul 18 18:54:12 2025 +0200 sme more translations commit ff12f010380914f9461966f2ef8ac6b303712ee4 Author: Birte Kristina Friesel Date: Fri Jul 18 18:53:31 2025 +0200 Add language selection to account page commit 9bf27132cbf2f87bca5af564914d96a57045ecc1 Author: Birte Kristina Friesel Date: Fri Jul 18 16:42:28 2025 +0200 Translate footer components commit 90c2c6505e933848268ed9c5bbe21e0b459cd72a Author: Birte Kristina Friesel Date: Fri Jul 18 16:16:50 2025 +0200 Use Accept-Language header if user has no preferred languages commit 814cb4a4dd4017606829ecc6b6c70822bf52a30e Author: Birte Kristina Friesel Date: Fri Jul 18 16:11:19 2025 +0200 Add list of preferred languages to user settings commit 731b789855914cb94ec091604e32aa68a678404a Author: Birte Kristina Friesel Date: Fri Jul 18 15:33:42 2025 +0200 Localization with Locale::Maketext WiP, no suitable foundation for merge requests yet. Still todo: * override Accept-Language header via account settings * Adjust all the templates and frontend javascript Related to #223 --- cpanfile | 2 + lib/Travelynx.pm | 45 ++++++++ lib/Travelynx/Command/database.pm | 22 ++++ lib/Travelynx/Controller/Account.pm | 29 +++++ lib/Travelynx/Helper/Locales.pm | 22 ++++ lib/Travelynx/Model/Journeys.pm | 2 + lib/Travelynx/Model/Users.pm | 19 +++- share/locales/de_DE.po | 198 ++++++++++++++++++++++++++++++++++ share/locales/en_GB.po | 180 +++++++++++++++++++++++++++++++ templates/_checked_in.html.ep | 8 +- templates/_public_status_card.html.ep | 14 +-- templates/_wagons.html.ep | 8 +- templates/account.html.ep | 37 ++++--- templates/landingpage.html.ep | 4 +- templates/language.html.ep | 36 +++++++ templates/layouts/default.html.ep | 14 +-- templates/login.html.ep | 10 +- templates/register.html.ep | 18 ++-- 18 files changed, 611 insertions(+), 57 deletions(-) create mode 100644 lib/Travelynx/Helper/Locales.pm create mode 100644 share/locales/de_DE.po create mode 100644 share/locales/en_GB.po create mode 100644 templates/language.html.ep 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) = @_; @@ -411,6 +420,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 %>" % } > - Teilen + <%= L('status.share') %> % } % 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 @@ <%= $name %>: <%= include '_format_train', journey => $journey %> % } % else { - <%= $name %> ist unterwegs + <%= $name %> <%= L('status.is-checked-in') %> % } <%= visibility_icon($journey->{effective_visibility_str}) %> % 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') % }
@@ -216,14 +216,14 @@ % } % else {
- Wagen:
+ <%= L('status.carriages') %>:
%= include '_wagons', wagongroups => $journey->{wagongroups};
% } % } % if (not stash('from_timeline')) {
- Route:
+ <%= L('status.route') %>:
% my $before = 1; % my $within = 0; % my $at_startstop = 0; @@ -280,7 +280,7 @@ Aktuell nicht eingecheckt % } % else { - <%= $name %> ist gerade nicht eingecheckt + <%= $name %> <%= L('status.is-not-checked-in') %> % }
% 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 <%= $wagongroup->{type} // $journey->{type} %> <%= $wagongroup->{no} %> + <%= L('wagons.name-as-type') %> <%= $wagongroup->{type} // $journey->{type} %> <%= $wagongroup->{no} %> % if ($wagongroup->{from}) { - von <%= $wagongroup->{from} %> + <%= L('wagons.from') %> <%= $wagongroup->{from} %> % } % if ($wagongroup->{to}) { - nach <%= $wagongroup->{to} %> + <%= L('wagons.to') %> <%= $wagongroup->{to} %> % }
% for my $wagon (@{$wagongroup->{wagons}}) { @@ -24,7 +24,7 @@ % } %= $wagon->{type} % if ($wagon->{number}) { - – Wagen <%= $wagon->{number} %> + – <%= L('wagons.carriage') %> <%= $wagon->{number} %> % }
% } 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') { Passwort geändert % } + % elsif ($success eq 'language') { + Sprache geändert + % } % elsif ($success eq 'privacy') { Einstellungen zu öffentlichen Account-Daten geändert % } @@ -46,34 +49,38 @@ % my $use_history = users->use_history(uid => $acc->{id});
-

Account

+

<%= L('account.account') %>

- + - + - + - + + + + + - + - + @@ -163,7 +170,7 @@ %= form_for 'logout' => begin %= csrf_field %= end 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 @@
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 @@ +

Sprache

+%= form_for '/account/language' => (method => 'POST') => begin + %= csrf_field +
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+ +
+
+
+
+%= 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 @@ 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 @@
- Mit der Anmeldung stimmst du den Nutzungsbedingungen zu. + %= L('login.accept-tos-pre') + <%= L('login.tos') %> + %= L('login.accept-tos-post')
@@ -83,7 +85,7 @@
@@ -95,7 +97,7 @@
@@ -104,7 +106,7 @@ % if (app->config->{registration}{disabled}) {
- Diese Instanz erlaubt derzeit keine Registrierung neuer Accounts + <%= L('login.registration-disabled') %>
% } 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 @@
account_circle %= text_field 'user', id => 'account', class => 'validate', required => undef, pattern => '[0-9a-zA-Z_-]+', maxlength => 60, autocomplete => 'username' - +
email %= email_field 'email', id => 'email', class => 'validate', required => undef, maxlength => 250 - +
lock %= password_field 'password', id => 'password', class => 'validate', required => undef, minlength => 8, maxlength => 10000, autocomplete => 'new-password' - +
lock %= password_field 'password2', id => 'password2', class => 'validate', required => undef, minlength => 8, maxlength => 10000, autocomplete => 'new-password' - +
- Mit deiner Registrierung stimmst du den Nutzungsbedingungen zu. + %= L('register.accept-tos-pre') + <%= L('register.tos') %> + %= L('register.accept-tos-post')
@@ -36,7 +38,7 @@
@@ -47,9 +49,7 @@

- 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')

Die Mail-Adresse wird ausschließlich zur Bestätigung der Anmeldung, -- cgit v1.2.3

Name<%= L('account.name') %> edit<%= $acc->{name} %>
Mail<%= L('account.mail') %> edit<%= $acc->{email} %>
Passwort<%= L('account.password') %> edit
Verbindungen<%= L('account.language') %>edit<%= $acc->{languages}[0] // q{} %>
<%= L('account.connections') %> edit % if ($use_history & 0x03) { - Vorschläge aktiv + %= L('account.connections.enabled') % } % else { - Vorschläge deaktiviert + <%= L('account.connections.disabled') %> % }
Sichtbarkeit<%= L('account.visibility') %> edit check<%= visibility_icon($acc->{default_visibility_str}) %> @@ -81,23 +88,23 @@
Interaktion<%= L('account.interaction') %> edit % if ($acc->{accept_follows}) { - Accounts können dir direkt folgen + <%= L('account.interaction.accept-follows') %> % } % elsif ($acc->{accept_follow_requests}) { - Accounts können dir auf Anfrage folgen + <%= L('account.interaction.accept-follow-requests') %> % if ($num_rx_follow_requests == 1) { - – eine offene Anfrage + – <%= L('account.interaction.one') %> <%= L('account.interaction.open-request') %> % } elsif ($num_rx_follow_requests) { - – <%= $num_rx_follow_requests %> offene Anfragen + – <%= $num_rx_follow_requests %> <%= L('account.interaction.open-requests') %> % } % } % else { - Accounts können dir nicht folgen + <%= L('account.interaction.disabled') %> % }