diff options
| -rw-r--r-- | cpanfile | 2 | ||||
| -rwxr-xr-x | lib/Travelynx.pm | 45 | ||||
| -rw-r--r-- | lib/Travelynx/Command/database.pm | 22 | ||||
| -rw-r--r-- | lib/Travelynx/Controller/Account.pm | 29 | ||||
| -rw-r--r-- | lib/Travelynx/Helper/Locales.pm | 22 | ||||
| -rwxr-xr-x | lib/Travelynx/Model/Journeys.pm | 2 | ||||
| -rw-r--r-- | lib/Travelynx/Model/Users.pm | 19 | ||||
| -rw-r--r-- | share/locales/de_DE.po | 198 | ||||
| -rw-r--r-- | share/locales/en_GB.po | 180 | ||||
| -rw-r--r-- | templates/_checked_in.html.ep | 8 | ||||
| -rw-r--r-- | templates/_public_status_card.html.ep | 14 | ||||
| -rw-r--r-- | templates/_wagons.html.ep | 8 | ||||
| -rw-r--r-- | templates/account.html.ep | 37 | ||||
| -rw-r--r-- | templates/landingpage.html.ep | 4 | ||||
| -rw-r--r-- | templates/language.html.ep | 36 | ||||
| -rw-r--r-- | templates/layouts/default.html.ep | 14 | ||||
| -rw-r--r-- | templates/login.html.ep | 10 | ||||
| -rw-r--r-- | templates/register.html.ep | 18 | 
18 files changed, 611 insertions, 57 deletions
| @@ -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, | 
