diff options
Diffstat (limited to 'lib/VNDB')
-rw-r--r-- | lib/VNDB/DB/ULists.pm | 8 | ||||
-rw-r--r-- | lib/VNDB/DB/Users.pm | 4 | ||||
-rw-r--r-- | lib/VNDB/Func.pm | 75 | ||||
-rw-r--r-- | lib/VNDB/Handler/Discussions.pm | 102 | ||||
-rw-r--r-- | lib/VNDB/Handler/Misc.pm | 261 | ||||
-rw-r--r-- | lib/VNDB/Handler/Producers.pm | 69 | ||||
-rw-r--r-- | lib/VNDB/Handler/Releases.pm | 300 | ||||
-rw-r--r-- | lib/VNDB/Handler/Tags.pm | 281 | ||||
-rw-r--r-- | lib/VNDB/Handler/ULists.pm | 69 | ||||
-rw-r--r-- | lib/VNDB/Handler/Users.pm | 225 | ||||
-rw-r--r-- | lib/VNDB/Handler/VNBrowse.pm | 67 | ||||
-rw-r--r-- | lib/VNDB/Handler/VNEdit.pm | 92 | ||||
-rw-r--r-- | lib/VNDB/Handler/VNPage.pm | 164 | ||||
-rw-r--r-- | lib/VNDB/L10N.pm | 189 | ||||
-rw-r--r-- | lib/VNDB/Util/Auth.pm | 2 | ||||
-rw-r--r-- | lib/VNDB/Util/CommonHTML.pm | 164 | ||||
-rw-r--r-- | lib/VNDB/Util/FormHTML.pm | 132 | ||||
-rw-r--r-- | lib/VNDB/Util/LayoutHTML.pm | 84 |
18 files changed, 1162 insertions, 1126 deletions
diff --git a/lib/VNDB/DB/ULists.pm b/lib/VNDB/DB/ULists.pm index 34bdc2aa..e9b38e57 100644 --- a/lib/VNDB/DB/ULists.pm +++ b/lib/VNDB/DB/ULists.pm @@ -155,6 +155,7 @@ sub dbVoteGet { $o{uid} ? ( 'n.uid = ?' => $o{uid} ) : (), $o{vid} ? ( 'n.vid = ?' => $o{vid} ) : (), $o{hide} ? ( 'u.show_list = TRUE' => 1 ) : (), + $o{hide_ign} ? ( '(NOT u.ign_votes OR u.id = ?)' => $self->authInfo->{id}||0 ) : (), ); my @select = ( @@ -186,16 +187,19 @@ sub dbVoteGet { } -# Arguments: (uid|vid), id +# Arguments: (uid|vid), id, use_ignore_list # Returns an arrayref with 10 elements containing the number of votes for index+1 sub dbVoteStats { - my($self, $col, $id) = @_; + my($self, $col, $id, $ign) = @_; + my $u = $self->authInfo->{id}; my $r = [ qw| 0 0 0 0 0 0 0 0 0 0 | ]; $r->[$_->{vote}-1] = $_->{votes} for (@{$self->dbAll(q| SELECT vote, COUNT(vote) as votes FROM votes + !s !W GROUP BY vote|, + $ign ? 'JOIN users ON id = uid AND (NOT ign_votes'.($u?sprintf(' OR id = %d',$u):'').')' : '', $col ? { '!s = ?' => [ $col, $id ] } : {}, )}); return $r; diff --git a/lib/VNDB/DB/Users.pm b/lib/VNDB/DB/Users.pm index e1f4c378..b2cd1a31 100644 --- a/lib/VNDB/DB/Users.pm +++ b/lib/VNDB/DB/Users.pm @@ -43,7 +43,7 @@ sub dbUserGet { ); my @select = ( - qw|id username mail rank salt c_votes c_changes show_nsfw show_list skin customcss ip c_tags|, + qw|id username mail rank salt c_votes c_changes show_nsfw show_list skin customcss ip c_tags ign_votes|, q|encode(passwd, 'hex') AS passwd|, q|extract('epoch' from registered) as registered|, $o{what} =~ /stats/ ? ( '(SELECT COUNT(*) FROM rlists WHERE uid = u.id) AS releasecount', @@ -74,7 +74,7 @@ sub dbUserEdit { my %h; defined $o{$_} && ($h{$_.' = ?'} = $o{$_}) - for (qw| username mail rank show_nsfw show_list skin customcss salt |); + for (qw| username mail rank show_nsfw show_list skin customcss salt ign_votes |); $h{'passwd = decode(?, \'hex\')'} = $o{passwd} if defined $o{passwd}; diff --git a/lib/VNDB/Func.pm b/lib/VNDB/Func.pm index 4ca20dc5..cd4c4b62 100644 --- a/lib/VNDB/Func.pm +++ b/lib/VNDB/Func.pm @@ -6,7 +6,7 @@ use warnings; use YAWF ':html'; use Exporter 'import'; use POSIX 'strftime', 'ceil', 'floor'; -our @EXPORT = qw| shorten age date datestr monthstr userstr bb2html gtintype liststat clearfloat cssicon tagscore|; +our @EXPORT = qw| shorten bb2html gtintype liststat clearfloat cssicon tagscore mt |; # I would've done this as a #define if this was C... @@ -16,72 +16,6 @@ sub shorten { } -# Argument: unix timestamp -# Returns: age -sub age { - my $a = time-$_[0]; - return sprintf '%d %s ago', - $a > 60*60*24*365*2 ? ( $a/60/60/24/365, 'years' ) : - $a > 60*60*24*(365/12)*2 ? ( $a/60/60/24/(365/12), 'months' ) : - $a > 60*60*24*7*2 ? ( $a/60/60/24/7, 'weeks' ) : - $a > 60*60*24*2 ? ( $a/60/60/24, 'days' ) : - $a > 60*60*2 ? ( $a/60/60, 'hours' ) : - $a > 60*2 ? ( $a/60, 'min' ) : - ( $a, 'sec' ); -} - - -# argument: unix timestamp and optional format (compact/full) -# return value: yyyy-mm-dd -# (maybe an idea to use cgit-style ages for recent timestamps) -sub date { - my($t, $f) = @_; - return strftime '%Y-%m-%d', gmtime $t if !$f || $f eq 'compact'; - return strftime '%Y-%m-%d at %R', gmtime $t; -} - - -# argument: database release date format (yyyymmdd) -# y = 0000 -> unknown -# y = 9999 -> TBA -# m = 99 -> month+day unknown -# d = 99 -> day unknown -# return value: (unknown|TBA|yyyy|yyyy-mm|yyyy-mm-dd) -# if date > now: <b class="future">str</b> -sub datestr { - my $date = sprintf '%08d', shift||0; - my $future = $date > strftime '%Y%m%d', gmtime; - my($y, $m, $d) = ($1, $2, $3) if $date =~ /^([0-9]{4})([0-9]{2})([0-9]{2})$/; - - my $str = $y == 0 ? 'unknown' : $y == 9999 ? 'TBA' : - $m == 99 ? sprintf('%04d', $y) : - $d == 99 ? sprintf('%04d-%02d', $y, $m) : - sprintf('%04d-%02d-%02d', $y, $m, $d); - - return $str if !$future; - return qq|<b class="future">$str</b>|; -} - -# same as datestr(), but different output format: -# e.g.: 'Jan 2009', '2009', 'unknown', 'TBA' -sub monthstr { - my $date = sprintf '%08d', shift||0; - my($y, $m, $d) = ($1, $2, $3) if $date =~ /^([0-9]{4})([0-9]{2})([0-9]{2})/; - return 'TBA' if $y == 9999; - return 'unknown' if $y == 0; - return $y if $m == 99; - my $r = sprintf '%s %d', [qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)]->[$m-1], $y; - return $d == 99 ? "<i>$r</i>" : $r; -} - - -# Arguments: (uid, username), or a hashref containing that info -sub userstr { - my($id,$n) = ref($_[0])eq'HASH'?($_[0]{uid}||$_[0]{requester}, $_[0]{username}):@_; - return !$id ? '[deleted]' : '<a href="/u'.$id.'">'.$n.'</a>'; -} - - # Arguments: input, and optionally the maximum length # Parses: # [url=..] [/url] @@ -267,5 +201,12 @@ sub tagscore { } +# short wrapper around maketext() +# (not thread-safe, in the same sense as YAWF::XML. But who cares about threads, anyway?) +sub mt { + return $YAWF::OBJ->{l10n}->maketext(@_); +} + + 1; diff --git a/lib/VNDB/Handler/Discussions.pm b/lib/VNDB/Handler/Discussions.pm index 811bd74a..1d74ac6a 100644 --- a/lib/VNDB/Handler/Discussions.pm +++ b/lib/VNDB/Handler/Discussions.pm @@ -33,11 +33,11 @@ sub thread { div class => 'mainbox'; h1 $t->{title}; - h2 'Posted in'; + h2 mt '_thread_postedin'; ul; for (sort { $a->{type}.$a->{iid} cmp $b->{type}.$b->{iid} } @{$t->{boards}}) { li; - a href => "/t/$_->{type}", $self->{discussion_boards}{$_->{type}}; + a href => "/t/$_->{type}", mt "_dboard_$_->{type}"; if($_->{iid}) { txt ' > '; a style => 'font-weight: bold', href => "/t/$_->{type}$_->{iid}", "$_->{type}$_->{iid}"; @@ -60,25 +60,24 @@ sub thread { td class => 'tc1'; a href => "/t$tid.$_->{num}", name => $_->{num}, "#$_->{num}"; if(!$_->{hidden}) { - txt ' by '; - lit userstr $_; + lit ' '.mt "_thread_byuser", $_; br; - lit date $_->{date}, 'full'; + lit $self->{l10n}->date($_->{date}, 'full'); } end; td class => 'tc2'; if($self->authCan('boardmod') || $self->authInfo->{id} && $_->{uid} == $self->authInfo->{id} && !$_->{hidden}) { i class => 'edit'; txt '< '; - a href => "/t$tid.$_->{num}/edit", 'edit'; + a href => "/t$tid.$_->{num}/edit", mt '_thread_editpost'; txt ' >'; end; } if($_->{hidden}) { - i class => 'deleted', 'Post deleted.'; + i class => 'deleted', mt '_thread_deletedpost'; } else { lit bb2html $_->{msg}; - i class => 'lastmod', 'Last modified on '.date($_->{edited}, 'full') if $_->{edited}; + i class => 'lastmod', mt '_thread_lastmodified', $_->{edited} if $_->{edited}; } end; end; @@ -89,24 +88,28 @@ sub thread { if($t->{locked}) { div class => 'mainbox'; - h1 'Reply'; - p class => 'center', 'This thread has been locked, you can\'t reply to it anymore.'; + h1 mt '_thread_noreply_title'; + p class => 'center', mt '_thread_noreply_locked'; end; } elsif($t->{count} <= $page*25 && $self->authCan('board')) { form action => "/t$tid/reply", method => 'post', 'accept-charset' => 'UTF-8'; div class => 'mainbox'; fieldset class => 'submit'; - h2 'Quick reply'; + h2; + txt mt '_thread_quickreply_title'; + b class => 'standout', ' ('.mt('_inenglish').')'; + end; textarea name => 'msg', id => 'msg', rows => 4, cols => 50, ''; br; - input type => 'submit', value => 'Reply', class => 'submit'; + input type => 'submit', value => mt('_thread_quickreply_submit'), class => 'submit'; + input type => 'submit', value => mt('_thread_quickreply_full'), class => 'submit', name => 'fullreply'; end; end; end; } elsif(!$self->authCan('board')) { div class => 'mainbox'; - h1 'Reply'; - p class => 'center', 'You must be logged in to reply to this thread.'; + h1 mt '_thread_noreply_title'; + p class => 'center', mt '_thread_noreply_login'; end; } @@ -164,10 +167,13 @@ sub edit { { name => 'nolastmod', required => 0 }, ) : (), { name => 'msg', maxlenght => 5000 }, + { name => 'fullreply', required => 0 }, ); + $frm->{_err} = 1 if $frm->{fullreply}; + # check for double-posting - push @{$frm->{_err}}, 'doublepost' if !$num && $self->dbPostGet( + push @{$frm->{_err}}, 'doublepost' if !$num && !$frm->{_err} && $self->dbPostGet( uid => $self->authInfo->{id}, tid => $tid, mindate => time - 30, results => 1, $tid ? () : (num => 1))->[0]{num}; # parse and validate the boards @@ -177,7 +183,7 @@ sub edit { my($ty, $id) = ($1, $2) if /^([a-z]{1,2})([0-9]*)$/; push @boards, [ $ty, $id ]; push @{$frm->{_err}}, [ 'boards', 'wrongboard', $_ ] if - !$ty || !$self->{discussion_boards}{$ty} + !$ty || !grep($_ eq $ty, @{$self->{discussion_boards}}) || $ty eq 'an' && ($id || !$self->authCan('boardmod')) || $ty eq 'db' && $id || $ty eq 'v' && (!$id || !$self->dbVNGet(id => $id)->[0]{id}) @@ -225,35 +231,36 @@ sub edit { $frm->{hidden} = $t->{hidden} if !exists $frm->{hidden}; } } + delete $frm->{_err} unless ref $frm->{_err}; $frm->{boards} ||= $board; $frm->{nolastmod} = 1 if $num && $self->authCan('boardmod') && !exists $frm->{nolastmod}; # generate html - my $title = !$tid ? 'Start new thread' : - !$num ? 'Reply to '.$t->{title} : - 'Edit post'; + my $title = mt !$tid ? '_postedit_newthread' : + !$num ? ('_postedit_replyto', $t->{title}) : + '_postedit_edit'; my $url = !$tid ? "/t/$board/new" : !$num ? "/t$tid/reply" : "/t$tid.$num/edit"; $self->htmlHeader(title => $title, noindex => 1); - $self->htmlForm({ frm => $frm, action => $url }, $title => [ - [ static => label => 'Username', content => userstr($self->authInfo->{id}, $self->authInfo->{username}) ], + $self->htmlForm({ frm => $frm, action => $url }, 'postedit' => [$title, + [ static => label => mt('_postedit_form_username'), content => $self->{l10n}->userstr($self->authInfo->{id}, $self->authInfo->{username}) ], !$tid || $num == 1 ? ( - [ input => short => 'title', name => 'Thread title' ], - [ input => short => 'boards', name => 'Board(s)' ], - [ static => content => 'Read <a href="/d9.2">d9.2</a> for information about how to specify boards' ], + [ input => short => 'title', name => mt('_postedit_form_title') ], + [ input => short => 'boards', name => mt('_postedit_form_boards') ], + [ static => content => mt('_postedit_form_boards_info') ], $self->authCan('boardmod') ? ( - [ check => name => 'Locked', short => 'locked' ], + [ check => name => mt('_postedit_form_locked'), short => 'locked' ], ) : (), ) : ( - [ static => label => 'Topic', content => qq|<a href="/t$tid">|.xml_escape($t->{title}).'</a>' ], + [ static => label => mt('_postedit_form_topic'), content => qq|<a href="/t$tid">|.xml_escape($t->{title}).'</a>' ], ), $self->authCan('boardmod') ? ( - [ check => name => 'Hidden', short => 'hidden' ], + [ check => name => mt('_postedit_form_hidden'), short => 'hidden' ], $num ? ( - [ check => name => 'Don\'t update last modified field', short => 'nolastmod' ], + [ check => name => mt('_postedit_form_nolastmod'), short => 'nolastmod' ], ) : (), ) : (), - [ text => name => 'Message', short => 'msg', rows => 10 ], - [ static => content => 'See <a href="/d9.3">d9.3</a> for the allowed formatting codes' ], + [ text => name => mt('_postedit_form_msg').'<br /><b class="standout">'.mt('_inenglish').'</b>', short => 'msg', rows => 25, cols => 75 ], + [ static => content => mt('_postedit_form_msg_format') ], ]); $self->htmlFooter; } @@ -275,7 +282,7 @@ sub board { $self->dbVNGet(id => $iid)->[0]; return 404 if $iid && !$obj; my $ititle = $obj && ($obj->{title}||$obj->{name}||$obj->{username}); - my $title = !$obj ? $self->{discussion_boards}{$type} : 'Related discussions for '.$ititle; + my $title = !$obj ? mt("_dboard_$type") : mt '_disboard_item_title', $ititle; my($list, $np) = $self->dbThreadGet( type => $type, @@ -292,9 +299,9 @@ sub board { div class => 'mainbox'; h1 $title; p; - a href => '/t', 'Discussion board'; + a href => '/t', mt '_disboard_rootlink'; txt ' > '; - a href => "/t/$type", $self->{discussion_boards}{$type}; + a href => "/t/$type", mt "_dboard_$type"; if($iid) { txt ' > '; a style => 'font-weight: bold', href => "/t/$type$iid", "$type$iid"; @@ -304,11 +311,11 @@ sub board { end; p class => 'center'; if(!@$list) { - b 'No related threads found'; + b mt '_disboard_nothreads'; br; br; - a href => "/t/$type$iid/new", 'Why not create one yourself?'; + a href => "/t/$type$iid/new", mt '_disboard_createyourown'; } else { - a href => '/t/'.($iid ? $type.$iid : 'db').'/new', 'Start a new thread'; + a href => '/t/'.($iid ? $type.$iid : 'db').'/new', mt '_disboard_startnew'; } end; end; @@ -322,11 +329,11 @@ sub board { sub index { my $self = shift; - $self->htmlHeader(title => 'Discussion board index'); + $self->htmlHeader(title => mt '_disindex_title'); div class => 'mainbox'; - h1 'Discussion board index'; + h1 mt '_disindex_title'; p class => 'browseopts'; - a href => '/t/'.$_, $self->{discussion_boards}{$_} + a href => '/t/'.$_, mt "_dboard_$_" for (qw|an db v p u|); end; end; @@ -340,7 +347,7 @@ sub index { order => 'tpl.date DESC', ); h1 class => 'boxtitle'; - a href => "/t/$_", $self->{discussion_boards}{$_}; + a href => "/t/$_", mt "_dboard_$_"; end; _threadlist($self, $list, {p=>1}, 0, "/t"); } @@ -358,7 +365,10 @@ sub _threadlist { pageurl => $url, class => 'discussions', header => [ - [ 'Topic' ], [ 'Replies' ], [ 'Starter' ], [ 'Last post' ] + [ mt '_threadlist_col_topic' ], + [ mt '_threadlist_col_replies' ], + [ mt '_threadlist_col_starter' ], + [ mt '_threadlist_col_lastpost' ], ], row => sub { my($self, $n, $o) = @_; @@ -368,13 +378,13 @@ sub _threadlist { end; td class => 'tc2', $o->{count}-1; td class => 'tc3'; - lit userstr $o->{fuid}, $o->{fusername}; + lit $self->{l10n}->userstr($o->{fuid}, $o->{fusername}); end; td class => 'tc4'; - lit userstr $o->{luid}, $o->{lusername}; + lit $self->{l10n}->userstr($o->{luid}, $o->{lusername}); lit ' @ '; a href => "/t$o->{id}.$o->{count}"; - lit date $o->{ldate}; + lit $self->{l10n}->date($o->{ldate}); end; end; end; @@ -386,8 +396,8 @@ sub _threadlist { last if $i++ > 5; txt ', ' if $i > 2; a href => "/t/$_->{type}".($_->{iid}||''), - title => $_->{original}||$self->{discussion_boards}{$_->{type}}, - shorten $_->{title}||$self->{discussion_boards}{$_->{type}}, 30; + title => $_->{original}||mt("_dboard_$_->{type}"), + shorten $_->{title}||mt("_dboard_$_->{type}"), 30; } txt ', ...' if @{$o->{boards}} > 5; end; diff --git a/lib/VNDB/Handler/Misc.pm b/lib/VNDB/Handler/Misc.pm index ceae11ce..891a83f0 100644 --- a/lib/VNDB/Handler/Misc.pm +++ b/lib/VNDB/Handler/Misc.pm @@ -4,8 +4,9 @@ package VNDB::Handler::Misc; use strict; use warnings; -use YAWF ':html', ':xml'; +use YAWF ':html', ':xml', 'xml_escape'; use VNDB::Func; +use POSIX 'strftime'; YAWF::register( @@ -36,23 +37,12 @@ YAWF::register( sub homepage { my $self = shift; - $self->htmlHeader(title => $self->{site_title}); + $self->htmlHeader(title => mt '_site_title'); div class => 'mainbox'; - h1 $self->{site_title}; + h1 mt '_site_title'; p class => 'description'; - lit qq| - VNDB.org strives to be a comprehensive database for information about visual novels and - eroge.<br /> - This website is built as a wiki, meaning that anyone can freely add and contribute information - to the database, allowing us to create the largest, most accurate and most up-to-date visual novel - database on the web.<br /> - Registered users are also able to keep track of a personal list of games they want to play or have finished - and they can vote on all visual novels.<br /> - <br /> - Feel free to <a href="/v/all">browse around</a>, <a href="/u/register">register an account</a> - or to participate in the discussions about visual novels or VNDB on our <a href="/t">discussion board</a>. - |; + lit mt '_home_intro'; end; my $scr = $self->dbScreenshotRandom; @@ -65,104 +55,121 @@ sub homepage { end; end; - # Recent changes - div class => 'mainbox threelayout'; - h1 'Recent changes'; - my $changes = $self->dbRevisionGet(what => 'item user', results => 10, auto => 1, hidden => 1); - ul; - for (@$changes) { - my $t = (qw|v r p|)[$_->{type}]; - li; - b "$t:"; - a href => "/$t$_->{iid}.$_->{rev}", title => $_->{ioriginal}||$_->{ititle}, shorten $_->{ititle}, 30; - txt ' by '; - a href => "/u$_->{requester}", $_->{username}; - end; - } - end; - end; + table class => 'mainbox threelayout'; + Tr; - # Announcements - div class => 'mainbox threelayout'; - my $an = $self->dbThreadGet(type => 'an', order => 't.id DESC', results => 2); - a class => 'right', href => '/t/an', 'News archive'; - h1 'Announcements'; - for (@$an) { - my $post = $self->dbPostGet(tid => $_->{id}, num => 1)->[0]; - h2; - a href => "/t$_->{id}", $_->{title}; + # Recent changes + td; + h1; + a href => '/hist', mt '_home_recentchanges'; end; - p; - lit bb2html $post->{msg}, 150; + my $changes = $self->dbRevisionGet(what => 'item user', results => 10, auto => 1, hidden => 1); + ul; + for (@$changes) { + my $t = (qw|v r p|)[$_->{type}]; + li; + lit mt '_home_recentchanges_item', $t, + sprintf('<a href="%s" title="%s">%s</a>', "/$t$_->{iid}.$_->{rev}", + xml_escape($_->{ioriginal}||$_->{ititle}), xml_escape shorten $_->{ititle}, 33), + $_; + end; + } end; - } - end; + end; - # Recent posts - div class => 'mainbox threelayout last'; - h1 'Recent posts'; - my $posts = $self->dbThreadGet(what => 'lastpost boardtitles', results => 10, order => 'tpl.date DESC', notusers => 1); - ul; - for (@$posts) { - my $boards = join ', ', map $self->{discussion_boards}{$_->{type}}.($_->{iid}?' > '.$_->{title}:''), @{$_->{boards}}; - li; - txt age($_->{ldate}).' '; - a href => "/t$_->{id}.$_->{count}", title => "Posted in $boards", shorten $_->{title}, 20; - txt ' by '; - a href => "/u$_->{luid}", $_->{lusername}; - end; - } - end; - end; + # Announcements + td; + my $an = $self->dbThreadGet(type => 'an', order => 't.id DESC', results => 2); + h1; + a href => '/t/an', mt '_home_announcements'; + end; + for (@$an) { + my $post = $self->dbPostGet(tid => $_->{id}, num => 1)->[0]; + h2; + a href => "/t$_->{id}", $_->{title}; + end; + p; + lit bb2html $post->{msg}, 150; + end; + } + end; - # Random visual novels - div class => 'mainbox threelayout'; - h1 'Random visual novels'; - my $random = $self->dbVNGet(results => 10, order => 'RANDOM()'); - ul; - for (@$random) { - li; - a href => "/v$_->{id}", title => $_->{original}||$_->{title}, shorten $_->{title}, 40; - end; - } - end; - end; + # Recent posts + td; + h1; + a href => '/t', mt '_home_recentposts'; + end; + my $posts = $self->dbThreadGet(what => 'lastpost boardtitles', results => 10, order => 'tpl.date DESC', notusers => 1); + ul; + for (@$posts) { + my $boards = join ', ', map mt("_dboard_$_->{type}").($_->{iid}?' > '.$_->{title}:''), @{$_->{boards}}; + li; + lit mt '_home_recentposts_item', $_->{ldate}, + sprintf('<a href="%s" title="%s">%s</a>', "/t$_->{id}.$_->{count}", + xml_escape("Posted in $boards"), xml_escape shorten $_->{title}, 25), + {uid => $_->{luid}, username => $_->{lusername}}; + end; + } + end; + end; - # Upcoming releases - div class => 'mainbox threelayout'; - h1 'Upcoming releases'; - my $upcoming = $self->dbReleaseGet(results => 10, unreleased => 1, what => 'platforms'); - ul; - for (@$upcoming) { - li; - lit datestr $_->{released}; - txt ' '; - cssicon $_, $self->{platforms}{$_} for (@{$_->{platforms}}); - txt ' '; - a href => "/r$_->{id}", title => $_->{original}||$_->{title}, shorten $_->{title}, 30; - end; - } end; - end; + Tr; + + # Random visual novels + td; + h1 mt '_home_randomvn'; + my $random = $self->dbVNGet(results => 10, order => 'RANDOM()'); + ul; + for (@$random) { + li; + a href => "/v$_->{id}", title => $_->{original}||$_->{title}, shorten $_->{title}, 40; + end; + } + end; + end; - # Just released - div class => 'mainbox threelayout last'; - h1 'Just released'; - my $justrel = $self->dbReleaseGet(results => 10, order => 'rr.released DESC', unreleased => 0, what => 'platforms'); - ul; - for (@$justrel) { - li; - lit datestr $_->{released}; - txt ' '; - cssicon $_, $self->{platforms}{$_} for (@{$_->{platforms}}); - txt ' '; - a href => "/r$_->{id}", title => $_->{original}||$_->{title}, shorten $_->{title}, 30; - end; - } - end; - end; + # Upcoming releases + td; + h1; + a href => strftime('/r?mi=%Y%m%d;o=a;s=released', gmtime), mt '_home_upcoming'; + end; + my $upcoming = $self->dbReleaseGet(results => 10, unreleased => 1, what => 'platforms'); + ul; + for (@$upcoming) { + li; + lit $self->{l10n}->datestr($_->{released}); + txt ' '; + cssicon $_, mt "_plat_$_" for (@{$_->{platforms}}); + txt ' '; + a href => "/r$_->{id}", title => $_->{original}||$_->{title}, shorten $_->{title}, 30; + end; + } + end; + end; + + # Just released + td; + h1; + a href => strftime('/r?ma=%Y%m%d;o=d;s=released', gmtime), mt '_home_justreleased'; + end; + my $justrel = $self->dbReleaseGet(results => 10, order => 'rr.released DESC', unreleased => 0, what => 'platforms'); + ul; + for (@$justrel) { + li; + lit $self->{l10n}->datestr($_->{released}); + txt ' '; + cssicon $_, mt "_plat_$_" for (@{$_->{platforms}}); + txt ' '; + a href => "/r$_->{id}", title => $_->{original}||$_->{title}, shorten $_->{title}, 30; + end; + } + end; + end; + + end; # /tr + end; # /table - clearfloat; $self->htmlFooter; } @@ -187,7 +194,7 @@ sub history { $type eq 'p' ? $self->dbProducerGet(id => $id)->[0] : $type eq 'r' ? $self->dbReleaseGet(id => $id)->[0] : $type eq 'v' ? $self->dbVNGet(id => $id)->[0] : undef; - my $title = $type ? 'Edit history of '.($obj->{title} || $obj->{name} || $obj->{username}) : 'Recent changes'; + my $title = mt $type ? ('_hist_title_item', $obj->{title} || $obj->{name} || $obj->{username}) : '_hist_title'; return 404 if $type && !$obj->{id}; # get the edit history @@ -224,33 +231,33 @@ sub history { h1 $title; if($type ne 'u') { p class => 'browseopts'; - a !$f->{m} ? (class => 'optselected') : (), href => $u->(m => 0), 'Show automated edits'; - a $f->{m} ? (class => 'optselected') : (), href => $u->(m => 1), 'Hide automated edits'; + a !$f->{m} ? (class => 'optselected') : (), href => $u->(m => 0), mt '_hist_filter_showauto'; + a $f->{m} ? (class => 'optselected') : (), href => $u->(m => 1), mt '_hist_filter_hideauto'; end; } if(!$type || $type eq 'u') { if($self->authCan('del')) { p class => 'browseopts'; - a $f->{h} == 1 ? (class => 'optselected') : (), href => $u->(h => 1), 'Hide deleted items'; - a $f->{h} == -1 ? (class => 'optselected') : (), href => $u->(h => -1), 'Show deleted items'; + a $f->{h} == 1 ? (class => 'optselected') : (), href => $u->(h => 1), mt '_hist_filter_hidedel'; + a $f->{h} == -1 ? (class => 'optselected') : (), href => $u->(h => -1), mt '_hist_filter_showdel'; end; } p class => 'browseopts'; - a !$f->{t} ? (class => 'optselected') : (), href => $u->(t => ''), 'Show all items'; - a $f->{t} eq 'v' ? (class => 'optselected') : (), href => $u->(t => 'v'), 'Only visual novels'; - a $f->{t} eq 'r' ? (class => 'optselected') : (), href => $u->(t => 'r'), 'Only releases'; - a $f->{t} eq 'p' ? (class => 'optselected') : (), href => $u->(t => 'p'), 'Only producers'; + a !$f->{t} ? (class => 'optselected') : (), href => $u->(t => ''), mt '_hist_filter_alltypes'; + a $f->{t} eq 'v' ? (class => 'optselected') : (), href => $u->(t => 'v'), mt '_hist_filter_onlyvn'; + a $f->{t} eq 'r' ? (class => 'optselected') : (), href => $u->(t => 'r'), mt '_hist_filter_onlyreleases'; + a $f->{t} eq 'p' ? (class => 'optselected') : (), href => $u->(t => 'p'), mt '_hist_filter_onlyproducers'; end; p class => 'browseopts'; - a !$f->{e} ? (class => 'optselected') : (), href => $u->(e => 0), 'Show all changes'; - a $f->{e} == 1 ? (class => 'optselected') : (), href => $u->(e => 1), 'Only edits'; - a $f->{e} == -1 ? (class => 'optselected') : (), href => $u->(e => -1), 'Only newly created pages'; + a !$f->{e} ? (class => 'optselected') : (), href => $u->(e => 0), mt '_hist_filter_allactions'; + a $f->{e} == 1 ? (class => 'optselected') : (), href => $u->(e => 1), mt '_hist_filter_onlyedits'; + a $f->{e} == -1 ? (class => 'optselected') : (), href => $u->(e => -1), mt '_hist_filter_onlynew'; end; } if($type eq 'v') { p class => 'browseopts'; - a !$f->{r} ? (class => 'optselected') : (), href => $u->(r => 0), 'Exclude edits of releases'; - a $f->{r} ? (class => 'optselected') : (), href => $u->(r => 1), 'Include edits of releases'; + a !$f->{r} ? (class => 'optselected') : (), href => $u->(r => 0), mt '_hist_filter_exrel'; + a $f->{r} ? (class => 'optselected') : (), href => $u->(r => 1), mt '_hist_filter_increl'; end; } end; @@ -263,7 +270,10 @@ sub history { sub docpage { my($self, $did) = @_; - open my $F, '<', sprintf('%s/data/docs/%d', $VNDB::ROOT, $did) or return 404; + my $l = '.'.$self->{l10n}->language_tag(); + my $f = sprintf('%s/data/docs/%d', $VNDB::ROOT, $did); + my $F; + open($F, '<:utf8', $f.$l) or open($F, '<:utf8', $f) or return 404; my @c = <$F>; close $F; @@ -277,7 +287,8 @@ sub docpage { qq|<h3><a href="#$sec" name="$sec">$sec. $1</a></h3>\n| }eg; s{^:INC:(.+)\r?\n$}{ - open $F, '<', sprintf('%s/data/docs/%s', $VNDB::ROOT, $1) or die $!; + $f = sprintf('%s/data/docs/%s', $VNDB::ROOT, $1); + open($F, '<:utf8', $f.$l) or open($F, '<:utf8', $f) or die $!; my $ii = join('', <$F>); close $F; $ii; @@ -297,13 +308,13 @@ sub docpage { sub nospam { my $self = shift; - $self->htmlHeader(title => 'Could not send form', noindex => 1); + $self->htmlHeader(title => mt '_nospam_title', noindex => 1); div class => 'mainbox'; - h1 'Could not send form'; + h1 mt '_nospam_title'; div class => 'warning'; - h2 'Error'; - p 'The form could not be sent, please make sure you have Javascript enabled in your browser.'; + h2 mt '_nospam_subtitle'; + p mt '_nospam_msg'; end; end; @@ -354,7 +365,7 @@ sub ie6message { end; body; div; - h1 'Oops, we were too lazy support your browser!'; + h1 'Oops, we were too lazy to support your browser!'; p; lit qq|We decided to stop supporting Internet Explorer 6, as it's a royal pain in | .qq|the ass to make our site look good in a browser that doesn't want to cooperate with us.<br />| diff --git a/lib/VNDB/Handler/Producers.pm b/lib/VNDB/Handler/Producers.pm index aa010e6e..e6477567 100644 --- a/lib/VNDB/Handler/Producers.pm +++ b/lib/VNDB/Handler/Producers.pm @@ -33,13 +33,13 @@ sub page { if($rev) { my $prev = $rev && $rev > 1 && $self->dbProducerGet(id => $pid, rev => $rev-1, what => 'changes extended')->[0]; $self->htmlRevision('p', $prev, $p, - [ type => 'Type', serialize => sub { $self->{producer_types}{$_[0]} } ], - [ name => 'Name (romaji)', diff => 1 ], - [ original => 'Original name', diff => 1 ], - [ alias => 'Aliases', diff => 1 ], - [ lang => 'Language', serialize => sub { "$_[0] ($self->{languages}{$_[0]})" } ], - [ website => 'Website', diff => 1 ], - [ desc => 'Description', diff => 1 ], + [ type => serialize => sub { mt "_ptype_$_[0]" } ], + [ name => diff => 1 ], + [ original => diff => 1 ], + [ alias => diff => 1 ], + [ lang => serialize => sub { "$_[0] (".mt("_lang_$_[0]").')' } ], + [ website => diff => 1 ], + [ desc => diff => 1 ], ); } @@ -48,8 +48,8 @@ sub page { h1 $p->{name}; h2 class => 'alttitle', $p->{original} if $p->{original}; p class => 'center'; - txt "$self->{languages}{$p->{lang}} \L$self->{producer_types}{$p->{type}}"; - txt "\na.k.a. $p->{alias}" if $p->{alias}; + txt mt '_prodpage_langtype', mt("_lang_$p->{lang}"), mt "_ptype_$p->{type}"; + txt "\n".mt '_prodpage_aliases', $p->{alias} if $p->{alias}; if($p->{website}) { txt "\n"; a href => $p->{website}, $p->{website}; @@ -64,15 +64,15 @@ sub page { end; div class => 'mainbox producerpage'; - h1 'Visual Novel Relations'; + h1 mt '_prodpage_vnrel'; if(!@{$p->{vn}}) { - p 'We have currently no visual novels related to this producer.'; + p mt '_prodpage_norel'; } else { ul; for (@{$p->{vn}}) { li; i; - lit datestr $_->{date}; + lit $self->{l10n}->datestr($_->{date}); end; a href => "/v$_->{id}", title => $_->{original}, $_->{title}; end; @@ -101,11 +101,11 @@ sub edit { if($self->reqMethod eq 'POST') { $frm = $self->formValidate( - { name => 'type', enum => [ keys %{$self->{producer_types}} ] }, + { name => 'type', enum => $self->{producer_types} }, { name => 'name', maxlength => 200 }, { name => 'original', required => 0, maxlength => 200, default => '' }, { name => 'alias', required => 0, maxlength => 500, default => '' }, - { name => 'lang', enum => [ keys %{$self->{languages}} ] }, + { name => 'lang', enum => $self->{languages} }, { name => 'website', required => 0, template => 'url', default => '' }, { name => 'desc', required => 0, maxlength => 5000, default => '' }, { name => 'editsum', maxlength => 5000 }, @@ -129,21 +129,22 @@ sub edit { $frm->{lang} = 'ja' if !$pid && !defined $frm->{lang}; $frm->{editsum} = sprintf 'Reverted to revision p%d.%d', $pid, $rev if $rev && !defined $frm->{editsum}; - $self->htmlHeader(title => $pid ? 'Edit '.$p->{name} : 'Add new producer', noindex => 1); + my $title = mt $pid ? ('_pedit_title_edit', $p->{name}) : '_pedit_title_add'; + $self->htmlHeader(title => $title, noindex => 1); $self->htmlMainTabs('p', $p, 'edit') if $pid; - $self->htmlEditMessage('p', $p); - $self->htmlForm({ frm => $frm, action => $pid ? "/p$pid/edit" : '/p/new', editsum => 1 }, "General info" => [ - [ select => name => 'Type', short => 'type', - options => [ map [ $_, $self->{producer_types}{$_} ], sort keys %{$self->{producer_types}} ] ], - [ input => name => 'Name (romaji)', short => 'name' ], - [ input => name => 'Original name', short => 'original' ], - [ static => content => q|The original name of the producer, leave blank if it is already in the Latin alphabet.| ], - [ input => name => 'Aliases', short => 'alias', width => 400 ], - [ static => content => q|(Un)official aliases, separated by a comma.| ], - [ select => name => 'Primary language', short => 'lang', - options => [ map [ $_, "$_ ($self->{languages}{$_})" ], sort keys %{$self->{languages}} ] ], - [ input => name => 'Website', short => 'website' ], - [ text => name => 'Description', short => 'desc', rows => 6 ], + $self->htmlEditMessage('p', $p, $title); + $self->htmlForm({ frm => $frm, action => $pid ? "/p$pid/edit" : '/p/new', editsum => 1 }, 'pedit_geninfo' => [mt('_pedit_form_generalinfo'), + [ select => name => mt('_pedit_form_type'), short => 'type', + options => [ map [ $_, mt "_ptype_$_" ], sort @{$self->{producer_types}} ] ], + [ input => name => mt('_pedit_form_name'), short => 'name' ], + [ input => name => mt('_pedit_form_original'), short => 'original' ], + [ static => content => mt('_pedit_form_original_note') ], + [ input => name => mt('_pedit_form_alias'), short => 'alias', width => 400 ], + [ static => content => mt('_pedit_form_alias_note') ], + [ select => name => mt('_pedit_form_lang'), short => 'lang', + options => [ map [ $_, "$_ (".mt("_lang_$_").')' ], sort @{$self->{languages}} ] ], + [ input => name => mt('_pedit_form_website'), short => 'website' ], + [ text => name => mt('_pedit_form_desc').'<br /><b class="standout">'.mt('_inenglish').'</b>', short => 'desc', rows => 6 ], ]); $self->htmlFooter; } @@ -165,16 +166,16 @@ sub list { page => $f->{p} ); - $self->htmlHeader(title => 'Browse producers'); + $self->htmlHeader(title => mt '_pbrowse_title'); div class => 'mainbox'; - h1 'Browse producers'; + h1 mt '_pbrowse_title'; form action => '/p/all', 'accept-charset' => 'UTF-8', method => 'get'; $self->htmlSearchBox('p', $f->{q}); end; p class => 'browseopts'; for ('all', 'a'..'z', 0) { - a href => "/p/$_", $_ eq $char ? (class => 'optselected') : (), $_ ? uc $_ : '#'; + a href => "/p/$_", $_ eq $char ? (class => 'optselected') : (), $_ eq 'all' ? mt('_char_all') : $_ ? uc $_ : '#'; } end; end; @@ -182,9 +183,9 @@ sub list { my $pageurl = "/p/$char" . ($f->{q} ? "?q=$f->{q}" : ''); $self->htmlBrowseNavigate($pageurl, $f->{p}, $np, 't'); div class => 'mainbox producerbrowse'; - h1 $f->{q} ? 'Search results' : 'Producer list'; + h1 mt $f->{q} ? '_pbrowse_searchres' : '_pbrowse_list'; if(!@$list) { - p 'No results found'; + p mt '_pbrowse_noresults'; } else { # spread the results over 3 equivalent-sized lists my $perlist = @$list/3 < 1 ? 1 : @$list/3; @@ -192,7 +193,7 @@ sub list { ul; for ($perlist*$c..($perlist*($c+1))-1) { li; - cssicon 'lang '.$list->[$_]{lang}, $self->{languages}{$list->[$_]{lang}}; + cssicon 'lang '.$list->[$_]{lang}, mt "_lang_$list->[$_]{lang}"; a href => "/p$list->[$_]{id}", title => $list->[$_]{original}, $list->[$_]{name}; end; } diff --git a/lib/VNDB/Handler/Releases.pm b/lib/VNDB/Handler/Releases.pm index db234aea..f0daa4cb 100644 --- a/lib/VNDB/Handler/Releases.pm +++ b/lib/VNDB/Handler/Releases.pm @@ -36,34 +36,34 @@ sub page { what => 'vn extended producers platforms media changes' )->[0]; $self->htmlRevision('r', $prev, $r, - [ vn => 'Relations', join => '<br />', split => sub { + [ vn => join => '<br />', split => sub { map sprintf('<a href="/v%d" title="%s">%s</a>', $_->{vid}, $_->{original}||$_->{title}, shorten $_->{title}, 50), @{$_[0]}; } ], - [ type => 'Type', serialize => sub { $self->{release_types}[$_[0]] } ], - [ patch => 'Patch', serialize => sub { $_[0] ? 'Patch' : 'Not a patch' } ], - [ freeware => 'Freeware', serialize => sub { $_[0] ? 'yes' : 'nope' } ], - [ doujin => 'Doujin', serialize => sub { $_[0] ? 'yups' : 'nope' } ], - [ title => 'Title (romaji)', diff => 1 ], - [ original => 'Original title', diff => 1 ], - [ gtin => 'JAN/UPC/EAN', serialize => sub { $_[0]||'[none]' } ], - [ catalog => 'Catalog number', serialize => sub { $_[0]||'[none]' } ], - [ languages => 'Language', join => ', ', split => sub { map $self->{languages}{$_}, @{$_[0]} } ], - [ website => 'Website', ], - [ released => 'Release date', htmlize => sub { datestr $_[0] } ], - [ minage => 'Age rating', serialize => sub { $self->{age_ratings}{$_[0]}[0] } ], - [ notes => 'Notes', diff => 1 ], - [ platforms => 'Platforms', join => ', ', split => sub { map $self->{platforms}{$_}, @{$_[0]} } ], - [ media => 'Media', join => ', ', split => sub { + [ type => serialize => sub { mt "_rtype_$_[0]" } ], + [ patch => serialize => sub { $_[0] ? 'Patch' : 'Not a patch' } ], + [ freeware => serialize => sub { $_[0] ? 'yes' : 'nope' } ], + [ doujin => serialize => sub { $_[0] ? 'yups' : 'nope' } ], + [ title => diff => 1 ], + [ original => diff => 1 ], + [ gtin => serialize => sub { $_[0]||'[none]' } ], + [ catalog => serialize => sub { $_[0]||'[none]' } ], + [ languages => join => ', ', split => sub { map mt("_lang_$_"), @{$_[0]} } ], + [ 'website' ], + [ released => htmlize => sub { $self->{l10n}->datestr($_[0]) } ], + [ minage => serialize => sub { $self->{age_ratings}{$_[0]}[0] } ], + [ notes => diff => 1 ], + [ platforms => join => ', ', split => sub { map mt("_plat_$_"), @{$_[0]} } ], + [ media => join => ', ', split => sub { map { my $med = $self->{media}{$_->{medium}}; $med->[1] ? sprintf('%d %s%s', $_->{qty}, $med->[0], $_->{qty}>1?'s':'') : $med->[0] } @{$_[0]}; } ], - [ resolution => 'Resolution', serialize => sub { $self->{resolutions}[$_[0]][0] } ], - [ voiced => 'Voiced', serialize => sub { $self->{voiced}[$_[0]] } ], - [ ani_story => 'Story animation',serialize => sub { $self->{animated}[$_[0]] } ], - [ ani_ero => 'Ero animation', serialize => sub { $self->{animated}[$_[0]] } ], - [ producers => 'Producers', join => '<br />', split => sub { + [ resolution => serialize => sub { $self->{resolutions}[$_[0]][0] } ], + [ voiced => serialize => sub { mt '_voiced_'.$_[0] } ], + [ ani_story => serialize => sub { mt '_animated_'.$_[0] } ], + [ ani_ero => serialize => sub { mt '_animated_'.$_[0] } ], + [ producers => join => '<br />', split => sub { map sprintf('<a href="/p%d" title="%s">%s</a>', $_->{id}, $_->{original}||$_->{name}, shorten $_->{name}, 50), @{$_[0]}; } ], ); @@ -93,7 +93,7 @@ sub _infotable { my $i = 0; Tr ++$i % 2 ? (class => 'odd') : (); - td class => 'key', 'Relation'; + td class => 'key', mt '_relinfo_vnrel'; td; for (@{$r->{vn}}) { a href => "/v$_->{vid}", title => $_->{original}||$_->{title}, shorten $_->{title}, 60; @@ -103,50 +103,48 @@ sub _infotable { end; Tr ++$i % 2 ? (class => 'odd') : (); - td 'Title'; + td mt '_relinfo_title'; td $r->{title}; end; if($r->{original}) { Tr ++$i % 2 ? (class => 'odd') : (); - td 'Original title'; + td mt '_relinfo_original'; td $r->{original}; end; } Tr ++$i % 2 ? (class => 'odd') : (); - td 'Type'; + td mt '_relinfo_type'; td; - my $type = $self->{release_types}[$r->{type}]; - cssicon lc(substr $type, 0, 3), $type; - txt ' '.$type; - txt ' patch' if $r->{patch}; + cssicon "rt$r->{type}", mt "_rtype_$r->{type}"; + txt ' '.mt '_relinfo_type_format', mt("_rtype_$r->{type}"), $r->{patch}?1:0; end; end; Tr ++$i % 2 ? (class => 'odd') : (); - td 'Language'; + td mt '_relinfo_lang'; td; for (@{$r->{languages}}) { - cssicon "lang $_", $self->{languages}{$_}; - txt ' '.$self->{languages}{$_}; + cssicon "lang $_", mt "_lang_$_"; + txt ' '.mt("_lang_$_"); br if $_ ne $r->{languages}[$#{$r->{languages}}]; } end; end; Tr ++$i % 2 ? (class => 'odd') : (); - td 'Publication'; - td join ', ', $r->{freeware} ? 'Freeware' : 'Non-free', $r->{patch} ? () : $r->{doujin} ? 'doujin' : 'commercial'; + td mt '_relinfo_publication'; + td mt $r->{patch} ? '_relinfo_pub_patch' : '_relinfo_pub_nopatch', $r->{freeware}?0:1, $r->{doujin}?0:1; end; if(@{$r->{platforms}}) { Tr ++$i % 2 ? (class => 'odd') : (); - td 'Platform'.($#{$r->{platforms}} ? 's' : ''); + td mt '_relinfo_platform', scalar @{$r->{platforms}}; td; for(@{$r->{platforms}}) { - cssicon $_, $self->{platforms}{$_}; - txt ' '.$self->{platforms}{$_}; + cssicon $_, mt "_plat_$_"; + txt ' '.mt("_plat_$_"); br if $_ ne $r->{platforms}[$#{$r->{platforms}}]; } end; @@ -155,7 +153,8 @@ sub _infotable { if(@{$r->{media}}) { Tr ++$i % 2 ? (class => 'odd') : (); - td 'Medi'.($#{$r->{media}} ? 'a' : 'um'); + td mt '_relinfo_media', scalar @{$r->{media}}; + # TODO: TL the media td join ', ', map { my $med = $self->{media}{$_->{medium}}; $med->[1] ? sprintf('%d %s%s', $_->{qty}, $med->[0], $_->{qty}>1?'s':'') : $med->[0] @@ -165,44 +164,44 @@ sub _infotable { if($r->{resolution}) { Tr ++$i % 2 ? (class => 'odd') : (); - td 'Resolution'; + td mt '_relinfo_resolution'; td $self->{resolutions}[$r->{resolution}][0]; end; } if($r->{voiced}) { Tr ++$i % 2 ? (class => 'odd') : (); - td 'Voiced'; - td $self->{voiced}[$r->{voiced}]; + td mt '_relinfo_voiced'; + td mt '_voiced_'.$r->{voiced}; end; } if($r->{ani_story} || $r->{ani_ero}) { Tr ++$i % 2 ? (class => 'odd') : (); - td 'Animation'; + td mt '_relinfo_ani'; td join ', ', - $r->{ani_story} ? ('Story: ' .$self->{animated}[$r->{ani_story}]):(), - $r->{ani_ero} ? ('Ero scenes: '.$self->{animated}[$r->{ani_ero} ]):(); + $r->{ani_story} ? mt('_relinfo_ani_story', mt '_animated_'.$r->{ani_story}):(), + $r->{ani_ero} ? mt('_relinfo_ani_ero', mt '_animated_'.$r->{ani_ero} ):(); end; } Tr ++$i % 2 ? (class => 'odd') : (); - td 'Released'; + td mt '_relinfo_released'; td; - lit datestr $r->{released}; + lit $self->{l10n}->datestr($r->{released}); end; end; if($r->{minage} >= 0) { Tr ++$i % 2 ? (class => 'odd') : (); - td 'Age rating'; + td mt '_relinfo_minage'; td $self->{age_ratings}{$r->{minage}}[0]; end; } if(@{$r->{producers}}) { Tr ++$i % 2 ? (class => 'odd') : (); - td 'Producer'.($#{$r->{producers}} ? 's' : ''); + td mt '_relinfo_producer', scalar @{$r->{producers}}; td; for (@{$r->{producers}}) { a href => "/p$_->{id}", title => $_->{original}||$_->{name}, shorten $_->{name}, 60; @@ -221,16 +220,16 @@ sub _infotable { if($r->{catalog}) { Tr ++$i % 2 ? (class => 'odd') : (); - td 'Catalog no.'; + td mt '_relinfo_catalog'; td $r->{catalog}; end; } if($r->{website}) { Tr ++$i % 2 ? (class => 'odd') : (); - td 'Links'; + td mt '_relinfo_links'; td; - a href => $r->{website}, rel => 'nofollow', 'Official website'; + a href => $r->{website}, rel => 'nofollow', mt '_relinfo_website'; end; end; } @@ -238,19 +237,20 @@ sub _infotable { if($self->authInfo->{id}) { my $rl = $self->dbVNListGet(uid => $self->authInfo->{id}, rid => $r->{id})->[0]; Tr ++$i % 2 ? (class => 'odd') : (); - td 'User options'; + td mt '_relinfo_user'; td; Select id => 'listsel', name => 'listsel'; - option !$rl ? 'not in your list' : "Status: $self->{vn_rstat}[$rl->{rstat}] / $self->{vn_vstat}[$rl->{vstat}]"; - optgroup label => 'Set release status'; + option mt !$rl ? '_relinfo_user_notlist' : + ('_relinfo_user_inlist', $self->{vn_rstat}[$rl->{rstat}], $self->{vn_vstat}[$rl->{vstat}]); + optgroup label => mt '_relinfo_user_setr'; option value => "r$_", $self->{vn_rstat}[$_] for (0..$#{$self->{vn_rstat}}); end; - optgroup label => 'Set play status'; + optgroup label => mt '_relinfo_user_setv'; option value => "v$_", $self->{vn_vstat}[$_] for (0..$#{$self->{vn_vstat}}); end; - option value => 'del', 'remove from list' if $rl; + option value => 'del', mt '_relinfo_user_del' if $rl; end; end; end; @@ -296,7 +296,7 @@ sub edit { if($self->reqMethod eq 'POST') { $frm = $self->formValidate( - { name => 'type', enum => [ 0..$#{$self->{release_types}} ] }, + { name => 'type', enum => $self->{release_types} }, { name => 'patch', required => 0, default => 0 }, { name => 'freeware', required => 0, default => 0 }, { name => 'doujin', required => 0, default => 0 }, @@ -305,17 +305,17 @@ sub edit { { name => 'gtin', required => 0, default => '0', func => [ \>intype, 'Not a valid JAN/UPC/EAN code' ] }, { name => 'catalog', required => 0, default => '', maxlength => 50 }, - { name => 'languages', multi => 1, enum => [ keys %{$self->{languages}} ] }, + { name => 'languages', multi => 1, enum => $self->{languages} }, { name => 'website', required => 0, default => '', template => 'url' }, { name => 'released', required => 0, default => 0, template => 'int' }, { name => 'minage' , required => 0, default => -1, enum => [ keys %{$self->{age_ratings}} ] }, { name => 'notes', required => 0, default => '', maxlength => 10240 }, - { name => 'platforms', required => 0, default => '', multi => 1, enum => [ keys %{$self->{platforms}} ] }, + { name => 'platforms', required => 0, default => '', multi => 1, enum => $self->{platforms} }, { name => 'media', required => 0, default => '' }, { name => 'resolution',required => 0, default => 0, enum => [ 0..$#{$self->{resolutions}} ] }, - { name => 'voiced', required => 0, default => 0, enum => [ 0..$#{$self->{voiced}} ] }, - { name => 'ani_story', required => 0, default => 0, enum => [ 0..$#{$self->{animated}} ] }, - { name => 'ani_ero', required => 0, default => 0, enum => [ 0..$#{$self->{animated}} ] }, + { name => 'voiced', required => 0, default => 0, enum => $self->{voiced} }, + { name => 'ani_story', required => 0, default => 0, enum => $self->{animated} }, + { name => 'ani_ero', required => 0, default => 0, enum => $self->{animated} }, { name => 'producers', required => 0, default => '' }, { name => 'vn', maxlength => 5000 }, { name => 'editsum', maxlength => 5000 }, @@ -329,7 +329,9 @@ sub edit { $new_vn = [ map { /^([0-9]+)/ ? $1 : () } split /\|\|\|/, $frm->{vn} ]; $frm->{platforms} = [ grep $_, @{$frm->{platforms}} ]; $frm->{$_} = $frm->{$_} ? 1 : 0 for (qw|patch freeware doujin|); - $frm->{doujin} = 0 if $frm->{patch}; + + # reset some fields when the patch flag is set + $frm->{doujin} = $frm->{resolution} = $frm->{voiced} = $frm->{ani_story} = $frm->{ani_ero} = 0 if $frm->{patch}; my $same = $rid && (join(',', sort @{$b4{platforms}}) eq join(',', sort @{$frm->{platforms}})) && @@ -367,10 +369,11 @@ sub edit { $frm->{title} = $v->{title} if !defined $frm->{title} && !$r; $frm->{original} = $v->{original} if !defined $frm->{original} && !$r; - $self->htmlHeader(js => 'forms', title => $rid ? ''.($copy ? 'Copy ':'Edit ').$r->{title} : 'Add release to '.$v->{title}, noindex => 1); + my $title = mt $rid ? ($copy ? '_redit_title_copy' : '_redit_title_edit', $r->{title}) : ('_redit_title_add', $v->{title}); + $self->htmlHeader(js => 'forms', title => $title, noindex => 1); $self->htmlMainTabs('r', $r, $copy ? 'copy' : 'edit') if $rid; $self->htmlMainTabs('v', $v, 'edit') if $vid; - $self->htmlEditMessage('r', $r, $copy); + $self->htmlEditMessage('r', $r, $title, $copy); _form($self, $r, $v, $frm, $copy); $self->htmlFooter; } @@ -380,57 +383,56 @@ sub _form { my($self, $r, $v, $frm, $copy) = @_; $self->htmlForm({ frm => $frm, action => $r ? "/r$r->{id}/".($copy ? 'copy' : 'edit') : "/v$v->{id}/add", editsum => 1 }, - "General info" => [ - [ select => short => 'type', name => 'Type', - options => [ map [ $_, $self->{release_types}[$_] ], 0..$#{$self->{release_types}} ] ], - [ check => short => 'patch', name => 'This release is a patch to another release.' ], - [ check => short => 'freeware', name => 'Freeware (i.e. available at no cost)' ], - [ check => short => 'doujin', name => 'Doujin (self-published / not by a commercial company)' ], - [ input => short => 'title', name => 'Title (romaji)', width => 300 ], - [ input => short => 'original', name => 'Original title', width => 300 ], - [ static => content => 'The original title of this release, leave blank if it already is in the Latin alphabet.' ], - [ select => short => 'languages', name => 'Language(s)', multi => 1, - options => [ map [ $_, "$_ ($self->{languages}{$_})" ], sort keys %{$self->{languages}} ] ], - [ input => short => 'gtin', name => 'JAN/UPC/EAN' ], - [ input => short => 'catalog', name => 'Catalog number' ], - [ input => short => 'website', name => 'Official website' ], - [ date => short => 'released', name => 'Release date' ], - [ static => content => 'Leave month or day blank if they are unknown' ], - [ select => short => 'minage', name => 'Age rating', + rel_geninfo => [ mt('_redit_form_geninfo'), + [ select => short => 'type', name => mt('_redit_form_type'), + options => [ map [ $_, mt "_rtype_$_" ], @{$self->{release_types}} ] ], + [ check => short => 'patch', name => mt('_redit_form_patch') ], + [ check => short => 'freeware', name => mt('_redit_form_freeware') ], + [ check => short => 'doujin', name => mt('_redit_form_doujin') ], + [ input => short => 'title', name => mt('_redit_form_title'), width => 300 ], + [ input => short => 'original', name => mt('_redit_form_original'), width => 300 ], + [ static => content => mt '_redit_form_original_note' ], + [ select => short => 'languages', name => mt('_redit_form_languages'), multi => 1, + options => [ map [ $_, "$_ (".mt("_lang_$_").')' ], sort @{$self->{languages}} ] ], + [ input => short => 'gtin', name => mt('_redit_form_gtin') ], + [ input => short => 'catalog', name => mt('_redit_form_catalog') ], + [ input => short => 'website', name => mt('_redit_form_website') ], + [ date => short => 'released', name => mt('_redit_form_released') ], + [ static => content => mt('_redit_form_released_note') ], + [ select => short => 'minage', name => mt('_redit_form_minage'), options => [ map [ $_, $self->{age_ratings}{$_}[0].($self->{age_ratings}{$_}[1]?" (e.g. $self->{age_ratings}{$_}[1])":'') ], sort { $a <=> $b } keys %{$self->{age_ratings}} ] ], - [ textarea => short => 'notes', name => 'Notes' ], - [ static => content => 'Miscellaneous notes/comments, information that does not fit in the above fields. ' - .'E.g.: Censored/uncensored or for which releases this patch applies. Max. 250 characters.' ], + [ textarea => short => 'notes', name => mt('_redit_form_notes').'<br /><b class="standout">'.mt('_inenglish').'</b>' ], + [ static => content => mt('_redit_form_notes_note') ], ], - 'Format' => [ - [ select => short => 'resolution', name => 'Resolution', options => [ + rel_format => [ mt('_redit_form_format'), + [ select => short => 'resolution', name => mt('_redit_form_resolution'), options => [ map [ $_, @{$self->{resolutions}[$_]} ], 0..$#{$self->{resolutions}} ] ], - [ select => short => 'voiced', name => 'Voiced', options => [ - map [ $_, $self->{voiced}[$_] ], 0..$#{$self->{voiced}} ] ], - [ select => short => 'ani_story', name => 'Story animation', options => [ - map [ $_, $self->{animated}[$_] ], 0..$#{$self->{animated}} ] ], - [ select => short => 'ani_ero', name => 'Ero animation', options => [ - map [ $_, $_ ? $self->{animated}[$_] : 'Unknown / no ero scenes' ], 0..$#{$self->{animated}} ] ], - [ static => content => 'Animation in erotic scenes, leave to unknown if there are no ero scenes.' ], + [ select => short => 'voiced', name => mt('_redit_form_voiced'), options => [ + map [ $_, mt '_voiced_'.$_ ], @{$self->{voiced}} ] ], + [ select => short => 'ani_story', name => mt('_redit_form_ani_story'), options => [ + map [ $_, mt '_animated_'.$_ ], @{$self->{animated}} ] ], + [ select => short => 'ani_ero', name => mt('_redit_form_ani_ero'), options => [ + map [ $_, $_ ? mt '_animated_'.$_ : mt('_redit_form_ani_ero_none') ], @{$self->{animated}} ] ], + [ static => content => mt('_redit_form_ani_ero_note') ], [ hidden => short => 'media' ], [ static => nolabel => 1, content => sub { - h2 'Platforms'; + h2 mt '_redit_form_platforms'; div class => 'platforms'; - for my $p (sort keys %{$self->{platforms}}) { + for my $p (sort @{$self->{platforms}}) { span; input type => 'checkbox', name => 'platforms', value => $p, id => $p, $frm->{platforms} && grep($_ eq $p, @{$frm->{platforms}}) ? (checked => 'checked') : (); label for => $p; - cssicon $p, $self->{platforms}{$p}; - txt ' '.$self->{platforms}{$p}; + cssicon $p, mt "_plat_$p"; + txt ' '.mt("_plat_$p"); end; end; } end; - h2 'Media'; + h2 mt '_redit_form_media'; div id => 'media_div'; Select; option value => $_, class => $self->{media}{$_}[1] ? 'qty' : 'noqty', $self->{media}{$_}[0] @@ -440,13 +442,13 @@ sub _form { }], ], - 'Producers' => [ + rel_prod => [ mt('_redit_form_prod'), [ hidden => short => 'producers' ], [ static => nolabel => 1, content => sub { - h2 'Selected producers'; + h2 mt('_redit_form_prod_sel'); div id => 'producerssel'; end; - h2 'Add producer'; + h2 mt('_redit_form_prod_add'); div; input type => 'text', class => 'text'; a href => '#', 'add'; @@ -454,13 +456,13 @@ sub _form { }], ], - 'Visual novels' => [ + rel_vn => [ mt('_redit_form_vn'), [ hidden => short => 'vn' ], [ static => nolabel => 1, content => sub { - h2 'Selected visual novels'; + h2 mt('_redit_form_vn_sel'); div id => 'vnsel'; end; - h2 'Add visual novel'; + h2 mt('_redit_form_vn_add'); div; input type => 'text', class => 'text'; a href => '#', 'add'; @@ -479,10 +481,10 @@ sub browse { { name => 's', required => 0, default => 'title', enum => [qw|released minage title|] }, { name => 'o', required => 0, default => 'a', enum => ['a', 'd'] }, { name => 'q', required => 0, default => '', maxlength => 500 }, - { name => 'ln', required => 0, multi => 1, default => '', enum => [ keys %{$self->{languages}} ] }, - { name => 'pl', required => 0, multi => 1, default => '', enum => [ keys %{$self->{platforms}} ] }, + { name => 'ln', required => 0, multi => 1, default => '', enum => $self->{languages} }, + { name => 'pl', required => 0, multi => 1, default => '', enum => $self->{platforms} }, { name => 'me', required => 0, multi => 1, default => '', enum => [ keys %{$self->{media}} ] }, - { name => 'tp', required => 0, default => -1, enum => [ -1..$#{$self->{release_types}} ] }, + { name => 'tp', required => 0, default => -1, enum => [ -1, @{$self->{release_types}} ] }, { name => 'pa', required => 0, default => 0, enum => [ 0..2 ] }, { name => 'fw', required => 0, default => 0, enum => [ 0..2 ] }, { name => 'do', required => 0, default => 0, enum => [ 0..2 ] }, @@ -521,7 +523,7 @@ sub browse { $_&&($url .= ";re=$_") for @{$f->{re}}; $_&&($url .= ";me=$_") for @{$f->{me}}; - $self->htmlHeader(title => 'Browse releases'); + $self->htmlHeader(title => mt('_rbrowse_title')); _filters($self, $f, !@filters || !@$list); $self->htmlBrowse( class => 'relbrowse', @@ -531,22 +533,22 @@ sub browse { pageurl => "$url;s=$f->{s};o=$f->{o}", sorturl => $url, header => [ - [ 'Released', 'released' ], - [ 'Rating', 'minage' ], + [ mt('_rbrowse_col_released'), 'released' ], + [ mt('_rbrowse_col_minage'), 'minage' ], [ '', '' ], - [ 'Title', 'title' ], + [ mt('_rbrowse_col_title'), 'title' ], ], row => sub { my($s, $n, $l) = @_; Tr $n % 2 ? (class => 'odd') : (); td class => 'tc1'; - lit datestr $l->{released}; + lit $self->{l10n}->datestr($l->{released}); end; td class => 'tc2', $l->{minage} > -1 ? $self->{age_ratings}{$l->{minage}}[0] : ''; td class => 'tc3'; - $_ ne 'oth' && cssicon $_, $self->{platforms}{$_} for (@{$l->{platforms}}); - cssicon "lang $_", $self->{languages}{$_} for (@{$l->{languages}}); - cssicon lc(substr($self->{release_types}[$l->{type}],0,3)), $self->{release_types}[$l->{type}]; + $_ ne 'oth' && cssicon $_, mt "_plat_$_" for (@{$l->{platforms}}); + cssicon "lang $_", mt "_lang_$_" for (@{$l->{languages}}); + cssicon "rt$l->{type}", mt "_rtype_$l->{type}"; end; td class => 'tc4'; a href => "/r$l->{id}", title => $l->{original}||$l->{title}, shorten $l->{title}, 90; @@ -557,11 +559,9 @@ sub browse { ) if @$list; if(@filters && !@$list) { div class => 'mainbox'; - h1 'No results found'; + h1 mt '_rbrowse_noresults_title'; div class => 'notice'; - p qq|Sorry, couldn't find anything that comes through your filters. You might want to disable a few filters to get more results.\n\n| - .qq|Also, keep in mind that we don't have all information about all releases. So e.g. filtering on screen resolution will exclude | - .qq|all releases of which we don't know it's resolution, even though it might in fact be in the resolution you're looking for.|; + p mt '_rbrowse_noresults_msg'; end; end; } @@ -574,32 +574,31 @@ sub _filters { form method => 'get', action => '/r', 'accept-charset' => 'UTF-8'; div class => 'mainbox'; - h1 'Browse releases'; + h1 mt '_rbrowse_title'; $self->htmlSearchBox('r', $f->{q}); a id => 'advselect', href => '#'; - lit '<i>'.($shown?'▾':'▸').'</i> filters'; + lit '<i>'.($shown?'▾':'▸').'</i> '.mt('_rbrowse_filters'); end; div id => 'advoptions', !$shown ? (class => 'hidden') : (); - h2 'Filters'; + h2 mt '_rbrowse_filters'; table class => 'formtable', style => 'margin-left: 0'; Tr class => 'newfield'; - td class => 'label'; label for => 'ma_m', 'Age rating'; end; + td class => 'label'; label for => 'ma_m', mt '_rbrowse_minage'; end; td class => 'field'; - Select id => 'ma_m', name => 'ma_m', style => 'width: 70px'; - option value => 0, $f->{ma_m} == 0 ? ('selected' => 'selected') : (), 'greater'; - option value => 1, $f->{ma_m} == 1 ? ('selected' => 'selected') : (), 'smaller'; + Select id => 'ma_m', name => 'ma_m', style => 'width: 160px'; + option value => 0, $f->{ma_m} == 0 ? ('selected' => 'selected') : (), mt '_rbrowse_ge'; + option value => 1, $f->{ma_m} == 1 ? ('selected' => 'selected') : (), mt '_rbrowse_le'; end; - txt ' than or equal to '; Select id => 'ma_a', name => 'ma_a', style => 'width: 80px; text-align: center'; $_>=0 && option value => $_, $f->{ma_a} == $_ ? ('selected' => 'selected') : (), $self->{age_ratings}{$_}[0] for (sort { $a <=> $b } keys %{$self->{age_ratings}}); end; end; td rowspan => 5, style => 'padding-left: 40px'; - label for => 're', 'Screen resolution'; br; + label for => 're', mt '_rbrowse_resolution'; br; Select id => 're', name => 're', multiple => 'multiple', size => 8; my $l=''; for my $i (1..$#{$self->{resolutions}}) { @@ -614,47 +613,50 @@ sub _filters { end; end; end; - $self->htmlFormPart($f, [ select => short => 'tp', name => 'Release type', - options => [ [-1, 'All'], map [ $_, $self->{release_types}[$_] ], 0..$#{$self->{release_types}} ]]); - $self->htmlFormPart($f, [ select => short => 'pa', name => 'Patch status', - options => [ [0, 'All'], [1, 'Only patches'], [2, 'Only standalone releases']]]); - $self->htmlFormPart($f, [ select => short => 'fw', name => 'Freeware', - options => [ [0, 'All'], [1, 'Freeware only'], [2, 'Only non-free releases']]]); - $self->htmlFormPart($f, [ select => short => 'do', name => 'Doujin', - options => [ [0, 'All'], [1, 'Only doujin releases'], [2, 'Only commercial releases']]]); - $self->htmlFormPart($f, [ date => short => 'mi', name => 'Released after' ]); - $self->htmlFormPart($f, [ date => short => 'ma', name => 'Released before' ]); + $self->htmlFormPart($f, [ select => short => 'tp', name => mt('_rbrowse_type'), + options => [ [-1, mt '_rbrowse_all'], map [ $_, mt "_rtype_$_" ], @{$self->{release_types}} ]]); + $self->htmlFormPart($f, [ select => short => 'pa', name => mt('_rbrowse_patch'), + options => [ [0, mt '_rbrowse_all' ], [1, mt '_rbrowse_patchonly'], [2, mt '_rbrowse_patchnone']]]); + $self->htmlFormPart($f, [ select => short => 'fw', name => mt('_rbrowse_freeware'), + options => [ [0, mt '_rbrowse_all' ], [1, mt '_rbrowse_freewareonly'], [2, mt '_rbrowse_freewarenone']]]); + $self->htmlFormPart($f, [ select => short => 'do', name => mt('_rbrowse_doujin'), + options => [ [0, mt '_rbrowse_all' ], [1, mt '_rbrowse_doujinonly'], [2, mt '_rbrowse_doujinnone']]]); + $self->htmlFormPart($f, [ date => short => 'mi', name => mt '_rbrowse_dateafter' ]); + $self->htmlFormPart($f, [ date => short => 'ma', name => mt '_rbrowse_datebefore' ]); end; h2; - lit 'Languages <b>(boolean or, selecting more gives more results)</b>'; + txt mt '_rbrowse_languages'; + b ' ('.mt('_rbrowse_boolor').')'; end; for my $i (sort @{$self->dbLanguages}) { span; input type => 'checkbox', name => 'ln', value => $i, id => "lang_$i", grep($_ eq $i, @{$f->{ln}}) ? (checked => 'checked') : (); label for => "lang_$i"; - cssicon "lang $i", $self->{languages}{$i}; - txt $self->{languages}{$i}; + cssicon "lang $i", mt "_lang_$i"; + txt mt "_lang_$i"; end; end; } h2; - lit 'Platforms <b>(boolean or, selecting more gives more results)</b>'; + txt mt '_rbrowse_platforms'; + b ' ('.mt('_rbrowse_boolor').')'; end; - for my $i (sort keys %{$self->{platforms}}) { + for my $i (sort @{$self->{platforms}}) { next if $i eq 'oth'; span; input type => 'checkbox', name => 'pl', value => $i, id => "plat_$i", grep($_ eq $i, @{$f->{pl}}) ? (checked => 'checked') : (); label for => "plat_$i"; - cssicon $i, $self->{platforms}{$i}; - txt $self->{platforms}{$i}; + cssicon $i, mt "_plat_$i"; + txt mt "_plat_$i"; end; end; } h2; - lit 'Media <b>(boolean or, selecting more gives more results)</b>'; + txt mt '_rbrowse_media'; + b ' ('.mt('_rbrowse_boolor').')'; end; for my $i (sort keys %{$self->{media}}) { next if $i eq 'otc'; @@ -665,8 +667,8 @@ sub _filters { } div style => 'text-align: center; clear: left;'; - input type => 'submit', value => 'Apply', class => 'submit'; - input type => 'reset', value => 'Clear', class => 'submit', onclick => 'location.href="/r"'; + input type => 'submit', value => mt('_rbrowse_apply'), class => 'submit'; + input type => 'reset', value => mt('_rbrowse_clear'), class => 'submit', onclick => 'location.href="/r"'; end; end; end; diff --git a/lib/VNDB/Handler/Tags.pm b/lib/VNDB/Handler/Tags.pm index 8f0e1486..ae240d38 100644 --- a/lib/VNDB/Handler/Tags.pm +++ b/lib/VNDB/Handler/Tags.pm @@ -46,32 +46,31 @@ sub tagpage { maxspoil => $f->{m}, ); - my $title = ($t->{meta} ? 'Meta tag: ' : 'Tag: ').$t->{name}; + my $title = mt '_tagp_title', $t->{meta}?0:1, $t->{name}; $self->htmlHeader(title => $title, noindex => $t->{state} != 2); $self->htmlMainTabs('g', $t); if($t->{state} != 2) { div class => 'mainbox'; - h1 "Tag: $t->{name}"; + h1 $title; if($t->{state} == 1) { div class => 'warning'; - h2 'Tag deleted'; + h2 mt '_tagp_del_title'; p; - lit qq|This tag has been removed from the database, and cannot be used or re-added.|. - qq| File a request on the <a href="/t/db">discussion board</a> if you disagree with this.|; + lit mt '_tagp_del_msg'; end; end; } else { div class => 'notice'; - h2 'Waiting for approval'; - p 'This tag is waiting for a moderator to approve it. You can still use it to tag VNs as you would with a normal tag.'; + h2 mt '_tagp_pending_title'; + p mt '_tagp_pending_msg'; end; } end; } div class => 'mainbox'; - a class => 'addnew', href => "/g$tag/add", ($self->authCan('tagmod')?'Create':'Request').' child tag' if $self->authCan('tag'); + a class => 'addnew', href => "/g$tag/add", mt '_tagp_addchild' if $self->authCan('tag') && $t->{state} != 1; h1 $title; p; @@ -84,7 +83,7 @@ sub tagpage { if($_ < $#p && $p[$_+1]{lvl} < $p[$_]{lvl}) { push @r, $p[$_]; } elsif($#p == $_ || $p[$_+1]{lvl} >= $p[$_]{lvl}) { - a href => '/g', 'Tags'; + a href => '/g', mt '_tagp_indexlink'; for ($p[$_], reverse @r) { txt ' > '; a href => "/g$_->{tag}", $_->{name}; @@ -93,7 +92,7 @@ sub tagpage { } } if(!@p) { - a href => '/g', 'Tags'; + a href => '/g', mt '_tagp_indexlink'; txt " > $t->{name}\n"; } end; @@ -105,7 +104,7 @@ sub tagpage { } if(@{$t->{aliases}}) { p class => 'center'; - b "Aliases:\n"; + b mt('_tagp_aliases')."\n"; txt "$_\n" for (@{$t->{aliases}}); end; } @@ -133,11 +132,7 @@ sub _childtags { } div class => 'mainbox'; - if(!$index) { - h1 'Child tags'; - } else { - h1 'Tag tree'; - } + h1 mt $index ? '_tagp_tree' : '_tagp_childs'; ul class => 'tagtree'; for my $p (sort { @{$b->{childs}} <=> @{$a->{childs}} } @tags) { li; @@ -156,7 +151,7 @@ sub _childtags { if(@{$p->{childs}} > 6) { li; txt '> '; - a href => "/g$p->{tag}", style => 'font-style: italic', sprintf '%d more tags...', @{$p->{childs}}-5; + a href => "/g$p->{tag}", style => 'font-style: italic', mt '_tagp_moretags', @{$p->{childs}}-5; end; } end; @@ -171,16 +166,16 @@ sub _childtags { sub _vnlist { my($self, $t, $f, $list, $np) = @_; div class => 'mainbox'; - h1 'Visual novels'; + h1 mt '_tagp_vnlist'; p class => 'browseopts'; - a href => "/g$t->{id}?m=0", $f->{m} == 0 ? (class => 'optselected') : (), onclick => "setCookie('tagspoil', 0);return true;", 'Hide spoilers'; - a href => "/g$t->{id}?m=1", $f->{m} == 1 ? (class => 'optselected') : (), onclick => "setCookie('tagspoil', 1);return true;", 'Show minor spoilers'; - a href => "/g$t->{id}?m=2", $f->{m} == 2 ? (class => 'optselected') : (), onclick => "setCookie('tagspoil', 2);return true;", 'Show major spoilers'; + a href => "/g$t->{id}?m=0", $f->{m} == 0 ? (class => 'optselected') : (), onclick => "setCookie('tagspoil', 0);return true;", mt '_tagp_spoil0'; + a href => "/g$t->{id}?m=1", $f->{m} == 1 ? (class => 'optselected') : (), onclick => "setCookie('tagspoil', 1);return true;", mt '_tagp_spoil1'; + a href => "/g$t->{id}?m=2", $f->{m} == 2 ? (class => 'optselected') : (), onclick => "setCookie('tagspoil', 2);return true;", mt '_tagp_spoil2'; end; if(!@$list) { - p "\n\nThis tag has not been linked to any visual novels yet, or they were hidden because of the spoiler settings."; + p "\n\n".mt '_tagp_novn'; } - p "\nNOTE: This list is cached, it can take up to 24 hours after a visual novel has been tagged for it to show up on this page."; + p "\n".mt '_tagp_cached'; end; return if !@$list; $self->htmlBrowse( @@ -191,12 +186,12 @@ sub _vnlist { pageurl => "/g$t->{id}?m=$f->{m};o=$f->{o};s=$f->{s}", sorturl => "/g$t->{id}?m=$f->{m}", header => [ - [ 'Score', 'score' ], - [ 'Title', 'title' ], - [ '', 0 ], - [ '', 0 ], - [ 'Released', 'rel' ], - [ 'Popularity', 'pop' ], + [ mt('_tagp_vncol_score'), 'score' ], + [ mt('_tagp_vncol_title'), 'title' ], + [ '', 0 ], + [ '', 0 ], + [ mt('_tagp_vncol_rel'), 'rel' ], + [ mt('_tagp_vncol_pop'), 'pop' ], ], row => sub { my($s, $n, $l) = @_; @@ -209,15 +204,15 @@ sub _vnlist { a href => '/v'.$l->{vid}, title => $l->{original}||$l->{title}, shorten $l->{title}, 100; end; td class => 'tc3'; - $_ ne 'oth' && cssicon $_, $self->{platforms}{$_} + $_ ne 'oth' && cssicon $_, mt "_plat_$_" for (sort split /\//, $l->{c_platforms}); end; td class => 'tc4'; - cssicon "lang $_", $self->{languages}{$_} + cssicon "lang $_", mt "_lang_$_" for (reverse sort split /\//, $l->{c_languages}); end; td class => 'tc5'; - lit monthstr $l->{c_released}; + lit $self->{l10n}->datestr($l->{c_released}); end; td class => 'tc6', sprintf '%.2f', $l->{c_popularity}*100; end; @@ -264,7 +259,7 @@ sub tagedit { } for(@parents, @merge) { my $c = $self->dbTagGet(name => $_, noid => $tag); - push @{$frm->{_err}}, [ 'parents', 'func', [ 0, "Tag '$_' not found." ]] if !@$c; + push @{$frm->{_err}}, [ 'parents', 'func', [ 0, mt '_tagedit_err_notfound', $_ ]] if !@$c; $_ = $c->[0]{id}; } } @@ -295,50 +290,42 @@ sub tagedit { $frm->{parents} ||= join ', ', map $_->{name}, @{$t->{parents}}; } - my $title = $par ? "Add child tag to $par->{name}" : $tag ? "Edit tag: $t->{name}" : 'Add new tag'; + my $title = $par ? mt('_tagedit_title_add', $par->{name}) : $tag ? mt('_tagedit_title_edit', $t->{name}) : mt '_tagedit_title_new'; $self->htmlHeader(title => $title, noindex => 1); $self->htmlMainTabs('g', $par || $t, 'edit') if $t || $par; if(!$self->authCan('tagmod')) { div class => 'mainbox'; - h1 'Requesting new tag'; + h1 mt '_tagedit_req_title'; div class => 'notice'; - h2 'Your tag must be approved'; + h2 mt '_tagedit_req_subtitle'; p; - txt 'Because all tags have to be approved by moderators, it can take a while before it '. - 'will show up in the tag list or on visual novel pages. You can still vote on tag even if '. - 'it has not been approved yet, though.'. - "\n\n"; - lit 'Also, make sure you\'ve read the <a href="/d10">guidelines</a>, so you can predict whether '. - 'your tag will be accepted or not.'; + lit mt '_tagedit_req_msg'; end; end; end; } - $self->htmlForm({ frm => $frm, action => $par ? "/g$par->{id}/add" : $tag ? "/g$tag/edit" : '/g/new' }, $title => [ - [ input => short => 'name', name => 'Primary name' ], + $self->htmlForm({ frm => $frm, action => $par ? "/g$par->{id}/add" : $tag ? "/g$tag/edit" : '/g/new' }, 'tagedit' => [ $title, + [ input => short => 'name', name => mt '_tagedit_frm_name' ], $self->authCan('tagmod') ? ( $tag ? - [ static => label => 'Added by', content => sub { a href => "/u$t->{addedby}", $t->{username}; } ] : (), - [ select => short => 'state', name => 'State', options => [ - [ 0, 'Awaiting moderation' ], [ 1, 'Deleted/hidden' ], [ 2, 'Approved' ] ] ], - [ checkbox => short => 'meta', name => 'This is a meta-tag (only to be used as parent for other tags, not for linking to VN entries)' ], + [ static => label => mt('_tagedit_frm_by'), content => $self->{l10n}->userstr($t->{addedby}, $t->{username}) ] : (), + [ select => short => 'state', name => mt('_tagedit_frm_state'), options => [ + map [$_, mt '_tagedit_frm_state'.$_], 0..2 ] ], + [ checkbox => short => 'meta', name => mt '_tagedit_frm_meta' ], $tag ? - [ static => content => 'WARNING: Checking this option or selecting "Deleted" as state will permanently delete all existing VN relations!' ] : (), + [ static => content => mt '_tagedit_frm_meta_warn' ] : (), ) : (), - [ textarea => short => 'alias', name => "Aliases\n(separated by newlines)", cols => 30, rows => 4 ], - [ textarea => short => 'description', name => 'Description' ], - [ static => content => 'What should the tag be used for? Having a good description helps users choose which tags to link to a VN.' ], - [ input => short => 'parents', name => 'Parent tags' ], - [ static => content => "Comma separated list of tag names to be used as parent for this tag." ], + [ textarea => short => 'alias', name => mt('_tagedit_frm_alias'), cols => 30, rows => 4 ], + [ textarea => short => 'description', name => mt '_tagedit_frm_desc' ], + [ static => content => mt '_tagedit_frm_desc_msg' ], + [ input => short => 'parents', name => mt '_tagedit_frm_parents' ], + [ static => content => mt '_tagedit_frm_parents_msg' ], $self->authCan('tagmod') ? ( - [ part => title => 'Merge tags' ], - [ input => short => 'merge', name => 'Tags to merge' ], - [ static => content => 'Comma separated list of tag names to merge into this one.' - .' All votes and aliases/names will be moved over to this tag, and the old tags will be deleted.' - .' Just leave this field empty if you don\'t intend to do a merge.' - .'<br />WARNING: this action cannot be undone!' ], + [ part => title => mt '_tagedit_frm_merge' ], + [ input => short => 'merge', name => mt '_tagedit_frm_merge_tags' ], + [ static => content => mt '_tagedit_frm_merge_msg' ], ) : (), ]); $self->htmlFooter; @@ -365,22 +352,21 @@ sub taglist { search => $f->{q} ); - my $title = $f->{t} == -1 ? 'Browse tags' : $f->{t} == 0 ? 'Tags awaiting moderation' : $f->{t} == 1 ? 'Deleted tags' : 'All visible tags'; - $self->htmlHeader(title => $title); + $self->htmlHeader(title => mt '_tagb_title'); div class => 'mainbox'; - h1 $title; + h1 mt '_tagb_title'; form action => '/g/list', 'accept-charset' => 'UTF-8', method => 'get'; input type => 'hidden', name => 't', value => $f->{t}; $self->htmlSearchBox('g', $f->{q}); end; p class => 'browseopts'; - a href => "/g/list?q=$f->{q};t=-1", $f->{t} == -1 ? (class => 'optselected') : (), 'All'; - a href => "/g/list?q=$f->{q};t=0", $f->{t} == 0 ? (class => 'optselected') : (), 'Awaiting moderation'; - a href => "/g/list?q=$f->{q};t=1", $f->{t} == 1 ? (class => 'optselected') : (), 'Deleted'; - a href => "/g/list?q=$f->{q};t=2", $f->{t} == 2 ? (class => 'optselected') : (), 'Accepted'; + a href => "/g/list?q=$f->{q};t=-1", $f->{t} == -1 ? (class => 'optselected') : (), mt '_tagb_state-1'; + a href => "/g/list?q=$f->{q};t=0", $f->{t} == 0 ? (class => 'optselected') : (), mt '_tagb_state0'; + a href => "/g/list?q=$f->{q};t=1", $f->{t} == 1 ? (class => 'optselected') : (), mt '_tagb_state1'; + a href => "/g/list?q=$f->{q};t=2", $f->{t} == 2 ? (class => 'optselected') : (), mt '_tagb_state2'; end; if(!@$t) { - p 'No results found'; + p mt '_tagb_noresults'; } end; if(@$t) { @@ -392,18 +378,18 @@ sub taglist { pageurl => "/g/list?t=$f->{t};q=$f->{q};s=$f->{s};o=$f->{o}", sorturl => "/g/list?t=$f->{t};q=$f->{q}", header => [ - [ 'Created', 'added' ], - [ 'Tag', 'name' ], + [ mt('_tagb_col_added'), 'added' ], + [ mt('_tagb_col_name'), 'name' ], ], row => sub { my($s, $n, $l) = @_; Tr $n % 2 ? (class => 'odd') : (); - td class => 'tc1', age $l->{added}; + td class => 'tc1', $self->{l10n}->age($l->{added}); td class => 'tc3'; a href => "/g$l->{id}", $l->{name}; if($f->{t} == -1) { - b class => 'grayedout', ' awaiting moderation' if $l->{state} == 0; - b class => 'grayedout', ' deleted' if $l->{state} == 1; + b class => 'grayedout', ' '.mt '_tagb_note_awaiting' if $l->{state} == 0; + b class => 'grayedout', ' '.mt '_tagb_note_del' if $l->{state} == 1; } end; end; @@ -435,44 +421,43 @@ sub vntagmod { my $frm; - $self->htmlHeader(title => "Add/remove tags for $v->{title}", noindex => 1, js => 'forms'); + my $title = mt '_tagv_title', $v->{title}; + $self->htmlHeader(title => $title, noindex => 1, js => 'forms'); $self->htmlMainTabs('v', $v, 'tagmod'); div class => 'mainbox'; - h1 "Add/remove tags for $v->{title}"; + h1 $title; div class => 'notice'; - h2 'Tagging'; + h2 mt '_tagv_msg_title'; ul; - li; - lit 'Make sure you have read the <a href="/d10">guidelines</a>!'; - end; - li "Don't forget to hit the submit button on the bottom of the page to make your changes permanent."; - li 'Some tag information on the site is cached, it can take up to an hour for your changes to be visible everywhere.'; + li; lit mt '_tagv_msg_guidelines'; end; + li mt '_tagv_msg_submit'; + li mt '_tagv_msg_cache'; end; end; end; - $self->htmlForm({ frm => $frm, action => "/v$vid/tagmod", hitsubmit => 1 }, 'Tags' => [ + $self->htmlForm({ frm => $frm, action => "/v$vid/tagmod", nosubmit => 1 }, tagmod => [ mt('_tagv_frm_title'), [ hidden => short => 'taglinks', value => '' ], [ static => nolabel => 1, content => sub { table id => 'tagtable'; thead; Tr; td ''; - td colspan => 2, class => 'tc2_1', 'You'; - td colspan => 2, class => 'tc3_1', 'Others'; + td colspan => 2, class => 'tc2_1', mt '_tagv_col_you'; + td colspan => 2, class => 'tc3_1', mt '_tagv_col_others'; end; Tr; my $i=0; - td class => 'tc'.++$i, $_ for(qw|Tag Rating Spoiler Rating Spoiler|); + td class => 'tc'.++$i, mt '_tagv_col_'.$_ for(qw|tag rating spoiler rating spoiler|); end; end; tfoot; Tr; td colspan => 5; + input type => 'submit', class => 'submit', value => mt('_tagv_save'), style => 'float: right'; input type => 'text', class => 'text', name => 'addtag', value => ''; - input type => 'button', class => 'submit', value => 'Add tag'; + input type => 'button', class => 'submit', value => mt '_tagv_add'; br; p; - lit 'Check the <a href="/g">tag list</a> to browse all available tags.'. - '<br />Can\'t find what you\'re looking for? <a href="/g/new">Request a new tag</a>.'; + lit mt '_tagv_addmsg'; end; end; end; end; @@ -521,14 +506,15 @@ sub usertags { what => 'vns', ); - $self->htmlHeader(title => "Tags by $u->{username}", noindex => 1); + my $title = mt '_tagu_title', $u->{username}; + $self->htmlHeader(title => $title, noindex => 1); $self->htmlMainTabs('u', $u, 'tags'); div class => 'mainbox'; - h1 "Tags by $u->{username}"; + h1 $title; if(@$list) { - p 'Warning: spoilery tags are not hidden in this list!'; + p mt '_tagu_spoilerwarn'; } else { - p "$u->{username} doesn't seem to have used the tagging system yet..."; + p mt '_tagu_notags', $u->{username}; } end; @@ -544,13 +530,13 @@ sub usertags { sub { td class => 'tc1'; b id => 'relhidall'; - lit '<i>▸</i> #VNs '; + lit '<i>▸</i> '.mt('_tagu_col_num').' '; end; lit $f->{s} eq 'cnt' && $f->{o} eq 'a' ? "\x{25B4}" : qq|<a href="/u$u->{id}/tags?o=a;s=cnt">\x{25B4}</a>|; lit $f->{s} eq 'cnt' && $f->{o} eq 'd' ? "\x{25BE}" : qq|<a href="/u$u->{id}/tags?o=d;s=cnt">\x{25BE}</a>|; end; }, - [ 'Tag', 'name' ], + [ mt('_tagu_col_name'), 'name' ], [ ' ', '' ], ], row => sub { @@ -571,7 +557,7 @@ sub usertags { td class => 'tc1_2'; a href => "/v$_->{vid}", title => $_->{original}||$_->{title}, shorten $_->{title}, 50; end; - td class => 'tc1_3', !defined $_->{spoiler} ? ' ' : ['No spoiler', 'Minor spoiler', 'Major spoiler']->[$_->{spoiler}]; + td class => 'tc1_3', !defined $_->{spoiler} ? ' ' : mt "_tagu_spoil$_->{spoiler}"; end; } }, @@ -584,10 +570,10 @@ sub usertags { sub tagindex { my $self = shift; - $self->htmlHeader(title => 'Browse tags'); + $self->htmlHeader(title => mt '_tagidx_title'); div class => 'mainbox'; - a class => 'addnew', href => "/g/new", ($self->authCan('tagmod')?'Create':'Request').' new tag' if $self->authCan('tag'); - h1 'Search tags'; + a class => 'addnew', href => "/g/new", mt '_tagidx_create' if $self->authCan('tag'); + h1 mt '_tagidx_search'; form action => '/g/list', 'accept-charset' => 'UTF-8', method => 'get'; $self->htmlSearchBox('g', ''); end; @@ -596,58 +582,63 @@ sub tagindex { my $t = $self->dbTagTree(0, 2, 1); _childtags($self, {childs => $t}, 1); - # Recently added - div class => 'mainbox threelayout'; - a class => 'right', href => '/g/list', 'Browse all tags'; - my $r = $self->dbTagGet(order => 'added DESC', results => 10, state => 2); - h1 'Recently added'; - ul; - for (@$r) { - li; - txt age $_->{added}; - txt ' '; - a href => "/g$_->{id}", $_->{name}; - end; - } - end; - end; + table class => 'mainbox threelayout'; + Tr; + + # Recently added + td; + a class => 'right', href => '/g/list', mt '_tagidx_browseall'; + my $r = $self->dbTagGet(order => 'added DESC', results => 10, state => 2); + h1 mt '_tagidx_recent'; + ul; + for (@$r) { + li; + txt $self->{l10n}->age($_->{added}); + txt ' '; + a href => "/g$_->{id}", $_->{name}; + end; + } + end; + end; - # Popular - div class => 'mainbox threelayout'; - $r = $self->dbTagGet(order => 'c_vns DESC', meta => 0, results => 10); - h1 'Popular tags'; - ul; - for (@$r) { - li; - a href => "/g$_->{id}", $_->{name}; - txt " ($_->{c_vns})"; - end; - } - end; - end; + # Popular + td; + $r = $self->dbTagGet(order => 'c_vns DESC', meta => 0, results => 10); + h1 mt '_tagidx_popular'; + ul; + for (@$r) { + li; + a href => "/g$_->{id}", $_->{name}; + txt " ($_->{c_vns})"; + end; + } + end; + end; - # Moderation queue - div class => 'mainbox threelayout last'; - h1 'Awaiting moderation'; - $r = $self->dbTagGet(state => 0, order => 'added DESC', results => 10); - ul; - li "Moderation queue empty! yay!" if !@$r; - for (@$r) { + # Moderation queue + td; + h1 mt '_tagidx_queue'; + $r = $self->dbTagGet(state => 0, order => 'added DESC', results => 10); + ul; + li mt '_tagidx_queue_empty' if !@$r; + for (@$r) { + li; + txt $self->{l10n}->age($_->{added}); + txt ' '; + a href => "/g$_->{id}", $_->{name}; + end; + } li; - txt age $_->{added}; - txt ' '; - a href => "/g$_->{id}", $_->{name}; + txt "\n"; + a href => '/g/list?t=0;o=d;s=added', mt '_tagidx_queue_link'; + txt ' - '; + a href => '/g/list?t=1;o=d;s=added', mt '_tagidx_denied'; end; - } - li; - txt "\n"; - a href => '/g/list?t=0;o=d;s=added', 'Moderation queue'; - txt ' - '; - a href => '/g/list?t=1;o=d;s=added', 'Denied tags'; + end; end; - end; - end; - clearfloat; + + end; # /tr + end; # /table $self->htmlFooter; } diff --git a/lib/VNDB/Handler/ULists.pm b/lib/VNDB/Handler/ULists.pm index 05b1f60f..7d1a9304 100644 --- a/lib/VNDB/Handler/ULists.pm +++ b/lib/VNDB/Handler/ULists.pm @@ -42,7 +42,7 @@ sub vnwish { return $self->htmlDenied() if !$uid; my $f = $self->formValidate( - { name => 's', enum => [ -1..$#{$self->{wishlist_status}} ] } + { name => 's', enum => [ -1, @{$self->{wishlist_status}} ] } ); return 404 if $f->{_err}; @@ -99,20 +99,20 @@ sub wishlist { my $own = $self->authInfo->{id} && $self->authInfo->{id} == $uid; my $u = $self->dbUserGet(uid => $uid)->[0]; - return 404 if !$u || !$own && !$u->{show_list}; + return 404 if !$u || !$own && !($u->{show_list} || $self->authCan('usermod')); my $f = $self->formValidate( { name => 'p', required => 0, default => 1, template => 'int' }, { name => 'o', required => 0, default => 'd', enum => [ 'a', 'd' ] }, { name => 's', required => 0, default => 'wstat', enum => [qw|title added wstat|] }, - { name => 'f', required => 0, default => -1, enum => [ -1..$#{$self->{wishlist_status}} ] }, + { name => 'f', required => 0, default => -1, enum => [ -1, @{$self->{wishlist_status}} ] }, ); return 404 if $f->{_err}; if($own && $self->reqMethod eq 'POST') { my $frm = $self->formValidate( { name => 'sel', required => 0, default => 0, multi => 1, template => 'int' }, - { name => 'batchedit', required => 1, enum => [ -1..$#{$self->{wishlist_status}} ] }, + { name => 'batchedit', required => 1, enum => [ -1, @{$self->{wishlist_status}} ] }, ); if(!$frm->{_err} && @{$frm->{sel}} && $frm->{sel}[0]) { $self->dbWishListDel($uid, $frm->{sel}) if $frm->{batchedit} == -1; @@ -129,20 +129,20 @@ sub wishlist { page => $f->{p}, ); - my $title = $own ? 'My wishlist' : "\u$u->{username}'s wishlist"; + my $title = $own ? mt('_wishlist_title_my') : mt('_wishlist_title_other', $u->{username}); $self->htmlHeader(title => $title, noindex => 1); $self->htmlMainTabs('u', $u, 'wish'); div class => 'mainbox'; h1 $title; if(!@$list && $f->{f} == -1) { - p 'Wishlist empty...'; + p mt '_wishlist_noresults'; end; return $self->htmlFooter; } p class => 'browseopts'; a $f->{f} == $_ ? (class => 'optselected') : (), href => "/u$uid/wish?f=$_", - $_ == -1 ? 'All priorities' : ucfirst $self->{wishlist_status}[$_] - for (-1..$#{$self->{wishlist_status}}); + $_ == -1 ? mt '_wishlist_prio_all' : mt "_wish_$_" + for (-1, @{$self->{wishlist_status}}); end; end; @@ -157,9 +157,9 @@ sub wishlist { pageurl => "/u$uid/wish?f=$f->{f};o=$f->{o};s=$f->{s}", sorturl => "/u$uid/wish?f=$f->{f}", header => [ - [ Title => 'title' ], - [ Priority => 'wstat' ], - [ Added => 'added' ], + [ mt('_wishlist_col_title') => 'title' ], + [ mt('_wishlist_col_prio') => 'wstat' ], + [ mt('_wishlist_col_added') => 'added' ], ], row => sub { my($s, $n, $i) = @_; @@ -169,20 +169,20 @@ sub wishlist { if $own; a href => "/v$i->{vid}", title => $i->{original}||$i->{title}, ' '.shorten $i->{title}, 70; end; - td class => 'tc2', ucfirst $self->{wishlist_status}[$i->{wstat}]; - td class => 'tc3', date $i->{added}, 'compact'; + td class => 'tc2', mt "_wish_$i->{wstat}"; + td class => 'tc3', $self->{l10n}->date($i->{added}, 'compact'); end; }, $own ? (footer => sub { Tr; td colspan => 3; Select name => 'batchedit', id => 'batchedit'; - option '-- with selected --'; - optgroup label => 'Change priority'; - option value => $_, $self->{wishlist_status}[$_] - for (0..$#{$self->{wishlist_status}}); + option mt '_wishlist_select'; + optgroup label => mt '_wishlist_changeprio'; + option value => $_, mt "_wish_$_" + for (@{$self->{wishlist_status}}); end; - option value => -1, 'remove from wishlist'; + option value => -1, mt '_wishlist_remove'; end; end; end; @@ -198,7 +198,7 @@ sub vnlist { my $own = $self->authInfo->{id} && $self->authInfo->{id} == $uid; my $u = $self->dbUserGet(uid => $uid)->[0]; - return 404 if !$u || !$own && !$u->{show_list}; + return 404 if !$u || !$own && !($u->{show_list} || $self->authCan('usermod')); my $f = $self->formValidate( { name => 'p', required => 0, default => 1, template => 'int' }, @@ -234,7 +234,7 @@ sub vnlist { $f->{c} ne 'all' ? (char => $f->{c}) : (), ); - my $title = $own ? 'My visual novel list' : "\u$u->{username}'s visual novel list"; + my $title = $own ? mt '_rlist_title_my' : mt '_rlist_title_other', $u->{username}; $self->htmlHeader(title => $title, noindex => 1); $self->htmlMainTabs('u', $u, 'list'); @@ -260,9 +260,9 @@ sub vnlist { } end; p class => 'browseopts'; - a href => $url->(v => 0), 0 == $f->{v} ? (class => 'optselected') : (), 'All'; - a href => $url->(v => 1), 1 == $f->{v} ? (class => 'optselected') : (), 'Only voted'; - a href => $url->(v => -1), -1 == $f->{v} ? (class => 'optselected') : (), 'Hide voted'; + a href => $url->(v => 0), 0 == $f->{v} ? (class => 'optselected') : (), mt '_rlist_voted_all'; + a href => $url->(v => 1), 1 == $f->{v} ? (class => 'optselected') : (), mt '_rlist_voted_only'; + a href => $url->(v => -1), -1 == $f->{v} ? (class => 'optselected') : (), mt '_rlist_voted_none'; end; end; @@ -284,12 +284,11 @@ sub _vnlist_browse { sorturl => $url->(), pageurl => $url->('page'), header => [ - [ Title => 'title', 3 ], - sub { td class => 'tc2', id => 'relhidall'; lit '<i>▸</i>Releases*'; end; }, - [ Vote => 'vote' ], + [ mt('_rlist_col_title') => 'title', 3 ], + sub { td class => 'tc2', id => 'relhidall'; lit '<i>▸</i>'.mt('_rlist_col_releases').'*'; end; }, + [ mt('_rlist_col_vote') => 'vote' ], ], row => sub { - my($s, $n, $i) = @_; Tr $n % 2 == 0 ? (class => 'odd') : (); td class => 'tc1', colspan => 3; @@ -312,11 +311,11 @@ sub _vnlist_browse { td class => 'tc1'.($own ? ' own' : ''); input type => 'checkbox', name => 'sel', value => $_->{rid} if $own; - lit datestr $_->{released}; + lit $self->{l10n}->datestr($_->{released}); end; td class => 'tc2'; - cssicon "lang $_", $self->{languages}{$_} for @{$_->{languages}}; - cssicon substr(lc $self->{release_types}[$_->{type}], 0, 3), $self->{release_types}[$_->{type}].' release'; + cssicon "lang $_", mt "_lang_$_" for @{$_->{languages}}; + cssicon "rt$_->{type}", mt "_rtype_$_->{type}"; end; td class => 'tc3'; a href => "/r$_->{rid}", title => $_->{original}||$_->{title}, shorten $_->{title}, 50; @@ -332,19 +331,19 @@ sub _vnlist_browse { Tr; td class => 'tc1', colspan => 3; Select id => 'batchedit', name => 'batchedit'; - option '- with selected -'; - optgroup label => 'Change release status'; + option mt '_rlist_selection'; + optgroup label => mt '_rlist_changerel'; option value => "r$_", $self->{vn_rstat}[$_] for (0..$#{$self->{vn_rstat}}); end; - optgroup label => 'Change play status'; + optgroup label => mt '_rlist_changeplay'; option value => "v$_", $self->{vn_vstat}[$_] for (0..$#{$self->{vn_vstat}}); end; - option value => 'del', 'remove from list'; + option value => 'del', mt '_rlist_del'; end; end; - td class => 'tc2', colspan => 2, '* Obtained/finished/total'; + td class => 'tc2', colspan => 2, mt '_rlist_releasenote'; end; }) : (), ); diff --git a/lib/VNDB/Handler/Users.pm b/lib/VNDB/Handler/Users.pm index 2e8ac8ee..8575771e 100644 --- a/lib/VNDB/Handler/Users.pm +++ b/lib/VNDB/Handler/Users.pm @@ -29,20 +29,17 @@ sub userpage { my $votes = $u->{c_votes} && $self->dbVoteStats(uid => $uid); - $self->htmlHeader(title => ucfirst($u->{username})."'s profile"); + my $title = mt '_userpage_title', $u->{username}; + $self->htmlHeader(title => $title); $self->htmlMainTabs('u', $u); div class => 'mainbox userpage'; - h1 ucfirst($u->{username})."'s profile"; + h1 $title; table; - Tr; - td class => 'key', ' '; - td ' '; - end; my $i = 0; Tr ++$i % 2 ? (class => 'odd') : (); - td 'Username'; + td class => 'key', mt '_userpage_username'; td; txt ucfirst($u->{username}).' ('; a href => "/u$uid", "u$uid"; @@ -51,12 +48,12 @@ sub userpage { end; Tr ++$i % 2 ? (class => 'odd') : (); - td 'Registered'; - td date $u->{registered}; + td mt '_userpage_registered'; + td $self->{l10n}->date($u->{registered}); end; Tr ++$i % 2 ? (class => 'odd') : (); - td 'Edits'; + td mt '_userpage_edits'; td; if($u->{c_changes}) { a href => "/u$uid/hist", $u->{c_changes}; @@ -67,18 +64,17 @@ sub userpage { end; Tr ++$i % 2 ? (class => 'odd') : (); - td 'Votes'; + td mt '_userpage_votes'; td; if(!$u->{show_list}) { - txt 'hidden'; + txt mt '_userpage_hidden'; } elsif($votes) { my($total, $count) = (0, 0); for (1..@$votes) { $total += $_*$votes->[$_-1]; $count += $votes->[$_-1]; } - a href => "/u$uid/list?v=1", $count; - txt sprintf ' (%.2f average)', $total/$count; + lit mt '_userpage_votes_item', "/u$uid/list?v=1", $count, sprintf '%.2f', $total/$count; } else { txt '-'; } @@ -86,27 +82,23 @@ sub userpage { end; Tr ++$i % 2 ? (class => 'odd') : (); - td 'Tags'; - td !$u->{c_tags} ? '-' : sprintf '%d votes on %d distinct tags and %d visual novels', - $u->{c_tags}, $u->{tagcount}, $u->{tagvncount}; + td mt '_userpage_tags'; + td !$u->{c_tags} ? '-' : mt '_userpage_tags_item', $u->{c_tags}, $u->{tagcount}, $u->{tagvncount}; end; Tr ++$i % 2 ? (class => 'odd') : (); - td 'List stats'; - td !$u->{show_list} ? 'hidden' : - sprintf '%d release%s of %d visual novel%s', - $u->{releasecount}, $u->{releasecount} != 1 ? 's' : '', - $u->{vncount}, $u->{vncount} != 1 ? 's' : ''; + td mt '_userpage_list'; + td !$u->{show_list} ? mt('_userpage_hidden') : + mt('_userpage_list_item', $u->{releasecount}, $u->{vncount}); end; Tr ++$i % 2 ? (class => 'odd') : (); - td 'Forum stats'; + td mt '_userpage_forum'; td; - txt sprintf '%d post%s, %d new thread%s. ', - $u->{postcount}, $u->{postcount} != 1 ? 's' : '', - $u->{threadcount}, $u->{threadcount} != 1 ? 's' : ''; + lit mt '_userpage_forum_item',$u->{postcount}, $u->{threadcount}; if($u->{postcount}) { - a href => "/u$uid/posts"; lit 'Browse posts »'; end; + txt ' '; + a href => "/u$uid/posts"; lit mt('_userpage_forum_browse').' »'; end; } end; end; @@ -115,7 +107,7 @@ sub userpage { if($u->{show_list} && $votes) { div class => 'mainbox'; - h1 'Vote statistics'; + h1 mt '_userpage_votestats'; $self->htmlVoteStats(u => $u, $votes); end; } @@ -123,7 +115,7 @@ sub userpage { if($u->{c_changes}) { my $list = $self->dbRevisionGet(what => 'item user', uid => $uid, results => 5, hidden => 1); h1 class => 'boxtitle'; - a href => "/u$uid/hist", 'Recent changes'; + a href => "/u$uid/hist", mt '_userpage_changes'; end; $self->htmlHistory($list, { p => 1 }, 0, "/u$uid/hist"); } @@ -149,12 +141,12 @@ sub login { $frm->{_err} = [ 'login_failed' ] if !$frm->{_err}; } - $self->htmlHeader(title => 'Login', noindex => 1); - $self->htmlForm({ frm => $frm, action => '/u/login' }, Login => [ - [ input => name => 'Username', short => 'usrname' ], - [ static => content => '<a href="/u/register">No account yet?</a>' ], - [ passwd => name => 'Password', short => 'usrpass' ], - [ static => content => '<a href="/u/newpass">Forgot your password?</a>' ], + $self->htmlHeader(noindex => 1, title => mt '_login_title'); + $self->htmlForm({ frm => $frm, action => '/u/login' }, login => [ mt('_login_title'), + [ input => short => 'usrname', name => mt '_login_username' ], + [ static => content => '<a href="/u/register">'.mt('_login_register').'</a>' ], + [ passwd => short => 'usrpass', name => mt '_login_password' ], + [ static => content => '<a href="/u/newpass">'.mt('_login_forgotpass').'</a>' ], ]); $self->htmlFooter; } @@ -185,38 +177,22 @@ sub newpass { my %o; ($o{passwd}, $o{salt}) = $self->authPreparePass($pass); $self->dbUserEdit($u->{id}, %o); - my $body = <<'__'; -Hello %s, - -Your password has been reset, you can now login at http://vndb.org/ with the -following information: - -Username: %1$s -Password: %s - -Now don't forget your password again! :-) - -vndb.org -__ - $self->mail( - sprintf($body, $u->{username}, $pass), + $self->mail(mt('_newpass_mail_body', $u->{username}, $pass), To => $u->{mail}, From => 'VNDB <noreply@vndb.org>', - Subject => 'New password for '.$u->{username} + Subject => mt('_newpass_mail_subject', $u->{username}), ); return $self->resRedirect('/u/newpass/sent', 'post'); } } - $self->htmlHeader(title => 'Forgot Password', noindex => 1); + $self->htmlHeader(title => mt('_newpass_title'), noindex => 1); div class => 'mainbox'; - h1 'Forgot Password'; - p "Forgot your password and can't login to VNDB anymore?\n" - ."Don't worry! Just give us the email address you used to register on VNDB,\n" - ."and we'll send you a new password within a few minutes!"; + h1 mt '_newpass_title'; + p mt '_newpass_msg'; end; - $self->htmlForm({ frm => $frm, action => '/u/newpass' }, 'Reset Password' => [ - [ input => name => 'Email', short => 'mail' ], + $self->htmlForm({ frm => $frm, action => '/u/newpass' }, newpass => [ mt('_newpass_reset_title'), + [ input => short => 'mail', name => mt '_newpass_mail' ], ]); $self->htmlFooter; } @@ -225,15 +201,13 @@ __ sub newpass_sent { my $self = shift; return $self->resRedirect('/') if $self->authInfo->{id}; - $self->htmlHeader(title => 'New Password', noindex => 1); + $self->htmlHeader(title => mt('_newpass_sent_title'), noindex => 1); div class => 'mainbox'; - h1 'New Password'; + h1 mt '_newpass_sent_title'; div class => 'notice'; - h2 'Password Reset'; + h2 mt '_newpass_sent_subtitle'; p; - txt "Your password has been reset and your new password should reach your mailbox in a few minutes.\n" - ."You can always change your password again after logging in.\n\n"; - lit '<a href="/u/login">Login</a> - <a href="/">Home</a>'; + lit mt '_newpass_sent_msg'; end; end; end; @@ -243,7 +217,7 @@ sub newpass_sent { sub register { my $self = shift; - return $self->resRedirect('/') if $self->authInfo->{id}; + #return $self->resRedirect('/') if $self->authInfo->{id}; my $frm; if($self->reqMethod eq 'POST') { @@ -265,33 +239,22 @@ sub register { } } - $self->htmlHeader(title => 'Create an Account', noindex => 1); + $self->htmlHeader(title => mt('_register_title'), noindex => 1); div class => 'mainbox'; - h1 'Create an Account'; - h2 'Why should I register?'; - p 'Creating an account is completely painless, the only thing we need to know is your prefered username ' - .'and a password. You can just use any email address that isn\'t yours, as we don\'t even confirm ' - .'that the address you gave us is really yours. Keep in mind, however, that you would probably ' - .'want to remember your password if you do choose to give us an invalid email address...'; - - p 'Anyway, having an account here has a few advantages over being just a regular visitor:'; - ul; - li 'You can contribute to the database by editing any entries and adding new ones'; - li 'Keep track of all visual novels and releases you have, you\'d like to play, are playing, or have finished playing'; - li 'Vote on the visual novels you liked or disliked'; - li 'Contribute to the discussions on the boards'; - li 'And boast about the fact that you have an account on the best visual novel database in the world!'; + h1 mt '_register_title'; + h2 mt '_register_why'; + p; + lit mt '_register_why_msg'; end; end; - $self->htmlForm({ frm => $frm, action => '/u/register' }, 'New Account' => [ - [ input => short => 'usrname', name => 'Username' ], - [ static => content => 'Requested username. Must be lowercase and can only consist of alphanumeric characters.' ], - [ input => short => 'mail', name => 'Email' ], - [ static => content => 'Your email address will only be used in case you lose your password. We will never send' - .' spam or newsletters unless you explicitly ask us for it.<br /><br />' ], - [ passwd => short => 'usrpass', name => 'Password' ], - [ passwd => short => 'usrpass2', name => 'Confirm pass.' ], + $self->htmlForm({ frm => $frm, action => '/u/register' }, register => [ mt('_register_form_title'), + [ input => short => 'usrname', name => mt '_register_username' ], + [ static => content => mt '_register_username_msg' ], + [ input => short => 'mail', name => mt '_register_mail' ], + [ static => content => mt('_register_mail_msg').'<br /><br />' ], + [ passwd => short => 'usrpass', name => mt('_register_password') ], + [ passwd => short => 'usrpass2', name => mt('_register_confirm') ], ]); $self->htmlFooter; } @@ -314,6 +277,7 @@ sub edit { $self->authCan('usermod') ? ( { name => 'usrname', template => 'pname', minlength => 2, maxlength => 15 }, { name => 'rank', enum => [ 1..$#{$self->{user_ranks}} ] }, + { name => 'ign_votes', required => 0, default => 0 }, ) : (), { name => 'mail', template => 'mail' }, { name => 'usrpass', required => 0, minlength => 4, maxlength => 64, template => 'asciiprint' }, @@ -335,6 +299,7 @@ sub edit { ($o{passwd}, $o{salt}) = $self->authPreparePass($frm->{usrpass}) if $frm->{usrpass}; $o{show_list} = $frm->{flags_list} ? 1 : 0; $o{show_nsfw} = $frm->{flags_nsfw} ? 1 : 0; + $o{ign_votes} = $frm->{ign_votes} ? 1 : 0 if $self->authCan('usermod'); $self->dbUserEdit($uid, %o); $self->dbSessionDel($uid) if $frm->{usrpass}; return $self->resRedirect("/u$uid/edit?d=1", 'post') if $uid != $self->authInfo->{id} || !$frm->{usrpass}; @@ -347,43 +312,42 @@ sub edit { $frm->{$_} ||= $u->{$_} for(qw|rank mail skin customcss|); $frm->{flags_list} = $u->{show_list} if !defined $frm->{flags_list}; $frm->{flags_nsfw} = $u->{show_nsfw} if !defined $frm->{flags_nsfw}; + $frm->{ign_votes} = $u->{ign_votes} if !defined $frm->{ign_votes}; # create the page - my $title = $self->authInfo->{id} != $uid ? "Edit $u->{username}'s Account" : 'My Account'; - $self->htmlHeader(title => $title, noindex => 1); + $self->htmlHeader(title => mt('_usere_title'), noindex => 1); $self->htmlMainTabs('u', $u, 'edit'); if($self->reqParam('d')) { div class => 'mainbox'; - h1 'Settings saved'; + h1 mt '_usere_saved_title'; div class => 'notice'; - p 'Settings successfully saved.'; + p mt '_usere_saved_msg'; end; end } - $self->htmlForm({ frm => $frm, action => "/u$uid/edit" }, $title => [ - [ part => title => 'General Info' ], + $self->htmlForm({ frm => $frm, action => "/u$uid/edit" }, useredit => [ mt('_usere_title'), + [ part => title => mt '_usere_geninfo' ], $self->authCan('usermod') ? ( - [ input => short => 'usrname', name => 'Username' ], - [ select => short => 'rank', name => 'Rank', options => [ - map [ $_, $self->{user_ranks}[$_][0] ], 1..$#{$self->{user_ranks}} ] ], + [ input => short => 'usrname', name => mt('_usere_username') ], + [ select => short => 'rank', name => mt('_usere_rank'), options => [ + map [ $_, mt '_urank_'.$_ ], 1..$#{$self->{user_ranks}} ] ], + [ check => short => 'ign_votes', name => mt '_usere_ignvotes' ], ) : ( - [ static => label => 'Username', content => $frm->{usrname} ], + [ static => label => mt('_usere_username'), content => $frm->{usrname} ], ), - [ input => short => 'mail', name => 'Email' ], - - [ part => title => 'Change Password' ], - [ static => content => 'Leave blank to keep your current password' ], - [ passwd => short => 'usrpass', name => 'Password' ], - [ passwd => short => 'usrpass2', name => 'Confirm pass.' ], - - [ part => title => 'Options' ], - [ check => short => 'flags_list', name => - qq|Allow other people to see my visual novel list (<a href="/u$uid/list">/u$uid/list</a>) |. - qq|and wishlist (<a href="/u$uid/wish">/u$uid/wish</a>)| ], - [ check => short => 'flags_nsfw', name => 'Disable warnings for images that are not safe for work.' ], - [ select => short => 'skin', name => 'Prefered skin', width => 300, options => [ + [ input => short => 'mail', name => mt '_usere_mail' ], + + [ part => title => mt '_usere_changepass' ], + [ static => content => mt '_usere_changepass_msg' ], + [ passwd => short => 'usrpass', name => mt '_usere_password' ], + [ passwd => short => 'usrpass2', name => mt '_usere_confirm' ], + + [ part => title => mt '_usere_options' ], + [ check => short => 'flags_list', name => mt '_usere_flist' ], + [ check => short => 'flags_nsfw', name => mt '_usere_fnsfw' ], + [ select => short => 'skin', name => mt('_usere_skin'), width => 300, options => [ map [ $_ eq $self->{skin_default} ? '' : $_, $self->{skins}{$_}.($self->debug?" [$_]":'') ], sort { $self->{skins}{$a} cmp $self->{skins}{$b} } keys %{$self->{skins}} ] ], - [ textarea => short => 'customcss', name => 'Additional <a href="http://en.wikipedia.org/wiki/Cascading_Style_Sheets">CSS</a>' ], + [ textarea => short => 'customcss', name => mt '_usere_css' ], ]); $self->htmlFooter; } @@ -402,12 +366,13 @@ sub posts { my($posts, $np) = $self->dbPostGet(uid => $uid, hide => 1, what => 'thread', page => $f->{p}, order => 'tp.date DESC'); - $self->htmlHeader(title => "Posts made by $u->{username}", noindex => 1); + my $title = mt '_uposts_title', $u->{username}; + $self->htmlHeader(title => $title, noindex => 1); $self->htmlMainTabs(u => $u, 'posts'); div class => 'mainbox'; - h1 "Posts made by $u->{username}"; + h1 $title; if(!@$posts) { - p "\u$u->{username} hasn't made any posts yet."; + p mt '_uposts_noresults', $u->{username}; } end; @@ -420,15 +385,15 @@ sub posts { header => [ [ '' ], [ '' ], - [ 'Date' ], - sub { td; a href => '#', id => 'history_comments', 'expand'; txt 'Title'; end; } + [ mt '_uposts_col_date' ], + sub { td; a href => '#', id => 'history_comments', 'expand'; txt mt '_uposts_col_title'; end; } ], row => sub { my($s, $n, $l) = @_; Tr $n % 2 ? (class => 'odd') : (); td class => 'tc1'; a href => "/t$l->{tid}.$l->{num}", 't'.$l->{tid}; end; td class => 'tc2'; a href => "/t$l->{tid}.$l->{num}", '.'.$l->{num}; end; - td class => 'tc3', date $l->{date}; + td class => 'tc3', $self->{l10n}->date($l->{date}); td class => 'tc4'; a href => "/t$l->{tid}.$l->{num}", $l->{title}; end; end; Tr class => $n % 2 ? 'editsum odd hidden' : 'editsum hidden'; @@ -446,6 +411,8 @@ sub delete { my($self, $uid, $act) = @_; return $self->htmlDenied if !$self->authCan('usermod'); + # rarely used admin function, won't really need translating + # confirm if(!$act) { my $u = $self->dbUserGet(uid => $uid)->[0]; @@ -492,16 +459,16 @@ sub list { ); return 404 if $f->{_err}; - $self->htmlHeader(title => 'Browse users'); + $self->htmlHeader(noindex => 1, title => mt '_ulist_title'); div class => 'mainbox'; - h1 'Browse users'; + h1 mt '_ulist_title'; form action => '/u/all', 'accept-charset' => 'UTF-8', method => 'get'; $self->htmlSearchBox('u', $f->{q}); end; p class => 'browseopts'; for ('all', 'a'..'z', 0) { - a href => "/u/$_", $_ eq $char ? (class => 'optselected') : (), $_ ? uc $_ : '#'; + a href => "/u/$_", $_ eq $char ? (class => 'optselected') : (), $_ eq 'all' ? mt('_char_all') : $_ ? uc $_ : '#'; } end; end; @@ -522,11 +489,11 @@ sub list { pageurl => "/u/$char?o=$f->{o};s=$f->{s};q=$f->{q}", sorturl => "/u/$char?q=$f->{q}", header => [ - [ 'Username', 'username' ], - [ 'Registered', 'registered' ], - [ 'Votes', 'votes' ], - [ 'Edits', 'changes' ], - [ 'Tags', 'tags' ], + [ mt('_ulist_col_username'), 'username' ], + [ mt('_ulist_col_registered'), 'registered' ], + [ mt('_ulist_col_votes'), 'votes' ], + [ mt('_ulist_col_edits'), 'changes' ], + [ mt('_ulist_col_tags'), 'tags' ], ], row => sub { my($s, $n, $l) = @_; @@ -534,9 +501,9 @@ sub list { td class => 'tc1'; a href => '/u'.$l->{id}, $l->{username}; end; - td class => 'tc2', date $l->{registered}; - td class => 'tc3'; - lit !$l->{show_list} ? '-' : !$l->{c_votes} ? 0 : + td class => 'tc2', $self->{l10n}->date($l->{registered}); + td class => 'tc3'.(!$l->{show_list} && $self->authCan('usermod') ? ' linethrough' : ''); + lit !$l->{show_list} && !$self->authCan('usermod') ? '-' : !$l->{c_votes} ? 0 : qq|<a href="/u$l->{id}/list">$l->{c_votes}</a>|; end; td class => 'tc4'; diff --git a/lib/VNDB/Handler/VNBrowse.pm b/lib/VNDB/Handler/VNBrowse.pm index 5a22bccc..2a6d6cd7 100644 --- a/lib/VNDB/Handler/VNBrowse.pm +++ b/lib/VNDB/Handler/VNBrowse.pm @@ -21,8 +21,8 @@ sub list { { name => 'p', required => 0, default => 1, template => 'int' }, { name => 'q', required => 0, default => '' }, { name => 'sq', required => 0, default => '' }, - { name => 'ln', required => 0, multi => 1, enum => [ keys %{$self->{languages}} ], default => '' }, - { name => 'pl', required => 0, multi => 1, enum => [ keys %{$self->{platforms}} ], default => '' }, + { name => 'ln', required => 0, multi => 1, enum => $self->{languages}, default => '' }, + { name => 'pl', required => 0, multi => 1, enum => $self->{platforms}, default => '' }, { name => 'ti', required => 0, default => '', maxlength => 200 }, { name => 'te', required => 0, default => '', maxlength => 200 }, { name => 'sp', required => 0, default => $self->reqCookie('tagspoil') =~ /^([0-2])$/ ? $1 : 1, enum => [0..2] }, @@ -34,9 +34,9 @@ sub list { return $self->resRedirect('/'.$1.$2.(!$3 ? '' : $1 eq 'd' ? '#'.$3 : '.'.$3), 'temp') if $f->{q} =~ /^([gvrptud])([0-9]+)(?:\.([0-9]+))?$/; - # for URL compatibilty with older versions + # for URL compatibilty with older versions (ugly hack to get English strings) my @lang; - $f->{q} =~ s/\s*$self->{languages}{$_}\s*//&&push @lang, $_ for (keys %{$self->{languages}}); + $f->{q} =~ s/\s*$VNDB::L10N::en::Lexicon{"_lang_$_"}\s*//&&push @lang, $_ for (@{$self->{languages}}); $f->{ln} = $f->{ln}[0] ? [ @{$f->{ln}}, @lang ] : \@lang; } @@ -70,7 +70,7 @@ sub list { $self->resRedirect('/v'.$list->[0]{id}, 'temp') if $f->{q} && @$list == 1; - $self->htmlHeader(title => 'Browse visual novels', search => $f->{q}, js => 'forms'); + $self->htmlHeader(title => mt('_vnbrowse_title'), search => $f->{q}, js => 'forms'); _filters($self, $f, $char, \@ignored); my $url = "/v/$char?q=$f->{q};ti=$f->{ti};te=$f->{te}"; @@ -84,12 +84,12 @@ sub list { pageurl => "$url;o=$f->{o};s=$f->{s}", sorturl => $url, header => [ - @ti ? [ 'Score', 'tagscore', undef, 'tc_s' ] : (), - [ 'Title', 'title', undef, @ti ? 'tc_t' : 'tc1' ], - [ '', 0, undef, 'tc2' ], - [ '', 0, undef, 'tc3' ], - [ 'Released', 'rel', undef, 'tc4' ], - [ 'Popularity', 'pop', undef, 'tc5' ], + @ti ? [ mt('_vnbrowse_col_score'), 'tagscore', undef, 'tc_s' ] : (), + [ mt('_vnbrowse_col_title'), 'title', undef, @ti ? 'tc_t' : 'tc1' ], + [ '', 0, undef, 'tc2' ], + [ '', 0, undef, 'tc3' ], + [ mt('_vnbrowse_col_released'), 'rel', undef, 'tc4' ], + [ mt('_vnbrowse_col_popularity'), 'pop', undef, 'tc5' ], ], row => sub { my($s, $n, $l) = @_; @@ -103,15 +103,15 @@ sub list { a href => '/v'.$l->{id}, title => $l->{original}||$l->{title}, shorten $l->{title}, 100; end; td class => 'tc2'; - $_ ne 'oth' && cssicon $_, $self->{platforms}{$_} + $_ ne 'oth' && cssicon $_, mt "_plat_$_" for (sort split /\//, $l->{c_platforms}); end; td class => 'tc3'; - cssicon "lang $_", $self->{languages}{$_} + cssicon "lang $_", mt "_lang_$_" for (reverse sort split /\//, $l->{c_languages}); end; td class => 'tc4'; - lit monthstr $l->{c_released}; + lit $self->{l10n}->datestr($l->{c_released}); end; td class => 'tc5', sprintf '%.2f', $l->{c_popularity}*100; end; @@ -126,69 +126,72 @@ sub _filters { form action => '/v/all', 'accept-charset' => 'UTF-8', method => 'get'; div class => 'mainbox'; - h1 'Browse visual novels'; + h1 mt '_vnbrowse_title'; $self->htmlSearchBox('v', $f->{q}); p class => 'browseopts'; for ('all', 'a'..'z', 0) { - a href => "/v/$_", $_ eq $char ? (class => 'optselected') : (), $_ ? uc $_ : '#'; + a href => "/v/$_", $_ eq $char ? (class => 'optselected') : (), $_ eq 'all' ? mt('_char_all') : $_ ? uc $_ : '#'; } end; if(@$ign) { div class => 'warning'; - h2 'The following tags were ignored:'; + h2 mt '_vnbrowse_tagign_title'; ul; - li $_->[0].' ('.($_->[1]?"can't filter on meta tags":"no such tag found").')' for @$ign; + li $_->[0].' ('.mt('_vnbrowse_tagign_'.($_->[1]?'meta':'notfound')).')' for @$ign; end; end; } a id => 'advselect', href => '#'; - lit '<i>▸</i> advanced search'; + lit '<i>▸</i> '.mt('_vnbrowse_advsearch'); end; div id => 'advoptions', class => 'hidden vnoptions'; h2; - lit 'Tag filters <b>(boolean and, selecting more gives less results)</b>'; + txt mt '_vnbrowse_tags'; + b ' ('.mt('_vnbrowse_booland').')'; end; table class => 'formtable', style => 'margin-left: 0'; - $self->htmlFormPart($f, [ input => short => 'ti', name => 'Tags to include', width => 350 ]); - $self->htmlFormPart($f, [ radio => short => 'sp', name => '', options => [[0,'Hide spoilers'],[1,'Show minor spoilers'],[2,'Show major spoilers']]]); - $self->htmlFormPart($f, [ input => short => 'te', name => 'Tags to exclude', width => 350 ]); + $self->htmlFormPart($f, [ input => short => 'ti', name => mt('_vnbrowse_taginc'), width => 350 ]); + $self->htmlFormPart($f, [ radio => short => 'sp', name => '', options => [map [$_, mt '_vnbrowse_spoil'.$_], 0..2]]); + $self->htmlFormPart($f, [ input => short => 'te', name => mt('_vnbrowse_tagexc'), width => 350 ]); end; h2; - lit 'Languages <b>(boolean or, selecting more gives more results)</b>'; + txt mt '_vnbrowse_lang'; + b ' ('.mt('_vnbrowse_boolor').')'; end; for my $i (sort @{$self->dbLanguages}) { span; input type => 'checkbox', name => 'ln', value => $i, id => "lang_$i", (scalar grep $_ eq $i, @{$f->{ln}}) ? (checked => 'checked') : (); label for => "lang_$i"; - cssicon "lang $i", $self->{languages}{$i}; - txt $self->{languages}{$i}; + cssicon "lang $i", mt "_lang_$i"; + txt mt "_lang_$i"; end; end; } h2; - lit 'Platforms <b>(boolean or, selecting more gives more results)</b>'; + txt mt '_vnbrowse_plat'; + b ' ('.mt('_vnbrowse_boolor').')'; end; - for my $i (sort keys %{$self->{platforms}}) { + for my $i (sort @{$self->{platforms}}) { next if $i eq 'oth'; span; input type => 'checkbox', id => "plat_$i", name => 'pl', value => $i, (scalar grep $_ eq $i, @{$f->{pl}}) ? (checked => 'checked') : (); label for => "plat_$i"; - cssicon $i, $self->{platforms}{$i}; - txt $self->{platforms}{$i}; + cssicon $i, mt "_plat_$i"; + txt mt "_plat_$i"; end; end; } div style => 'text-align: center; clear: left;'; - input type => 'submit', value => 'Apply', class => 'submit'; - input type => 'reset', value => 'Clear', class => 'submit', onclick => 'location.href="/v/all"'; + input type => 'submit', value => mt('_vnbrowse_apply'), class => 'submit'; + input type => 'reset', value => mt('_vnbrowse_clear'), class => 'submit', onclick => 'location.href="/v/all"'; end; end; end; diff --git a/lib/VNDB/Handler/VNEdit.pm b/lib/VNDB/Handler/VNEdit.pm index feca72d6..fcbe6753 100644 --- a/lib/VNDB/Handler/VNEdit.pm +++ b/lib/VNDB/Handler/VNEdit.pm @@ -4,6 +4,7 @@ package VNDB::Handler::VNEdit; use strict; use warnings; use YAWF ':html', ':xml'; +use VNDB::Func; YAWF::register( @@ -37,12 +38,12 @@ sub edit { { name => 'title', maxlength => 250 }, { name => 'original', required => 0, maxlength => 250, default => '' }, { name => 'alias', required => 0, maxlength => 500, default => '' }, - { name => 'desc', maxlength => 10240 }, - { name => 'length', required => 0, default => 0, enum => [ 0..$#{$self->{vn_lengths}} ] }, + { name => 'desc', required => 0, default => '', maxlength => 10240 }, + { name => 'length', required => 0, default => 0, enum => $self->{vn_lengths} }, { name => 'l_wp', required => 0, default => '', maxlength => 150 }, { name => 'l_encubed', required => 0, default => '', maxlength => 100 }, { name => 'l_renai', required => 0, default => '', maxlength => 100 }, - { name => 'l_vnn', required => 0, default => 0, template => 'int' }, + { name => 'l_vnn', required => 0, default => $b4{l_vnn}, template => 'int' }, { name => 'anime', required => 0, default => '' }, { name => 'img_nsfw', required => 0, default => 0 }, { name => 'relations', required => 0, default => '', maxlength => 5000 }, @@ -95,9 +96,10 @@ sub edit { !exists $frm->{$_} && ($frm->{$_} = $b4{$_}) for (keys %b4); $frm->{editsum} = sprintf 'Reverted to revision v%d.%d', $vid, $rev if $rev && !defined $frm->{editsum}; - $self->htmlHeader(js => 'forms', title => $vid ? "Edit $v->{title}" : 'Add a new visual novel', noindex => 1); + my $title = $vid ? mt('_vnedit_title_edit', $v->{title}) : mt '_vnedit_title_add'; + $self->htmlHeader(js => 'forms', title => $title, noindex => 1); $self->htmlMainTabs('v', $v, 'edit') if $vid; - $self->htmlEditMessage('v', $v); + $self->htmlEditMessage('v', $v, $title); _form($self, $v, $frm); $self->htmlFooter; } @@ -139,72 +141,59 @@ sub _form { my($self, $v, $frm) = @_; my $r = $v ? $self->dbReleaseGet(vid => $v->{id}) : []; $self->htmlForm({ frm => $frm, action => $v ? "/v$v->{id}/edit" : '/v/new', editsum => 1, upload => 1 }, - 'General info' => [ - [ input => short => 'title', name => 'Title (romaji)' ], - [ input => short => 'original', name => 'Original title' ], - [ static => content => 'The original title of this visual novel, leave blank if it already is in the Latin alphabet.' ], - [ textarea => short => 'alias', name => 'Aliases', rows => 4 ], - [ static => content => q| - Comma seperated list of alternative titles or abbreviations. Can include both official - (japanese/english) titles and unofficial titles used around net.<br /> - <b>Titles that are listed in the releases do not have to be added here.</b> - |], - [ textarea => short => 'desc', name => 'Description', rows => 10 ], - [ static => content => q| - Short description of the main story. Please do not include spoilers, and don't forget to list - the source in case you didn't write the description yourself. (formatting codes are allowed) - |], - [ select => short => 'length', name => 'Length', width => 300, options => - [ map [ $_ => $self->{vn_lengths}[$_][0].($_ ? " ($self->{vn_lengths}[$_][2])" : '') ], 0..$#{$self->{vn_lengths}} ] ], - - [ input => short => 'l_wp', name => 'External links', pre => 'http://en.wikipedia.org/wiki/' ], + vn_geninfo => [ mt('_vnedit_geninfo'), + [ input => short => 'title', name => mt '_vnedit_frm_title' ], + [ input => short => 'original', name => mt '_vnedit_original' ], + [ static => content => mt '_vnedit_original_msg' ], + [ textarea => short => 'alias', name => mt('_vnedit_alias'), rows => 4 ], + [ static => content => mt '_vnedit_alias_msg' ], + [ textarea => short => 'desc', name => mt('_vnedit_desc').'<br /><b class="standout">'.mt('_inenglish').'</b>', rows => 10 ], + [ static => content => mt '_vnedit_desc_msg' ], + [ select => short => 'length', name => mt('_vnedit_length'), width => 300, options => + [ map [ $_ => mt '_vnlength_'.$_, 2 ], @{$self->{vn_lengths}} ] ], + + [ input => short => 'l_wp', name => mt('_vnedit_links'), pre => 'http://en.wikipedia.org/wiki/' ], [ input => short => 'l_encubed', pre => 'http://novelnews.net/tag/', post => '/' ], [ input => short => 'l_renai', pre => 'http://renai.us/game/', post => '.shtml' ], - [ input => short => 'l_vnn', pre => 'http://visual-novels.net/vn/index.php?option=com_content&task=view&id=', width => 40 ], - - [ input => short => 'anime', name => 'Anime' ], - [ static => content => q| - Whitespace seperated list of <a href="http://anidb.net/">AniDB</a> anime IDs. - E.g. "1015 3348" will add <a href="http://anidb.net/a1015">Shingetsutan Tsukihime</a> - and <a href="http://anidb.net/a3348">Fate/stay night</a> as related anime.<br /> - <b>Note:</b> It can take a few minutes for the anime titles to appear on the VN page. - |], + + [ input => short => 'anime', name => mt '_vnedit_anime' ], + [ static => content => mt '_vnedit_anime_msg' ], ], - 'Image' => [ + vn_img => [ mt('_vnedit_image'), [ static => nolabel => 1, content => sub { div class => 'img'; - p 'No image uploaded yet' if !$v || !$v->{image}; - p '[processing image, please return in a few minutes]' if $v && $v->{image} < 0; + p mt '_vnedit_image_none' if !$v || !$v->{image}; + p mt '_vnedit_image_processing' if $v && $v->{image} < 0; img src => sprintf("%s/cv/%02d/%d.jpg", $self->{url_static}, $v->{image}%100, $v->{image}), alt => $v->{title} if $v && $v->{image} > 0; end; div; - h2 'Upload new image'; + h2 mt '_vnedit_image_upload'; input type => 'file', class => 'text', name => 'img', id => 'img'; - p 'Preferably the cover of the CD/DVD/package. Image must be in JPEG or PNG format' - ." and at most 500kB. Images larger than 256x400 will automatically be resized.\n\n\n"; + p mt('_vnedit_image_upload_msg')."\n\n\n"; - h2 'NSFW'; + h2 mt '_vnedit_image_nsfw'; input type => 'checkbox', class => 'checkbox', id => 'img_nsfw', name => 'img_nsfw', $frm->{img_nsfw} ? (checked => 'checked') : (); - label class => 'checkbox', for => 'img_nsfw', "Not Safe For Work.\n"; - p 'Please check this option if the image contains nudity, gore, or is otherwise not safe in a work-friendly environment.'; + label class => 'checkbox', for => 'img_nsfw', mt '_vnedit_image_nsfw_check'; + p "\n".mt '_vnedit_image_nsfw_msg'; end; }], ], - 'Relations' => [ + vn_rel => [ mt('_vnedit_rel'), [ hidden => short => 'relations' ], [ static => nolabel => 1, content => sub { - h2 'Selected relations'; + h2 mt '_vnedit_rel_sel'; table; tbody id => 'relation_tbl'; # to be filled using javascript end; end; - h2 'Add relation'; + h2 mt '_vnedit_rel_add'; + # TODO: localize JS relartion selector table; Tr id => 'relation_new'; td class => 'tc1'; @@ -226,21 +215,14 @@ sub _form { }], ], - !@$r ? () : ( 'Screenshots' => [ + !@$r ? () : ( vn_scr => [ mt('_vnedit_scr'), [ hidden => short => 'screenshots' ], [ static => nolabel => 1, content => sub { div class => 'warning'; - b 'Please keep the following in mind when uploading screenshots:'; - ul; - li 'Screenshots have to be in the native resolution of the game,'; - li 'Remove any window borders and make sure the image is unmarked,'; - li 'Don\'t only upload event CGs.'; - end; - lit 'Please read the <a href="/d2#6">guidelines</a> for more information.'; - br; - b 'Make sure to submit the form after the upload has finished!'; + lit mt '_vnedit_scr_msg'; end; br; + # TODO: localize screenshot uploader table; tbody id => 'scr_table', ''; end; diff --git a/lib/VNDB/Handler/VNPage.pm b/lib/VNDB/Handler/VNPage.pm index 0563c1dc..361963a8 100644 --- a/lib/VNDB/Handler/VNPage.pm +++ b/lib/VNDB/Handler/VNPage.pm @@ -8,25 +8,33 @@ use VNDB::Func; YAWF::register( + qr{v/rand} => \&rand, qr{v([1-9]\d*)/rg} => \&rg, qr{v([1-9]\d*)(?:\.([1-9]\d*))?} => \&page, ); +sub rand { + my $self = shift; + $self->resRedirect('/v'.$self->dbVNGet(results => 1, order => 'RANDOM()')->[0]{id}, 'temp'); +} + + sub rg { my($self, $vid) = @_; my $v = $self->dbVNGet(id => $vid, what => 'relgraph')->[0]; return 404 if !$v->{id} || !$v->{rgraph}; - $self->htmlHeader(title => 'Relation graph for '.$v->{title}); + my $title = mt '_vnrg_title', $v->{title}; + $self->htmlHeader(title => $title); $self->htmlMainTabs('v', $v, 'rg'); div class => 'mainbox'; - h1 'Relation graph for '.$v->{title}; + h1 $title; lit $v->{cmap}; p class => 'center'; img src => sprintf('%s/rg/%02d/%d.png', $self->{url_static}, $v->{rgraph}%100, $v->{rgraph}), - alt => 'Relation graph for '.$v->{title}, usemap => '#rgraph'; + alt => $title, usemap => '#rgraph'; end; end; } @@ -60,19 +68,19 @@ sub page { # image div class => 'vnimg'; if(!$v->{image}) { - p 'No image uploaded yet'; + p mt '_vnpage_noimg'; } elsif($v->{image} < 0) { - p '[processing image, please return in a few minutes]'; + p mt '_vnpage_imgproc'; } elsif($v->{img_nsfw} && !$self->authInfo->{show_nsfw}) { img id => 'nsfw_hid', src => sprintf("%s/cv/%02d/%d.jpg", $self->{url_static}, $v->{image}%100, $v->{image}), alt => $v->{title}; p id => 'nsfw_show'; - txt "This image has been flagged\nas Not Safe For Work.\n\n"; - a href => '#', 'Show me anyway'; - txt "\n\n(This warning can be disabled in your account)"; + txt mt('_vnpage_imgnsfw_msg')."\n\n"; + a href => '#', mt '_vnpage_imgnsfw_show'; + txt "\n\n".mt '_vnpage_imgnsfw_note'; end; } else { img src => sprintf("%s/cv/%02d/%d.jpg", $self->{url_static}, $v->{image}%100, $v->{image}), alt => $v->{title}; - i 'Flagged as NSFW' if $v->{img_nsfw} && $self->authInfo->{show_nsfw}; + i mt '_vnpage_imgnsfw_foot' if $v->{img_nsfw} && $self->authInfo->{show_nsfw}; } end; @@ -80,36 +88,35 @@ sub page { table; my $i = 0; Tr ++$i % 2 ? (class => 'odd') : (); - td class => 'key', 'Title'; + td class => 'key', mt '_vnpage_vntitle'; td $v->{title}; end; if($v->{original}) { Tr ++$i % 2 ? (class => 'odd') : (); - td 'Original title'; + td mt '_vnpage_original'; td $v->{original}; end; } if($v->{alias}) { Tr ++$i % 2 ? (class => 'odd') : (); - td 'Aliases'; + td mt '_vnpage_alias'; td $v->{alias}; end; } if($v->{length}) { Tr ++$i % 2 ? (class => 'odd') : (); - td 'Length'; - td "$self->{vn_lengths}[$v->{length}][0] ($self->{vn_lengths}[$v->{length}][1])"; + td mt '_vnpage_length'; + td mt '_vnlength_'.$v->{length}, 1; end; } my @links = ( $v->{l_wp} ? [ 'Wikipedia', 'http://en.wikipedia.org/wiki/%s', $v->{l_wp} ] : (), $v->{l_encubed} ? [ 'Encubed', 'http://novelnews.net/tag/%s/', $v->{l_encubed} ] : (), $v->{l_renai} ? [ 'Renai.us', 'http://renai.us/game/%s.shtml', $v->{l_renai} ] : (), - $v->{l_vnn} ? [ 'V-N.net', 'http://visual-novels.net/vn/index.php?option=com_content&task=view&id=%d', $v->{l_vnn} ] : (), ); if(@links) { Tr ++$i % 2 ? (class => 'odd') : (); - td 'Links'; + td mt '_vnpage_links'; td; for(@links) { a href => sprintf($_->[1], $_->[2]), $_->[0]; @@ -126,9 +133,9 @@ sub page { Tr; td class => 'vndesc', colspan => 2; - h2 'Description'; + h2 mt '_vnpage_description'; p; - lit bb2html $v->{desc}; + lit $v->{desc} ? bb2html $v->{desc} : '-'; end; end; end; @@ -141,11 +148,11 @@ sub page { my $t = $self->dbTagStats(vid => $v->{id}, order => 'avg(tv.vote) DESC', minrating => 0, results => 999); if(@$t) { div id => 'tagops'; - a href => '#', 'hide spoilers'; - a href => '#', class => 'tsel', 'show minor spoilers'; - a href => '#', 'spoil me!'; - a href => '#', class => 'sec', 'summary'; - a href => '#', 'all'; + a href => '#', mt '_vnpage_tags_spoil0'; + a href => '#', class => 'tsel', mt '_vnpage_tags_spoil1'; + a href => '#', mt '_vnpage_tags_spoil2'; + a href => '#', class => 'sec', mt '_vnpage_tags_summary'; + a href => '#', mt '_vnpage_tags_all'; end; div id => 'vntags'; for (@$t) { @@ -176,41 +183,38 @@ sub _revision { )->[0]; $self->htmlRevision('v', $prev, $v, - [ title => 'Title (romaji)', diff => 1 ], - [ original => 'Original title', diff => 1 ], - [ alias => 'Alias', diff => 1 ], - [ desc => 'Description', diff => 1 ], - [ length => 'Length', serialize => sub { $self->{vn_lengths}[$_[0]][0] } ], - [ l_wp => 'Wikipedia link', htmlize => sub { - $_[0] ? sprintf '<a href="http://en.wikipedia.org/wiki/%s">%1$s</a>', xml_escape $_[0] : '[no link]' - }], - [ l_encubed => 'Encubed tag', htmlize => sub { - $_[0] ? sprintf '<a href="http://novelnews.net/tag/%s/">%1$s</a>', xml_escape $_[0] : '[no link]' + [ title => diff => 1 ], + [ original => diff => 1 ], + [ alias => diff => 1 ], + [ desc => diff => 1 ], + [ length => serialize => sub { mt '_vnlength_'.$_[0] } ], + [ l_wp => htmlize => sub { + $_[0] ? sprintf '<a href="http://en.wikipedia.org/wiki/%s">%1$s</a>', xml_escape $_[0] : mt '_vndiff_nolink' }], - [ l_renai => 'Renai.us link', htmlize => sub { - $_[0] ? sprintf '<a href="http://renai.us/game/%s.shtml">%1$s</a>', xml_escape $_[0] : '[no link]' + [ l_encubed => htmlize => sub { + $_[0] ? sprintf '<a href="http://novelnews.net/tag/%s/">%1$s</a>', xml_escape $_[0] : mt '_vndiff_nolink' }], - [ l_vnn => 'V-N.net link', htmlize => sub { - $_[0] ? sprintf '<a href="http://visual-novels.net/vn/index.php?option=com_content&task=view&id=%d">%1$d</a>', xml_escape $_[0] : '[no link]' + [ l_renai => htmlize => sub { + $_[0] ? sprintf '<a href="http://renai.us/game/%s.shtml">%1$s</a>', xml_escape $_[0] : mt '_vndiff_nolink' }], - [ relations => 'Relations', join => '<br />', split => sub { + [ relations => join => '<br />', split => sub { my @r = map sprintf('%s: <a href="/v%d" title="%s">%s</a>', $self->{vn_relations}[$_->{relation}][0], $_->{id}, xml_escape($_->{original}||$_->{title}), xml_escape shorten $_->{title}, 40 ), sort { $a->{id} <=> $b->{id} } @{$_[0]}; - return @r ? @r : ('[none]'); + return @r ? @r : (mt '_vndiff_none'); }], - [ anime => 'Anime', join => ', ', split => sub { + [ anime => join => ', ', split => sub { my @r = map sprintf('<a href="http://anidb.net/a%d">a%1$d</a>', $_->{id}), sort { $a->{id} <=> $b->{id} } @{$_[0]}; - return @r ? @r : ('[none]'); + return @r ? @r : (mt '_vndiff_none'); }], - [ screenshots => 'Screenshots', join => '<br />', split => sub { + [ screenshots => join => '<br />', split => sub { my @r = map sprintf('[%s] <a href="%s/sf/%02d/%d.jpg" rel="iv:%dx%d">%4$d</a> (%s)', $_->{rid} ? qq|<a href="/r$_->{rid}">r$_->{rid}</a>| : 'no release', $self->{url_static}, $_->{id}%100, $_->{id}, $_->{width}, $_->{height}, $_->{nsfw} ? 'NSFW' : 'Safe' ), @{$_[0]}; - return @r ? @r : ('[no screenshots]'); + return @r ? @r : (mt '_vndiff_none'); }], - [ image => 'Image', htmlize => sub { + [ image => htmlize => sub { my $url = sprintf "%s/cv/%02d/%d.jpg", $self->{url_static}, $_[0]%100, $_[0]; if($_[0] > 0) { return $_[1]->{img_nsfw} && !$self->authInfo->{show_nsfw} ? "<a href=\"$url\">(NSFW)</a>" : "<img src=\"$url\" />"; @@ -218,7 +222,7 @@ sub _revision { return $_[0] < 0 ? '[processing]' : 'No image'; } }], - [ img_nsfw => 'Image NSFW', serialize => sub { $_[0] ? 'Not safe' : 'Safe' } ], + [ img_nsfw => serialize => sub { $_[0] ? 'Not safe' : 'Safe' } ], ); } @@ -231,13 +235,13 @@ sub _producers { my @lang = grep !$lang{$_}++, map @{$_->{languages}}, @$r; Tr ++$$i % 2 ? (class => 'odd') : (); - td 'Producers'; + td mt '_vnpage_producers'; td; for my $l (@lang) { my %p = map { $_->{id} => $_ } map @{$_->{producers}}, grep grep($_ eq $l, @{$_->{languages}}), @$r; my @p = values %p; next if !@p; - cssicon "lang $l", $self->{languages}{$l}; + cssicon "lang $l", mt "_lang_$l"; for (@p) { a href => "/p$_->{id}", title => $_->{original}||$_->{name}, shorten $_->{name}, 30; txt ' & ' if $_ != $p[$#p]; @@ -258,7 +262,7 @@ sub _relations { Tr ++$$i % 2 ? (class => 'odd') : (); - td 'Relations'; + td mt '_vnpage_relations'; td class => 'relations'; dl; for(sort keys %rel) { @@ -280,14 +284,12 @@ sub _anime { my($self, $i, $v) = @_; Tr ++$$i % 2 ? (class => 'odd') : (); - td 'Related anime'; + td mt '_vnpage_anime'; td class => 'anime'; for (sort { ($a->{year}||9999) <=> ($b->{year}||9999) } @{$v->{anime}}) { if(!$_->{lastfetch} || !$_->{year} || !$_->{title_romaji}) { b; - txt '[no information available at this time: '; - a href => "http://anidb.net/a$_->{id}", $_->{id}; - txt ']'; + lit mt '_vnpage_anime_noinfo', $_->{id}, "http://anidb.net/a$_->{id}"; end; } else { b; @@ -320,25 +322,25 @@ sub _useroptions { my $wish = $self->dbWishListGet(uid => $self->authInfo->{id}, vid => $v->{id})->[0]; Tr ++$$i % 2 ? (class => 'odd') : (); - td 'User options'; + td mt '_vnpage_uopt'; td; if($vote || !$wish) { Select id => 'votesel'; - option $vote ? "your vote: $vote->{vote}" : 'not voted yet'; - optgroup label => $vote ? 'Change vote' : 'Vote'; - option value => $_, "$_ ($self->{votes}[$_-1])" for (reverse 1..10); + option $vote ? mt '_vnpage_uopt_voted', $vote->{vote} : mt '_vnpage_uopt_novote'; + optgroup label => $vote ? mt '_vnpage_uopt_changevote' : mt '_vnpage_uopt_dovote'; + option value => $_, "$_ (".mt("_vote_$_").')' for (reverse 1..10); end; - option value => -1, 'revoke' if $vote; + option value => -1, mt '_vnpage_uopt_delvote' if $vote; end; br; } if(!$vote || $wish) { Select id => 'wishsel'; - option $wish ? "wishlist: $self->{wishlist_status}[$wish->{wstat}]" : 'not on your wishlist'; - optgroup label => $wish ? 'Change status' : 'Add to wishlist'; - option value => $_, $self->{wishlist_status}[$_] for (0..$#{$self->{wishlist_status}}); + option $wish ? mt '_vnpage_uopt_wishlisted', mt '_wish_'.$wish->{wstat} : mt '_vnpage_uopt_nowish'; + optgroup label => $wish ? mt '_vnpage_uopt_changewish' : mt '_vnpage_uopt_addwish'; + option value => $_, mt "_wish_$_" for (@{$self->{wishlist_status}}); end; - option value => -1, 'remove from wishlist'; + option value => -1, mt '_vnpage_uopt_delwish'; end; } end; @@ -350,10 +352,10 @@ sub _releases { my($self, $v, $r) = @_; div class => 'mainbox releases'; - a class => 'addnew', href => "/v$v->{id}/add", 'add release'; - h1 'Releases'; + a class => 'addnew', href => "/v$v->{id}/add", mt '_vnpage_rel_add'; + h1 mt '_vnpage_rel'; if(!@$r) { - p 'We don\'t have any information about releases of this visual novel yet...'; + p mt '_vnpage_rel_none'; end; return; } @@ -372,24 +374,24 @@ sub _releases { for my $l (@lang) { Tr class => 'lang'; td colspan => 6; - cssicon "lang $l", $self->{languages}{$l}; - txt $self->{languages}{$l}; + cssicon "lang $l", mt "_lang_$l"; + txt mt "_lang_$l"; end; end; for my $rel (grep grep($_ eq $l, @{$_->{languages}}), @$r) { Tr; - td class => 'tc1'; lit datestr $rel->{released}; end; + td class => 'tc1'; lit $self->{l10n}->datestr($rel->{released}); end; td class => 'tc2', $rel->{minage} < 0 ? '' : $self->{age_ratings}{$rel->{minage}}[0]; td class => 'tc3'; for (sort @{$rel->{platforms}}) { next if $_ eq 'oth'; - cssicon $_, $self->{platforms}{$_}; + cssicon $_, mt "_plat_$_"; } - cssicon lc(substr($self->{release_types}[$rel->{type}],0,3)), $self->{release_types}[$rel->{type}]; + cssicon "rt$rel->{type}", mt "_rtype_$rel->{type}"; end; td class => 'tc4'; a href => "/r$rel->{id}", title => $rel->{original}||$rel->{title}, $rel->{title}; - b class => 'grayedout', ' (patch)' if $rel->{patch}; + b class => 'grayedout', ' '.mt '_vnpage_rel_patch' if $rel->{patch}; end; td class => 'tc5'; if($self->authInfo->{id}) { @@ -403,7 +405,7 @@ sub _releases { td class => 'tc6'; if($rel->{website}) { a href => $rel->{website}, rel => 'nofollow'; - cssicon 'ext', 'External link'; + cssicon 'ext', mt '_vnpage_rel_extlink'; end; } else { txt ' '; @@ -423,22 +425,22 @@ sub _screenshots { if(grep $_->{nsfw}, @{$v->{screenshots}}) { p class => 'nsfwtoggle'; - lit sprintf 'Showing <i id="nsfwshown">%d</i> out of %d screenshots, ', - $self->authInfo->{show_nsfw} ? scalar @{$v->{screenshots}} : scalar grep(!$_->{nsfw}, @{$v->{screenshots}}), + lit mt '_vnpage_scr_showing', + sprintf('<i id="nsfwshown">%d</i>', $self->authInfo->{show_nsfw} ? scalar @{$v->{screenshots}} : scalar grep(!$_->{nsfw}, @{$v->{screenshots}})), scalar @{$v->{screenshots}}; - a href => '#', id => "nsfwhide", 'show/hide NSFW'; - txt '.'; + txt " "; + a href => '#', id => "nsfwhide", mt '_vnpage_scr_nsfwhide'; end; } - h1 'Screenshots'; + h1 mt '_vnpage_scr'; table; for my $rel (@$r) { my @scr = grep $_->{rid} && $rel->{id} == $_->{rid}, @{$v->{screenshots}}; next if !@scr; Tr class => 'rel'; td colspan => 5; - cssicon "lang $_", $self->{languages}{$_} for (@{$rel->{languages}}); + cssicon "lang $_", mt "_lang_$_" for (@{$rel->{languages}}); txt $rel->{title}; end; end; @@ -448,7 +450,7 @@ sub _screenshots { div $_->{nsfw} ? (class => 'nsfw'.(!$self->authInfo->{show_nsfw} ? ' hidden' : '')) : (); a href => sprintf('%s/sf/%02d/%d.jpg', $self->{url_static}, $_->{id}%100, $_->{id}), rel => "iv:$_->{width}x$_->{height}:scr", $_->{nsfw} && !$self->authInfo->{show_nsfw} ? (class => 'hidden') : (); - img src => sprintf('%s/st/%02d/%d.jpg', $self->{url_static}, $_->{id}%100, $_->{id}), alt => "Screenshot #$_->{id}"; + img src => sprintf('%s/st/%02d/%d.jpg', $self->{url_static}, $_->{id}%100, $_->{id}), alt => mt '_vnpage_scr_num', $_->{id}; end; end; } @@ -463,11 +465,11 @@ sub _screenshots { sub _stats { my($self, $v) = @_; - my $stats = $self->dbVoteStats(vid => $v->{id}); + my $stats = $self->dbVoteStats(vid => $v->{id}, 1); div class => 'mainbox'; - h1 'User stats'; + h1 mt '_vnpage_stats'; if(!grep $_ > 0, @$stats) { - p "Nobody has voted on this visual novel yet..."; + p mt '_vnpage_stats_none'; } else { $self->htmlVoteStats(v => $v, $stats); } diff --git a/lib/VNDB/L10N.pm b/lib/VNDB/L10N.pm new file mode 100644 index 00000000..d4ff872c --- /dev/null +++ b/lib/VNDB/L10N.pm @@ -0,0 +1,189 @@ + +use strict; +use warnings; + +{ + package VNDB::L10N; + use base 'Locale::Maketext'; + + sub fallback_languages { ('en') }; + + # used for the language switch interface, language tags must + # be the same as in the languages hash in global.pl + sub languages { ('en', 'ru') } + + sub maketext { + my $r = eval { shift->SUPER::maketext(@_) }; + return $r if defined $r; + warn "maketext failed for '@_': $@\n"; + return $_[0]||''; # not quite sure we want this + } + + # can be called as either a subroutine or a method + sub loadfile { + my %lang = ( + en => \%VNDB::L10N::en::Lexicon, + ru => \%VNDB::L10N::ru::Lexicon, + ); + + open my $F, '<:utf8', $VNDB::ROOT.'/data/lang.txt' or die "Opening language file: $!\n"; + my($empty, $line, $key, $lang) = (0, 0); + while(<$F>) { + chomp; + $line++; + + # ignore intro + if(!defined $key) { + $key = 0 if /^\/intro$/; + next; + } + # ignore comments + next if /^#/; + # key + if(/^:(.+)$/) { + $key = $1; + $lang = undef; + $empty = 0; + next; + } + # locale string + if(/^([a-z_-]{2,7})[ *]: (.+)$/) { + $lang = $1; + die "Unknown language on #$line: $lang\n" if !$lang{$lang}; + die "Unknown key for locale on #$line\n" if !$key; + $lang{$lang}{$key} = $2; + $empty = 0; + next; + } + # multi-line locale string + if($lang && /^\s+([^\s].*)$/) { + $lang{$lang}{$key} .= ''.("\n"x$empty)."\n$1"; + $empty = 0; + next; + } + # empty string (count them in case they're part of a multi-line locale string) + if(/^\s*$/) { + $empty++; + next; + } + # something we didn't expect + die "Don't know what to do with line $line\n" unless /^([a-z_-]{2,7})[ *]:/; + } + close $F; + } +} + + +{ + package VNDB::L10N::en; + use base 'VNDB::L10N'; + use POSIX 'strftime'; + use YAWF 'xml_escape'; + our %Lexicon; + + sub quant { + return $_[1]==1 ? $_[2] : $_[3]; + } + + # Argument: unix timestamp + # Returns: age + sub age { + my $a = time-$_[1]; + return sprintf '%d %s ago', + $a > 60*60*24*365*2 ? ( $a/60/60/24/365, 'years' ) : + $a > 60*60*24*(365/12)*2 ? ( $a/60/60/24/(365/12), 'months' ) : + $a > 60*60*24*7*2 ? ( $a/60/60/24/7, 'weeks' ) : + $a > 60*60*24*2 ? ( $a/60/60/24, 'days' ) : + $a > 60*60*2 ? ( $a/60/60, 'hours' ) : + $a > 60*2 ? ( $a/60, 'min' ) : + ( $a, 'sec' ); + } + + # argument: unix timestamp and optional format (compact/full) + # return value: yyyy-mm-dd + # (maybe an idea to use cgit-style ages for recent timestamps) + sub date { + my($s, $t, $f) = @_; + return strftime '%Y-%m-%d', gmtime $t if !$f || $f eq 'compact'; + return strftime '%Y-%m-%d at %R', gmtime $t; + } + + # argument: database release date format (yyyymmdd) + # y = 0000 -> unknown + # y = 9999 -> TBA + # m = 99 -> month+day unknown + # d = 99 -> day unknown + # return value: (unknown|TBA|yyyy|yyyy-mm|yyyy-mm-dd) + # if date > now: <b class="future">str</b> + sub datestr { + my $self = shift; + my $date = sprintf '%08d', shift||0; + my $future = $date > strftime '%Y%m%d', gmtime; + my($y, $m, $d) = ($1, $2, $3) if $date =~ /^([0-9]{4})([0-9]{2})([0-9]{2})$/; + + my $str = $y == 0 ? 'unknown' : $y == 9999 ? 'TBA' : + $m == 99 ? sprintf('%04d', $y) : + $d == 99 ? sprintf('%04d-%02d', $y, $m) : + sprintf('%04d-%02d-%02d', $y, $m, $d); + + return $str if !$future; + return qq|<b class="future">$str</b>|; + } + + # Arguments: (uid, username), or a hashref containing that info + sub userstr { + my $self = shift; + my($id,$n) = ref($_[0])eq'HASH'?($_[0]{uid}||$_[0]{requester}, $_[0]{username}):@_; + return !$id ? '[deleted]' : '<a href="/u'.$id.'">'.$n.'</a>'; + } + + # Arguments: index, @list. returns $list[index] + sub index { + shift; + return $_[shift||0]; + } + + # Shortcut for <a href="arg1">arg2</a> + sub url { + return sprintf '<a href="%s">%s</a>', xml_escape($_[1]), xml_escape($_[2]); + } + + # <br /> + sub br { return '<br />' } +} + + + +{ + package VNDB::L10N::ru; + use base 'VNDB::L10N::en'; + our %Lexicon; + + sub quant { + my($self, $num, $single, $couple, $lots) = @_; + return $single if ($num % 10) == 1 && ($num % 100) != 11; + return $couple if ($num % 10) >= 2 && ($num % 10) <= 4 && !(($num % 100) >= 12 && ($num % 100) <= 14); + return $lots; + } + + sub age { + my $self = shift; + my $a = time-shift; + use utf8; + my @l = ( + $a > 60*60*24*365*2 ? ( $a/60/60/24/365, 'год', 'года', 'лет' ) : + $a > 60*60*24*(365/12)*2 ? ( $a/60/60/24/(365/12), 'месяц', 'месяца', 'месяцев' ) : + $a > 60*60*24*7*2 ? ( $a/60/60/24/7, 'неделя', 'недели', 'недель' ) : + $a > 60*60*24*2 ? ( $a/60/60/24, 'день', 'дня', 'дней' ) : + $a > 60*60*2 ? ( $a/60/60, 'час', 'часа', 'часов' ) : + $a > 60*2 ? ( $a/60, 'минута', 'минуты', 'минут' ) : + ( $a, 'секунда', 'секунды', 'секунд' ) + ); + return sprintf '%d %s назад', $l[0], $self->quant(@l); + } + +} + + +1; + diff --git a/lib/VNDB/Util/Auth.pm b/lib/VNDB/Util/Auth.pm index ad225d92..c4daffd9 100644 --- a/lib/VNDB/Util/Auth.pm +++ b/lib/VNDB/Util/Auth.pm @@ -83,7 +83,7 @@ sub authInfo { sub authCan { my($self, $act) = @_; my $r = $self->{_auth}{rank}||0; - return scalar grep $_ eq $act, @{$self->{user_ranks}[$r]}[1..$#{$self->{user_ranks}[$r]}]; + return scalar grep $_ eq $act, @{$self->{user_ranks}[$r]}[0..$#{$self->{user_ranks}[$r]}]; } diff --git a/lib/VNDB/Util/CommonHTML.pm b/lib/VNDB/Util/CommonHTML.pm index 981edfe8..176f2960 100644 --- a/lib/VNDB/Util/CommonHTML.pm +++ b/lib/VNDB/Util/CommonHTML.pm @@ -29,48 +29,48 @@ sub htmlMainTabs { ul class => 'maintabs'; if($type =~ /[uvrp]/) { li $sel eq 'hist' ? (class => 'tabselected') : (); - a href => "/$id/hist", 'history'; + a href => "/$id/hist", mt '_mtabs_hist'; end; } if($type =~ /[uvp]/) { my $cnt = $self->dbThreadCount($type, $obj->{id}); li $sel eq 'disc' ? (class => 'tabselected') : (); - a href => "/t/$id", "discussions ($cnt)"; + a href => "/t/$id", mt '_mtabs_discuss', $cnt; end; } if($type eq 'u') { li $sel eq 'posts' ? (class => 'tabselected') : (); - a href => "/$id/posts", 'posts'; + a href => "/$id/posts", mt '_mtabs_posts'; end; } - if($type eq 'u' && $obj->{show_list}) { + if($type eq 'u' && ($obj->{show_list} || $self->authCan('usermod'))) { li $sel eq 'wish' ? (class => 'tabselected') : (); - a href => "/$id/wish", 'wishlist'; + a href => "/$id/wish", mt '_mtabs_wishlist'; end; li $sel eq 'list' ? (class => 'tabselected') : (); - a href => "/$id/list", 'list'; + a href => "/$id/list", mt '_mtabs_list'; end; } if($type eq 'u') { li $sel eq 'tags' ? (class => 'tabselected') : (); - a href => "/$id/tags", 'tags'; + a href => "/$id/tags", mt '_mtabs_tags'; end; } if($type eq 'v' && $self->authCan('tag') && !$obj->{hidden}) { li $sel eq 'tagmod' ? (class => 'tabselected') : (); - a href => "/$id/tagmod", 'modify tags'; + a href => "/$id/tagmod", mt '_mtabs_tagmod'; end; } if($type eq 'r' && $self->authCan('edit')) { li $sel eq 'copy' ? (class => 'tabselected') : (); - a href => "/$id/copy", 'copy'; + a href => "/$id/copy", mt '_mtabs_copy'; end; } @@ -79,31 +79,31 @@ sub htmlMainTabs { || $type eq 'g' && $self->authCan('tagmod') ) { li $sel eq 'edit' ? (class => 'tabselected') : (); - a href => "/$id/edit", 'edit'; + a href => "/$id/edit", mt '_mtabs_edit'; end; } if($type =~ /[vrp]/ && $self->authCan('del')) { li; - a href => "/$id/hide", $obj->{hidden} ? 'unhide' : 'hide'; + a href => "/$id/hide", mt $obj->{hidden} ? '_mtabs_unhide' : '_mtabs_hide'; end; } if($type =~ /[vrp]/ && $self->authCan('lock')) { li; - a href => "/$id/lock", $obj->{locked} ? 'unlock' : 'lock'; + a href => "/$id/lock", mt $obj->{locked} ? '_mtabs_unlock' : '_mtabs_lock'; end; } if($type eq 'u' && $self->authCan('usermod')) { li $sel eq 'del' ? (class => 'tabselected') : (); - a href => "/$id/del", 'del'; + a href => "/$id/del", mt '_mtabs_del'; end; } if($type eq 'v' && $obj->{rgraph}) { li $sel eq 'rg' ? (class => 'tabselected') : (); - a href => "/$id/rg", 'relations'; + a href => "/$id/rg", mt '_mtabs_relations'; end; } @@ -117,19 +117,16 @@ sub htmlMainTabs { # generates a full error page, including header and footer sub htmlDenied { my $self = shift; - $self->htmlHeader(title => 'Access Denied'); + $self->htmlHeader(title => mt '_denied_title'); div class => 'mainbox'; - h1 'Access Denied'; + h1 mt '_denied_title'; div class => 'warning'; if(!$self->authInfo->{id}) { - h2 'You need to be logged in to perform this action.'; - p; - lit 'Please <a href="/u/login">login</a>, or <a href="/u/register">create an account</a> ' - .'if you don\'t have one yet.'; - end; + h2 mt '_denied_needlogin_title'; + p; lit mt '_denied_needlogin_msg'; end; } else { - h2 "You are not allowed to perform this action."; - p 'It seems you don\'t have the proper rights to perform the action you wanted to perform...'; + h2 mt '_denied_noaccess_title'; + p mt '_denied_noaccess_msg'; } end; end; @@ -147,10 +144,9 @@ sub htmlHiddenMessage { div class => 'mainbox'; h1 $obj->{title}||$obj->{name}; div class => 'warning'; - h2 'Item deleted'; + h2 mt '_hiddenmsg_title'; p; - lit qq|This item has been deleted from the database, File a request on the| - .qq| <a href="/t/$board">discussion board</a> to undelete this page.|; + lit mt '_hiddenmsg_msg', "/t/$board"; end; end; end; @@ -240,12 +236,12 @@ sub htmlBrowseNavigate { ul class => 'maintabs ' . ($al eq 't' ? 'notfirst' : 'bottom'); if($p > 1) { li class => 'left'; - a href => $url.($p-1), '<- previous'; + a href => $url.($p-1), '<- '.mt '_browse_previous'; end; } if($np) { li; - a href => $url.($p+1), 'next ->'; + a href => $url.($p+1), mt('_browse_next').' ->'; end; } end; @@ -265,12 +261,12 @@ sub htmlBrowseNavigate { sub htmlRevision { my($self, $type, $old, $new, @fields) = @_; div class => 'mainbox revision'; - h1 'Revision '.$new->{rev}; + h1 mt '_revision_title', $new->{rev}; # previous/next revision links - a class => 'prev', href => sprintf('/%s%d.%d', $type, $new->{id}, $new->{rev}-1), '<- earlier revision' + a class => 'prev', href => sprintf('/%s%d.%d', $type, $new->{id}, $new->{rev}-1), '<- '.mt '_revision_previous' if $new->{rev} > 1; - a class => 'next', href => sprintf('/%s%d.%d', $type, $new->{id}, $new->{rev}+1), 'later revision ->' + a class => 'next', href => sprintf('/%s%d.%d', $type, $new->{id}, $new->{rev}+1), mt('_revision_next').' ->' if $new->{cid} != $new->{latest}; p class => 'center'; a href => "/$type$new->{id}", "$type$new->{id}"; @@ -279,11 +275,11 @@ sub htmlRevision { # no previous revision, just show info about the revision itself if(!$old) { div; - revheader($type, $new); + revheader($self, $type, $new); br; - b 'Edit summary:'; + b mt '_revision_new_summary'; br; br; - lit bb2html($new->{comments})||'[no summary]'; + lit bb2html($new->{comments})||'-'; end; } @@ -293,40 +289,37 @@ sub htmlRevision { thead; Tr; td; lit ' '; end; - td; revheader($type, $old); end; - td; revheader($type, $new); end; + td; revheader($self, $type, $old); end; + td; revheader($self, $type, $new); end; end; Tr; td; lit ' '; end; td colspan => 2; - b 'Edit summary of revision '.$new->{rev}.':'; + b mt '_revision_edit_summary', $new->{rev}; br; br; - lit bb2html($new->{comments})||'[no summary]'; + lit bb2html($new->{comments})||'-'; end; end; end; my $i = 1; - revdiff(\$i, $old, $new, @$_) for (@fields); + revdiff(\$i, $type, $old, $new, @$_) for (@fields); end; } end; } sub revheader { # type, obj - my($type, $obj) = @_; - b 'Revision '.$obj->{rev}; + my($self, $type, $obj) = @_; + b mt '_revision_title', $obj->{rev}; txt ' ('; - a href => "/$type$obj->{id}.$obj->{rev}/edit", 'edit'; + a href => "/$type$obj->{id}.$obj->{rev}/edit", mt '_mtabs_edit'; txt ')'; br; - txt 'By '; - lit userstr($obj); - txt ' on '; - lit date $obj->{added}, 'full'; + lit mt '_revision_user_date', $obj, $obj->{added}; } sub revdiff { - my($i, $old, $new, $short, $name, %o) = @_; + my($i, $type, $old, $new, $short, %o) = @_; $o{serialize} ||= $o{htmlize}; $o{diff}++ if $o{split}; @@ -358,11 +351,11 @@ sub revdiff { $ser2 = xml_escape $ser2; } - $ser1 = '[empty]' if !$ser1 && $ser1 ne '0'; - $ser2 = '[empty]' if !$ser2 && $ser2 ne '0'; + $ser1 = mt '_revision_emptyfield' if !$ser1 && $ser1 ne '0'; + $ser2 = mt '_revision_emptyfield' if !$ser2 && $ser2 ne '0'; Tr $$i++ % 2 ? (class => 'odd') : (); - td $name; + td mt "_revfield_${type}_$short"; td class => 'tcval'; lit $ser1; end; td class => 'tcval'; lit $ser2; end; end; @@ -372,38 +365,36 @@ sub revdiff { # Generates a generic message to show as the header of the edit forms # Arguments: v/r/p, obj sub htmlEditMessage { - my($self, $type, $obj, $copy) = @_; - my $full = {v => 'visual novel', r => 'release', p => 'producer'}->{$type}; + my($self, $type, $obj, $title, $copy) = @_; + my $num = {v => 0, r => 1, p => 2}->{$type}; my $guidelines = {v => 2, r => 3, p => 4}->{$type}; div class => 'mainbox'; - h1 $obj ? ''.($copy ? 'Copy ':'Edit ').($obj->{name}||$obj->{title}) : "Add new $full"; + h1 $title; if($copy) { div class => 'warning'; - h2 "You're not editing a release!"; + h2 mt '_editmsg_copy_title'; p; - txt "You're about to insert a new release into the database with information based on "; - a href => "/$type$obj->{id}", $obj->{title}; - txt ". Hit the 'edit' tab on the right-top if you intended to edit the release instead of creating a new one."; + lit mt '_editmsg_copy_msg', sprintf '<a href="/%s%d">%s</a>', $type, $obj->{id}, xml_escape $obj->{title}, end; end; } div class => 'notice'; - h2 'Before editing:'; + h2 mt '_editmsg_msg_title'; ul; - li; lit qq|Read the <a href="/d$guidelines">guidelines</a>!|; end; + li; lit mt '_editmsg_msg_guidelines', "/d$guidelines"; end; if($obj) { - li; lit qq|Check for any existing discussions on the <a href="/t/$type$obj->{id}">discussion board</a>|; end; - li; lit qq|Browse the <a href="/$type$obj->{id}/hist">edit history</a> for any recent changes related to what you want to change.|; end; + li; lit mt '_editmsg_msg_discuss', $type eq 'r' ? "/t/v$obj->{vn}[0]{vid}" : "/t/$type$obj->{id}"; end; + li; lit mt '_editmsg_msg_history', "/$type$obj->{id}/hist"; end; } elsif($type ne 'r') { - li; lit qq|<a href="/$type/all">Search the database</a> to see if we already have information about this $full|; end; + li; lit mt '_editmsg_msg_search', "/$type/all", $num; end; } end; end; if($obj && $obj->{latest} != $obj->{cid}) { div class => 'warning'; - h2 'Reverting'; - p qq|You are editing an old revision of this $full. If you save it, all changes made after this revision will be reverted!|; + h2 mt '_editmsg_revert_title'; + p mt '_editmsg_revert_msg', $num; end; } end; @@ -417,13 +408,13 @@ sub htmlItemMessage { my($self, $type, $obj) = @_; if($obj->{locked}) { - p class => 'locked', 'Locked for editing' + p class => 'locked', mt '_itemmsg_locked'; } elsif(!$self->authInfo->{id}) { p class => 'locked'; - lit 'You need to be <a href="/u/login">logged in</a> to edit this page'; + lit mt '_itemmsg_login', '/u/login'; end; } elsif(!$self->authCan('edit')) { - p class => 'locked', "You're not allowed to edit this page"; + p class => 'locked', mt '_itemmsg_denied'; } } @@ -441,11 +432,11 @@ sub htmlVoteStats { div class => 'votestats'; table class => 'votegraph'; thead; Tr; - td colspan => 2, 'Vote graph'; + td colspan => 2, mt '_votestats_title'; end; end; tfoot; Tr; - td colspan => 2, sprintf '%d vote%s total, average %.2f%s', $count, $count != 1 ? 's' : '', $total/$count, - $type eq 'v' ? ' ('.$self->{votes}[ceil($total/$count-1)].')' : ''; + td colspan => 2, mt('_votestats_sum', $count, sprintf('%.2f', $total/$count)) + .($type eq 'v' ? ' ('.mt('_vote_'.ceil($total/$count-1)).')' : ''); end; end; for (reverse 0..$#$stats) { Tr; @@ -464,11 +455,12 @@ sub htmlVoteStats { order => 'date DESC', what => $type eq 'v' ? 'user' : 'vn', hide => $type eq 'v', + hide_ign => $type eq 'v', ); if(@$recent) { table class => 'recentvotes'; thead; Tr; - td colspan => 3, 'Recent votes'; + td colspan => 3, mt '_votestats_recent'; end; end; for (0..$#$recent) { Tr $_ % 2 == 0 ? (class => 'odd') : (); @@ -480,7 +472,7 @@ sub htmlVoteStats { } end; td $recent->[$_]{vote}; - td date $recent->[$_]{date}; + td $self->{l10n}->date($recent->[$_]{date}); end; } end; @@ -489,8 +481,8 @@ sub htmlVoteStats { clearfloat; if($type eq 'v') { div; - h3 'Popularity'; - p sprintf 'Ranked #%d out of %d with a score of %.2f.', $obj->{ranking}, $self->{stats}{vn}, $obj->{c_popularity}*100; + h3 mt '_votestats_pop_title'; + p mt '_votestats_pop_sum', $obj->{ranking}, $self->{stats}{vn}, sprintf('%0.2f',$obj->{c_popularity}*100); end; } end; @@ -506,10 +498,10 @@ sub htmlHistory { pageurl => $url, class => 'history', header => [ - sub { td colspan => 2, class => 'tc1', 'Rev.' }, - [ 'Date' ], - [ 'User' ], - sub { td; a href => '#', id => 'history_comments', 'expand'; txt 'Page'; end; } + sub { td colspan => 2, class => 'tc1', mt '_hist_col_rev' }, + [ mt '_hist_col_date' ], + [ mt '_hist_col_user' ], + sub { td; a href => '#', id => 'history_comments', 'expand'; txt mt '_hist_col_page'; end; } ], row => sub { my($s, $n, $i) = @_; @@ -523,9 +515,9 @@ sub htmlHistory { td class => 'tc1_2'; a href => $revurl, ".$i->{rev}"; end; - td class => 'tc2', date $i->{added}; + td class => 'tc2', $self->{l10n}->date($i->{added}); td class => 'tc3'; - lit userstr($i); + lit $self->{l10n}->userstr($i); end; td; a href => $revurl, title => $i->{ioriginal}, shorten $i->{ititle}, 80; @@ -548,14 +540,14 @@ sub htmlSearchBox { fieldset class => 'search'; p class => 'searchtabs'; - a href => '/v/all', $sel eq 'v' ? (class => 'sel') : (), 'Visual novels'; - a href => '/r', $sel eq 'r' ? (class => 'sel') : (), 'Releases'; - a href => '/p/all', $sel eq 'p' ? (class => 'sel') : (), 'Producers'; - a href => '/g', $sel eq 'g' ? (class => 'sel') : (), 'Tags'; - a href => '/u/all', $sel eq 'u' ? (class => 'sel') : (), 'Users'; + a href => '/v/all', $sel eq 'v' ? (class => 'sel') : (), mt '_searchbox_vn'; + a href => '/r', $sel eq 'r' ? (class => 'sel') : (), mt '_searchbox_releases'; + a href => '/p/all', $sel eq 'p' ? (class => 'sel') : (), mt '_searchbox_producers'; + a href => '/g', $sel eq 'g' ? (class => 'sel') : (), mt '_searchbox_tags'; + a href => '/u/all', $sel eq 'u' ? (class => 'sel') : (), mt '_searchbox_users'; end; input type => 'text', name => 'q', id => 'q', class => 'text', value => $v; - input type => 'submit', class => 'submit', value => 'Search!'; + input type => 'submit', class => 'submit', value => mt '_searchbox_submit'; end; } diff --git a/lib/VNDB/Util/FormHTML.pm b/lib/VNDB/Util/FormHTML.pm index f8047c4c..2882b4c4 100644 --- a/lib/VNDB/Util/FormHTML.pm +++ b/lib/VNDB/Util/FormHTML.pm @@ -6,58 +6,11 @@ use warnings; use YAWF ':html'; use Exporter 'import'; use POSIX 'strftime'; +use VNDB::Func; our @EXPORT = qw| htmlFormError htmlFormPart htmlForm |; -# form error messages -my %formerr_names = ( - alias => 'Aliases', - anime => 'Anime', - desc => 'Description', - description => 'Description', - editsum => 'Edit summary', - gtin => 'JAN/EAN/UPC', - lang => 'Language', - language => 'Language', - length => 'Length', - l_wp => 'Wikipedia link', - l_encubed => 'Novelnews link', - l_renai => 'Renai.us link', - l_vnn => 'V-N.net link', - mail => 'Email', - media => 'Media', - minage => 'Age rating', - msg => 'Message', - name => 'Name', - notes => 'Notes', - original => 'Original', - platforms => 'Platforms', - producers => 'Producers', - released => 'Release date', - boards => 'Boards', - title => 'Title', - type => 'Type', - usrname => 'Username', - usrpass => 'Password', - usrpass2 => 'Password (confirm)', - vn => 'Visual novels', - website => 'Website', -); -my %formerr_exeptions = ( - login_failed => 'Invalid username or password', - nomail => 'No user found with that email address', - passmatch => 'Passwords do not match', - usrexists => 'Someone already has this username, please choose something else', - mailexists => 'Someone already registered with that email address', - noimage => 'Image must be in JPEG or PNG format', - toolarge => 'Image is too large, only 500kB allowed', - oneaday => 'You can only register one account from the same IP within 24 hours', - nochanges => 'No changes, please don\'t create an entry that is fully -identical- to another', - doublepost => 'Please wait 30 seconds before making another post', -); - - # Displays friendly error message when form validation failed # Argument is the return value of formValidate, and an optional # argument indicating whether we should create a special mainbox @@ -67,40 +20,25 @@ sub htmlFormError { return if !$frm->{_err}; if($mainbox) { div class => 'mainbox'; - h1 'Error'; + h1 mt '_formerr_title'; } div class => 'warning'; - h2 'Form could not be sent:'; + h2 mt '_formerr_subtitle'; ul; for my $e (@{$frm->{_err}}) { if(!ref $e) { - li $formerr_exeptions{$e}; + li mt '_formerr_e_'.$e; next; } my($field, $type, $rule) = @$e; - $field = $formerr_names{$field}||$field; - li sprintf '%s is a required field!', $field if $type eq 'required'; - li sprintf '%s should have at least %d characters', $field, $rule if $type eq 'minlength'; - li sprintf '%s: only %d characters allowed', $field, $rule if $type eq 'maxlength'; - li sprintf '%s must be one of the following: %s', $field, join ', ', @$rule if $type eq 'enum'; - li sprintf 'Wrong board: %s', $rule if $type eq 'wrongboard'; - if($type eq 'tagexists') { - li; - lit $rule->{state} != 1 ? qq|Tag <a href="/g$rule->{id}">$rule->{name}</a> already exists!| - : qq|A tag <a href="/g$rule->{id}">with the same name</a> has been deleted in the past,| - .qq| please use <a href="/t/db">the discussion board</a> if you want it to be re-added.|; - end; - } + li mt '_formerr_required', $field if $type eq 'required'; + li mt '_formerr_minlength', $field, $rule if $type eq 'minlength'; + li mt '_formerr_maxlength', $field, $rule if $type eq 'maxlength'; + li mt '_formerr_enum', $field, join ', ', @$rule if $type eq 'enum'; + li mt '_formerr_wrongboard', $rule if $type eq 'wrongboard'; + li mt '_formerr_tagexists', "/g$rule->{id}", $rule->{name} if $type eq 'tagexists'; li $rule->[1] if $type eq 'func' || $type eq 'regex'; - if($type eq 'template') { - li sprintf - $rule eq 'mail' ? 'Invalid email address' : - $rule eq 'url' ? '%s: Invalid URL' : - $rule eq 'asciiprint' ? '%s may only contain ASCII characters' : - $rule eq 'int' ? '%s: Not a valid number' : - $rule eq 'pname' ? '%s can only contain lowercase alphanumberic characters and a hyphen, and must start with a character' : '', - $field; - } + li mt "_formerr_tpl_$rule", $field if $type eq 'template'; } end; end; @@ -246,21 +184,20 @@ sub htmlForm { if(@subs > 2) { ul class => 'maintabs notfirst', id => 'jt_select'; for (0..$#subs/2) { - (my $short = lc $subs[$_*2]) =~ s/[^\w\d]+/_/g; li class => 'left'; - a href => "#$short", id => "jt_sel_$short", $subs[$_*2]; + a href => "#$subs[$_*2]", id => "jt_sel_$subs[$_*2]", $subs[$_*2+1][0]; end; } li class => 'left'; - a href => '#all', id => 'jt_sel_all', 'All items'; + a href => '#all', id => 'jt_sel_all', mt '_form_tab_all'; end; end; } # form subs - while(my($name, $parts) = (shift(@subs), shift(@subs))) { - last if !$name || !$parts; - (my $short = lc $name) =~ s/[^\w\d]+/_/g; + while(my($short, $parts) = (shift(@subs), shift(@subs))) { + last if !$short || !$parts; + my $name = shift @$parts; div class => 'mainbox', id => 'jt_box_'.$short; h1 $name; fieldset; @@ -273,23 +210,26 @@ sub htmlForm { } # edit summary / submit button - div class => 'mainbox'; - fieldset class => 'submit'; - if($options->{editsum}) { - (my $txt = $options->{frm}{editsum}||'') =~ s/&/&/; - $txt =~ s/</</; - $txt =~ s/>/>/; - h2 'Edit summary'; - textarea name => 'editsum', id => 'editsum', rows => 4, cols => 50; - lit $txt; - end; - br; - } - b "Don't forget! -> " if $options->{hitsubmit}; - input type => 'submit', value => 'Submit', class => 'submit'; - b ' <-' if $options->{hitsubmit}; - end; - end; + if(!$options->{nosubmit}) { + div class => 'mainbox'; + fieldset class => 'submit'; + if($options->{editsum}) { + (my $txt = $options->{frm}{editsum}||'') =~ s/&/&/; + $txt =~ s/</</; + $txt =~ s/>/>/; + h2; + txt mt '_form_editsum'; + b class => 'standout', ' ('.mt('_inenglish').')'; + end; + textarea name => 'editsum', id => 'editsum', rows => 4, cols => 50; + lit $txt; + end; + br; + } + input type => 'submit', value => mt('_form_submit'), class => 'submit'; + end; + end; + } end; } diff --git a/lib/VNDB/Util/LayoutHTML.pm b/lib/VNDB/Util/LayoutHTML.pm index b5813330..85971ba9 100644 --- a/lib/VNDB/Util/LayoutHTML.pm +++ b/lib/VNDB/Util/LayoutHTML.pm @@ -38,7 +38,7 @@ sub htmlHeader { # %options->{ title, js, noindex, search } div id => 'bgright', ' '; div id => 'header'; h1; - a href => '/', lc $self->{site_title}; + a href => '/', lc mt '_site_title'; end; end; @@ -54,24 +54,34 @@ sub _menu { div id => 'menulist'; div class => 'menubox'; - h2 'Menu'; + h2; + span; + for (grep $self->{l10n}->language_tag() ne $_, $self->{l10n}->languages()) { + a href => "?l10n=$_"; + cssicon "lang $_", mt "_lang_$_"; # NOTE: should actually be in the destination language... + end; + } + end; + txt mt '_menu'; + end; div; - a href => '/', 'Home'; br; - a href => '/v/all', 'Visual novels'; br; - a href => '/r', 'Releases'; br; - a href => '/p/all', 'Producers'; br; - a href => '/g', 'Tags'; br; - a href => '/u/all', 'Users'; br; - a href => '/hist', 'Recent changes'; br; - a href => '/t', 'Discussion board'; br; - a href => '/d6', 'FAQ'; br; + a href => '/', mt '_menu_home'; br; + a href => '/v/all', mt '_menu_vn'; br; + a href => '/r', mt '_menu_releases'; br; + a href => '/p/all', mt '_menu_producers'; br; + a href => '/g', mt '_menu_tags'; br; + a href => '/u/all', mt '_menu_users'; br; + a href => '/hist', mt '_menu_recent_changes'; br; + a href => '/t', mt '_menu_discussion_board'; br; + a href => '/d6', mt '_menu_faq'; br; + a href => '/v/rand', mt '_menu_randvn'; br; a href => 'irc://irc.synirc.net/vndb', '#vndb'; - lit ' (<a href="http://cgiirc.synirc.net/?chan=%23vndb">webchat</a>)'; + lit ' (<a href="http://cgiirc.synirc.net/?chan=%23vndb">'.mt('_menu_webchat').'</a>)'; end; form action => '/v/all', method => 'get', id => 'search'; fieldset; legend 'Search'; - input type => 'text', class => 'text', id => 'sq', name => 'sq', value => $o{search}||'search'; + input type => 'text', class => 'text', id => 'sq', name => 'sq', value => $o{search}||mt('_menu_emptysearch'); input type => 'submit', class => 'submit', value => 'Search'; end; end; @@ -82,24 +92,25 @@ sub _menu { my $uid = sprintf '/u%d', $self->authInfo->{id}; h2; a href => $uid, ucfirst $self->authInfo->{username}; - txt ' ('.$self->{user_ranks}[$self->authInfo->{rank}][0].')'; + # note: user ranks aren't TL'ed (but might be in the future, hmm) + txt ' ('.mt('_urank_'.$self->authInfo->{rank}).')'; end; div; - a href => "$uid/edit", 'My Profile'; br; - a href => "$uid/list", 'My Visual Novel List'; br; - a href => "$uid/wish", 'My Wishlist'; br; - a href => "/t$uid", sprintf 'My Messages (%d)', $self->authInfo->{mymessages}; br; - a href => "$uid/hist", 'My Recent Changes'; br; - a href => "$uid/tags", 'My Tags'; br; + a href => "$uid/edit", mt '_menu_myprofile'; br; + a href => "$uid/list", mt '_menu_myvnlist'; br; + a href => "$uid/wish", mt '_menu_mywishlist'; br; + a href => "/t$uid", mt '_menu_mymessages', $self->authInfo->{mymessages}; br; + a href => "$uid/hist", mt '_menu_mychanges'; br; + a href => "$uid/tags", mt '_menu_mytags'; br; br; - a href => '/v/new', 'Add Visual Novel'; br; - a href => '/p/new', 'Add Producer'; br; + a href => '/v/new', mt '_menu_addvn'; br; + a href => '/p/new', mt '_menu_addproducer'; br; br; - a href => '/u/logout', 'Logout'; + a href => '/u/logout', mt '_menu_logout'; end; } else { h2; - a href => '/u/login', 'Login'; + a href => '/u/login', mt '_menu_login'; end; div; form action => '/nospam?/u/login', id => 'loginform', method => 'post'; @@ -107,32 +118,23 @@ sub _menu { legend 'Login'; input type => 'text', class => 'text', id => 'username', name => 'usrname'; input type => 'password', class => 'text', id => 'userpass', name => 'usrpass'; - input type => 'submit', class => 'submit', value => 'Login'; + input type => 'submit', class => 'submit', value => mt '_menu_login'; end; end; p; - lit 'Need to <a href="/u/register">register</a>,<br />'; - lit 'or <a href="/u/newpass">forgot your password?</a>'; + lit mt '_menu_loginmsg', '/u/register', '/u/newpass'; end; end; } end; - my @stats = ( - [ vn => 'Visual Novels' ], - [ releases => 'Releases' ], - [ producers => 'Producers' ], - [ users => 'Users' ], - [ threads => 'Threads' ], - [ posts => 'Posts' ], - ); div class => 'menubox'; - h2 'Database Statistics'; + h2 mt '_menu_dbstats'; div; dl; - for (@stats) { - dt $$_[1]; - dd $self->{stats}{$$_[0]}; + for (qw|vn releases producers users threads posts|) { + dt mt "_menu_stat_$_"; + dd $self->{stats}{$_}; } end; clearfloat; @@ -154,11 +156,11 @@ sub htmlFooter { } txt "vndb $self->{version} | "; - a href => '/d7', 'about us'; + a href => '/d7', mt '_footer_aboutus'; txt ' | '; a href => "mailto:$self->{admin_email}", $self->{admin_email}; txt ' | '; - a href => $self->{source_url}, 'source'; + a href => $self->{source_url}, mt '_footer_source'; end; end; # /div maincontent end; # /body |