summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog23
-rw-r--r--README59
-rw-r--r--data/docs/1194
-rw-r--r--data/docs/218
-rw-r--r--data/docs/index1
-rw-r--r--data/global.pl131
-rw-r--r--data/lang.txt3310
-rw-r--r--data/style.css91
-rw-r--r--lib/VNDB/DB/ULists.pm8
-rw-r--r--lib/VNDB/DB/Users.pm4
-rw-r--r--lib/VNDB/Func.pm75
-rw-r--r--lib/VNDB/Handler/Discussions.pm102
-rw-r--r--lib/VNDB/Handler/Misc.pm261
-rw-r--r--lib/VNDB/Handler/Producers.pm69
-rw-r--r--lib/VNDB/Handler/Releases.pm300
-rw-r--r--lib/VNDB/Handler/Tags.pm281
-rw-r--r--lib/VNDB/Handler/ULists.pm69
-rw-r--r--lib/VNDB/Handler/Users.pm225
-rw-r--r--lib/VNDB/Handler/VNBrowse.pm67
-rw-r--r--lib/VNDB/Handler/VNEdit.pm92
-rw-r--r--lib/VNDB/Handler/VNPage.pm164
-rw-r--r--lib/VNDB/L10N.pm189
-rw-r--r--lib/VNDB/Util/Auth.pm2
-rw-r--r--lib/VNDB/Util/CommonHTML.pm164
-rw-r--r--lib/VNDB/Util/FormHTML.pm132
-rw-r--r--lib/VNDB/Util/LayoutHTML.pm84
-rw-r--r--static/f/forms.js28
-rw-r--r--static/f/icons.pngbin7526 -> 9403 bytes
-rw-r--r--static/f/script.js15
-rw-r--r--util/dump.sql16
-rw-r--r--util/updates/update_2.7.sql108
-rwxr-xr-xutil/vndb.pl11
32 files changed, 4787 insertions, 1506 deletions
diff --git a/ChangeLog b/ChangeLog
index 190f96af..2a269f21 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,26 @@
+2.7 - 2009-09-24
+ - Improved styling of the threeboxes layout
+ - Blacklist a users' votes from the VN vote statistics
+ - usermods can browse a users' votes and list even when they are hidden
+ - More sensible placing of the submit button on /v+/tagmod
+ - Improved VN relations:
+ - Removed: summary, full story
+ - Added: same series, fandisc, original game
+ - Renamed: same characters to shares characters
+ - Merged: alternative setting into alternative version, and other into same series
+ - Allow empty VN descriptions
+ - New platforms: DOS, PC-98, Sega Saturn
+ - Box titles on homepage are click-able
+ - Russian translation of the interface
+ - Random VN link in menu
+ - Ignore some release fields when the patch status is checked
+ - Batch edit downloadable trial releases to add freeware status
+ - Remind the user to type English in several form fields
+ - Full reply button in Quick reply box + larger textarea in post form
+ - Removed visual-novels.net link from the interface
+ - Fixed bug with excluding AVG(vote) < 0 VNs from tag pages
+ - Allow media quantity up to 20 instead of 10
+
2.6 - 2009-08-09
- New screen resolutions: 1024x600 and 1600x1200
- Rewritten authentication system
diff --git a/README b/README
new file mode 100644
index 00000000..5177b6e2
--- /dev/null
+++ b/README
@@ -0,0 +1,59 @@
+The VNDB.org Source Code
+------------------------
+
+
+Installation & documentation
+
+ Documentation is lacking, you're pretty much on your own if you want to
+ get things running. :-(
+
+
+Requirements
+
+ global requirements:
+ Linux, or an OS that resembles Linux. Chances are VNDB won't run on Windows.
+ PostgreSQL 8.3 recommended, 8.1 may also work
+ perl 5.10 recommended, 5.8 may also work
+ A webserver that works with YAWF (lighttpd and Apache are known to work)
+
+ (perl 5.10 core modules are not listed.)
+
+ util/vndb.pl:
+ Algorithm::Diff::XS
+ CGI::Minimal (required by YAWF)
+ CGI::Cookie::XS (required by YAWF)
+ DBI
+ DBD::Pg
+ FCGI (optional, for running as a FastCGI script)
+ PerlIO::gzip (optional, for output compression)
+
+ util/multi.pl:
+ Core:
+ DBI
+ DBD::Pg
+ POE
+ POE::Component::Pg (get it from http://g.blicky.net/poco-pg.git/)
+ IRC:
+ POE::Component::IRC
+ URI::Escape
+ Image:
+ Image::Magick
+ Maintenance:
+ PerlIO::gzip
+ RG:
+ graphviz (/usr/bin/dot is used by default)
+ Sitemap:
+ XML::Writer
+ PerlIO::gzip
+
+
+Contact
+
+ IRC: #vndb @ irc.synirc.net
+ Email: contact@vndb.org
+
+
+License
+
+ GNU AGPL, see COPYING file for details.
+
diff --git a/data/docs/1 b/data/docs/1
deleted file mode 100644
index c6d7914c..00000000
--- a/data/docs/1
+++ /dev/null
@@ -1,194 +0,0 @@
-:TITLE:Categories
-<div class="warning">
- The category system has been replaced with a new tagging system. This page
- is still available for reference, but will be removed in a later site update.
-</div>
-:INC:index
-
-
-:SUB:Elements
-<p>
- The contents of the story and the general feeling the characters give.
-</p>
-<dl>
- <dt>Action</dt><dd>
- The story has fast paced scenes and plenty of explosions or gun battles.
- Or lots of running...
- </dd><dt>Comedy</dt><dd>
- The story is light-hearted and makes you laugh, either by the un-seriousness
- or by character design.
- </dd><dt>Drama</dt><dd>
- The story takes a very serious path, often involving real-life scenarios
- in which you could relate to.
- A slice-of-life, with it's ups and downs.
- </dd><dt>Fantasy</dt><dd>
- Is it actually possible? Plenty of <i>Mahou Shoujo</i> and transformations,
- parallel universes and witches abound! Maybe pigs fly...
- </dd><dt>Horror</dt><dd>
- Plenty of blood and guts laying around. The guy with the knife looks
- suspicious.
- </dd><dt>Mystery</dt><dd>
- Suspense, tension and a little bit of morning dew makes a mystery game.
- Something has happened, so work it out! Alternatively, hide under a rock.
- </dd><dt>Romance</dt><dd>
- Pull some heart-strings, get intimate with another character, and have some fun!
- <br />
- <b>Note</b>: Romance does not imply that sexual intercourse takes place. Likewise,
- Sexual Content does not imply romantic intercourse takes place (for instance,
- rape).
- </dd><dt>School Life</dt><dd>
- Obviously, a school is needed.
- It seems like a chore to us, but the characters like learning.
- Maybe it's the people they meet? Or the lunch they share...
- </dd><dt>SciFi</dt><dd>
- Science Fiction. Using intelligence and building something not available
- today but certainly possible in real-life.
- Lasers are ficticious...
- </dd><dt>Shoujo Ai</dt><dd>
- Romance between two (or more) female characters.
- </dd><dt>Shounen Ai</dt><dd>
- Romance between two (or more) male characters. Love has no boundaries.
- </dd>
-</dl>
-
-
-:SUB:Gameplay
-<p>
- This category is used to describe the gameplay or game engine.
-</p>
-<dl>
- <dt>NVL</dt><dd>
- Also known as Visual Novel or Novel Style.
- All games where the text is overlaid on the background and there is no special
- dialog-box fall under this category.
- </dd><dt>ADV</dt><dd>
- The text is presented in a special window, usually at the bottom of the screen.
- Also known as Adventure or AVG.
- In some (rare) cases a game will switch between both styles, for these games both
- the <i>NVL</i> and <i>ADV</i> categories should be selected.
- </dd><dt>Action</dt><dd>
- This category indicates that the game includes a gameplay that challenges the
- player's speed, dexterity and reaction time. Common examples are fighting games,
- puzzles that should be solved within a short time limit, and shooter games.
- </dd><dt>RPG</dt><dd>
- Abbreviation for Role Playing Game. An RPG is a game in which you assume the
- role of a character introduced to a vast world to be explored. Games typically
- place emphasis on gaining equipment and experience points through fighting enemies
- in order to advance through different levels.
- </dd><dt>Strategy</dt><dd>
- A strategy game is one that challenges the player to think critically in order
- to achieve victory.
- </dd><dt>Simulation</dt><dd>
- A simulation game attempts to recreate aspects of reality and puts the player in
- control.
- </dd>
-</dl>
-
-
-:SUB:Plot
-<p>
- Indicates the plot type of a game. There are only two options: <i>Branching</i> and
- <i>Linear</i>.
-</p>
-<dl>
- <dt>Linear</dt><dd>
- A game with a linear plot has a static story; it is not possible to get different paths
- or endings. Many games in this category do not prompt the player with choices and simply
- tell the story as it is. This is, however, not a rule: it is also possible for a game
- to provide choices, but they have no influence on the story itself. (e.g.
- <a href="/v3">Utawarerumono</a>)
- </dd><dt>Branching</dt><dd>
- A game with a branching plot has a story whose path is directly affected by choices
- made by the player during the game. These different paths are sometimes referred to
- as "arcs" when they pertain to the stories of different female characters within a game.
- </dd>
-</dl>
-
-
-:SUB:Time
-<p>
- Indicates the time period in which the story has been set.
-</p>
-<dl>
- <dt>Future</dt><dd>
- The game is set in a time beyond that of our own. Games may incorperate elements of
- future technologies or events yet-to-come.
- </dd><dt>Present</dt><dd>
- The game is set in the current day.
- </dd><dt>Past</dt><dd>
- The game is set in a time before our own. Games may or may not adhere to historic fact.
- </dd>
-</dl>
-
-
-:SUB:Place
-<p>
- Indicates the place in which the story is told.
-</p>
-<dl>
- <dt>Earth</dt><dd>
- The game takes place on our own planet.
- Most continents, countries and cities featured are portrayed as they are
- or have been on Earth.
- Stories set in ficticious cities based on real cities in recognised countries
- take place on Earth.
- </dd><dt>Fantasy World</dt><dd>
- The game takes place on another world. The game's environment could be similar
- to that of our own with a few significant changes, but it could also be
- radically different.
- </dd><dt>Space</dt><dd>
- The game takes place in the vacuum of space between celestial bodies. For example,
- this category can be used to define games where the characters may inhabit
- spaceships that journey across the universe.
- </dd>
-</dl>
-
-
-:SUB:Protagonist
-<p>
- Indicates some information about the character the player takes control of.
- A combination of male and female protagonists is possible, e.g. multiple
- protagonists.
- In cases of sex change or transsexuality, use both.
-</p>
-<dl>
- <dt>Male</dt><dd>
- The protagonist is male.
- </dd><dt>Female</dt><dd>
- The protagonist is female.
- </dd>
-</dl>
-
-
-:SUB:Sexual content
-<p>
- Indicates the types of sexual content that the game contains.
-</p>
-<dl>
- <dt>Sexual content</dt><dd>
- This is a generic category to indicate the presence of any sexual content in the
- game. If there is any such content, this category should be selected.
- </dd><dt>Bestiality</dt><dd>
- Sexual activity between characters and animals.<br />
- Also includes tentacles.
- </dd><dt>Incest</dt><dd>
- Sexual activity between members of the same family. Most of the time under the
- justification of participants not blood related (step-sister etc.).
- </dd><dt>Lolicon</dt><dd>
- The usage of female characters with childlike features in sexual situations.
- </dd><dt>Shotacon</dt><dd>
- The usage of male characters with childlike features in sexual situations.
- </dd><dt>Yaoi</dt><dd>
- Sexual content depicting activity between males.
- This does not imply Shounen Ai.
- <br />
- Also known as: Boys' Love.
- </dd><dt>Yuri</dt><dd>
- Sexual content depicting activity between females.
- This does not imply Shoujo Ai (for example, threesomes).
- </dd><dt>Rape</dt><dd>
- Situation in which a character is made to engage in sexual activities against
- their will.
- </dd>
-</dl>
-
diff --git a/data/docs/2 b/data/docs/2
index 5b81ce10..3f402aab 100644
--- a/data/docs/2
+++ b/data/docs/2
@@ -140,24 +140,20 @@
The definition of "setting" is not always easy to define, but usually it
means that if places or items not existing in the real world described in one
game also exist in the other game, you could use this relation.
- </dd><dt>Alternative setting</dt><dd>
- Same characters and (mostly) the same story, but set in a different universe,
- world, reality or timeline.
</dd><dt>Alternative version</dt><dd>
Same setting, same characters, but the story is told differently.
- </dd><dt>Same characters</dt><dd>
+ </dd><dt>Shares characters</dt><dd>
Different story, but shares some characters.
</dd><dt>Side story</dt><dd>
The story takes place sometime during the parent storyline. &lt;=&gt;<i>Parent story</i>
</dd><dt>Parent story</dt><dd>
Opposite of <i>Side story</i>.
- </dd><dt>Summary</dt><dd>
- Summarizes full story, may contain additional stuff. &lt;=&gt;<i>Full story</i>.
- </dd><dt>Full story</dt><dd>
- Full version of the summarized story. &lt;=&gt;<i>Summary</i>.
- </dd><dt>Other</dt><dd>
- There is a relation, but it's not possible to describe it using the other available
- options. This is a relation that should rarely be used.
+ </dd><dt>Same series</dt><dd>
+ The games are part of the same series.
+ </dd><dt>Fandisc</dt><dd>
+ <a href="http://en.wikipedia.org/wiki/Fan_disc">Fandisc</a>.
+ </dd><dt>Original game</dt><dd>
+ The opposite of fandisc.
</dd>
</dl>
diff --git a/data/docs/index b/data/docs/index
index 79d751af..0c94b71f 100644
--- a/data/docs/index
+++ b/data/docs/index
@@ -1,7 +1,6 @@
<ul class="index">
<li><b>Guidelines</b></li>
<li><a href="/d5">Editing guidelines</a></li>
- <li><a href="/d1">Categories</a></li>
<li><a href="/d2">Visual Novels</a></li>
<li><a href="/d3">Releases</a></li>
<li><a href="/d4">Producers</a></li>
diff --git a/data/global.pl b/data/global.pl
index 673c3777..4ab04d5b 100644
--- a/data/global.pl
+++ b/data/global.pl
@@ -17,63 +17,24 @@ our %S = (%S,
version => `cd $VNDB::ROOT; git describe` =~ /^(.+)$/ && $1,
url => 'http://vndb.org',
url_static => 'http://s.vndb.org',
- site_title => 'Yet another VNDB clone',
skin_default => 'angel',
cookie_domain => '.vndb.org',
global_salt => 'any-private-string-here',
source_url => 'http://git.blicky.net/vndb.git/?h=master',
admin_email => 'contact@vndb.org',
- sharedmem_key => 'VNDB',
user_ranks => [
- # rankname allowed actions # DB number
- [qw| visitor hist |], # 0
- [qw| banned hist |], # 1
- [qw| loser hist board |], # 2
- [qw| user hist board edit tag |], # 3
- [qw| mod hist board boardmod edit tag mod lock del tagmod |], # 4
- [qw| admin hist board boardmod edit tag mod lock del tagmod usermod |], # 5
- ],
- languages => {
- cs => q|Czech|,
- da => q|Danish|,
- de => q|German|,
- en => q|English|,
- es => q|Spanish|,
- fi => q|Finnish|,
- fr => q|French|,
- it => q|Italian|,
- ja => q|Japanese|,
- ko => q|Korean|,
- nl => q|Dutch|,
- no => q|Norwegian|,
- pl => q|Polish|,
- pt => q|Portuguese|,
- ru => q|Russian|,
- sv => q|Swedish|,
- tr => q|Turkish|,
- vi => q|Vietnamese|,
- zh => q|Chinese|,
- },
- producer_types => {
- co => 'Company',
- in => 'Individual',
- ng => 'Amateur group',
- },
- discussion_boards => {
- an => 'Announcements', # 0 - usage restricted to boardmods
- db => 'VNDB Discussions', # 0
- v => 'Visual novels', # vid
- p => 'Producers', # pid
- u => 'Users', # uid
- },
- vn_lengths => [
- [ 'Unknown', '', '' ],
- [ 'Very short', '< 2 hours', 'OMGWTFOTL, A Dream of Summer' ],
- [ 'Short', '2 - 10 hours', 'Narcissu, Planetarian' ],
- [ 'Medium', '10 - 30 hours', 'Kana: Little Sister' ],
- [ 'Long', '30 - 50 hours', 'Tsukihime' ],
- [ 'Very long', '> 50 hours', 'Clannad' ],
+ # allowed actions # DB number
+ [qw| hist |], # 0
+ [qw| hist |], # 1
+ [qw| hist board |], # 2
+ [qw| hist board edit tag |], # 3
+ [qw| hist board boardmod edit tag mod lock del tagmod |], # 4
+ [qw| hist board boardmod edit tag mod lock del tagmod usermod |], # 5
],
+ languages => [qw|cs da de en es fi fr it ja ko nl no pl pt ru sv tr vi zh|],
+ producer_types => [qw|co in ng|],
+ discussion_boards => [qw|an db v p u|],
+ vn_lengths => [ 0..5 ],
anime_types => [
# AniDB anime type starts counting at 1, 0 = unknown
# we start counting at 0, with NULL being unknown
@@ -90,14 +51,13 @@ our %S = (%S,
[ 'Sequel', 0 ],
[ 'Prequel', 1 ],
[ 'Same setting', 0 ],
- [ 'Alternative setting', 0 ],
[ 'Alternative version', 0 ],
- [ 'Same characters', 0 ],
+ [ 'Shares characters', 0 ],
[ 'Side story', 0 ],
[ 'Parent story', 1 ],
- [ 'Summary', 0 ],
- [ 'Full story', 1 ],
- [ 'Other', 0 ],
+ [ 'Same series', 0 ],
+ [ 'Fandisc', 0 ],
+ [ 'Original game', 1 ],
],
age_ratings => {
-1 => [ 'Unknown' ],
@@ -116,30 +76,8 @@ our %S = (%S,
17 => [ '17+', 'CERO D' ],
18 => [ '18+', 'CERO Z' ],
},
- release_types => [
- 'Complete',
- 'Partial',
- 'Trial'
- ],
- platforms => {
- win => 'Windows',
- lin => 'Linux',
- mac => 'Mac OS',
- dvd => 'DVD Player',
- gba => "Game Boy Advance",
- msx => 'MSX',
- nds => 'Nintendo DS',
- nes => 'Famicom',
- psp => 'Playstation Portable',
- ps1 => 'Playstation 1',
- ps2 => 'Playstation 2',
- ps3 => 'Playstation 3',
- drc => 'Dreamcast',
- sfc => 'Super Nintendo',
- wii => 'Nintendo Wii',
- xb3 => 'Xbox 360',
- oth => 'Other'
- },
+ release_types => [0..2],
+ platforms => [qw|win dos lin mac dvd gba msx nds nes p98 psp ps1 ps2 ps3 drc sat sfc wii xb3 oth|],
media => {
#DB display qty
cd => [ 'CD', 1 ],
@@ -167,38 +105,9 @@ our %S = (%S,
[ '1280x720 (720p)', 'widescreen' ],
[ '1920x1080 (1080p)', 'widescreen' ],
],
- voiced => [
- 'Unknown',
- 'Not voiced',
- 'Only ero scenes voiced',
- 'Partially voiced',
- 'Fully voiced',
- ],
- animated => [
- 'Unknown',
- 'No animations',
- 'Simple animations',
- 'Some fully animated scenes',
- 'All scenes fully animated',
- ],
- votes => [
- 'worst ever',
- 'awful',
- 'bad',
- 'weak',
- 'so-so',
- 'decent',
- 'good',
- 'very good',
- 'excellent',
- 'masterpiece',
- ],
- wishlist_status => [
- 'high',
- 'medium',
- 'low',
- 'blacklist',
- ],
+ voiced => [ 0..4 ],
+ animated => [ 0..4 ],
+ wishlist_status => [ 0..3 ],
# note: keep these synchronised in script.js
vn_rstat => [
'Unknown',
diff --git a/data/lang.txt b/data/lang.txt
new file mode 100644
index 00000000..c7267fe2
--- /dev/null
+++ b/data/lang.txt
@@ -0,0 +1,3310 @@
+This file contains all the translatable text of the VNDB interface.
+
+IMPORTANT: This file is encoded in UTF-8 and uses UNIX-style line endings,
+ make sure your text editor is configured to correctly handle this!
+
+The syntax of this file is as follows: empty lines, or lines beginning with a
+'#' are ignored by the parser. Each translatable piece of text is identified by
+a 'key', this is a short identifier starting with a colon and an underscore.
+The lines following such a key line specify which text to use for each
+language. A language text starts with the language tag, followed by a space or
+asterisk, followed by a colon, another space, and the text for that language.
+Here's an example of a line:
+
+ # this is a comment
+ :_relinfo_minage
+ en : Age rating
+ ru : Возрастной рейтинг
+ nl*:
+
+In this example, "_relinfo_minage" is the 'key', and the lines following are
+the translations. If a line does not have a translation for a particular
+language, the system will fall back to the English version (i.e. the string
+that starts with 'en :').
+
+Because VNDB is in constant development, changes to the interface text are
+inevitable, and the translations will get out of sync with the English text
+very quickly. To indicate which lines should be synchronised to their English
+counterparts, and asterisk is used between the language tag and the colon, as
+used in the above examples. As soon as a line is retranslated or synchronised
+again by the translator, (s)he should replace the asterick with a space to
+indicate that it has been checked.
+
+The text strings can contain some special formatting and text expansion codes.
+Such a code starts with '[', followed by a list of comma-separated options, and
+ends with ']'. The following options are supported:
+
+ [_x]
+ Insert argument x in here. The arguments differ for each string, and come
+ from the VNDB code. The English line would give an indication of the
+ supported arguments.
+
+ [age,{arg}]
+ Formats the date/time '{arg}' into an age string, e.g. '3 days ago'. Commonly
+ used as '[age,_1]'.
+
+ [br]
+ The classic HTML <br>, used to force a line break into the text.
+
+ [date,{arg}]
+ Formats the date '{arg}' into a readable date. Usually used as: [date,_1].
+
+ [date,{arg},full]
+ Formats the date and time '{arg}' into something readable. Usually used as:
+ [date,_1,full]
+
+ [datestr,{arg}]
+ Similar to [date], but used for release dates. (These are slightly different
+ from normal dates, as the month or year can be unknown).
+
+ [index,{idx},{list},..]
+ Displays the item in {list} numbered by {idx}. Often used when the same
+ sentence is used for different types of information. E.g.:
+ [index,_1,visual novel,release,producer]
+
+ [monthstr,{arg}]
+ Similar to [datestr], but doesn't display the day, only month+year.
+
+ [quant,{num},{singular},{plural}] (English)
+ Takes the correct form of a word depending on the quantity specified by {num}.
+ E.g.:
+ 2 [quant,2,room,rooms]
+ Will be displayed as:
+ 2 rooms
+ {num} is usually an argument (which is variable), otherwise it wouldn't
+ make much sense to use this format.
+
+ [quant,{num},{singular},{multi},{many}] (Russian)
+ Same as the english [quant] as above, but has three forms for a word
+ instead of two.
+
+ [url,{url},{title}]
+ Formats a link to another page, where {url} is the location of the page
+ and {title} the link title. {url} is usually an argument, e.g.:
+ [url,_1,discussion board]
+
+ [user,{arg}]
+ Formats a username+id into a link to the userpage.
+
+
+
+(Actual processing of this file begins after the following line)
+/intro
+
+
+
+
+#############################################################################
+## Global strings ##
+#############################################################################
+# data/global.pl - used in many places
+
+
+# user ranks
+
+:_urank_0
+en : visitor
+ru : посетитель
+
+:_urank_1
+en : banned
+ru : забанен
+
+:_urank_2
+en : loser
+ru : лузер
+
+:_urank_3
+en : user
+ru : пользователь
+
+:_urank_4
+en : mod
+ru : модератор
+
+:_urank_5
+en : admin
+ru : администратор
+
+
+# languages
+
+:_lang_cs
+en : Czech
+ru : Чешский
+
+:_lang_da
+en : Danish
+ru : Датский
+
+:_lang_de
+en : German
+ru : Немецкий
+
+:_lang_en
+en : English
+ru : Английский
+
+:_lang_es
+en : Spanish
+ru : Испанский
+
+:_lang_fi
+en : Finnish
+ru : Финский
+
+:_lang_fr
+en : French
+ru : Французский
+
+:_lang_it
+en : Italian
+ru : Итальянский
+
+:_lang_ja
+en : Japanese
+ru : Японский
+
+:_lang_ko
+en : Korean
+ru : Корейский
+
+:_lang_nl
+en : Dutch
+ru : Голландский
+
+:_lang_no
+en : Norwegian
+ru : Норвежский
+
+:_lang_pl
+en : Polish
+ru : Польский
+
+:_lang_pt
+en : Portuguese
+ru : Португальский
+
+:_lang_ru
+en : Russian
+ru : Русский
+
+:_lang_sv
+en : Swedish
+ru : Шведский
+
+:_lang_tr
+en : Turkish
+ru : Турецкий
+
+:_lang_vi
+en : Vietnamese
+ru : Вьетнамский
+
+:_lang_zh
+en : Chinese
+ru : Китайский
+
+
+# platforms
+# most of these probably don't need TL in most languages, but some
+# languages (i.e. Japanese) do use different names for products
+
+:_plat_win
+en : Windows
+ru : Windows
+
+:_plat_dos
+en : DOS
+ru : DOS
+
+:_plat_lin
+en : Linux
+ru : Linux
+
+:_plat_mac
+en : Mac OS
+ru : Mac OS
+
+:_plat_dvd
+en : DVD Player
+ru : DVD Плеер
+
+:_plat_gba
+en : Game Boy Advance
+ru : Game Boy Advance
+
+:_plat_msx
+en : MSX
+ru : MSX
+
+:_plat_nds
+en : Nintendo DS
+ru : Nintendo DS
+
+:_plat_nes
+en : Famicom
+ru : Famicom (Dendy)
+
+:_plat_p98
+en : PC-98
+ru : PC-98
+
+:_plat_psp
+en : Playstation Portable
+ru : Playstation Portable
+
+:_plat_ps1
+en : Playstation 1
+ru : Playstation 1
+
+:_plat_ps2
+en : Playstation 2
+ru : Playstation 2
+
+:_plat_ps3
+en : Playstation 3
+ru : Playstation 3
+
+:_plat_drc
+en : Dreamcast
+ru : Dreamcast
+
+:_plat_sat
+en : Sega Saturn
+ru : Sega Saturn
+
+:_plat_sfc
+en : Super Nintendo
+ru : Super Nintendo
+
+:_plat_wii
+en : Nintendo Wii
+ru : Nintendo Wii
+
+:_plat_xb3
+en : Xbox 360
+ru : Xbox 360
+
+:_plat_oth
+en : Other
+ru : Другая
+
+
+# producer types
+
+:_ptype_co
+en : Company
+ru : Компания
+
+:_ptype_in
+en : Individual
+ru : Частное лицо
+
+:_ptype_ng
+en : Amateur group
+ru : Любительская группа
+
+
+# release types
+
+:_rtype_0
+en : Complete
+ru : Полный
+
+:_rtype_1
+en : Partial
+ru : Частичный
+
+:_rtype_2
+en : Trial
+ru : Триальный
+
+
+# Discussion board types
+
+:_dboard_an
+en : Announcements
+ru : Объявления
+
+:_dboard_db
+en : VNDB Discussions
+ru : Форум VNDB
+
+:_dboard_v
+en : Visual novels
+ru : Новеллы
+
+:_dboard_p
+en : Producers
+ru : Компании
+
+:_dboard_u
+en : Users
+ru : Пользователи
+
+
+# Wishlist statuses
+
+:_wish_0
+en : high
+ru : высокий
+
+:_wish_1
+en : medium
+ru : средний
+
+:_wish_2
+en : low
+ru : низкий
+
+:_wish_3
+en : blacklist
+ru : в чёрном списке
+
+
+# 'Voiced' information for releases
+
+:_voiced_0
+en : Unknown
+ru : Неизвестно
+
+:_voiced_1
+en : Not voiced
+ru : Нет озвучки
+
+:_voiced_2
+en : Only ero scenes voiced
+ru : Озвучены лишь эросцены
+
+:_voiced_3
+en : Partially voiced
+ru : Частичная озвучка
+
+:_voiced_4
+en : Fully voiced
+ru : Озвучено целиком
+
+
+# 'Animated' information for releases
+
+:_animated_0
+en : Unknown
+ru : Неизвестно
+
+:_animated_1
+en : No animations
+ru : Без анимации
+
+:_animated_2
+en : Simple animations
+ru : Простая анимация
+
+:_animated_3
+en : Some fully animated scenes
+ru : Некоторые сцены анимированы целиком
+
+:_animated_4
+en : All scenes fully animated
+ru : Все сцены полностью анимированы
+
+
+# Rating indications
+
+:_vote_1
+en : worst ever
+ru : хуже некуда
+
+:_vote_2
+en : awful
+ru : ужасно
+
+:_vote_3
+en : bad
+ru : плохо
+
+:_vote_4
+en : weak
+ru : слабо
+
+:_vote_5
+en : so-so
+ru : так себе
+
+:_vote_6
+en : decent
+ru : неплохо
+
+:_vote_7
+en : good
+ru : хорошо
+
+:_vote_8
+en : very good
+ru : здорово
+
+:_vote_9
+en : excellent
+ru : отлично
+
+:_vote_10
+en : masterpiece
+ru : шедевр
+
+
+# VN lengths
+
+:_vnlength_0
+en : Unknown
+ru : Неизвестно
+
+:_vnlength_1
+en : Very short[index,_1,, (< 2 hours), (OMGWTHOTL~, A Dream of Summer)]
+ru : Очень короткая[index,_1,, (< 2 часов), (OMGWTFOTL~, A Dream Of Summer)]
+
+:_vnlength_2
+en : Short[index,_1,, (2 - 10 hours), (Narcissu~, Planetarian)]
+ru : Короткая[index,_1,, (2 - 10 часов), (Narcissu~, Planetarian)]
+
+:_vnlength_3
+en : Medium[index,_1,, (10 - 30 hours), (Kana: Little Sister)]
+ru : Средняя[index,_1,, (10 - 30 часов), (Kana: Little Sister)]
+
+:_vnlength_4
+en : Long[index,_1,, (30 - 50 hours), (Tsukihime)]
+ru : Длинная[index,_1,, (30 - 50 часов), (Tsukihime)]
+
+:_vnlength_5
+en : Very long[index,_1,, (> 50 hours), (Clannad)]
+ru : Очень длинная[index,_1,, (> 50 часов), (Clannad)]
+
+
+# Form messages
+
+:_formerr_e_login_failed
+en : Invalid username or password
+ru : Некорректное имя пользователя или пароль
+
+:_formerr_e_nomail
+en : No user found with that email address
+ru : Пользователя с такой электронной почтой не существует
+
+:_formerr_e_passmatch
+en : Passwords do not match
+ru : Пароли не совпадают
+
+:_formerr_e_usrexists
+en : Someone already has this username, please choose something else
+ru : Кто-то уже зарегистрировал такой ник, пожалуйста выберите другой
+
+:_formerr_e_mailexists
+en : Someone already registered with that email address
+ru : Кто-то уже регистрировался с таким адресом электронной почты
+
+:_formerr_e_noimage
+en : Image must be in JPEG or PNG format
+ru : Изображение должно быть в формате JPEG, либо в формате PNG
+
+:_formerr_e_toolarge
+en : Image is too large, only 500kB allowed
+ru : Изображение слишком большое, 500 кб - максимально допустимый предел
+
+:_formerr_e_oneaday
+en : You can only register one account from the same IP within 24 hours
+ru : Вы можете зарегистрировать учётную запись с одного и того же IP лишь по прошествии 24 часов
+
+:_formerr_e_nochanges
+en : No changes, please don't create an entry that is fully identical to another
+ru : Изменения отсутствуют, пожалуйста не создавайте идентичных копий записей
+
+:_formerr_e_doublepost
+en : Please wait 30 seconds before making another post
+ru : Прежде чем публиковать очередное сообщение, пожалуйста подождите 30 секунд
+
+:_formerr_title
+en : Error
+ru : Ошибка
+
+:_formerr_subtitle
+en : Form could not be sent:
+ru : Невозможно отправить форму:
+
+:_formerr_required
+en : [_1] is a required field!
+ru : [_1] - обязательное поле!
+
+:_formerr_minlength
+en : [_1]: should have at least [_2] characters
+ru : [_1]: [quant,_2,необходим,необходимы,необходимо] хотя бы [_2] [quant,_2,символ,символа,символов]
+
+:_formerr_maxlength
+en : [_1]: only [_2] characters allowed
+ru : [_1]: [quant,_2,разрешён,разрешены,разрешено] лишь [_2] [quant,_2,символ,символа,символов]
+
+:_formerr_enum
+en : [_1] must be one of the following: [_2]
+ru : Поле '[_1]' должно равняться одному из следующих значений: [_2]
+
+:_formerr_wrongboard
+en : Wrong board: [_1]
+ru : Некорректная ветка: [_1]
+
+:_formerr_tagexists
+en : Tag [url,_1,_2] already exists!
+ru : Тег [url,_1,_2] уже существует!
+
+:_formerr_tpl_mail
+en : Invalid email address
+ru : Некорректный адрес электронной почты
+
+:_formerr_tpl_url
+en : [_1]: Invalid URL
+ru : [_1]: Некорректная ссылка
+
+:_formerr_tpl_asciiprint
+en : [_1] may only contain ASCII characters
+ru : Поле '[_1]' может содержать лишь символы диапазона ASCII
+
+:_formerr_tpl_int
+en : [_1]: Not a valid number
+ru : [_1]: Не является правильным числом
+
+:_formerr_tpl_pname
+en : [_1] can only contain lowercase alphanumeric characters and a hyphen, and must start with a character
+ru : Поле '[_1]' может содержать лишь символы буквенно-цифрового диапазона в нижнем регистре и чёрточку, а так же начинаться с буквы
+
+:_form_tab_all
+en : All items
+ru : Все поля
+
+:_form_editsum
+en : Edit summary
+ru : Суммарно о правке
+
+:_form_submit
+en : Submit
+ru : Отправить
+
+
+
+
+#############################################################################
+## Main website layout ##
+#############################################################################
+# Util::LayoutHTML
+# Util::CommonHTML::htmlMainTabs, htmlBrowseNavigate, htmlRevision, htmlSearchBox
+# Handler::Misc::nospam
+
+
+# Main title of the site, used on header of each page and as title on the homepage
+:_site_title
+en : The Visual Novel Database
+ru : The Visual Novel Database
+
+
+# the 'ALL' in "ALL A B C D .. #"
+:_char_all
+en : ALL
+ru : ВСЕ
+
+
+# Main menu
+
+:_menu
+en : Menu
+ru : Меню
+
+:_menu_home
+en : Home
+ru : Главная
+
+:_menu_vn
+en : Visual novels
+ru : Новеллы
+
+:_menu_releases
+en : Releases
+ru : Выпуски
+
+:_menu_producers
+en : Producers
+ru : Компании
+
+:_menu_tags
+en : Tags
+ru : Теги
+
+:_menu_users
+en : Users
+ru : Пользователи
+
+:_menu_recent_changes
+en : Recent changes
+ru : Свежие правки
+
+:_menu_discussion_board
+en : Discussion board
+ru : Форум
+
+:_menu_faq
+en : FAQ
+ru : ЧаВо
+
+:_menu_randvn
+en : Random visual novel
+ru : Случайная новелла
+
+:_menu_webchat
+en : webchat
+ru : Веб-чат
+
+:_menu_emptysearch
+en : search
+ru : поиск
+
+
+# User menu
+
+:_menu_myprofile
+en : My Profile
+ru : Мой профиль
+
+:_menu_myvnlist
+en : My Visual Novel List
+ru : Мой список новелл
+
+:_menu_mywishlist
+en : My Wishlist
+ru : Мой список желаемого
+
+# [_1] = number of messages
+:_menu_mymessages
+en : My Messages ([_1])
+ru : Мои сообщения
+
+:_menu_mychanges
+en : My Recent Changes
+ru : Мои недавние правки
+
+:_menu_mytags
+en : My Tags
+ru : Мои теги
+
+:_menu_addvn
+en : Add Visual Novel
+ru : Добавить новеллу
+
+:_menu_addproducer
+en : Add Producer
+ru : Добавить компанию
+
+:_menu_logout
+en : Logout
+ru : Выйти
+
+# used for both the box title and submit button
+:_menu_login
+en : Login
+ru : Вход
+
+:_menu_loginmsg
+en : Need to [url,_1,register],[br]
+ or [url,_2,forgot your password]?
+ru : Нужна [url,_1,регистрация],[br]
+ или [url,_2,забыли свой пароль]?
+
+# database statistics
+
+:_menu_dbstats
+en : Database Statistics
+ru : Статистика базы данных
+
+:_menu_stat_vn
+en : Visual Novels
+ru : Новелл
+
+:_menu_stat_releases
+en : Releases
+ru : Выпусков
+
+:_menu_stat_producers
+en : Producers
+ru : Компаний
+
+:_menu_stat_users
+en : Users
+ru : Пользователей
+
+:_menu_stat_threads
+en : Threads
+ru : Тем
+
+:_menu_stat_posts
+en : Posts
+ru : Сообщений
+
+
+# Footer
+
+:_footer_aboutus
+en : abous us
+ru : о нас
+
+:_footer_source
+en : source
+ru : исходный код
+
+
+# Main tabs (those on the right top of the highest box)
+
+:_mtabs_hist
+en : history
+ru : история
+
+:_mtabs_discuss
+en : discussions ([_1])
+ru : обсуждения ([_1])
+
+# the following 4 tabs are only present on user pages
+:_mtabs_posts
+en : posts
+ru : сообщения
+
+:_mtabs_wishlist
+en : wishlist
+ru : список желаемого
+
+:_mtabs_list
+en : list
+ru : список
+
+:_mtabs_tags
+en : tags
+ru : теги
+
+# modify tags on VN pages
+:_mtabs_tagmod
+en : modify tags
+ru : править теги
+
+# copy a release
+:_mtabs_copy
+en : copy
+ru : копировать
+
+# following line is also used on revision pages (it's the same action, anyway)
+:_mtabs_edit
+en : edit
+ru : правка
+
+# hide/unhide a DB item
+:_mtabs_hide
+en : hide
+ru : скрыть
+
+:_mtabs_unhide
+en : unhide
+ru : показать
+
+# lock/unlock for editing
+:_mtabs_lock
+en : lock
+ru : заблокировать
+
+:_mtabs_unlock
+en : unlock
+ru : снять блокировку
+
+# delete
+:_mtabs_del
+en : del
+ru : удалить
+
+# VN relations
+:_mtabs_relations
+en : relations
+ru : связи
+
+
+# Navigation buttons on the browse pages
+
+:_browse_previous
+en : previous
+ru : назад
+
+:_browse_next
+en : next
+ru : далее
+
+
+# Revision pages
+
+:_revision_previous
+en : earlier revision
+ru : более ранняя редакция
+
+:_revision_next
+en : later revision
+ru : более поздняя редакция
+
+:_revision_title
+en : Revision [_1]
+ru : Редакция [_1]
+
+# it's the summary of the edit, "edit" is not a verb here.
+:_revision_new_summary
+en : Edit summary
+ru : Суммарно
+
+:_revision_edit_summary
+en : Edit summary of revision [_1]:
+ru : Суммарно о редакции [_1]:
+
+:_revision_user_date
+en : By [userstr,_1] on [date,_2,full]
+ru : [userstr,_1], [date,_2,full]
+
+:_revision_emptyfield
+en : ~[empty~]
+ru : ~[пусто~]
+
+
+# tabs above the search boxes
+
+:_searchbox_vn
+en : Visual novels
+ru : Новеллы
+
+:_searchbox_releases
+en : Releases
+ru : Выпуски
+
+:_searchbox_producers
+en : Producers
+ru : Компании
+
+:_searchbox_tags
+en : Tags
+ru : Теги
+
+:_searchbox_users
+en : Users
+ru : Пользователи
+
+# text on the search button
+:_searchbox_submit
+en : Search!
+ru : Искать!
+
+
+
+
+#############################################################################
+## Home page (/) ##
+#############################################################################
+# Handler::Misc::home
+
+
+:_home_intro
+en : VNDB.org strives to be a comprehensive database for information about visual novels.[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 [url,/v/all,browse around], [url,/u/register,register an account]
+ or to participate in the discussions about visual novels or VNDB on our [url,/t,discussion board].
+ru : VNDB.org стремится быть наиболее полной базой данных с информацией по интерактивным визуальным новеллам.[br]
+ Данный сайт создан в стиле вики, то есть любой желающий может добавлять сюда информацию, тем самым позволяя
+ нам создать крупнейшую, наиболее аккуратную и самую актуальную базу по визуальным новеллам в сети.[br]
+ Зарегистрированные пользователи могут составлять списки желаемых и пройденных игр, а так же влиять на
+ их рейтинг.[br][br]
+ Если хотите, можете [url,/v/all,побродить по сайту], [url,/u/register,создать учётную запись] или
+ поучаствовать в обсуждении новелл (либо самой VNDB) на нашем [url,/t,форуме] (убедительная просьба писать
+ только на английском).
+
+
+:_home_recentchanges
+en : Recent Changes
+ru : Свежие правки
+
+:_home_recentchanges_item
+en : [_1]:[_2] by [userstr,_3]
+ru : [_1]:[_2], [userstr,_3]
+
+:_home_announcements
+en : Announcements
+ru : Объявления
+
+:_home_recentposts
+en : Recent Posts
+ru : Недавние сообщения
+
+:_home_recentposts_item
+en : [age,_1] [_2] by [userstr,_3]
+ru : [age,_1] [_2], [userstr,_3]
+
+:_home_randomvn
+en : Random vsual novels
+ru : Случайные новеллы
+
+:_home_upcoming
+en : Upcoming releases
+ru : Грядущие выпуски
+
+:_home_justreleased
+en : Just released
+ru : Только что вышли
+
+
+
+
+#############################################################################
+## History browser (/*/hist) ##
+#############################################################################
+# Handler::Misc::history and Util::CommonHTML::htmlHistory
+
+
+:_hist_title
+en : Recent changes
+ru : Свежие правки
+
+:_hist_title_item
+en : Edit history of [_1]
+ru : История изменений [_1]
+
+
+# the filter buttons
+
+:_hist_filter_showauto
+en : Show automated edits
+ru : Показать автоматические правки
+
+:_hist_filter_hideauto
+en : Hide automated edits
+ru : Скрыть автоматические правки
+
+:_hist_filter_hidedel
+en : Hide deleted items
+ru : Скрыть удалённые страницы
+
+:_hist_filter_showdel
+en : Show deleted items
+ru : Показать удалённые страницы
+
+:_hist_filter_alltypes
+en : Show all items
+ru : Показать все страницы
+
+:_hist_filter_onlyvn
+en : Only visual novels
+ru : Только новеллы
+
+:_hist_filter_onlyreleases
+en : Only releases
+ru : Только выпуски
+
+:_hist_filter_onlyproducers
+en : Only producers
+ru : Только компании
+
+:_hist_filter_allactions
+en : Show all changes
+ru : Показать все изменения
+
+:_hist_filter_onlyedits
+en : Only edits
+ru : Только правки
+
+:_hist_filter_onlynew
+en : Only newly created pages
+ru : Только новые страницы
+
+:_hist_filter_exrel
+en : Exclude edits of releases
+ru : Исключить правки выпусков
+
+:_hist_filter_increl
+en : Include edits of releases
+ru : Включить правки выпусков
+
+
+# column headers
+
+# Short version of 'Revision'
+:_hist_col_rev
+en : Rev.
+ru : Ревизия
+
+:_hist_col_date
+en : Date
+ru : Дата
+
+:_hist_col_user
+en : User
+ru : Пользователь
+
+:_hist_col_page
+en : Page
+ru : Страница
+
+
+
+
+#############################################################################
+## Discussion board (/t/*) ##
+#############################################################################
+# Handler::Discussions
+
+
+# thread page (/t+)
+
+:_thread_postedin
+en : Posted in
+ru : В теме
+
+:_thread_byuser
+en : by [userstr,_1]
+ru : [userstr,_1]
+
+:_thread_editpost
+en : edit
+ru : правка
+
+:_thread_deletedpost
+en : Post deleted.
+ru : Сообщение удалено.
+
+:_thread_lastmodified
+en : Last modified on [date,_1,full]
+ru : Последний раз редактировалось [date,_1,full]
+
+:_thread_noreply_title
+en : Reply
+ru : Ответить
+
+:_thread_noreply_locked
+en : This thread has been locked, you can't reply to it anymore
+ru : Данная тема заблокирована, вы больше не можете в неё ответить
+
+:_thread_noreply_login
+en : You must be logged in to reply to this thread.
+ru : Вы должны быть авторизованы чтобы ответить в эту тему.
+
+:_thread_quickreply_title
+en : Quick reply
+ru : Быстрый ответ
+
+:_thread_quickreply_submit
+en : Reply
+ru : Ответить
+
+:_thread_quickreply_full
+en : Go advanced...
+ru : В расширенный режим...
+
+
+# Post edit/reply/new thread form
+
+:_postedit_newthread
+en : Start new thread
+ru : Начать новую тему
+
+:_postedit_replyto
+en : Reply to [_1]
+ru : Ответить в [_1]
+
+:_postedit_edit
+en : Edit post
+ru : Правка сообщения
+
+:_postedit_form_username
+en : Username
+ru : Имя пользователя
+
+:_postedit_form_title
+en : Thread title
+ru : Название темы
+
+:_postedit_form_boards
+en : Board(s)
+ru : Форум(ы)
+
+:_postedit_form_boards_info
+en : Read [url,/d9.2,d9.2] for information about how to specify boards.
+ru : О том как указывать форумы читайте в разделе [url,/d9.2,d9.2].
+
+:_postedit_form_locked
+en : Locked
+ru : Заблокировано
+
+:_postedit_form_topic
+en : Topic
+ru : Тема
+
+:_postedit_form_hidden
+en : Hidden
+ru : Скрыто
+
+:_postedit_form_nolastmod
+en : Don't update last modified field
+ru : Не обновлять дату последнего изменения
+
+:_postedit_form_msg
+en : Message
+ru : Сообщение
+
+:_postedit_form_msg_format
+en : See [url,/d9.3,d9.3] for the allowed formatting codes
+ru : Список разрешённых кодов смотрите в разделе [url,/d9.3,d9.3]
+
+
+# Browsing threads by board (/t/{board_id})
+
+:_disboard_item_title
+en : Related discussions for [_1]
+ru : Темы, относящиеся к [_1]
+
+:_disboard_rootlink
+en : Discussion board
+ru : Форум
+
+:_disboard_nothreads
+en : No related threads found
+ru : Связанных тем не найдено
+
+:_disboard_createyourown
+en : Why not create one yourself?
+ru : Почему бы не создать новую?
+
+:_disboard_startnew
+en : Start a new thread
+ru : Начать новую тему
+
+
+# The discussion board index (/t)
+
+:_disindex_title
+en : Discussion board index
+ru : Корневая директория форума
+
+
+# Thread list (on discussion board index and board browser)
+
+:_threadlist_col_topic
+en : Topic
+ru : Тема
+
+:_threadlist_col_replies
+en : Replies
+ru : Ответов
+
+:_threadlist_col_starter
+en : Starter
+ru : Автор темы
+
+:_threadlist_col_lastpost
+en : Last post
+ru : Последнее сообщение
+
+
+
+
+#############################################################################
+## Producer pages (/p/*) ##
+#############################################################################
+# Handler::Producers
+
+
+# Producer page (/p+)
+
+# 1 = Language, 2 = Type, so '[_1] [_2]' => 'Japanese company'
+# if this doesn't work in other languages, be creative :-)
+:_prodpage_langtype
+en : [_1] [_2]
+ru : [_2], основной язык: [_1]
+
+:_prodpage_aliases
+en : a.k.a. [_1]
+ru : a.k.a. [_1]
+
+:_prodpage_vnrel
+en : Visual Novel Relations
+ru : Связи новелл
+
+:_prodpage_norel
+en : We have currently no visual novels by this producer.
+ru : У нас пока нет сведений о новеллах авторства этой компании.
+
+
+# producer diff fields
+
+:_revfield_p_type
+en : Type
+ru : Тип
+
+:_revfield_p_name
+en : Name (romaji)
+ru : Название (ромадзи)
+
+:_revfield_p_original
+en : Original name
+ru : Оригинальное название
+
+:_revfield_p_alias
+en : Aliases
+ru : Прочие названия
+
+:_revfield_p_lang
+en : Language
+ru : Язык
+
+:_revfield_p_website
+en : Website
+ru : Веб-сайт
+
+:_revfield_p_desc
+en : Description
+ru : Описание
+
+
+# Add/Edit producer
+
+:_pedit_title_edit
+en : Edit [_1]
+ru : Править [_1]
+
+:_pedit_title_add
+en : Add new producer
+ru : Добавление новой компании
+
+:_pedit_form_generalinfo
+en : General info
+ru : Основная информация
+
+:_pedit_form_type
+en : Type
+ru : Тип
+
+:_pedit_form_name
+en : Name (romaji)
+ru : Название (ромадзи)
+
+:_pedit_form_original
+en : Original name
+ru : Оригинальное название
+
+:_pedit_form_original_note
+en : The original name of the producer, leave blank if it is already in the Latin alphabet.
+ru : Оригинальное название компании, оставьте пустым если уже в латинском алфавите.
+
+:_pedit_form_alias
+en : Aliases
+ru : Прочие названия
+
+:_pedit_form_alias_note
+en : (Un)official aliases, separated by a comma.
+ru : (Не)официальные альтернативные названия, через запятую.
+
+:_pedit_form_lang
+en : Primary language
+ru : Основной язык
+
+:_pedit_form_website
+en : Website
+ru : Веб-сайт
+
+:_pedit_form_desc
+en : Description
+ru : Описание
+
+
+# Browse/search producers
+
+:_pbrowse_title
+en : Browse producers
+ru : Обзор компаний
+
+:_pbrowse_searchres
+en : Search results
+ru : Результаты поиска
+
+:_pbrowse_list
+en : Producer list
+ru : Список компаний
+
+:_pbrowse_noresults
+en : No results found
+ru : Ничего не найдено
+
+
+
+
+#############################################################################
+## Release pages (/r/*) ##
+#############################################################################
+# Handler::Releases
+
+
+# Release diff viewer (/r+.+)
+
+:_revfield_r_vn
+en : Relations
+ru : Связи
+
+:_revfield_r_type
+en : Type
+ru : Тип
+
+:_revfield_r_patch
+en : Patch
+ru : Патч
+
+:_revfield_r_freeware
+en : Freeware
+ru : Freeware
+
+:_revfield_r_doujin
+en : Doujin
+ru : Додзинси
+
+:_revfield_r_title
+en : Title (romaji)
+ru : Название (ромадзи)
+
+:_revfield_r_original
+en : Original title
+ru : Оригинальное название
+
+:_revfield_r_gtin
+en : JAN/UPC/EAN
+ru : JAN/UPC/EAN
+
+:_revfield_r_catalog
+en : Catalog number
+ru : Номер в каталоге
+
+:_revfield_r_languages
+en : Language
+ru : Язык
+
+:_revfield_r_website
+en : Website
+ru : Веб-сайт
+
+:_revfield_r_released
+en : Release date
+ru : Дата выпуска
+
+:_revfield_r_minage
+en : Age rating
+ru : Возрастной рейтинг
+
+:_revfield_r_notes
+en : Notes
+ru : Заметки
+
+:_revfield_r_platforms
+en : Platforms
+ru : Платформы
+
+:_revfield_r_media
+en : Media
+ru : Носители
+
+:_revfield_r_resolution
+en : Resolution
+ru : Разрешение
+
+:_revfield_r_voiced
+en : Voiced
+ru : Озвучка
+
+:_revfield_r_ani_story
+en : Story animation
+ru : Сюжетная анимация
+
+:_revfield_r_ani_ero
+en : Ero animation
+ru : Анимация эросцен
+
+:_revfield_r_producers
+en : Producers
+ru : Компании
+
+
+# Information table (on every release page)
+
+:_relinfo_vnrel
+en : Relation
+ru : Связи
+
+:_relinfo_title
+en : Title
+ru : Название
+
+:_relinfo_original
+en : Original title
+ru : Оригинальное название
+
+:_relinfo_type
+en : Type
+ru : Тип
+
+:_relinfo_type_format
+en : [_1][index,_2,, patch]
+ru : [_1][index,_2,, патч]
+
+:_relinfo_lang
+en : Language
+ru : Язык
+
+:_relinfo_publication
+en : Publication
+ru : Публикация
+
+:_relinfo_pub_nopatch
+en : [index,_1,Freeware,Non-free], [index,_2,doujin,commercial]
+ru : [index,_1,Freeware,Несвободная], [index,_2,додзинси,коммерческая]
+
+:_relinfo_pub_patch
+en : [index,_1,Freeware,Non-free]
+ru : [index,_1,Freeware,Несвободная]
+
+:_relinfo_platform
+en : [quant,_1,Platform,Platforms]
+ru : [quant,_1,Платформа,Платформы,Платформ]
+
+:_relinfo_media
+en : [quant,_1,Medium,Media]
+ru : [quant,_1,Носитель,Носители,Носителей]
+
+:_relinfo_resolution
+en : Resolution
+ru : Разрешение
+
+:_relinfo_voiced
+en : Voiced
+ru : Озвучка
+
+:_relinfo_ani
+en : Animation
+ru : Анимация
+
+:_relinfo_ani_story
+en : Story: [_1]
+ru : Сюжет: [_1]
+
+:_relinfo_ani_ero
+en : Ero scenes: [_1]
+ru : Эросцены: [_1]
+
+:_relinfo_released
+en : Released
+ru : Дата выпуска
+
+:_relinfo_minage
+en : Age rating
+ru : Возрастной рейтинг
+
+:_relinfo_producer
+en : [quant,_1,Producer,Producers]
+ru : [quant,_1,Компания,Компании,Компаний]
+
+:_relinfo_catalog
+en : Catalog no.
+ru : № в каталоге
+
+:_relinfo_links
+en : Links
+ru : Ссылки
+
+:_relinfo_website
+en : Official website
+ru : Официальный веб-сайт
+
+:_relinfo_user
+en : User options
+ru : Настройки пользователя
+
+:_relinfo_user_notlist
+en : not in your list
+ru : не в вашем списке
+
+:_relinfo_user_inlist
+en : Status: [_1] / [_2]
+ru : Статус: [_1] / [_2]
+
+:_relinfo_user_setr
+en : Set release status
+ru : Установка статуса выпуска
+
+:_relinfo_user_setv
+en : Set play status
+ru : Установка статуса игры
+
+:_relinfo_user_del
+en : remove from list
+ru : убрать из списка
+
+
+# Editing a release
+
+:_redit_title_edit
+en : Edit [_1]
+ru : Правка [_1]
+
+:_redit_title_copy
+en : Copy [_1]
+ru : Копирование [_1]
+
+:_redit_title_add
+en : Add release to [_1]
+ru : Добавление выпуска для [_1]
+
+:_redit_form_geninfo
+en : General info
+ru : Основная информация
+
+:_redit_form_type
+en : Type
+ru : Тип
+
+:_redit_form_patch
+en : This release is a patch to another release.
+ru : Данный выпуск является патчем для другого выпуска.
+
+:_redit_form_freeware
+en : Freeware (i.e. available at no cost)
+ru : Freeware (т.е. распространяется бесплатно)
+
+:_redit_form_doujin
+en : Doujin (self-published, not by a company)
+ru : Додзинси (выпущено самостоятельно, частным лицом)
+
+:_redit_form_title
+en : Title (romaji)
+ru : Название (ромадзи)
+
+:_redit_form_original
+en : Original title
+ru : Оригинальное название
+
+:_redit_form_original_note
+en : The original title of this release, leave blank if it already is in the Latin alphabet.
+ru : Оригинальное название данного выпуска, осавьте пустым если уже в латинском алфавите.
+
+:_redit_form_languages
+en : Language(s)
+ru : Язык(и)
+
+:_redit_form_gtin
+en : JAN/UPC/EAN
+ru : JAN/UPC/EAN
+
+:_redit_form_catalog
+en : Catalog number
+ru : Номер в каталоге
+
+:_redit_form_website
+en : Official website
+ru : Официальный веб-сайт
+
+:_redit_form_released
+en : Release date
+ru : Дата выпуска
+
+:_redit_form_released_note
+en : Leave month or day blank if they are unknown
+ru : Если месяц или день неизвестны, оставьте их пустыми
+
+:_redit_form_minage
+en : Age rating
+ru : Возрастной рейтинг
+
+:_redit_form_notes
+en : Notes
+ru : Заметки
+
+:_redit_form_notes_note
+en : Miscellaneous notes/comments, information that does not fit in the above fields.
+ E.g.: Censored/uncensored or for which releases this patch applies.
+ru : Дополнительные заметки/комментарии, информация для которой не нашлось приемлимых полей.
+ Т.е.: присутствие/отсутствие цензуры, или для каких версий применим данный патч.
+
+:_redit_form_format
+en : Format
+ru : Формат
+
+:_redit_form_resolution
+en : Resolution
+ru : Разрешение
+
+:_redit_form_voiced
+en : Voiced
+ru : Озвучка
+
+:_redit_form_ani_story
+en : Story animation
+ru : Сюжетная анимация
+
+:_redit_form_ani_ero
+en : Ero animation
+ru : Анимация эросцен
+
+:_redit_form_ani_ero_none
+en : Unknown / no ero scenes
+ru : Неизвестно / нет эросцен
+
+:_redit_form_ani_ero_note
+en : Animation in erotic scenes, leave to unknown if there are no ero scenes.
+ru : Анимация в эротических сценах, не изменяйте поле если эросцены отсутствуют.
+
+:_redit_form_platforms
+en : Platforms
+ru : Платформы
+
+:_redit_form_media
+en : Media
+ru : Носители
+
+:_redit_form_prod
+en : Producers
+ru : Компании
+
+:_redit_form_prod_sel
+en : Selected producers
+ru : Выбранные компании
+
+:_redit_form_prod_add
+en : Add producer
+ru : Добавить компанию
+
+:_redit_form_vn
+en : Visual novels
+ru : Новеллы
+
+:_redit_form_vn_sel
+en : Selected visual novels
+ru : Выбранные новеллы
+
+:_redit_form_vn_add
+en : Add visual novel
+ru : Добавить новеллу
+
+
+# Release browser
+
+:_rbrowse_title
+en : Browse releases
+ru : Обзор выпусков
+
+:_rbrowse_col_released
+en : Released
+ru : Выпущено
+
+:_rbrowse_col_minage
+en : Rating
+ru : Рейтинг
+
+:_rbrowse_col_title
+en : Title
+ru : Название
+
+:_rbrowse_noresults_title
+en : No results found
+ru : Совпадений не найдено
+
+:_rbrowse_noresults_msg
+en : Sorry, couldn't find anything that comes through your filters. You might want to disable a few filters to get more results.
+
+ Also, keep in mind that we don't have all information about all releases. So e.g. filtering on screen resolution will exclude 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.
+ru : Простите, по вашему запросу ничего не найдено. Для получения результатов, попробуйте отключить пару-тройку фильтров.
+
+ Также, не забывайте что у нас нет информации об абсолютно всех выпусках. Поэтому, если вы, например, поставили фильтр по разрешению экрана, он исключит все новеллы, не удовлетворяющие выбранному разрешению, даже если в действительности та которую вы ищите имеет искомое разрешение.
+
+:_rbrowse_filters
+en : Filters
+ru : Фильтры
+
+:_rbrowse_minage
+en : Age rating
+ru : Возрастной рейтинг
+
+:_rbrowse_ge
+en : Greater than or equal to
+ru : Больше или равно
+
+:_rbrowse_le
+en : Less than or equal to
+ru : Меньше или равно
+
+:_rbrowse_resolution
+en : Screen resolution
+ru : Разрешение экрана
+
+:_rbrowse_type
+en : Release type
+ru : Тип выпуска
+
+:_rbrowse_all
+en : All
+ru : Все
+
+:_rbrowse_patch
+en : Patch status
+ru : Статус патча
+
+:_rbrowse_patchonly
+en : Only patches
+ru : Только патчи
+
+:_rbrowse_patchnone
+en : Only standalone releases
+ru : Только самостоятельные выпуски
+
+:_rbrowse_freeware
+en : Freeware
+ru : Freeware
+
+:_rbrowse_freewareonly
+en : Freeware only
+ru : Только Freeware
+
+:_rbrowse_freewarenone
+en : Only non-free releases
+ru : Только несвободные
+
+:_rbrowse_doujin
+en : Doujin
+ru : Додзинси
+
+:_rbrowse_doujinonly
+en : Only doujin releases
+ru : Только додзинси
+
+:_rbrowse_doujinnone
+en : Only commercial releases
+ru : Только коммерческие
+
+:_rbrowse_dateafter
+en : Released after
+ru : Выпущены после
+
+:_rbrowse_datebefore
+en : Released before
+ru : Выпущены до
+
+:_rbrowse_languages
+en : Languages
+ru : Языки
+
+:_rbrowse_boolor
+en : boolean or, selecting more gives more results
+ru : булевое 'или', чем больше выбрано, тем больше даёт результатов
+
+:_rbrowse_platforms
+en : Platforms
+ru : Платформы
+
+:_rbrowse_media
+en : Media
+ru : Носители
+
+:_rbrowse_apply
+en : Apply
+ru : Применить
+
+:_rbrowse_clear
+en : Clear
+ru : Очистить
+
+
+
+
+#############################################################################
+## Tag pages (/g/*) ##
+#############################################################################
+# Handler::Tags
+
+
+# Tag page (/g+)
+
+:_tagp_title
+en : [index,_1,Meta tag,Tag]: [_2]
+ru : [index,_1,Мета тег,Тег]: [_2]
+
+:_tagp_del_title
+en : Tag deleted
+ru : Тег удалён
+
+:_tagp_del_msg
+en : This tag has been removed from the database, and cannot be used or re-added.
+ File a request on the [url,/t/db,discussion board] if you disagree with this.
+ru : Данный тег удалён из базы данных, и не может быть использован.
+ Напишите на [url,/t/db,форуме], если вы с этим не согласны.
+
+:_tagp_pending_title
+en : Waiting for approval
+ru : Ждёт одобрения
+
+:_tagp_pending_msg
+en : 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.
+ru : Данный тег ожидает одобрения модератором. Однако, вы можете использовать его для пометки новелл, как и любой нормальный тег.
+
+:_tagp_addchild
+en : Create child tag
+ru : Создать дочерний тег
+
+:_tagp_indexlink
+en : Tags
+ru : Теги
+
+:_tagp_aliases
+en : Aliases
+ru : Прочие названия
+
+:_tagp_childs
+en : Child tags
+ru : Дочерние теги
+
+:_tagp_tree
+en : Tag tree
+ru : Древо тега
+
+:_tagp_moretags
+en : [_1] more [quant,_1,tag,tags]
+ru : ещё [_1] [quant,_1,тег,тега,тегов]
+
+:_tagp_vnlist
+en : Visual novels
+ru : Новеллы
+
+:_tagp_spoil0
+en : Hide spoilers
+ru : Скрыть спойлеры
+
+:_tagp_spoil1
+en : Show minor spoilers
+ru : Показать лёгкие спойлеры
+
+:_tagp_spoil2
+en : Show major spoilers
+ru : Показать жёсткие спойлеры
+
+:_tagp_novn
+en : This tag has not been linked to any visual novels yet, or they were hidden because of the spoiler settings.
+ru : Этот тег пока не содержит ссылок ни на одну новеллу, либо они скрыты из-за настроек отображения спойлеров.
+
+:_tagp_cached
+en : NOTE: 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.
+ru : ЗАМЕЧАНИЕ: Данный список кэшируется, поэтому может пройти вплоть до 24 часов, прежде чем новелла, помеченная тегом, отобразится на этой странице.
+
+:_tagp_vncol_score
+en : Score
+ru : Балл
+
+:_tagp_vncol_title
+en : Title
+ru : Название
+
+:_tagp_vncol_rel
+en : Released
+ru : Выпуск
+
+:_tagp_vncol_pop
+en : Popularity
+ru : Популярность
+
+
+# Tag add/edit form (/g+/edit, /g+/add, /g/add)
+
+:_tagedit_err_notfound
+en : Tag '[_1]' not found
+ru : Тег '[_1]' не найден
+
+:_tagedit_title_add
+en : Add child tag to [_1]
+ru : Добавление дочернего тега для '[_1]'
+
+:_tagedit_title_edit
+en : Edit tag: [_1]
+ru : Править тег: [_1]
+
+:_tagedit_title_new
+en : Add new tag
+ru : Добавить новый тег
+
+:_tagedit_req_title
+en : Requesting new tag
+ru : Запросить новый тег
+
+:_tagedit_req_subtitle
+en : Your tag must be approved
+ru : Ваш тег должен быть одобрен
+
+:_tagedit_req_msg
+en : 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.
+ [br][br]
+ Also, make sure you've read the [url,/d10,guidelines], so you can predict whether your tag will be accepted or not.
+ru : В связи с тем что все теги должны быть утверждены модераторами, их появление в списке тегов или на страницах новелл
+ займёт некоторое время. Однако, вы всё равно можете голосовать за тег, даже если тот ещё не был утверждён.
+ [br][br]
+ Также, пожалуйста убедитесь что прочли [url,/d10,рекомендации], тогда вам не придётся гадать, утвердят ваш тег или нет.
+
+:_tagedit_frm_name
+en : Primary name
+ru : Основное название
+
+:_tagedit_frm_by
+en : Added by
+ru : Добавлено
+
+:_tagedit_frm_state
+en : State
+ru : Состояние
+
+:_tagedit_frm_state0
+en : Awaiting moderation
+ru : Ждёт модерации
+
+:_tagedit_frm_state1
+en : Deleted/hidden
+ru : Удалён/скрыт
+
+:_tagedit_frm_state2
+en : Approved
+ru : Одобрен
+
+:_tagedit_frm_meta
+en : This is a meta-tag (only to be used as parent for other tags, not for linking to VN entries)
+ru : Это мета-тег (его нельзя добавлять к новеллам, он может использоваться лишь как родитель для других тегов)
+
+:_tagedit_frm_meta_warn
+en : WARNING: Checking this option or selecting "Deleted" as state will permanently delete all existing VN relations!
+ru : ПРЕДУПРЕЖДЕНИЕ: Если выбрать данную опцию, либо выставить состояние "Удалён", произойдёт немедленное удаление всех существующих связей с новеллами!
+
+:_tagedit_frm_alias
+en : Aliases
+ (separated by newlines)
+ru : Прочие названия
+ (каждое с новой строки)
+
+:_tagedit_frm_desc
+en : Description
+ru : Описание
+
+:_tagedit_frm_desc_msg
+en : What should the tag be used for? Having a good description helps users choose which tags to link to a VN.
+ru : Для пометки чего будет использоваться тег? Хорошее описание поможет пользователям выбрать подходящий тег для подходящей новеллы.
+
+:_tagedit_frm_parents
+en : Parent tags
+ru : Родительские теги
+
+:_tagedit_frm_parents_msg
+en : Comma separated list of tag names to be used as parent for this tag.
+ru : Теги, которые будут задействованы для этого в качестве родительских. Через запятую.
+
+:_tagedit_frm_merge
+en : Merge tags
+ru : Объединение тегов
+
+:_tagedit_frm_merge_tags
+en : Tags to merge
+ru : Теги для объединения
+
+:_tagedit_frm_merge_msg
+en : 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!
+ru : Список тегов для объединения в данный, через запятую. Все голоса и побочные названия будут перемещены в
+ данный тег, а все старые теги удалены. Оставьте поле пустым, если вы не собираетесь проводить объединение.
+ [br]ПРЕДУПРЕЖДЕНИЕ: данную операцию отменить невозможно!
+
+
+# Plain tag browser (/g/list)
+
+:_tagb_title
+en : Browse tags
+ru : Обзор тегов
+
+:_tagb_state-1
+en : All
+ru : Все
+
+:_tagb_state0
+en : Awaiting moderation
+ru : Ожидающие модерации
+
+:_tagb_state1
+en : Deleted
+ru : Удалённые
+
+:_tagb_state2
+en : Accepted
+ru : Одобренные
+
+:_tagb_noresults
+en : No results found
+ru : Совпадений не найдено
+
+:_tagb_col_added
+en : Created
+ru : Созданы
+
+:_tagb_col_name
+en : Tag
+ru : Тег
+
+:_tagb_note_awaiting
+en : awaiting moderation
+ru : ждёт модерации
+
+:_tagb_note_del
+en : deleted
+ru : удалён
+
+
+# VN<->Tag voting (/v+/tagmod)
+
+:_tagv_title
+en : Add/remove tags for [_1]
+ru : Добавление/удаление тегов для [_1]
+
+:_tagv_msg_title
+en : Tagging
+ru : Пометка тегами
+
+:_tagv_msg_guidelines
+en : Make sure you have read the [url,/d10,guidelines]!
+ru : Пожалуйста, убедитесь что прочли [url,/d10,рекомендации]!
+
+:_tagv_msg_submit
+en : Don't forget to hit the submit button on the bottom of the page to make your changes permanent.
+ru : Не забудьте сохранить изменения, нажав соответствующую кнопку в низу страницы, иначе они не вступят в силу.
+
+:_tagv_msg_cache
+en : Some tag information on the site is cached, it can take up to an hour for your changes to be visible everywhere.
+ru : Информация о некоторых тегах на данном сайте кэшируется, поэтому ваши изменения станут видны везде в течение часа.
+
+:_tagv_frm_title
+en : Tags
+ru : Теги
+
+:_tagv_col_you
+en : You
+ru : Вы
+
+:_tagv_col_others
+en : Others
+ru : Остальные
+
+:_tagv_col_tag
+en : Tag
+ru : Тег
+
+:_tagv_col_rating
+en : Rating
+ru : Рейтинг
+
+:_tagv_col_spoiler
+en : Spoiler
+ru : Спойлер
+
+:_tagv_save
+en : Save changes
+ru : Сохранить изменения
+
+:_tagv_add
+en : Add tag
+ru : Добавить тег
+
+:_tagv_addmsg
+en : Check the [url,/g,tag list] to browse all available tags.[br]
+ Can't find what you're looking for? [url,/g/new,Request a new tag].
+ru : Откройте [url,/g,список тегов] чтобы увидеть все доступные теги.[br]
+ Не нашли что искали? Вы можете [url,/g/new,запросить новый тег].
+
+
+# User tag list (/u+/tags)
+
+:_tagu_title
+en : Tags by [_1]
+ru : Теги [_1]
+
+:_tagu_spoilerwarn
+en : Warning: spoilery tags are not hidden in this list!
+ru : Предупреждение: в этом списке отображаются теги-спойлеры!
+
+:_tagu_notags
+en : [_1] doesn't seem to have used the tagging system yet...
+ru : Похоже, что [_1] пока не использует систему тегов...
+
+:_tagu_col_num
+en : #VNs
+ru : #Новеллы
+
+:_tagu_col_name
+en : Tag
+ru : Тег
+
+:_tagu_spoil0
+en : No spoiler
+ru : Нет спойлера
+
+:_tagu_spoil1
+en : Minor spoiler
+ru : Лёгкий спойлер
+
+:_tagu_spoil2
+en : Major spoiler
+ru : Жёсткий спойлер
+
+
+# Tag index (/g)
+
+:_tagidx_title
+en : Tag index
+ru : Индекс тега
+
+:_tagidx_create
+en : Create new tag
+ru : Создать новый тег
+
+:_tagidx_search
+en : Search tags
+ru : Поиск тегов
+
+:_tagidx_browseall
+en : Browse all tags
+ru : Обзор всех тегов
+
+:_tagidx_recent
+en : Recently added
+ru : Недавно добавлены
+
+:_tagidx_popular
+en : Popular tags
+ru : Популярные теги
+
+:_tagidx_queue
+en : Awaiting moderation
+ru : Ожидают модерации
+
+:_tagidx_queue_empty
+en : Moderation queue empty! yay!
+ru : В очереди модерации пусто! Няа!
+
+:_tagidx_queue_link
+en : Moderation queue
+ru : Очередь модерации
+
+:_tagidx_denied
+en : Denied tags
+ru : Отклонённые теги
+
+
+
+
+#############################################################################
+## Personal User Lists ##
+#############################################################################
+# Handler::ULists
+
+
+# Wishlist (/u+/wish)
+
+:_wishlist_title_my
+en : My wishlist
+ru : Мой список желаемого
+
+:_wishlist_title_other
+en : [_1]'s wishlist
+ru : Список желаемого [_1]
+
+:_wishlist_noresults
+en : Wishlist empty...
+ru : Список пуст...
+
+:_wishlist_prio_all
+en : All priorities
+ru : Все приоритеты
+
+:_wishlist_col_title
+en : Title
+ru : Название
+
+:_wishlist_col_prio
+en : Priority
+ru : Приоритет
+
+:_wishlist_col_added
+en : Added
+ru : Добавлено
+
+:_wishlist_select
+en : -- with selected --
+ru : -- с выбранными --
+
+:_wishlist_changeprio
+en : Change priority
+ru : Изменить приоритет
+
+:_wishlist_remove
+en : remove from wishlist
+ru : убрать из списка желаемого
+
+
+# VN list (/u+/list)
+
+:_rlist_title_my
+en : My visual novel list
+ru : Мой список новелл
+
+:_rlist_title_other
+en : [_1]'s visual novel list
+ru : Список новелл [_1]
+
+:_rlist_voted_all
+en : All
+ru : Все
+
+:_rlist_voted_only
+en : Only voted
+ru : Проголосованные
+
+:_rlist_voted_none
+en : Hide voted
+ru : Скрыть проголосованные
+
+:_rlist_col_title
+en : Title
+ru : Название
+
+:_rlist_col_releases
+en : Releases
+ru : Выпуски
+
+:_rlist_col_vote
+en : Vote
+ru : Голос
+
+:_rlist_selection
+en : -- with selected --
+ru : -- с выбранными --
+
+:_rlist_changerel
+en : Change release status
+ru : Смена статуса выпуска
+
+:_rlist_changeplay
+en : Change play status
+ru : Смена статуса игры
+
+:_rlist_del
+en : remove from list
+ru : убрать из списка
+
+:_rlist_releasenote
+en : * Obtained/finished/total
+ru : * Приобретено/прочитано/всего
+
+
+
+
+#############################################################################
+## User pages ##
+#############################################################################
+# Util::Users
+
+
+# User page (/u+)
+
+:_userpage_title
+en : [_1]'s profile
+ru : Учётная запись [_1]
+
+:_userpage_username
+en : Username
+ru : Имя пользователя
+
+:_userpage_registered
+en : Registered
+ru : Регистрация
+
+:_userpage_edits
+en : Edits
+ru : Правки
+
+:_userpage_votes
+en : Votes
+ru : Отдано голосов
+
+:_userpage_votes_item
+en : [url,_1,_2] ([_3] average)
+ru : [url,_1,_2] ([_3] в среднем)
+
+:_userpage_hidden
+en : hidden
+ru : скрыто
+
+:_userpage_tags
+en : Tags
+ru : Теги
+
+:_userpage_tags_item
+en : [_1] [quant,_1,vote,votes] on [_2] distinct [quant,_2,tag,tags] and [_3] visual [quant,_3,novel,novels]
+ru : [_1] [quant,_1,голос,голоса,голосов] по [_2][quant,_2,-у тегу,-м различным тегам,-и различным тегам] и [_3][quant,_3,-й новелле,-м новеллам,-и новеллам]
+
+:_userpage_list
+en : List stats
+ru : Статистика по спискам
+
+:_userpage_list_item
+en : [_1] [quant,_1,release,releases] of [_2] visual [quant,_2,novel,novels]
+ru : [_1] [quant,_1,выпуск,выпуска,выпусков] [_2][quant,_2,-й новеллы,-х новелл,-и новелл]
+
+:_userpage_forum
+en : Forum stats
+ru : Статистика по форуму
+
+:_userpage_forum_item
+en : [_1] [quant,_1,post,posts], [_2] new [quant,_2,thead,threads].
+ru : [_1] [quant,_1,сообщение,сообщения,сообщений], [_2] [quant,_2,новая тема,новые темы,новых тем]
+
+:_userpage_forum_browse
+en : Browse posts
+ru : Обзор сообщений
+
+:_userpage_votestats
+en : Vote statistics
+ru : Статистика по голосованиям
+
+:_userpage_changes
+en : Recent changes
+ru : Свежие правки
+
+
+# Login form (/u/login)
+
+:_login_title
+en : Login
+ru : Вход
+
+:_login_username
+en : Username
+ru : Имя пользователя
+
+:_login_register
+en : No account yet?
+ru : Нет учётной записи?
+
+:_login_password
+en : Password
+ru : Пароль
+
+:_login_forgotpass
+en : Forgot your password?
+ru : Забыли пароль?
+
+
+# Reset password (/u/newpass)
+
+:_newpass_mail_body
+en : Hello [_1]
+
+ Your password has been reset, you can now login at http://vndb.org/ with the
+ following information:
+
+ Username: [_1]
+ Password: [_2]
+
+ Now don't forget your password again! :-)
+
+ vndb.org
+ru : Привет, [_1]
+
+ Ваш пароль был сброшен, теперь вы можете пройти авторизацию на http://vndb.org/
+ со следующими данными:
+
+ Имя пользователя: [_1]
+ Ваш новый пароль: [_2]
+
+ Постарайтесь больше не забывать свой пароль! :-)
+
+ vndb.org
+:_newpass_mail_subject
+en : New password for [_1]
+ru : Новый пароль для [_1]
+
+:_newpass_title
+en : Forgot password
+ru : Забытый пароль
+
+:_newpass_msg
+en : Forgot your password and can't login to VNDB anymore?
+ Don't worry! Just give us the email address you used to register on VNDB,
+ and we'll send you a new password within a few minutes!
+ru : Забыли пароль и больше не можете авторизоваться на VNDB?
+ Без паники! Всё что вам нужно - указать адрес электронной почты, который
+ вы использовали для регистрации в VNDB, и мы вышлем вам новый пароль за
+ считанные минуты!
+
+:_newpass_reset_title
+en : Reset password
+ru : Сброс пароля
+
+:_newpass_mail
+en : Email
+ru : E-mail
+
+:_newpass_sent_title
+en : New password
+ru : Новый пароль
+
+:_newpass_sent_subtitle
+en : Password reset
+ru : Сбросить пароль
+
+:_newpass_sent_msg
+en : Your password has been reset and your new password should reach your mailbox in a few minutes.[br]
+ You can always change your password again after logging in.[br]
+ [br]
+ [url,/u/login,Login] - [url,/,Home]
+ru : Ваш пароль был сброшен. Через несколько минут на ваш ящик прибудет письмо с новым паролем.[br]
+ Вы всегда можете изменить пароль после того как прошли авторизацию.[br]
+ [br]
+ [url,/u/login,Вход] - [url,/,Главная]
+
+# Register new account (/u/register)
+
+:_register_title
+en : Create an account
+ru : Создание учётной записи
+
+:_register_why
+en : Why should I register?
+ru : Для чего нужна регистрация?
+
+:_register_why_msg
+en : 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...[br]
+ [br]
+ Anyway, having an account here has a few advantages over being just a regular visitor[br]
+ - You can contribute to the database by editing any entries and adding new ones[br]
+ - Keep track of all visual novels and releases you have, you'd like to play, are playing, or have finished playing[br]
+ - Vote on the visual novels you liked or disliked[br]
+ - Contribute to the discussions on the boards
+ru : Создание учётной записи совершенно безопасно. Единственное, что нам от вас требуется - желаемое имя
+ пользователя и пароль. Вы можете ввести любой, даже абсолютно "левый", адрес электронной почты, поскольку
+ мы даже не проверяем его подлинность. Однако, помните что если вы ненароком забудете свой пароль, да ещё
+ и ввели неправильный адрес...[br]
+ [br]
+ В общем, наличие учётной записи здесь даёт несколько преимуществ перед простыми посетителями:[br]
+ - Вы можете помогать базе развиваться, редактируя любые страницы и добавляя новые[br]
+ - Следить за всеми новеллами и выпусками, которые у вас есть, в которые вы бы хотели сыграть, в которые играете, либо уже доиграли[br]
+ - Голосовать за понравившиеся или, наоборот, не понравившиеся новеллы[br]
+ - Вступать в обсуждения на ветках форума
+
+:_register_form_title
+en : New account
+ru : Новая учётная запись
+
+:_register_username
+en : Username
+ru : Имя пользователя
+
+:_register_username_msg
+en : Requested username. Must be lowercase and can only consist of alphanumeric characters.
+ru : Запрашиваемое имя пользователя. Должно состоять из буквенно-цифровых символов в нижнем регистре.
+
+:_register_mail
+en : Email
+ru : E-mail
+
+:_register_mail_msg
+en : 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.
+ru : Адрес вашей электронной почты будет использоваться лишь в случае утери пароля. Мы никогда не
+ пришлём вам спама или новостных рассылок, пока вы недвусмысленно не попросите об обратном.
+
+:_register_password
+en : Password
+ru : Пароль
+
+:_register_confirm
+en : Confirm password
+ru : Подтверждение пароля
+
+
+# User edit (/u+/edit)
+
+:_usere_title
+en : My account
+ru : Моя учётная запись
+
+:_usere_saved_title
+en : Settings saved
+ru : Параметры сохранены
+
+:_usere_saved_msg
+en : Settings successfully saved.
+ru : Параметры успешно сохранены.
+
+:_usere_geninfo
+en : General info
+ru : Основная информация
+
+:_usere_username
+en : Username
+ru : Имя пользователя
+
+:_usere_rank
+en : Rank
+ru : Ранг
+
+:_usere_ignvotes
+en : Ignore votes in VN statistics
+ru : Игнорировать голосования в статистике новелл
+
+:_usere_mail
+en : Email
+ru : E-mail
+
+:_usere_changepass
+en : Change password
+ru : Смена пароля
+
+:_usere_changepass_msg
+en : Leave blank to keep your current password
+ru : Оставьте пустым чтобы сохранить текущий пароль
+
+:_usere_password
+en : Password
+ru : Пароль
+
+:_usere_confirm
+en : Confirm password
+ru : Подтверждение пароля
+
+:_usere_options
+en : Options
+ru : Настройки
+
+:_usere_flist
+en : Allow other people to see my visual novel list ([url,_1,_1]) and wishlist ([url,_2,_2])
+ru : Разрешить остальным видеть мой список новелл ([url,_1,_1]) и список желаемого ([url,_2,_2])
+
+:_usere_fnsfw
+en : Disable warnings for images that are not safe for work.
+ru : Отключить предупреждения для небезопасных изображений.
+
+:_usere_skin
+en : Prefered skin
+ru : Предпочитаемая шкурка
+
+:_usere_css
+en : Additional [url,http://en.wikipedia.org/wiki/Cascading_Style_Sheets,CSS]
+ru : Дополнительный [url,http://en.wikipedia.org/wiki/Cascading_Style_Sheets,CSS]
+
+
+# Users posts (/u+/posts)
+
+:_uposts_title
+en : Posts made by [_1]
+ru : Сообщения [_1]
+
+:_uposts_noresults
+en : [_1] hasn't made any posts yet.
+ru : [_1] пока не имеет сообщений.
+
+:_uposts_col_date
+en : Date
+ru : Дата
+
+:_uposts_col_title
+en : Title
+ru : Название
+
+
+# User list (/u/all)
+
+:_ulist_title
+en : Browse users
+ru : Обзор пользователей
+
+:_ulist_col_username
+en : Username
+ru : Имя пользователя
+
+:_ulist_col_registered
+en : Registered
+ru : Регистрация
+
+:_ulist_col_votes
+en : Votes
+ru : Отдано голосов
+
+:_ulist_col_edits
+en : Edits
+ru : Правок
+
+:_ulist_col_tags
+en : Tags
+ru : Тегов
+
+
+
+
+#############################################################################
+## Visual novel pages ##
+#############################################################################
+# Handler::VNBrowse
+# Handler::VNEdit
+# Handler::VNPage
+# Util::CommonHTML::htmlVoteStats
+
+
+# VN browser (/v/all)
+
+:_vnbrowse_title
+en : Browse visual novels
+ru : Обзор новелл
+
+:_vnbrowse_col_score
+en : Score
+ru : Рейтинг
+
+:_vnbrowse_col_title
+en : Title
+ru : Название
+
+:_vnbrowse_col_released
+en : Released
+ru : Выпуск
+
+:_vnbrowse_col_popularity
+en : Popularity
+ru : Популярность
+
+:_vnbrowse_tagign_title
+en : The following tags were ignored:
+ru : Следующие теги были пропущены:
+
+:_vnbrowse_tagign_meta
+en : can't filter on meta tags
+ru : фильтрация мета-тегов невозможна
+
+:_vnbrowse_tagign_notfound
+en : no such tag found
+ru : тег не найден
+
+:_vnbrowse_advsearch
+en : advanced search
+ru : расширенный поиск
+
+:_vnbrowse_tags
+en : Tag filters
+ru : Фильтры тегов
+
+:_vnbrowse_booland
+en : boolean and, selecting more gives less results
+ru : булевое 'и', чем больше выбрано, тем меньше даёт результатов
+
+:_vnbrowse_taginc
+en : Tags to include
+ru : Включить теги
+
+:_vnbrowse_tagexc
+en : Tags to exclude
+ru : Исключить теги
+
+:_vnbrowse_spoil0
+en : Hide spoilers
+ru : Скрыть спойлеры
+
+:_vnbrowse_spoil1
+en : Show minor spoilers
+ru : Показать лёгкие спойлеры
+
+:_vnbrowse_spoil2
+en : Show major spoilers
+ru : Показать жёсткие спойлеры
+
+:_vnbrowse_lang
+en : Languages
+ru : Языки
+
+:_vnbrowse_boolor
+en : boolean or, selecting more gives more results
+ru : булевое 'или', чем больше выбрано, тем больше даёт результатов
+
+:_vnbrowse_plat
+en : Platforms
+ru : Платформы
+
+:_vnbrowse_apply
+en : Apply
+ru : Применить
+
+:_vnbrowse_clear
+en : Clear
+ru : Очистить
+
+
+# VN add/edit form (/v+/edit)
+
+:_vnedit_title_edit
+en : Edit [_1]
+ru : Правка [_1]
+
+:_vnedit_title_add
+en : Add a new visual novel
+ru : Добавление новой новеллы
+
+:_vnedit_geninfo
+en : General info
+ru : Основная информация
+
+:_vnedit_frm_title
+en : Title (romaji)
+ru : Название (ромадзи)
+
+:_vnedit_original
+en : Original title
+ru : Оригинальное название
+
+:_vnedit_original_msg
+en : The original title of this visual novel, leave blank if it already is in the Latin alphabet.
+ru : Оригинальное название данной новеллы, оставьте пустым если уже набрано в латинском алфавите.
+
+:_vnedit_alias
+en : Aliases
+ru : Прочие названия
+
+:_vnedit_alias_msg
+en : Comma seperated list of alternative titles or abbreviations. Can include both official
+ (japanese/english) titles and unofficial titles used around net.[br]
+ Titles that are listed in the releases should not be added here!
+ru : Список альтернативных названий или аббревиатур, перечисленных через запятую. Может включать как
+ официальные (японские/английские/русские) названия, так и неофициальные, имеющие хождение в сети.[br]
+ Не следует добавлять сюда названия, указанные в выпусках!
+
+:_vnedit_desc
+en : Description
+ru : Описание
+
+:_vnedit_desc_msg
+en : 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)
+ru : Краткое описание главной сюжетной линии. Пожалуйста, не вносите сюда спойлеры, а так же не
+ забывайте указать источник описания, если не являетесь его автором. (коды форматирования разрешены)
+
+:_vnedit_length
+en : Length
+ru : Продолжительность
+
+:_vnedit_links
+en : External links
+ru : Внешние ссылки
+
+:_vnedit_anime
+en : Anime
+ru : Аниме
+
+:_vnedit_anime_msg
+en : Whitespace seperated list of [url,http://anidb.net/,AniDB] anime IDs.
+ E.g. "1015 3348" will add [url,http://anidb.net/a1015,Shingetsutan Tsukihime]
+ and [url,http://anidb.net/a3348,Fate/stay night] as related anime.[br]
+ Note: It can take a few minutes for the anime titles to appear on the VN page.
+ru : Список идентификаторов аниме по [url,http://anidb.net/,AniDB], разделённых пробелами.
+ Например, "1015 3348" добавит связь с аниме [url,http://anidb.net/a1015,Shingetsutan Tsukihime]
+ и [url,http://anidb.net/a3348,Fate/stay night].[br]
+ Замечание: появление названия аниме на странице новеллы может занять несколько минут.
+
+:_vnedit_image
+en : Image
+ru : Изображение
+
+:_vnedit_image_none
+en : No image uploaded yet
+ru : Изображения пока нет
+
+:_vnedit_image_processing
+en : ~[processing image, please return in a few minutes~]
+ru : ~[обработка изображения, пожалуйста подождите несколько минут~]
+
+:_vnedit_image_upload
+en : Upload new image
+ru : Загрузить новое изображение
+
+:_vnedit_image_upload_msg
+en : 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.
+ru : Желательно, обложка CD/DVD/коробки. Изображение должно быть в формате JPEG, либо в
+ формате PNG, и весить не более 500 Кб. Изображения размером более 256 на 400 точек
+ будут автоматически уменьшены.
+
+:_vnedit_image_nsfw
+en : NSFW
+ru : НБДР (NSFW)
+
+:_vnedit_image_nsfw_check
+en : Not Safe For Work
+ru : Не безопасно для работы
+
+:_vnedit_image_nsfw_msg
+en : Please check this option if the image contains nudity, gore, or is otherwise not safe in a work-friendly environment.
+ru : Пожалуйста, поставьте эту галочку если изображение содержит наготу, кровищу, либо тем или иным образом не безопасно для рабочего окружения.
+
+:_vnedit_rel
+en : Relations
+ru : Связи
+
+:_vnedit_rel_sel
+en : Selected relations
+ru : Выбранные связи
+
+:_vnedit_rel_add
+en : Add relation
+ru : Добавить связь
+
+:_vnedit_scr
+en : Screenshots
+ru : Скриншоты
+
+:_vnedit_scr_msg
+en : Please keep the following in mind when uploading screenshots:[br]
+ - Screenshots have to be in the native resolution of the game,[br]
+ - Remove any window borders and make sure the image is unmarked,[br]
+ - Don't only upload event CGs.[br]
+ Please read the [url,/d2#6,guidelines] for more information.[br]
+ Make sure to submit the form after the upload has finished!
+ru : Когда загружаете скриншоты, пожалуйста помните:[br]
+ - Они должны быть исходного размера игры,[br]
+ - На изображении не должно быть "водяных знаков", а так же границ и заголовка окна,[br]
+ - Старайтесь загружать не только одни сюжетные картинки.[br]
+ За подробностями обращайтесь к [url,/d2#6,рекомендациям].[br]
+ Не забудьте сохранить изменения когда закончите загружать изображения!
+
+
+# VN Relation graph page (/v+/rg)
+
+:_vnrg_title
+en : Relation graph for [_1]
+ru : Схема связей для [_1]
+
+
+# VN Diff viewer (/v+.+)
+
+:_revfield_v_title
+en : Title (romaji)
+ru : Название (ромадзи)
+
+:_revfield_v_original
+en : Original title
+ru : Оригинальное название
+
+:_revfield_v_alias
+en : Alias
+ru : Прочие названия
+
+:_revfield_v_desc
+en : Description
+ru : Описание
+
+:_revfield_v_length
+en : Length
+ru : Продолжительность
+
+:_vndiff_nolink
+en : ~[no link~]
+ru : ~[нет ссылки~]
+
+:_vndiff_none
+en : ~[none~]
+ru : ~[пусто~]
+
+:_revfield_v_l_wp
+en : Wikipedia link
+ru : Ссылка Википедии
+
+:_revfield_v_l_encubed
+en : Encubed tag
+ru : Тег Encubed'а
+
+:_revfield_v_l_renai
+en : Renai.us link
+ru : Ссылка Renai.us
+
+:_revfield_v_l_vnn
+en : V-N.net link
+ru : Ссылка V-N.net
+
+:_revfield_v_relations
+en : Relations
+ru : Связи
+
+:_revfield_v_anime
+en : Anime
+ru : Аниме
+
+:_revfield_v_screenshots
+en : Screenshots
+ru : Скриншоты
+
+:_revfield_v_image
+en : Image
+ru : Изображение
+
+:_vndiff_image_nsfw
+en : (NSFW)
+ru : (НБДР|NSFW)
+
+:_vndiff_image_proc
+en : ~[processing~]
+ru : ~[обработка~]
+
+:_vndiff_image_none
+en : No image
+ru : Нет изображения
+
+:_revfield_v_img_nsfw
+en : Image NSFW
+ru : Изображение НБДР (NSFW)
+
+:_vndiff_nsfw_safe
+en : Safe
+ru : Безопасно
+
+:_vndiff_nsfw_notsafe
+en : Not safe
+ru : Не безопасно
+
+
+# VN page (/v+)
+
+:_vnpage_noimg
+en : No image uploaded yet
+ru : Нет загруженного изображения
+
+:_vnpage_imgproc
+en : ~[processing image, please return in a few minutes~]
+ru : ~[идёт обработка изображения, подождите несколько минут~]
+
+:_vnpage_imgnsfw_msg
+en : This image has been flagged as Not Safe For Work.
+ru : Данное изображение помечено как не безопасное для работы.
+
+:_vnpage_imgnsfw_show
+en : Show me anyway
+ru : Всё равно показать
+
+:_vnpage_imgnsfw_note
+en : (This warning can be disabled in your account)
+ru : (Это предупреждение можно отключить в настройках вашей учётной записи)
+
+:_vnpage_imgnsfw_foot
+en : Flagged as NSFW
+ru : Помечено как НБДР (NSFW)
+
+:_vnpage_vntitle
+en : Title
+ru : Название
+
+:_vnpage_original
+en : Original title
+ru : Оригинальное название
+
+:_vnpage_alias
+en : Aliases
+ru : Прочие названия
+
+:_vnpage_length
+en : Length
+ru : Продолжительность
+
+:_vnpage_links
+en : Links
+ru : Ссылки
+
+:_vnpage_description
+en : Description
+ru : Описание
+
+:_vnpage_tags_spoil0
+en : hide spoilers
+ru : скрыть спойлеры
+
+:_vnpage_tags_spoil1
+en : show minor spoilers
+ru : показать лёгкие спойлеры
+
+:_vnpage_tags_spoil2
+en : spoil me!
+ru : показать все!
+
+:_vnpage_tags_summary
+en : summary
+ru : суммарно
+
+:_vnpage_tags_all
+en : all
+ru : все
+
+:_vnpage_producers
+en : Producers
+ru : Компании
+
+:_vnpage_relations
+en : Relations
+ru : Связи
+
+:_vnpage_anime
+en : Related anime
+ru : Связанное аниме
+
+:_vnpage_anime_noinfo
+en : ~[no information available at this time: [url,_2,_1]~]
+ru : ~[к сожалению, пока никакой информации: [url,_2,_1]~]
+
+:_vnpage_uopt
+en : User options
+ru : Настройки пользователя
+
+:_vnpage_uopt_voted
+en : your vote: [_1]
+ru : ваш голос: [_1]
+
+:_vnpage_uopt_novote
+en : not voted yet
+ru : пока без голоса
+
+:_vnpage_uopt_changevote
+en : Change vote
+ru : Переголосовать
+
+:_vnpage_uopt_dovote
+en : Vote
+ru : Голосовать
+
+:_vnpage_uopt_delvote
+en : revoke
+ru : снять голос
+
+:_vnpage_uopt_wishlisted
+en : wishlist: [_1]
+ru : список желаемого: [_1]
+
+:_vnpage_uopt_nowish
+en : not on your wishlist
+ru : не в вашем списке
+
+:_vnpage_uopt_changewish
+en : Change status
+ru : Сменить статус
+
+:_vnpage_uopt_addwish
+en : Add to wishlist
+ru : Добавить к списку
+
+:_vnpage_uopt_delwish
+en : remove from wishlist
+ru : убрать из списка
+
+:_vnpage_rel
+en : Releases
+ru : Выпуски
+
+:_vnpage_rel_none
+en : We don't have any information about releases of this visual novel yet...
+ru : У нас пока нет информации о выпусках этой новеллы...
+
+:_vnpage_rel_add
+en : add release
+ru : добавить выпуск
+
+:_vnpage_rel_patch
+en : (patch)
+ru : (патч)
+
+:_vnpage_rel_extlink
+en : External link
+ru : Внешняя ссылка
+
+:_vnpage_scr
+en : Screenshots
+ru : Скриншоты
+
+:_vnpage_scr_showing
+en : Showing [_1] out of [_2] [quant,_2,screenshot,screenshots].
+ru : [_1] из [_2] [quant,_2,скриншота,скриншотов,скриншотов] отображено.
+
+:_vnpage_scr_nsfwhide
+en : show/hide NSFW
+ru : показать/скрыть НБДР (NSFW)
+
+:_vnpage_scr_num
+en : Screenshot #[_1]
+ru : Скриншот #[_1]
+
+:_vnpage_stats
+en : User stats
+ru : Статистика пользователей
+
+:_vnpage_stats_none
+en : Nobody has voted on this visual novel yet...
+ru : Никто пока не голосовал за эту новеллу...
+
+:_votestats_title
+en : Vote stats
+ru : Статистика голосования
+
+:_votestats_sum
+en : [_1] [quant,_1,vote,votes] total, average [_1]
+ru : Всего [_1] [quant,_1,голос,голоса,голосов], в среднем [_1]
+
+:_votestats_recent
+en : Recent votes
+ru : Недавно проголосовали
+
+:_votestats_pop_title
+en : Popularity
+ru : Популярность
+
+:_votestats_pop_sum
+en : Ranked #[_1] out of [_2] with a score of [_3].
+ru : Рейтинг - #[_1] из [_2], средний балл - [_3].
+
+
+
+
+#############################################################################
+## Misc. messages ##
+#############################################################################
+# Util::CommonHTML::htmlDenied,
+# htmlHiddenMessage, htmlEditMessage and htmlItemMessage
+
+
+# Generic "Access denied!" page
+
+:_denied_title
+en : Access Denied
+ru : В доступе отказано
+
+# not logged in
+:_denied_needlogin_title
+en : You need to be logged in to perform this action.
+ru : Для выполнения этого действия требуется авторизация.
+
+:_denied_needlogin_msg
+en : Please [url,/u/login,login], or [url,/u/register,create an account] if you don't have one yet.
+ru : Пожалуйста [url,/u/login,авторизуйтесь], либо [url,/u/register,создайте учётную запись], если таковой у вас нет.
+
+# logged in, but simply no access
+:_denied_noaccess_title
+en : You are not allowed to perform this action.
+ru : Вам запрещено выполнять это действие.
+
+:_denied_noaccess_msg
+en : It seems you don't have the proper rights to perform the action you wanted to perform...
+ru : Похоже, у вас нет прав на выполнение того, чего вы хотите...
+
+
+# "DB Item has been deleted" page
+
+:_hiddenmsg_title
+en : Item deleted
+ru : Запись удалена
+
+:_hiddenmsg_msg
+en : This item has been deleted from the database, File a request on the
+ [url,_1,discussion board] to undelete this page.
+ru : Данная запись удалена из базы данных. Пожалуйста, подайте заявку на
+ [url,_1,форуме] для восстановления этой страницы.
+
+
+# The warning/notice messages on edit pages
+
+:_editmsg_copy_title
+en : You're not editing a release!
+ru : Вы не редактируете выпуск, а копируете его!
+
+:_editmsg_copy_msg
+en : You're about to insert a new release into the database with information based on [_1].[br]
+ Hit the 'edit' tab on the right-top if you intended to edit the release instead of creating a new one.
+ru : Вы собираетесь вставить новый выпуск, основанный на информации из [_1].[br]
+ Щёлкните вкладку 'правка' в правом верхнем углу, если собирались редактировать выпуск, а не создавать новый.
+
+:_editmsg_msg_title
+en : Before editing:
+ru : Прежде чем приступить к редактированию:
+
+:_editmsg_msg_guidelines
+en : Read the [url,_1,guidelines]!
+ru : Прочтите [url,_1,рекомендации]!
+
+:_editmsg_msg_discuss
+en : Check for any existing discussions on the [url,_1,discussion board]
+ru : Проверьте все существующие темы на [url,_1,форуме]
+
+:_editmsg_msg_history
+en : Browse the [url,_1,edit history] for any recent changes related to what you want to change.
+ru : Просмотрите [url,_1,историю правок] на предмет недавних изменений данных, которые вы хотите изменить.
+
+:_editmsg_msg_search
+en : [url,_1,Search the database] to see if we already have information about this [index,_2,visual novel,release,producer].
+ru : [url,_1,Воспользуйтесь поиском], ведь вполне возможно что у нас уже есть информация об [index,_2,этой новелле,этом выпуске,этой компании].
+
+:_editmsg_revert_title
+en : Reverting
+ru : Восстановление
+
+:_editmsg_revert_msg
+en : You are editing an old revision of this [index,_1,visual novel,release,producer].
+ If you save it, all changes made after this revision will be reverted!
+ru : Вы правите старую редакцию страницы [index,_1,новеллы,выпуска,компании].
+ Если вы сохраните её, все изменения сделанные после этой правки будут утеряны!
+
+
+# Messages about editing on the VN/Release/Producer pages (right beneath the tabs)
+
+:_itemmsg_locked
+en : Locked for editing
+ru : Правка заблокирована
+
+:_itemmsg_login
+en : You need to be [url,_1,logged in] to edit this page
+ru : Чтобы редактировать эту страницу, вы должны быть [url,_1,авторизованы]
+
+:_itemmsg_denied
+en : You are not allowed to edit this page
+ru : Вам запрещено редактировать эту страницу
+
+
+# User didn't pass the spam protection
+
+:_nospam_title
+en : Could not send form
+ru : Невозможно отправить форму
+
+:_nospam_subtitle
+en : Error
+ru : Ошибка
+
+:_nospam_msg
+en : The form could not be sent, please make sure you have Javascript enabled in your browser.
+ru : Не удалось отправить форму, пожалуйста убедитесь что в вашем браузере включён Javascript.
+
+
+# Short message reminding the user to post in ENGLISH (Used at about every message/description input field)
+
+:_inenglish
+en : English please!
+ru : Пожалуйста, пишите на английском!
+
+
diff --git a/data/style.css b/data/style.css
index 84e6fd45..53255c74 100644
--- a/data/style.css
+++ b/data/style.css
@@ -119,6 +119,7 @@ b.spoiler_shown { font-weight: normal }
border-left: 1px dotted $border$;
text-align: left;
}
+.linethrough { text-decoration: line-through }
@@ -156,7 +157,7 @@ fieldset.submit {
margin: 5px;
}
fieldset.submit input {
- width: 200px;
+ width: 150px;
}
fieldset.submit h2 {
font-size: 11px!important;
@@ -215,13 +216,13 @@ td.field label {
margin: -17px 0 15px 15px;
font-weight: normal;
}
-#maincontent div.mainbox {
+#maincontent div.mainbox, #maincontent table.mainbox td {
border: 1px solid $border$;
margin: 21px 0 -10px 0;
padding: 5px;
background: url($_boxbg$) repeat;
}
-#maincontent div.mainbox a {
+#maincontent .mainbox a {
color: $link$;
}
#maincontent p {
@@ -238,7 +239,7 @@ td.field label {
p.center {
text-align: center;
}
-b.future {
+b.future, b.standout {
font-weight: normal;
color: $standout$;
}
@@ -271,6 +272,10 @@ b.future {
font-size: 11px;
color: $maintext$;
}
+#menulist h2 span {
+ float: right;
+ padding-top: 1px;
+}
#menulist dt {
display: block;
float: left;
@@ -362,18 +367,17 @@ b.future {
/***** Homepage ******/
#maincontent .mainbox.threelayout {
- float: left;
- width: 31%;
- height: 200px;
- margin: 21px 10px -10px 0;
- padding: 2px;
- overflow: hidden;
+ border-collapse: separate;
+ border-spacing: 10px;
+ margin: 10px -10px -20px -10px;
+ min-width: 100%;
}
-#maincontent .mainbox.threelayout.last {
- margin-right: 0;
+#maincontent .mainbox.threelayout td {
+ width: 32%;
+ padding: 0 2px 10px 2px;
}
#maincontent .mainbox.threelayout h1 {
- margin: -3px 0 1px 0;
+ margin: 0;
font-size: 14px;
font-weight: bold;
}
@@ -397,6 +401,9 @@ p.screenshots {
p.screenshots img {
margin: 2px;
}
+#maincontent .mainbox.threelayout h1 a {
+ color: $boxtitle$;
+}
@@ -717,15 +724,15 @@ a.addnew {
/***** VN edit *****/
-#jt_box_relations table { margin-bottom: 10px; }
-#jt_box_relations h2 { margin: 0 0 3px 0px; }
-#jt_box_relations td { padding: 1px 2px; vertical-align: middle; }
-#jt_box_relations td.tc1 { width: 300px; text-align: right }
-#jt_box_relations td.tc2 { width: 170px; white-space: nowrap }
-#jt_box_relations td.tc3 { width: 200px; }
-#jt_box_relations td.tc4 { width: 40px; text-align: right }
-#jt_box_relations td.tc1 input { width: 280px; }
-#jt_box_relations td.tc2 select { width: 130px; }
+#jt_box_vn_rel table { margin-bottom: 10px; }
+#jt_box_vn_rel h2 { margin: 0 0 3px 0px; }
+#jt_box_vn_rel td { padding: 1px 2px; vertical-align: middle; }
+#jt_box_vn_rel td.tc1 { width: 300px; text-align: right }
+#jt_box_vn_rel td.tc2 { width: 170px; white-space: nowrap }
+#jt_box_vn_rel td.tc3 { width: 200px; }
+#jt_box_vn_rel td.tc4 { width: 40px; text-align: right }
+#jt_box_vn_rel td.tc1 input { width: 280px; }
+#jt_box_vn_rel td.tc2 select { width: 130px; }
#ds_box {
border: 1px solid $border$;
@@ -743,16 +750,16 @@ a.addnew {
width: 100%;
}
-#jt_box_image div.img {
+#jt_box_vn_img div.img {
float: left;
height: 400px;
padding-right: 20px;
}
-#jt_box_image h2 {
+#jt_box_vn_img h2 {
margin: 0;
}
-#jt_box_screenshots table { width: 95% }
+#jt_box_vn_scr table { width: 95% }
#scr_table td { height: 108px; border-top: 1px solid #258; padding: 0; padding-right: 5px }
#scr_table td.thumb { width: 136px; vertical-align: middle }
#scr_table select { width: 400px; }
@@ -850,19 +857,19 @@ a.addnew {
.platforms { padding-left: 20px; }
.platforms span { display: block; float: left; width: 150px; }
-#jt_box_format h2 { clear: left; padding-top: 10px; }
+#jt_box_rel_format h2 { clear: left; padding-top: 10px; }
#media_div select.qty { width: 90px; }
#media_div select.medium { width: 150px }
#media_div { padding-left: 20px; }
#media_div span { display: block }
-#jt_box_visual_novels h2, #jt_box_producers h2 { clear: left; padding-top: 10px; }
-#jt_box_visual_novels div, #jt_box_producers div { padding-left: 20px }
-#jt_box_visual_novels input, #jt_box_producers input { margin-right: 10px; width: 300px }
-#jt_box_visual_novels span, #jt_box_producers span { clear: left; display: block; padding: 2px; }
-#jt_box_visual_novels span.odd, #jt_box_producers span.odd { background: url($_boxbg$) repeat; }
-#jt_box_visual_novels i, #jt_box_producers i { font-style: normal; display: block; float: left; width: 310px }
-#jt_box_visual_novels b, #jt_box_producers b { font-weight: normal; }
+#jt_box_rel_vn h2, #jt_box_rel_prod h2 { clear: left; padding-top: 10px; }
+#jt_box_rel_vn div, #jt_box_rel_prod div { padding-left: 20px }
+#jt_box_rel_vn input, #jt_box_rel_prod input { margin-right: 10px; width: 300px }
+#jt_box_rel_vn span, #jt_box_rel_prod span { clear: left; display: block; padding: 2px; }
+#jt_box_rel_vn span.odd, #jt_box_rel_prod span.odd { background: url($_boxbg$) repeat; }
+#jt_box_rel_vn i, #jt_box_rel_prod i { font-style: normal; display: block; float: left; width: 310px }
+#jt_box_rel_vn b, #jt_box_rel_prod b { font-weight: normal; }
@@ -963,7 +970,7 @@ div.browse.uposts td.tc1 {
/***** VN tagmod *****/
-#jt_box_tags .formtable table td { padding: 1px 5px }
+#jt_box_tagmod .formtable table td { padding: 1px 5px }
#tagtable tfoot td { padding-top: 20px!important; }
#tagtable .tc2_1 { border-right: 1px solid $border$; border-left: 1px solid $border$; width: 150px; text-align: center }
#tagtable .tc3_1 { border-left: 1px solid $border$; width: 150px; text-align: center }
@@ -984,7 +991,7 @@ a.taglvl:hover { border-bottom: 1px solid transparent!important }
.taglvlsel.taglvl1 { background-color: #cf0; border-color: #cf0 }
.taglvlsel.taglvl2 { background-color: #8f0; border-color: #8f0 }
.taglvlsel.taglvl3 { background-color: #0f0; border-color: #0f0 }
-#jt_box_tags #tagtable .tc3 { padding: 0 }
+#jt_box_tagmod #tagtable .tc3 { padding: 0 }
#tagtable .tc3 select { width: 90px; height: 15px; border: 0; margin: 0; font-size: 11px; background-color: $_blendbg$; text-align: right }
#tagtable .odd .tc3 select { background-color: $secbg$ }
@@ -1124,7 +1131,7 @@ div#iv_view {
height: 11px;
opacity: 0.5;
}
-.icons.par, .icons.tri, .icons.com { width: 11px; }
+.icons.rt0, .icons.rt1, .icons.rt2 { width: 11px; }
acronym.icons, acronym.uicons { cursor: default; }
a .icons { cursor: pointer }
.icons.oth { background: none; }
@@ -1135,6 +1142,7 @@ a .icons { cursor: pointer }
.icons.sfc { background-position: 0px -56px; }
.icons.gba { background-position: 0px -70px; }
.icons.ps3 { background-position: 0px -84px; }
+.icons.p98 { background-position: 0px -98px; }
.icons.dvd { background-position: -16px 0px; }
.icons.mac { background-position: -16px -14px; }
@@ -1143,15 +1151,15 @@ a .icons { cursor: pointer }
.icons.win { background-position: -16px -56px; }
.icons.wii { background-position: -16px -70px; }
.icons.xb3 { background-position: -16px -84px; }
+.icons.sat { background-position: -16px -98px; }
-.icons.com { background-position: -32px 0px; }
-.icons.par { background-position: -32px -14px; }
-.icons.tri { background-position: -32px -28px; }
+.icons.rt0 { background-position: -32px 0px; }
+.icons.rt1 { background-position: -32px -14px; }
+.icons.rt2 { background-position: -32px -28px; }
.icons.ext { background-position: -32px -42px; }
.icons.msx { background-position: -32px -56px; }
.icons.nes { background-position: -32px -70px; }
-
-.icons.vi { background-position: -34px -88px; }
+.icons.dos { background-position: -32px -84px; }
.icons.cs { background-position: -48px 0px; }
.icons.da { background-position: -48px -11px; }
@@ -1172,4 +1180,5 @@ a .icons { cursor: pointer }
.icons.tr { background-position: -61px -66px; }
.icons.zh { background-position: -61px -77px; }
.icons.ko { background-position: -61px -88px; }
+.icons.vi { background-position: -61px -99px; }
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 => [ \&gtintype, '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?'&#9662;':'&#9656;').'</i> filters';
+ lit '<i>'.($shown?'&#9662;':'&#9656;').'</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>&#9656;</i> #VNs ';
+ lit '<i>&#9656;</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>&#9656;</i>Releases*'; end; },
- [ Vote => 'vote' ],
+ [ mt('_rlist_col_title') => 'title', 3 ],
+ sub { td class => 'tc2', id => 'relhidall'; lit '<i>&#9656;</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 &raquo;'; end;
+ txt ' ';
+ a href => "/u$uid/posts"; lit mt('_userpage_forum_browse').' &raquo;'; 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>&#9656;</i> advanced search';
+ lit '<i>&#9656;</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&amp;task=view&amp;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&amp;task=view&amp;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 '&nbsp;'; 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 '&nbsp;'; 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/&/&amp;/;
- $txt =~ s/</&lt;/;
- $txt =~ s/>/&gt;/;
- 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/&/&amp;/;
+ $txt =~ s/</&lt;/;
+ $txt =~ s/>/&gt;/;
+ 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
diff --git a/static/f/forms.js b/static/f/forms.js
index 4e59796d..ce9daab5 100644
--- a/static/f/forms.js
+++ b/static/f/forms.js
@@ -213,7 +213,7 @@ function relLoad() {
// make sure the title is up-to-date
x('title').onchange = function() {
- l = x('jt_box_relations').getElementsByTagName('td');
+ l = x('jt_box_vn_rel').getElementsByTagName('td');
for(i=0;i<l.length;i++)
if(l[i].className == 'tc3')
l[i].innerHTML = shorten(this.value, 40);
@@ -645,7 +645,7 @@ function medSetSubmit() {
function medAddNew(med, qty) {
var o = document.createElement('span');
var r = '<select class="qty" onchange="medSerialize()"><option value="0">- quantity -</option>';
- for(var i=1;i<=10;i++)
+ for(var i=1;i<=20;i++)
r += '<option value="'+i+'"'+(qty == i ? ' selected="selected"' : '')+'>'+i+'</option>';
r += '</select><select class="medium" onchange="return medCheckNew(this)">';
for(i=0;i<medTypes.length;i++)
@@ -720,7 +720,7 @@ function vnpLoad(type) {
vnpCheckEmpty(type);
// dropdown
- var n = x('jt_box_'+(type == 'vn' ? 'visual_novels' : type)).getElementsByTagName('div')[1];
+ var n = x('jt_box_'+(type == 'vn' ? 'rel_vn' : 'rel_prod')).getElementsByTagName('div')[1];
dsInit(n.getElementsByTagName('input')[0], '/xml/'+type+'.xml?q=', function(item, tr) {
var td = document.createElement('td');
td.innerHTML = type.substr(0,1)+item.getAttribute('id');
@@ -771,7 +771,7 @@ function vnpStripe(type) {
}
function vnpFormAdd(type) {
- var n = x('jt_box_'+(type == 'vn' ? 'visual_novels' : type)).getElementsByTagName('div')[1];
+ var n = x('jt_box_'+(type == 'vn' ? 'rel_vn' : 'rel_prod')).getElementsByTagName('div')[1];
var txt = n.getElementsByTagName('input')[0];
var lnk = n.getElementsByTagName('a')[0];
var input = txt.value;
@@ -825,7 +825,7 @@ function vnpSerialize(type) {
function tglLoad() {
var n = x('tagtable').getElementsByTagName('tfoot')[0].getElementsByTagName('input');
- dsInit(n[0], '/xml/tags.xml?q=', function(item, tr) {
+ dsInit(n[1], '/xml/tags.xml?q=', function(item, tr) {
var td = document.createElement('td');
td.innerHTML = shorten(item.firstChild.nodeValue, 40);
if(item.getAttribute('meta') == 'yes')
@@ -836,7 +836,7 @@ function tglLoad() {
}, function(item) {
return item.firstChild.nodeValue;
}, tglAdd);
- n[1].onclick = tglAdd;
+ n[2].onclick = tglAdd;
tglStripe();
var l = x('tagtable').getElementsByTagName('tbody')[0].getElementsByTagName('tr');
@@ -888,12 +888,12 @@ function tglVoteBarSel(obj, vote) {
function tglAdd() {
var n = x('tagtable').getElementsByTagName('tfoot')[0].getElementsByTagName('input');
- n[0].disabled = n[1].disabled = true;
- n[1].value = 'loading...';
- ajax('/xml/tags.xml?q=name:'+encodeURIComponent(n[0].value), function(hr) {
- n[0].disabled = n[1].disabled = false;
- n[1].value = 'Add tag';
- n[0].value = '';
+ n[1].disabled = n[2].disabled = true;
+ n[2].value = 'loading...';
+ ajax('/xml/tags.xml?q=name:'+encodeURIComponent(n[1].value), function(hr) {
+ n[1].disabled = n[1].disabled = false;
+ n[2].value = 'Add tag';
+ n[1].value = '';
var items = hr.responseXML.getElementsByTagName('item');
if(items.length < 1)
@@ -903,12 +903,12 @@ function tglAdd() {
var name = items[0].firstChild.nodeValue;
var l = x('tagtable').getElementsByTagName('a');
for(var i=0; i<l.length; i++)
- if(l[i].innerHTML == shorten(name, 40))
+ if(l[i].innerHTML == qq(name))
return alert('Tag is already present!');
var tr = document.createElement('tr');
var td = document.createElement('td');
- td.innerHTML = '<a href="/g'+items[0].getAttribute('id')+'">'+name+'</a>';
+ td.innerHTML = '<a href="/g'+items[0].getAttribute('id')+'">'+qq(name)+'</a>';
td.className = 'tc1';
tr.appendChild(td);
td = document.createElement('td');
diff --git a/static/f/icons.png b/static/f/icons.png
index 47412c62..78ec79fc 100644
--- a/static/f/icons.png
+++ b/static/f/icons.png
Binary files differ
diff --git a/static/f/script.js b/static/f/script.js
index c94d781a..987963dc 100644
--- a/static/f/script.js
+++ b/static/f/script.js
@@ -607,17 +607,26 @@ DOMLoad(function() {
// forms.js
if(x('relations'))
relLoad();
- if(x('jt_box_screenshots'))
+ if(x('jt_box_vn_scr'))
scrLoad();
if(x('media'))
medLoad();
- if(x('jt_box_visual_novels'))
+ if(x('jt_box_rel_vn'))
vnpLoad('vn');
- if(x('jt_box_producers'))
+ if(x('jt_box_rel_prod'))
vnpLoad('producers');
if(x('taglinks'))
tglLoad();
+ // make some fields readonly when patch flag is set
+ if(x('jt_box_rel_geninfo')) {
+ var func = function() {
+ x('doujin').disabled = x('resolution').disabled = x('voiced').disabled = x('ani_story').disabled = x('ani_ero').disabled = x('patch').checked;
+ };
+ func();
+ x('patch').onclick = func;
+ }
+
// spam protection on all forms
if(document.forms.length >= 1)
for(i=0; i<document.forms.length; i++)
diff --git a/util/dump.sql b/util/dump.sql
index b3baae99..aaa1a5e9 100644
--- a/util/dump.sql
+++ b/util/dump.sql
@@ -234,7 +234,7 @@ CREATE TABLE threads_posts (
num smallint NOT NULL DEFAULT 0,
uid integer NOT NULL DEFAULT 0,
date timestamptz NOT NULL DEFAULT NOW(),
- edited timestamptz NOT NULL,
+ edited timestamptz,
msg text NOT NULL DEFAULT '',
hidden boolean NOT NULL DEFAULT FALSE,
PRIMARY KEY(tid, num)
@@ -264,7 +264,8 @@ CREATE TABLE users (
customcss text NOT NULL DEFAULT '',
ip inet NOT NULL DEFAULT '0.0.0.0',
c_tags integer NOT NULL DEFAULT 0,
- salt character(9) NOT NULL DEFAULT ''
+ salt character(9) NOT NULL DEFAULT '',
+ ign_votes voolean NOT NULL DEFAULT FALSE
);
-- vn
@@ -484,7 +485,11 @@ $$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION update_vnpopularity() RETURNS void AS $$
BEGIN
CREATE OR REPLACE TEMP VIEW tmp_pop1 (uid, vid, rank) AS
- SELECT v.uid, v.vid, sqrt(count(*))::real FROM votes v JOIN votes v2 ON v.uid = v2.uid AND v2.vote < v.vote GROUP BY v.vid, v.uid;
+ SELECT v.uid, v.vid, sqrt(count(*))::real
+ FROM votes v
+ JOIN votes v2 ON v.uid = v2.uid AND v2.vote < v.vote
+ WHERE v.uid NOT IN(SELECT id FROM users WHERE ign_votes)
+ GROUP BY v.vid, v.uid;
CREATE OR REPLACE TEMP VIEW tmp_pop2 (vid, win) AS
SELECT vid, sum(rank) FROM tmp_pop1 GROUP BY vid;
UPDATE vn SET c_popularity = COALESCE((SELECT win/(SELECT MAX(win) FROM tmp_pop2) FROM tmp_pop2 WHERE vid = id), 0);
@@ -581,7 +586,7 @@ BEGIN
-- grouped by (tag, vid, uid), so only one user votes on one parent tag per VN entry
CREATE OR REPLACE TEMPORARY VIEW tags_vn_grouped AS
SELECT tag, vid, uid, MAX(vote)::real AS vote, COALESCE(AVG(spoiler), 0)::real AS spoiler
- FROM tags_vn_all WHERE vote > 0 GROUP BY tag, vid, uid;
+ FROM tags_vn_all GROUP BY tag, vid, uid;
-- grouped by (tag, vid) and serialized into a table
DROP INDEX IF EXISTS tags_vn_bayesian_tag;
TRUNCATE tags_vn_bayesian;
@@ -589,7 +594,8 @@ BEGIN
SELECT tag, vid, COUNT(uid) AS users, AVG(vote)::real AS rating,
(CASE WHEN AVG(spoiler) < 0.7 THEN 0 WHEN AVG(spoiler) > 1.3 THEN 2 ELSE 1 END)::smallint AS spoiler
FROM tags_vn_grouped
- GROUP BY tag, vid;
+ GROUP BY tag, vid
+ HAVING AVG(vote) > 0;
CREATE INDEX tags_vn_bayesian_tag ON tags_vn_bayesian (tag);
-- now perform the bayesian ranking calculation
UPDATE tags_vn_bayesian tvs SET rating =
diff --git a/util/updates/update_2.7.sql b/util/updates/update_2.7.sql
new file mode 100644
index 00000000..e9061106
--- /dev/null
+++ b/util/updates/update_2.7.sql
@@ -0,0 +1,108 @@
+
+
+-- add a flag to users whose votes we want to ignore
+ALTER TABLE users ADD COLUMN ign_votes boolean NOT NULL DEFAULT FALSE;
+
+CREATE OR REPLACE FUNCTION update_vnpopularity() RETURNS void AS $$
+BEGIN
+ CREATE OR REPLACE TEMP VIEW tmp_pop1 (uid, vid, rank) AS
+ SELECT v.uid, v.vid, sqrt(count(*))::real
+ FROM votes v
+-- JOIN users u ON u.id = v.uid AND NOT u.ign_votes -- slow
+ JOIN votes v2 ON v.uid = v2.uid AND v2.vote < v.vote
+ WHERE v.uid NOT IN(SELECT id FROM users WHERE ign_votes) -- faster
+ GROUP BY v.vid, v.uid;
+ CREATE OR REPLACE TEMP VIEW tmp_pop2 (vid, win) AS
+ SELECT vid, sum(rank) FROM tmp_pop1 GROUP BY vid;
+ UPDATE vn SET c_popularity = COALESCE((SELECT win/(SELECT MAX(win) FROM tmp_pop2) FROM tmp_pop2 WHERE vid = id), 0);
+ RETURN;
+END;
+$$ LANGUAGE plpgsql;
+
+
+
+-- VN relations cleanup
+
+UPDATE vn_relations SET relation = relation + 50 WHERE relation IN(8, 9, 10);
+UPDATE vn_relations SET relation = relation - 1 WHERE relation > 3 AND relation < 50;
+UPDATE vn_relations SET relation = 7 WHERE relation = 60;
+DELETE FROM vn_relations WHERE relation > 50;
+
+-- Be sure to execute the following query after restarting Multi, to regenerate the relation graphs:
+-- UPDATE vn SET rgraph = NULL;
+
+
+
+
+
+-- set freeware flag for all trials with internet download as medium
+
+CREATE FUNCTION tmp_edit_release(iid integer) RETURNS void AS $$
+DECLARE
+ cid integer;
+ oid integer;
+BEGIN
+ SELECT INTO oid latest FROM releases WHERE id = iid;
+ INSERT INTO changes (type, requester, ip, comments, rev)
+ VALUES (1, 1, '0.0.0.0',
+ E'Automated edit with the update to VNDB 2.7.\n\nThis release is a downloadable trial, freeware flag is assumed.',
+ (SELECT rev+1 FROM changes WHERE id = oid))
+ RETURNING id INTO cid;
+ INSERT INTO releases_rev (id, rid, title, original, type, website, released, notes,
+ minage, gtin, patch, catalog, resolution, voiced, freeware, doujin, ani_story, ani_ero)
+ SELECT cid, rid, title, original, type, website, released, notes,
+ minage, gtin, patch, catalog, resolution, voiced, true, doujin, ani_story, ani_ero
+ FROM releases_rev WHERE id = oid;
+ INSERT INTO releases_media (rid, medium, qty) SELECT cid, medium, qty FROM releases_media WHERE rid = oid;
+ INSERT INTO releases_platforms (rid, platform) SELECT cid, platform FROM releases_platforms WHERE rid = oid;
+ INSERT INTO releases_producers (rid, pid) SELECT cid, pid FROM releases_producers WHERE rid = oid;
+ INSERT INTO releases_lang (rid, lang) SELECT cid, lang FROM releases_lang WHERE rid = oid;
+ INSERT INTO releases_vn (rid, vid) SELECT cid, vid FROM releases_vn WHERE rid = oid;
+ UPDATE releases SET latest = cid WHERE id = iid;
+END;
+$$ LANGUAGE plpgsql;
+
+SELECT tmp_edit_release(r.id)
+ FROM releases r
+ JOIN releases_rev rr ON rr.id = r.latest
+ WHERE r.hidden = FALSE
+ AND rr.type = 2
+ AND NOT rr.freeware
+ AND EXISTS(SELECT 1 FROM releases_media rm WHERE rm.medium = 'in ' AND rm.rid = rr.id)
+ ORDER BY r.id;
+
+DROP FUNCTION tmp_edit_release(integer);
+
+
+
+-- Really don't consider VNs with AVG(vote) < 0 on tag pages
+CREATE OR REPLACE FUNCTION tag_vn_calc() RETURNS void AS $$
+BEGIN
+ -- all votes for all tags
+ CREATE OR REPLACE TEMPORARY VIEW tags_vn_all AS
+ SELECT * FROM tags_vn UNION SELECT * FROM tag_vn_childs();
+ -- grouped by (tag, vid, uid), so only one user votes on one parent tag per VN entry
+ CREATE OR REPLACE TEMPORARY VIEW tags_vn_grouped AS
+ SELECT tag, vid, uid, MAX(vote)::real AS vote, COALESCE(AVG(spoiler), 0)::real AS spoiler
+ FROM tags_vn_all GROUP BY tag, vid, uid;
+ -- grouped by (tag, vid) and serialized into a table
+ DROP INDEX IF EXISTS tags_vn_bayesian_tag;
+ TRUNCATE tags_vn_bayesian;
+ INSERT INTO tags_vn_bayesian
+ SELECT tag, vid, COUNT(uid) AS users, AVG(vote)::real AS rating,
+ (CASE WHEN AVG(spoiler) < 0.7 THEN 0 WHEN AVG(spoiler) > 1.3 THEN 2 ELSE 1 END)::smallint AS spoiler
+ FROM tags_vn_grouped
+ GROUP BY tag, vid
+ HAVING AVG(vote) > 0;
+ CREATE INDEX tags_vn_bayesian_tag ON tags_vn_bayesian (tag);
+ -- now perform the bayesian ranking calculation
+ UPDATE tags_vn_bayesian tvs SET rating =
+ ((SELECT AVG(users)::real * AVG(rating)::real FROM tags_vn_bayesian WHERE tag = tvs.tag) + users*rating)
+ / ((SELECT AVG(users)::real FROM tags_vn_bayesian WHERE tag = tvs.tag) + users)::real;
+ -- and update the VN count in the tags table as well
+ UPDATE tags SET c_vns = (SELECT COUNT(*) FROM tags_vn_bayesian WHERE tag = id);
+ RETURN;
+END;
+$$ LANGUAGE plpgsql;
+SELECT tag_vn_calc();
+
diff --git a/util/vndb.pl b/util/vndb.pl
index 455a1c23..ed74a92f 100755
--- a/util/vndb.pl
+++ b/util/vndb.pl
@@ -17,6 +17,7 @@ use lib $ROOT.'/lib';
use YAWF ':html';
+use VNDB::L10N;
our(%O, %S);
@@ -27,6 +28,10 @@ our(%O, %S);
$S{skins} = readskins();
+# load lang.dat
+VNDB::L10N::loadfile();
+
+
# load settings from global.pl
require $ROOT.'/data/global.pl';
@@ -42,6 +47,12 @@ YAWF::init(
sub reqinit {
my $self = shift;
+
+ $self->{l10n} = VNDB::L10N->get_handle($self->reqParam('l10n') || $self->reqCookie('l10n') || ());
+ my $lang = $self->{l10n}->language_tag();
+ $self->resHeader('Set-Cookie', "l10n=$lang; expires=Sat, 01-Jan-2030 00:00:00 GMT; path=/; domain=$self->{cookie_domain}")
+ if $lang ne ($self->reqCookie('l10n')||'');
+
$self->authInit;
# check for IE6