diff --git a/sources/CHANGELOG b/sources/CHANGELOG
new file mode 100644
index 0000000..43b732c
--- /dev/null
+++ b/sources/CHANGELOG
@@ -0,0 +1,292 @@
+1.1.0 - 201612XX
+ * Merged a huge PR that clean most of COPS source code. Thanks to Markus Birth for his work and his patience.
+ * Updated Polish tranlations.
+ * Fixed a bad external dependency in login.html causing problem with HTPPS. Thanks to polytan02.
+ * Added automatic redirection to the OPDS feed for many new Android apps (see #309). Thanks to horus68.
+
+1.0.1 - 20161015
+ * Fixed some type of custom column showing id instead of text - Thanks to Mike Schwörer.
+ * Fixed the redirection to the OPDS catalog for Moon+ Reader.
+ * Fixed the mail character encoding, now in UTF-8.
+ * Fixed checkconfig.php to avoid sending content before headers. Thanks to Luke Stevenson.
+ * Fixed server side rendering with custom columns.
+ * Moved /icons to /images (Apache issues). Thanks to CgX.
+
+1.0.0 - 20160708
+ * Updated the OPDS icons to better looking ones. Thanks to Horus68.
+ * Updated the README.md.
+ * Updated Brazillian, French, Hungarian, Portuguese, Russian translations.
+ * Added support of language and country code. This allow to have proper Brazil Portuguese and Portugal Portuguese.
+ * Added Korean translation. Thanks to Jin, Heonkyu.
+ * Added Romanian translation. Thanks to mtzro2003.
+ * Added Greek translation. Thanks to George Litos.
+ * Added Turkish Translation. Thanks to Yunus Emre Deligöz.
+ * Added Serbian Translation. Thanks to Dalibor Vinkić.
+ * Added the transliteration of search text. You can enable it with $config ['cops_normalized_search']. Thanks to George Litos.
+ * Added Ebookdroid, Chunky and AlReader in the know OPDS clients. Thanks to Mike Ferenduros and Horus68.
+ * Added some mime types for audio books.
+ * Added the rewrite rule for IIS.
+ * Added a now parameter to set the style ($config['cops_style']). Thanks to Pablo Santiago Blum de Aguiar.
+ * Added a directory cache ($config['cops_thumbnail_cache_directory']) to store the resized thumbnails (should help on slow NAS). Thanks to O2 Graphics.
+ * Added support of all kind of custom columns (see configuration file). Thanks to Mike Schwörer.
+ * Fixed COPS so that it's completely embedded (no external resources to download needed anymore).
+ * Fixed a Reflected XSS vulnerability.
+ * Fixed the tag filters with Bootstrap. Thanks to Klaus Broelemann.
+ * Fixed some COPS path errors with reverse proxy. Thanks to Benjamin Kitt.
+ * Fixed the publication date (wasn't working for date before 1901).
+ * Fixed the download file name (replace + by %20 to be RFC compliant).
+
+
+1.0.0RC3 - 20141229
+ * Fixed server side render with Bootstrap template (a proper unit test was also added).
+ * Upgraded to latest doT-php, Typeahead 0.10.5, jquery-cookie 1.4.1, JQuery 1.11.1
+ * Fixed book count with custom columns.
+ * Updated Catalan, Dutch, French and Russian translations.
+ * Added AZW3 to the format that can be sent to Kindle (by mail).
+ * Fixed $config['cops_thumbnail_handling'] with bootstrap template.
+ * Added Hungarian translation. Thanks to harunibn.
+ * Added Ukrainian translation. Thanks to Anatoliy Zavalinich
+ * Added full PHP password check (without any need from specific webserver configuration). Thanks to Mark Bond.
+ * Added new IOS7 style with default template. Thanks to an anonymous source ;).
+ * Fixed display of authors names for books with more than one author.
+ * Added PHP version to checkconfig.php (will help debugging for me).
+ * Added a configuration item ($config['cops_template']) to change the default template. Thanks to Shin.
+ * Added a configuration item ($config['cops_language']) to force COPS language. Thanks to Sandy Pleyte.
+ * Added a trick to have user based configuration, check https://github.com/seblucas/cops/wiki/User-based-config for more information. Thanks to Sandy Pleyte.
+ * Changed the default sort order on books by author page to show books in a series before all other books.
+
+
+1.0.0RC2 - 20140731
+ * Updated Italian, Spanish, Portuguese, Norwegian translations.
+ * Added Polish translation. Thanks to macak_pl.
+ * Added Haitian Creole translation. Thanks to Ian Macdonald & Jacinta.
+ * Added Basque translation. Thanks to Turutarena.
+ * Upgraded to JQuery 1.11.0, Magnific Popup 0.9.9, Normalize 3.0.1, Typeahead 0.10.2
+ * Fixed search with accentuated characters on Internet Explorer.
+ * Author can now be searched by sort or by name (Carroll, Lewis or Lewis Carroll will work).
+ * Added a new bootstrap user interface.
+ * Added correct mimetype for *.ibooks. Reported by Flowney.
+ * Added an empty line at the end of .htaccess to make it easier to modify. Reported by Mariosipad.
+ * Modified the README and checkconfig.php to check for php5-json. Reported by Mariosipad.
+ * Handled properly the cancelling of a mail. Reported by coach0742.
+ * Added an ugly hack to try to fix bad rendering with Kindle. Please report if it's better or not.
+
+1.0.0RC1 - 20140404
+ * Updated English, Spanish, German, Italian, Portuguese, Dutch translation files. Huge thanks to all to the translators.
+ * Added Swedish translation. Thanks to Bo Rosén.
+ * Added Czech translation. Thanks to Zdenek Hadrava.
+ * Added a lot of refactoring to simplify the code.
+ * Added a lot of new unit tests.
+ * Fixed a caching bug causing problems with IE.
+ * Added an embedded Epub Reader based on Monocle. Thanks to all the beta testers.
+ * Cleaned up a lot of stuff to prepare for bootstrap template. Note to all CSS hackers, the stylesheets are now in templates/default/styles.
+ * Fixed the charset of most of the pages. Thanks to edent.
+ * Added a new category : ratings. Thanks to Michael.
+ * Fixed the URL rewriting in the OPDS stream, should fix file naming with FBReader. Reported by Rassie.
+ * Fixed a confusion between author's name and author's sort. Reported by At_Libitum.
+ * Fixed the style of the tag filters to show that they're clickable. Thanks to cycojesus.
+ * Replaced | by space in author name.
+
+0.9.0 - 20131231
+ * Add a lot of unit testing. I hope it will limit the risks of regression.
+ * Added a "smart / autocomplete" search.
+ * Updated the way locales are handled. Should be easier to add new languages.
+ * Fixed display of Cyrillic characters.
+ * Upgraded doT to version 1.0.1, Magnific-Popup to 0.9.8, Normalize.css to 2.1.3, Jquery-cookie to 1.4.0.
+ * Fixed OPDS stream validity. Reported by Didier.
+ * Added a new check in checkconfig.php to detect case problem between the actual path and the path stored in Calibre database. Try checkconfig.php?full=1. Reported by Ruud.
+ * Fixed the display of the rating stars with Chrome. Thanks to At_Libitum.
+ * Added a new parameter ($config['cops_titles_split_first_letter']) to avoid splitting the books by first letter. Thanks to At_Libitum.
+ * Fixed non compliant OPDS search (for Stanza, Moon+ Reader, ...). Reported by At_Libitum.
+ * Fixed the redirection in case the Calibre database is not found. Reported by At_Libitum
+ * Changed .htaccess to allow the use of password protected catalogs with Sony's eReader (PRS-TX). Thanks to Ruud for the beta testing.
+ * Updated Chinese, German, Norwegian, Portuguese, Russian translations. Huge thanks to all the translators.
+ * Fixed a small problem : If a book had no summary the cover could be cut.
+ * Fix COPS on Internet Explorer 9. Reported by At_Libitum.
+ * Added publishers in home categories / search / autocomplete search.
+ * Added a new configuration item ($config ['cops_ignored_categories']) to ignore some categories (author, tag, publisher, ...) in home screen and searches. It's also available in the "Customize UI" page.
+ * Updated .htaccess to allow downloading books with a password protected COPS on a Sony PRS-TX. Reported by Ruud.
+ * Changed the default search to search by categories also (should help with OPDS). Thanks to At_Libitum.
+ * Fixed the tag filtering in the HTML catalog when two tags starts by the same word. Reported by Tyler.
+
+0.6.2 - 20130913
+ * Added server side rendering for devices like PRS-TX / Kindle / Cybook. Thanks to all the testers.
+ * Added a configuration item to tweak how thumbnail are handled.
+ * Fixed the click on cog on IOS. Thanks to sb domo.
+ * Added dashboard icons / standalone mode for IOS. Thanks to sb domo.
+ * Fixed a regression about custom favicon.ico. Thanks to Tyler.
+ * Fixed another regression about COPS's version in the about box. Reported by Ian.
+ * Upgraded Magnific Popup to v0.9.5.
+ * Added a style for IPhone. Thanks to sb domo.
+ * Added Portuguese translation. Thanks to Pablo Aguiar.
+ * Fixed rendering on Internet Explorer < 9.0.
+
+0.6.1 - 20130730
+ * Properly close the lightbox when clicking in a link. Reported by le_.
+ * Fix the book by languages list when the language is not found in the resources. Reported by le_.
+ * Fix the string for Portuguese. Reported by le_.
+ * Add again the series Index in the book list. Reported by fatzgenfatz.
+
+0.6.0 - 20130724
+ * COPS HTML catalog now use templated client side rendering. You can build your own template if you want. Should be a lot faster.
+ * Fancybox has been replaced by Magnific Popup, it seems faster.
+ * Added a way to send book by mail (to send to Kindle or to send to your friends).
+ * Added expires instruction in .htaccess (won't crash if you haven't enabled mod_expires).
+ * Upgrade to JQuery 1.10.2.
+ * Changed the way thumbnails are handled to offer greater visual quality (especially on high pixel density devices : Retina, Nexus, ...).
+ * Changed all icon by a vectorial font (again better visual quality).
+ * Added a way to filter books by tags.
+ * Added a login page (login.html) to allow access to a password protected COPS on a Kobo ereader (that does not support basic auth).
+ * Fixed cookie expiry date.
+ * Added a default web.config for IIS installation.
+ * The eink style doesn't use shadow anymore.
+ * Fixed the link to the series in book detail.
+
+0.5.0 - 20130605
+ * Upgrade COPS UI to HTML5 / CSS3 to hopefully make it prettier. Most of the code was contributed by Thomas Severinsen.
+ * Add the number of books in each databases (when multiple database is enabled).
+ * Add Norwegian Bokmål strings. Thanks to Rune Mathisen for the pull request.
+ * Add a split by language of catalog. Thanks to Puiu Ionut for the pull request.
+ * You can now change the theme and fancybox use on all your devices (You have to enable cookies).
+ * Add an eink theme. Thanks to Gregory Bodin for the code.
+
+0.4.0 - 20130507
+ * Add multiple database support. Check the documentation of $config['calibre_directory'] in config-default.php to see how to enable it.
+ * Include jquery library in COPS's repository to be sure that COPS will work on LAN (without Internet access).
+ * Prepare the switch to HTML5. Thanks to Thomas Severinsen for most of the code.
+ * Update the locale strings to be more strict with plurals. Thanks to Tobias Ausländer for the code.
+ * If Fancybox is not enabled ($config['cops_use_fancyapps'] = "0") then it's not used at all (even in the about box).
+ * Fix book comments if it contains UTF8 characters. Reported by Alain.
+ * Link to the book permalink was not working correctly in some cases. Reported by celta.
+ * Moved some external resources to a resources directory.
+ * Add chinese translation. Thanks to wogong for the pull request.
+
+0.3.4 - 20130327
+ * Hopefully fix metadata update. Beware you should remove the directory php-epub-meta if you have one. Thanks to Mario for his time.
+ * Fix two warnings. Reported by Goner and Mario.
+
+0.3.3 - 20130323
+ * Fix catalog if book summary contains bad HTML again :(.
+ * Upgrade to Fancybox 2.4.0 and JQuery 1.9.1.
+ * Search is now dependant on the page you're in. For now if you're on author page it'll look for author name.
+ * Update checkconfig to check if the database provided comes from Calibre.
+ * Update to latest php-epub-meta should fix the metadata update with Epub.
+ * Fix OPDS catalog with Ibis Reader. It didn't like empty language.
+
+0.3.2 - 20130303
+ * Add dutch translation. Provided by Northguy.
+ * Fix an ugly bug introduced in 0.3.1. Reported by mariosipad.
+ * Small fixes/enhancement to the update metadata tools :
+ * The book's name is Author - Title.epub
+ * Add the Calibre uuid so that the book is automatically recognised by Calibre.
+ * Update the cover
+ * Fix display of the HTML catalog on Kobo's browser.
+ * Enable kepub.epub download with cover fix (enable with $config['cops_provide_kepub']).
+ * Hopefully fix browsing with PRS-T1. Thanks to Northguy.
+ * Hopefully fix the OPDS catalog when the summary is full of HTML crap.
+ * Merged 3 patches from Tyler J. Wagner :
+ * Detect empty publication date set in Calibre to avoid having (0101) as publication year.
+ * Don't print "Languages" if there are none defined.
+ * Don't print the tag string if there's no tags.
+ * If an OPDS client try to access index.php it will be automatically redirected to feed.php.
+ * Move the search & sort tool box to a new line (also fix a w3c error).
+
+
+0.3.1 - 20130127
+ * Add Facets to the OPDS catalog (check config item cops_books_filter).
+ So far the only OPDS client that support facets are Mantano Reader and Bluefire
+ * Fix book sort in some list. Patch provided by Tyler J. Wagner.
+ * Update .htaccess to check if Xsendfile is available. Thanks to Gaspine for the patch.
+ * Add basic support of custom columns. Check the following config item : cops_calibre_custom_column
+ * Usage of X-Accel-Redirect / X-Sendfile is not necessary anymore. Warning all Nginx users
+ who wants to still use X-Accel-Redirect must add
+ $config['cops_x_accel_redirect'] = "X-Accel-Redirect" in their config_local.php
+ * Fix COPS on IIS / Windows. Reported by Kevnancy.
+ * Simplified config_default.php
+ * Add a new config_local.php.example with the minimal configuration item to change.
+
+
+0.3.0 - 20130106
+ * Add a config item to avoid using Fancyapps (pop-ups). Reported by mcister and Northguy.
+ * Update documentation of .htaccess. Thanks to Stephane.
+ * Add a config item to specify a custom icon. Based on a patch by Tyler J. Wagner.
+ * Better handling of content type for book. Reported by Morg.
+ * Upped the size of thumbnails for OPDS. They look way better with Mantano.
+ * Add language in OPDS feed (shown in Mantano for example).
+ * Update metadata on downloaded epub. Disabled by default (check config item cops_update_epub-metadata).
+ * New Catalan translation provided by David Ciscar Presas.
+ * Add a permalink to books, that way direct link to books can be shared. Reported by mcister and Tyler J. Wagner.
+ * Add checkconfig.php that should allow to better detect the configuration problem (page in english only for now).
+ * Fix some plural strings / some missing title. Reported by David Ciscar Presas.
+ * Add an hint about the OPDS catalog in the HTML catalog.
+
+0.2.3 - 20121205
+ * Add a .htaccess to make it easier to use with Apache
+ * Fix a typo in book download. Reported by jillmess
+ * Update localization (thanks to Calibre2Opds)
+ * Add some missing information from Calibre (language, rating for now). Reported by mcister
+ * Upgrade Fancybox to 2.1.3
+
+
+0.2.2 - 20121020
+ * Changed JQuery URL to https (thanks to Dan Greve for the patch)
+ * Added paging to both OPDS and HTML catalog (use new config item cops_max_item_per_page)
+ * lots of code refactoring
+ * Authors are now splitted by first letter, this is the new default. You can go back to the old way with the config item cops_author_split_first_letter (reported by Northguy)
+ * Fix the link to books starting by special characters (reported by vinpel)
+ * Upgrade to Fancyapps 2.1.0. I had to adapt the CSS so maybe it'll display better in PRS-T1
+ * Add an about box on the HTML catalog which show the current version
+
+0.2.1 - 20120916
+ * Fix one last error (hopefully) in link generation (thanks to gaspine)
+ * Add Sony PRS-T1 to the list of E-Ink device (thanks to Northguy)
+ * Fix another HTML special characters problem (thanks to NeilBryant)
+ * Add an ugly config parameter to allow search in non-compliant OPDS reader (thanks to Don Caruana and David Lee)
+
+0.2.0 - 20120722
+ * Fix all rewriting rule I forgot to change it in last release
+ * Fix
in book comment (thanks to jillmess)
+ * Fix cover zoom in HTML catalog (you can also navigate through cover with keyboard)
+ * Simplify Fancybox transition for e-Ink devices (for now Kobo and Kindle)
+
+0.1.1 - 20120702
+ * A lot of bug fixes in HTML catalog
+ * Fixed the book comment in OPDS (broken in some rare case)
+ * Fixed handling of HTML reserved characters
+ * Changed book OPDS id to use an UUID (thanks to ilovejedd for the bug report)
+ * Add new config item for the default timezone (thanks to gaspine)
+ * Better handling of missing covers
+ * Should support every book format supported by Calibre (thanks to Artem)
+ * URL rewriting is off by default for the HTML catalog
+ * Add some documentation about URL rewriting (thanks to gaspine and Christophe)
+ * Tested and ready to use with PHP5.4
+
+0.1.0 - 20120605
+ * Add localization support (thanks to Calibre2Opds)
+ * Hopefully fixed an issue with & in comment
+ * HTML catalog is in the sources with no support (WIP)
+
+0.0.4 - 20120523
+ * More code refactoring to simplify code.
+ * Changed OPDS Page id to match Calibre2Opds
+ * Add icons to author, serie, tags and recent items (there is config item to disable it)
+ * Fixed author URL
+ * Added publishing date (works on Mantano)
+ * Added Tags support
+
+0.0.3 - 20120507
+ * Fixed many things blocking opensearch from working
+ * There was a bug introduced in 0.0.2
+ * The URL can't be relative for Mantano reader, so I added a configuration item.
+ * I continued the refactoring to bring HTML to COPS
+ * Thumbnails have bigger size (I'll add a configuration item later)
+ * Add headers to help caching image and thumbnail to the browser
+ *
+
+0.0.2 - 20120411
+ * Add support for MOBI and PDF
+ * Major refactoring to prepare something nice for the future ;)
+ * Add a config item to make use of X-Sendfile instead of X-Accel-Redirect if needed
+
+0.0.1 - 20120302
+ * First public release
diff --git a/sources/COPYING b/sources/COPYING
new file mode 100644
index 0000000..d60c31a
--- /dev/null
+++ b/sources/COPYING
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ , 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/sources/README.md b/sources/README.md
new file mode 100644
index 0000000..e4661c3
--- /dev/null
+++ b/sources/README.md
@@ -0,0 +1,103 @@
+# COPS
+
+COPS stands for Calibre OPDS (and HTML) Php Server.
+
+See : [COPS's home](http://blog.slucas.fr/en/oss/calibre-opds-php-server) for more details.
+
+Don't forget to check the [Wiki](https://github.com/seblucas/cops/wiki).
+
+[](https://scrutinizer-ci.com/g/seblucas/cops/?branch=master)
+
+[](https://scrutinizer-ci.com/g/seblucas/cops/?branch=master)
+
+[](https://scrutinizer-ci.com/g/seblucas/cops/build-status/master)
+
+[](https://travis-ci.org/seblucas/cops)
+
+[](https://saucelabs.com/u/seblucas)
+
+# Why ?
+
+In my opinion Calibre is a marvelous tool but is too big and has too much
+dependencies to be used for its content server.
+
+That's the main reason why I coded this OPDS server. I needed a simple
+tool to be installed on a small server (Seagate Dockstar in my case).
+
+I initially thought of Calibre2OPDS but as it generate static file no
+search was possible.
+
+Later I added an simple HTML catalog that should be usable on my Kobo.
+
+So COPS's main advantages are :
+ * No need for many dependencies.
+ * No need for a lot of CPU or RAM.
+ * Not much code.
+ * Search is available.
+ * With Dropbox / owncloud it's very easy to have an up to date OPDS server.
+ * It was fun to code.
+
+If you want to use the OPDS feed don't forget to specify feed.php at the end of your URL.
+
+# Prerequisites
+
+1. PHP 5.3, 5.4, 5.5, 5.6 or hhvm with GD image processing, Libxml, Intl, Json & SQLite3 support.
+2. A web server with PHP support. I only tested with various version of Nginx.
+ Other people reported it working with Apache and Cherokee. You can also use PHP
+ embedded server (https://github.com/seblucas/cops/wiki/Howto---PhpEmbeddedServer)
+3. The path to a calibre library (metadata.db, format, & cover files).
+
+On any Debian base Linux you can use :
+ aptitude install php5-gd php5-sqlite php5-json php5-intl
+
+On Centos you may have to add :
+ yum install php-xml
+
+# Install
+
+1. Extract the zip file to a folder in web space (visible to the web server).
+2. If you're doing a first-time install, copy config_local.php.example to config_local.php
+3. Edit config_local.php to match your config.
+4. If needed add other configuration item from config_default.php
+
+If you choose to put your Calibre directory inside your web directory then you
+will have to edit /etc/nginx/mime.types to add this line :
+application/epub+zip epub;
+
+If you like Docker, you can also try [this project](https://github.com/linuxserver/docker-cops)
+
+# Known problems
+
+Not a lot, except for the bad quality of the code (first PHP project ever) ;)
+
+Please see https://github.com/seblucas/cops/issues for open issues
+
+# Need help
+
+Please read https://github.com/seblucas/cops/wiki and check the FAQ.
+
+# Credits
+
+ * Locale message handling is inspired of http://www.mind-it.info/2010/02/22/a-simple-approach-to-localization-in-php/
+ * str_format function come from http://tmont.com/blargh/2010/1/string-format-in-php
+ * All icons come from Font Awesome : http://fontawesome.github.io/Font-Awesome/
+ * The unofficial OPDS validator : http://opds-validator.appspot.com/
+ * Thanks to all testers, translators and contributors.
+ * Feed icons made by Freepik from Flaticon website licensed under Creative Commons BY 3.0 http://www.flaticon.com and http://www.freepik.com
+
+External libraries used :
+ * JQuery : http://jquery.com/
+ * Magnific Popup : http://dimsemenov.com/plugins/magnific-popup/
+ * Php-epub-meta : https://github.com/splitbrain/php-epub-meta with some modification by me
+ https://github.com/seblucas/php-epub-meta
+ * TbsZip : http://www.tinybutstrong.com/apps/tbszip/tbszip_help.html
+ * DoT.js : http://olado.github.io/doT/index.html
+ * PHPMailer : https://github.com/PHPMailer/PHPMailer
+ * js-lru : https://github.com/rsms/js-lru
+
+# Copyright & License
+
+COPS - 2012-2016 (c) Sbastien Lucas
+
+See COPYING and file headers for license info
+
diff --git a/sources/about.html b/sources/about.html
new file mode 100644
index 0000000..d794905
--- /dev/null
+++ b/sources/about.html
@@ -0,0 +1,23 @@
+
diff --git a/sources/base.php b/sources/base.php
new file mode 100644
index 0000000..dd6d7fd
--- /dev/null
+++ b/sources/base.php
@@ -0,0 +1,364 @@
+
+ */
+
+require 'config.php';
+
+define ('VERSION', '1.0.2');
+define ('DB', 'db');
+date_default_timezone_set($config['default_timezone']);
+
+
+function useServerSideRendering()
+{
+ global $config;
+ return preg_match('/' . $config['cops_server_side_render'] . '/', $_SERVER['HTTP_USER_AGENT']);
+}
+
+function serverSideRender($data)
+{
+ // Get the templates
+ $theme = getCurrentTemplate ();
+ $header = file_get_contents('templates/' . $theme . '/header.html');
+ $footer = file_get_contents('templates/' . $theme . '/footer.html');
+ $main = file_get_contents('templates/' . $theme . '/main.html');
+ $bookdetail = file_get_contents('templates/' . $theme . '/bookdetail.html');
+ $page = file_get_contents('templates/' . $theme . '/page.html');
+
+ // Generate the function for the template
+ $template = new doT ();
+ $dot = $template->template ($page, array ('bookdetail' => $bookdetail,
+ 'header' => $header,
+ 'footer' => $footer,
+ 'main' => $main));
+ // If there is a syntax error in the function created
+ // $dot will be equal to FALSE
+ if (!$dot) {
+ return FALSE;
+ }
+ // Execute the template
+ if (!empty ($data)) {
+ return $dot ($data);
+ }
+
+ return NULL;
+}
+
+function getQueryString()
+{
+ if (isset($_SERVER['QUERY_STRING'])) {
+ return $_SERVER['QUERY_STRING'];
+ }
+ return "";
+}
+
+function notFound()
+{
+ header($_SERVER['SERVER_PROTOCOL'].' 404 Not Found');
+ header('Status: 404 Not Found');
+
+ $_SERVER['REDIRECT_STATUS'] = 404;
+}
+
+function getURLParam($name, $default = NULL)
+{
+ if (!empty ($_GET) && isset($_GET[$name]) && $_GET[$name] != '') {
+ return $_GET[$name];
+ }
+ return $default;
+}
+
+function getCurrentOption($option)
+{
+ global $config;
+ if (isset($_COOKIE[$option])) {
+ if (isset($config ['cops_' . $option]) && is_array ($config ['cops_' . $option])) {
+ return explode (',', $_COOKIE[$option]);
+ } else {
+ return $_COOKIE[$option];
+ }
+ }
+ if (isset($config ['cops_' . $option])) {
+ return $config ['cops_' . $option];
+ }
+
+ return '';
+}
+
+function getCurrentCss()
+{
+ return 'templates/' . getCurrentTemplate () . '/styles/style-' . getCurrentOption('style') . '.css';
+}
+
+function getCurrentTemplate()
+{
+ return getCurrentOption ('template');
+}
+
+function getUrlWithVersion($url)
+{
+ return $url . '?v=' . VERSION;
+}
+
+function xml2xhtml($xml)
+{
+ return preg_replace_callback('#<(\w+)([^>]*)\s*/>#s', function($m) {
+ $xhtml_tags = array('br', 'hr', 'input', 'frame', 'img', 'area', 'link', 'col', 'base', 'basefont', 'param');
+ if (in_array($m[1], $xhtml_tags)) {
+ return '<' . $m[1] . $m[2] . ' />';
+ } else {
+ return '<' . $m[1] . $m[2] . '>' . $m[1] . '>';
+ }
+ }, $xml);
+}
+
+function display_xml_error($error)
+{
+ $return = '';
+ $return .= str_repeat('-', $error->column) . "^\n";
+
+ switch ($error->level) {
+ case LIBXML_ERR_WARNING:
+ $return .= 'Warning ' . $error->code . ': ';
+ break;
+ case LIBXML_ERR_ERROR:
+ $return .= 'Error ' . $error->code . ': ';
+ break;
+ case LIBXML_ERR_FATAL:
+ $return .= 'Fatal Error ' . $error->code . ': ';
+ break;
+ }
+
+ $return .= trim($error->message) .
+ "\n Line: " . $error->line .
+ "\n Column: " . $error->column;
+
+ if ($error->file) {
+ $return .= "\n File: " . $error->file;
+ }
+
+ return "$return\n\n--------------------------------------------\n\n";
+}
+
+function are_libxml_errors_ok()
+{
+ $errors = libxml_get_errors();
+
+ foreach ($errors as $error) {
+ if ($error->code == 801) return false;
+ }
+ return true;
+}
+
+function html2xhtml($html)
+{
+ $doc = new DOMDocument();
+ libxml_use_internal_errors(true);
+
+ $doc->loadHTML(' ' .
+ $html . ''); // Load the HTML
+ $output = $doc->saveXML($doc->documentElement); // Transform to an Ansi xml stream
+ $output = xml2xhtml($output);
+ if (preg_match ('# (.*)#ms', $output, $matches)) {
+ $output = $matches [1]; // Remove
+ }
+ /*
+ // In case of error with summary, use it to debug
+ $errors = libxml_get_errors();
+
+ foreach ($errors as $error) {
+ $output .= display_xml_error($error);
+ }
+ */
+
+ if (!are_libxml_errors_ok ()) $output = 'HTML code not valid.';
+
+ libxml_use_internal_errors(false);
+ return $output;
+}
+
+/**
+ * This method is a direct copy-paste from
+ * http://tmont.com/blargh/2010/1/string-format-in-php
+ */
+function str_format($format)
+{
+ $args = func_get_args();
+ $format = array_shift($args);
+
+ preg_match_all('/(?=\{)\{(\d+)\}(?!\})/', $format, $matches, PREG_OFFSET_CAPTURE);
+ $offset = 0;
+ foreach ($matches[1] as $data) {
+ $i = $data[0];
+ $format = substr_replace($format, @$args[$i], $offset + $data[1] - 1, 2 + strlen($i));
+ $offset += strlen(@$args[$i]) - 2 - strlen($i);
+ }
+
+ return $format;
+}
+
+/**
+ * Get all accepted languages from the browser and put them in a sorted array
+ * languages id are normalized : fr-fr -> fr_FR
+ * @return array of languages
+ */
+function getAcceptLanguages()
+{
+ $langs = array();
+
+ if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
+ // break up string into pieces (languages and q factors)
+ $accept = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
+ if (preg_match('/^(\w{2})-\w{2}$/', $accept, $matches)) {
+ // Special fix for IE11 which send fr-FR and nothing else
+ $accept = $accept . ',' . $matches[1] . ';q=0.8';
+ }
+ preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $accept, $lang_parse);
+
+ if (count($lang_parse[1])) {
+ $langs = array();
+ foreach ($lang_parse[1] as $lang) {
+ // Format the language code (not standard among browsers)
+ if (strlen($lang) == 5) {
+ $lang = str_replace('-', '_', $lang);
+ $splitted = preg_split('/_/', $lang);
+ $lang = $splitted[0] . '_' . strtoupper($splitted[1]);
+ }
+ array_push($langs, $lang);
+ }
+ // create a list like "en" => 0.8
+ $langs = array_combine($langs, $lang_parse[4]);
+
+ // set default to 1 for any without q factor
+ foreach ($langs as $lang => $val) {
+ if ($val === '') $langs[$lang] = 1;
+ }
+
+ // sort list based on value
+ arsort($langs, SORT_NUMERIC);
+ }
+ }
+
+ return $langs;
+}
+
+/**
+ * Find the best translation file possible based on the accepted languages
+ * @return array of language and language file
+ */
+function getLangAndTranslationFile()
+{
+ global $config;
+ $langs = array();
+ $lang = 'en';
+ if (!empty($config['cops_language'])) {
+ $lang = $config['cops_language'];
+ }
+ elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
+ $langs = getAcceptLanguages();
+ }
+ //echo var_dump($langs);
+ $lang_file = NULL;
+ foreach ($langs as $language => $val) {
+ $temp_file = dirname(__FILE__). '/lang/Localization_' . $language . '.json';
+ if (file_exists($temp_file)) {
+ $lang = $language;
+ $lang_file = $temp_file;
+ break;
+ }
+ }
+ if (empty ($lang_file)) {
+ $lang_file = dirname(__FILE__). '/lang/Localization_' . $lang . '.json';
+ }
+ return array($lang, $lang_file);
+}
+
+/**
+ * This method is based on this page
+ * http://www.mind-it.info/2010/02/22/a-simple-approach-to-localization-in-php/
+ */
+function localize($phrase, $count=-1, $reset=false)
+{
+ global $config;
+ if ($count == 0)
+ $phrase .= '.none';
+ if ($count == 1)
+ $phrase .= '.one';
+ if ($count > 1)
+ $phrase .= '.many';
+
+ /* Static keyword is used to ensure the file is loaded only once */
+ static $translations = NULL;
+ if ($reset) {
+ $translations = NULL;
+ }
+ /* If no instance of $translations has occured load the language file */
+ if (is_null($translations)) {
+ $lang_file_en = NULL;
+ list ($lang, $lang_file) = getLangAndTranslationFile();
+ if ($lang != 'en') {
+ $lang_file_en = dirname(__FILE__). '/lang/' . 'Localization_en.json';
+ }
+
+ $lang_file_content = file_get_contents($lang_file);
+ /* Load the language file as a JSON object and transform it into an associative array */
+ $translations = json_decode($lang_file_content, true);
+
+ /* Clean the array of all unfinished translations */
+ foreach (array_keys ($translations) as $key) {
+ if (preg_match ('/^##TODO##/', $key)) {
+ unset ($translations [$key]);
+ }
+ }
+ if (!is_null($lang_file_en)) {
+ $lang_file_content = file_get_contents($lang_file_en);
+ $translations_en = json_decode($lang_file_content, true);
+ $translations = array_merge ($translations_en, $translations);
+ }
+ }
+ if (array_key_exists ($phrase, $translations)) {
+ return $translations[$phrase];
+ }
+ return $phrase;
+}
+
+function addURLParameter($urlParams, $paramName, $paramValue)
+{
+ if (empty ($urlParams)) {
+ $urlParams = '';
+ }
+ $start = '';
+ if (preg_match ('#^\?(.*)#', $urlParams, $matches)) {
+ $start = '?';
+ $urlParams = $matches[1];
+ }
+ $params = array();
+ parse_str($urlParams, $params);
+ if (empty ($paramValue) && $paramValue != 0) {
+ unset ($params[$paramName]);
+ } else {
+ $params[$paramName] = $paramValue;
+ }
+ return $start . http_build_query($params);
+}
+
+function useNormAndUp()
+{
+ global $config;
+ return $config ['cops_normalized_search'] == '1';
+}
+
+function normalizeUtf8String($s)
+{
+ include_once 'transliteration.php';
+ return _transliteration_process($s);
+}
+
+function normAndUp($s)
+{
+ return mb_strtoupper(normalizeUtf8String($s), 'UTF-8');
+}
diff --git a/sources/build.xml b/sources/build.xml
new file mode 100644
index 0000000..9cb7a2d
--- /dev/null
+++ b/sources/build.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sources/checkconfig.php b/sources/checkconfig.php
new file mode 100644
index 0000000..242a80a
--- /dev/null
+++ b/sources/checkconfig.php
@@ -0,0 +1,260 @@
+
+
+
+ *
+ */
+
+ require_once 'config.php';
+ require_once 'base.php';
+
+ $err = getURLParam('err', -1);
+ $full = getURLParam('full');
+ $error = NULL;
+ switch ($err) {
+ case 1 :
+ $error = 'Database error';
+ break;
+ }
+
+?>
+
+
+
+ COPS Configuration Check
+
+
+
+
+
+
+
+
+
+ You've been redirected because COPS is not configured properly
+
+
+
+
+ Check if PHP version is correct
+
+ = 50300) {
+ echo 'OK (' . PHP_VERSION . ')';
+ } else {
+ echo 'Please install PHP >= 5.3 (' . PHP_VERSION . ')';
+ }
+ } else {
+ echo 'Please install PHP >= 5.3';
+ }
+ ?>
+
+
+
+ Check if GD is properly installed and loaded
+
+
+
+
+
+ Check if Sqlite is properly installed and loaded
+
+
+
+
+
+ Check if libxml is properly installed and loaded
+
+
+
+
+
+ Check if Json is properly installed and loaded
+
+
+
+
+
+ Check if mbstring is properly installed and loaded
+
+
+
+
+
+ Check if intl is properly installed and loaded
+
+
+
+
+
+ Check if Normalizer class is properly installed and loaded
+
+
+
+
+
+ Check if the rendering will be done on client side or server side
+
+
+
+
+ $database) {
+?>
+
+ Check if Calibre database path is not an URL
+
+
+
+
+
+ Check if Calibre database file exists and is readable
+
+
+ Value of $config[\'calibre_directory\'] in config_local.php
+Value of open_basedir in your php.ini
+The access rights of the Calibre Database
+Synology users please read this
+';
+ }
+ ?>
+
+
+
+
+ Check if Calibre database file can be opened with PHP
+
+
+
+
+
+ Check if Calibre database file contains at least some of the needed tables
+
+ query('select count(*) FROM sqlite_master WHERE type="table" AND name in ("books", "authors", "tags", "series")')->fetchColumn();
+ if ($count == 4) {
+ echo $name . ' OK';
+ } else {
+ echo $name . ' Not all Calibre tables were found. Are you sure you\'re using the correct database.';
+ }
+ } catch (Exception $e) {
+ echo $name . ' If the file is readable, check your php configuration. Exception detail : ' . $e;
+ }
+ ?>
+
+
+
+
+ Check if all Calibre books are found
+
+ prepare('select books.path || "/" || data.name || "." || lower (format) as fullpath from data join books on data.book = books.id');
+ $result->execute();
+ while ($post = $result->fetchObject())
+ {
+ if (!is_file (Base::getDbDirectory($i) . $post->fullpath)) {
+ echo ' ' . Base::getDbDirectory($i) . $post->fullpath . '
';
+ }
+ }
+ } catch (Exception $e) {
+ echo $name . ' If the file is readable, check your php configuration. Exception detail : ' . $e;
+ }
+ ?>
+
+
+
+
+
+
+
+
diff --git a/sources/composer-dl.sh b/sources/composer-dl.sh
new file mode 100755
index 0000000..043f357
--- /dev/null
+++ b/sources/composer-dl.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+# https://getcomposer.org/
+if [ -x `which wget` ]; then
+ echo "wget found."
+ wget -q https://getcomposer.org/installer -O - | php
+elif [ -x `which curl` ]; then
+ echo "curl found."
+ curl -sS https://getcomposer.org/installer | php
+else
+ echo "Please install wget or curl to download Composer."
+fi
+
+if [ -f "./composer.phar" ]; then
+ chmod a+x ./composer.phar
+
+ # Install support for bower and NPM packages
+ ./composer.phar global require "fxp/composer-asset-plugin:~1.1"
+fi
diff --git a/sources/composer.json b/sources/composer.json
new file mode 100644
index 0000000..6933c45
--- /dev/null
+++ b/sources/composer.json
@@ -0,0 +1,74 @@
+{
+ "name": "Calibre OPDS (and HTML) PHP Server",
+ "description": "web-based light alternative to Calibre content server / Calibre2OPDS to serve ebooks (epub, mobi, pdf, ...)",
+ "require": {
+ "ext-gd": "*",
+ "ext-xml": "*",
+ "ext-intl": "*",
+ "ext-json": "*",
+ "ext-pdo_sqlite": "*",
+ "ext-mbstring": "*",
+ "dimsemenov/magnific-popup": "~1.0",
+ "phpmailer/phpmailer": "~5.2",
+ "twbs/bootstrap": "~3.3",
+ "bower-asset/jQuery": "~1.11",
+ "bower-asset/jquery-cookie": "~1.4",
+ "bower-asset/normalize.css": "~3.0",
+ "twitter/typeahead.js": "~0.10.5",
+ "seblucas/dot-php": "~1.0.0",
+ "seblucas/php-epub-meta": "~1.0.0",
+ "seblucas/tbszip": "~2.16",
+ "simonpioli/sortelements": "dev-master",
+ "bower-asset/doT": "~1.0.1",
+ "rsms/js-lru": "dev-v2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "5.4.* || 4.8.*",
+ "sauce/sausage": ">=0.12.0",
+ "phing/phing": "2.*"
+ },
+ "autoload": {
+ "classmap": ["lib/", "resources/"]
+ },
+ "repositories": [
+ {
+ "type": "package",
+ "package": {
+ "name": "seblucas/dot-php",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/seblucas/doT-php",
+ "reference": "master"
+ },
+ "autoload": {
+ "classmap": ["./"]
+ }
+ }
+ },
+ {
+ "type": "package",
+ "package": {
+ "name": "simonpioli/sortelements",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/simonpioli/sortElements",
+ "reference": "master"
+ }
+ }
+ },
+ {
+ "type": "package",
+ "package": {
+ "name": "rsms/js-lru",
+ "version": "dev-v2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/rsms/js-lru",
+ "reference": "v2"
+ }
+ }
+ }
+ ]
+}
diff --git a/sources/composer.lock b/sources/composer.lock
new file mode 100644
index 0000000..0144816
--- /dev/null
+++ b/sources/composer.lock
@@ -0,0 +1,2517 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
+ "This file is @generated automatically"
+ ],
+ "hash": "796be60d03ca5c5b4f2f01e3d4d4b870",
+ "content-hash": "5cdf7fa70c21736e02127769497e9b5b",
+ "packages": [
+ {
+ "name": "bower-asset/dot",
+ "version": "1.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/olado/doT.git",
+ "reference": "195025f06055761f6da3a900251382e788c42d0e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/olado/doT/zipball/195025f06055761f6da3a900251382e788c42d0e",
+ "reference": "195025f06055761f6da3a900251382e788c42d0e",
+ "shasum": ""
+ },
+ "type": "bower-asset-library"
+ },
+ {
+ "name": "bower-asset/jquery",
+ "version": "1.12.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/jquery/jquery-dist.git",
+ "reference": "5e89585e0121e72ff47de177c5ef604f3089a53d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/jquery/jquery-dist/zipball/5e89585e0121e72ff47de177c5ef604f3089a53d",
+ "reference": "5e89585e0121e72ff47de177c5ef604f3089a53d",
+ "shasum": ""
+ },
+ "type": "bower-asset-library",
+ "extra": {
+ "bower-asset-main": "dist/jquery.js",
+ "bower-asset-ignore": [
+ "package.json"
+ ]
+ },
+ "license": [
+ "MIT"
+ ],
+ "keywords": [
+ "browser",
+ "javascript",
+ "jquery",
+ "library"
+ ]
+ },
+ {
+ "name": "bower-asset/jquery-cookie",
+ "version": "v1.4.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/carhartl/jquery-cookie.git",
+ "reference": "7f88a4e631aba8a8c688fd8999ce6b9bcfd50718"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/carhartl/jquery-cookie/zipball/7f88a4e631aba8a8c688fd8999ce6b9bcfd50718",
+ "reference": "7f88a4e631aba8a8c688fd8999ce6b9bcfd50718",
+ "shasum": ""
+ },
+ "require": {
+ "bower-asset/jquery": ">=1.2"
+ },
+ "type": "bower-asset-library",
+ "extra": {
+ "bower-asset-main": [
+ "./jquery.cookie.js"
+ ],
+ "bower-asset-ignore": [
+ "test",
+ ".*",
+ "*.json",
+ "*.md",
+ "*.txt",
+ "Gruntfile.js"
+ ]
+ }
+ },
+ {
+ "name": "bower-asset/normalize.css",
+ "version": "3.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/necolas/normalize.css.git",
+ "reference": "2bdda84272650aedfb45d8abe11a6d177933a803"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/necolas/normalize.css/zipball/2bdda84272650aedfb45d8abe11a6d177933a803",
+ "reference": "2bdda84272650aedfb45d8abe11a6d177933a803",
+ "shasum": ""
+ },
+ "type": "bower-asset-library",
+ "extra": {
+ "bower-asset-main": "normalize.css",
+ "bower-asset-ignore": [
+ "CHANGELOG.md",
+ "CONTRIBUTING.md",
+ "component.json",
+ "package.json",
+ "test.html"
+ ]
+ }
+ },
+ {
+ "name": "dimsemenov/magnific-popup",
+ "version": "1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/dimsemenov/Magnific-Popup.git",
+ "reference": "6b7a8088783cbce01034414c1fd2d8e1889093ae"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/dimsemenov/Magnific-Popup/zipball/6b7a8088783cbce01034414c1fd2d8e1889093ae",
+ "reference": "6b7a8088783cbce01034414c1fd2d8e1889093ae",
+ "shasum": ""
+ },
+ "type": "library",
+ "notification-url": "https://packagist.org/downloads/",
+ "description": "Light and responsive lightbox script with focus on performance.",
+ "homepage": "http://dimsemenov.com/plugins/magnific-popup/",
+ "time": "2016-02-20 09:06:30"
+ },
+ {
+ "name": "phpmailer/phpmailer",
+ "version": "v5.2.17",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPMailer/PHPMailer.git",
+ "reference": "208913c6042967ba404f4bfa0819bf5bef79dbec"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/208913c6042967ba404f4bfa0819bf5bef79dbec",
+ "reference": "208913c6042967ba404f4bfa0819bf5bef79dbec",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.0.0"
+ },
+ "require-dev": {
+ "phpdocumentor/phpdocumentor": "*",
+ "phpunit/phpunit": "4.7.*"
+ },
+ "suggest": {
+ "league/oauth2-google": "Needed for Google XOAUTH2 authentication"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "class.phpmailer.php",
+ "class.phpmaileroauth.php",
+ "class.phpmaileroauthgoogle.php",
+ "class.smtp.php",
+ "class.pop3.php",
+ "extras/EasyPeasyICS.php",
+ "extras/ntlm_sasl_client.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL-2.1"
+ ],
+ "authors": [
+ {
+ "name": "Jim Jagielski",
+ "email": "jimjag@gmail.com"
+ },
+ {
+ "name": "Marcus Bointon",
+ "email": "phpmailer@synchromedia.co.uk"
+ },
+ {
+ "name": "Andy Prevost",
+ "email": "codeworxtech@users.sourceforge.net"
+ },
+ {
+ "name": "Brent R. Matzelle"
+ }
+ ],
+ "description": "PHPMailer is a full-featured email creation and transfer class for PHP",
+ "time": "2016-12-09 10:03:48"
+ },
+ {
+ "name": "rsms/js-lru",
+ "version": "dev-v2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/rsms/js-lru",
+ "reference": "v2"
+ },
+ "type": "library",
+ "time": "2016-11-16 03:30:49"
+ },
+ {
+ "name": "seblucas/dot-php",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/seblucas/doT-php.git",
+ "reference": "0b6b351e539007eb72c0c34131204d036ce4454f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/seblucas/doT-php/zipball/0b6b351e539007eb72c0c34131204d036ce4454f",
+ "reference": "0b6b351e539007eb72c0c34131204d036ce4454f",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "doT.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "GPL-2.0"
+ ],
+ "authors": [
+ {
+ "name": "Sébastien Lucas",
+ "email": "sebastien@slucas.fr",
+ "homepage": "http://www.slucas.fr/",
+ "role": "Developer"
+ }
+ ],
+ "description": "PHP rendering engine for doT.js (The fastest + concise javascript template engine for nodejs and browsers)",
+ "homepage": "https://github.com/seblucas/doT-php",
+ "keywords": [
+ "Rendering",
+ "dot",
+ "engine",
+ "template"
+ ],
+ "time": "2016-07-03 12:29:39"
+ },
+ {
+ "name": "seblucas/php-epub-meta",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/seblucas/php-epub-meta.git",
+ "reference": "ee222ba6f75c809dd88fa8bc5798fc7868fd915c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/seblucas/php-epub-meta/zipball/ee222ba6f75c809dd88fa8bc5798fc7868fd915c",
+ "reference": "ee222ba6f75c809dd88fa8bc5798fc7868fd915c",
+ "shasum": ""
+ },
+ "require": {
+ "ext-xml": "*",
+ "ext-zip": "*",
+ "php": ">=5.3.0",
+ "seblucas/tbszip": "~2.16.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "4.*"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "lib/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "authors": [
+ {
+ "name": "Sébastien Lucas",
+ "email": "sebastien@slucas.fr",
+ "homepage": "http://www.slucas.fr/",
+ "role": "Developer"
+ },
+ {
+ "name": "Andreas Gohr",
+ "email": "andi@splitbrain.org",
+ "homepage": "https://www.splitbrain.org/",
+ "role": "Developer"
+ }
+ ],
+ "description": "Reading and writing metadata included in the EPub ebook format",
+ "homepage": "https://github.com/seblucas/php-epub-meta",
+ "keywords": [
+ "ebook",
+ "epub",
+ "metadata"
+ ],
+ "time": "2016-07-03 20:07:37"
+ },
+ {
+ "name": "seblucas/tbszip",
+ "version": "2.16.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/seblucas/tbszip.git",
+ "reference": "2c50bf309bb4431a24e206f164fdb4a2e6b10b7f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/seblucas/tbszip/zipball/2c50bf309bb4431a24e206f164fdb4a2e6b10b7f",
+ "reference": "2c50bf309bb4431a24e206f164fdb4a2e6b10b7f",
+ "shasum": ""
+ },
+ "require": {
+ "ext-zlib": "*",
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "5.4.*"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "tbszip.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL-2.1+"
+ ],
+ "authors": [
+ {
+ "name": "Skrol29",
+ "homepage": "http://www.tinybutstrong.com/",
+ "role": "Developer"
+ },
+ {
+ "name": "Sébastien Lucas",
+ "email": "sebastien@slucas.fr",
+ "homepage": "http://www.slucas.fr/",
+ "role": "Developer"
+ }
+ ],
+ "description": "Work with zip archives without making temporary files or needing binaries",
+ "homepage": "http://www.tinybutstrong.com/tools.php",
+ "keywords": [
+ "archive",
+ "compression",
+ "zip"
+ ],
+ "time": "2016-07-03 12:26:16"
+ },
+ {
+ "name": "simonpioli/sortelements",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/simonpioli/sortElements",
+ "reference": "master"
+ },
+ "type": "library",
+ "time": "2012-04-25 11:04:51"
+ },
+ {
+ "name": "twbs/bootstrap",
+ "version": "v3.3.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/twbs/bootstrap.git",
+ "reference": "0b9c4a4007c44201dce9a6cc1a38407005c26c86"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/twbs/bootstrap/zipball/0b9c4a4007c44201dce9a6cc1a38407005c26c86",
+ "reference": "0b9c4a4007c44201dce9a6cc1a38407005c26c86",
+ "shasum": ""
+ },
+ "replace": {
+ "twitter/bootstrap": "self.version"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.3.x-dev"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jacob Thornton",
+ "email": "jacobthornton@gmail.com"
+ },
+ {
+ "name": "Mark Otto",
+ "email": "markdotto@gmail.com"
+ }
+ ],
+ "description": "The most popular front-end framework for developing responsive, mobile first projects on the web.",
+ "homepage": "http://getbootstrap.com",
+ "keywords": [
+ "JS",
+ "css",
+ "framework",
+ "front-end",
+ "less",
+ "mobile-first",
+ "responsive",
+ "web"
+ ],
+ "time": "2016-07-25 15:51:55"
+ },
+ {
+ "name": "twitter/typeahead.js",
+ "version": "v0.10.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/twitter/typeahead.js.git",
+ "reference": "5f198b87d1af845da502ea9df93a5e84801ce742"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/twitter/typeahead.js/zipball/5f198b87d1af845da502ea9df93a5e84801ce742",
+ "reference": "5f198b87d1af845da502ea9df93a5e84801ce742",
+ "shasum": ""
+ },
+ "type": "library",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Twitter Inc.",
+ "homepage": "https://twitter.com/twitteross"
+ }
+ ],
+ "description": "fast and fully-featured autocomplete library",
+ "homepage": "http://twitter.github.com/typeahead.js",
+ "keywords": [
+ "autocomplete",
+ "typeahead"
+ ],
+ "time": "2014-08-08 06:18:07"
+ }
+ ],
+ "packages-dev": [
+ {
+ "name": "appium/php-client",
+ "version": "v0.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/appium/php-client.git",
+ "reference": "06c68c20d389bfd50a512393b1fe750cc5044b2f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/appium/php-client/zipball/06c68c20d389bfd50a512393b1fe750cc5044b2f",
+ "reference": "06c68c20d389bfd50a512393b1fe750cc5044b2f",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0",
+ "phpunit/phpunit-selenium": ">=1.3.3"
+ },
+ "type": "appium-php",
+ "autoload": {
+ "classmap": [
+ "PHPUnit/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "include-path": [
+ ""
+ ],
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "Isaac Murchie",
+ "email": "isaac@saucelabs.com",
+ "homepage": "http://www.saucelabs.com",
+ "role": "Lead"
+ }
+ ],
+ "description": "PHP client for Selenium 3.0/Appium 1.0",
+ "homepage": "http://github.com/appium/appium-php",
+ "keywords": [
+ "appium",
+ "phpunit",
+ "selenium"
+ ],
+ "time": "2016-11-09 01:09:43"
+ },
+ {
+ "name": "brianium/habitat",
+ "version": "v1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/brianium/habitat.git",
+ "reference": "d0979e3bb379cbc78ecb42b3ac171bc2b7e06d96"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/brianium/habitat/zipball/d0979e3bb379cbc78ecb42b3ac171bc2b7e06d96",
+ "reference": "d0979e3bb379cbc78ecb42b3ac171bc2b7e06d96",
+ "shasum": ""
+ },
+ "require-dev": {
+ "monolog/monolog": ">=1.5.0",
+ "phpunit/phpunit": ">=3.7.21"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "Habitat": [
+ "src/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Brian",
+ "email": "scaturrob@gmail.com",
+ "homepage": "http://brianscaturro.com",
+ "role": "Lead"
+ }
+ ],
+ "description": "A dependable php environment",
+ "time": "2013-06-08 04:42:29"
+ },
+ {
+ "name": "brianium/paratest",
+ "version": "0.14.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/brianium/paratest.git",
+ "reference": "dddcfa8510da7aae9616ba1738ebf630d9e84aa4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/brianium/paratest/zipball/dddcfa8510da7aae9616ba1738ebf630d9e84aa4",
+ "reference": "dddcfa8510da7aae9616ba1738ebf630d9e84aa4",
+ "shasum": ""
+ },
+ "require": {
+ "brianium/habitat": "1.0.0",
+ "composer/semver": "~1.2",
+ "ext-pcre": "*",
+ "ext-reflection": "*",
+ "ext-simplexml": "*",
+ "php": ">=5.5.11",
+ "phpunit/php-timer": ">=1.0.4",
+ "phpunit/phpunit": ">=3.7.8",
+ "symfony/console": "~2.3|~3.0",
+ "symfony/process": "~2.3|~3.0"
+ },
+ "bin": [
+ "bin/paratest"
+ ],
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "ParaTest": [
+ "src/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Brian Scaturro",
+ "email": "scaturrob@gmail.com",
+ "homepage": "http://brianscaturro.com",
+ "role": "Lead"
+ }
+ ],
+ "description": "Parallel testing for PHP",
+ "homepage": "https://github.com/brianium/paratest",
+ "keywords": [
+ "concurrent",
+ "parallel",
+ "phpunit",
+ "testing"
+ ],
+ "time": "2016-08-12 20:14:13"
+ },
+ {
+ "name": "composer/semver",
+ "version": "1.4.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/composer/semver.git",
+ "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/composer/semver/zipball/c7cb9a2095a074d131b65a8a0cd294479d785573",
+ "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3.2 || ^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.5 || ^5.0.5",
+ "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Composer\\Semver\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nils Adermann",
+ "email": "naderman@naderman.de",
+ "homepage": "http://www.naderman.de"
+ },
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be",
+ "homepage": "http://seld.be"
+ },
+ {
+ "name": "Rob Bast",
+ "email": "rob.bast@gmail.com",
+ "homepage": "http://robbast.nl"
+ }
+ ],
+ "description": "Semver library that offers utilities, version constraint parsing and validation.",
+ "keywords": [
+ "semantic",
+ "semver",
+ "validation",
+ "versioning"
+ ],
+ "time": "2016-08-30 16:08:34"
+ },
+ {
+ "name": "doctrine/instantiator",
+ "version": "1.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/instantiator.git",
+ "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d",
+ "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3,<8.0-DEV"
+ },
+ "require-dev": {
+ "athletic/athletic": "~0.1.8",
+ "ext-pdo": "*",
+ "ext-phar": "*",
+ "phpunit/phpunit": "~4.0",
+ "squizlabs/php_codesniffer": "~2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Marco Pivetta",
+ "email": "ocramius@gmail.com",
+ "homepage": "http://ocramius.github.com/"
+ }
+ ],
+ "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
+ "homepage": "https://github.com/doctrine/instantiator",
+ "keywords": [
+ "constructor",
+ "instantiate"
+ ],
+ "time": "2015-06-14 21:17:01"
+ },
+ {
+ "name": "myclabs/deep-copy",
+ "version": "1.5.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/myclabs/DeepCopy.git",
+ "reference": "399c1f9781e222f6eb6cc238796f5200d1b7f108"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/399c1f9781e222f6eb6cc238796f5200d1b7f108",
+ "reference": "399c1f9781e222f6eb6cc238796f5200d1b7f108",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "doctrine/collections": "1.*",
+ "phpunit/phpunit": "~4.1"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "DeepCopy\\": "src/DeepCopy/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Create deep copies (clones) of your objects",
+ "homepage": "https://github.com/myclabs/DeepCopy",
+ "keywords": [
+ "clone",
+ "copy",
+ "duplicate",
+ "object",
+ "object graph"
+ ],
+ "time": "2016-10-31 17:19:45"
+ },
+ {
+ "name": "phing/phing",
+ "version": "2.15.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phingofficial/phing.git",
+ "reference": "0999ab4e94e609dc00998e3d1b88df843054db7c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phingofficial/phing/zipball/0999ab4e94e609dc00998e3d1b88df843054db7c",
+ "reference": "0999ab4e94e609dc00998e3d1b88df843054db7c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.2.0"
+ },
+ "require-dev": {
+ "ext-pdo_sqlite": "*",
+ "lastcraft/simpletest": "@dev",
+ "mikey179/vfsstream": "^1.6",
+ "pdepend/pdepend": "2.x",
+ "pear/archive_tar": "1.4.x",
+ "pear/http_request2": "dev-trunk",
+ "pear/net_growl": "dev-trunk",
+ "pear/pear-core-minimal": "1.10.1",
+ "pear/versioncontrol_git": "@dev",
+ "pear/versioncontrol_svn": "~0.5",
+ "phpdocumentor/phpdocumentor": "2.x",
+ "phploc/phploc": "~2.0.6",
+ "phpmd/phpmd": "~2.2",
+ "phpunit/phpunit": ">=3.7",
+ "sebastian/git": "~1.0",
+ "sebastian/phpcpd": "2.x",
+ "siad007/versioncontrol_hg": "^1.0",
+ "squizlabs/php_codesniffer": "~2.2",
+ "symfony/yaml": "~2.7"
+ },
+ "suggest": {
+ "pdepend/pdepend": "PHP version of JDepend",
+ "pear/archive_tar": "Tar file management class",
+ "pear/versioncontrol_git": "A library that provides OO interface to handle Git repository",
+ "pear/versioncontrol_svn": "A simple OO-style interface for Subversion, the free/open-source version control system",
+ "phpdocumentor/phpdocumentor": "Documentation Generator for PHP",
+ "phploc/phploc": "A tool for quickly measuring the size of a PHP project",
+ "phpmd/phpmd": "PHP version of PMD tool",
+ "phpunit/php-code-coverage": "Library that provides collection, processing, and rendering functionality for PHP code coverage information",
+ "phpunit/phpunit": "The PHP Unit Testing Framework",
+ "sebastian/phpcpd": "Copy/Paste Detector (CPD) for PHP code",
+ "siad007/versioncontrol_hg": "A library for interfacing with Mercurial repositories.",
+ "tedivm/jshrink": "Javascript Minifier built in PHP"
+ },
+ "bin": [
+ "bin/phing"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.15.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "classes/phing/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "include-path": [
+ "classes"
+ ],
+ "license": [
+ "LGPL-3.0"
+ ],
+ "authors": [
+ {
+ "name": "Michiel Rook",
+ "email": "mrook@php.net"
+ },
+ {
+ "name": "Phing Community",
+ "homepage": "https://www.phing.info/trac/wiki/Development/Contributors"
+ }
+ ],
+ "description": "PHing Is Not GNU make; it's a PHP project build system or build tool based on Apache Ant.",
+ "homepage": "https://www.phing.info/",
+ "keywords": [
+ "build",
+ "phing",
+ "task",
+ "tool"
+ ],
+ "time": "2016-10-13 09:01:45"
+ },
+ {
+ "name": "phpdocumentor/reflection-common",
+ "version": "1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
+ "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c",
+ "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.6"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": [
+ "src"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jaap van Otterdijk",
+ "email": "opensource@ijaap.nl"
+ }
+ ],
+ "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
+ "homepage": "http://www.phpdoc.org",
+ "keywords": [
+ "FQSEN",
+ "phpDocumentor",
+ "phpdoc",
+ "reflection",
+ "static analysis"
+ ],
+ "time": "2015-12-27 11:43:31"
+ },
+ {
+ "name": "phpdocumentor/reflection-docblock",
+ "version": "3.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
+ "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/8331b5efe816ae05461b7ca1e721c01b46bafb3e",
+ "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5",
+ "phpdocumentor/reflection-common": "^1.0@dev",
+ "phpdocumentor/type-resolver": "^0.2.0",
+ "webmozart/assert": "^1.0"
+ },
+ "require-dev": {
+ "mockery/mockery": "^0.9.4",
+ "phpunit/phpunit": "^4.4"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": [
+ "src/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike van Riel",
+ "email": "me@mikevanriel.com"
+ }
+ ],
+ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
+ "time": "2016-09-30 07:12:33"
+ },
+ {
+ "name": "phpdocumentor/type-resolver",
+ "version": "0.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/TypeResolver.git",
+ "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb",
+ "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5",
+ "phpdocumentor/reflection-common": "^1.0"
+ },
+ "require-dev": {
+ "mockery/mockery": "^0.9.4",
+ "phpunit/phpunit": "^5.2||^4.8.24"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": [
+ "src/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike van Riel",
+ "email": "me@mikevanriel.com"
+ }
+ ],
+ "time": "2016-11-25 06:54:22"
+ },
+ {
+ "name": "phpspec/prophecy",
+ "version": "v1.6.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpspec/prophecy.git",
+ "reference": "6c52c2722f8460122f96f86346600e1077ce22cb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpspec/prophecy/zipball/6c52c2722f8460122f96f86346600e1077ce22cb",
+ "reference": "6c52c2722f8460122f96f86346600e1077ce22cb",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/instantiator": "^1.0.2",
+ "php": "^5.3|^7.0",
+ "phpdocumentor/reflection-docblock": "^2.0|^3.0.2",
+ "sebastian/comparator": "^1.1",
+ "sebastian/recursion-context": "^1.0|^2.0"
+ },
+ "require-dev": {
+ "phpspec/phpspec": "^2.0",
+ "phpunit/phpunit": "^4.8 || ^5.6.5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.6.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Prophecy\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Konstantin Kudryashov",
+ "email": "ever.zet@gmail.com",
+ "homepage": "http://everzet.com"
+ },
+ {
+ "name": "Marcello Duarte",
+ "email": "marcello.duarte@gmail.com"
+ }
+ ],
+ "description": "Highly opinionated mocking framework for PHP 5.3+",
+ "homepage": "https://github.com/phpspec/prophecy",
+ "keywords": [
+ "Double",
+ "Dummy",
+ "fake",
+ "mock",
+ "spy",
+ "stub"
+ ],
+ "time": "2016-11-21 14:58:47"
+ },
+ {
+ "name": "phpunit/php-code-coverage",
+ "version": "4.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+ "reference": "903fd6318d0a90b4770a009ff73e4a4e9c437929"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/903fd6318d0a90b4770a009ff73e4a4e9c437929",
+ "reference": "903fd6318d0a90b4770a009ff73e4a4e9c437929",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.6 || ^7.0",
+ "phpunit/php-file-iterator": "~1.3",
+ "phpunit/php-text-template": "~1.2",
+ "phpunit/php-token-stream": "^1.4.2",
+ "sebastian/code-unit-reverse-lookup": "~1.0",
+ "sebastian/environment": "^1.3.2 || ^2.0",
+ "sebastian/version": "~1.0|~2.0"
+ },
+ "require-dev": {
+ "ext-xdebug": ">=2.1.4",
+ "phpunit/phpunit": "^5.4"
+ },
+ "suggest": {
+ "ext-dom": "*",
+ "ext-xdebug": ">=2.4.0",
+ "ext-xmlwriter": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+ "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+ "keywords": [
+ "coverage",
+ "testing",
+ "xunit"
+ ],
+ "time": "2016-11-28 16:00:31"
+ },
+ {
+ "name": "phpunit/php-file-iterator",
+ "version": "1.4.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+ "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3cc8f69b3028d0f96a9078e6295d86e9bf019be5",
+ "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+ "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
+ "keywords": [
+ "filesystem",
+ "iterator"
+ ],
+ "time": "2016-10-03 07:40:28"
+ },
+ {
+ "name": "phpunit/php-text-template",
+ "version": "1.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-text-template.git",
+ "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+ "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Simple template engine.",
+ "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+ "keywords": [
+ "template"
+ ],
+ "time": "2015-06-21 13:50:34"
+ },
+ {
+ "name": "phpunit/php-timer",
+ "version": "1.0.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-timer.git",
+ "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/38e9124049cf1a164f1e4537caf19c99bf1eb260",
+ "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4|~5"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Utility class for timing",
+ "homepage": "https://github.com/sebastianbergmann/php-timer/",
+ "keywords": [
+ "timer"
+ ],
+ "time": "2016-05-12 18:03:57"
+ },
+ {
+ "name": "phpunit/php-token-stream",
+ "version": "1.4.9",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-token-stream.git",
+ "reference": "3b402f65a4cc90abf6e1104e388b896ce209631b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3b402f65a4cc90abf6e1104e388b896ce209631b",
+ "reference": "3b402f65a4cc90abf6e1104e388b896ce209631b",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Wrapper around PHP's tokenizer extension.",
+ "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
+ "keywords": [
+ "tokenizer"
+ ],
+ "time": "2016-11-15 14:06:22"
+ },
+ {
+ "name": "phpunit/phpunit",
+ "version": "5.4.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit.git",
+ "reference": "3132365e1430c091f208e120b8845d39c25f20e6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3132365e1430c091f208e120b8845d39c25f20e6",
+ "reference": "3132365e1430c091f208e120b8845d39c25f20e6",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-json": "*",
+ "ext-pcre": "*",
+ "ext-reflection": "*",
+ "ext-spl": "*",
+ "myclabs/deep-copy": "~1.3",
+ "php": "^5.6 || ^7.0",
+ "phpspec/prophecy": "^1.3.1",
+ "phpunit/php-code-coverage": "^4.0.1",
+ "phpunit/php-file-iterator": "~1.4",
+ "phpunit/php-text-template": "~1.2",
+ "phpunit/php-timer": "^1.0.6",
+ "phpunit/phpunit-mock-objects": "^3.2",
+ "sebastian/comparator": "~1.1",
+ "sebastian/diff": "~1.2",
+ "sebastian/environment": "^1.3 || ^2.0",
+ "sebastian/exporter": "~1.2",
+ "sebastian/global-state": "~1.0",
+ "sebastian/object-enumerator": "~1.0",
+ "sebastian/resource-operations": "~1.0",
+ "sebastian/version": "~1.0|~2.0",
+ "symfony/yaml": "~2.1|~3.0"
+ },
+ "conflict": {
+ "phpdocumentor/reflection-docblock": "3.0.2"
+ },
+ "suggest": {
+ "phpunit/php-invoker": "~1.1"
+ },
+ "bin": [
+ "phpunit"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.4.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "The PHP Unit Testing framework.",
+ "homepage": "https://phpunit.de/",
+ "keywords": [
+ "phpunit",
+ "testing",
+ "xunit"
+ ],
+ "time": "2016-07-26 14:48:00"
+ },
+ {
+ "name": "phpunit/phpunit-mock-objects",
+ "version": "3.4.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
+ "reference": "3ab72b65b39b491e0c011e2e09bb2206c2aa8e24"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/3ab72b65b39b491e0c011e2e09bb2206c2aa8e24",
+ "reference": "3ab72b65b39b491e0c011e2e09bb2206c2aa8e24",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/instantiator": "^1.0.2",
+ "php": "^5.6 || ^7.0",
+ "phpunit/php-text-template": "^1.2",
+ "sebastian/exporter": "^1.2 || ^2.0"
+ },
+ "conflict": {
+ "phpunit/phpunit": "<5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^5.4"
+ },
+ "suggest": {
+ "ext-soap": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.2.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Mock Object library for PHPUnit",
+ "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
+ "keywords": [
+ "mock",
+ "xunit"
+ ],
+ "time": "2016-12-08 20:27:08"
+ },
+ {
+ "name": "phpunit/phpunit-selenium",
+ "version": "3.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/giorgiosironi/phpunit-selenium.git",
+ "reference": "d3aa8984c31efcff7c8829b9bd9ad7ab4c94709c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/giorgiosironi/phpunit-selenium/zipball/d3aa8984c31efcff7c8829b9bd9ad7ab4c94709c",
+ "reference": "d3aa8984c31efcff7c8829b9bd9ad7ab4c94709c",
+ "shasum": ""
+ },
+ "require": {
+ "ext-curl": "*",
+ "ext-dom": "*",
+ "php": ">=5.6",
+ "phpunit/phpunit": "~5.0",
+ "sebastian/comparator": "~1.0"
+ },
+ "require-dev": {
+ "phing/phing": "2.*"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "PHPUnit/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "include-path": [
+ ""
+ ],
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Giorgio Sironi",
+ "email": "info@giorgiosironi.com",
+ "role": "developer"
+ },
+ {
+ "name": "Ivan Kurnosov",
+ "email": "zerkms@zerkms.com",
+ "role": "developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "original developer"
+ }
+ ],
+ "description": "Selenium Server integration for PHPUnit",
+ "homepage": "http://www.phpunit.de/",
+ "keywords": [
+ "phpunit",
+ "selenium",
+ "testing",
+ "xunit"
+ ],
+ "time": "2016-04-22 10:41:33"
+ },
+ {
+ "name": "psr/log",
+ "version": "1.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/log.git",
+ "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
+ "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "Psr/Log/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for logging libraries",
+ "homepage": "https://github.com/php-fig/log",
+ "keywords": [
+ "log",
+ "psr",
+ "psr-3"
+ ],
+ "time": "2016-10-10 12:19:37"
+ },
+ {
+ "name": "sauce/sausage",
+ "version": "0.17.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/jlipps/sausage.git",
+ "reference": "ce7fea6a8de0090459cf23719aec907452600aa6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/jlipps/sausage/zipball/ce7fea6a8de0090459cf23719aec907452600aa6",
+ "reference": "ce7fea6a8de0090459cf23719aec907452600aa6",
+ "shasum": ""
+ },
+ "require": {
+ "appium/php-client": ">=0.1.0",
+ "brianium/paratest": ">=0.12.1",
+ "php": ">=5.4.0",
+ "phpunit/phpunit-selenium": ">=1.4.1",
+ "sauce/sausage-installer": ">=0.1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": ">=4.5.1"
+ },
+ "suggest": {
+ "sauce/connect": ">=3.1"
+ },
+ "bin": [
+ "bin/sauce_config"
+ ],
+ "type": "sauce-sausage",
+ "autoload": {
+ "psr-0": {
+ "Sauce": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "Jonathan Lipps",
+ "email": "jlipps@saucelabs.com",
+ "homepage": "http://www.saucelabs.com",
+ "role": "Lead"
+ }
+ ],
+ "description": "PHP version of the Sauce Labs API",
+ "homepage": "http://github.com/jlipps/sausage",
+ "keywords": [
+ "Sauce",
+ "SauceLabs",
+ "api",
+ "appium",
+ "phpunit",
+ "selenium",
+ "testing"
+ ],
+ "time": "2015-04-28 03:02:03"
+ },
+ {
+ "name": "sauce/sausage-installer",
+ "version": "v0.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/jlipps/sausage-installer.git",
+ "reference": "5435cadb3ef1cec77218814af3c121b3556a5444"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/jlipps/sausage-installer/zipball/5435cadb3ef1cec77218814af3c121b3556a5444",
+ "reference": "5435cadb3ef1cec77218814af3c121b3556a5444",
+ "shasum": ""
+ },
+ "type": "composer-installer",
+ "extra": {
+ "class": "Sauce\\Composer\\SausageInstaller"
+ },
+ "autoload": {
+ "psr-0": {
+ "Sauce": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "Jonathan Lipps",
+ "email": "jlipps@saucelabs.com",
+ "homepage": "http://www.saucelabs.com",
+ "role": "Lead"
+ }
+ ],
+ "homepage": "http://github.com/jlipps/sausage-installer",
+ "time": "2012-09-28 18:41:38"
+ },
+ {
+ "name": "sebastian/code-unit-reverse-lookup",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
+ "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/c36f5e7cfce482fde5bf8d10d41a53591e0198fe",
+ "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Looks up which function or method a line of code belongs to",
+ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
+ "time": "2016-02-13 06:45:14"
+ },
+ {
+ "name": "sebastian/comparator",
+ "version": "1.2.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/comparator.git",
+ "reference": "6a1ed12e8b2409076ab22e3897126211ff8b1f7f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/6a1ed12e8b2409076ab22e3897126211ff8b1f7f",
+ "reference": "6a1ed12e8b2409076ab22e3897126211ff8b1f7f",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3",
+ "sebastian/diff": "~1.2",
+ "sebastian/exporter": "~1.2 || ~2.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.2.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@2bepublished.at"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides the functionality to compare PHP values for equality",
+ "homepage": "http://www.github.com/sebastianbergmann/comparator",
+ "keywords": [
+ "comparator",
+ "compare",
+ "equality"
+ ],
+ "time": "2016-11-19 09:18:40"
+ },
+ {
+ "name": "sebastian/diff",
+ "version": "1.4.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/diff.git",
+ "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e",
+ "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.8"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Kore Nordmann",
+ "email": "mail@kore-nordmann.de"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Diff implementation",
+ "homepage": "https://github.com/sebastianbergmann/diff",
+ "keywords": [
+ "diff"
+ ],
+ "time": "2015-12-08 07:14:41"
+ },
+ {
+ "name": "sebastian/environment",
+ "version": "2.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/environment.git",
+ "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac",
+ "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.6 || ^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^5.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides functionality to handle HHVM/PHP environments",
+ "homepage": "http://www.github.com/sebastianbergmann/environment",
+ "keywords": [
+ "Xdebug",
+ "environment",
+ "hhvm"
+ ],
+ "time": "2016-11-26 07:53:53"
+ },
+ {
+ "name": "sebastian/exporter",
+ "version": "1.2.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/exporter.git",
+ "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4",
+ "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3",
+ "sebastian/recursion-context": "~1.0"
+ },
+ "require-dev": {
+ "ext-mbstring": "*",
+ "phpunit/phpunit": "~4.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.3.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@2bepublished.at"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ }
+ ],
+ "description": "Provides the functionality to export PHP variables for visualization",
+ "homepage": "http://www.github.com/sebastianbergmann/exporter",
+ "keywords": [
+ "export",
+ "exporter"
+ ],
+ "time": "2016-06-17 09:04:28"
+ },
+ {
+ "name": "sebastian/global-state",
+ "version": "1.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/global-state.git",
+ "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4",
+ "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.2"
+ },
+ "suggest": {
+ "ext-uopz": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Snapshotting of global state",
+ "homepage": "http://www.github.com/sebastianbergmann/global-state",
+ "keywords": [
+ "global state"
+ ],
+ "time": "2015-10-12 03:26:01"
+ },
+ {
+ "name": "sebastian/object-enumerator",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+ "reference": "d4ca2fb70344987502567bc50081c03e6192fb26"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/d4ca2fb70344987502567bc50081c03e6192fb26",
+ "reference": "d4ca2fb70344987502567bc50081c03e6192fb26",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6",
+ "sebastian/recursion-context": "~1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Traverses array structures and object graphs to enumerate all referenced objects",
+ "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
+ "time": "2016-01-28 13:25:10"
+ },
+ {
+ "name": "sebastian/recursion-context",
+ "version": "1.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/recursion-context.git",
+ "reference": "913401df809e99e4f47b27cdd781f4a258d58791"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791",
+ "reference": "913401df809e99e4f47b27cdd781f4a258d58791",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ }
+ ],
+ "description": "Provides functionality to recursively process PHP variables",
+ "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
+ "time": "2015-11-11 19:50:13"
+ },
+ {
+ "name": "sebastian/resource-operations",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/resource-operations.git",
+ "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52",
+ "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides a list of PHP built-in functions that operate on resources",
+ "homepage": "https://www.github.com/sebastianbergmann/resource-operations",
+ "time": "2015-07-28 20:34:47"
+ },
+ {
+ "name": "sebastian/version",
+ "version": "2.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/version.git",
+ "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019",
+ "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+ "homepage": "https://github.com/sebastianbergmann/version",
+ "time": "2016-10-03 07:35:21"
+ },
+ {
+ "name": "symfony/console",
+ "version": "v3.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/console.git",
+ "reference": "d12aa9ca20f4db83ec58410978dab6afcb9d6aaa"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/console/zipball/d12aa9ca20f4db83ec58410978dab6afcb9d6aaa",
+ "reference": "d12aa9ca20f4db83ec58410978dab6afcb9d6aaa",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.9",
+ "symfony/debug": "~2.8|~3.0",
+ "symfony/polyfill-mbstring": "~1.0"
+ },
+ "require-dev": {
+ "psr/log": "~1.0",
+ "symfony/event-dispatcher": "~2.8|~3.0",
+ "symfony/filesystem": "~2.8|~3.0",
+ "symfony/process": "~2.8|~3.0"
+ },
+ "suggest": {
+ "psr/log": "For using the console logger",
+ "symfony/event-dispatcher": "",
+ "symfony/filesystem": "",
+ "symfony/process": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.2-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Console\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Console Component",
+ "homepage": "https://symfony.com",
+ "time": "2016-12-11 14:34:22"
+ },
+ {
+ "name": "symfony/debug",
+ "version": "v3.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/debug.git",
+ "reference": "9f923e68d524a3095c5a2ae5fc7220c7cbc12231"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/debug/zipball/9f923e68d524a3095c5a2ae5fc7220c7cbc12231",
+ "reference": "9f923e68d524a3095c5a2ae5fc7220c7cbc12231",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.9",
+ "psr/log": "~1.0"
+ },
+ "conflict": {
+ "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2"
+ },
+ "require-dev": {
+ "symfony/class-loader": "~2.8|~3.0",
+ "symfony/http-kernel": "~2.8|~3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.2-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Debug\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Debug Component",
+ "homepage": "https://symfony.com",
+ "time": "2016-11-16 22:18:16"
+ },
+ {
+ "name": "symfony/polyfill-mbstring",
+ "version": "v1.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-mbstring.git",
+ "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/e79d363049d1c2128f133a2667e4f4190904f7f4",
+ "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "suggest": {
+ "ext-mbstring": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.3-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Mbstring\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for the Mbstring extension",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "mbstring",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "time": "2016-11-14 01:06:16"
+ },
+ {
+ "name": "symfony/process",
+ "version": "v3.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/process.git",
+ "reference": "02ea84847aad71be7e32056408bb19f3a616cdd3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/process/zipball/02ea84847aad71be7e32056408bb19f3a616cdd3",
+ "reference": "02ea84847aad71be7e32056408bb19f3a616cdd3",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.9"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.2-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Process\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Process Component",
+ "homepage": "https://symfony.com",
+ "time": "2016-11-24 10:40:28"
+ },
+ {
+ "name": "symfony/yaml",
+ "version": "v3.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/yaml.git",
+ "reference": "a7095af4b97a0955f85c8989106c249fa649011f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/a7095af4b97a0955f85c8989106c249fa649011f",
+ "reference": "a7095af4b97a0955f85c8989106c249fa649011f",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.9"
+ },
+ "require-dev": {
+ "symfony/console": "~2.8|~3.0"
+ },
+ "suggest": {
+ "symfony/console": "For validating YAML files using the lint command"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.2-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Yaml\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Yaml Component",
+ "homepage": "https://symfony.com",
+ "time": "2016-12-10 10:07:06"
+ },
+ {
+ "name": "webmozart/assert",
+ "version": "1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/webmozart/assert.git",
+ "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/webmozart/assert/zipball/2db61e59ff05fe5126d152bd0655c9ea113e550f",
+ "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3.3 || ^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.6",
+ "sebastian/version": "^1.0.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.3-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Webmozart\\Assert\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ }
+ ],
+ "description": "Assertions to validate method input/output with nice error messages.",
+ "keywords": [
+ "assert",
+ "check",
+ "validate"
+ ],
+ "time": "2016-11-23 20:04:58"
+ }
+ ],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": {
+ "simonpioli/sortelements": 20,
+ "rsms/js-lru": 20
+ },
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {
+ "ext-gd": "*",
+ "ext-xml": "*",
+ "ext-intl": "*",
+ "ext-json": "*",
+ "ext-pdo_sqlite": "*",
+ "ext-mbstring": "*"
+ },
+ "platform-dev": []
+}
diff --git a/sources/config.php b/sources/config.php
new file mode 100644
index 0000000..b1e487a
--- /dev/null
+++ b/sources/config.php
@@ -0,0 +1,36 @@
+
+ */
+
+require_once dirname(__FILE__) . '/vendor/autoload.php';
+require dirname(__FILE__) . '/config_default.php';
+if (file_exists(dirname(__FILE__) . '/config_local.php') && (php_sapi_name() !== 'cli')) {
+ require dirname(__FILE__) . '/config_local.php';
+}
+
+$remote_user = array_key_exists('PHP_AUTH_USER', $_SERVER) ? $_SERVER['PHP_AUTH_USER'] : '';
+// Clean username, only allow a-z, A-Z, 0-9, -_ chars
+$remote_user = preg_replace( '/[^a-zA-Z0-9_-]/', '', $remote_user);
+$user_config_file = 'config_local.' . $remote_user . '.php';
+if (file_exists(dirname(__FILE__) . '/' . $user_config_file) && (php_sapi_name() !== 'cli')) {
+ require_once dirname(__FILE__) . '/' . $user_config_file;
+}
+
+if(!is_null($config['cops_basic_authentication']) &&
+ is_array($config['cops_basic_authentication']))
+{
+ if (!isset($_SERVER['PHP_AUTH_USER']) ||
+ (isset($_SERVER['PHP_AUTH_USER']) &&
+ ($_SERVER['PHP_AUTH_USER']!=$config['cops_basic_authentication']['username'] ||
+ $_SERVER['PHP_AUTH_PW'] != $config['cops_basic_authentication']['password'])))
+ {
+ header('WWW-Authenticate: Basic realm="COPS Authentication"');
+ header('HTTP/1.0 401 Unauthorized');
+ echo 'This site is password protected';
+ exit;
+ }
+}
diff --git a/sources/config_default.php b/sources/config_default.php
new file mode 100644
index 0000000..c1d8418
--- /dev/null
+++ b/sources/config_default.php
@@ -0,0 +1,329 @@
+
+ */
+
+ if (!isset($config)) {
+ $config = array();
+ }
+
+ /*
+ * The directory containing calibre's metadata.db file, with sub-directories
+ * containing all the formats.
+ * BEWARE : it has to end with a /
+ * You can enable multiple database with this notation instead of a simple string :
+ * $config['calibre_directory'] = array ("My database name" => "/home/directory/calibre1/", "My other database name" => "/home/directory/calibre2/");
+ */
+ $config['calibre_directory'] = './';
+
+ /*
+ * SPECIFIC TO NGINX
+ * The internal directory set in nginx config file
+ * Leave empty if you don't know what you're doing
+ */
+ $config['calibre_internal_directory'] = '';
+
+ /*
+ * Full URL prefix (with trailing /)
+ * useful especially for Opensearch where a full URL is often required
+ * For example Mantano, Aldiko and Marvin require it.
+ */
+ $config['cops_full_url'] = '';
+
+ /*
+ * Number of recent books to show
+ */
+ $config['cops_recentbooks_limit'] = '50';
+
+ /*
+ * Catalog's author name
+ */
+ $config['cops_author_name'] = 'Sébastien Lucas';
+
+ /*
+ * Catalog's author uri
+ */
+ $config['cops_author_uri'] = 'http://blog.slucas.fr';
+
+ /*
+ * Catalog's author email
+ */
+ $config['cops_author_email'] = 'sebastien@slucas.fr';
+
+ /*
+ * Catalog's title
+ */
+ $config['cops_title_default'] = 'COPS';
+
+ /*
+ * Catalog's subtitle
+ */
+ $config['cops_subtitle_default'] = '';
+
+ /*
+ * Wich header to use when downloading books outside the web directory
+ * Possible values are :
+ * X-Accel-Redirect : For Nginx
+ * X-Sendfile : For Lightttpd or Apache (with mod_xsendfile)
+ * No value (default) : Let PHP handle the download
+ */
+ $config['cops_x_accel_redirect'] = '';
+
+ /*
+ * Height of thumbnail image for OPDS
+ */
+ $config['cops_opds_thumbnail_height'] = '164';
+
+ /*
+ * Height of thumbnail image for HTML
+ */
+ $config['cops_html_thumbnail_height'] = '164';
+
+ /*
+ * Icon for both OPDS and HTML catalog
+ * Note that this has to be a real icon (.ico)
+ */
+ $config['cops_icon'] = 'favicon.ico';
+
+ /*
+ * Show icon for authors, series, tags and books on OPDS feed
+ * 1 : enable
+ * 0 : disable
+ */
+ $config['cops_show_icons'] = '1';
+
+ /*
+ * Default timezone
+ * Check following link for other timezones :
+ * http://www.php.net/manual/en/timezones.php
+ */
+ $config['default_timezone'] = 'Europe/Paris';
+
+ /*
+ * Prefered format for HTML catalog
+ * The two first will be displayed in book entries
+ * The other only appear in book detail
+ */
+ $config['cops_prefered_format'] = array('EPUB', 'PDF', 'AZW3', 'AZW', 'MOBI', 'CBR', 'CBZ');
+
+ /*
+ * use URL rewriting for downloading of ebook in HTML catalog
+ * See Github wiki for more information
+ * 1 : enable
+ * 0 : disable
+ */
+ $config['cops_use_url_rewriting'] = '0';
+
+ /*
+ * generate a invalid OPDS stream to allow bad OPDS client to use search
+ * Example of non compliant OPDS client : Moon+ Reader
+ * Example of good OPDS client : Mantano, FBReader
+ * 1 : enable support for non compliant OPDS client
+ * 0 : always generate valid OPDS code
+ */
+ $config['cops_generate_invalid_opds_stream'] = '0';
+
+ /*
+ * Max number of items per page
+ * -1 unlimited
+ */
+ $config['cops_max_item_per_page'] = '-1';
+
+ /*
+ * split authors by first letter
+ * 1 : Yes
+ * 0 : No
+ */
+ $config['cops_author_split_first_letter'] = '1';
+
+ /*
+ * split titles by first letter
+ * 1 : Yes
+ * 0 : No
+ */
+ $config['cops_titles_split_first_letter'] = '1';
+
+ /*
+ * Enable the Lightboxes (for popups)
+ * 1 : Yes (enable)
+ * 0 : No
+ */
+ $config['cops_use_fancyapps'] = '1';
+
+ /*
+ * Update Epub metadata before download
+ * 1 : Yes (enable)
+ * 0 : No
+ */
+ $config['cops_update_epub-metadata'] = '0';
+
+ /*
+ * Filter on tags to book list
+ * Only works with the OPDS catalog
+ * Usage : array ("I only want to see books using the tag : Tag1" => "Tag1",
+ * "I only want to see books not using the tag : Tag1" => "!Tag1",
+ * "I want to see every books" => "",
+ *
+ * Example : array ("All" => "", "Unread" => "!Read", "Read" => "Read")
+ */
+ $config['cops_books_filter'] = array();
+
+ /*
+ * Custom Columns for the index page
+ * to add as an array containing the lookup names configured in Calibre
+ *
+ * For example : array ("genre", "mycolumn");
+ *
+ * Note that the composite custom columns are not supported
+ */
+ $config['cops_calibre_custom_column'] = array();
+
+ /*
+ * Custom Columns for the list representation
+ * to add as an array containing the lookup names configured in Calibre
+ *
+ * For example : array ("genre", "mycolumn");
+ *
+ * Note that the composite custom columns are not supported
+ */
+ $config['cops_calibre_custom_column_list'] = array ();
+
+ /*
+ * Custom Columns for the book preview panel
+ * to add as an array containing the lookup names configured in Calibre
+ *
+ * For example : array ("genre", "mycolumn");
+ *
+ * Note that the composite custom columns are not supported
+ */
+ $config['cops_calibre_custom_column_preview'] = array ();
+
+ /*
+ * Rename .epub to .kepub.epub if downloaded from a Kobo eReader
+ * The ebook will then be recognized a Kepub so with chaptered paging, statistics, ...
+ * You have to enable URL rewriting if you want to enable kepup.epub download
+ * 1 : Yes (enable)
+ * 0 : No
+ */
+ $config['cops_provide_kepub'] = '0';
+
+ /*
+ * Enable and configure Send To Kindle (or Email) feature.
+ *
+ * Don't forget to authorize the sender email you configured in your Kindle's Approved Personal Document E-mail List.
+ *
+ * If you want to use a simple smtp server (provided by your ISP for example), you can configure it like that :
+ * $config['cops_mail_configuration'] = array( "smtp.host" => "smtp.free.fr",
+ * "smtp.username" => "",
+ * "smtp.password" => "",
+ * "smtp.secure" => "",
+ * "address.from" => "cops@slucas.fr"
+ * );
+ *
+ * For Gmail (ssl is mandatory) :
+ * $config['cops_mail_configuration'] = array( "smtp.host" => "smtp.gmail.com",
+ * "smtp.username" => "YOUR GMAIL ADRESS",
+ * "smtp.password" => "YOUR GMAIL PASSWORD",
+ * "smtp.secure" => "ssl",
+ * "address.from" => "cops@slucas.fr"
+ * );
+ */
+ $config['cops_mail_configuration'] = NULL;
+
+ /*
+ * Use filter in HTML catalog
+ * 1 : Yes (enable)
+ * 0 : No
+ */
+ $config['cops_html_tag_filter'] = '0';
+
+ /*
+ * Thumbnails are generated on-the-fly so it can be problematic on servers with slow CPU (Raspberry Pi, Dockstar, Piratebox, ...).
+ * This configuration item allow to customize how thumbnail will be generated
+ * "" : Generate thumbnail (CPU hungry)
+ * "1" : always send the full size image (Network hungry)
+ * any url : Send a constant image as the thumbnail (you can try "images/bookcover.png")
+ */
+ $config['cops_thumbnail_handling'] = '';
+
+ /*
+ * Directory to keep resized thumbnails: allow to resize thumbnails only on first access, then use this cache.
+ * $config['cops_thumbnail_handling'] must be ""
+ * "" : don't cache thumbnail
+ * "/tmp/cache/" (example) : will generate thumbnails in /tmp/cache/
+ * BEWARE : it has to end with a /
+ */
+ $config['cops_thumbnail_cache_directory'] = '';
+
+ /*
+ * Contains a list of user agent for browsers not compatible with client side rendering
+ * For now : Kindle, Sony PRS-T1, Sony PRS-T2, All Cybook devices (maybe a little extreme).
+ * This item is used as regular expression so "." will force server side rendering for all devices
+ */
+ $config['cops_server_side_render'] = 'Kindle\/1\.0|Kindle\/2\.0|Kindle\/3\.0|EBRD1101|EBRD1201|cybook';
+
+ /*
+ * Specify the ignored categories for the home screen and with search
+ * Meaning that if you don't want to search in publishers or tags just add them from the list
+ * Only accepted values :
+ * - author
+ * - book
+ * - series
+ * - tag
+ * - publisher
+ * - rating
+ * - language
+ */
+ $config ['cops_ignored_categories'] = array();
+
+ /*
+ * If you use a Sony eReader or Aldiko you can't download ebooks if your catalog
+ * is password protected. A simple workaround is to leave fetch.php not protected (see .htaccess).
+ * But In that case your COPS installation is not completely safe.
+ * Setting this parameter to "1" ensure that nobody can access fetch.php before accessing
+ * index.php or feed.php first.
+ * BEWARE : Do not touch this if you're not using password, not using PRS-TX or not using Aldiko.
+ */
+ $config ['cops_fetch_protect'] = '0';
+
+ /*
+ * WARNING NOT READY FOR PRODUCTION USE
+ * Make the search better (don't care about diacritics, uppercase should work on Cyrillic) but slower.
+ * 1 : Yes (enable)
+ * 0 : No
+ */
+ $config ['cops_normalized_search'] = '0';
+
+ /*
+ * Enable PHP password protection (You can use if htpasswd is not possible for you)
+ * If possible prefer htpasswd !
+ * array( "username" => "xxx", "password" => "secret") : Enable PHP password protection
+ * NULL : Disable PHP password protection (You can still use htpasswd)
+ */
+ $config['cops_basic_authentication'] = NULL;
+
+ /*
+ * Which template is used by default :
+ * 'default'
+ * 'bootstrap'
+ */
+ $config['cops_template'] = 'default';
+
+ /*
+ * Which style is used by default :
+ * 'base'
+ * 'default'
+ * 'eink' (only available for the 'default' template)
+ * 'iphone' (only available for the 'default' template)
+ * 'iphone7' (only available for the 'default' template)
+ */
+ $config['cops_style'] = 'default';
+
+ /*
+ * Set language code to force a language (see lang/ directory for available languages).
+ * When empty it will auto detect the language.
+ */
+ $config['cops_language'] = '';
diff --git a/sources/config_local.php.example b/sources/config_local.php.example
new file mode 100644
index 0000000..78a9e54
--- /dev/null
+++ b/sources/config_local.php.example
@@ -0,0 +1,30 @@
+
+ */
+
+require 'config.php';
+require 'base.php';
+
+function getComponentContent($book, $component, $add)
+{
+ $data = $book->component($component);
+
+ $callback = function($m) use ($book, $component, $add) {
+ $method = $m[1];
+ $path = $m[2];
+ $end = '';
+ if (preg_match('/^src\s*:/', $method)) {
+ $end = ')';
+ }
+ if (preg_match('/^#/', $path)) {
+ return $method . "'" . $path . "'" . $end;
+ }
+ $hash = '';
+ if (preg_match('/^(.+)#(.+)$/', $path, $matches)) {
+ $path = $matches[1];
+ $hash = '#' . $matches[2];
+ }
+ $comp = $book->getComponentName($component, $path);
+ if (!$comp) {
+ return $method . "'#'" . $end;
+ }
+ $out = $method . "'epubfs.php?" . $add . 'comp=' . $comp . $hash . "'" . $end;
+ if ($end) {
+ return $out;
+ }
+ return str_replace('&', '&', $out);
+ };
+
+ $data = preg_replace_callback("/(src=)[\"']([^:]*?)[\"']/", $callback, $data);
+ $data = preg_replace_callback("/(href=)[\"']([^:]*?)[\"']/", $callback, $data);
+ $data = preg_replace_callback("/(\@import\s+)[\"'](.*?)[\"'];/", $callback, $data);
+ $data = preg_replace_callback('/(src\s*:\s*url\()(.*?)\)/', $callback, $data);
+
+ return $data;
+}
+
+if (php_sapi_name() === 'cli') {
+ return;
+}
+
+$idData = getURLParam('data', NULL);
+$add = 'data=' . $idData . '&';
+if (!is_null(GetUrlParam(DB))) {
+ $add .= DB . '=' . GetUrlParam(DB) . '&';
+}
+$myBook = Book::getBookByDataId($idData);
+
+$book = new EPub($myBook->getFilePath('EPUB', $idData));
+
+$book->initSpineComponent();
+
+if (!isset($_GET['comp'])) {
+ notFound();
+ return;
+}
+
+$component = $_GET['comp'];
+
+try {
+ $data = getComponentContent($book, $component, $add);
+
+ $expires = 60*60*24*14;
+ header('Pragma: public');
+ header('Cache-Control: maxage='.$expires);
+ header('Expires: ' . gmdate('D, d M Y H:i:s', time()+$expires) . ' GMT');
+ header ('Content-Type: ' . $book->componentContentType($component));
+ echo $data;
+} catch (Exception $e) {
+ error_log($e);
+ notFound();
+}
diff --git a/sources/epubreader.php b/sources/epubreader.php
new file mode 100644
index 0000000..f2867e8
--- /dev/null
+++ b/sources/epubreader.php
@@ -0,0 +1,74 @@
+
+
+
+ */
+
+require_once 'config.php';
+require_once 'base.php';
+
+header('Content-Type: text/html;charset=utf-8');
+
+$idData = getURLParam('data', NULL);
+$add = 'data=' . $idData . '&';
+if (!is_null (GetUrlParam (DB))) {
+ $add .= DB . '=' . GetUrlParam (DB) . '&';
+}
+$myBook = Book::getBookByDataId($idData);
+
+$book = new EPub($myBook->getFilePath('EPUB', $idData));
+$book->initSpineComponent();
+
+?>
+
+
+
+
+ COPS's Epub Reader
+
+
+ " media="screen" />
+ " media="screen" />
+
+
+ " media="screen" />
+
+
+
+
+
+
diff --git a/sources/favicon.ico b/sources/favicon.ico
new file mode 100644
index 0000000..9be986b
Binary files /dev/null and b/sources/favicon.ico differ
diff --git a/sources/feed.php b/sources/feed.php
new file mode 100644
index 0000000..1b053e1
--- /dev/null
+++ b/sources/feed.php
@@ -0,0 +1,40 @@
+
+ *
+ */
+
+ require_once 'config.php';
+ require_once 'base.php';
+
+ header('Content-Type:application/xml');
+ $page = getURLParam('page', Base::PAGE_INDEX);
+ $query = getURLParam('query');
+ $n = getURLParam('n', '1');
+ if ($query) {
+ $page = Base::PAGE_OPENSEARCH_QUERY;
+ }
+ $qid = getURLParam('id');
+
+ if ($config ['cops_fetch_protect'] == '1') {
+ session_start();
+ if (!isset($_SESSION['connected'])) {
+ $_SESSION['connected'] = 0;
+ }
+ }
+
+ $OPDSRender = new OPDSRenderer();
+
+ switch ($page) {
+ case Base::PAGE_OPENSEARCH :
+ echo $OPDSRender->getOpenSearch();
+ return;
+ default:
+ $currentPage = Page::getPage($page, $qid, $query, $n);
+ $currentPage->InitializeContent();
+ echo $OPDSRender->render($currentPage);
+ return;
+ }
diff --git a/sources/fetch.php b/sources/fetch.php
new file mode 100644
index 0000000..ebce1e7
--- /dev/null
+++ b/sources/fetch.php
@@ -0,0 +1,122 @@
+
+ */
+
+ require_once dirname(__FILE__) . '/config.php';
+ require_once dirname(__FILE__) . '/base.php';
+
+ global $config;
+
+ if ($config['cops_fetch_protect'] == '1') {
+ session_start();
+ if (!isset($_SESSION['connected'])) {
+ notFound();
+ return;
+ }
+ }
+
+ $expires = 60*60*24*14;
+ header('Pragma: public');
+ header('Cache-Control: maxage=' . $expires);
+ header('Expires: ' . gmdate('D, d M Y H:i:s', time()+$expires) . ' GMT');
+ $bookId = getURLParam('id', NULL);
+ $type = getURLParam('type', 'jpg');
+ $idData = getURLParam('data', NULL);
+ if (is_null($bookId)) {
+ $book = Book::getBookByDataId($idData);
+ } else {
+ $book = Book::getBookById($bookId);
+ }
+
+ if (!$book) {
+ notFound ();
+ return;
+ }
+
+ if ($book && ($type == 'jpg' || empty ($config['calibre_internal_directory']))) {
+ if ($type == 'jpg') {
+ $file = $book->getFilePath($type);
+ } else {
+ $file = $book->getFilePath($type, $idData);
+ }
+ if (is_null($file) || !file_exists($file)) {
+ notFound();
+ return;
+ }
+ }
+
+ switch ($type)
+ {
+ case 'jpg':
+ header('Content-Type: image/jpeg');
+ //by default, we don't cache
+ $thumbnailCacheFullpath = null;
+ if ( isset($config['cops_thumbnail_cache_directory']) && $config['cops_thumbnail_cache_directory'] !== '' ) {
+ $thumbnailCacheFullpath = $config['cops_thumbnail_cache_directory'];
+ //if multiple databases, add a subfolder with the database ID
+ $thumbnailCacheFullpath .= !is_null(GetUrlParam (DB)) ? 'db-' . GetUrlParam (DB) . DIRECTORY_SEPARATOR : '';
+ //when there are lots of thumbnails, it's better to save files in subfolders, so if the book's uuid is
+ //"01234567-89ab-cdef-0123-456789abcdef", we will save the thumbnail in .../0/12/34567-89ab-cdef-0123-456789abcdef-...
+ $thumbnailCacheFullpath .= substr($book->uuid, 0, 1) . DIRECTORY_SEPARATOR . substr($book->uuid, 1, 2) . DIRECTORY_SEPARATOR;
+ //check if cache folder exists or create it
+ if ( file_exists($thumbnailCacheFullpath) || mkdir($thumbnailCacheFullpath, 0700, true) ) {
+ //we name the thumbnail from the book's uuid and it's dimensions (width and/or height)
+ $thumbnailCacheName = substr($book->uuid, 3) . '-' . getURLParam('width') . 'x' . getURLParam('height') . '.jpg';
+ $thumbnailCacheFullpath = $thumbnailCacheFullpath . $thumbnailCacheName;
+ } else {
+ //error creating the folder, so we don't cache
+ $thumbnailCacheFullpath = null;
+ }
+ }
+
+ if ( $thumbnailCacheFullpath !== null && file_exists($thumbnailCacheFullpath) ) {
+ //return the already cached thumbnail
+ readfile( $thumbnailCacheFullpath );
+ return;
+ }
+
+ if ($book->getThumbnail (getURLParam('width'), getURLParam('height'), $thumbnailCacheFullpath)) {
+ //if we don't cache the thumbnail, imagejpeg() in $book->getThumbnail() already return the image data
+ if ( $thumbnailCacheFullpath === null ) {
+ // The cover had to be resized
+ return;
+ } else {
+ //return the just cached thumbnail
+ readfile( $thumbnailCacheFullpath );
+ return;
+ }
+ }
+ break;
+ default:
+ $data = $book->getDataById($idData);
+ header('Content-Type: ' . $data->getMimeType());
+ break;
+ }
+ $file = $book->getFilePath($type, $idData, true);
+ if ($type == 'epub' && $config['cops_update_epub-metadata']) {
+ $book->getUpdatedEpub($idData);
+ return;
+ }
+ if ($type == 'jpg') {
+ header('Content-Disposition: filename="' . basename($file) . '"');
+ } else {
+ header('Content-Disposition: attachment; filename="' . basename($file) . '"');
+ }
+
+ $dir = $config['calibre_internal_directory'];
+ if (empty($config['calibre_internal_directory'])) {
+ $dir = Base::getDbDirectory();
+ }
+
+ if (empty($config['cops_x_accel_redirect'])) {
+ $filename = $dir . $file;
+ $fp = fopen($filename, 'rb');
+ header('Content-Length: ' . filesize($filename));
+ fpassthru($fp);
+ } else {
+ header($config['cops_x_accel_redirect'] . ': ' . $dir . $file);
+ }
diff --git a/sources/getJSON.php b/sources/getJSON.php
new file mode 100644
index 0000000..05bf095
--- /dev/null
+++ b/sources/getJSON.php
@@ -0,0 +1,15 @@
+
+ *
+ */
+
+require_once('config.php');
+
+header('Content-Type:application/json;charset=utf-8');
+
+echo json_encode(JSONRenderer::getJson());
+
diff --git a/sources/images/allbook.png b/sources/images/allbook.png
new file mode 100644
index 0000000..6803171
Binary files /dev/null and b/sources/images/allbook.png differ
diff --git a/sources/images/author.png b/sources/images/author.png
new file mode 100644
index 0000000..5c09d1a
Binary files /dev/null and b/sources/images/author.png differ
diff --git a/sources/images/bookcover.png b/sources/images/bookcover.png
new file mode 100644
index 0000000..6c3c8c7
Binary files /dev/null and b/sources/images/bookcover.png differ
diff --git a/sources/images/custom.png b/sources/images/custom.png
new file mode 100644
index 0000000..86020c3
Binary files /dev/null and b/sources/images/custom.png differ
diff --git a/sources/images/icons/icon114.png b/sources/images/icons/icon114.png
new file mode 100644
index 0000000..559a4c6
Binary files /dev/null and b/sources/images/icons/icon114.png differ
diff --git a/sources/images/icons/icon144.png b/sources/images/icons/icon144.png
new file mode 100644
index 0000000..9bbcdcd
Binary files /dev/null and b/sources/images/icons/icon144.png differ
diff --git a/sources/images/icons/icon57.png b/sources/images/icons/icon57.png
new file mode 100644
index 0000000..5dd81f3
Binary files /dev/null and b/sources/images/icons/icon57.png differ
diff --git a/sources/images/icons/icon72.png b/sources/images/icons/icon72.png
new file mode 100644
index 0000000..87eab9a
Binary files /dev/null and b/sources/images/icons/icon72.png differ
diff --git a/sources/images/language.png b/sources/images/language.png
new file mode 100644
index 0000000..5b18b4f
Binary files /dev/null and b/sources/images/language.png differ
diff --git a/sources/images/publisher.png b/sources/images/publisher.png
new file mode 100644
index 0000000..f34b24f
Binary files /dev/null and b/sources/images/publisher.png differ
diff --git a/sources/images/rating.png b/sources/images/rating.png
new file mode 100644
index 0000000..68c53c5
Binary files /dev/null and b/sources/images/rating.png differ
diff --git a/sources/images/recent.png b/sources/images/recent.png
new file mode 100644
index 0000000..e792a20
Binary files /dev/null and b/sources/images/recent.png differ
diff --git a/sources/images/serie.png b/sources/images/serie.png
new file mode 100644
index 0000000..fcf4140
Binary files /dev/null and b/sources/images/serie.png differ
diff --git a/sources/images/tag.png b/sources/images/tag.png
new file mode 100644
index 0000000..746d236
Binary files /dev/null and b/sources/images/tag.png differ
diff --git a/sources/index.php b/sources/index.php
new file mode 100644
index 0000000..9130d64
--- /dev/null
+++ b/sources/index.php
@@ -0,0 +1,65 @@
+
+ *
+ */
+
+ require_once dirname(__FILE__) . '/config.php';
+ require_once dirname(__FILE__) . '/base.php';
+
+ // If we detect that an OPDS reader try to connect try to redirect to feed.php
+ if (preg_match('/(MantanoReader|FBReader|Stanza|Marvin|Aldiko|Moon\+ Reader|Chunky|AlReader|EBookDroid|BookReader|CoolReader|PageTurner|books\.ebook\.pdf\.reader|com\.hiwapps\.ebookreader|OpenBook)/', $_SERVER['HTTP_USER_AGENT'])) {
+ header('location: feed.php');
+ exit();
+ }
+
+ $page = getURLParam('page', Base::PAGE_INDEX);
+ $query = getURLParam('query');
+ $qid = getURLParam('id');
+ $n = getURLParam('n', '1');
+ $database = GetUrlParam(DB);
+
+
+ // Access the database ASAP to be sure it's readable, redirect if that's not the case.
+ // It has to be done before any header is sent.
+ Base::checkDatabaseAvailability();
+
+ if ($config ['cops_fetch_protect'] == '1') {
+ session_start();
+ if (!isset($_SESSION['connected'])) {
+ $_SESSION['connected'] = 0;
+ }
+ }
+
+ header('Content-Type:text/html;charset=utf-8');
+
+ $data = array('title' => $config['cops_title_default'],
+ 'version' => VERSION,
+ 'opds_url' => $config['cops_full_url'] . 'feed.php',
+ 'customHeader' => '',
+ 'template' => getCurrentTemplate(),
+ 'server_side_rendering' => useServerSideRendering(),
+ 'current_css' => getCurrentCss(),
+ 'favico' => $config['cops_icon'],
+ 'getjson_url' => 'getJSON.php?' . addURLParameter(getQueryString(), 'complete', 1));
+ if (preg_match('/Kindle/', $_SERVER['HTTP_USER_AGENT'])) {
+ $data['customHeader'] = '';
+ }
+ $headcontent = file_get_contents('templates/' . getCurrentTemplate() . '/file.html');
+ $template = new doT();
+ $dot = $template->template($headcontent, NULL);
+ echo($dot($data));
+?>
+
+
+
diff --git a/sources/lang/Localization_ca.json b/sources/lang/Localization_ca.json
new file mode 100644
index 0000000..7ed234c
--- /dev/null
+++ b/sources/lang/Localization_ca.json
@@ -0,0 +1,293 @@
+{
+ "about.title": "Sobre COPS",
+ "allbooks.alphabetical.many": "Llistat alfabètic de {0} llibres",
+ "allbooks.alphabetical.none": "Llistat sense llibres",
+ "allbooks.alphabetical.one": "Llistat amb un llibre",
+ "allbooks.title": "Tots els llibres",
+ "authors.alphabetical.many": "Llistat alfabètic de {0} autors",
+ "authors.alphabetical.none": "Llistat sense autors",
+ "authors.alphabetical.one": "Llistat amb un autor",
+ "authors.title": "Autors",
+ "authorword.many": "{0} autors",
+ "authorword.none": "Cap autor",
+ "authorword.one": "1 autor",
+ "bookentry.author": "{0} de {1}",
+ "bookword.many": "{0} llibres",
+ "bookword.none": "Cap llibre",
+ "bookword.one": "1 llibre",
+ "bookword.title": "Llibres",
+ "cog.alternate": "Buscar, ordenar i filtres",
+ "content.series": "Series:",
+ "content.series.data": "Llibre {0} de la sèrie {1}",
+ "content.summary": "Sinopsi:",
+ "customcolumn.boolean.no": "No",
+ "customcolumn.boolean.unknown": "No Establert",
+ "customcolumn.boolean.yes": "Si",
+ "customcolumn.date.format": "Y-m-d",
+ "customcolumn.date.unknown": "No Establert",
+ "customcolumn.description": "Custom column '{0}'",
+ "customcolumn.description.bool": "Index of a boolean value",
+ "customcolumn.description.enum.many": "Alphabetical index of the {0} values",
+ "customcolumn.description.enum.none": "Alphabetical index of absolutely no values",
+ "customcolumn.description.enum.one": "Alphabetical index of one value",
+ "customcolumn.description.rating": "Index of ratings",
+ "customcolumn.description.series.many": "{0} sèries ordenades alfabèticament",
+ "customcolumn.description.series.none": "Llistat sense sèries",
+ "customcolumn.description.series.one": "Llistat alfabètic d'una sola sèrie",
+ "customcolumn.enum.unknown": "No Establert",
+ "customcolumn.float.unknown": "No Establert",
+ "customcolumn.int.unknown": "No Establert",
+ "customcolumn.rating.unknown": "No Establert",
+ "customcolumn.stars.many": "{0} Stars",
+ "customcolumn.stars.none": "No Stars",
+ "customcolumn.stars.one": "1 Star",
+ "customize.email": "Introdueix el teu email (permitir enviament de llibres per e-mail)",
+ "customize.fancybox": "Usar Lightbox (llibres en marc flotant)",
+ "customize.filter": "Habilitar filtre per etiqueta",
+ "customize.ignored": "Ocultar categories",
+ "customize.paging": "Nombre màx de llibres per pàgina (-1 per deshabilitar)",
+ "customize.style": "Tema",
+ "customize.title": "Configura COPS UI",
+ "home.alternate": "Inici",
+ "i18n.coversection": "Portada",
+ "language.title": "Idioma",
+ "languages.alphabetical.many": "Llistat alfabètic de {0} llenguatges",
+ "languages.alphabetical.none": "Llistat sense idiomes disponibles",
+ "languages.alphabetical.one": "llistat amb un idioma",
+ "languages.title": "Idiomes",
+ "mail.messagenotsent": "El missatge no por ser enviat.",
+ "mail.messagesent": "Missatge enviat",
+ "paging.next.alternate": "Següent",
+ "paging.previous.alternate": "Anterior",
+ "permalink.alternate": "Enllaç permanent",
+ "pubdate.title": "Any publicació",
+ "publisher.name": "Publicador",
+ "publishers.alphabetical.many": "Llistat alfabètic de {0} editorials",
+ "publishers.alphabetical.none": "Llistat sense editorials",
+ "publishers.alphabetical.one": "Llistat amb una editorial",
+ "publishers.title": "Editorials",
+ "publisherword.many": "{0} editorials",
+ "publisherword.none": "Sense editorials",
+ "publisherword.one": "1 editorial",
+ "ratings.many": "{0} valoració",
+ "ratings.none": "sense valoracions",
+ "ratings.one": "1 valoració",
+ "ratings.title": "Valoracions",
+ "ratingword.many": "{0} estrelles",
+ "ratingword.none": "Sense estrella",
+ "ratingword.one": "1 estrella",
+ "recent.list": "{0} darrers títols incorporats",
+ "recent.title": "Els més recents",
+ "search.alternate": "Cerca",
+ "search.result": "Resultats de búsqueda per *{0}*",
+ "search.result.author": "Resultats de búsqueda per *{0}* per autors",
+ "search.result.book": "Resultats de búsqueda per *{0}* per llibres",
+ "search.result.publisher": "Resultats de búsqueda per *{0}* per editorials",
+ "search.result.series": "Resultats de búsqueda per *{0}* per sèries",
+ "search.result.tag": "Resultats de búsqueda per *{0}* per etiquetes",
+ "search.sortorder.asc": "Asc",
+ "search.sortorder.desc": "Desc",
+ "series.alphabetical.many": "{0} sèries ordenades alfabèticament",
+ "series.alphabetical.none": "Llistat sense sèries",
+ "series.alphabetical.one": "Llistat alfabètic d'una sola sèrie",
+ "series.title": "Sèries",
+ "seriesword.many": "sèries",
+ "seriesword.none": "Cap sèrie",
+ "seriesword.one": "1 sèrie",
+ "sort.alternate": "Ordenar",
+ "splitByLetter.book.other": "Altres llibres",
+ "splitByLetter.letter": "{0} ({1})",
+ "tags.alphabetical.many": "{0} etiquetes ordenades alfabèticament",
+ "tags.alphabetical.none": "Llistat sense etiquetes",
+ "tags.alphabetical.one": "Llistat alfabètic de la única etiqueta",
+ "tags.title": "Etiquetes",
+ "tagword.many": "etiquetes",
+ "tagword.none": "Cap etiqueta",
+ "tagword.one": "1 etiqueta",
+ "tagword.title": "Etiquetes",
+ "languages.abk": "Abkhaz",
+ "languages.aaf": "Afar",
+ "languages.afr": "Africà",
+ "languages.aka": "Akan",
+ "languages.sqi": "Albani",
+ "languages.amh": "Amharic",
+ "languages.ara": "Àrab",
+ "languages.arg": "Aragonese",
+ "languages.hye": "Armenian",
+ "languages.asm": "Assamese",
+ "languages.ava": "Avaric",
+ "languages.ave": "Avestan",
+ "languages.aym": "Aymara",
+ "languages.aze": "Azerbaijani",
+ "languages.bam": "Bambara",
+ "languages.bak": "Bashkir",
+ "languages.eus": "Basque",
+ "languages.bel": "Belarusian",
+ "languages.ben": "Bengali",
+ "languages.bih": "Bihari",
+ "languages.bis": "Bislama",
+ "languages.bos": "Bosnian",
+ "languages.bre": "Breton",
+ "languages.bul": "Bulgar",
+ "languages.mya": "Burmese",
+ "languages.cat": "Català",
+ "languages.cha": "Chamorro",
+ "languages.che": "Chechen",
+ "languages.nya": "Chichewa",
+ "languages.zho": "Chinès",
+ "languages.chv": "Chuvash",
+ "languages.cor": "Cornish",
+ "languages.cos": "Corsican",
+ "languages.cre": "Cree",
+ "languages.hrv": "Croat",
+ "languages.ces": "Czech",
+ "languages.dan": "Danish",
+ "languages.div": "Divehi",
+ "languages.nld": "Holandès",
+ "languages.dzo": "Dzongkha",
+ "languages.eng": "Anglès",
+ "languages.epo": "Esperant",
+ "languages.est": "Estoni",
+ "languages.ewe": "Ewe",
+ "languages.fao": "Faroese",
+ "languages.fij": "Fijian",
+ "languages.fin": "Finnish",
+ "languages.fra": "Francès",
+ "languages.ful": "Fula",
+ "languages.glg": "Gallec",
+ "languages.kat": "Georgià",
+ "languages.deu": "Alemany",
+ "languages.ell": "Grec",
+ "languages.grn": "Guaraní",
+ "languages.guj": "Gujarati",
+ "languages.hat": "Haitià",
+ "languages.hau": "Hausa",
+ "languages.hed": "Hebreu",
+ "languages.her": "Herero",
+ "languages.hin": "Hindi",
+ "languages.hmo": "Hiri Motu",
+ "languages.hun": "Hungarià",
+ "languages.ina": "Interlingua",
+ "languages.ind": "Indonesian",
+ "languages.ile": "Interlingue",
+ "languages.gle": "Irish",
+ "languages.ibo": "Igbo",
+ "languages.ipk": "Inupiaq",
+ "languages.ido": "Ido",
+ "languages.isl": "Icelandic",
+ "languages.ita": "Italian",
+ "languages.iku": "Inuktitut",
+ "languages.jpn": "Japonès",
+ "languages.jav": "Javanese",
+ "languages.kal": "Kalaallisut",
+ "languages.kan": "Kannada",
+ "languages.kau": "Kanuri",
+ "languages.kas": "Kashmiri",
+ "languages.kaz": "Kazakh",
+ "languages.khm": "Khmer",
+ "languages.kik": "Kikuyu",
+ "languages.kin": "Kinyarwanda",
+ "languages.kir": "Kyrgyz",
+ "languages.kom": "Komi",
+ "languages.kon": "Kongo",
+ "languages.kor": "Coreà",
+ "languages.kur": "Kurdish",
+ "languages.kua": "Kwanyama",
+ "languages.lat": "Latí",
+ "languages.ltz": "Luxembourgish",
+ "languages.lug": "Ganda",
+ "languages.lim": "Limburgish",
+ "languages.lin": "Lingala",
+ "languages.lao": "Lao",
+ "languages.lit": "Lithuanian",
+ "languages.lub": "Luba-Katanga",
+ "languages.lav": "Latvian",
+ "languages.glv": "Manx",
+ "languages.mkd": "Macedonian",
+ "languages.mlg": "Malagasy",
+ "languages.msa": "Malay",
+ "languages.mal": "Malayalam",
+ "languages.mlt": "Maltese",
+ "languages.mri": "Maorí",
+ "languages.mar": "Marathi",
+ "languages.mah": "Marshallese",
+ "languages.mon": "Mongol",
+ "languages.nau": "Nauru",
+ "languages.nav": "Navajo",
+ "languages.nob": "Norwegian Bokmål",
+ "languages.nde": "North Ndebele",
+ "languages.nep": "Nepali",
+ "languages.ndo": "Ndonga",
+ "languages.nno": "Norwegian Nynorsk",
+ "languages.nor": "Norwegian",
+ "languages.iii": "Nuosu",
+ "languages.nbl": "South Ndebele",
+ "languages.oci": "Occità",
+ "languages.oji": "Ojibwe",
+ "languages.chu": "Old Church Slavonic",
+ "languages.orm": "Oromo",
+ "languages.ori": "Oriya",
+ "languages.oss": "Ossetian",
+ "languages.pan": "Panjabi",
+ "languages.pli": "Pāli",
+ "languages.fas": "Persa",
+ "languages.pol": "Polac",
+ "languages.pus": "Pashto",
+ "languages.por": "Portuguès",
+ "languages.que": "Quechua",
+ "languages.roh": "Romansh",
+ "languages.run": "Kirundi",
+ "languages.ron": "Romanià",
+ "languages.rus": "Rus",
+ "languages.san": "Sanskrit",
+ "languages.srd": "Sardinian",
+ "languages.snd": "Sindhi",
+ "languages.sme": "Northern Sami",
+ "languages.smo": "Samoan",
+ "languages.sag": "Sango",
+ "languages.srp": "Serbi",
+ "languages.gla": "Scottish Gaelic",
+ "languages.sna": "Shona",
+ "languages.sin": "Sinhala",
+ "languages.slk": "Slovak",
+ "languages.slv": "Slovene",
+ "languages.som": "Somali",
+ "languages.sot": "Southern Sotho",
+ "languages.spa": "Espanyol",
+ "languages.sun": "Sundanese",
+ "languages.swa": "Swahili",
+ "languages.ssw": "Swati",
+ "languages.swe": "Swedish",
+ "languages.tam": "Tamil",
+ "languages.tel": "Telugu",
+ "languages.tgk": "Tajik",
+ "languages.tha": "Thai",
+ "languages.tir": "Tigrinya",
+ "languages.bod": "Tibetan Standard",
+ "languages.tuk": "Turkmen",
+ "languages.tgl": "Tagalog",
+ "languages.tsn": "Tswana",
+ "languages.ton": "Tonga",
+ "languages.tur": "Turkish",
+ "languages.tso": "Tsonga",
+ "languages.tat": "Tatar",
+ "languages.twi": "Twi",
+ "languages.tah": "Tahitian",
+ "languages.uig": "Uighur",
+ "languages.ukr": "Ukrainian",
+ "languages.urd": "Urdu",
+ "languages.uzb": "Uzbek",
+ "languages.ven": "Venda",
+ "languages.vie": "Vietnamese",
+ "languages.vol": "Volapük",
+ "languages.win": "Walloon",
+ "languages.cym": "Welsh",
+ "languages.wol": "Wolof",
+ "languages.fry": "Western Frisian",
+ "languages.xho": "Xhosa",
+ "languages.yid": "Yiddish",
+ "languages.yor": "Yoruba",
+ "languages.zha": "Zhuang",
+ "languages.zul": "Zulu",
+ "DO_NOT_TRANSLATE": "end"
+}
diff --git a/sources/lang/Localization_cs.json b/sources/lang/Localization_cs.json
new file mode 100644
index 0000000..298652a
--- /dev/null
+++ b/sources/lang/Localization_cs.json
@@ -0,0 +1,293 @@
+{
+ "about.title": "Více o COPS",
+ "allbooks.alphabetical.many": "Abecední seznam {0} knih",
+ "allbooks.alphabetical.none": "Abecední seznam neobsahuje žádné knihy",
+ "allbooks.alphabetical.one": "Abecední seznam obsahuje jednu knihu",
+ "allbooks.title": "Všechny knihy",
+ "authors.alphabetical.many": "Abecední seznam {0} autorů",
+ "authors.alphabetical.none": "Abecední seznam neobsahuje žádné autory",
+ "authors.alphabetical.one": "Abecední seznam obsahuje jednoho autora",
+ "authors.title": "Autoři",
+ "authorword.many": "{0} autorů",
+ "authorword.none": "Žádný autor",
+ "authorword.one": "Jeden autor",
+ "bookentry.author": "{0} by {1}",
+ "bookword.many": "{0} knih",
+ "bookword.none": "Žádná kniha",
+ "bookword.one": "Jedna kniha",
+ "bookword.title": "Knihy",
+ "cog.alternate": "Hledání, třídění a filtry",
+ "content.series": "Série:",
+ "content.series.data": "Kniha {0} ze série {1}",
+ "content.summary": "Shrnutí",
+ "customcolumn.boolean.no": "Ne",
+ "customcolumn.boolean.unknown": "Není nastaveno",
+ "customcolumn.boolean.yes": "Ano",
+ "customcolumn.date.format": "Y-m-d",
+ "customcolumn.date.unknown": "Není nastaveno",
+ "customcolumn.description": "Custom column '{0}'",
+ "customcolumn.description.bool": "Index of a boolean value",
+ "customcolumn.description.enum.many": "Alphabetical index of the {0} values",
+ "customcolumn.description.enum.none": "Alphabetical index of absolutely no values",
+ "customcolumn.description.enum.one": "Alphabetical index of one value",
+ "customcolumn.description.rating": "Index of ratings",
+ "customcolumn.description.series.many": "Abecední výpis {0} sérií",
+ "customcolumn.description.series.none": "Abecední výpis neobsahuje žádné série",
+ "customcolumn.description.series.one": "Abecední výpis jedné série",
+ "customcolumn.enum.unknown": "Není nastaveno",
+ "customcolumn.float.unknown": "Není nastaveno",
+ "customcolumn.int.unknown": "Není nastaveno",
+ "customcolumn.rating.unknown": "Není nastaveno",
+ "customcolumn.stars.many": "{0} Stars",
+ "customcolumn.stars.none": "No Stars",
+ "customcolumn.stars.one": "1 Star",
+ "customize.email": "Nastavit email (umožní zasílání knih)",
+ "customize.fancybox": "Použít Lightbox",
+ "customize.filter": "Povolit filtrování štítků",
+ "customize.ignored": "Ignorované kategorie",
+ "customize.paging": "Maximální počet knih na stránku (-1 pro vypnutí)",
+ "customize.style": "Téma",
+ "customize.title": "Úpravy vzhledu COPS",
+ "home.alternate": "Domů",
+ "i18n.coversection": "Obálka",
+ "language.title": "Jazyk",
+ "languages.alphabetical.many": "Abecední seznam {0} jazyků",
+ "languages.alphabetical.none": "Abecední seznam neobsahuje žádné jazyky",
+ "languages.alphabetical.one": "Abecední seznam obsahuje jeden jazyk",
+ "languages.title": "Jazyky",
+ "mail.messagenotsent": "Zprávu se nepodařilo odeslat.",
+ "mail.messagesent": "Zpráva byla odeslána",
+ "paging.next.alternate": "Další",
+ "paging.previous.alternate": "Předchozí",
+ "permalink.alternate": "Permalink",
+ "pubdate.title": "Rok vydání",
+ "publisher.name": "Vydavatel",
+ "publishers.alphabetical.many": "Abecední výpis {0} vydavatelů",
+ "publishers.alphabetical.none": "Abecední výpis neobsahuje žádné vydavetele",
+ "publishers.alphabetical.one": "Abecední výpis jednoho vydavetele",
+ "publishers.title": "Vydavatelé",
+ "publisherword.many": "{0} vydavatelů",
+ "publisherword.none": "Žádný vydavatel",
+ "publisherword.one": "Jeden vydavatel",
+ "ratings.many": "{0} hodnocení",
+ "ratings.none": "bez hodnocení",
+ "ratings.one": "1 hodnocení",
+ "ratings.title": "Hodnocení",
+ "ratingword.many": "{0} hvězd",
+ "ratingword.none": "Bez hvězd",
+ "ratingword.one": "1 hvězda",
+ "recent.list": "{0} posledních přidaných knih",
+ "recent.title": "Poslední přidané knihy",
+ "search.alternate": "Hledat",
+ "search.result": "Výsledek hledání pro *{0}*",
+ "search.result.author": "Výsledek hledání pro *{0}* mezi autory",
+ "search.result.book": "Výsledek hledání pro *{0}* mezi knihami",
+ "search.result.publisher": "Výsledek hledání pro *{0}* vydavateli",
+ "search.result.series": "Výsledek hledání pro *{0}* sériemi",
+ "search.result.tag": "Výsledek hledání pro *{0}* mezi štítky",
+ "search.sortorder.asc": "Vzestupně",
+ "search.sortorder.desc": "Sestupně",
+ "series.alphabetical.many": "Abecední výpis {0} sérií",
+ "series.alphabetical.none": "Abecední výpis neobsahuje žádné série",
+ "series.alphabetical.one": "Abecední výpis jedné série",
+ "series.title": "Série",
+ "seriesword.many": "{0} sérií",
+ "seriesword.none": "Žádné série",
+ "seriesword.one": "Jedna série",
+ "sort.alternate": "Třídit",
+ "splitByLetter.book.other": "Other books",
+ "splitByLetter.letter": "{0} knih začínajících na {1}",
+ "tags.alphabetical.many": "Abecední výpis {0} štítků",
+ "tags.alphabetical.none": "Abecední výpis neobsahuje žádné štítky",
+ "tags.alphabetical.one": "Abecední výpis obsahuje jeden štítek",
+ "tags.title": "Štítky",
+ "tagword.many": "{0} štítků",
+ "tagword.none": "Žádné štítky",
+ "tagword.one": "Jeden štítek",
+ "tagword.title": "Štítky",
+ "languages.abk": "Abcházština",
+ "languages.aaf": "Afarština",
+ "languages.afr": "Afrikánština",
+ "languages.aka": "Akanština",
+ "languages.sqi": "Albánština",
+ "languages.amh": "Amharština",
+ "languages.ara": "Arabština",
+ "languages.arg": "Aragonština",
+ "languages.hye": "Arménština",
+ "languages.asm": "Ásámština",
+ "languages.ava": "Avarština",
+ "languages.ave": "Avestánština",
+ "languages.aym": "Ajmarština",
+ "languages.aze": "Ázerbájdžánština",
+ "languages.bam": "Bambarština",
+ "languages.bak": "Baškirština",
+ "languages.eus": "Baskičtina",
+ "languages.bel": "Běloruština",
+ "languages.ben": "Bengálština",
+ "languages.bih": "Bihárština",
+ "languages.bis": "Bislamština",
+ "languages.bos": "Bosenština",
+ "languages.bre": "Bretonština",
+ "languages.bul": "Bulharština",
+ "languages.mya": "Barmština",
+ "languages.cat": "Katalánština",
+ "languages.cha": "Chamorro",
+ "languages.che": "Čečenština",
+ "languages.nya": "Čičevština",
+ "languages.zho": "Čínština",
+ "languages.chv": "Čuvaština",
+ "languages.cor": "Kornština",
+ "languages.cos": "Korsičtina",
+ "languages.cre": "Kríjština",
+ "languages.hrv": "Chorvatština",
+ "languages.ces": "Čeština",
+ "languages.dan": "Dánština",
+ "languages.div": "Divehi",
+ "languages.nld": "Nizozemština",
+ "languages.dzo": "Dzongkha",
+ "languages.eng": "Angličtina",
+ "languages.epo": "Esperanto",
+ "languages.est": "Estonština",
+ "languages.ewe": "Eveština",
+ "languages.fao": "Faerština",
+ "languages.fij": "Fidžijština",
+ "languages.fin": "Finština",
+ "languages.fra": "Francouština",
+ "languages.ful": "Fulbština",
+ "languages.glg": "Galicijština",
+ "languages.kat": "Gruzínština",
+ "languages.deu": "Němčina",
+ "languages.ell": "Řečtina",
+ "languages.grn": "Guaraní",
+ "languages.guj": "Gudžarátština",
+ "languages.hat": "Haitština",
+ "languages.hau": "Hauština",
+ "languages.hed": "Hebrejština",
+ "languages.her": "Hererština",
+ "languages.hin": "Hindština",
+ "languages.hmo": "Hiri motu",
+ "languages.hun": "Maďarština",
+ "languages.ina": "Interlingua",
+ "languages.ind": "Indonéština",
+ "languages.ile": "Interlingue",
+ "languages.gle": "Irština",
+ "languages.ibo": "Igbo",
+ "languages.ipk": "Inupiaq",
+ "languages.ido": "Ido",
+ "languages.isl": "Islandština",
+ "languages.ita": "Italština",
+ "languages.iku": "Inuitština",
+ "languages.jpn": "Japonština",
+ "languages.jav": "Javánština",
+ "languages.kal": "Grónština",
+ "languages.kan": "Kannadština",
+ "languages.kau": "Kanurijština",
+ "languages.kas": "Kašmírština",
+ "languages.kaz": "Kazaština",
+ "languages.khm": "Khmerština",
+ "languages.kik": "Kikujština",
+ "languages.kin": "Rwandština",
+ "languages.kir": "Kyrgyzština",
+ "languages.kom": "Komijština",
+ "languages.kon": "Konžština",
+ "languages.kor": "Korejština",
+ "languages.kur": "Kurdština",
+ "languages.kua": "Kuanyama",
+ "languages.lat": "Latina",
+ "languages.ltz": "Lucemburština",
+ "languages.lug": "Gandština",
+ "languages.lim": "Limburština",
+ "languages.lin": "Ngalština",
+ "languages.lao": "Laoština",
+ "languages.lit": "Litevština",
+ "languages.lub": "Lubština",
+ "languages.lav": "Lotyština",
+ "languages.glv": "Manština",
+ "languages.mkd": "Makedonština",
+ "languages.mlg": "Malgaština",
+ "languages.msa": "Malajština",
+ "languages.mal": "Malajámština",
+ "languages.mlt": "Maltština",
+ "languages.mri": "Maorština",
+ "languages.mar": "Maráthština",
+ "languages.mah": "Maršálština",
+ "languages.mon": "Mongolština",
+ "languages.nau": "Nauruština",
+ "languages.nav": "Navažština",
+ "languages.nob": "Bokmål",
+ "languages.nde": "Severní Ndebelština",
+ "languages.nep": "Nepálština",
+ "languages.ndo": "Ndonga",
+ "languages.nno": "Nynorsk",
+ "languages.nor": "Norština",
+ "languages.iii": "Yi",
+ "languages.nbl": "Jižní Ndebelština",
+ "languages.oci": "Okcitánština",
+ "languages.oji": "Odžibvejština",
+ "languages.chu": "Staroslověnština",
+ "languages.orm": "Oromština",
+ "languages.ori": "Urijština",
+ "languages.oss": "Osetština",
+ "languages.pan": "Paňdžábština",
+ "languages.pli": "Páli",
+ "languages.fas": "Perština",
+ "languages.pol": "Polština",
+ "languages.pus": "Paštština",
+ "languages.por": "Portugalština",
+ "languages.que": "Kečuánština",
+ "languages.roh": "Románština",
+ "languages.run": "Kirundština",
+ "languages.ron": "Rumunština",
+ "languages.rus": "Ruština",
+ "languages.san": "Sanskrt",
+ "languages.srd": "Sardština",
+ "languages.snd": "Sindhština",
+ "languages.sme": "Severní Sámština",
+ "languages.smo": "Samojština",
+ "languages.sag": "Sangština",
+ "languages.srp": "Srbština",
+ "languages.gla": "Skotská Gaelština",
+ "languages.sna": "Šonština",
+ "languages.sin": "Sinhálština",
+ "languages.slk": "Slovenština",
+ "languages.slv": "Slovinština",
+ "languages.som": "Somálština",
+ "languages.sot": "Sotština",
+ "languages.spa": "Španělština",
+ "languages.sun": "Sundština",
+ "languages.swa": "Svahilština",
+ "languages.ssw": "Svazijština",
+ "languages.swe": "Švédština",
+ "languages.tam": "Tamilština",
+ "languages.tel": "Telugština",
+ "languages.tgk": "Tádžičtina",
+ "languages.tha": "Thajština",
+ "languages.tir": "Tigriňňa",
+ "languages.bod": "Tibetština",
+ "languages.tuk": "Turkmenština",
+ "languages.tgl": "Tagalština",
+ "languages.tsn": "Čwanština",
+ "languages.ton": "Tonžština",
+ "languages.tur": "Turečtina",
+ "languages.tso": "Tsonga",
+ "languages.tat": "Tatarština",
+ "languages.twi": "Ťwiština",
+ "languages.tah": "Tahitština",
+ "languages.uig": "Ujgurština",
+ "languages.ukr": "Ukrajinština",
+ "languages.urd": "Urdština",
+ "languages.uzb": "Uzbečtina",
+ "languages.ven": "Luvendština",
+ "languages.vie": "Vietnamština",
+ "languages.vol": "Volapük",
+ "languages.win": "Walonština",
+ "languages.cym": "Velština",
+ "languages.wol": "Volofština",
+ "languages.fry": "Západofríština",
+ "languages.xho": "Xhoština",
+ "languages.yid": "Jidiš",
+ "languages.yor": "Jorubština",
+ "languages.zha": "Čuangština",
+ "languages.zul": "Zulština",
+ "DO_NOT_TRANSLATE": "end"
+}
diff --git a/sources/lang/Localization_da.json b/sources/lang/Localization_da.json
new file mode 100644
index 0000000..244de8c
--- /dev/null
+++ b/sources/lang/Localization_da.json
@@ -0,0 +1,293 @@
+{
+ "about.title": "About COPS",
+ "allbooks.alphabetical.many": "Alphabetical index of the {0} books",
+ "allbooks.alphabetical.none": "Alphabetical index of absolutely no books",
+ "allbooks.alphabetical.one": "Alphabetical index of the single book",
+ "allbooks.title": "All books",
+ "authors.alphabetical.many": "Alphabetical index of the {0} authors",
+ "authors.alphabetical.none": "Alphabetical index of absolutely no authors",
+ "authors.alphabetical.one": "Alphabetical index of the single author",
+ "authors.title": "Authors",
+ "authorword.many": "{0} authors",
+ "authorword.none": "No authors",
+ "authorword.one": "1 author",
+ "bookentry.author": "{0} by {1}",
+ "bookword.many": "{0} books",
+ "bookword.none": "No books",
+ "bookword.one": "1 book",
+ "bookword.title": "Books",
+ "cog.alternate": "Search, sort and filters",
+ "content.series": "Series:",
+ "content.series.data": "Book {0} in the {1} series",
+ "content.summary": "Oversigt",
+ "customcolumn.boolean.no": "Nej",
+ "customcolumn.boolean.unknown": "Ikke sat",
+ "customcolumn.boolean.yes": "Ja",
+ "customcolumn.date.format": "Y-m-d",
+ "customcolumn.date.unknown": "Ikke sat",
+ "customcolumn.description": "Custom column '{0}'",
+ "customcolumn.description.bool": "Index of a boolean value",
+ "customcolumn.description.enum.many": "Alphabetical index of the {0} values",
+ "customcolumn.description.enum.none": "Alphabetical index of absolutely no values",
+ "customcolumn.description.enum.one": "Alphabetical index of one value",
+ "customcolumn.description.rating": "Index of ratings",
+ "customcolumn.description.series.many": "Alphabetical index of the {0} series",
+ "customcolumn.description.series.none": "Alphabetical index of absolutely no series",
+ "customcolumn.description.series.one": "Alphabetical index of the single series",
+ "customcolumn.enum.unknown": "Ikke sat",
+ "customcolumn.float.unknown": "Ikke sat",
+ "customcolumn.int.unknown": "Ikke sat",
+ "customcolumn.rating.unknown": "Ikke sat",
+ "customcolumn.stars.many": "{0} Stars",
+ "customcolumn.stars.none": "No Stars",
+ "customcolumn.stars.one": "1 Star",
+ "customize.email": "Set your email (to allow book emailing)",
+ "customize.fancybox": "Use Lightbox (books load in floating frame)",
+ "customize.filter": "Enable tag filtering",
+ "customize.ignored": "Ignored categories",
+ "customize.paging": "Max number of books per page (-1 to disable)",
+ "customize.style": "Tema",
+ "customize.title": "Customize COPS UI",
+ "home.alternate": "Startside",
+ "i18n.coversection": "Cover",
+ "language.title": "Sprog",
+ "languages.alphabetical.many": "Alphabetical index of the {0} languages",
+ "languages.alphabetical.none": "Alphabetical index of absolutely no languages",
+ "languages.alphabetical.one": "Alphabetical index of the single language",
+ "languages.title": "Sprog",
+ "mail.messagenotsent": "Message could not be sent.",
+ "mail.messagesent": "Message has been sent",
+ "paging.next.alternate": "Næste",
+ "paging.previous.alternate": "Foregående",
+ "permalink.alternate": "Permalink",
+ "pubdate.title": "Publication year",
+ "publisher.name": "Publisher",
+ "publishers.alphabetical.many": "Alphabetical index of the {0} publishers",
+ "publishers.alphabetical.none": "Alphabetical index of absolutely no publishers",
+ "publishers.alphabetical.one": "Alphabetical index of the single publisher",
+ "publishers.title": "Publishers",
+ "publisherword.many": "{0} publishers",
+ "publisherword.none": "No publishers",
+ "publisherword.one": "1 publisher",
+ "ratings.many": "{0} ratings",
+ "ratings.none": "no ratings",
+ "ratings.one": "1 rating",
+ "ratings.title": "Ratings",
+ "ratingword.many": "{0} stars",
+ "ratingword.none": "No star",
+ "ratingword.one": "1 star",
+ "recent.list": "{0} most recent books",
+ "recent.title": "Recent additions",
+ "search.alternate": "Søg",
+ "search.result": "Search result for *{0}*",
+ "search.result.author": "Search result for *{0}* in authors",
+ "search.result.book": "Search result for *{0}* in books",
+ "search.result.publisher": "Search result for *{0}* in publishers",
+ "search.result.series": "Search result for *{0}* in series",
+ "search.result.tag": "Search result for *{0}* in tags",
+ "search.sortorder.asc": "Asc",
+ "search.sortorder.desc": "Desc",
+ "series.alphabetical.many": "Alphabetical index of the {0} series",
+ "series.alphabetical.none": "Alphabetical index of absolutely no series",
+ "series.alphabetical.one": "Alphabetical index of the single series",
+ "series.title": "Series",
+ "seriesword.many": "{0} series",
+ "seriesword.none": "No series",
+ "seriesword.one": "1 series",
+ "sort.alternate": "Sort",
+ "splitByLetter.book.other": "Other books",
+ "splitByLetter.letter": "{0} starting with {1}",
+ "tags.alphabetical.many": "Alphabetical index of the {0} tags",
+ "tags.alphabetical.none": "Alphabetical index of absolutely no tags",
+ "tags.alphabetical.one": "Alphabetical index of the single tag",
+ "tags.title": "Tags",
+ "tagword.many": "{0} tags",
+ "tagword.none": "No tags",
+ "tagword.one": "1 tag",
+ "tagword.title": "Tags",
+ "languages.abk": "Abkhaz",
+ "languages.aaf": "Afar",
+ "languages.afr": "Afrikaans",
+ "languages.aka": "Akan",
+ "languages.sqi": "Albanian",
+ "languages.amh": "Amharic",
+ "languages.ara": "Arabic",
+ "languages.arg": "Aragonese",
+ "languages.hye": "Armenian",
+ "languages.asm": "Assamese",
+ "languages.ava": "Avaric",
+ "languages.ave": "Avestan",
+ "languages.aym": "Aymara",
+ "languages.aze": "Azerbaijani",
+ "languages.bam": "Bambara",
+ "languages.bak": "Bashkir",
+ "languages.eus": "Basque",
+ "languages.bel": "Belarusian",
+ "languages.ben": "Bengali",
+ "languages.bih": "Bihari",
+ "languages.bis": "Bislama",
+ "languages.bos": "Bosnian",
+ "languages.bre": "Breton",
+ "languages.bul": "Bulgarian",
+ "languages.mya": "Burmese",
+ "languages.cat": "Catalan",
+ "languages.cha": "Chamorro",
+ "languages.che": "Chechen",
+ "languages.nya": "Chichewa",
+ "languages.zho": "Chinese",
+ "languages.chv": "Chuvash",
+ "languages.cor": "Cornish",
+ "languages.cos": "Corsican",
+ "languages.cre": "Cree",
+ "languages.hrv": "Croatian",
+ "languages.ces": "Czech",
+ "languages.dan": "Danish",
+ "languages.div": "Divehi",
+ "languages.nld": "Dutch",
+ "languages.dzo": "Dzongkha",
+ "languages.eng": "English",
+ "languages.epo": "Esperanto",
+ "languages.est": "Estonian",
+ "languages.ewe": "Ewe",
+ "languages.fao": "Faroese",
+ "languages.fij": "Fijian",
+ "languages.fin": "Finnish",
+ "languages.fra": "French",
+ "languages.ful": "Fula",
+ "languages.glg": "Galician",
+ "languages.kat": "Georgian",
+ "languages.deu": "German",
+ "languages.ell": "Greek",
+ "languages.grn": "Guaraní",
+ "languages.guj": "Gujarati",
+ "languages.hat": "Haitian",
+ "languages.hau": "Hausa",
+ "languages.hed": "Hebrew",
+ "languages.her": "Herero",
+ "languages.hin": "Hindi",
+ "languages.hmo": "Hiri Motu",
+ "languages.hun": "Hungarian",
+ "languages.ina": "Interlingua",
+ "languages.ind": "Indonesian",
+ "languages.ile": "Interlingue",
+ "languages.gle": "Irish",
+ "languages.ibo": "Igbo",
+ "languages.ipk": "Inupiaq",
+ "languages.ido": "Ido",
+ "languages.isl": "Icelandic",
+ "languages.ita": "Italian",
+ "languages.iku": "Inuktitut",
+ "languages.jpn": "Japanese",
+ "languages.jav": "Javanese",
+ "languages.kal": "Kalaallisut",
+ "languages.kan": "Kannada",
+ "languages.kau": "Kanuri",
+ "languages.kas": "Kashmiri",
+ "languages.kaz": "Kazakh",
+ "languages.khm": "Khmer",
+ "languages.kik": "Kikuyu",
+ "languages.kin": "Kinyarwanda",
+ "languages.kir": "Kyrgyz",
+ "languages.kom": "Komi",
+ "languages.kon": "Kongo",
+ "languages.kor": "Korean",
+ "languages.kur": "Kurdish",
+ "languages.kua": "Kwanyama",
+ "languages.lat": "Latin",
+ "languages.ltz": "Luxembourgish",
+ "languages.lug": "Ganda",
+ "languages.lim": "Limburgish",
+ "languages.lin": "Lingala",
+ "languages.lao": "Lao",
+ "languages.lit": "Lithuanian",
+ "languages.lub": "Luba-Katanga",
+ "languages.lav": "Latvian",
+ "languages.glv": "Manx",
+ "languages.mkd": "Macedonian",
+ "languages.mlg": "Malagasy",
+ "languages.msa": "Malay",
+ "languages.mal": "Malayalam",
+ "languages.mlt": "Maltese",
+ "languages.mri": "Māori",
+ "languages.mar": "Marathi",
+ "languages.mah": "Marshallese",
+ "languages.mon": "Mongolian",
+ "languages.nau": "Nauru",
+ "languages.nav": "Navajo",
+ "languages.nob": "Norwegian Bokmål",
+ "languages.nde": "North Ndebele",
+ "languages.nep": "Nepali",
+ "languages.ndo": "Ndonga",
+ "languages.nno": "Norwegian Nynorsk",
+ "languages.nor": "Norwegian",
+ "languages.iii": "Nuosu",
+ "languages.nbl": "South Ndebele",
+ "languages.oci": "Occitan",
+ "languages.oji": "Ojibwe",
+ "languages.chu": "Old Church Slavonic",
+ "languages.orm": "Oromo",
+ "languages.ori": "Oriya",
+ "languages.oss": "Ossetian",
+ "languages.pan": "Panjabi",
+ "languages.pli": "Pāli",
+ "languages.fas": "Persian",
+ "languages.pol": "Polish",
+ "languages.pus": "Pashto",
+ "languages.por": "Portuguese",
+ "languages.que": "Quechua",
+ "languages.roh": "Romansh",
+ "languages.run": "Kirundi",
+ "languages.ron": "Romanian",
+ "languages.rus": "Russian",
+ "languages.san": "Sanskrit",
+ "languages.srd": "Sardinian",
+ "languages.snd": "Sindhi",
+ "languages.sme": "Northern Sami",
+ "languages.smo": "Samoan",
+ "languages.sag": "Sango",
+ "languages.srp": "Serbian",
+ "languages.gla": "Scottish Gaelic",
+ "languages.sna": "Shona",
+ "languages.sin": "Sinhala",
+ "languages.slk": "Slovak",
+ "languages.slv": "Slovene",
+ "languages.som": "Somali",
+ "languages.sot": "Southern Sotho",
+ "languages.spa": "Spanish",
+ "languages.sun": "Sundanese",
+ "languages.swa": "Swahili",
+ "languages.ssw": "Swati",
+ "languages.swe": "Swedish",
+ "languages.tam": "Tamil",
+ "languages.tel": "Telugu",
+ "languages.tgk": "Tajik",
+ "languages.tha": "Thai",
+ "languages.tir": "Tigrinya",
+ "languages.bod": "Tibetan Standard",
+ "languages.tuk": "Turkmen",
+ "languages.tgl": "Tagalog",
+ "languages.tsn": "Tswana",
+ "languages.ton": "Tonga",
+ "languages.tur": "Turkish",
+ "languages.tso": "Tsonga",
+ "languages.tat": "Tatar",
+ "languages.twi": "Twi",
+ "languages.tah": "Tahitian",
+ "languages.uig": "Uighur",
+ "languages.ukr": "Ukrainian",
+ "languages.urd": "Urdu",
+ "languages.uzb": "Uzbek",
+ "languages.ven": "Venda",
+ "languages.vie": "Vietnamese",
+ "languages.vol": "Volapük",
+ "languages.win": "Walloon",
+ "languages.cym": "Welsh",
+ "languages.wol": "Wolof",
+ "languages.fry": "Western Frisian",
+ "languages.xho": "Xhosa",
+ "languages.yid": "Yiddish",
+ "languages.yor": "Yoruba",
+ "languages.zha": "Zhuang",
+ "languages.zul": "Zulu",
+ "DO_NOT_TRANSLATE": "end"
+}
diff --git a/sources/lang/Localization_de.json b/sources/lang/Localization_de.json
new file mode 100644
index 0000000..47e02b9
--- /dev/null
+++ b/sources/lang/Localization_de.json
@@ -0,0 +1,293 @@
+{
+ "about.title": "Über COPS",
+ "allbooks.alphabetical.many": "Alphabetischer Index der {0} Bücher",
+ "allbooks.alphabetical.none": "Alphabetischer Index von absolut keinem Buch",
+ "allbooks.alphabetical.one": "Alphabetischer Index des einzigen Buchs",
+ "allbooks.title": "Alle Bücher",
+ "authors.alphabetical.many": "Alphabetischer Index der {0} Autoren",
+ "authors.alphabetical.none": "Alphabetischer Index von absolut keinem Autor",
+ "authors.alphabetical.one": "Alphabetischer Index des einzigen Autors",
+ "authors.title": "Autoren",
+ "authorword.many": "{0} Autoren",
+ "authorword.none": "Kein Autor",
+ "authorword.one": "1 Autor",
+ "bookentry.author": "{0} von {1}",
+ "bookword.many": "{0} Bücher",
+ "bookword.none": "Kein Buch",
+ "bookword.one": "1 Buch",
+ "bookword.title": "Bücher",
+ "cog.alternate": "Suche, Sortierung und Filter",
+ "content.series": "Serien:",
+ "content.series.data": "Buch {0} der {1} - Reihe",
+ "content.summary": "Inhalt",
+ "customcolumn.boolean.no": "Nein",
+ "customcolumn.boolean.unknown": "Nicht gesetzt",
+ "customcolumn.boolean.yes": "Ja",
+ "customcolumn.date.format": "d.m.Y",
+ "customcolumn.date.unknown": "Nicht gesetzt",
+ "customcolumn.description": "Bentzerdefinierte Spalte '{0}",
+ "customcolumn.description.bool": "Index eines Wahrheitswertes",
+ "customcolumn.description.enum.many": "Alphabetischer Index der {0} Werte",
+ "customcolumn.description.enum.none": "Alphabetischer Index von absolut keinen Werten",
+ "customcolumn.description.enum.one": "Alphabetischer Index von einem Wert",
+ "customcolumn.description.rating": "Index von Bewertungen",
+ "customcolumn.description.series.many": "Alphabetischer Index der {0} Serien",
+ "customcolumn.description.series.none": "Alphabetischer Index von absolut keinen Serien",
+ "customcolumn.description.series.one": "Alphabetischer Index der einzigen Serie",
+ "customcolumn.enum.unknown": "Nicht gesetzt",
+ "customcolumn.float.unknown": "Nicht gesetzt",
+ "customcolumn.int.unknown": "Nicht gesetzt",
+ "customcolumn.rating.unknown": "Nicht gesetzt",
+ "customcolumn.stars.many": "{0} Sterne",
+ "customcolumn.stars.none": "Kein Stern",
+ "customcolumn.stars.one": "1 Stern",
+ "customize.email": "Geben Sie Ihre E-Mail-Adresse an (erlaubt das Zusenden von Büchern)",
+ "customize.fancybox": "Benutze die Lightbox",
+ "customize.filter": "Erlaube das Filtern nach Stichworten",
+ "customize.ignored": "Nicht verwendete Kategorien",
+ "customize.paging": "Maximale Anzahl von Büchern pro Seite (-1 zum Deaktivieren)",
+ "customize.style": "Thema",
+ "customize.title": "Anpassungen an COPS",
+ "home.alternate": "Startseite",
+ "i18n.coversection": "Umschlag",
+ "language.title": "Sprache",
+ "languages.alphabetical.many": "Alphabetischer Index der {0} Sprachen",
+ "languages.alphabetical.none": "Alphabetischer Index von absolut keiner Sprache",
+ "languages.alphabetical.one": "Alphabetischer Index der einzigen Sprache",
+ "languages.title": "Sprachen",
+ "mail.messagenotsent": "E-Mail konnte nicht gesendet werden.",
+ "mail.messagesent": "E-Mail wurde gesendet.",
+ "paging.next.alternate": "Nächste",
+ "paging.previous.alternate": "Vorherige",
+ "permalink.alternate": "Permalink",
+ "pubdate.title": "Veröffentlichung",
+ "publisher.name": "Verlag",
+ "publishers.alphabetical.many": "Alphabetischer Index der {0} Verlage",
+ "publishers.alphabetical.none": "Alphabetischer Index von absolut keinem Verlag",
+ "publishers.alphabetical.one": "Alphabetischer Index des einzigen Verlags",
+ "publishers.title": "Verlage",
+ "publisherword.many": "{0} Verlage",
+ "publisherword.none": "Kein Verlag",
+ "publisherword.one": "1 Verlag",
+ "ratings.many": "{0} verschiedene Bewertungen",
+ "ratings.none": "Keine Bewertungen",
+ "ratings.one": "{0} Bewertung",
+ "ratings.title": "Bewertung",
+ "ratingword.many": "{0} Sterne",
+ "ratingword.none": "{0} Sterne",
+ "ratingword.one": "{0} Stern",
+ "recent.list": "{0} neue Bücher",
+ "recent.title": "Neuzugänge",
+ "search.alternate": "Suche",
+ "search.result": "Suchergebnis für",
+ "search.result.author": "Suchergebnis für *{0}* in Autoren",
+ "search.result.book": "Suchergebnis für *{0}* in Bücher",
+ "search.result.publisher": "Suchergebnis für *{0}* in Verlage",
+ "search.result.series": "Suchergebnis für *{0}* in Serien",
+ "search.result.tag": "Suchergebnis für *{0}* in Schlagwörter",
+ "search.sortorder.asc": "Auf",
+ "search.sortorder.desc": "Ab",
+ "series.alphabetical.many": "Alphabetischer Index der {0} Serien",
+ "series.alphabetical.none": "Alphabetischer Index von absolut keiner Serie",
+ "series.alphabetical.one": "Alphabetischer Index der Serie",
+ "series.title": "Serien",
+ "seriesword.many": "{0} Serien",
+ "seriesword.none": "Keine Serie",
+ "seriesword.one": "1 Serie",
+ "sort.alternate": "Sortierung",
+ "splitByLetter.book.other": "Andere Bücher",
+ "splitByLetter.letter": "{0} unter {1}",
+ "tags.alphabetical.many": "Alphabetischer Index der {0} Schlagwörter",
+ "tags.alphabetical.none": "Alphabetischer Index von absolut keinem Schlagwort",
+ "tags.alphabetical.one": "Alphabetischer Index des Schlagworts",
+ "tags.title": "Schlagwörter",
+ "tagword.many": "{0} Schlagwörter",
+ "tagword.none": "Kein Schlagwort",
+ "tagword.one": "1 Schlagwort",
+ "tagword.title": "Schlagwörter",
+ "languages.abk": "Abchasisch",
+ "languages.aaf": "Afar",
+ "languages.afr": "Afrikaans",
+ "languages.aka": "Akan",
+ "languages.sqi": "Albanisch",
+ "languages.amh": "Amharisch",
+ "languages.ara": "Arabisch",
+ "languages.arg": "Aragonisch",
+ "languages.hye": "Armenisch",
+ "languages.asm": "Assamesisch",
+ "languages.ava": "Awarisch",
+ "languages.ave": "Avestisch",
+ "languages.aym": "Aymara",
+ "languages.aze": "Aserbaidschanisch",
+ "languages.bam": "Bambara",
+ "languages.bak": "Baschkirisch",
+ "languages.eus": "Baskisch",
+ "languages.bel": "Weissrussisch",
+ "languages.ben": "Bengalisch",
+ "languages.bih": "Biharisch",
+ "languages.bis": "Bislamisch",
+ "languages.bos": "Bosnisch",
+ "languages.bre": "Bretonisch",
+ "languages.bul": "Bulgarisch",
+ "languages.mya": "Burmesisch",
+ "languages.cat": "Katalanisch",
+ "languages.cha": "Chamorro",
+ "languages.che": "Tschetschenisch",
+ "languages.nya": "Chichewa",
+ "languages.zho": "Chinesisch",
+ "languages.chv": "Tschuwaschisch",
+ "languages.cor": "Kornisch",
+ "languages.cos": "Korsisch",
+ "languages.cre": "Cree",
+ "languages.hrv": "Kroatisch",
+ "languages.ces": "Tschechisch",
+ "languages.dan": "Dänisch",
+ "languages.div": "Divehi",
+ "languages.nld": "Niederländisch",
+ "languages.dzo": "Dzongkha",
+ "languages.eng": "Englisch",
+ "languages.epo": "Esperanto",
+ "languages.est": "Estisch",
+ "languages.ewe": "Ewe",
+ "languages.fao": "Färöisch",
+ "languages.fij": "Fidschi",
+ "languages.fin": "Finnisch",
+ "languages.fra": "Französisch",
+ "languages.ful": "Fulfulde",
+ "languages.glg": "Galizisch",
+ "languages.kat": "Georgisch",
+ "languages.deu": "Deutsch",
+ "languages.ell": "Griechisch",
+ "languages.grn": "Guaraní",
+ "languages.guj": "Gujarati",
+ "languages.hat": "Haitianisch",
+ "languages.hau": "Haussa",
+ "languages.hed": "Hebräisch",
+ "languages.her": "Herero",
+ "languages.hin": "Hindi",
+ "languages.hmo": "Hiri Motu",
+ "languages.hun": "Ungarisch",
+ "languages.ina": "Interlingua",
+ "languages.ind": "Indonesisch",
+ "languages.ile": "Interlingue",
+ "languages.gle": "Irisch",
+ "languages.ibo": "Igbo",
+ "languages.ipk": "Inupiaq",
+ "languages.ido": "Ido",
+ "languages.isl": "Isländisch",
+ "languages.ita": "Italienisch",
+ "languages.iku": "Inuktitut (Eskimo)",
+ "languages.jpn": "Japanisch",
+ "languages.jav": "Javanisch",
+ "languages.kal": "Kalaallisut (Grönländisch)",
+ "languages.kan": "Kanarisch",
+ "languages.kau": "Kanuri",
+ "languages.kas": "Kaschmirisch",
+ "languages.kaz": "Kasachisch",
+ "languages.khm": "Kambodschanisch",
+ "languages.kik": "Kikuyu",
+ "languages.kin": "Kinyarwanda",
+ "languages.kir": "Kirgisisch",
+ "languages.kom": "Komi",
+ "languages.kon": "Kongolesisch",
+ "languages.kor": "Koreanisch",
+ "languages.kur": "Kurdisch",
+ "languages.kua": "Oshivambo",
+ "languages.lat": "Lateinisch",
+ "languages.ltz": "Luxemburgisch",
+ "languages.lug": "Luganda",
+ "languages.lim": "Limburgisch",
+ "languages.lin": "Lingála",
+ "languages.lao": "Laotisch",
+ "languages.lit": "Litauisch",
+ "languages.lub": "Kiluba",
+ "languages.lav": "Lettisch",
+ "languages.glv": "Manx",
+ "languages.mkd": "Mazedonisch",
+ "languages.mlg": "Malagasy",
+ "languages.msa": "Malaiisch",
+ "languages.mal": "Malaysisch",
+ "languages.mlt": "Maltesisch",
+ "languages.mri": "Maorisch",
+ "languages.mar": "Marathi",
+ "languages.mah": "Marshallesisch",
+ "languages.mon": "Mongolisch",
+ "languages.nau": "Nauruisch",
+ "languages.nav": "Navajo",
+ "languages.nob": "Norwegisch (Bokmål)",
+ "languages.nde": "Nord-Ndebele",
+ "languages.nep": "Nepalesisch",
+ "languages.ndo": "Ndonga",
+ "languages.nno": "Norwegisch (Nynorsk)",
+ "languages.nor": "Norwegisch",
+ "languages.iii": "Yiyu",
+ "languages.nbl": "Süd-Ndebele",
+ "languages.oci": "Okzitanisch",
+ "languages.oji": "Ojibwe",
+ "languages.chu": "Altkirchenslawisch",
+ "languages.orm": "Oromo",
+ "languages.ori": "Oriya",
+ "languages.oss": "Ossetisch",
+ "languages.pan": "Punjabisch",
+ "languages.pli": "Pali",
+ "languages.fas": "Persisch",
+ "languages.pol": "Polnisch",
+ "languages.pus": "Pashtu",
+ "languages.por": "Portugisisch",
+ "languages.que": "Quechua",
+ "languages.roh": "Bündnerromanisch",
+ "languages.run": "Kirundisch",
+ "languages.ron": "Rumänisch",
+ "languages.rus": "Russisch",
+ "languages.san": "Sanskrit",
+ "languages.srd": "Sardisch",
+ "languages.snd": "Sindhi",
+ "languages.sme": "Nordsamisch",
+ "languages.smo": "Samoanisch",
+ "languages.sag": "Sango",
+ "languages.srp": "Serbisch",
+ "languages.gla": "Schottisches Gälisch",
+ "languages.sna": "Schonisch",
+ "languages.sin": "Singhalesisch",
+ "languages.slk": "Slowakisch",
+ "languages.slv": "Slowenisch",
+ "languages.som": "Somalisch",
+ "languages.sot": "Sesotho",
+ "languages.spa": "Spanisch",
+ "languages.sun": "Sundanesisch",
+ "languages.swa": "Kisuaheli",
+ "languages.ssw": "Siswati",
+ "languages.swe": "Schwedisch",
+ "languages.tam": "Tamilisch",
+ "languages.tel": "Telugisch",
+ "languages.tgk": "Tadschikisch",
+ "languages.tha": "Thai",
+ "languages.tir": "Tigrinya",
+ "languages.bod": "Tibetisch",
+ "languages.tuk": "Turkmenisch",
+ "languages.tgl": "Tagalog",
+ "languages.tsn": "Setswana",
+ "languages.ton": "Tongaisch",
+ "languages.tur": "Türkisch",
+ "languages.tso": "Tsongaisch",
+ "languages.tat": "Tatarisch",
+ "languages.twi": "Twi",
+ "languages.tah": "Tahitianisch",
+ "languages.uig": "Uigur",
+ "languages.ukr": "Ukrainisch",
+ "languages.urd": "Urdu",
+ "languages.uzb": "Usbekisch",
+ "languages.ven": "Tshivenda",
+ "languages.vie": "Vietnamesisch",
+ "languages.vol": "Volapük",
+ "languages.win": "Wallonisch",
+ "languages.cym": "Walisisch",
+ "languages.wol": "Wolof",
+ "languages.fry": "Westfriesisch",
+ "languages.xho": "IsiXhosa",
+ "languages.yid": "Yi (Jiddisch)",
+ "languages.yor": "Joruba",
+ "languages.zha": "Zhuang",
+ "languages.zul": "Zulu",
+ "DO_NOT_TRANSLATE": "end"
+}
diff --git a/sources/lang/Localization_el.json b/sources/lang/Localization_el.json
new file mode 100644
index 0000000..5be2cd2
--- /dev/null
+++ b/sources/lang/Localization_el.json
@@ -0,0 +1,293 @@
+{
+ "about.title": "Σχετικά με COPS",
+ "allbooks.alphabetical.many": "Αλφαβητικό ευρετήριο των {0} βιβλίων",
+ "allbooks.alphabetical.none": "Αλφαβητικός κατάλογος από απολύτως κανένα βιβλίο",
+ "allbooks.alphabetical.one": "Αλφαβητικό ευρετήριο του μοναδικού βιβλίου",
+ "allbooks.title": "Όλα τα βιβλία",
+ "authors.alphabetical.many": "Αλφαβητικό ευρετήριο των {0} συγγραφέων",
+ "authors.alphabetical.none": "Αλφαβητικός κατάλογος από απολύτως κανένα συγγραφέα",
+ "authors.alphabetical.one": "Αλφαβητικό ευρετήριο του μοναδικού συγγραφέα",
+ "authors.title": "Συγγραφείς",
+ "authorword.many": "{0} συγγραφείς",
+ "authorword.none": "Χωρίς συγγραφείς",
+ "authorword.one": "1 συγγραφέας",
+ "bookentry.author": "{0} από {1}",
+ "bookword.many": "{0} βιβλία",
+ "bookword.none": "Δεν υπάρχουν βιβλία",
+ "bookword.one": "1 βιβλίο",
+ "bookword.title": "Βιβλία",
+ "cog.alternate": "Αναζήτηση, ταξινόμηση και φίλτρα",
+ "content.series": "Σειρά:",
+ "content.series.data": "Βιβλίο {0} από την σειρά {1}",
+ "content.summary": "Περίληψη",
+ "customcolumn.boolean.no": "Όχι",
+ "customcolumn.boolean.unknown": "Προεπιλογή",
+ "customcolumn.boolean.yes": "Ναι",
+ "customcolumn.date.format": "Y-m-d",
+ "customcolumn.date.unknown": "Προεπιλογή",
+ "customcolumn.description": "Custom column '{0}'",
+ "customcolumn.description.bool": "Index of a boolean value",
+ "customcolumn.description.enum.many": "Alphabetical index of the {0} values",
+ "customcolumn.description.enum.none": "Alphabetical index of absolutely no values",
+ "customcolumn.description.enum.one": "Alphabetical index of one value",
+ "customcolumn.description.rating": "Index of ratings",
+ "customcolumn.description.series.many": "Αλφαβητικό ευρετήριο των {0} σειρών",
+ "customcolumn.description.series.none": "Αλφαβητικός κατάλογος από απολύτως καμία σειρά",
+ "customcolumn.description.series.one": "Αλφαβητικό ευρετήριο της μοναδικής σειράς",
+ "customcolumn.enum.unknown": "Προεπιλογή",
+ "customcolumn.float.unknown": "Προεπιλογή",
+ "customcolumn.int.unknown": "Προεπιλογή",
+ "customcolumn.rating.unknown": "Προεπιλογή",
+ "customcolumn.stars.many": "{0} Stars",
+ "customcolumn.stars.none": "No Stars",
+ "customcolumn.stars.one": "1 Star",
+ "customize.email": "Ρυθμίστε το email σας (για να επιτραπεί αποστολή email)",
+ "customize.fancybox": "Χρήση Lightbox (τα βιβλία φορτώνουν σε πλαίσιο)",
+ "customize.filter": "Ενεργοποίηση φιλτραρίσματος ανά ετικέτα",
+ "customize.ignored": "Αγνοείστε κατηγορίες",
+ "customize.paging": "Μέγιστος αριθμός βιβλίων ανά σελίδα (-1 για απενεργοποίηση)",
+ "customize.style": "Θέμα",
+ "customize.title": "Προσαρμογή COPS UI",
+ "home.alternate": "Αρχική",
+ "i18n.coversection": "Εξώφυλλο",
+ "language.title": "Γλώσσα",
+ "languages.alphabetical.many": "Αλφαβητικό ευρετήριο των {0} γλωσσών",
+ "languages.alphabetical.none": "Αλφαβητικός κατάλογος από απολύτως καμία γλώσσες",
+ "languages.alphabetical.one": "Αλφαβητικό ευρετήριο της μοναδικής γλώσσας",
+ "languages.title": "Γλώσσες",
+ "mail.messagenotsent": "Το μήνυμα δεν μπόρεσε να σταλεί.",
+ "mail.messagesent": "Το μήνυμα εστάλη",
+ "paging.next.alternate": "Επόμενος",
+ "paging.previous.alternate": "Προηγούμενος",
+ "permalink.alternate": "Permalink",
+ "pubdate.title": "Έτος έκδοσης",
+ "publisher.name": "Εκδότης",
+ "publishers.alphabetical.many": "Αλφαβητικό ευρετήριο από {0} εκδότες",
+ "publishers.alphabetical.none": "Αλφαβητικός κατάλογος από απολύτως κανένα εκδότη",
+ "publishers.alphabetical.one": "Αλφαβητικό ευρετήριο του μοναδικού εκδότη",
+ "publishers.title": "Εκδότες",
+ "publisherword.many": "{0} εκδότες",
+ "publisherword.none": "Δεν υπάρχουν εκδότες",
+ "publisherword.one": "1 εκδότης",
+ "ratings.many": "{0} αξιολογήσεις",
+ "ratings.none": "Δεν υπάρχουν αξιολογήσεις",
+ "ratings.one": "1 αξιολόγηση",
+ "ratings.title": "Αξιολογήσεις",
+ "ratingword.many": "{0} αστέρια",
+ "ratingword.none": "Χωρίς αστέρι",
+ "ratingword.one": "1 αστέρι",
+ "recent.list": "{0} πιο πρόσφατα βιβλία",
+ "recent.title": "Πρόσφατες προσθήκες",
+ "search.alternate": "Αναζήτηση",
+ "search.result": "Αποτέλεσμα αναζήτησης για * {0} *",
+ "search.result.author": "Αποτέλεσμα αναζήτησης για * {0} * σε συγγραφείς",
+ "search.result.book": "Αποτέλεσμα αναζήτησης για * {0} * στα βιβλία",
+ "search.result.publisher": "Αποτέλεσμα αναζήτησης για * {0} * σε εκδότες",
+ "search.result.series": "Αποτέλεσμα αναζήτησης για * {0} * σε σειρά",
+ "search.result.tag": "Αποτέλεσμα αναζήτησης για * {0} * σε ετικέτες",
+ "search.sortorder.asc": "Αυξ",
+ "search.sortorder.desc": "Φθιν",
+ "series.alphabetical.many": "Αλφαβητικό ευρετήριο των {0} σειρών",
+ "series.alphabetical.none": "Αλφαβητικός κατάλογος από απολύτως καμία σειρά",
+ "series.alphabetical.one": "Αλφαβητικό ευρετήριο της μοναδικής σειράς",
+ "series.title": "Σειρά",
+ "seriesword.many": "{0} σειρά",
+ "seriesword.none": "Χωρίς σειρά",
+ "seriesword.one": "Σειρά 1",
+ "sort.alternate": "Ταξινόμηση",
+ "splitByLetter.book.other": "Άλλα βιβλία",
+ "splitByLetter.letter": "{0} ξεκινά με {1}",
+ "tags.alphabetical.many": "Αλφαβητικό ευρετήριο των {0} ετικετών",
+ "tags.alphabetical.none": "Αλφαβητικός κατάλογος από απολύτως καμία ετικέτες",
+ "tags.alphabetical.one": "Αλφαβητικό ευρετήριο της μοναδικής ετικέτας",
+ "tags.title": "Ετικέτες",
+ "tagword.many": "{0} ετικέτες",
+ "tagword.none": "Δεν υπάρχουν ετικέτες",
+ "tagword.one": "1 ετικέτα",
+ "tagword.title": "Ετικέτες",
+ "languages.abk": "Αμπχαζίας",
+ "languages.aaf": "Αφάρ",
+ "languages.afr": "Αφρικάνικα",
+ "languages.aka": "Akan",
+ "languages.sqi": "Αλβανικά",
+ "languages.amh": "Αμαρικά",
+ "languages.ara": "Αραβικά",
+ "languages.arg": "Αραγονίας",
+ "languages.hye": "Αρμενικός",
+ "languages.asm": "Ασαμέζικα",
+ "languages.ava": "Avaric",
+ "languages.ave": "Avestan",
+ "languages.aym": "Aymara",
+ "languages.aze": "Αζερμπαϊτζάν",
+ "languages.bam": "Μπαμπάρα",
+ "languages.bak": "Μπασκίρ",
+ "languages.eus": "Βάσκων",
+ "languages.bel": "Λευκορωσίας",
+ "languages.ben": "Βεγγαλική",
+ "languages.bih": "Μπιχάρι",
+ "languages.bis": "Μπισλάμα",
+ "languages.bos": "Βοσνιακά",
+ "languages.bre": "Breton",
+ "languages.bul": "Βούλγαρος",
+ "languages.mya": "Βιρμανίας",
+ "languages.cat": "Καταλανικά",
+ "languages.cha": "Chamorro",
+ "languages.che": "Της Τσετσενίας",
+ "languages.nya": "Chichewa",
+ "languages.zho": "Κινέζικα",
+ "languages.chv": "Χουβάς",
+ "languages.cor": "Cornish",
+ "languages.cos": "Κορσικανός",
+ "languages.cre": "Κρι",
+ "languages.hrv": "Κροατία",
+ "languages.ces": "Τσέχος",
+ "languages.dan": "Δανός",
+ "languages.div": "Ντιβέχι",
+ "languages.nld": "Ολλανδός",
+ "languages.dzo": "Dzongkha",
+ "languages.eng": "Αγγλικά",
+ "languages.epo": "Εσπεράντο",
+ "languages.est": "Εσθονική",
+ "languages.ewe": "Προβατίνα",
+ "languages.fao": "Των Νήσων Φερόε",
+ "languages.fij": "Φίτζι",
+ "languages.fin": "Φινλανδικά",
+ "languages.fra": "Γαλλικά",
+ "languages.ful": "Fula",
+ "languages.glg": "Γαλικίας",
+ "languages.kat": "Γεωργιανά",
+ "languages.deu": "Γερμανός",
+ "languages.ell": "Ελληνικά",
+ "languages.grn": "Γκουαρανί",
+ "languages.guj": "Γκουτζαρατικά",
+ "languages.hat": "Αϊτής",
+ "languages.hau": "Χάουσα",
+ "languages.hed": "Εβραϊκά",
+ "languages.her": "Herero",
+ "languages.hin": "Χίντι",
+ "languages.hmo": "Hiri Motu",
+ "languages.hun": "Ούγγρος",
+ "languages.ina": "Ιντερλίνγκουα",
+ "languages.ind": "Ινδονησίας",
+ "languages.ile": "Ιντερλίνγκουε",
+ "languages.gle": "Ιρλανδικός",
+ "languages.ibo": "Ίγκμπο",
+ "languages.ipk": "Ινουπιάκ",
+ "languages.ido": "Ιντο",
+ "languages.isl": "Ισλανδικός",
+ "languages.ita": "Ιταλικά",
+ "languages.iku": "Ινουκτιτούτ",
+ "languages.jpn": "Ιαπωνικά",
+ "languages.jav": "Ιάβας",
+ "languages.kal": "Kalaallisut",
+ "languages.kan": "Κανάντα",
+ "languages.kau": "Κανούρι",
+ "languages.kas": "Κασμίρ",
+ "languages.kaz": "Του Καζακστάν",
+ "languages.khm": "Χμερ",
+ "languages.kik": "Κικούγιου",
+ "languages.kin": "Kinyarwanda",
+ "languages.kir": "Κιργιζίας",
+ "languages.kom": "Κώμη",
+ "languages.kon": "Kongo",
+ "languages.kor": "Κορέατικα",
+ "languages.kur": "Κουρδικά",
+ "languages.kua": "Kwanyama",
+ "languages.lat": "Λατινικά",
+ "languages.ltz": "Λουξεμβούργου",
+ "languages.lug": "Ganda",
+ "languages.lim": "Λιμβουργιανά",
+ "languages.lin": "Lingala",
+ "languages.lao": "Λάος",
+ "languages.lit": "Λιθουανικά",
+ "languages.lub": "Λούμπα-Κατάνγκα",
+ "languages.lav": "Της Λετονίας",
+ "languages.glv": "Μανξ",
+ "languages.mkd": "ΦΥΡΟΜ",
+ "languages.mlg": "Μαδαγασκάρης",
+ "languages.msa": "Μαλαισίας",
+ "languages.mal": "Μαλαγιάλαμ",
+ "languages.mlt": "Της Μάλτας",
+ "languages.mri": "Μαορί",
+ "languages.mar": "Μαράθι",
+ "languages.mah": "Μάρσαλ",
+ "languages.mon": "Μογγόλος",
+ "languages.nau": "Ναουρού",
+ "languages.nav": "Navajo",
+ "languages.nob": "Νορβηγικά",
+ "languages.nde": "Βόρεια Ντέμπελε",
+ "languages.nep": "Νεπάλ",
+ "languages.ndo": "Ndonga",
+ "languages.nno": "Νορβηγικά Νινόρσκ",
+ "languages.nor": "Νορβηγός",
+ "languages.iii": "Nuosu",
+ "languages.nbl": "Ισλανδική γλώσσα",
+ "languages.oci": "Occitan",
+ "languages.oji": "Ojibwe",
+ "languages.chu": "Παλιά εκκλησιαστική σλαβονική",
+ "languages.orm": "Ορόμο",
+ "languages.ori": "Οριγικά",
+ "languages.oss": "Οσετίας",
+ "languages.pan": "Panjabi",
+ "languages.pli": "Pāli",
+ "languages.fas": "Πέρσης",
+ "languages.pol": "Πολωνός",
+ "languages.pus": "Πάστο",
+ "languages.por": "Πορτογάλος",
+ "languages.que": "Κέτσουα",
+ "languages.roh": "Ρωμανικά",
+ "languages.run": "Kirundi",
+ "languages.ron": "Ρουμανικός",
+ "languages.rus": "Ρωσικός",
+ "languages.san": "Σανσκριτικά",
+ "languages.srd": "Σαρδηνίας",
+ "languages.snd": "Σίντι",
+ "languages.sme": "Βόρεια Σάμι",
+ "languages.smo": "Σαμόα",
+ "languages.sag": "Σάνγκο",
+ "languages.srp": "Σέρβικα",
+ "languages.gla": "Scottish Gaelic",
+ "languages.sna": "Σόνα",
+ "languages.sin": "Sinhala",
+ "languages.slk": "Σλοβάκος",
+ "languages.slv": "Σλοβενικά",
+ "languages.som": "Της Σομαλίας",
+ "languages.sot": "Νότια Σόθο",
+ "languages.spa": "Ισπανικά",
+ "languages.sun": "Σουδανικά",
+ "languages.swa": "Σουαχίλι",
+ "languages.ssw": "Swati",
+ "languages.swe": "Σουηδικά",
+ "languages.tam": "Ταμίλ",
+ "languages.tel": "Τελούγκου",
+ "languages.tgk": "Τατζικιστάν",
+ "languages.tha": "Ταϊλάνδης",
+ "languages.tir": "Τιγκρινιανά",
+ "languages.bod": "Θιβετιανά κανονικά",
+ "languages.tuk": "Τουρκμενιστάν",
+ "languages.tgl": "Tagalog",
+ "languages.tsn": "Tswana",
+ "languages.ton": "Τόνγκα",
+ "languages.tur": "Τουρκική",
+ "languages.tso": "Τσόνγκα",
+ "languages.tat": "Ταταρικά",
+ "languages.twi": "Twi",
+ "languages.tah": "Tahitian",
+ "languages.uig": "Ουιγούρων",
+ "languages.ukr": "Ουκρανός",
+ "languages.urd": "Ούρντου",
+ "languages.uzb": "Ουζμπεκιστάν",
+ "languages.ven": "Venda",
+ "languages.vie": "Βιετνάμ",
+ "languages.vol": "Βόλαπικ",
+ "languages.win": "Βαλλονίας",
+ "languages.cym": "Ουαλίας",
+ "languages.wol": "Γουόλοφ",
+ "languages.fry": "Δυτικά Φριζιανά",
+ "languages.xho": "Xhosa",
+ "languages.yid": "Γίντις",
+ "languages.yor": "Γιορούμπα",
+ "languages.zha": "Zhuang",
+ "languages.zul": "Ζουλού",
+ "DO_NOT_TRANSLATE": "end"
+}
diff --git a/sources/lang/Localization_en.json b/sources/lang/Localization_en.json
new file mode 100644
index 0000000..0074eaf
--- /dev/null
+++ b/sources/lang/Localization_en.json
@@ -0,0 +1,293 @@
+{
+ "about.title": "About COPS",
+ "allbooks.alphabetical.many": "Alphabetical index of the {0} books",
+ "allbooks.alphabetical.none": "Alphabetical index of absolutely no books",
+ "allbooks.alphabetical.one": "Alphabetical index of the single book",
+ "allbooks.title": "All books",
+ "authors.alphabetical.many": "Alphabetical index of the {0} authors",
+ "authors.alphabetical.none": "Alphabetical index of absolutely no authors",
+ "authors.alphabetical.one": "Alphabetical index of the single author",
+ "authors.title": "Authors",
+ "authorword.many": "{0} authors",
+ "authorword.none": "No authors",
+ "authorword.one": "1 author",
+ "bookentry.author": "{0} by {1}",
+ "bookword.many": "{0} books",
+ "bookword.none": "No books",
+ "bookword.one": "1 book",
+ "bookword.title": "Books",
+ "cog.alternate": "Search, sort and filters",
+ "content.series": "Series:",
+ "content.series.data": "Book {0} in the {1} series",
+ "content.summary": "Summary",
+ "customcolumn.boolean.no": "No",
+ "customcolumn.boolean.unknown": "Not Set",
+ "customcolumn.boolean.yes": "Yes",
+ "customcolumn.date.format": "Y-m-d",
+ "customcolumn.date.unknown": "Not Set",
+ "customcolumn.description": "Custom column '{0}'",
+ "customcolumn.description.bool": "Index of a boolean value",
+ "customcolumn.description.enum.many": "Alphabetical index of the {0} values",
+ "customcolumn.description.enum.none": "Alphabetical index of absolutely no values",
+ "customcolumn.description.enum.one": "Alphabetical index of one value",
+ "customcolumn.description.rating": "Index of ratings",
+ "customcolumn.description.series.many": "Alphabetical index of the {0} series",
+ "customcolumn.description.series.none": "Alphabetical index of absolutely no series",
+ "customcolumn.description.series.one": "Alphabetical index of the single series",
+ "customcolumn.enum.unknown": "Not Set",
+ "customcolumn.float.unknown": "Not Set",
+ "customcolumn.int.unknown": "Not Set",
+ "customcolumn.rating.unknown": "Not Set",
+ "customcolumn.stars.many": "{0} Stars",
+ "customcolumn.stars.none": "No Stars",
+ "customcolumn.stars.one": "1 Star",
+ "customize.email": "Set your email (to allow book emailing)",
+ "customize.fancybox": "Use Lightbox (books load in floating frame)",
+ "customize.filter": "Enable tag filtering",
+ "customize.ignored": "Ignored categories",
+ "customize.paging": "Max number of books per page (-1 to disable)",
+ "customize.style": "Theme",
+ "customize.title": "Customize COPS UI",
+ "home.alternate": "Home",
+ "i18n.coversection": "Cover",
+ "language.title": "Language",
+ "languages.alphabetical.many": "Alphabetical index of the {0} languages",
+ "languages.alphabetical.none": "Alphabetical index of absolutely no languages",
+ "languages.alphabetical.one": "Alphabetical index of the single language",
+ "languages.title": "Languages",
+ "mail.messagenotsent": "Message could not be sent.",
+ "mail.messagesent": "Message has been sent",
+ "paging.next.alternate": "Next",
+ "paging.previous.alternate": "Previous",
+ "permalink.alternate": "Permalink",
+ "pubdate.title": "Publication year",
+ "publisher.name": "Publisher",
+ "publishers.alphabetical.many": "Alphabetical index of the {0} publishers",
+ "publishers.alphabetical.none": "Alphabetical index of absolutely no publishers",
+ "publishers.alphabetical.one": "Alphabetical index of the single publisher",
+ "publishers.title": "Publishers",
+ "publisherword.many": "{0} publishers",
+ "publisherword.none": "No publishers",
+ "publisherword.one": "1 publisher",
+ "ratings.many": "{0} ratings",
+ "ratings.none": "no ratings",
+ "ratings.one": "1 rating",
+ "ratings.title": "Ratings",
+ "ratingword.many": "{0} stars",
+ "ratingword.none": "No star",
+ "ratingword.one": "1 star",
+ "recent.list": "{0} most recent books",
+ "recent.title": "Recent additions",
+ "search.alternate": "Search",
+ "search.result": "Search result for *{0}*",
+ "search.result.author": "Search result for *{0}* in authors",
+ "search.result.book": "Search result for *{0}* in books",
+ "search.result.publisher": "Search result for *{0}* in publishers",
+ "search.result.series": "Search result for *{0}* in series",
+ "search.result.tag": "Search result for *{0}* in tags",
+ "search.sortorder.asc": "Asc",
+ "search.sortorder.desc": "Desc",
+ "series.alphabetical.many": "Alphabetical index of the {0} series",
+ "series.alphabetical.none": "Alphabetical index of absolutely no series",
+ "series.alphabetical.one": "Alphabetical index of the single series",
+ "series.title": "Series",
+ "seriesword.many": "{0} series",
+ "seriesword.none": "No series",
+ "seriesword.one": "1 series",
+ "sort.alternate": "Sort",
+ "splitByLetter.book.other": "Other books",
+ "splitByLetter.letter": "{0} starting with {1}",
+ "tags.alphabetical.many": "Alphabetical index of the {0} tags",
+ "tags.alphabetical.none": "Alphabetical index of absolutely no tags",
+ "tags.alphabetical.one": "Alphabetical index of the single tag",
+ "tags.title": "Tags",
+ "tagword.many": "{0} tags",
+ "tagword.none": "No tags",
+ "tagword.one": "1 tag",
+ "tagword.title": "Tags",
+ "languages.abk": "Abkhaz",
+ "languages.aaf": "Afar",
+ "languages.afr": "Afrikaans",
+ "languages.aka": "Akan",
+ "languages.sqi": "Albanian",
+ "languages.amh": "Amharic",
+ "languages.ara": "Arabic",
+ "languages.arg": "Aragonese",
+ "languages.hye": "Armenian",
+ "languages.asm": "Assamese",
+ "languages.ava": "Avaric",
+ "languages.ave": "Avestan",
+ "languages.aym": "Aymara",
+ "languages.aze": "Azerbaijani",
+ "languages.bam": "Bambara",
+ "languages.bak": "Bashkir",
+ "languages.eus": "Basque",
+ "languages.bel": "Belarusian",
+ "languages.ben": "Bengali",
+ "languages.bih": "Bihari",
+ "languages.bis": "Bislama",
+ "languages.bos": "Bosnian",
+ "languages.bre": "Breton",
+ "languages.bul": "Bulgarian",
+ "languages.mya": "Burmese",
+ "languages.cat": "Catalan",
+ "languages.cha": "Chamorro",
+ "languages.che": "Chechen",
+ "languages.nya": "Chichewa",
+ "languages.zho": "Chinese",
+ "languages.chv": "Chuvash",
+ "languages.cor": "Cornish",
+ "languages.cos": "Corsican",
+ "languages.cre": "Cree",
+ "languages.hrv": "Croatian",
+ "languages.ces": "Czech",
+ "languages.dan": "Danish",
+ "languages.div": "Divehi",
+ "languages.nld": "Dutch",
+ "languages.dzo": "Dzongkha",
+ "languages.eng": "English",
+ "languages.epo": "Esperanto",
+ "languages.est": "Estonian",
+ "languages.ewe": "Ewe",
+ "languages.fao": "Faroese",
+ "languages.fij": "Fijian",
+ "languages.fin": "Finnish",
+ "languages.fra": "French",
+ "languages.ful": "Fula",
+ "languages.glg": "Galician",
+ "languages.kat": "Georgian",
+ "languages.deu": "German",
+ "languages.ell": "Greek",
+ "languages.grn": "Guaraní",
+ "languages.guj": "Gujarati",
+ "languages.hat": "Haitian",
+ "languages.hau": "Hausa",
+ "languages.hed": "Hebrew",
+ "languages.her": "Herero",
+ "languages.hin": "Hindi",
+ "languages.hmo": "Hiri Motu",
+ "languages.hun": "Hungarian",
+ "languages.ina": "Interlingua",
+ "languages.ind": "Indonesian",
+ "languages.ile": "Interlingue",
+ "languages.gle": "Irish",
+ "languages.ibo": "Igbo",
+ "languages.ipk": "Inupiaq",
+ "languages.ido": "Ido",
+ "languages.isl": "Icelandic",
+ "languages.ita": "Italian",
+ "languages.iku": "Inuktitut",
+ "languages.jpn": "Japanese",
+ "languages.jav": "Javanese",
+ "languages.kal": "Kalaallisut",
+ "languages.kan": "Kannada",
+ "languages.kau": "Kanuri",
+ "languages.kas": "Kashmiri",
+ "languages.kaz": "Kazakh",
+ "languages.khm": "Khmer",
+ "languages.kik": "Kikuyu",
+ "languages.kin": "Kinyarwanda",
+ "languages.kir": "Kyrgyz",
+ "languages.kom": "Komi",
+ "languages.kon": "Kongo",
+ "languages.kor": "Korean",
+ "languages.kur": "Kurdish",
+ "languages.kua": "Kwanyama",
+ "languages.lat": "Latin",
+ "languages.ltz": "Luxembourgish",
+ "languages.lug": "Ganda",
+ "languages.lim": "Limburgish",
+ "languages.lin": "Lingala",
+ "languages.lao": "Lao",
+ "languages.lit": "Lithuanian",
+ "languages.lub": "Luba-Katanga",
+ "languages.lav": "Latvian",
+ "languages.glv": "Manx",
+ "languages.mkd": "Macedonian",
+ "languages.mlg": "Malagasy",
+ "languages.msa": "Malay",
+ "languages.mal": "Malayalam",
+ "languages.mlt": "Maltese",
+ "languages.mri": "Māori",
+ "languages.mar": "Marathi",
+ "languages.mah": "Marshallese",
+ "languages.mon": "Mongolian",
+ "languages.nau": "Nauru",
+ "languages.nav": "Navajo",
+ "languages.nob": "Norwegian Bokmål",
+ "languages.nde": "North Ndebele",
+ "languages.nep": "Nepali",
+ "languages.ndo": "Ndonga",
+ "languages.nno": "Norwegian Nynorsk",
+ "languages.nor": "Norwegian",
+ "languages.iii": "Nuosu",
+ "languages.nbl": "South Ndebele",
+ "languages.oci": "Occitan",
+ "languages.oji": "Ojibwe",
+ "languages.chu": "Old Church Slavonic",
+ "languages.orm": "Oromo",
+ "languages.ori": "Oriya",
+ "languages.oss": "Ossetian",
+ "languages.pan": "Panjabi",
+ "languages.pli": "Pāli",
+ "languages.fas": "Persian",
+ "languages.pol": "Polish",
+ "languages.pus": "Pashto",
+ "languages.por": "Portuguese",
+ "languages.que": "Quechua",
+ "languages.roh": "Romansh",
+ "languages.run": "Kirundi",
+ "languages.ron": "Romanian",
+ "languages.rus": "Russian",
+ "languages.san": "Sanskrit",
+ "languages.srd": "Sardinian",
+ "languages.snd": "Sindhi",
+ "languages.sme": "Northern Sami",
+ "languages.smo": "Samoan",
+ "languages.sag": "Sango",
+ "languages.srp": "Serbian",
+ "languages.gla": "Scottish Gaelic",
+ "languages.sna": "Shona",
+ "languages.sin": "Sinhala",
+ "languages.slk": "Slovak",
+ "languages.slv": "Slovene",
+ "languages.som": "Somali",
+ "languages.sot": "Southern Sotho",
+ "languages.spa": "Spanish",
+ "languages.sun": "Sundanese",
+ "languages.swa": "Swahili",
+ "languages.ssw": "Swati",
+ "languages.swe": "Swedish",
+ "languages.tam": "Tamil",
+ "languages.tel": "Telugu",
+ "languages.tgk": "Tajik",
+ "languages.tha": "Thai",
+ "languages.tir": "Tigrinya",
+ "languages.bod": "Tibetan Standard",
+ "languages.tuk": "Turkmen",
+ "languages.tgl": "Tagalog",
+ "languages.tsn": "Tswana",
+ "languages.ton": "Tonga",
+ "languages.tur": "Turkish",
+ "languages.tso": "Tsonga",
+ "languages.tat": "Tatar",
+ "languages.twi": "Twi",
+ "languages.tah": "Tahitian",
+ "languages.uig": "Uighur",
+ "languages.ukr": "Ukrainian",
+ "languages.urd": "Urdu",
+ "languages.uzb": "Uzbek",
+ "languages.ven": "Venda",
+ "languages.vie": "Vietnamese",
+ "languages.vol": "Volapük",
+ "languages.win": "Walloon",
+ "languages.cym": "Welsh",
+ "languages.wol": "Wolof",
+ "languages.fry": "Western Frisian",
+ "languages.xho": "Xhosa",
+ "languages.yid": "Yiddish",
+ "languages.yor": "Yoruba",
+ "languages.zha": "Zhuang",
+ "languages.zul": "Zulu",
+ "DO_NOT_TRANSLATE": "end"
+}
diff --git a/sources/lang/Localization_es.json b/sources/lang/Localization_es.json
new file mode 100644
index 0000000..d68138e
--- /dev/null
+++ b/sources/lang/Localization_es.json
@@ -0,0 +1,293 @@
+{
+ "about.title": "Acerca de COPS",
+ "allbooks.alphabetical.many": "Listado alfabético de {0} libros",
+ "allbooks.alphabetical.none": "Listado sin libros",
+ "allbooks.alphabetical.one": "Listado con un libro",
+ "allbooks.title": "Todos los libros",
+ "authors.alphabetical.many": "Listado alfabético de {0} autores",
+ "authors.alphabetical.none": "Listado sin autores",
+ "authors.alphabetical.one": "Listado con un autor",
+ "authors.title": "Autores",
+ "authorword.many": "{0} autores",
+ "authorword.none": "Sin autor",
+ "authorword.one": "1 autor",
+ "bookentry.author": "{0} de {1}",
+ "bookword.many": "{0} libros",
+ "bookword.none": "Sin libros",
+ "bookword.one": "1 libro",
+ "bookword.title": "Libros",
+ "cog.alternate": "Búsqueda, ordenación y filtros",
+ "content.series": "Series:",
+ "content.series.data": "Libro {0} en la {1} serie",
+ "content.summary": "Resumen",
+ "customcolumn.boolean.no": "No",
+ "customcolumn.boolean.unknown": "No Establecido",
+ "customcolumn.boolean.yes": "Sí",
+ "customcolumn.date.format": "Y-m-d",
+ "customcolumn.date.unknown": "No Establecido",
+ "customcolumn.description": "Custom column '{0}'",
+ "customcolumn.description.bool": "Index of a boolean value",
+ "customcolumn.description.enum.many": "Alphabetical index of the {0} values",
+ "customcolumn.description.enum.none": "Alphabetical index of absolutely no values",
+ "customcolumn.description.enum.one": "Alphabetical index of one value",
+ "customcolumn.description.rating": "Index of ratings",
+ "customcolumn.description.series.many": "Listado alfabético de {0} series",
+ "customcolumn.description.series.none": "Listado sin series",
+ "customcolumn.description.series.one": "Listado de una serie",
+ "customcolumn.enum.unknown": "No Establecido",
+ "customcolumn.float.unknown": "No Establecido",
+ "customcolumn.int.unknown": "No Establecido",
+ "customcolumn.rating.unknown": "No Establecido",
+ "customcolumn.stars.many": "{0} Stars",
+ "customcolumn.stars.none": "No Stars",
+ "customcolumn.stars.one": "1 Star",
+ "customize.email": "Su correo (Para envío de libros por correo)",
+ "customize.fancybox": "Usar LightBox",
+ "customize.filter": "Activar filtro por etiqueta",
+ "customize.ignored": "Ocultar categorías",
+ "customize.paging": "Max. número de libros por página (-1 para desactivar)",
+ "customize.style": "Tema",
+ "customize.title": "Configurar COPS UI",
+ "home.alternate": "Inicio",
+ "i18n.coversection": "Cubierta",
+ "language.title": "Idioma",
+ "languages.alphabetical.many": "Listado alfabético de {0} idiomas",
+ "languages.alphabetical.none": "Listado sin idiomas disponibles",
+ "languages.alphabetical.one": "Listado con un idioma",
+ "languages.title": "Idiomas",
+ "mail.messagenotsent": "El mensaje no pudo enviarse.",
+ "mail.messagesent": "El mensaje se ha enviado",
+ "paging.next.alternate": "Siguiente",
+ "paging.previous.alternate": "Anterior",
+ "permalink.alternate": "Enlace permanente",
+ "pubdate.title": "Año de publicación",
+ "publisher.name": "Editorial",
+ "publishers.alphabetical.many": "Listado alfabético de {0} editoriales",
+ "publishers.alphabetical.none": "Listado sin editoriales",
+ "publishers.alphabetical.one": "Listado con una editorial",
+ "publishers.title": "Editoriales",
+ "publisherword.many": "{0} editoriales",
+ "publisherword.none": "No hay editoriales",
+ "publisherword.one": "1 editorial",
+ "ratings.many": "{0} valoraciones",
+ "ratings.none": "sin valoraciones",
+ "ratings.one": "1 valoración",
+ "ratings.title": "Puntuaciones",
+ "ratingword.many": "{0} estrellas",
+ "ratingword.none": "Sin estrellas",
+ "ratingword.one": "1 estrella",
+ "recent.list": "{0} libros más recientes",
+ "recent.title": "Añadidos recientemente",
+ "search.alternate": "Buscar",
+ "search.result": "Resultados de buscar *{0}*",
+ "search.result.author": "Resultados de buscar *{0}* en autores",
+ "search.result.book": "Resultados de buscar *{0}* en libros",
+ "search.result.publisher": "Resultados de buscar *{0}* en editoriales",
+ "search.result.series": "Resultados de buscar *{0}* en series",
+ "search.result.tag": "Resultados de buscar *{0}* en etiquetas",
+ "search.sortorder.asc": "Asc",
+ "search.sortorder.desc": "Desc",
+ "series.alphabetical.many": "Listado alfabético de {0} series",
+ "series.alphabetical.none": "Listado sin series",
+ "series.alphabetical.one": "Listado de una serie",
+ "series.title": "Series",
+ "seriesword.many": "{0} series",
+ "seriesword.none": "Sin series",
+ "seriesword.one": "1 serie",
+ "sort.alternate": "Ordenar",
+ "splitByLetter.book.other": "Otros libros",
+ "splitByLetter.letter": "{0} que empiezan por {1}",
+ "tags.alphabetical.many": "Listado alfabético de las {0} etiquetas",
+ "tags.alphabetical.none": "Listado sin etiquetas",
+ "tags.alphabetical.one": "Listado alfabético de la unica etiqueta",
+ "tags.title": "Etiquetas",
+ "tagword.many": "etiquetas",
+ "tagword.none": "Sin etiquetas",
+ "tagword.one": "1 etiqueta",
+ "tagword.title": "Etiquetas",
+ "languages.abk": "Abjasio",
+ "languages.aaf": "Afar",
+ "languages.afr": "Afrikáans",
+ "languages.aka": "Ákan",
+ "languages.sqi": "Albano",
+ "languages.amh": "Amharic",
+ "languages.ara": "Arabe",
+ "languages.arg": "Aragonés",
+ "languages.hye": "Armenio",
+ "languages.asm": "Assamese",
+ "languages.ava": "Avaric",
+ "languages.ave": "Avestan",
+ "languages.aym": "Aymara",
+ "languages.aze": "Azerbaijani",
+ "languages.bam": "Bambara",
+ "languages.bak": "Bashkir",
+ "languages.eus": "Euskera",
+ "languages.bel": "Belarusian",
+ "languages.ben": "Bengalí",
+ "languages.bih": "Bihari",
+ "languages.bis": "Bislama",
+ "languages.bos": "Bosnio",
+ "languages.bre": "Bretón",
+ "languages.bul": "Bulgaro",
+ "languages.mya": "Burmese",
+ "languages.cat": "Catalán",
+ "languages.cha": "Chamorro",
+ "languages.che": "Chechen",
+ "languages.nya": "Chichewa",
+ "languages.zho": "Chino",
+ "languages.chv": "Chuvash",
+ "languages.cor": "Cornish",
+ "languages.cos": "Corsican",
+ "languages.cre": "Cree",
+ "languages.hrv": "Croata",
+ "languages.ces": "Checo",
+ "languages.dan": "Danés",
+ "languages.div": "Divehi",
+ "languages.nld": "Dutch",
+ "languages.dzo": "Dzongkha",
+ "languages.eng": "Inglés",
+ "languages.epo": "Esperanto",
+ "languages.est": "Estonio",
+ "languages.ewe": "Ewe",
+ "languages.fao": "Faroese",
+ "languages.fij": "Fiji",
+ "languages.fin": "Finlandés",
+ "languages.fra": "Francés",
+ "languages.ful": "Fula",
+ "languages.glg": "Gallego",
+ "languages.kat": "Georgiano",
+ "languages.deu": "Alemán",
+ "languages.ell": "Griego",
+ "languages.grn": "Guaraní",
+ "languages.guj": "Gujarati",
+ "languages.hat": "Haitiano",
+ "languages.hau": "Hausa",
+ "languages.hed": "Hebreo",
+ "languages.her": "Herero",
+ "languages.hin": "Hindú",
+ "languages.hmo": "Hiri Motu",
+ "languages.hun": "Hungaro",
+ "languages.ina": "Interlingua",
+ "languages.ind": "Indonesio",
+ "languages.ile": "Interlingue",
+ "languages.gle": "Irlandés",
+ "languages.ibo": "Igbo",
+ "languages.ipk": "Inupiaq",
+ "languages.ido": "Ido",
+ "languages.isl": "Islandés",
+ "languages.ita": "Italiano",
+ "languages.iku": "Inuktitut",
+ "languages.jpn": "Japonés",
+ "languages.jav": "Javanés",
+ "languages.kal": "Kalaallisut",
+ "languages.kan": "Kannada",
+ "languages.kau": "Kanuri",
+ "languages.kas": "Kashmiri",
+ "languages.kaz": "Kazakh",
+ "languages.khm": "Khmer",
+ "languages.kik": "Kikuyu",
+ "languages.kin": "Kinyarwanda",
+ "languages.kir": "Kyrgyz",
+ "languages.kom": "Komi",
+ "languages.kon": "Congoleño",
+ "languages.kor": "Coreano",
+ "languages.kur": "Kurdo",
+ "languages.kua": "Kwanyama",
+ "languages.lat": "Latín",
+ "languages.ltz": "Luxemburgés",
+ "languages.lug": "Ganda",
+ "languages.lim": "Limburgish",
+ "languages.lin": "Lingala",
+ "languages.lao": "Lao",
+ "languages.lit": "Lituano",
+ "languages.lub": "Luba-Katanga",
+ "languages.lav": "Latvian",
+ "languages.glv": "Manx",
+ "languages.mkd": "Macedonio",
+ "languages.mlg": "Malagasy",
+ "languages.msa": "Malayo",
+ "languages.mal": "Malayalam",
+ "languages.mlt": "Maltés",
+ "languages.mri": "Maorí",
+ "languages.mar": "Marathi",
+ "languages.mah": "Marshallese",
+ "languages.mon": "Mongol",
+ "languages.nau": "Nauru",
+ "languages.nav": "Navajo",
+ "languages.nob": "Norwego Bokmål",
+ "languages.nde": "North Ndebele",
+ "languages.nep": "Nepalí",
+ "languages.ndo": "Ndonga",
+ "languages.nno": "Norwego Nynorsk",
+ "languages.nor": "Noruego",
+ "languages.iii": "Nuosu",
+ "languages.nbl": "South Ndebele",
+ "languages.oci": "Occitan",
+ "languages.oji": "Ojibwe",
+ "languages.chu": "Old Church Slavonic",
+ "languages.orm": "Oromo",
+ "languages.ori": "Oriya",
+ "languages.oss": "Ossetian",
+ "languages.pan": "Panjabi",
+ "languages.pli": "Pāli",
+ "languages.fas": "Persa",
+ "languages.pol": "Polaco",
+ "languages.pus": "Pashto",
+ "languages.por": "Portugués",
+ "languages.que": "Quechua",
+ "languages.roh": "Romansh",
+ "languages.run": "Kirundi",
+ "languages.ron": "Rumano",
+ "languages.rus": "Ruso",
+ "languages.san": "Sánscrito",
+ "languages.srd": "Sardinian",
+ "languages.snd": "Sindhi",
+ "languages.sme": "Northern Sami",
+ "languages.smo": "Samoan",
+ "languages.sag": "Sango",
+ "languages.srp": "Serbio",
+ "languages.gla": "Escocés Gaélico",
+ "languages.sna": "Shona",
+ "languages.sin": "Sinhala",
+ "languages.slk": "Slovak",
+ "languages.slv": "Sloveno",
+ "languages.som": "Somali",
+ "languages.sot": "Southern Sotho",
+ "languages.spa": "Español",
+ "languages.sun": "Sundanese",
+ "languages.swa": "Swahili",
+ "languages.ssw": "Swati",
+ "languages.swe": "Sueco",
+ "languages.tam": "Tamil",
+ "languages.tel": "Telugu",
+ "languages.tgk": "Tajik",
+ "languages.tha": "Thai",
+ "languages.tir": "Tigrinya",
+ "languages.bod": "Tibetan Standard",
+ "languages.tuk": "Turkmen",
+ "languages.tgl": "Tagalog",
+ "languages.tsn": "Tswana",
+ "languages.ton": "Tonga",
+ "languages.tur": "Turco",
+ "languages.tso": "Tsonga",
+ "languages.tat": "Tatar",
+ "languages.twi": "Twi",
+ "languages.tah": "Tahitiano",
+ "languages.uig": "Uighur",
+ "languages.ukr": "Ucrainiano",
+ "languages.urd": "Urdu",
+ "languages.uzb": "Uzbek",
+ "languages.ven": "Venda",
+ "languages.vie": "Vietnamita",
+ "languages.vol": "Volapük",
+ "languages.win": "Valón",
+ "languages.cym": "Welsh",
+ "languages.wol": "Wolof",
+ "languages.fry": "Western Frisian",
+ "languages.xho": "Xhosa",
+ "languages.yid": "Yiddish",
+ "languages.yor": "Yoruba",
+ "languages.zha": "Zhuang",
+ "languages.zul": "Zulú",
+ "DO_NOT_TRANSLATE": "end"
+}
diff --git a/sources/lang/Localization_eu.json b/sources/lang/Localization_eu.json
new file mode 100644
index 0000000..e69abf8
--- /dev/null
+++ b/sources/lang/Localization_eu.json
@@ -0,0 +1,293 @@
+{
+ "about.title": "COPS-i buruz",
+ "allbooks.alphabetical.many": "{0} libururen zerrenda alfabetikoa",
+ "allbooks.alphabetical.none": "Liburu gabeko zerrenda",
+ "allbooks.alphabetical.one": "Liburu bakarreko zerrenda",
+ "allbooks.title": "Liburu guztiak",
+ "authors.alphabetical.many": "{0} egileren zerrenda alfabetikoa",
+ "authors.alphabetical.none": "Egile gabeko zerrenda",
+ "authors.alphabetical.one": "Egile bakarreko zerrenda",
+ "authors.title": "Egileak",
+ "authorword.many": "{0} egile",
+ "authorword.none": "Egile gabe",
+ "authorword.one": "Egile 1",
+ "bookentry.author": "{1}-tik {0}",
+ "bookword.many": "{0} liburu",
+ "bookword.none": "Liburu gabe",
+ "bookword.one": "Liburu 1",
+ "bookword.title": "Liburuak",
+ "cog.alternate": "Bilaketa, ordena eta filtroak",
+ "content.series": "Sailak:",
+ "content.series.data": "{0} liburu {1} sailekoak",
+ "content.summary": "Laburpena:",
+ "customcolumn.boolean.no": "Ez",
+ "customcolumn.boolean.unknown": "Not Set",
+ "customcolumn.boolean.yes": "Bai",
+ "customcolumn.date.format": "Y-m-d",
+ "customcolumn.date.unknown": "Not Set",
+ "customcolumn.description": "Custom column '{0}'",
+ "customcolumn.description.bool": "Index of a boolean value",
+ "customcolumn.description.enum.many": "Alphabetical index of the {0} values",
+ "customcolumn.description.enum.none": "Alphabetical index of absolutely no values",
+ "customcolumn.description.enum.one": "Alphabetical index of one value",
+ "customcolumn.description.rating": "Index of ratings",
+ "customcolumn.description.series.many": "{0} sailen zerrenda alfabetikoa",
+ "customcolumn.description.series.none": "Sail gabeko zerrenda",
+ "customcolumn.description.series.one": "Sail bakarreko zerrenda",
+ "customcolumn.enum.unknown": "Not Set",
+ "customcolumn.float.unknown": "Not Set",
+ "customcolumn.int.unknown": "Not Set",
+ "customcolumn.rating.unknown": "Not Set",
+ "customcolumn.stars.many": "{0} Stars",
+ "customcolumn.stars.none": "No Stars",
+ "customcolumn.stars.one": "1 Star",
+ "customize.email": "Zure posta elektronikoa (posta bidezko liburu bidalketarako)",
+ "customize.fancybox": "LightBox erabili",
+ "customize.filter": "Etiketa bidezko filtroa aktibatu",
+ "customize.ignored": "Kategoriak ezkutatu",
+ "customize.paging": "Liburu kopuru maximoa orrialdeko (-1 desaktibatzeko)",
+ "customize.style": "Gaia",
+ "customize.title": "COPS UI konfiguratu",
+ "home.alternate": "Hasiera",
+ "i18n.coversection": "Azala",
+ "language.title": "Hizkuntza",
+ "languages.alphabetical.many": "{0} hizkuntzen zerrenda alfabetikoa",
+ "languages.alphabetical.none": "Hizkutzarik gabeko zerrenda",
+ "languages.alphabetical.one": "Hizkuntza bakarreko zerrenda",
+ "languages.title": "Hizkuntza",
+ "mail.messagenotsent": "Mezua ezin izan da bidali.",
+ "mail.messagesent": "Mezua bidali da",
+ "paging.next.alternate": "Hurrengoa",
+ "paging.previous.alternate": "Aurrekoa",
+ "permalink.alternate": "Esteka iraunkorra",
+ "pubdate.title": "Argitaratze urtea",
+ "publisher.name": "Argitaletxea",
+ "publishers.alphabetical.many": "{0} argitaletxeren zerrenda alfabetikoa",
+ "publishers.alphabetical.none": "Argitaletxe gabeko zerrenda",
+ "publishers.alphabetical.one": "Argitaletxe bakarreko zerrenda",
+ "publishers.title": "Argitaletxeak",
+ "publisherword.many": "{0} argitaletxe",
+ "publisherword.none": "Ez dago argitaletxerik",
+ "publisherword.one": "Argitaletxe 1",
+ "ratings.many": "{0} puntuaketa",
+ "ratings.none": "Ez dago puntuaketarik",
+ "ratings.one": "Puntuaketa 1",
+ "ratings.title": "Puntuaketak",
+ "ratingword.many": "{0} izar",
+ "ratingword.none": "Izar gabe",
+ "ratingword.one": "Izar 1",
+ "recent.list": "{0} liburu berrienak",
+ "recent.title": "Berriki zerrendatuak",
+ "search.alternate": "Bilatu",
+ "search.result": "*{0}* bilaketaren emaitzak",
+ "search.result.author": "*{0}* bilaketaren emaitzak egileetan",
+ "search.result.book": "*{0}* bilaketaren emaitzak liburuetan",
+ "search.result.publisher": "*{0}* bilaketaren emaitzak argitaletxetan",
+ "search.result.series": "*{0}* bilaketaren emaitzak sailetan",
+ "search.result.tag": "*{0}* bilaketaren emaitzak etiketetan",
+ "search.sortorder.asc": "Gora",
+ "search.sortorder.desc": "Behera",
+ "series.alphabetical.many": "{0} sailen zerrenda alfabetikoa",
+ "series.alphabetical.none": "Sail gabeko zerrenda",
+ "series.alphabetical.one": "Sail bakarreko zerrenda",
+ "series.title": "Sailak",
+ "seriesword.many": "{0} sail",
+ "seriesword.none": "Sail gabe",
+ "seriesword.one": "Sail 1",
+ "sort.alternate": "Ordenatu",
+ "splitByLetter.book.other": "Beste liburu batzuk",
+ "splitByLetter.letter": "{1}-tik haste d(ir)en {0}",
+ "tags.alphabetical.many": "{0} etiketen zerrenda alfabetikoa",
+ "tags.alphabetical.none": "Etiketa gabeko zerrenda",
+ "tags.alphabetical.one": "Etiketa bakarreko zerrenda",
+ "tags.title": "Etiketak",
+ "tagword.many": "{0} etiketa",
+ "tagword.none": "Etiketa gabe",
+ "tagword.one": "Etiketa 1",
+ "tagword.title": "Etiketak",
+ "languages.abk": "Abkhaz",
+ "languages.aaf": "Afar",
+ "languages.afr": "Afrikaans",
+ "languages.aka": "Akan",
+ "languages.sqi": "Albanian",
+ "languages.amh": "Amharic",
+ "languages.ara": "Arabic",
+ "languages.arg": "Aragonese",
+ "languages.hye": "Armenian",
+ "languages.asm": "Assamese",
+ "languages.ava": "Avaric",
+ "languages.ave": "Avestan",
+ "languages.aym": "Aymara",
+ "languages.aze": "Azerbaijani",
+ "languages.bam": "Bambara",
+ "languages.bak": "Bashkir",
+ "languages.eus": "Euskara",
+ "languages.bel": "Belarusian",
+ "languages.ben": "Bengali",
+ "languages.bih": "Bihari",
+ "languages.bis": "Bislama",
+ "languages.bos": "Bosnian",
+ "languages.bre": "Breton",
+ "languages.bul": "Bulgarian",
+ "languages.mya": "Burmese",
+ "languages.cat": "Katalana",
+ "languages.cha": "Chamorro",
+ "languages.che": "Chechen",
+ "languages.nya": "Chichewa",
+ "languages.zho": "Chinese",
+ "languages.chv": "Chuvash",
+ "languages.cor": "Cornish",
+ "languages.cos": "Corsican",
+ "languages.cre": "Cree",
+ "languages.hrv": "Croatian",
+ "languages.ces": "Czech",
+ "languages.dan": "Danish",
+ "languages.div": "Divehi",
+ "languages.nld": "Dutch",
+ "languages.dzo": "Dzongkha",
+ "languages.eng": "Ingelesa",
+ "languages.epo": "Esperanto",
+ "languages.est": "Estonian",
+ "languages.ewe": "Ewe",
+ "languages.fao": "Faroese",
+ "languages.fij": "Fijian",
+ "languages.fin": "Finnish",
+ "languages.fra": "Frantzesa",
+ "languages.ful": "Fula",
+ "languages.glg": "Galician",
+ "languages.kat": "Georgian",
+ "languages.deu": "Alemana",
+ "languages.ell": "Greek",
+ "languages.grn": "Guaraní",
+ "languages.guj": "Gujarati",
+ "languages.hat": "Haitian",
+ "languages.hau": "Hausa",
+ "languages.hed": "Hebrew",
+ "languages.her": "Herero",
+ "languages.hin": "Hindi",
+ "languages.hmo": "Hiri Motu",
+ "languages.hun": "Hungarian",
+ "languages.ina": "Interlingua",
+ "languages.ind": "Indonesian",
+ "languages.ile": "Interlingue",
+ "languages.gle": "Irish",
+ "languages.ibo": "Igbo",
+ "languages.ipk": "Inupiaq",
+ "languages.ido": "Ido",
+ "languages.isl": "Icelandic",
+ "languages.ita": "Italiera",
+ "languages.iku": "Inuktitut",
+ "languages.jpn": "Japanese",
+ "languages.jav": "Javanese",
+ "languages.kal": "Kalaallisut",
+ "languages.kan": "Kannada",
+ "languages.kau": "Kanuri",
+ "languages.kas": "Kashmiri",
+ "languages.kaz": "Kazakh",
+ "languages.khm": "Khmer",
+ "languages.kik": "Kikuyu",
+ "languages.kin": "Kinyarwanda",
+ "languages.kir": "Kyrgyz",
+ "languages.kom": "Komi",
+ "languages.kon": "Kongo",
+ "languages.kor": "Korean",
+ "languages.kur": "Kurdish",
+ "languages.kua": "Kwanyama",
+ "languages.lat": "Latina",
+ "languages.ltz": "Luxembourgish",
+ "languages.lug": "Ganda",
+ "languages.lim": "Limburgish",
+ "languages.lin": "Lingala",
+ "languages.lao": "Lao",
+ "languages.lit": "Lithuanian",
+ "languages.lub": "Luba-Katanga",
+ "languages.lav": "Latvian",
+ "languages.glv": "Manx",
+ "languages.mkd": "Macedonian",
+ "languages.mlg": "Malagasy",
+ "languages.msa": "Malay",
+ "languages.mal": "Malayalam",
+ "languages.mlt": "Maltese",
+ "languages.mri": "Māori",
+ "languages.mar": "Marathi",
+ "languages.mah": "Marshallese",
+ "languages.mon": "Mongolian",
+ "languages.nau": "Nauru",
+ "languages.nav": "Navajo",
+ "languages.nob": "Norwegian Bokmål",
+ "languages.nde": "North Ndebele",
+ "languages.nep": "Nepali",
+ "languages.ndo": "Ndonga",
+ "languages.nno": "Norwegian Nynorsk",
+ "languages.nor": "Norwegian",
+ "languages.iii": "Nuosu",
+ "languages.nbl": "South Ndebele",
+ "languages.oci": "Occitan",
+ "languages.oji": "Ojibwe",
+ "languages.chu": "Old Church Slavonic",
+ "languages.orm": "Oromo",
+ "languages.ori": "Oriya",
+ "languages.oss": "Ossetian",
+ "languages.pan": "Panjabi",
+ "languages.pli": "Pāli",
+ "languages.fas": "Persian",
+ "languages.pol": "Polish",
+ "languages.pus": "Pashto",
+ "languages.por": "Portuguese",
+ "languages.que": "Quechua",
+ "languages.roh": "Romansh",
+ "languages.run": "Kirundi",
+ "languages.ron": "Romanian",
+ "languages.rus": "Russian",
+ "languages.san": "Sanskrit",
+ "languages.srd": "Sardinian",
+ "languages.snd": "Sindhi",
+ "languages.sme": "Northern Sami",
+ "languages.smo": "Samoan",
+ "languages.sag": "Sango",
+ "languages.srp": "Serbian",
+ "languages.gla": "Scottish Gaelic",
+ "languages.sna": "Shona",
+ "languages.sin": "Sinhala",
+ "languages.slk": "Slovak",
+ "languages.slv": "Slovene",
+ "languages.som": "Somali",
+ "languages.sot": "Southern Sotho",
+ "languages.spa": "Gaztelania",
+ "languages.sun": "Sundanese",
+ "languages.swa": "Swahili",
+ "languages.ssw": "Swati",
+ "languages.swe": "Swedish",
+ "languages.tam": "Tamil",
+ "languages.tel": "Telugu",
+ "languages.tgk": "Tajik",
+ "languages.tha": "Thai",
+ "languages.tir": "Tigrinya",
+ "languages.bod": "Tibetan Standard",
+ "languages.tuk": "Turkmen",
+ "languages.tgl": "Tagalog",
+ "languages.tsn": "Tswana",
+ "languages.ton": "Tonga",
+ "languages.tur": "Turkish",
+ "languages.tso": "Tsonga",
+ "languages.tat": "Tatar",
+ "languages.twi": "Twi",
+ "languages.tah": "Tahitian",
+ "languages.uig": "Uighur",
+ "languages.ukr": "Ukrainian",
+ "languages.urd": "Urdu",
+ "languages.uzb": "Uzbek",
+ "languages.ven": "Venda",
+ "languages.vie": "Vietnamese",
+ "languages.vol": "Volapük",
+ "languages.win": "Walloon",
+ "languages.cym": "Welsh",
+ "languages.wol": "Wolof",
+ "languages.fry": "Western Frisian",
+ "languages.xho": "Xhosa",
+ "languages.yid": "Yiddish",
+ "languages.yor": "Yoruba",
+ "languages.zha": "Zhuang",
+ "languages.zul": "Zulu",
+ "DO_NOT_TRANSLATE": "end"
+}
diff --git a/sources/lang/Localization_fr.json b/sources/lang/Localization_fr.json
new file mode 100644
index 0000000..85b9c19
--- /dev/null
+++ b/sources/lang/Localization_fr.json
@@ -0,0 +1,293 @@
+{
+ "about.title": "A propos de COPS",
+ "allbooks.alphabetical.many": "Index alphabétique des {0} livres",
+ "allbooks.alphabetical.none": "Index alphabétique - aucun livre",
+ "allbooks.alphabetical.one": "Index alphabétique du seul livre",
+ "allbooks.title": "Tous les livres",
+ "authors.alphabetical.many": "Index alphabétique des {0} auteurs",
+ "authors.alphabetical.none": "Index alphabétique - aucun auteur",
+ "authors.alphabetical.one": "Index alphabétique du seul auteur",
+ "authors.title": "Auteurs",
+ "authorword.many": "{0} auteurs",
+ "authorword.none": "Pas d'auteur",
+ "authorword.one": "1 auteur",
+ "bookentry.author": "{0} de {1}",
+ "bookword.many": "{0} livres",
+ "bookword.none": "Aucun livre",
+ "bookword.one": "1 livre",
+ "bookword.title": "Livres",
+ "cog.alternate": "Recherche, tri et filtres",
+ "content.series": "Collection:",
+ "content.series.data": "Livre {0} dans la collection {1}",
+ "content.summary": "Résumé",
+ "customcolumn.boolean.no": "Non",
+ "customcolumn.boolean.unknown": "Non défini",
+ "customcolumn.boolean.yes": "Oui",
+ "customcolumn.date.format": "Y-m-d",
+ "customcolumn.date.unknown": "Non défini",
+ "customcolumn.description": "Colonne personnalisée '{0}'",
+ "customcolumn.description.bool": "Index des valeurs booléenne",
+ "customcolumn.description.enum.many": "Index alphabétique de {0} valeurs",
+ "customcolumn.description.enum.none": "Index alphabétique - aucune valeur",
+ "customcolumn.description.enum.one": "Index alphabétique - une seule valeur",
+ "customcolumn.description.rating": "Index des appréciations",
+ "customcolumn.description.series.many": "Index alphabétique de {0} collections",
+ "customcolumn.description.series.none": "Index alphabétique - aucune collection",
+ "customcolumn.description.series.one": "Index alphabétique - Une seule collection",
+ "customcolumn.enum.unknown": "Non défini",
+ "customcolumn.float.unknown": "Non défini",
+ "customcolumn.int.unknown": "Non défini",
+ "customcolumn.rating.unknown": "Non défini",
+ "customcolumn.stars.many": "{0} étoiles",
+ "customcolumn.stars.none": "Aucune étoile",
+ "customcolumn.stars.one": "1 étoile",
+ "customize.email": "Adresse email (pour l'envoi automatique de livres)",
+ "customize.fancybox": "Utiliser une Lightbox",
+ "customize.filter": "Filtrage via les étiquettes",
+ "customize.ignored": "Catégories ignorées",
+ "customize.paging": "Nombre de livres par page (-1 pour désactiver)",
+ "customize.style": "Thème",
+ "customize.title": "Paramétrage de COPS",
+ "home.alternate": "Accueil",
+ "i18n.coversection": "Couverture",
+ "language.title": "Langue",
+ "languages.alphabetical.many": "Index alphabétique des {0} langues",
+ "languages.alphabetical.none": "Index alphabétique - Aucune langue",
+ "languages.alphabetical.one": "Index alphabétique - Une seule langue",
+ "languages.title": "Langues",
+ "mail.messagenotsent": "Le message n'a pas pu être envoyé.",
+ "mail.messagesent": "Le message a été envoyé",
+ "paging.next.alternate": "Suivant",
+ "paging.previous.alternate": "Précédent",
+ "permalink.alternate": "Permalien",
+ "pubdate.title": "Année de publication",
+ "publisher.name": "Editeur",
+ "publishers.alphabetical.many": "Index alphabétique des {0} éditeurs",
+ "publishers.alphabetical.none": "Index alphabétique - Aucun éditeur",
+ "publishers.alphabetical.one": "Index alphabétique - Un seul éditeur",
+ "publishers.title": "Editeurs",
+ "publisherword.many": "{0} éditeurs",
+ "publisherword.none": "Aucun éditeur",
+ "publisherword.one": "1 éditeur",
+ "ratings.many": "{0} Appréciations",
+ "ratings.none": "Pas d'appréciations",
+ "ratings.one": "1 appréciation",
+ "ratings.title": "Appréciations",
+ "ratingword.many": "{0} étoiles",
+ "ratingword.none": "Aucune étoile",
+ "ratingword.one": "1 étoile",
+ "recent.list": "{0} livres les plus récents",
+ "recent.title": "Ajouts récents",
+ "search.alternate": "Rechercher",
+ "search.result": "Résultats pour *{0}*",
+ "search.result.author": "Résultats pour *{0}* dans les auteurs",
+ "search.result.book": "Résultats pour *{0}* dans les livres",
+ "search.result.publisher": "Résultats pour *{0}* dans les éditeurs",
+ "search.result.series": "Résultats pour *{0}* dans les colllections",
+ "search.result.tag": "Résultats pour *{0}* dans les étiquettes",
+ "search.sortorder.asc": "Crois.",
+ "search.sortorder.desc": "Décrois.",
+ "series.alphabetical.many": "Index alphabétique de {0} collections",
+ "series.alphabetical.none": "Index alphabétique - aucune collection",
+ "series.alphabetical.one": "Index alphabétique - Une seule collection",
+ "series.title": "Collections",
+ "seriesword.many": "{0} collections",
+ "seriesword.none": "Pas de collection",
+ "seriesword.one": "1 collection",
+ "sort.alternate": "Trier",
+ "splitByLetter.book.other": "Autres livres",
+ "splitByLetter.letter": "{0} débutant par {1}",
+ "tags.alphabetical.many": "Index alphabétique des {0} étiquettes",
+ "tags.alphabetical.none": "Index alphabétique - aucune étiquette",
+ "tags.alphabetical.one": "Index alphabétique - Une seule étiquette",
+ "tags.title": "Étiquettes",
+ "tagword.many": "{0} étiquettes",
+ "tagword.none": "Sans étiquette",
+ "tagword.one": "1 étiquette",
+ "tagword.title": "Étiquettes",
+ "languages.abk": "Abkhaze",
+ "languages.aaf": "Afar",
+ "languages.afr": "Afrikaner",
+ "languages.aka": "Akan",
+ "languages.sqi": "Albanais",
+ "languages.amh": "Amharique",
+ "languages.ara": "Arabe",
+ "languages.arg": "Aragonais",
+ "languages.hye": "Armenien",
+ "languages.asm": "Assamais",
+ "languages.ava": "Avar",
+ "languages.ave": "Avestique",
+ "languages.aym": "Aymara",
+ "languages.aze": "Azerbaïjanais",
+ "languages.bam": "Bambara",
+ "languages.bak": "Bachkir",
+ "languages.eus": "Basque",
+ "languages.bel": "Bielorusse",
+ "languages.ben": "Bengalais",
+ "languages.bih": "Bihari",
+ "languages.bis": "Bichelamar",
+ "languages.bos": "Bosnien",
+ "languages.bre": "Breton",
+ "languages.bul": "Bulgare",
+ "languages.mya": "Birman",
+ "languages.cat": "Catalan",
+ "languages.cha": "Chamorro",
+ "languages.che": "Tchétchène",
+ "languages.nya": "Chichewa",
+ "languages.zho": "Chinois",
+ "languages.chv": "Tchouvache",
+ "languages.cor": "Cornique",
+ "languages.cos": "Corse",
+ "languages.cre": "Cree",
+ "languages.hrv": "Croate",
+ "languages.ces": "Tchèque",
+ "languages.dan": "Danois",
+ "languages.div": "Divehi",
+ "languages.nld": "Néerlandais",
+ "languages.dzo": "Dzongkha",
+ "languages.eng": "Anglais",
+ "languages.epo": "Esperanto",
+ "languages.est": "Estonien",
+ "languages.ewe": "Ewe",
+ "languages.fao": "Féroïen",
+ "languages.fij": "Fidjien",
+ "languages.fin": "Finnois",
+ "languages.fra": "Français",
+ "languages.ful": "Peul",
+ "languages.glg": "Galicien",
+ "languages.kat": "Georgien",
+ "languages.deu": "Allemand",
+ "languages.ell": "Grec",
+ "languages.grn": "Guaraní",
+ "languages.guj": "Gujarati",
+ "languages.hat": "Haitien",
+ "languages.hau": "Hausa",
+ "languages.hed": "Hébreu",
+ "languages.her": "Héréro",
+ "languages.hin": "Hindi",
+ "languages.hmo": "Hiri Motu",
+ "languages.hun": "Hongrois",
+ "languages.ina": "Interlingua",
+ "languages.ind": "Indonésien",
+ "languages.ile": "Interlingue",
+ "languages.gle": "Irlandais",
+ "languages.ibo": "Igbo",
+ "languages.ipk": "Inupiak",
+ "languages.ido": "Ido",
+ "languages.isl": "Islandais",
+ "languages.ita": "Italien",
+ "languages.iku": "Inuktitut",
+ "languages.jpn": "Japonais",
+ "languages.jav": "Javanais",
+ "languages.kal": "Groenlandais",
+ "languages.kan": "Kannada",
+ "languages.kau": "Kanouri",
+ "languages.kas": "Kashmiri",
+ "languages.kaz": "Kazak",
+ "languages.khm": "Khmer",
+ "languages.kik": "Kikouyou",
+ "languages.kin": "Kinyarwanda",
+ "languages.kir": "Kyrgize",
+ "languages.kom": "Komi",
+ "languages.kon": "Kikongo",
+ "languages.kor": "Coréen",
+ "languages.kur": "Kurde",
+ "languages.kua": "Kuanyama",
+ "languages.lat": "Latin",
+ "languages.ltz": "Luxembourgeois",
+ "languages.lug": "Ganda",
+ "languages.lim": "Limburgeois",
+ "languages.lin": "Lingala",
+ "languages.lao": "Lao",
+ "languages.lit": "Lithuanien",
+ "languages.lub": "Luba-Katanga",
+ "languages.lav": "Latvien",
+ "languages.glv": "Mannois",
+ "languages.mkd": "Macédonien",
+ "languages.mlg": "Malagais",
+ "languages.msa": "Malais",
+ "languages.mal": "Malayalam",
+ "languages.mlt": "Maltais",
+ "languages.mri": "Māori",
+ "languages.mar": "Marathi",
+ "languages.mah": "Marshallais",
+ "languages.mon": "Mongol",
+ "languages.nau": "Nauruan",
+ "languages.nav": "Navajo",
+ "languages.nob": "Norvégien Bokmål",
+ "languages.nde": "Ndébélé",
+ "languages.nep": "Népalais",
+ "languages.ndo": "Ndonga",
+ "languages.nno": "Norvégien Nynorsk",
+ "languages.nor": "Norvégien",
+ "languages.iii": "Nuosu",
+ "languages.nbl": "Sindebele",
+ "languages.oci": "Occitan",
+ "languages.oji": "Ojibwe",
+ "languages.chu": "Vieux-slave",
+ "languages.orm": "Oromo",
+ "languages.ori": "Odia",
+ "languages.oss": "Ossete",
+ "languages.pan": "Penjabais",
+ "languages.pli": "Pāli",
+ "languages.fas": "Perse",
+ "languages.pol": "Polonais",
+ "languages.pus": "Pachtoune",
+ "languages.por": "Portugais",
+ "languages.que": "Quechua",
+ "languages.roh": "Romanche",
+ "languages.run": "Kirundi",
+ "languages.ron": "Roumain",
+ "languages.rus": "Russe",
+ "languages.san": "Sanscrit",
+ "languages.srd": "Sarde",
+ "languages.snd": "Sindhi",
+ "languages.sme": "Sami du Nord",
+ "languages.smo": "Samoais",
+ "languages.sag": "Sango",
+ "languages.srp": "Serbe",
+ "languages.gla": "Gaélique Écossais",
+ "languages.sna": "Shona",
+ "languages.sin": "Cingalais",
+ "languages.slk": "Slovaque",
+ "languages.slv": "Slovene",
+ "languages.som": "Somalien",
+ "languages.sot": "Sotho du Sud",
+ "languages.spa": "Espagnol",
+ "languages.sun": "Soudanais",
+ "languages.swa": "Swahili",
+ "languages.ssw": "Swati",
+ "languages.swe": "Suédois",
+ "languages.tam": "Tamile",
+ "languages.tel": "Telougou",
+ "languages.tgk": "Tajik",
+ "languages.tha": "Thai",
+ "languages.tir": "Tigrinia",
+ "languages.bod": "Tibétain Standard",
+ "languages.tuk": "Turkmène",
+ "languages.tgl": "Tagalog",
+ "languages.tsn": "Tswana",
+ "languages.ton": "Tongien",
+ "languages.tur": "Turc",
+ "languages.tso": "Tsonga",
+ "languages.tat": "Tatar",
+ "languages.twi": "Twi",
+ "languages.tah": "Tahitien",
+ "languages.uig": "Ouïghour",
+ "languages.ukr": "Ukrainien",
+ "languages.urd": "Ourdou",
+ "languages.uzb": "Ouzbeque",
+ "languages.ven": "Venda",
+ "languages.vie": "Vietnamien",
+ "languages.vol": "Volapük",
+ "languages.win": "Wallon",
+ "languages.cym": "Gallois",
+ "languages.wol": "Wolof",
+ "languages.fry": "Frison occidental",
+ "languages.xho": "Xhosa",
+ "languages.yid": "Yiddish",
+ "languages.yor": "Yoruba",
+ "languages.zha": "Zhuang",
+ "languages.zul": "Zoulou",
+ "DO_NOT_TRANSLATE": "end"
+}
diff --git a/sources/lang/Localization_ht.json b/sources/lang/Localization_ht.json
new file mode 100644
index 0000000..70d8de6
--- /dev/null
+++ b/sources/lang/Localization_ht.json
@@ -0,0 +1,293 @@
+{
+ "about.title": "A pwopo COPS",
+ "allbooks.alphabetical.many": "lòd endèks nan {0} liv",
+ "allbooks.alphabetical.none": "lòd endèks nan yon liv",
+ "allbooks.alphabetical.one": "lòd endèks nan yon sèl liv",
+ "allbooks.title": "Tout liv",
+ "authors.alphabetical.many": "lòd endèks nan otè {0}",
+ "authors.alphabetical.none": "lòd endèks ki pa gen otè",
+ "authors.alphabetical.one": "lòd endèks nan yon sèl otè",
+ "authors.title": "Otè",
+ "authorword.many": "{0} Otè",
+ "authorword.none": "Pa gen otè",
+ "authorword.one": "1 otè",
+ "bookentry.author": "{0} de {1}",
+ "bookword.many": "{0} liv",
+ "bookword.none": "Pa gen liv",
+ "bookword.one": "1 liv",
+ "bookword.title": "Liv",
+ "cog.alternate": "Chèche, sòt ak filtre",
+ "content.series": "Koleksyon:",
+ "content.series.data": "Liv {0} ki nan koleksyon an {1}",
+ "content.summary": "Rezime:",
+ "customcolumn.boolean.no": "No",
+ "customcolumn.boolean.unknown": "Not Set",
+ "customcolumn.boolean.yes": "Yes",
+ "customcolumn.date.format": "Y-m-d",
+ "customcolumn.date.unknown": "Not Set",
+ "customcolumn.description": "Custom column '{0}'",
+ "customcolumn.description.bool": "Index of a boolean value",
+ "customcolumn.description.enum.many": "Alphabetical index of the {0} values",
+ "customcolumn.description.enum.none": "Alphabetical index of absolutely no values",
+ "customcolumn.description.enum.one": "Alphabetical index of one value",
+ "customcolumn.description.rating": "Index of ratings",
+ "customcolumn.description.series.many": "Lòd endèks nan {0} koleksyon s",
+ "customcolumn.description.series.none": "Lòd endèks nan absoliman okenn seri",
+ "customcolumn.description.series.one": "Lòd endèks nan koleksyon an sèlman",
+ "customcolumn.enum.unknown": "Not Set",
+ "customcolumn.float.unknown": "Not Set",
+ "customcolumn.int.unknown": "Not Set",
+ "customcolumn.rating.unknown": "Not Set",
+ "customcolumn.stars.many": "{0} Stars",
+ "customcolumn.stars.none": "No Stars",
+ "customcolumn.stars.one": "1 Star",
+ "customize.email": "adrès Imèl (pou otomatik voye nan liv )",
+ "customize.fancybox": "Sèvi ak yon bwat limyè",
+ "customize.filter": "Filtraj atravè Tags",
+ "customize.ignored": "inyore Kategori",
+ "customize.paging": "Nimewo nan liv pou chak paj (-1 vin enfim )",
+ "customize.style": "Tèm",
+ "customize.title": "Mete flik COPS",
+ "home.alternate": "Kay",
+ "i18n.coversection": "Kouvri",
+ "language.title": "Lang",
+ "languages.alphabetical.many": "Lòd endèks nan {0} lang",
+ "languages.alphabetical.none": "Lòd endèks nan absoliman okenn lang",
+ "languages.alphabetical.one": "Lòd endèks pale yon sèl lang",
+ "languages.title": "Lang",
+ "mail.messagenotsent": "Mesaj la pa t 'kapab ap voye.",
+ "mail.messagesent": "te mesaj la te voye",
+ "paging.next.alternate": "Suivant",
+ "paging.previous.alternate": "Pwochen",
+ "permalink.alternate": "Permalien",
+ "pubdate.title": "ane Piblikasyon",
+ "publisher.name": "Editè",
+ "publishers.alphabetical.many": "Lòd endèks nan {0} editè",
+ "publishers.alphabetical.none": "Lòd endèks nan absoliman okenn piblikatè",
+ "publishers.alphabetical.one": "Lòd Index Editè",
+ "publishers.title": "Piblikatè",
+ "publisherword.many": "{0} editè",
+ "publisherword.none": "Pa gen editè",
+ "publisherword.one": "1 editè",
+ "ratings.many": "{0} Li renmen",
+ "ratings.none": "Pa gen evalyasyon",
+ "ratings.one": "1 Revizyon",
+ "ratings.title": "Li renmen",
+ "ratingword.many": "{0} zetwal",
+ "ratingword.none": "Pa gen Etwal",
+ "ratingword.one": "1 etwal",
+ "recent.list": "{0} liv ki pi resan",
+ "recent.title": "Dènyèman te ajoute",
+ "search.alternate": "Chèche",
+ "search.result": "Rezilta pou *{0}*",
+ "search.result.author": "Rezilta pou * {0} * nan otè yo",
+ "search.result.book": "Rezilta pou * {0} * nan liv yo",
+ "search.result.publisher": "Rezilta pou * {0} * nan editè yo",
+ "search.result.series": "Rezilta pou * {0} * nan koleksyon",
+ "search.result.tag": "Rezilta pou * {0} * nan etikèt",
+ "search.sortorder.asc": "Mete konfyans nou.",
+ "search.sortorder.desc": "Ap desann.",
+ "series.alphabetical.many": "Lòd endèks nan {0} koleksyon s",
+ "series.alphabetical.none": "Lòd endèks nan absoliman okenn seri",
+ "series.alphabetical.one": "Lòd endèks nan koleksyon an sèlman",
+ "series.title": "Koleksyon",
+ "seriesword.many": "{0} koleksyon",
+ "seriesword.none": "Pa gen koleksyon",
+ "seriesword.one": "1 koleksyon",
+ "sort.alternate": "Triye",
+ "splitByLetter.book.other": "lòt liv",
+ "splitByLetter.letter": "{0} kòmanse avèk {1}",
+ "tags.alphabetical.many": "Lòd endèks nan etikèt {0}",
+ "tags.alphabetical.none": "Lòd endèks nan absoliman okenn tags",
+ "tags.alphabetical.one": "Lòd endèks nan etikèt la sèl",
+ "tags.title": "Etikèt",
+ "tagword.many": "{0} etikèt",
+ "tagword.none": "Tagless",
+ "tagword.one": "1 tag",
+ "tagword.title": "Etikèt",
+ "languages.abk": "Abkhaz",
+ "languages.aaf": "Afar",
+ "languages.afr": "Afrikaans",
+ "languages.aka": "Akan",
+ "languages.sqi": "Albanian",
+ "languages.amh": "Amharic",
+ "languages.ara": "Arabic",
+ "languages.arg": "Aragonese",
+ "languages.hye": "Armenian",
+ "languages.asm": "Assamese",
+ "languages.ava": "Avaric",
+ "languages.ave": "Avestan",
+ "languages.aym": "Aymara",
+ "languages.aze": "Azerbaijani",
+ "languages.bam": "Bambara",
+ "languages.bak": "Bashkir",
+ "languages.eus": "Basque",
+ "languages.bel": "Belarusian",
+ "languages.ben": "Bengali",
+ "languages.bih": "Bihari",
+ "languages.bis": "Bislama",
+ "languages.bos": "Bosnian",
+ "languages.bre": "Breton",
+ "languages.bul": "Bulgarian",
+ "languages.mya": "Burmese",
+ "languages.cat": "Catalan",
+ "languages.cha": "Chamorro",
+ "languages.che": "Chechen",
+ "languages.nya": "Chichewa",
+ "languages.zho": "Chinese",
+ "languages.chv": "Chuvash",
+ "languages.cor": "Cornish",
+ "languages.cos": "Corsican",
+ "languages.cre": "Cree",
+ "languages.hrv": "Croatian",
+ "languages.ces": "Czech",
+ "languages.dan": "Danish",
+ "languages.div": "Divehi",
+ "languages.nld": "Dutch",
+ "languages.dzo": "Dzongkha",
+ "languages.eng": "Anglais",
+ "languages.epo": "Esperanto",
+ "languages.est": "Estonian",
+ "languages.ewe": "Ewe",
+ "languages.fao": "Faroese",
+ "languages.fij": "Fijian",
+ "languages.fin": "Finnish",
+ "languages.fra": "Français",
+ "languages.ful": "Fula",
+ "languages.glg": "Galician",
+ "languages.kat": "Georgian",
+ "languages.deu": "German",
+ "languages.ell": "Greek",
+ "languages.grn": "Guaraní",
+ "languages.guj": "Gujarati",
+ "languages.hat": "Haitian",
+ "languages.hau": "Hausa",
+ "languages.hed": "Hebrew",
+ "languages.her": "Herero",
+ "languages.hin": "Hindi",
+ "languages.hmo": "Hiri Motu",
+ "languages.hun": "Hungarian",
+ "languages.ina": "Interlingua",
+ "languages.ind": "Indonesian",
+ "languages.ile": "Interlingue",
+ "languages.gle": "Irish",
+ "languages.ibo": "Igbo",
+ "languages.ipk": "Inupiaq",
+ "languages.ido": "Ido",
+ "languages.isl": "Icelandic",
+ "languages.ita": "Italian",
+ "languages.iku": "Inuktitut",
+ "languages.jpn": "Japanese",
+ "languages.jav": "Javanese",
+ "languages.kal": "Kalaallisut",
+ "languages.kan": "Kannada",
+ "languages.kau": "Kanuri",
+ "languages.kas": "Kashmiri",
+ "languages.kaz": "Kazakh",
+ "languages.khm": "Khmer",
+ "languages.kik": "Kikuyu",
+ "languages.kin": "Kinyarwanda",
+ "languages.kir": "Kyrgyz",
+ "languages.kom": "Komi",
+ "languages.kon": "Kongo",
+ "languages.kor": "Korean",
+ "languages.kur": "Kurdish",
+ "languages.kua": "Kwanyama",
+ "languages.lat": "Latin",
+ "languages.ltz": "Luxembourgish",
+ "languages.lug": "Ganda",
+ "languages.lim": "Limburgish",
+ "languages.lin": "Lingala",
+ "languages.lao": "Lao",
+ "languages.lit": "Lithuanian",
+ "languages.lub": "Luba-Katanga",
+ "languages.lav": "Latvian",
+ "languages.glv": "Manx",
+ "languages.mkd": "Macedonian",
+ "languages.mlg": "Malagasy",
+ "languages.msa": "Malay",
+ "languages.mal": "Malayalam",
+ "languages.mlt": "Maltese",
+ "languages.mri": "Māori",
+ "languages.mar": "Marathi",
+ "languages.mah": "Marshallese",
+ "languages.mon": "Mongolian",
+ "languages.nau": "Nauru",
+ "languages.nav": "Navajo",
+ "languages.nob": "Norwegian Bokmål",
+ "languages.nde": "North Ndebele",
+ "languages.nep": "Nepali",
+ "languages.ndo": "Ndonga",
+ "languages.nno": "Norwegian Nynorsk",
+ "languages.nor": "Norwegian",
+ "languages.iii": "Nuosu",
+ "languages.nbl": "South Ndebele",
+ "languages.oci": "Occitan",
+ "languages.oji": "Ojibwe",
+ "languages.chu": "Old Church Slavonic",
+ "languages.orm": "Oromo",
+ "languages.ori": "Oriya",
+ "languages.oss": "Ossetian",
+ "languages.pan": "Panjabi",
+ "languages.pli": "Pāli",
+ "languages.fas": "Persian",
+ "languages.pol": "Polish",
+ "languages.pus": "Pashto",
+ "languages.por": "Portuguese",
+ "languages.que": "Quechua",
+ "languages.roh": "Romansh",
+ "languages.run": "Kirundi",
+ "languages.ron": "Romanian",
+ "languages.rus": "Russian",
+ "languages.san": "Sanskrit",
+ "languages.srd": "Sardinian",
+ "languages.snd": "Sindhi",
+ "languages.sme": "Northern Sami",
+ "languages.smo": "Samoan",
+ "languages.sag": "Sango",
+ "languages.srp": "Serbian",
+ "languages.gla": "Scottish Gaelic",
+ "languages.sna": "Shona",
+ "languages.sin": "Sinhala",
+ "languages.slk": "Slovak",
+ "languages.slv": "Slovene",
+ "languages.som": "Somali",
+ "languages.sot": "Southern Sotho",
+ "languages.spa": "Spanish",
+ "languages.sun": "Sundanese",
+ "languages.swa": "Swahili",
+ "languages.ssw": "Swati",
+ "languages.swe": "Swedish",
+ "languages.tam": "Tamil",
+ "languages.tel": "Telugu",
+ "languages.tgk": "Tajik",
+ "languages.tha": "Thai",
+ "languages.tir": "Tigrinya",
+ "languages.bod": "Tibetan Standard",
+ "languages.tuk": "Turkmen",
+ "languages.tgl": "Tagalog",
+ "languages.tsn": "Tswana",
+ "languages.ton": "Tonga",
+ "languages.tur": "Turkish",
+ "languages.tso": "Tsonga",
+ "languages.tat": "Tatar",
+ "languages.twi": "Twi",
+ "languages.tah": "Tahitian",
+ "languages.uig": "Uighur",
+ "languages.ukr": "Ukrainian",
+ "languages.urd": "Urdu",
+ "languages.uzb": "Uzbek",
+ "languages.ven": "Venda",
+ "languages.vie": "Vietnamese",
+ "languages.vol": "Volapük",
+ "languages.win": "Walloon",
+ "languages.cym": "Welsh",
+ "languages.wol": "Wolof",
+ "languages.fry": "Western Frisian",
+ "languages.xho": "Xhosa",
+ "languages.yid": "Yiddish",
+ "languages.yor": "Yoruba",
+ "languages.zha": "Zhuang",
+ "languages.zul": "Zulu",
+ "DO_NOT_TRANSLATE": "end"
+}
diff --git a/sources/lang/Localization_hu.json b/sources/lang/Localization_hu.json
new file mode 100644
index 0000000..83c621b
--- /dev/null
+++ b/sources/lang/Localization_hu.json
@@ -0,0 +1,293 @@
+{
+ "about.title": "COPS-ról",
+ "allbooks.alphabetical.many": "ABC sorrendben {0}. könyv",
+ "allbooks.alphabetical.none": "ABC sorrendben, ha teljessen nincs könyv",
+ "allbooks.alphabetical.one": "ABC sorrendben egyetlen könyv",
+ "allbooks.title": "Minden könyv",
+ "authors.alphabetical.many": "ABC sorrendben {0} szerző",
+ "authors.alphabetical.none": "ABC sorrendben, ha teljesen nincs szerző",
+ "authors.alphabetical.one": "ABC sorrendben egyetlen szerző",
+ "authors.title": "Szerzők",
+ "authorword.many": "{0} szerző",
+ "authorword.none": "Nincs szerző",
+ "authorword.one": "1 szerző",
+ "bookentry.author": "{0} az {1}ből",
+ "bookword.many": "{0} könyv",
+ "bookword.none": "Nincs könyv",
+ "bookword.one": "1 könyv",
+ "bookword.title": "Könyvek",
+ "cog.alternate": "Keresés, rendezés és szürés",
+ "content.series": "Sorozatok:",
+ "content.series.data": "{0} .dik könyv a {1} sorozatból",
+ "content.summary": "Összegzés",
+ "customcolumn.boolean.no": "Nem",
+ "customcolumn.boolean.unknown": "Alapértelmezett",
+ "customcolumn.boolean.yes": "Igen",
+ "customcolumn.date.format": "Y-m-d",
+ "customcolumn.date.unknown": "Alapértelmezett",
+ "customcolumn.description": "Custom column '{0}'",
+ "customcolumn.description.bool": "Index of a boolean value",
+ "customcolumn.description.enum.many": "Alphabetical index of the {0} values",
+ "customcolumn.description.enum.none": "Alphabetical index of absolutely no values",
+ "customcolumn.description.enum.one": "Alphabetical index of one value",
+ "customcolumn.description.rating": "Index of ratings",
+ "customcolumn.description.series.many": "ABC sorrendben {0} sorozat",
+ "customcolumn.description.series.none": "ABC sorrendben, ha teljessen nincs sorozat",
+ "customcolumn.description.series.one": "ABC sorrendben egyetlen sorozat",
+ "customcolumn.enum.unknown": "Alapértelmezett",
+ "customcolumn.float.unknown": "Alapértelmezett",
+ "customcolumn.int.unknown": "Alapértelmezett",
+ "customcolumn.rating.unknown": "Alapértelmezett",
+ "customcolumn.stars.many": "{0} Stars",
+ "customcolumn.stars.none": "No Stars",
+ "customcolumn.stars.one": "1 Star",
+ "customize.email": "Állitsd be az email cimed (engedélyezi a könyvek küldését emailban)",
+ "customize.fancybox": "Lightbox használata",
+ "customize.filter": "Engedélyzi a tag alapján a szürést",
+ "customize.ignored": "Figyelmen kivül hagyott kategoriák",
+ "customize.paging": "Maximum könyv oldalanként (-1 kikapcsolja)",
+ "customize.style": "Téma",
+ "customize.title": "Egyedi COPS UI",
+ "home.alternate": "Főoldal",
+ "i18n.coversection": "Boritó",
+ "language.title": "Nyelv",
+ "languages.alphabetical.many": "ABC sorrendben {0} nyelv",
+ "languages.alphabetical.none": "ABC sorrendben, ha teljessen nincs nyelv",
+ "languages.alphabetical.one": "ABC sorrendben egyetlen nyelv",
+ "languages.title": "Nyelvek",
+ "mail.messagenotsent": "Az üzenet nincs elküldve.",
+ "mail.messagesent": "Az üzenet elküldve",
+ "paging.next.alternate": "Következő",
+ "paging.previous.alternate": "Elöző",
+ "permalink.alternate": "Permalink",
+ "pubdate.title": "Kiadás éve",
+ "publisher.name": "Kiadó",
+ "publishers.alphabetical.many": "ABC sorrendben {0} kiadó",
+ "publishers.alphabetical.none": "ABC sorrendben, ha teljessen nincs kiadó",
+ "publishers.alphabetical.one": "ABC sorrendben egyetlen kiadó",
+ "publishers.title": "Kiadók",
+ "publisherword.many": "{0} kiadó",
+ "publisherword.none": "Nincs kiadó",
+ "publisherword.one": "1 kiadó",
+ "ratings.many": "{0} értékelés",
+ "ratings.none": "Nincs értékelés",
+ "ratings.one": "1 értékelés",
+ "ratings.title": "Értékelések",
+ "ratingword.many": "{0} csillag",
+ "ratingword.none": "Nincs csillag",
+ "ratingword.one": "1 csillag",
+ "recent.list": "{0} legutóbbi könyv",
+ "recent.title": "Legutobbi hozzáadás",
+ "search.alternate": "Keresés",
+ "search.result": "Keresés eredménye a *{0}*",
+ "search.result.author": "Keresés eredménye a *{0}* szerzők közt",
+ "search.result.book": "Keresés eredménye a *{0}* könyvek közt",
+ "search.result.publisher": "Keresés eredménye a *{0}* kiadók közt",
+ "search.result.series": "Keresés eredménye a *{0}* sorozatok közt",
+ "search.result.tag": "Keresés eredménye a *{0}* tagok közt",
+ "search.sortorder.asc": "Emelkedő",
+ "search.sortorder.desc": "Csökkenő",
+ "series.alphabetical.many": "ABC sorrendben {0} sorozat",
+ "series.alphabetical.none": "ABC sorrendben, ha teljessen nincs sorozat",
+ "series.alphabetical.one": "ABC sorrendben egyetlen sorozat",
+ "series.title": "Sorozatok",
+ "seriesword.many": "{0} sorozat",
+ "seriesword.none": "Nincs sorozat",
+ "seriesword.one": "1 sorozat",
+ "sort.alternate": "Rendezés",
+ "splitByLetter.book.other": "Egyébb könyvek",
+ "splitByLetter.letter": "{0} kezdőbetűje {1}",
+ "tags.alphabetical.many": "ABC sorrendben {0} tags",
+ "tags.alphabetical.none": "ABC sorrendben, ha teljessen nincs tag",
+ "tags.alphabetical.one": "ABC sorrendben egyetlen tag",
+ "tags.title": "Tagok",
+ "tagword.many": "{0} tags",
+ "tagword.none": "Nincs tag",
+ "tagword.one": "1 tag",
+ "tagword.title": "Tagok",
+ "languages.abk": "Abkhaz",
+ "languages.aaf": "Afar",
+ "languages.afr": "Afrikaans",
+ "languages.aka": "Akan",
+ "languages.sqi": "Albán",
+ "languages.amh": "Amharic",
+ "languages.ara": "Arab",
+ "languages.arg": "Aragonese",
+ "languages.hye": "Örmény",
+ "languages.asm": "Assamese",
+ "languages.ava": "Avaric",
+ "languages.ave": "Avestan",
+ "languages.aym": "Aymara",
+ "languages.aze": "Azerbajdzsán",
+ "languages.bam": "Bambara",
+ "languages.bak": "Baskír",
+ "languages.eus": "Baszk",
+ "languages.bel": "Fehérorosz",
+ "languages.ben": "Bengáli",
+ "languages.bih": "Bihari",
+ "languages.bis": "Bislama",
+ "languages.bos": "Bosnyák",
+ "languages.bre": "Breton",
+ "languages.bul": "Bolgár",
+ "languages.mya": "Burmese",
+ "languages.cat": "Katalán",
+ "languages.cha": "Chamorro",
+ "languages.che": "Csecsen",
+ "languages.nya": "Chichewa",
+ "languages.zho": "Kínai",
+ "languages.chv": "Chuvash",
+ "languages.cor": "Cornish",
+ "languages.cos": "Corsican",
+ "languages.cre": "Cree",
+ "languages.hrv": "Horvát",
+ "languages.ces": "Cseh",
+ "languages.dan": "Dán",
+ "languages.div": "Divehi",
+ "languages.nld": "Holland",
+ "languages.dzo": "Dzongkha",
+ "languages.eng": "Angol",
+ "languages.epo": "Eszperantó",
+ "languages.est": "Észt",
+ "languages.ewe": "Ewe",
+ "languages.fao": "Faroese",
+ "languages.fij": "Fijian",
+ "languages.fin": "Finn",
+ "languages.fra": "Francia",
+ "languages.ful": "Fula",
+ "languages.glg": "Galician",
+ "languages.kat": "Georgian",
+ "languages.deu": "Német",
+ "languages.ell": "Görög",
+ "languages.grn": "Guaraní",
+ "languages.guj": "Gujarati",
+ "languages.hat": "Haiti",
+ "languages.hau": "Hausa",
+ "languages.hed": "Héber",
+ "languages.her": "Herero",
+ "languages.hin": "Hindi",
+ "languages.hmo": "Hiri Motu",
+ "languages.hun": "Magyar",
+ "languages.ina": "Interlingua",
+ "languages.ind": "Indonéz",
+ "languages.ile": "Interlingue",
+ "languages.gle": "Ír",
+ "languages.ibo": "Igbo",
+ "languages.ipk": "Inupiaq",
+ "languages.ido": "Ido",
+ "languages.isl": "Izlandi",
+ "languages.ita": "Italian",
+ "languages.iku": "Inuktitut",
+ "languages.jpn": "Japán",
+ "languages.jav": "Javanese",
+ "languages.kal": "Kalaallisut",
+ "languages.kan": "Kannada",
+ "languages.kau": "Kanuri",
+ "languages.kas": "Kashmiri",
+ "languages.kaz": "Kazah",
+ "languages.khm": "Khmer",
+ "languages.kik": "Kikuyu",
+ "languages.kin": "Kinyarwanda",
+ "languages.kir": "Kyrgyz",
+ "languages.kom": "Komi",
+ "languages.kon": "Kongo",
+ "languages.kor": "Koreai",
+ "languages.kur": "Kurdish",
+ "languages.kua": "Kwanyama",
+ "languages.lat": "Latin",
+ "languages.ltz": "Luxembourgish",
+ "languages.lug": "Ganda",
+ "languages.lim": "Limburgish",
+ "languages.lin": "Lingala",
+ "languages.lao": "Lao",
+ "languages.lit": "Lithuanian",
+ "languages.lub": "Luba-Katanga",
+ "languages.lav": "Latvian",
+ "languages.glv": "Manx",
+ "languages.mkd": "Makedón",
+ "languages.mlg": "Malagasy",
+ "languages.msa": "Maláj",
+ "languages.mal": "Malayalam",
+ "languages.mlt": "Maltese",
+ "languages.mri": "Maori",
+ "languages.mar": "Marathi",
+ "languages.mah": "Marshallese",
+ "languages.mon": "Mongol",
+ "languages.nau": "Nauru",
+ "languages.nav": "Navahó",
+ "languages.nob": "Norwegian Bokmål",
+ "languages.nde": "North Ndebele",
+ "languages.nep": "Nepáli",
+ "languages.ndo": "Ndonga",
+ "languages.nno": "Norwegian Nynorsk",
+ "languages.nor": "Norvég",
+ "languages.iii": "Nuosu",
+ "languages.nbl": "South Ndebele",
+ "languages.oci": "Occitan",
+ "languages.oji": "Ojibwe",
+ "languages.chu": "Old Church Slavonic",
+ "languages.orm": "Oromo",
+ "languages.ori": "Oriya",
+ "languages.oss": "Ossetian",
+ "languages.pan": "Pandzsabi",
+ "languages.pli": "Pāli",
+ "languages.fas": "Perzsa",
+ "languages.pol": "Lengyel",
+ "languages.pus": "Pastu",
+ "languages.por": "Portugál",
+ "languages.que": "Quechua",
+ "languages.roh": "Romansh",
+ "languages.run": "Kirundi",
+ "languages.ron": "Román",
+ "languages.rus": "Orosz",
+ "languages.san": "Sanskrit",
+ "languages.srd": "Sardinian",
+ "languages.snd": "Sindhi",
+ "languages.sme": "Northern Sami",
+ "languages.smo": "Samoan",
+ "languages.sag": "Sango",
+ "languages.srp": "Szerb",
+ "languages.gla": "Skót",
+ "languages.sna": "Shona",
+ "languages.sin": "Sinhala",
+ "languages.slk": "Szlovák",
+ "languages.slv": "Szlovén",
+ "languages.som": "Somali",
+ "languages.sot": "Southern Sotho",
+ "languages.spa": "Spanyol",
+ "languages.sun": "Sundanese",
+ "languages.swa": "Swahili",
+ "languages.ssw": "Swati",
+ "languages.swe": "Svéd",
+ "languages.tam": "Tamil",
+ "languages.tel": "Telugu",
+ "languages.tgk": "Tajik",
+ "languages.tha": "Thai",
+ "languages.tir": "Tigrinya",
+ "languages.bod": "Tibetan Standard",
+ "languages.tuk": "Turkmen",
+ "languages.tgl": "Tagalog",
+ "languages.tsn": "Tswana",
+ "languages.ton": "Tonga",
+ "languages.tur": "Török",
+ "languages.tso": "Tsonga",
+ "languages.tat": "Tatár",
+ "languages.twi": "Twi",
+ "languages.tah": "Tahitian",
+ "languages.uig": "Uighur",
+ "languages.ukr": "Ukrán",
+ "languages.urd": "Urdu",
+ "languages.uzb": "Uzbek",
+ "languages.ven": "Venda",
+ "languages.vie": "Vietnámi",
+ "languages.vol": "Volapük",
+ "languages.win": "Vallon",
+ "languages.cym": "Welsh",
+ "languages.wol": "Wolof",
+ "languages.fry": "Western Frisian",
+ "languages.xho": "Xhosa",
+ "languages.yid": "Yiddish",
+ "languages.yor": "Yoruba",
+ "languages.zha": "Zhuang",
+ "languages.zul": "Zulu",
+ "DO_NOT_TRANSLATE": "end"
+}
diff --git a/sources/lang/Localization_it.json b/sources/lang/Localization_it.json
new file mode 100644
index 0000000..5b34876
--- /dev/null
+++ b/sources/lang/Localization_it.json
@@ -0,0 +1,293 @@
+{
+ "about.title": "A proposito di COPS",
+ "allbooks.alphabetical.many": "Indice alfabetico di {0} libri",
+ "allbooks.alphabetical.none": "Indice alfabetico indipendente dal libro",
+ "allbooks.alphabetical.one": "Indice alfabetico di un solo libro",
+ "allbooks.title": "Tutti i libri",
+ "authors.alphabetical.many": "Indice alfabetico di {0} autori",
+ "authors.alphabetical.none": "Indice alfabetico indipendente dall'autore",
+ "authors.alphabetical.one": "Indice alfabetico di un solo autore",
+ "authors.title": "Autori",
+ "authorword.many": "{0} autori",
+ "authorword.none": "Senza autore",
+ "authorword.one": "1 autore",
+ "bookentry.author": "{0} di {1}",
+ "bookword.many": "{0} libri",
+ "bookword.none": "Nessun libro",
+ "bookword.one": "1 libro",
+ "bookword.title": "Libri",
+ "cog.alternate": "Ricerca, ordinamento e filtri",
+ "content.series": "Collana:",
+ "content.series.data": "Libro {0} nella collana {1}",
+ "content.summary": "Riassunto",
+ "customcolumn.boolean.no": "TLS",
+ "customcolumn.boolean.unknown": "Non Impostato",
+ "customcolumn.boolean.yes": "Si",
+ "customcolumn.date.format": "Y-m-d",
+ "customcolumn.date.unknown": "Non Impostato",
+ "customcolumn.description": "Custom column '{0}'",
+ "customcolumn.description.bool": "Index of a boolean value",
+ "customcolumn.description.enum.many": "Alphabetical index of the {0} values",
+ "customcolumn.description.enum.none": "Alphabetical index of absolutely no values",
+ "customcolumn.description.enum.one": "Alphabetical index of one value",
+ "customcolumn.description.rating": "Index of ratings",
+ "customcolumn.description.series.many": "Indice alfabetico di {0} collane",
+ "customcolumn.description.series.none": "Indice alfabetico indipendente dalla collana",
+ "customcolumn.description.series.one": "Indice alfabetico di una sola collana",
+ "customcolumn.enum.unknown": "Non Impostato",
+ "customcolumn.float.unknown": "Non Impostato",
+ "customcolumn.int.unknown": "Non Impostato",
+ "customcolumn.rating.unknown": "Non Impostato",
+ "customcolumn.stars.many": "{0} Stelle",
+ "customcolumn.stars.none": "Senza stelle",
+ "customcolumn.stars.one": "1 stella",
+ "customize.email": "Imposta la tua email (per permettere l'invio di email)",
+ "customize.fancybox": "Usa una Lightbox",
+ "customize.filter": "Abilita il filtro per argomento",
+ "customize.ignored": "Categorie ignorate",
+ "customize.paging": "Numero massimo di libri per pagina (-1 per disabilitare)",
+ "customize.style": "Tema",
+ "customize.title": "Personalizza l'interfaccia di COPS",
+ "home.alternate": "Home",
+ "i18n.coversection": "Copertina",
+ "language.title": "Lingua",
+ "languages.alphabetical.many": "Indice alfabetico delle {0} lingue",
+ "languages.alphabetical.none": "Indice alfabetico indipendente dalla lingua",
+ "languages.alphabetical.one": "Indice alfabetico della singola lingua",
+ "languages.title": "Lingue",
+ "mail.messagenotsent": "L'e-mail non può essere spedita.",
+ "mail.messagesent": "L'e-mail è stata spedita",
+ "paging.next.alternate": "Prossimo",
+ "paging.previous.alternate": "Precedente",
+ "permalink.alternate": "Permalink",
+ "pubdate.title": "Anno di publicazione",
+ "publisher.name": "Editore",
+ "publishers.alphabetical.many": "Indice alfabetico degli {0} editori",
+ "publishers.alphabetical.none": "Indice alfabetico indipendente dall'editore",
+ "publishers.alphabetical.one": "Indice alfabetico del singolo editore",
+ "publishers.title": "Editori",
+ "publisherword.many": "{0} editori",
+ "publisherword.none": "Nessun editore",
+ "publisherword.one": "1 editore",
+ "ratings.many": "{0} valutazioni",
+ "ratings.none": "nessuna valutazione",
+ "ratings.one": "1 valutazione",
+ "ratings.title": "Valutazioni",
+ "ratingword.many": "{0} stelle",
+ "ratingword.none": "Nessuna stella",
+ "ratingword.one": "1 stella",
+ "recent.list": "I {0} libri più recenti",
+ "recent.title": "Ultime aggiunte",
+ "search.alternate": "Cerca",
+ "search.result": "Risultati per *{0}*",
+ "search.result.author": "Risultato della ricerca di *{0}* negli autori",
+ "search.result.book": "Risultato della ricerca di *{0}* nei libri",
+ "search.result.publisher": "Risultato della ricerca di *{0}* negli editori",
+ "search.result.series": "Risultato della ricerca di *{0}* nelle collane",
+ "search.result.tag": "Risultato della ricerca di *{0}* negli argomenti",
+ "search.sortorder.asc": "Asc",
+ "search.sortorder.desc": "Disc",
+ "series.alphabetical.many": "Indice alfabetico di {0} collane",
+ "series.alphabetical.none": "Indice alfabetico indipendente dalla collana",
+ "series.alphabetical.one": "Indice alfabetico di una sola collana",
+ "series.title": "Collane",
+ "seriesword.many": "{0} collane",
+ "seriesword.none": "Nessuna collana",
+ "seriesword.one": "1 collana",
+ "sort.alternate": "Tipo",
+ "splitByLetter.book.other": "Altri libri",
+ "splitByLetter.letter": "{0} che iniziano per {1}",
+ "tags.alphabetical.many": "Indice alfabetico di {0} argomenti",
+ "tags.alphabetical.none": "Indice alfabetico indipendente dall'argomento",
+ "tags.alphabetical.one": "Indice alfabetico del solo argomento",
+ "tags.title": "Argomenti",
+ "tagword.many": "{0} argomenti",
+ "tagword.none": "Senza argomento",
+ "tagword.one": "1 argomento",
+ "tagword.title": "Argomenti",
+ "languages.abk": "Abkhaz",
+ "languages.aaf": "Afar",
+ "languages.afr": "Afrikaans",
+ "languages.aka": "Akan",
+ "languages.sqi": "Albanese",
+ "languages.amh": "Amharic",
+ "languages.ara": "Arabo",
+ "languages.arg": "Aragonese",
+ "languages.hye": "Armenian",
+ "languages.asm": "Assamese",
+ "languages.ava": "Avaric",
+ "languages.ave": "Avestan",
+ "languages.aym": "Aymara",
+ "languages.aze": "Azerbaijani",
+ "languages.bam": "Bambara",
+ "languages.bak": "Bashkir",
+ "languages.eus": "Basque",
+ "languages.bel": "Belarusian",
+ "languages.ben": "Bengali",
+ "languages.bih": "Bihari",
+ "languages.bis": "Bislama",
+ "languages.bos": "Bosnian",
+ "languages.bre": "Breton",
+ "languages.bul": "Bulgarian",
+ "languages.mya": "Burmese",
+ "languages.cat": "Catalan",
+ "languages.cha": "Chamorro",
+ "languages.che": "Chechen",
+ "languages.nya": "Chichewa",
+ "languages.zho": "Chinese",
+ "languages.chv": "Chuvash",
+ "languages.cor": "Cornish",
+ "languages.cos": "Corsican",
+ "languages.cre": "Cree",
+ "languages.hrv": "Croatian",
+ "languages.ces": "Czech",
+ "languages.dan": "Danish",
+ "languages.div": "Divehi",
+ "languages.nld": "Dutch",
+ "languages.dzo": "Dzongkha",
+ "languages.eng": "English",
+ "languages.epo": "Esperanto",
+ "languages.est": "Estonian",
+ "languages.ewe": "Ewe",
+ "languages.fao": "Faroese",
+ "languages.fij": "Fijian",
+ "languages.fin": "Finnish",
+ "languages.fra": "French",
+ "languages.ful": "Fula",
+ "languages.glg": "Galician",
+ "languages.kat": "Georgian",
+ "languages.deu": "German",
+ "languages.ell": "Greek",
+ "languages.grn": "Guaraní",
+ "languages.guj": "Gujarati",
+ "languages.hat": "Haitian",
+ "languages.hau": "Hausa",
+ "languages.hed": "Hebrew",
+ "languages.her": "Herero",
+ "languages.hin": "Hindi",
+ "languages.hmo": "Hiri Motu",
+ "languages.hun": "Hungarian",
+ "languages.ina": "Interlingua",
+ "languages.ind": "Indonesian",
+ "languages.ile": "Interlingue",
+ "languages.gle": "Irish",
+ "languages.ibo": "Igbo",
+ "languages.ipk": "Inupiaq",
+ "languages.ido": "Ido",
+ "languages.isl": "Icelandic",
+ "languages.ita": "Italiano",
+ "languages.iku": "Inuktitut",
+ "languages.jpn": "Japanese",
+ "languages.jav": "Javanese",
+ "languages.kal": "Kalaallisut",
+ "languages.kan": "Kannada",
+ "languages.kau": "Kanuri",
+ "languages.kas": "Kashmiri",
+ "languages.kaz": "Kazakh",
+ "languages.khm": "Khmer",
+ "languages.kik": "Kikuyu",
+ "languages.kin": "Kinyarwanda",
+ "languages.kir": "Kyrgyz",
+ "languages.kom": "Komi",
+ "languages.kon": "Kongo",
+ "languages.kor": "Korean",
+ "languages.kur": "Kurdish",
+ "languages.kua": "Kwanyama",
+ "languages.lat": "Latin",
+ "languages.ltz": "Luxembourgish",
+ "languages.lug": "Ganda",
+ "languages.lim": "Limburgish",
+ "languages.lin": "Lingala",
+ "languages.lao": "Lao",
+ "languages.lit": "Lithuanian",
+ "languages.lub": "Luba-Katanga",
+ "languages.lav": "Latvian",
+ "languages.glv": "Manx",
+ "languages.mkd": "Macedonian",
+ "languages.mlg": "Malagasy",
+ "languages.msa": "Malay",
+ "languages.mal": "Malayalam",
+ "languages.mlt": "Maltese",
+ "languages.mri": "Māori",
+ "languages.mar": "Marathi",
+ "languages.mah": "Marshallese",
+ "languages.mon": "Mongolian",
+ "languages.nau": "Nauru",
+ "languages.nav": "Navajo",
+ "languages.nob": "Norwegian Bokmål",
+ "languages.nde": "North Ndebele",
+ "languages.nep": "Nepali",
+ "languages.ndo": "Ndonga",
+ "languages.nno": "Norwegian Nynorsk",
+ "languages.nor": "Norwegian",
+ "languages.iii": "Nuosu",
+ "languages.nbl": "South Ndebele",
+ "languages.oci": "Occitan",
+ "languages.oji": "Ojibwe",
+ "languages.chu": "Old Church Slavonic",
+ "languages.orm": "Oromo",
+ "languages.ori": "Oriya",
+ "languages.oss": "Ossetian",
+ "languages.pan": "Panjabi",
+ "languages.pli": "Pāli",
+ "languages.fas": "Persian",
+ "languages.pol": "Polish",
+ "languages.pus": "Pashto",
+ "languages.por": "Portoghese",
+ "languages.que": "Quechua",
+ "languages.roh": "Romancio",
+ "languages.run": "Kirundi",
+ "languages.ron": "Romanian",
+ "languages.rus": "Russian",
+ "languages.san": "Sanskrit",
+ "languages.srd": "Sardinian",
+ "languages.snd": "Sindhi",
+ "languages.sme": "Northern Sami",
+ "languages.smo": "Samoan",
+ "languages.sag": "Sango",
+ "languages.srp": "Serbian",
+ "languages.gla": "Scottish Gaelic",
+ "languages.sna": "Shona",
+ "languages.sin": "Sinhala",
+ "languages.slk": "Slovak",
+ "languages.slv": "Slovene",
+ "languages.som": "Somali",
+ "languages.sot": "Southern Sotho",
+ "languages.spa": "Spagnolo",
+ "languages.sun": "Sundanese",
+ "languages.swa": "Swahili",
+ "languages.ssw": "Swati",
+ "languages.swe": "Swedish",
+ "languages.tam": "Tamil",
+ "languages.tel": "Telugu",
+ "languages.tgk": "Tajik",
+ "languages.tha": "Thai",
+ "languages.tir": "Tigrinya",
+ "languages.bod": "Tibetan Standard",
+ "languages.tuk": "Turkmen",
+ "languages.tgl": "Tagalog",
+ "languages.tsn": "Tswana",
+ "languages.ton": "Tonga",
+ "languages.tur": "Turkish",
+ "languages.tso": "Tsonga",
+ "languages.tat": "Tatar",
+ "languages.twi": "Twi",
+ "languages.tah": "Tahitian",
+ "languages.uig": "Uighur",
+ "languages.ukr": "Ukrainian",
+ "languages.urd": "Urdu",
+ "languages.uzb": "Uzbek",
+ "languages.ven": "Venda",
+ "languages.vie": "Vietnamita",
+ "languages.vol": "Volapük",
+ "languages.win": "vallone",
+ "languages.cym": "Gallese",
+ "languages.wol": "Senegalese",
+ "languages.fry": "Frisone del est",
+ "languages.xho": "Xhosa",
+ "languages.yid": "Yiddish",
+ "languages.yor": "Yoruba",
+ "languages.zha": "cinese semplificato",
+ "languages.zul": "Zuù",
+ "DO_NOT_TRANSLATE": "end"
+}
diff --git a/sources/lang/Localization_ko.json b/sources/lang/Localization_ko.json
new file mode 100644
index 0000000..32e8a7f
--- /dev/null
+++ b/sources/lang/Localization_ko.json
@@ -0,0 +1,293 @@
+{
+ "about.title": "COPS에 대해",
+ "allbooks.alphabetical.many": "{0}권의 도서별 색인",
+ "allbooks.alphabetical.none": "0권의 도서별 색인",
+ "allbooks.alphabetical.one": "1권의 도서별 색인",
+ "allbooks.title": "전체 도서",
+ "authors.alphabetical.many": "{0}명의 저자별 색인",
+ "authors.alphabetical.none": "0명의 저자별 색인",
+ "authors.alphabetical.one": "1명의 저자별 색인",
+ "authors.title": "저자",
+ "authorword.many": "{0}명의 저자",
+ "authorword.none": "0명의 저자",
+ "authorword.one": "1명의 저자",
+ "bookentry.author": "{0} by {1}",
+ "bookword.many": "{0}권",
+ "bookword.none": "(없음)",
+ "bookword.one": "1권",
+ "bookword.title": "도서",
+ "cog.alternate": "검색, 정렬 및 필터링",
+ "content.series": "Series:",
+ "content.series.data": "Book {0} in the {1} series",
+ "content.summary": "요약",
+ "customcolumn.boolean.no": "2월",
+ "customcolumn.boolean.unknown": "Not Set",
+ "customcolumn.boolean.yes": "1월",
+ "customcolumn.date.format": "Y-m-d",
+ "customcolumn.date.unknown": "Not Set",
+ "customcolumn.description": "Custom column '{0}'",
+ "customcolumn.description.bool": "Index of a boolean value",
+ "customcolumn.description.enum.many": "Alphabetical index of the {0} values",
+ "customcolumn.description.enum.none": "Alphabetical index of absolutely no values",
+ "customcolumn.description.enum.one": "Alphabetical index of one value",
+ "customcolumn.description.rating": "Index of ratings",
+ "customcolumn.description.series.many": "{0}개의 시리즈별 색인",
+ "customcolumn.description.series.none": "0개의 시리즈별 색인",
+ "customcolumn.description.series.one": "1개의 시리즈별 색인",
+ "customcolumn.enum.unknown": "Not Set",
+ "customcolumn.float.unknown": "Not Set",
+ "customcolumn.int.unknown": "Not Set",
+ "customcolumn.rating.unknown": "Not Set",
+ "customcolumn.stars.many": "{0} Stars",
+ "customcolumn.stars.none": "No Stars",
+ "customcolumn.stars.one": "1 Star",
+ "customize.email": "이메일주소 (도서 메일발송용)",
+ "customize.fancybox": "Lightbox 사용 (도서정보를 프레임으로 표시)",
+ "customize.filter": "태그 필터링기능 사용",
+ "customize.ignored": "무시할 카테고리",
+ "customize.paging": "페이지마다 표시할 도서 권수 (-1인 경우 무시)",
+ "customize.style": "스타일",
+ "customize.title": "COPS 설정",
+ "home.alternate": "최초",
+ "i18n.coversection": "Cover",
+ "language.title": "언어",
+ "languages.alphabetical.many": "4개의 언어별 색인",
+ "languages.alphabetical.none": "0개의 언어별 색인",
+ "languages.alphabetical.one": "1개의 언어별 색인",
+ "languages.title": "언어",
+ "mail.messagenotsent": "Message could not be sent.",
+ "mail.messagesent": "Message has been sent",
+ "paging.next.alternate": "다음",
+ "paging.previous.alternate": "이전",
+ "permalink.alternate": "Permalink",
+ "pubdate.title": "출판년도",
+ "publisher.name": "출판사",
+ "publishers.alphabetical.many": "{0}개 출판사별 색인",
+ "publishers.alphabetical.none": "0개 출판사별 색인",
+ "publishers.alphabetical.one": "1개 출판사별 색인",
+ "publishers.title": "출판사",
+ "publisherword.many": "{0}개 출판사",
+ "publisherword.none": "출판사 없음",
+ "publisherword.one": "1개 출판사",
+ "ratings.many": "{0}개 별점",
+ "ratings.none": "별점 없음",
+ "ratings.one": "1개 별점",
+ "ratings.title": "별점",
+ "ratingword.many": "별 {0}개",
+ "ratingword.none": "별 없음",
+ "ratingword.one": "별 1개",
+ "recent.list": "최근 추가된 {0}권의 책",
+ "recent.title": "최근 추가됨",
+ "search.alternate": "검색",
+ "search.result": "*{0}*의 검색결과",
+ "search.result.author": "저자명에서 *{0}* 검색",
+ "search.result.book": "도서정보에서 *{0}* 검색",
+ "search.result.publisher": "출판사명에서 *{0}* 검색",
+ "search.result.series": "시리즈명에서 *{0}* 검색",
+ "search.result.tag": "태그에서 *{0}* 검색",
+ "search.sortorder.asc": "오름차순",
+ "search.sortorder.desc": "내림차순",
+ "series.alphabetical.many": "{0}개의 시리즈별 색인",
+ "series.alphabetical.none": "0개의 시리즈별 색인",
+ "series.alphabetical.one": "1개의 시리즈별 색인",
+ "series.title": "시리즈",
+ "seriesword.many": "{0}개 시리즈",
+ "seriesword.none": "시리즈 없음",
+ "seriesword.one": "1개 시리즈",
+ "sort.alternate": "Sort",
+ "splitByLetter.book.other": "Other books",
+ "splitByLetter.letter": "{0} starting with {1}",
+ "tags.alphabetical.many": "{0}개의 태그별 색인",
+ "tags.alphabetical.none": "0개의 태그별 색인",
+ "tags.alphabetical.one": "1개의 태그별 색인",
+ "tags.title": "태그",
+ "tagword.many": "{0} tags",
+ "tagword.none": "No tags",
+ "tagword.one": "1 tag",
+ "tagword.title": "태그",
+ "languages.abk": "압하스어",
+ "languages.aaf": "아파르어",
+ "languages.afr": "아프리칸스어",
+ "languages.aka": "아칸어",
+ "languages.sqi": "알바니아어",
+ "languages.amh": "암하라어",
+ "languages.ara": "아랍어",
+ "languages.arg": "아라곤어",
+ "languages.hye": "아르메니아어",
+ "languages.asm": "아삼어",
+ "languages.ava": "아바르어",
+ "languages.ave": "아베스타어",
+ "languages.aym": "아이마라어",
+ "languages.aze": "아제르바이잔어",
+ "languages.bam": "밤바라어",
+ "languages.bak": "바슈키르어",
+ "languages.eus": "바스크어",
+ "languages.bel": "벨로루시어",
+ "languages.ben": "벵골어",
+ "languages.bih": "비하르어",
+ "languages.bis": "비슐라마어",
+ "languages.bos": "보스니아어",
+ "languages.bre": "브르타뉴어",
+ "languages.bul": "불가리아어",
+ "languages.mya": "버마어",
+ "languages.cat": "카탈루냐어",
+ "languages.cha": "차모로어",
+ "languages.che": "체첸어",
+ "languages.nya": "니안자어",
+ "languages.zho": "중국어",
+ "languages.chv": "추바슈어",
+ "languages.cor": "콘월어",
+ "languages.cos": "코르시카어",
+ "languages.cre": "크리어",
+ "languages.hrv": "크로아티아어",
+ "languages.ces": "체크어",
+ "languages.dan": "덴마크어",
+ "languages.div": "디베히어",
+ "languages.nld": "네덜란드어",
+ "languages.dzo": "종카어",
+ "languages.eng": "영어",
+ "languages.epo": "에스페란토",
+ "languages.est": "에스토이나어",
+ "languages.ewe": "에웨어",
+ "languages.fao": "페로어",
+ "languages.fij": "피지어",
+ "languages.fin": "핀란드어",
+ "languages.fra": "프랑스어",
+ "languages.ful": "풀라어",
+ "languages.glg": "갈리시아어",
+ "languages.kat": "조지아어",
+ "languages.deu": "독일어",
+ "languages.ell": "그리스어",
+ "languages.grn": "과라니어",
+ "languages.guj": "구자라트어",
+ "languages.hat": "아이티어",
+ "languages.hau": "하우사어",
+ "languages.hed": "히브리어",
+ "languages.her": "헤레로어",
+ "languages.hin": "힌디어",
+ "languages.hmo": "히리 모투어",
+ "languages.hun": "헝가리어",
+ "languages.ina": "인테르링구아",
+ "languages.ind": "인도네시아어",
+ "languages.ile": "인테르링구에",
+ "languages.gle": "아일랜드어",
+ "languages.ibo": "이그보어",
+ "languages.ipk": "이누피아크어",
+ "languages.ido": "이도",
+ "languages.isl": "아이슬란드어",
+ "languages.ita": "이탈리아어",
+ "languages.iku": "이누크티투트어",
+ "languages.jpn": "일본어",
+ "languages.jav": "자바어",
+ "languages.kal": "그린란드어",
+ "languages.kan": "칸나다어",
+ "languages.kau": "카누리어",
+ "languages.kas": "카슈미르어",
+ "languages.kaz": "카자흐어",
+ "languages.khm": "크메르어",
+ "languages.kik": "키쿠유어",
+ "languages.kin": "르완다어",
+ "languages.kir": "키르기스어",
+ "languages.kom": "코미어",
+ "languages.kon": "콩고어",
+ "languages.kor": "한국어",
+ "languages.kur": "쿠르드어",
+ "languages.kua": "콰냐마어",
+ "languages.lat": "라틴어",
+ "languages.ltz": "룩셈부르크어",
+ "languages.lug": "간다어",
+ "languages.lim": "림뷔르흐어",
+ "languages.lin": "링갈라어",
+ "languages.lao": "라오어",
+ "languages.lit": "리투아니아어",
+ "languages.lub": "루바-카탕가어",
+ "languages.lav": "라트비아어",
+ "languages.glv": "맨어",
+ "languages.mkd": "마케도니아어",
+ "languages.mlg": "마다가스카르어",
+ "languages.msa": "말레이어",
+ "languages.mal": "말라얄람어",
+ "languages.mlt": "몰타어",
+ "languages.mri": "마오리어",
+ "languages.mar": "마라티어",
+ "languages.mah": "마셜어",
+ "languages.mon": "몽골어",
+ "languages.nau": "나우루어",
+ "languages.nav": "나바호어",
+ "languages.nob": "덴마크-노르웨이어",
+ "languages.nde": "은데벨레어(북)",
+ "languages.nep": "네팔어",
+ "languages.ndo": "은동가어",
+ "languages.nno": "신노르웨이어",
+ "languages.nor": "노르웨이어",
+ "languages.iii": "쓰촨 이어",
+ "languages.nbl": "은데벨레어(남)",
+ "languages.oci": "오크어",
+ "languages.oji": "오지브와어",
+ "languages.chu": "슬라브어",
+ "languages.orm": "오로모어",
+ "languages.ori": "오리야어",
+ "languages.oss": "오세트어",
+ "languages.pan": "펀자브어",
+ "languages.pli": "팔리어",
+ "languages.fas": "페르시아어",
+ "languages.pol": "폴란드어",
+ "languages.pus": "파슈토어",
+ "languages.por": "포르투갈어",
+ "languages.que": "케추아어",
+ "languages.roh": "로만슈어",
+ "languages.run": "룬디어",
+ "languages.ron": "루마니아어",
+ "languages.rus": "러시아어",
+ "languages.san": "산스크리트어",
+ "languages.srd": "사르데냐어",
+ "languages.snd": "신드어",
+ "languages.sme": "사미어(북)",
+ "languages.smo": "사모아어",
+ "languages.sag": "상고어",
+ "languages.srp": "세르비아어",
+ "languages.gla": "게일어",
+ "languages.sna": "쇼나어",
+ "languages.sin": "싱할라어",
+ "languages.slk": "슬로바키아어",
+ "languages.slv": "슬로베니아어",
+ "languages.som": "소말리어",
+ "languages.sot": "소토어(남)",
+ "languages.spa": "스페인어",
+ "languages.sun": "순다어",
+ "languages.swa": "스와힐리어",
+ "languages.ssw": "스와티어",
+ "languages.swe": "스웨덴어",
+ "languages.tam": "타밀어",
+ "languages.tel": "텔루구어",
+ "languages.tgk": "타지크어",
+ "languages.tha": "태국어",
+ "languages.tir": "티그리냐어",
+ "languages.bod": "티베트어",
+ "languages.tuk": "투르크멘어",
+ "languages.tgl": "타갈로그어",
+ "languages.tsn": "츠와나어",
+ "languages.ton": "통가어",
+ "languages.tur": "터키어",
+ "languages.tso": "총가어",
+ "languages.tat": "타타르어",
+ "languages.twi": "트위어",
+ "languages.tah": "타히티어",
+ "languages.uig": "위구르어",
+ "languages.ukr": "우크라이나어",
+ "languages.urd": "우르두어",
+ "languages.uzb": "우즈베크어",
+ "languages.ven": "벤다어",
+ "languages.vie": "베트남어",
+ "languages.vol": "볼라퓌크",
+ "languages.win": "왈론어",
+ "languages.cym": "웨일스어",
+ "languages.wol": "월로프어",
+ "languages.fry": "프리지아어",
+ "languages.xho": "코사어",
+ "languages.yid": "이디시어",
+ "languages.yor": "요루바어",
+ "languages.zha": "좡어",
+ "languages.zul": "줄루어",
+ "DO_NOT_TRANSLATE": "end"
+}
diff --git a/sources/lang/Localization_nb.json b/sources/lang/Localization_nb.json
new file mode 100644
index 0000000..c0f84ed
--- /dev/null
+++ b/sources/lang/Localization_nb.json
@@ -0,0 +1,293 @@
+{
+ "about.title": "Om COPS",
+ "allbooks.alphabetical.many": "Alfabetisk indeks for {0} bøker",
+ "allbooks.alphabetical.none": "Alfabetisk indeks for absolutt ingen bøker",
+ "allbooks.alphabetical.one": "Alfabetisk indeks for èn enkelt bok",
+ "allbooks.title": "Alle bøker",
+ "authors.alphabetical.many": "Alfabetisk indeks for {0} forfattere",
+ "authors.alphabetical.none": "Alfabetisk indeks for ingen forfattere",
+ "authors.alphabetical.one": "Alfabetisk indeks for èn forfatter",
+ "authors.title": "Forfattere",
+ "authorword.many": "{0} forfattere",
+ "authorword.none": "Ingen forfattere",
+ "authorword.one": "1 forfatter",
+ "bookentry.author": "{0} av {1}",
+ "bookword.many": "{0} bøker",
+ "bookword.none": "Ingen bøker",
+ "bookword.one": "1 bok",
+ "bookword.title": "Bøker",
+ "cog.alternate": "Søk, sortering og filtrering",
+ "content.series": "Serier:",
+ "content.series.data": "Bok {0} i serien {1}",
+ "content.summary": "Sammendrag",
+ "customcolumn.boolean.no": "Nei",
+ "customcolumn.boolean.unknown": "Ikke satt",
+ "customcolumn.boolean.yes": "Ja",
+ "customcolumn.date.format": "Y-m-d",
+ "customcolumn.date.unknown": "Ikke satt",
+ "customcolumn.description": "Custom column '{0}'",
+ "customcolumn.description.bool": "Index of a boolean value",
+ "customcolumn.description.enum.many": "Alphabetical index of the {0} values",
+ "customcolumn.description.enum.none": "Alphabetical index of absolutely no values",
+ "customcolumn.description.enum.one": "Alphabetical index of one value",
+ "customcolumn.description.rating": "Index of ratings",
+ "customcolumn.description.series.many": "Alfabetisk indeks for {0} serier",
+ "customcolumn.description.series.none": "Alfabetisk indeks for ingen serier",
+ "customcolumn.description.series.one": "Alfabetisk indeks for èn enkelt serie",
+ "customcolumn.enum.unknown": "Ikke satt",
+ "customcolumn.float.unknown": "Ikke satt",
+ "customcolumn.int.unknown": "Ikke satt",
+ "customcolumn.rating.unknown": "Ikke satt",
+ "customcolumn.stars.many": "{0} Stars",
+ "customcolumn.stars.none": "No Stars",
+ "customcolumn.stars.one": "1 Star",
+ "customize.email": "Sett e-postadresse (for å sende bøker i e-post)",
+ "customize.fancybox": "Bruk en Lightbox",
+ "customize.filter": "Tillat filtrering på nøkkelord",
+ "customize.ignored": "Ignorerte kategorier",
+ "customize.paging": "Maks antall bøker per side (-1 for å deaktivere)",
+ "customize.style": "Tema",
+ "customize.title": "Tilpass COPS brukergrensesnitt",
+ "home.alternate": "Hjem",
+ "i18n.coversection": "Omslag",
+ "language.title": "Språk",
+ "languages.alphabetical.many": "Alfabetisk indek for de {0} språkene",
+ "languages.alphabetical.none": "Alfabetisk indeks for absolutt ingen språk",
+ "languages.alphabetical.one": "Alfabetisk indeks for ett enkelt språk",
+ "languages.title": "Språk",
+ "mail.messagenotsent": "Meldingen kunne ikke sendes.",
+ "mail.messagesent": "Meldingen er sendt",
+ "paging.next.alternate": "Neste",
+ "paging.previous.alternate": "Forrige",
+ "permalink.alternate": "Permalenke",
+ "pubdate.title": "Publikasjonsår",
+ "publisher.name": "Utgiver",
+ "publishers.alphabetical.many": "Alfabetisk indeks for {0} utgivere",
+ "publishers.alphabetical.none": "Alfabetisk indeks for absolutt ingen utgivere",
+ "publishers.alphabetical.one": "Alfabetisk indeks for en enkelt utgiver",
+ "publishers.title": "Utgivere",
+ "publisherword.many": "{0} utgivere",
+ "publisherword.none": "Ingen utgivere",
+ "publisherword.one": "1 utgiver",
+ "ratings.many": "{0} vurderinger",
+ "ratings.none": "ingen vurderinger",
+ "ratings.one": "1 vurdering",
+ "ratings.title": "Vurderinger",
+ "ratingword.many": "{0} stjerner",
+ "ratingword.none": "Ingen stjerner",
+ "ratingword.one": "1 stjerne",
+ "recent.list": "{0} nyeste bøker",
+ "recent.title": "Nylig lagt til",
+ "search.alternate": "Søk",
+ "search.result": "Søkeresultat for *{0}*",
+ "search.result.author": "Søkeresultat for *{0}* i forfattere",
+ "search.result.book": "Søkeresultat for *{0}* i bøker",
+ "search.result.publisher": "Søkeresultat for *{0}* i utgivere",
+ "search.result.series": "Søkeresultat for *{0}* i serier",
+ "search.result.tag": "Søkeresultat for *{0}* i nøkkelord",
+ "search.sortorder.asc": "Stigende",
+ "search.sortorder.desc": "Synkende",
+ "series.alphabetical.many": "Alfabetisk indeks for {0} serier",
+ "series.alphabetical.none": "Alfabetisk indeks for ingen serier",
+ "series.alphabetical.one": "Alfabetisk indeks for èn enkelt serie",
+ "series.title": "Serier",
+ "seriesword.many": "{0} serier",
+ "seriesword.none": "Ingen serier",
+ "seriesword.one": "1 serie",
+ "sort.alternate": "Sorter",
+ "splitByLetter.book.other": "Andre bøker",
+ "splitByLetter.letter": "{0} begynner med {1}",
+ "tags.alphabetical.many": "Alfabetisk indeks for {0} stikkord",
+ "tags.alphabetical.none": "Alfabetisk indeks for ingen stikkord",
+ "tags.alphabetical.one": "Alfabetisk indeks for ett enkelt stikkord",
+ "tags.title": "Stikkord",
+ "tagword.many": "{0} stikkord",
+ "tagword.none": "Ingen stikkord",
+ "tagword.one": "1 stikkord",
+ "tagword.title": "Stikkord",
+ "languages.abk": "Abkhaz",
+ "languages.aaf": "Afar",
+ "languages.afr": "Afrikaans",
+ "languages.aka": "Akan",
+ "languages.sqi": "Albanian",
+ "languages.amh": "Amharic",
+ "languages.ara": "Arabic",
+ "languages.arg": "Aragonese",
+ "languages.hye": "Armenian",
+ "languages.asm": "Assamese",
+ "languages.ava": "Avaric",
+ "languages.ave": "Avestan",
+ "languages.aym": "Aymara",
+ "languages.aze": "Azerbaijani",
+ "languages.bam": "Bambara",
+ "languages.bak": "Bashkir",
+ "languages.eus": "Basque",
+ "languages.bel": "Belarusian",
+ "languages.ben": "Bengali",
+ "languages.bih": "Bihari",
+ "languages.bis": "Bislama",
+ "languages.bos": "Bosnian",
+ "languages.bre": "Breton",
+ "languages.bul": "Bulgarian",
+ "languages.mya": "Burmese",
+ "languages.cat": "Catalan",
+ "languages.cha": "Chamorro",
+ "languages.che": "Chechen",
+ "languages.nya": "Chichewa",
+ "languages.zho": "Chinese",
+ "languages.chv": "Chuvash",
+ "languages.cor": "Cornish",
+ "languages.cos": "Corsican",
+ "languages.cre": "Cree",
+ "languages.hrv": "Croatian",
+ "languages.ces": "Czech",
+ "languages.dan": "Danish",
+ "languages.div": "Divehi",
+ "languages.nld": "Dutch",
+ "languages.dzo": "Dzongkha",
+ "languages.eng": "English",
+ "languages.epo": "Esperanto",
+ "languages.est": "Estonian",
+ "languages.ewe": "Ewe",
+ "languages.fao": "Faroese",
+ "languages.fij": "Fijian",
+ "languages.fin": "Finnish",
+ "languages.fra": "French",
+ "languages.ful": "Fula",
+ "languages.glg": "Galician",
+ "languages.kat": "Georgian",
+ "languages.deu": "German",
+ "languages.ell": "Greek",
+ "languages.grn": "Guaraní",
+ "languages.guj": "Gujarati",
+ "languages.hat": "Haitian",
+ "languages.hau": "Hausa",
+ "languages.hed": "Hebrew",
+ "languages.her": "Herero",
+ "languages.hin": "Hindi",
+ "languages.hmo": "Hiri Motu",
+ "languages.hun": "Hungarian",
+ "languages.ina": "Interlingua",
+ "languages.ind": "Indonesian",
+ "languages.ile": "Interlingue",
+ "languages.gle": "Irish",
+ "languages.ibo": "Igbo",
+ "languages.ipk": "Inupiaq",
+ "languages.ido": "Ido",
+ "languages.isl": "Icelandic",
+ "languages.ita": "Italian",
+ "languages.iku": "Inuktitut",
+ "languages.jpn": "Japanese",
+ "languages.jav": "Javanese",
+ "languages.kal": "Kalaallisut",
+ "languages.kan": "Kannada",
+ "languages.kau": "Kanuri",
+ "languages.kas": "Kashmiri",
+ "languages.kaz": "Kazakh",
+ "languages.khm": "Khmer",
+ "languages.kik": "Kikuyu",
+ "languages.kin": "Kinyarwanda",
+ "languages.kir": "Kyrgyz",
+ "languages.kom": "Komi",
+ "languages.kon": "Kongo",
+ "languages.kor": "Korean",
+ "languages.kur": "Kurdish",
+ "languages.kua": "Kwanyama",
+ "languages.lat": "Latin",
+ "languages.ltz": "Luxembourgish",
+ "languages.lug": "Ganda",
+ "languages.lim": "Limburgish",
+ "languages.lin": "Lingala",
+ "languages.lao": "Lao",
+ "languages.lit": "Lithuanian",
+ "languages.lub": "Luba-Katanga",
+ "languages.lav": "Latvian",
+ "languages.glv": "Manx",
+ "languages.mkd": "Macedonian",
+ "languages.mlg": "Malagasy",
+ "languages.msa": "Malay",
+ "languages.mal": "Malayalam",
+ "languages.mlt": "Maltese",
+ "languages.mri": "Māori",
+ "languages.mar": "Marathi",
+ "languages.mah": "Marshallese",
+ "languages.mon": "Mongolian",
+ "languages.nau": "Nauru",
+ "languages.nav": "Navajo",
+ "languages.nob": "Norsk bokmål",
+ "languages.nde": "North Ndebele",
+ "languages.nep": "Nepali",
+ "languages.ndo": "Ndonga",
+ "languages.nno": "Norsk nynorsk",
+ "languages.nor": "Norsk",
+ "languages.iii": "Nuosu",
+ "languages.nbl": "South Ndebele",
+ "languages.oci": "Occitan",
+ "languages.oji": "Ojibwe",
+ "languages.chu": "Old Church Slavonic",
+ "languages.orm": "Oromo",
+ "languages.ori": "Oriya",
+ "languages.oss": "Ossetian",
+ "languages.pan": "Panjabi",
+ "languages.pli": "Pāli",
+ "languages.fas": "Persian",
+ "languages.pol": "Polish",
+ "languages.pus": "Pashto",
+ "languages.por": "Portuguese",
+ "languages.que": "Quechua",
+ "languages.roh": "Romansh",
+ "languages.run": "Kirundi",
+ "languages.ron": "Romanian",
+ "languages.rus": "Russian",
+ "languages.san": "Sanskrit",
+ "languages.srd": "Sardinian",
+ "languages.snd": "Sindhi",
+ "languages.sme": "Northern Sami",
+ "languages.smo": "Samoan",
+ "languages.sag": "Sango",
+ "languages.srp": "Serbian",
+ "languages.gla": "Scottish Gaelic",
+ "languages.sna": "Shona",
+ "languages.sin": "Sinhala",
+ "languages.slk": "Slovak",
+ "languages.slv": "Slovene",
+ "languages.som": "Somali",
+ "languages.sot": "Southern Sotho",
+ "languages.spa": "Spanish",
+ "languages.sun": "Sundanese",
+ "languages.swa": "Swahili",
+ "languages.ssw": "Swati",
+ "languages.swe": "Swedish",
+ "languages.tam": "Tamil",
+ "languages.tel": "Telugu",
+ "languages.tgk": "Tajik",
+ "languages.tha": "Thai",
+ "languages.tir": "Tigrinya",
+ "languages.bod": "Tibetan Standard",
+ "languages.tuk": "Turkmen",
+ "languages.tgl": "Tagalog",
+ "languages.tsn": "Tswana",
+ "languages.ton": "Tonga",
+ "languages.tur": "Turkish",
+ "languages.tso": "Tsonga",
+ "languages.tat": "Tatar",
+ "languages.twi": "Twi",
+ "languages.tah": "Tahitian",
+ "languages.uig": "Uighur",
+ "languages.ukr": "Ukrainian",
+ "languages.urd": "Urdu",
+ "languages.uzb": "Uzbek",
+ "languages.ven": "Venda",
+ "languages.vie": "Vietnamese",
+ "languages.vol": "Volapük",
+ "languages.win": "Walloon",
+ "languages.cym": "Welsh",
+ "languages.wol": "Wolof",
+ "languages.fry": "Western Frisian",
+ "languages.xho": "Xhosa",
+ "languages.yid": "Yiddish",
+ "languages.yor": "Yoruba",
+ "languages.zha": "Zhuang",
+ "languages.zul": "Zulu",
+ "DO_NOT_TRANSLATE": "end"
+}
diff --git a/sources/lang/Localization_nl.json b/sources/lang/Localization_nl.json
new file mode 100644
index 0000000..6ee262a
--- /dev/null
+++ b/sources/lang/Localization_nl.json
@@ -0,0 +1,293 @@
+{
+ "about.title": "Over COPS",
+ "allbooks.alphabetical.many": "Alfabetische index van {0} boeken",
+ "allbooks.alphabetical.none": "Alfabetische index van absoluut geen enkel boek",
+ "allbooks.alphabetical.one": "Alfabetische index van 1 boek",
+ "allbooks.title": "Alle boeken",
+ "authors.alphabetical.many": "Alfabetische index van {0} auteurs",
+ "authors.alphabetical.none": "Alfabetische index van absoluut geen enkele auteur",
+ "authors.alphabetical.one": "Alfabetische index van 1 auteur",
+ "authors.title": "Auteurs",
+ "authorword.many": "{0} auteurs",
+ "authorword.none": "Geen auteur",
+ "authorword.one": "1 auteur",
+ "bookentry.author": "{0} door {1}",
+ "bookword.many": "{0} boeken",
+ "bookword.none": "Geen boek",
+ "bookword.one": "1 boek",
+ "bookword.title": "Boeken",
+ "cog.alternate": "Zoeken, sorteren en filters",
+ "content.series": "Reeksen:",
+ "content.series.data": "Boek {0} in de {1} reeks",
+ "content.summary": "Samenvatting",
+ "customcolumn.boolean.no": "Nee",
+ "customcolumn.boolean.unknown": "Niet ingesteld",
+ "customcolumn.boolean.yes": "Ja",
+ "customcolumn.date.format": "J-m-d",
+ "customcolumn.date.unknown": "Niet ingesteld",
+ "customcolumn.description": "Aangepaste kolom '{0}'",
+ "customcolumn.description.bool": "Index van een booleaanse waarde",
+ "customcolumn.description.enum.many": "Alfabetische index van {0} waarden",
+ "customcolumn.description.enum.none": "Alfabetische index van absoluut geen enkele waarde",
+ "customcolumn.description.enum.one": "Alfabetische index van één waarde",
+ "customcolumn.description.rating": "Index van waarderingen",
+ "customcolumn.description.series.many": "Alfabetische index van {0} reeksen",
+ "customcolumn.description.series.none": "Alfabetische index van absoluut geen enkele reeks",
+ "customcolumn.description.series.one": "Alfabetische index van 1 reeks",
+ "customcolumn.enum.unknown": "Niet ingesteld",
+ "customcolumn.float.unknown": "Niet ingesteld",
+ "customcolumn.int.unknown": "Niet ingesteld",
+ "customcolumn.rating.unknown": "Niet ingesteld",
+ "customcolumn.stars.many": "{0} Sterren",
+ "customcolumn.stars.none": "Geen sterren",
+ "customcolumn.stars.one": "1 Ster",
+ "customize.email": "E-mailadres ontvanger (om boeken te versturen per elektronische post)",
+ "customize.fancybox": "Gebruik een Lightbox",
+ "customize.filter": "Zet filteren op label aan",
+ "customize.ignored": "Niet opgenomen categorieën",
+ "customize.paging": "Maximaal aantal boeken per pagina (-1 voor oneindig aantal)",
+ "customize.style": "Opmaak COPS gebruikersomgeving",
+ "customize.title": "Aanpassen COPS gebruikersomgeving",
+ "home.alternate": "Terug",
+ "i18n.coversection": "Omslag",
+ "language.title": "Taal",
+ "languages.alphabetical.many": "Alfabetische index van {0} talen",
+ "languages.alphabetical.none": "Alfabetische index van absoluut geen enkele taal",
+ "languages.alphabetical.one": "Alfabetische index van taal",
+ "languages.title": "Talen",
+ "mail.messagenotsent": "Bericht kon niet verzonden worden.",
+ "mail.messagesent": "Bericht is verzonden",
+ "paging.next.alternate": "Volgende",
+ "paging.previous.alternate": "Vorige",
+ "permalink.alternate": "Permalink",
+ "pubdate.title": "Jaar van publicatie",
+ "publisher.name": "Uitgever",
+ "publishers.alphabetical.many": "Alfabetische index van {0} uitgevers",
+ "publishers.alphabetical.none": "Alfabetische index van afwezige uitgevers",
+ "publishers.alphabetical.one": "Alfabetische index van een uitgever",
+ "publishers.title": "Uitgevers",
+ "publisherword.many": "{0} uitgevers",
+ "publisherword.none": "Geen uitgever",
+ "publisherword.one": "1 uitgever",
+ "ratings.many": "{0} verschillende waarderingen",
+ "ratings.none": "geen waarderingen",
+ "ratings.one": "{0} waarderingen",
+ "ratings.title": "Waardering",
+ "ratingword.many": "{0} sterren",
+ "ratingword.none": "Geen ster",
+ "ratingword.one": "1 ster",
+ "recent.list": "{0} meest recente boeken",
+ "recent.title": "Recent toegevoegde boeken",
+ "search.alternate": "Zoeken",
+ "search.result": "Zoekresultaat voor *{0}*",
+ "search.result.author": "Zoekresultaat voor *{0}* in auteurs",
+ "search.result.book": "Zoekresultaat voor *{0}* in boeken",
+ "search.result.publisher": "Zoekresultaat voor *{0}* in uitgevers",
+ "search.result.series": "Zoekresultaat voor *{0}* in reeksen",
+ "search.result.tag": "Zoekresultaat voor *{0}* in labels",
+ "search.sortorder.asc": "A-Z",
+ "search.sortorder.desc": "Z-A",
+ "series.alphabetical.many": "Alfabetische index van {0} reeksen",
+ "series.alphabetical.none": "Alfabetische index van absoluut geen enkele reeks",
+ "series.alphabetical.one": "Alfabetische index van 1 reeks",
+ "series.title": "Reeksen",
+ "seriesword.many": "{0} reeksen",
+ "seriesword.none": "Geen reeks",
+ "seriesword.one": "1 reeks",
+ "sort.alternate": "Sorteren",
+ "splitByLetter.book.other": "Andere boeken",
+ "splitByLetter.letter": "{0} beginnend met {1}",
+ "tags.alphabetical.many": "Alfabetische index van {0} labels",
+ "tags.alphabetical.none": "Alfabetische index van absoluut geen enkele label",
+ "tags.alphabetical.one": "Alfabetische index van 1 label",
+ "tags.title": "Labels",
+ "tagword.many": "{0} labels",
+ "tagword.none": "Geen label",
+ "tagword.one": "1 label",
+ "tagword.title": "Labels",
+ "languages.abk": "Abchazisch",
+ "languages.aaf": "Afar",
+ "languages.afr": "Afrikaans",
+ "languages.aka": "Akan",
+ "languages.sqi": "Albanees",
+ "languages.amh": "Amhaars",
+ "languages.ara": "Arabisch",
+ "languages.arg": "Aragonees",
+ "languages.hye": "Armeens",
+ "languages.asm": "Assamees",
+ "languages.ava": "Avarisch",
+ "languages.ave": "Avestisch",
+ "languages.aym": "Aymara",
+ "languages.aze": "Azerbeidzjaans",
+ "languages.bam": "Bambara",
+ "languages.bak": "Basjkiers",
+ "languages.eus": "Baskisch",
+ "languages.bel": "Wit-Russisch",
+ "languages.ben": "Bengaals",
+ "languages.bih": "Biharitalen",
+ "languages.bis": "Bislama",
+ "languages.bos": "Bosnisch",
+ "languages.bre": "Bretons",
+ "languages.bul": "Bulgaars",
+ "languages.mya": "Birmees",
+ "languages.cat": "Catalaans",
+ "languages.cha": "Chamorro",
+ "languages.che": "Tsjetsjeens",
+ "languages.nya": "Nyanja",
+ "languages.zho": "Chinees",
+ "languages.chv": "Tsjoevasjisch",
+ "languages.cor": "Cornish",
+ "languages.cos": "Corsicaans",
+ "languages.cre": "Cree",
+ "languages.hrv": "Kroatisch",
+ "languages.ces": "Tsjechisch",
+ "languages.dan": "Deens",
+ "languages.div": "Divehi",
+ "languages.nld": "Nederlands",
+ "languages.dzo": "Dzongkha",
+ "languages.eng": "Engels",
+ "languages.epo": "Esperanto",
+ "languages.est": "Ests",
+ "languages.ewe": "Ewe",
+ "languages.fao": "Faeröers",
+ "languages.fij": "Fijisch",
+ "languages.fin": "Fins",
+ "languages.fra": "Frans",
+ "languages.ful": "Fulah",
+ "languages.glg": "Galicisch",
+ "languages.kat": "Georgisch",
+ "languages.deu": "Duits",
+ "languages.ell": "Grieks",
+ "languages.grn": "Guaraní",
+ "languages.guj": "Gujarati",
+ "languages.hat": "Haïtiaans",
+ "languages.hau": "Hausa",
+ "languages.hed": "Hebreeuws",
+ "languages.her": "Herero",
+ "languages.hin": "Hindi",
+ "languages.hmo": "Hiri Motu",
+ "languages.hun": "Hongaars",
+ "languages.ina": "Interlingua",
+ "languages.ind": "Indonesisch",
+ "languages.ile": "Interlingue",
+ "languages.gle": "Iers",
+ "languages.ibo": "Igbo",
+ "languages.ipk": "Inupiaq",
+ "languages.ido": "Ido",
+ "languages.isl": "IJslands",
+ "languages.ita": "Italiaans",
+ "languages.iku": "Inuktitut",
+ "languages.jpn": "Japans",
+ "languages.jav": "Javaans",
+ "languages.kal": "Groenlands",
+ "languages.kan": "Kannada",
+ "languages.kau": "Kanuri",
+ "languages.kas": "Kasjmiri",
+ "languages.kaz": "Kazachs",
+ "languages.khm": "Khmer",
+ "languages.kik": "Kikuyu",
+ "languages.kin": "Kinyarwanda",
+ "languages.kir": "Kirgizisch",
+ "languages.kom": "Komi",
+ "languages.kon": "Kongo",
+ "languages.kor": "Koreaans",
+ "languages.kur": "Koerdisch",
+ "languages.kua": "Kuanyama",
+ "languages.lat": "Latijn",
+ "languages.ltz": "Luxemburgs",
+ "languages.lug": "Ganda",
+ "languages.lim": "Limburgs",
+ "languages.lin": "Lingala",
+ "languages.lao": "Laotiaans",
+ "languages.lit": "Litouws",
+ "languages.lub": "Luba-Katanga",
+ "languages.lav": "Lets",
+ "languages.glv": "Manx",
+ "languages.mkd": "Macedonisch",
+ "languages.mlg": "Malagasisch",
+ "languages.msa": "Maleis",
+ "languages.mal": "Malayalam",
+ "languages.mlt": "Maltees",
+ "languages.mri": "Māori",
+ "languages.mar": "Marathi",
+ "languages.mah": "Marshallees",
+ "languages.mon": "Mongools",
+ "languages.nau": "Nauruaans",
+ "languages.nav": "Navajo",
+ "languages.nob": "Noors Bokmål",
+ "languages.nde": "Noord-Ndbele",
+ "languages.nep": "Nepalees",
+ "languages.ndo": "Ndonga",
+ "languages.nno": "Noors Nynorsk",
+ "languages.nor": "Noors",
+ "languages.iii": "Sichuan Yi",
+ "languages.nbl": "Zuid-Ndbele",
+ "languages.oci": "Occitaans",
+ "languages.oji": "Ojibwa",
+ "languages.chu": "Kerkslavisch",
+ "languages.orm": "Oromo",
+ "languages.ori": "Odia",
+ "languages.oss": "Ossetisch",
+ "languages.pan": "Punjabi",
+ "languages.pli": "Pāli",
+ "languages.fas": "Perzisch",
+ "languages.pol": "Pools",
+ "languages.pus": "Pasjtoe",
+ "languages.por": "Portugees",
+ "languages.que": "Quechua",
+ "languages.roh": "Reto-Romaans",
+ "languages.run": "Kirundi",
+ "languages.ron": "Roemeens",
+ "languages.rus": "Russisch",
+ "languages.san": "Sanskriet",
+ "languages.srd": "Sardinisch",
+ "languages.snd": "Sindhi",
+ "languages.sme": "Noord-Samisch",
+ "languages.smo": "Samoaans",
+ "languages.sag": "Sango",
+ "languages.srp": "Servisch",
+ "languages.gla": "Schots Gaelic",
+ "languages.sna": "Shona",
+ "languages.sin": "Singalees",
+ "languages.slk": "Slovaaks",
+ "languages.slv": "Sloveens",
+ "languages.som": "Somalisch",
+ "languages.sot": "Zuid-Sotho",
+ "languages.spa": "Spaans",
+ "languages.sun": "Sudanees",
+ "languages.swa": "Swahili",
+ "languages.ssw": "Swazi",
+ "languages.swe": "Zweeds",
+ "languages.tam": "Tamil",
+ "languages.tel": "Telugu",
+ "languages.tgk": "Tadzjieks",
+ "languages.tha": "Thais",
+ "languages.tir": "Tigrinya",
+ "languages.bod": "Tibetaans",
+ "languages.tuk": "Turkmeens",
+ "languages.tgl": "Tagalog",
+ "languages.tsn": "Tswana",
+ "languages.ton": "Tonga",
+ "languages.tur": "Turks",
+ "languages.tso": "Tongaans",
+ "languages.tat": "Tataars",
+ "languages.twi": "Twi",
+ "languages.tah": "Tahitisch",
+ "languages.uig": "Oeigoers",
+ "languages.ukr": "Oekraïens",
+ "languages.urd": "Urdu",
+ "languages.uzb": "Oezbeeks",
+ "languages.ven": "Venda",
+ "languages.vie": "Vietnamees",
+ "languages.vol": "Volapük",
+ "languages.win": "Waals",
+ "languages.cym": "Welsh",
+ "languages.wol": "Wolof",
+ "languages.fry": "Fries",
+ "languages.xho": "Xhosa",
+ "languages.yid": "Jiddisch",
+ "languages.yor": "Yoruba",
+ "languages.zha": "Zhuang",
+ "languages.zul": "Zulu",
+ "DO_NOT_TRANSLATE": "end"
+}
diff --git a/sources/lang/Localization_pl.json b/sources/lang/Localization_pl.json
new file mode 100644
index 0000000..c26b5fc
--- /dev/null
+++ b/sources/lang/Localization_pl.json
@@ -0,0 +1,293 @@
+{
+ "about.title": "About COPS",
+ "allbooks.alphabetical.many": "Indeks alfabetyczny według {0} tytułów książek",
+ "allbooks.alphabetical.none": "Brak książek w spisie",
+ "allbooks.alphabetical.one": "Indeks alfabetyczny jedna książka",
+ "allbooks.title": "Wszystkie książki",
+ "authors.alphabetical.many": "Indeks alfabetyczny według {0} autorów",
+ "authors.alphabetical.none": "Indeks książek w których autor jest nieznany",
+ "authors.alphabetical.one": "Indeks książek",
+ "authors.title": "Autorzy",
+ "authorword.many": "{0} autorów",
+ "authorword.none": "Brak autorów",
+ "authorword.one": "1 autor",
+ "bookentry.author": "{0} przez {1}",
+ "bookword.many": "{0} książek",
+ "bookword.none": "Brak ksiażek",
+ "bookword.one": "1 książka",
+ "bookword.title": "Książki",
+ "cog.alternate": "Wyszukiwanie, sortowanie i filtrowanie",
+ "content.series": "Serie:",
+ "content.series.data": "{0} Książek w {1} serii",
+ "content.summary": "Podsumowanie",
+ "customcolumn.boolean.no": "Nie",
+ "customcolumn.boolean.unknown": "Domyślny dostęp",
+ "customcolumn.boolean.yes": "Tak",
+ "customcolumn.date.format": "Y-m-d",
+ "customcolumn.date.unknown": "Domyślny dostęp",
+ "customcolumn.description": "Custom column '{0}'",
+ "customcolumn.description.bool": "Index of a boolean value",
+ "customcolumn.description.enum.many": "Alphabetical index of the {0} values",
+ "customcolumn.description.enum.none": "Alphabetical index of absolutely no values",
+ "customcolumn.description.enum.one": "Alphabetical index of one value",
+ "customcolumn.description.rating": "Index of ratings",
+ "customcolumn.description.series.many": "Indeks alfabetyczny {0} serii",
+ "customcolumn.description.series.none": "Indeks alfabetyczny - brak serii",
+ "customcolumn.description.series.one": "Indeks alfabetyczny jedna seria",
+ "customcolumn.enum.unknown": "Domyślny dostęp",
+ "customcolumn.float.unknown": "Domyślny dostęp",
+ "customcolumn.int.unknown": "Domyślny dostęp",
+ "customcolumn.rating.unknown": "Domyślny dostęp",
+ "customcolumn.stars.many": "{0} Stars",
+ "customcolumn.stars.none": "No Stars",
+ "customcolumn.stars.one": "1 Star",
+ "customize.email": "Ustaw swój adres e-mail (aby wysyłać książki)",
+ "customize.fancybox": "Używaj Lightbox (wyświetla książki w okienku)",
+ "customize.filter": "Używaj filtrowania po kategoriach",
+ "customize.ignored": "Zignorowane kategorie",
+ "customize.paging": "Maksymalna liczba książek na stronie (-1 blokada)",
+ "customize.style": "Szablon wyglądu",
+ "customize.title": "Ustawienia użytkownika COPS",
+ "home.alternate": "Strona głowna",
+ "i18n.coversection": "Okładka",
+ "language.title": "Język",
+ "languages.alphabetical.many": "Indeks alfabetyczny według {0} jezyków",
+ "languages.alphabetical.none": "Indeks alfabetyczny według języków - brak języków",
+ "languages.alphabetical.one": "Indeks alfabetyczny według języków jeden język",
+ "languages.title": "Języki",
+ "mail.messagenotsent": "Wiadomość nie może być wysłana",
+ "mail.messagesent": "Wiadomość wysłana",
+ "paging.next.alternate": "Następna",
+ "paging.previous.alternate": "Poprzednia",
+ "permalink.alternate": "Permalink",
+ "pubdate.title": "Rok publikacji",
+ "publisher.name": "Wydawca",
+ "publishers.alphabetical.many": "Indeks alfabetyczny według {0} wydawców",
+ "publishers.alphabetical.none": "Indeks alfabetyczny wydawców - brak wydawców",
+ "publishers.alphabetical.one": "Indeks alfabetyczny wydawców - jeden wydawca",
+ "publishers.title": "Wydawcy",
+ "publisherword.many": "{0} wydawców",
+ "publisherword.none": "brak wydawców",
+ "publisherword.one": "jeden wydawca",
+ "ratings.many": "{0} oceny",
+ "ratings.none": "brak ocen",
+ "ratings.one": "jedna ocena",
+ "ratings.title": "Oceny",
+ "ratingword.many": "{0} gwiazdek",
+ "ratingword.none": "brak gwiazdek",
+ "ratingword.one": "jedna gwiazdka",
+ "recent.list": "Ostatnio dodanych {0} książek",
+ "recent.title": "Ostatnio dodane książki",
+ "search.alternate": "Szukaj",
+ "search.result": "Wyniki wyszukiwania dla *{0}*",
+ "search.result.author": "Wyniki wyszukiwania dla *{0}* w autorach",
+ "search.result.book": "Wyniki wyszukiwania dla *{0}* w ksiązkach",
+ "search.result.publisher": "Wyniki wyszukiwania dla *{0}* w wydwcach",
+ "search.result.series": "Wyniki wyszukiwania dla *{0}* W seriach",
+ "search.result.tag": "Wyniki wyszukiwania dla *{0}* w tagach",
+ "search.sortorder.asc": "Sortowanie rosnące",
+ "search.sortorder.desc": "Sortowanie malejące",
+ "series.alphabetical.many": "Indeks alfabetyczny {0} serii",
+ "series.alphabetical.none": "Indeks alfabetyczny - brak serii",
+ "series.alphabetical.one": "Indeks alfabetyczny jedna seria",
+ "series.title": "Serie",
+ "seriesword.many": "{0} serii",
+ "seriesword.none": "Brak serii",
+ "seriesword.one": "Jedna seria",
+ "sort.alternate": "Sortowanie",
+ "splitByLetter.book.other": "Pozostałe książki",
+ "splitByLetter.letter": "{0} na literę {1}",
+ "tags.alphabetical.many": "Indeks alfabetyczny według {0} etykiet",
+ "tags.alphabetical.none": "Indeks alfabetyczny tagów brak etykiet",
+ "tags.alphabetical.one": "Indeks alfabetyczny według jednej etykiety",
+ "tags.title": "Etykiety",
+ "tagword.many": "{0} etykiet",
+ "tagword.none": "Brak etykiet",
+ "tagword.one": "Jedna eykieta",
+ "tagword.title": "Etykiety",
+ "languages.abk": "Abkhaz",
+ "languages.aaf": "Afar",
+ "languages.afr": "Afrikaans",
+ "languages.aka": "Akan",
+ "languages.sqi": "Albanian",
+ "languages.amh": "Amharic",
+ "languages.ara": "Arabic",
+ "languages.arg": "Aragonese",
+ "languages.hye": "Armenian",
+ "languages.asm": "Assamese",
+ "languages.ava": "Avaric",
+ "languages.ave": "Avestan",
+ "languages.aym": "Aymara",
+ "languages.aze": "Azerbaijani",
+ "languages.bam": "Bambara",
+ "languages.bak": "Bashkir",
+ "languages.eus": "Basque",
+ "languages.bel": "Belarusian",
+ "languages.ben": "Bengali",
+ "languages.bih": "Bihari",
+ "languages.bis": "Bislama",
+ "languages.bos": "Bosnian",
+ "languages.bre": "Breton",
+ "languages.bul": "Bulgarian",
+ "languages.mya": "Burmese",
+ "languages.cat": "Catalan",
+ "languages.cha": "Chamorro",
+ "languages.che": "Chechen",
+ "languages.nya": "Chichewa",
+ "languages.zho": "Chinese",
+ "languages.chv": "Chuvash",
+ "languages.cor": "Cornish",
+ "languages.cos": "Corsican",
+ "languages.cre": "Cree",
+ "languages.hrv": "Croatian",
+ "languages.ces": "Czech",
+ "languages.dan": "Danish",
+ "languages.div": "Divehi",
+ "languages.nld": "Dutch",
+ "languages.dzo": "Dzongkha",
+ "languages.eng": "English",
+ "languages.epo": "Esperanto",
+ "languages.est": "Estonian",
+ "languages.ewe": "Ewe",
+ "languages.fao": "Faroese",
+ "languages.fij": "Fijian",
+ "languages.fin": "Finnish",
+ "languages.fra": "French",
+ "languages.ful": "Fula",
+ "languages.glg": "Galician",
+ "languages.kat": "Georgian",
+ "languages.deu": "German",
+ "languages.ell": "Greek",
+ "languages.grn": "Guaraní",
+ "languages.guj": "Gujarati",
+ "languages.hat": "Haitian",
+ "languages.hau": "Hausa",
+ "languages.hed": "Hebrew",
+ "languages.her": "Herero",
+ "languages.hin": "Hindi",
+ "languages.hmo": "Hiri Motu",
+ "languages.hun": "Hungarian",
+ "languages.ina": "Interlingua",
+ "languages.ind": "Indonesian",
+ "languages.ile": "Interlingue",
+ "languages.gle": "Irish",
+ "languages.ibo": "Igbo",
+ "languages.ipk": "Inupiaq",
+ "languages.ido": "Ido",
+ "languages.isl": "Icelandic",
+ "languages.ita": "Italian",
+ "languages.iku": "Inuktitut",
+ "languages.jpn": "Japanese",
+ "languages.jav": "Javanese",
+ "languages.kal": "Kalaallisut",
+ "languages.kan": "Kannada",
+ "languages.kau": "Kanuri",
+ "languages.kas": "Kashmiri",
+ "languages.kaz": "Kazakh",
+ "languages.khm": "Khmer",
+ "languages.kik": "Kikuyu",
+ "languages.kin": "Kinyarwanda",
+ "languages.kir": "Kyrgyz",
+ "languages.kom": "Komi",
+ "languages.kon": "Kongo",
+ "languages.kor": "Korean",
+ "languages.kur": "Kurdish",
+ "languages.kua": "Kwanyama",
+ "languages.lat": "Latin",
+ "languages.ltz": "Luxembourgish",
+ "languages.lug": "Ganda",
+ "languages.lim": "Limburgish",
+ "languages.lin": "Lingala",
+ "languages.lao": "Lao",
+ "languages.lit": "Lithuanian",
+ "languages.lub": "Luba-Katanga",
+ "languages.lav": "Latvian",
+ "languages.glv": "Manx",
+ "languages.mkd": "Macedonian",
+ "languages.mlg": "Malagasy",
+ "languages.msa": "Malay",
+ "languages.mal": "Malayalam",
+ "languages.mlt": "Maltese",
+ "languages.mri": "Māori",
+ "languages.mar": "Marathi",
+ "languages.mah": "Marshallese",
+ "languages.mon": "Mongolian",
+ "languages.nau": "Nauru",
+ "languages.nav": "Navajo",
+ "languages.nob": "Norwegian Bokmål",
+ "languages.nde": "North Ndebele",
+ "languages.nep": "Nepali",
+ "languages.ndo": "Ndonga",
+ "languages.nno": "Norwegian Nynorsk",
+ "languages.nor": "Norwegian",
+ "languages.iii": "Nuosu",
+ "languages.nbl": "South Ndebele",
+ "languages.oci": "Occitan",
+ "languages.oji": "Ojibwe",
+ "languages.chu": "Old Church Slavonic",
+ "languages.orm": "Oromo",
+ "languages.ori": "Oriya",
+ "languages.oss": "Ossetian",
+ "languages.pan": "Panjabi",
+ "languages.pli": "Pāli",
+ "languages.fas": "Persian",
+ "languages.pol": "Polski",
+ "languages.pus": "Pashto",
+ "languages.por": "Portuguese",
+ "languages.que": "Quechua",
+ "languages.roh": "Romansh",
+ "languages.run": "Kirundi",
+ "languages.ron": "Romanian",
+ "languages.rus": "Russian",
+ "languages.san": "Sanskrit",
+ "languages.srd": "Sardinian",
+ "languages.snd": "Sindhi",
+ "languages.sme": "Northern Sami",
+ "languages.smo": "Samoan",
+ "languages.sag": "Sango",
+ "languages.srp": "Serbian",
+ "languages.gla": "Scottish Gaelic",
+ "languages.sna": "Shona",
+ "languages.sin": "Sinhala",
+ "languages.slk": "Slovak",
+ "languages.slv": "Slovene",
+ "languages.som": "Somali",
+ "languages.sot": "Southern Sotho",
+ "languages.spa": "Spanish",
+ "languages.sun": "Sundanese",
+ "languages.swa": "Swahili",
+ "languages.ssw": "Swati",
+ "languages.swe": "Swedish",
+ "languages.tam": "Tamil",
+ "languages.tel": "Telugu",
+ "languages.tgk": "Tajik",
+ "languages.tha": "Thai",
+ "languages.tir": "Tigrinya",
+ "languages.bod": "Tibetan Standard",
+ "languages.tuk": "Turkmen",
+ "languages.tgl": "Tagalog",
+ "languages.tsn": "Tswana",
+ "languages.ton": "Tonga",
+ "languages.tur": "Turkish",
+ "languages.tso": "Tsonga",
+ "languages.tat": "Tatar",
+ "languages.twi": "Twi",
+ "languages.tah": "Tahitian",
+ "languages.uig": "Uighur",
+ "languages.ukr": "Ukrainian",
+ "languages.urd": "Urdu",
+ "languages.uzb": "Uzbek",
+ "languages.ven": "Venda",
+ "languages.vie": "Vietnamese",
+ "languages.vol": "Volapük",
+ "languages.win": "Walloon",
+ "languages.cym": "Welsh",
+ "languages.wol": "Wolof",
+ "languages.fry": "Western Frisian",
+ "languages.xho": "Xhosa",
+ "languages.yid": "Yiddish",
+ "languages.yor": "Yoruba",
+ "languages.zha": "Zhuang",
+ "languages.zul": "Zulu",
+ "DO_NOT_TRANSLATE": "end"
+}
diff --git a/sources/lang/Localization_pt_BR.json b/sources/lang/Localization_pt_BR.json
new file mode 100644
index 0000000..1a44502
--- /dev/null
+++ b/sources/lang/Localization_pt_BR.json
@@ -0,0 +1,293 @@
+{
+ "about.title": "Sobre o COPS",
+ "allbooks.alphabetical.many": "Índice alfabético dos {0} livros",
+ "allbooks.alphabetical.none": "Índice alfabético - nenhum livro",
+ "allbooks.alphabetical.one": "Índice alfabético de um livro",
+ "allbooks.title": "Todos os livros",
+ "authors.alphabetical.many": "Índice alfabético dos {0} autores",
+ "authors.alphabetical.none": "Índice alfabético - nenhum autor",
+ "authors.alphabetical.one": "Índice alfabético de um autor",
+ "authors.title": "Autores",
+ "authorword.many": "{0} autores",
+ "authorword.none": "Nenhum autor",
+ "authorword.one": "1 autor",
+ "bookentry.author": "{0} por {1}",
+ "bookword.many": "{0} livros",
+ "bookword.none": "Nenhum livro",
+ "bookword.one": "1 livro",
+ "bookword.title": "Livros",
+ "cog.alternate": "Pesquisar, ordenar e filtrar",
+ "content.series": "Séries:",
+ "content.series.data": "Livro {0} da série {1}",
+ "content.summary": "Resumo",
+ "customcolumn.boolean.no": "Não",
+ "customcolumn.boolean.unknown": "Não definido",
+ "customcolumn.boolean.yes": "Sim",
+ "customcolumn.date.format": "Y-m-d",
+ "customcolumn.date.unknown": "Não definido",
+ "customcolumn.description": "Custom column '{0}'",
+ "customcolumn.description.bool": "Index of a boolean value",
+ "customcolumn.description.enum.many": "Alphabetical index of the {0} values",
+ "customcolumn.description.enum.none": "Alphabetical index of absolutely no values",
+ "customcolumn.description.enum.one": "Alphabetical index of one value",
+ "customcolumn.description.rating": "Index of ratings",
+ "customcolumn.description.series.many": "Índice alfabético das {0} séries",
+ "customcolumn.description.series.none": "Índice alfabético - nenhuma série",
+ "customcolumn.description.series.one": "Índice alfabético de uma série",
+ "customcolumn.enum.unknown": "Não definido",
+ "customcolumn.float.unknown": "Não definido",
+ "customcolumn.int.unknown": "Não definido",
+ "customcolumn.rating.unknown": "Não definido",
+ "customcolumn.stars.many": "{0} Stars",
+ "customcolumn.stars.none": "No Stars",
+ "customcolumn.stars.one": "1 Star",
+ "customize.email": "Forneça o seu email (para ativar envio de ebooks)",
+ "customize.fancybox": "Usar o Lightbox (detalhes do livro numa janela flutuante)",
+ "customize.filter": "Ativar filtro por etiqueta",
+ "customize.ignored": "Categorias ignoradas",
+ "customize.paging": "Número máximo de livros por página (-1 para desabilitar)",
+ "customize.style": "Tema",
+ "customize.title": "Personalizar a interface do COPS",
+ "home.alternate": "Principal",
+ "i18n.coversection": "Capa",
+ "language.title": "Idioma",
+ "languages.alphabetical.many": "Índice alfabético dos {0} idiomas",
+ "languages.alphabetical.none": "Índice alfabético - nenhum idioma",
+ "languages.alphabetical.one": "Índice alfabético de um idioma",
+ "languages.title": "Idiomas",
+ "mail.messagenotsent": "Não foi possível enviar a mensagem.",
+ "mail.messagesent": "A mensagem foi enviada",
+ "paging.next.alternate": "Próximo",
+ "paging.previous.alternate": "Anterior",
+ "permalink.alternate": "Link permanente",
+ "pubdate.title": "Ano de publicação",
+ "publisher.name": "Editora",
+ "publishers.alphabetical.many": "Índice alfabético das {0} editoras",
+ "publishers.alphabetical.none": "Índice alfabético - nenhuma editora",
+ "publishers.alphabetical.one": "Índice alfabético de uma editora",
+ "publishers.title": "Editoras",
+ "publisherword.many": "{0} editoras",
+ "publisherword.none": "Nenhuma editora",
+ "publisherword.one": "1 editora",
+ "ratings.many": "{0} avaliações",
+ "ratings.none": "nenhuma avaliação",
+ "ratings.one": "1 avaliação",
+ "ratings.title": "Avaliações",
+ "ratingword.many": "{0} estrelas",
+ "ratingword.none": "Nenhuma estrela",
+ "ratingword.one": "1 estrela",
+ "recent.list": "{0} livros mais recentes",
+ "recent.title": "Recentemente adicionados",
+ "search.alternate": "Pesquisar",
+ "search.result": "Resultado da pesquisa por *{0}*",
+ "search.result.author": "Resultado da pesquisa por *{0}* em autores",
+ "search.result.book": "Resultado da pesquisa por *{0}* em livros",
+ "search.result.publisher": "Resultado da pesquisa por *{0}* em editoras",
+ "search.result.series": "Resultado da pesquisa por *{0}* em séries",
+ "search.result.tag": "Resultado da pesquisa por *{0}* em etiquetas",
+ "search.sortorder.asc": "Asc",
+ "search.sortorder.desc": "Desc",
+ "series.alphabetical.many": "Índice alfabético das {0} séries",
+ "series.alphabetical.none": "Índice alfabético - nenhuma série",
+ "series.alphabetical.one": "Índice alfabético de uma série",
+ "series.title": "Séries",
+ "seriesword.many": "{0} séries",
+ "seriesword.none": "Nenhuma série",
+ "seriesword.one": "1 série",
+ "sort.alternate": "Classificar",
+ "splitByLetter.book.other": "Outros livros",
+ "splitByLetter.letter": "{0} começando com {1}",
+ "tags.alphabetical.many": "Índice alfabético das {0} etiquetas",
+ "tags.alphabetical.none": "Índice alfabético - nenhuma etiqueta",
+ "tags.alphabetical.one": "Índice alfabético de uma etiqueta",
+ "tags.title": "Etiquetas",
+ "tagword.many": "{0} etiquetas",
+ "tagword.none": "Nenhuma etiqueta",
+ "tagword.one": "1 etiqueta",
+ "tagword.title": "Etiquetas",
+ "languages.abk": "Abcázia",
+ "languages.aaf": "Afar",
+ "languages.afr": "Sul Africano",
+ "languages.aka": "Akan",
+ "languages.sqi": "Albanês",
+ "languages.amh": "Amárico",
+ "languages.ara": "Árabe",
+ "languages.arg": "Aragonês",
+ "languages.hye": "Armênio",
+ "languages.asm": "Assamês",
+ "languages.ava": "Avari",
+ "languages.ave": "Avéstico",
+ "languages.aym": "Aymara",
+ "languages.aze": "Azerbaidjanês",
+ "languages.bam": "Bambara",
+ "languages.bak": "Basquir",
+ "languages.eus": "Basco",
+ "languages.bel": "Bielorrusso",
+ "languages.ben": "Bengali",
+ "languages.bih": "Bihari",
+ "languages.bis": "Bislamá",
+ "languages.bos": "Bósnio",
+ "languages.bre": "Bretão",
+ "languages.bul": "Búlgaro",
+ "languages.mya": "Birmanês",
+ "languages.cat": "Catalão",
+ "languages.cha": "Chamorro",
+ "languages.che": "Checheno",
+ "languages.nya": "Chichewa",
+ "languages.zho": "Chinês",
+ "languages.chv": "Chuvache",
+ "languages.cor": "Corniso",
+ "languages.cos": "Córsego",
+ "languages.cre": "Cree",
+ "languages.hrv": "Croata",
+ "languages.ces": "Checo",
+ "languages.dan": "Dinamarquês",
+ "languages.div": "Divehi",
+ "languages.nld": "Holandês",
+ "languages.dzo": "Dzonga",
+ "languages.eng": "Inglês",
+ "languages.epo": "Esperanto",
+ "languages.est": "Estoniano",
+ "languages.ewe": "Ewe",
+ "languages.fao": "Faroense",
+ "languages.fij": "fijiano",
+ "languages.fin": "Finlandês",
+ "languages.fra": "Francês",
+ "languages.ful": "Fula",
+ "languages.glg": "Galego",
+ "languages.kat": "Georgiano",
+ "languages.deu": "Alemão",
+ "languages.ell": "Grego",
+ "languages.grn": "Guarani",
+ "languages.guj": "Gujarati",
+ "languages.hat": "Haitiano",
+ "languages.hau": "Hauçá",
+ "languages.hed": "Hebraico",
+ "languages.her": "Herero",
+ "languages.hin": "Híndi",
+ "languages.hmo": "Hiri Motu",
+ "languages.hun": "Húngaro",
+ "languages.ina": "Interlíngua",
+ "languages.ind": "Indonésio",
+ "languages.ile": "Interlíngue",
+ "languages.gle": "Irlandês",
+ "languages.ibo": "Igbo",
+ "languages.ipk": "Inupiaque",
+ "languages.ido": "Ido",
+ "languages.isl": "Islandês",
+ "languages.ita": "Italiano",
+ "languages.iku": "Inuktitut",
+ "languages.jpn": "Japonês",
+ "languages.jav": "Javanês",
+ "languages.kal": "Kal",
+ "languages.kan": "Kannada",
+ "languages.kau": "Kanuri",
+ "languages.kas": "Kashmiri",
+ "languages.kaz": "Cazaque",
+ "languages.khm": "Khmer",
+ "languages.kik": "Quicuio",
+ "languages.kin": "Ruanda",
+ "languages.kir": "Quirguistão",
+ "languages.kom": "Komi",
+ "languages.kon": "Kongo",
+ "languages.kor": "Coreano",
+ "languages.kur": "Curdo",
+ "languages.kua": "Kwanyama",
+ "languages.lat": "Latim",
+ "languages.ltz": "Luxemburguês",
+ "languages.lug": "Ganda",
+ "languages.lim": "Limburguês",
+ "languages.lin": "Lingala",
+ "languages.lao": "Lao",
+ "languages.lit": "Lituânio",
+ "languages.lub": "Luba-Katanga",
+ "languages.lav": "Letã",
+ "languages.glv": "Manês",
+ "languages.mkd": "Macedônio",
+ "languages.mlg": "Malgaxe",
+ "languages.msa": "Malaio",
+ "languages.mal": "Malaiala",
+ "languages.mlt": "Maltês",
+ "languages.mri": "Maori",
+ "languages.mar": "Marathi",
+ "languages.mah": "Marshalês",
+ "languages.mon": "Mongol",
+ "languages.nau": "Nauru",
+ "languages.nav": "Navajo",
+ "languages.nob": "Norueguês de Bokmal",
+ "languages.nde": "Ndebelê do Norte",
+ "languages.nep": "Nepalês",
+ "languages.ndo": "Ndonga",
+ "languages.nno": "Novo Norueguês",
+ "languages.nor": "Norueguês",
+ "languages.iii": "Nuosu",
+ "languages.nbl": "Ndebelê do Sul",
+ "languages.oci": "Occitan",
+ "languages.oji": "Ojíbua",
+ "languages.chu": "Eslavo Eclesiástico",
+ "languages.orm": "Oromo",
+ "languages.ori": "Oriá",
+ "languages.oss": "Osseta",
+ "languages.pan": "Panjabi",
+ "languages.pli": "Páli",
+ "languages.fas": "Pérsio",
+ "languages.pol": "Polonês",
+ "languages.pus": "Pachto",
+ "languages.por": "Português",
+ "languages.que": "Quíchua",
+ "languages.roh": "Romanche",
+ "languages.run": "Kirundi",
+ "languages.ron": "Romeno",
+ "languages.rus": "Russo",
+ "languages.san": "Sânscrito",
+ "languages.srd": "Sardenho",
+ "languages.snd": "Sindi",
+ "languages.sme": "Sami do Norte",
+ "languages.smo": "Samoano",
+ "languages.sag": "Sango",
+ "languages.srp": "Sérvio",
+ "languages.gla": "Gaélico Escocês",
+ "languages.sna": "Xichona",
+ "languages.sin": "Sinhala",
+ "languages.slk": "Eslovaco",
+ "languages.slv": "Esloveno",
+ "languages.som": "Somali",
+ "languages.sot": "Soto do Sul",
+ "languages.spa": "Espanhol",
+ "languages.sun": "Sundanês",
+ "languages.swa": "Suaíle",
+ "languages.ssw": "Swati",
+ "languages.swe": "Sueco",
+ "languages.tam": "Tâmil",
+ "languages.tel": "Telugu",
+ "languages.tgk": "Tadjique",
+ "languages.tha": "Tailandês",
+ "languages.tir": "Tigrinya",
+ "languages.bod": "Tibetano Padrão",
+ "languages.tuk": "Turcomeno",
+ "languages.tgl": "Tagalo",
+ "languages.tsn": "Tswana",
+ "languages.ton": "Tonganês",
+ "languages.tur": "Turco",
+ "languages.tso": "Tsonga",
+ "languages.tat": "Tártaro",
+ "languages.twi": "Twi",
+ "languages.tah": "Taitiano",
+ "languages.uig": "Uigur",
+ "languages.ukr": "Ucraniano",
+ "languages.urd": "Urdu",
+ "languages.uzb": "Uzbeque",
+ "languages.ven": "Venda",
+ "languages.vie": "Vietnamita",
+ "languages.vol": "Volapuque",
+ "languages.win": "Valão",
+ "languages.cym": "Galês",
+ "languages.wol": "Uólofe",
+ "languages.fry": "Frísico Ocidental",
+ "languages.xho": "Xhosa",
+ "languages.yid": "Iídiche",
+ "languages.yor": "Yoruba",
+ "languages.zha": "Zhuang",
+ "languages.zul": "Zulu",
+ "DO_NOT_TRANSLATE": "end"
+}
diff --git a/sources/lang/Localization_pt_PT.json b/sources/lang/Localization_pt_PT.json
new file mode 100644
index 0000000..9f93970
--- /dev/null
+++ b/sources/lang/Localization_pt_PT.json
@@ -0,0 +1,293 @@
+{
+ "about.title": "Sobre a aplicação",
+ "allbooks.alphabetical.many": "Índice alfabético dos {0} livros",
+ "allbooks.alphabetical.none": "Índice alfabético - sem livros",
+ "allbooks.alphabetical.one": "Índice alfabético de 1 livro",
+ "allbooks.title": "Todos os livros",
+ "authors.alphabetical.many": "Índice alfabético dos {0} autores",
+ "authors.alphabetical.none": "Índice alfabético sem autores",
+ "authors.alphabetical.one": "Índice alfabético de 1 autor",
+ "authors.title": "Autores",
+ "authorword.many": "{0} autores",
+ "authorword.none": "Sem autores",
+ "authorword.one": "1 autor",
+ "bookentry.author": "{0} por {1}",
+ "bookword.many": "{0} livros",
+ "bookword.none": "Sem livros",
+ "bookword.one": "1 livro",
+ "bookword.title": "Títulos",
+ "cog.alternate": "Pesquisar, ordenar e filtrar",
+ "content.series": "Séries:",
+ "content.series.data": "Livro {0} da série {1}",
+ "content.summary": "Informações/Resumo",
+ "customcolumn.boolean.no": "Não",
+ "customcolumn.boolean.unknown": "Não definido",
+ "customcolumn.boolean.yes": "Sim",
+ "customcolumn.date.format": "d-m-A",
+ "customcolumn.date.unknown": "Não definido",
+ "customcolumn.description": "Coluna personalizada '{0}'",
+ "customcolumn.description.bool": "Índice - valor boleano",
+ "customcolumn.description.enum.many": "Índice alfabético dos {0} valores",
+ "customcolumn.description.enum.none": "Índice alfabético - sem valores",
+ "customcolumn.description.enum.one": "Índice alfabético de 1 valor",
+ "customcolumn.description.rating": "Índice de pontuações",
+ "customcolumn.description.series.many": "Índice alfabético das {0} séries",
+ "customcolumn.description.series.none": "Índice alfabético - sem séries",
+ "customcolumn.description.series.one": "Índice alfabético com 1 série",
+ "customcolumn.enum.unknown": "Não definido",
+ "customcolumn.float.unknown": "Não definido",
+ "customcolumn.int.unknown": "Não definido",
+ "customcolumn.rating.unknown": "Não definido",
+ "customcolumn.stars.many": "{0} estrelas",
+ "customcolumn.stars.none": "Sem estrelas",
+ "customcolumn.stars.one": "1 estrela",
+ "customize.email": "Definir o seu email (para ativar envio de ebooks)",
+ "customize.fancybox": "Usar o Lightbox (detalhes do livro numa janela flutuante)",
+ "customize.filter": "Ativar filtro por etiquetas",
+ "customize.ignored": "Categorias ignoradas",
+ "customize.paging": "Número máximo de títulos por página (-1 para desativar)",
+ "customize.style": "Estilo",
+ "customize.title": "Personalizar o visual da aplicação",
+ "home.alternate": "Entrada",
+ "i18n.coversection": "Capa",
+ "language.title": "Idioma",
+ "languages.alphabetical.many": "Índice alfabético dos {0} idiomas",
+ "languages.alphabetical.none": "Índice alfabético - sem idiomas",
+ "languages.alphabetical.one": "Índice alfabético de 1 idioma",
+ "languages.title": "Idiomas",
+ "mail.messagenotsent": "Não foi possível enviar a mensagem.",
+ "mail.messagesent": "A mensagem foi enviada",
+ "paging.next.alternate": "Seguinte",
+ "paging.previous.alternate": "Anterior",
+ "permalink.alternate": "Ligação permanente",
+ "pubdate.title": "Ano de publicação",
+ "publisher.name": "Editora",
+ "publishers.alphabetical.many": "Índice alfabético das {0} editoras",
+ "publishers.alphabetical.none": "Índice alfabético - sem editoras",
+ "publishers.alphabetical.one": "Índice alfabético de 1 editora",
+ "publishers.title": "Editoras",
+ "publisherword.many": "{0} editoras",
+ "publisherword.none": "Sem editoras",
+ "publisherword.one": "1 editora",
+ "ratings.many": "{0} avaliações",
+ "ratings.none": "sem avaliações",
+ "ratings.one": "1 avaliação",
+ "ratings.title": "Avaliações",
+ "ratingword.many": "{0} estrelas",
+ "ratingword.none": "Sem estrelas",
+ "ratingword.one": "1 estrela",
+ "recent.list": "{0} livros mais recentes",
+ "recent.title": "Adicionados recentemente",
+ "search.alternate": "Pesquisar",
+ "search.result": "Resultado da pesquisa por *{0}*",
+ "search.result.author": "Resultado da pesquisa por *{0}* em autores",
+ "search.result.book": "Resultado da pesquisa por *{0}* em livros",
+ "search.result.publisher": "Resultado da pesquisa por *{0}* em editoras",
+ "search.result.series": "Resultado da pesquisa por *{0}* em séries",
+ "search.result.tag": "Resultado da pesquisa por *{0}* em etiquetas",
+ "search.sortorder.asc": "A-Z",
+ "search.sortorder.desc": "Z-A",
+ "series.alphabetical.many": "Índice alfabético das {0} séries",
+ "series.alphabetical.none": "Índice alfabético - sem séries",
+ "series.alphabetical.one": "Índice alfabético de 1 série",
+ "series.title": "Séries",
+ "seriesword.many": "{0} séries",
+ "seriesword.none": "Sem séries",
+ "seriesword.one": "1 série",
+ "sort.alternate": "Ordenar",
+ "splitByLetter.book.other": "Outros livros",
+ "splitByLetter.letter": "{0} pela letra: {1}",
+ "tags.alphabetical.many": "Índice alfabético das {0} etiquetas",
+ "tags.alphabetical.none": "Índice alfabético - sem etiquetas",
+ "tags.alphabetical.one": "Índice alfabético de 1 etiqueta",
+ "tags.title": "Etiquetas",
+ "tagword.many": "{0} etiquetas",
+ "tagword.none": "Sem etiquetas",
+ "tagword.one": "1 etiqueta",
+ "tagword.title": "Etiquetas",
+ "languages.abk": "Abcázia",
+ "languages.aaf": "Afar",
+ "languages.afr": "Sul Africano",
+ "languages.aka": "Akan",
+ "languages.sqi": "Albanês",
+ "languages.amh": "Amárico",
+ "languages.ara": "Árabe",
+ "languages.arg": "Aragonês",
+ "languages.hye": "Armênio",
+ "languages.asm": "Assamês",
+ "languages.ava": "Avari",
+ "languages.ave": "Avéstico",
+ "languages.aym": "Aymara",
+ "languages.aze": "Azerbaidjanês",
+ "languages.bam": "Bambara",
+ "languages.bak": "Basquir",
+ "languages.eus": "Basco",
+ "languages.bel": "Bielorrusso",
+ "languages.ben": "Bengali",
+ "languages.bih": "Bihari",
+ "languages.bis": "Bislamá",
+ "languages.bos": "Bósnio",
+ "languages.bre": "Bretão",
+ "languages.bul": "Búlgaro",
+ "languages.mya": "Birmanês",
+ "languages.cat": "Catalão",
+ "languages.cha": "Chamorro",
+ "languages.che": "Checheno",
+ "languages.nya": "Chichewa",
+ "languages.zho": "Chinês",
+ "languages.chv": "Chuvache",
+ "languages.cor": "Corniso",
+ "languages.cos": "Córsego",
+ "languages.cre": "Cree",
+ "languages.hrv": "Croata",
+ "languages.ces": "Checo",
+ "languages.dan": "Dinamarquês",
+ "languages.div": "Divehi",
+ "languages.nld": "Holandês",
+ "languages.dzo": "Dzonga",
+ "languages.eng": "Inglês",
+ "languages.epo": "Esperanto",
+ "languages.est": "Estoniano",
+ "languages.ewe": "Ewe",
+ "languages.fao": "Faroense",
+ "languages.fij": "Fijiano",
+ "languages.fin": "Finlandês",
+ "languages.fra": "Francês",
+ "languages.ful": "Fula",
+ "languages.glg": "Galego",
+ "languages.kat": "Georgiano",
+ "languages.deu": "Alemão",
+ "languages.ell": "Grego",
+ "languages.grn": "Guarani",
+ "languages.guj": "Gujarati",
+ "languages.hat": "Haitiano",
+ "languages.hau": "Hauçá",
+ "languages.hed": "Hebraico",
+ "languages.her": "Herero",
+ "languages.hin": "Híndi",
+ "languages.hmo": "Hiri Motu",
+ "languages.hun": "Húngaro",
+ "languages.ina": "Interlíngua",
+ "languages.ind": "Indonésio",
+ "languages.ile": "Interlíngue",
+ "languages.gle": "Irlandês",
+ "languages.ibo": "Igbo",
+ "languages.ipk": "Inupiaque",
+ "languages.ido": "Ido",
+ "languages.isl": "Islandês",
+ "languages.ita": "Italiano",
+ "languages.iku": "Inuktitut",
+ "languages.jpn": "Japonês",
+ "languages.jav": "Javanês",
+ "languages.kal": "Kal",
+ "languages.kan": "Kannada",
+ "languages.kau": "Kanuri",
+ "languages.kas": "Kashmiri",
+ "languages.kaz": "Cazaque",
+ "languages.khm": "Khmer",
+ "languages.kik": "Quicuio",
+ "languages.kin": "Ruanda",
+ "languages.kir": "Quirguistão",
+ "languages.kom": "Komi",
+ "languages.kon": "Kongo",
+ "languages.kor": "Coreano",
+ "languages.kur": "Curdo",
+ "languages.kua": "Kwanyama",
+ "languages.lat": "Latim",
+ "languages.ltz": "Luxemburguês",
+ "languages.lug": "Ganda",
+ "languages.lim": "Limburguês",
+ "languages.lin": "Lingala",
+ "languages.lao": "Lao",
+ "languages.lit": "Lituânio",
+ "languages.lub": "Luba-Katanga",
+ "languages.lav": "Letã",
+ "languages.glv": "Manês",
+ "languages.mkd": "Macedônio",
+ "languages.mlg": "Malgaxe",
+ "languages.msa": "Malaio",
+ "languages.mal": "Malaiala",
+ "languages.mlt": "Maltês",
+ "languages.mri": "Maori",
+ "languages.mar": "Marathi",
+ "languages.mah": "Marshalês",
+ "languages.mon": "Mongol",
+ "languages.nau": "Nauru",
+ "languages.nav": "Navajo",
+ "languages.nob": "Norueguês de Bokmal",
+ "languages.nde": "Ndebelê do Norte",
+ "languages.nep": "Nepalês",
+ "languages.ndo": "Ndonga",
+ "languages.nno": "Novo Norueguês",
+ "languages.nor": "Norueguês",
+ "languages.iii": "Nuosu",
+ "languages.nbl": "Ndebelê do Sul",
+ "languages.oci": "Occitan",
+ "languages.oji": "Ojíbua",
+ "languages.chu": "Eslavo Eclesiástico",
+ "languages.orm": "Oromo",
+ "languages.ori": "Oriá",
+ "languages.oss": "Osseta",
+ "languages.pan": "Panjabi",
+ "languages.pli": "Páli",
+ "languages.fas": "Persa",
+ "languages.pol": "Polaco",
+ "languages.pus": "Pachto",
+ "languages.por": "Português",
+ "languages.que": "Quíchua",
+ "languages.roh": "Romanche",
+ "languages.run": "Kirundi",
+ "languages.ron": "Romeno",
+ "languages.rus": "Russo",
+ "languages.san": "Sânscrito",
+ "languages.srd": "Sardenho",
+ "languages.snd": "Sindi",
+ "languages.sme": "Sami do Norte",
+ "languages.smo": "Samoano",
+ "languages.sag": "Sango",
+ "languages.srp": "Sérvio",
+ "languages.gla": "Gaélico Escocês",
+ "languages.sna": "Xichona",
+ "languages.sin": "Sinhala",
+ "languages.slk": "Eslovaco",
+ "languages.slv": "Esloveno",
+ "languages.som": "Somali",
+ "languages.sot": "Soto do Sul",
+ "languages.spa": "Espanhol",
+ "languages.sun": "Sundanês",
+ "languages.swa": "Suaíle",
+ "languages.ssw": "Swati",
+ "languages.swe": "Sueco",
+ "languages.tam": "Tâmil",
+ "languages.tel": "Telugu",
+ "languages.tgk": "Tadjique",
+ "languages.tha": "Tailandês",
+ "languages.tir": "Tigrinya",
+ "languages.bod": "Tibetano Padrão",
+ "languages.tuk": "Turcomeno",
+ "languages.tgl": "Tagalo",
+ "languages.tsn": "Tswana",
+ "languages.ton": "Tonganês",
+ "languages.tur": "Turco",
+ "languages.tso": "Tsonga",
+ "languages.tat": "Tártaro",
+ "languages.twi": "Twi",
+ "languages.tah": "Taitiano",
+ "languages.uig": "Uigur",
+ "languages.ukr": "Ucraniano",
+ "languages.urd": "Urdu",
+ "languages.uzb": "Uzbeque",
+ "languages.ven": "Venda",
+ "languages.vie": "Vietnamita",
+ "languages.vol": "Volapuque",
+ "languages.win": "Valão",
+ "languages.cym": "Galês",
+ "languages.wol": "Uólofe",
+ "languages.fry": "Frísico Ocidental",
+ "languages.xho": "Xhosa",
+ "languages.yid": "Iídiche",
+ "languages.yor": "Yoruba",
+ "languages.zha": "Zhuang",
+ "languages.zul": "Zulu",
+ "DO_NOT_TRANSLATE": "end"
+}
diff --git a/sources/lang/Localization_ro.json b/sources/lang/Localization_ro.json
new file mode 100644
index 0000000..c0c1b7c
--- /dev/null
+++ b/sources/lang/Localization_ro.json
@@ -0,0 +1,293 @@
+{
+ "about.title": "Despre COPS",
+ "allbooks.alphabetical.many": "Index alfabetic: {0} cărţi",
+ "allbooks.alphabetical.none": "Index alfabetic: 0 cărţi",
+ "allbooks.alphabetical.one": "Index alfabetic: 1 carte",
+ "allbooks.title": "Toate cărţile",
+ "authors.alphabetical.many": "Index alfabetic: {0} autori",
+ "authors.alphabetical.none": "Index alfabetic: 0 autori",
+ "authors.alphabetical.one": "Index alfabetic: 1 autor",
+ "authors.title": "Autori",
+ "authorword.many": "{0} autori",
+ "authorword.none": "Nici un autor",
+ "authorword.one": "1 autor",
+ "bookentry.author": "{0} din {1}",
+ "bookword.many": "{0} cărţi",
+ "bookword.none": "Fără cărţi",
+ "bookword.one": "1 carte",
+ "bookword.title": "Cărţi",
+ "cog.alternate": "Căutare, srotare şi filtrare",
+ "content.series": "Serii:",
+ "content.series.data": "Cartea {0} din serie de {1}",
+ "content.summary": "Sumar",
+ "customcolumn.boolean.no": "Nu",
+ "customcolumn.boolean.unknown": "Nu setaţi",
+ "customcolumn.boolean.yes": "Da",
+ "customcolumn.date.format": "Y-m-d",
+ "customcolumn.date.unknown": "Nu setaţi",
+ "customcolumn.description": "Custom column '{0}'",
+ "customcolumn.description.bool": "Index of a boolean value",
+ "customcolumn.description.enum.many": "Alphabetical index of the {0} values",
+ "customcolumn.description.enum.none": "Alphabetical index of absolutely no values",
+ "customcolumn.description.enum.one": "Alphabetical index of one value",
+ "customcolumn.description.rating": "Index of ratings",
+ "customcolumn.description.series.many": "Index alfabetic: {0} serii",
+ "customcolumn.description.series.none": "Index alfabetic: 0 serii",
+ "customcolumn.description.series.one": "Index alfabetic: 1 serie",
+ "customcolumn.enum.unknown": "Nu setaţi",
+ "customcolumn.float.unknown": "Nu setaţi",
+ "customcolumn.int.unknown": "Nu setaţi",
+ "customcolumn.rating.unknown": "Nu setaţi",
+ "customcolumn.stars.many": "{0} Stars",
+ "customcolumn.stars.none": "No Stars",
+ "customcolumn.stars.one": "1 Stea",
+ "customize.email": "Setează-ţi adresa de mail (pentru a putea trimite cărţi prin email)",
+ "customize.fancybox": "Foloseşte Lightbox (cărţile se încarcă într-un floating frame)",
+ "customize.filter": "Activează filtrarea după tag-uri",
+ "customize.ignored": "Categorii ignorate",
+ "customize.paging": "Număr maxim de cărţi pe pagină (-1 pentru dezactivare)",
+ "customize.style": "Temă",
+ "customize.title": "Customizează interfaţa COPS",
+ "home.alternate": "Acasă",
+ "i18n.coversection": "Copertă",
+ "language.title": "Limbă",
+ "languages.alphabetical.many": "Index alfabetic: {0} limbi",
+ "languages.alphabetical.none": "Index alfabetic: 0 limbi",
+ "languages.alphabetical.one": "Index alfabetic: 1 limbă",
+ "languages.title": "Limbi",
+ "mail.messagenotsent": "Mesajul nu a putut fi trimis.",
+ "mail.messagesent": "Mesajul a fost rtimis",
+ "paging.next.alternate": "Următor",
+ "paging.previous.alternate": "Anterior",
+ "permalink.alternate": "Link permanent",
+ "pubdate.title": "Anul publicării",
+ "publisher.name": "Editură",
+ "publishers.alphabetical.many": "Index alfabetic: {0} edituri",
+ "publishers.alphabetical.none": "Index alfabetic: 0 edituri",
+ "publishers.alphabetical.one": "Index alfabetic: 1 editură",
+ "publishers.title": "Edituri",
+ "publisherword.many": "{0} edituri",
+ "publisherword.none": "Fără edituri",
+ "publisherword.one": "1 editură",
+ "ratings.many": "{0} voturi",
+ "ratings.none": "fără voturi",
+ "ratings.one": "1 vot",
+ "ratings.title": "Voturi",
+ "ratingword.many": "{0} stele",
+ "ratingword.none": "Fără stele",
+ "ratingword.one": "1 stea",
+ "recent.list": "{0} cărţi recente",
+ "recent.title": "Recent adăugate",
+ "search.alternate": "Căutare",
+ "search.result": "Rezultatul căutării *{0}*",
+ "search.result.author": "Rezultatul căutării *{0}* în autori",
+ "search.result.book": "Rezultatul căutării *{0}* în cărţi",
+ "search.result.publisher": "Rezultatul căutării *{0}* în edituri",
+ "search.result.series": "Rezultatul căutării *{0}* in serii",
+ "search.result.tag": "Rezultatul căutării *{0}* in tag-uri",
+ "search.sortorder.asc": "Asc",
+ "search.sortorder.desc": "Desc",
+ "series.alphabetical.many": "Index alfabetic: {0} serii",
+ "series.alphabetical.none": "Index alfabetic: 0 serii",
+ "series.alphabetical.one": "Index alfabetic: 1 serie",
+ "series.title": "Serii",
+ "seriesword.many": "{0} serii",
+ "seriesword.none": "Fără serii",
+ "seriesword.one": "1 serie",
+ "sort.alternate": "Sortare",
+ "splitByLetter.book.other": "Alte cărţi",
+ "splitByLetter.letter": "{0} care încep cu {1}",
+ "tags.alphabetical.many": "Index alfabetic: {0} tag-uri",
+ "tags.alphabetical.none": "Index alfabetic: 0 tag-uri",
+ "tags.alphabetical.one": "Index alfabetic: 1 tag",
+ "tags.title": "Tag-uri",
+ "tagword.many": "{0} tag-uri",
+ "tagword.none": "Fără tag-uri",
+ "tagword.one": "1 tag",
+ "tagword.title": "Tag-uri",
+ "languages.abk": "Ahază",
+ "languages.aaf": "Afară",
+ "languages.afr": "Sud-africană",
+ "languages.aka": "Akan",
+ "languages.sqi": "Albaneză",
+ "languages.amh": "Amharică",
+ "languages.ara": "Arabă",
+ "languages.arg": "Aragoneză",
+ "languages.hye": "Armeană",
+ "languages.asm": "Assameză",
+ "languages.ava": "Avaric",
+ "languages.ave": "Avestană",
+ "languages.aym": "Aymara",
+ "languages.aze": "Azeră",
+ "languages.bam": "Bambara",
+ "languages.bak": "Bashkir",
+ "languages.eus": "Bască",
+ "languages.bel": "Bielorusă",
+ "languages.ben": "Bengaleză",
+ "languages.bih": "Bihari",
+ "languages.bis": "Bislama",
+ "languages.bos": "Bosniacă",
+ "languages.bre": "Bretonă",
+ "languages.bul": "Bulgară",
+ "languages.mya": "Burmeză",
+ "languages.cat": "Catalană",
+ "languages.cha": "Chamorro",
+ "languages.che": "Cecenă",
+ "languages.nya": "Chichewa",
+ "languages.zho": "Chineză",
+ "languages.chv": "Chuvash",
+ "languages.cor": "Cornică",
+ "languages.cos": "Corsicană",
+ "languages.cre": "Cree",
+ "languages.hrv": "Croată",
+ "languages.ces": "Cehă",
+ "languages.dan": "Daneză",
+ "languages.div": "Divehi",
+ "languages.nld": "Olandeză",
+ "languages.dzo": "Dzongkha",
+ "languages.eng": "Engleză",
+ "languages.epo": "Esperanto",
+ "languages.est": "Estoniană",
+ "languages.ewe": "Ewe",
+ "languages.fao": "Faroese",
+ "languages.fij": "Fijian",
+ "languages.fin": "Finlandeză",
+ "languages.fra": "Franceză",
+ "languages.ful": "Fula",
+ "languages.glg": "Galiciană",
+ "languages.kat": "Georgiană",
+ "languages.deu": "Germană",
+ "languages.ell": "Greacă",
+ "languages.grn": "Guaraní",
+ "languages.guj": "Gujarati",
+ "languages.hat": "Haitiană",
+ "languages.hau": "Hausa",
+ "languages.hed": "Ebraică",
+ "languages.her": "Herero",
+ "languages.hin": "Hindusă",
+ "languages.hmo": "Hiri Motu",
+ "languages.hun": "Maghiară",
+ "languages.ina": "Interlingua",
+ "languages.ind": "Indoneziană",
+ "languages.ile": "Interlingue",
+ "languages.gle": "Irlandeză",
+ "languages.ibo": "Igbo",
+ "languages.ipk": "Inupiaq",
+ "languages.ido": "Ido",
+ "languages.isl": "Islandeză",
+ "languages.ita": "Italiană",
+ "languages.iku": "Inuktitut",
+ "languages.jpn": "Japoneză",
+ "languages.jav": "Javaneză",
+ "languages.kal": "Kalaallisut",
+ "languages.kan": "Kannada",
+ "languages.kau": "Kanuri",
+ "languages.kas": "Kashmiri",
+ "languages.kaz": "Kazakh",
+ "languages.khm": "Khmer",
+ "languages.kik": "Kikuyu",
+ "languages.kin": "Kinyarwanda",
+ "languages.kir": "Kyrgyz",
+ "languages.kom": "Komi",
+ "languages.kon": "Congoleză",
+ "languages.kor": "Coreană",
+ "languages.kur": "Kurdish",
+ "languages.kua": "Kwanyama",
+ "languages.lat": "Latină",
+ "languages.ltz": "Luxemburgheză",
+ "languages.lug": "Ganda",
+ "languages.lim": "Limburgheză",
+ "languages.lin": "Lingala",
+ "languages.lao": "Lao",
+ "languages.lit": "Lituaniană",
+ "languages.lub": "Luba-Katanga",
+ "languages.lav": "Letonă",
+ "languages.glv": "Manx",
+ "languages.mkd": "Macedoneană",
+ "languages.mlg": "Malagasy",
+ "languages.msa": "Malay",
+ "languages.mal": "Malayalam",
+ "languages.mlt": "Malteză",
+ "languages.mri": "Maori",
+ "languages.mar": "Marathi",
+ "languages.mah": "Marshallese",
+ "languages.mon": "Mongolă",
+ "languages.nau": "Nauru",
+ "languages.nav": "Navajo",
+ "languages.nob": "Norwegian Bokmal",
+ "languages.nde": "North Ndebele",
+ "languages.nep": "Nepaleză",
+ "languages.ndo": "Ndonga",
+ "languages.nno": "Norvegiană Nynorsk",
+ "languages.nor": "Norvegiană",
+ "languages.iii": "Nuosu",
+ "languages.nbl": "South Ndebele",
+ "languages.oci": "Occitană",
+ "languages.oji": "Ojibwe",
+ "languages.chu": "Old Church Slavonic",
+ "languages.orm": "Oromo",
+ "languages.ori": "Oriya",
+ "languages.oss": "Ossetian",
+ "languages.pan": "Panjabi",
+ "languages.pli": "Pali",
+ "languages.fas": "Persană",
+ "languages.pol": "Poloneză",
+ "languages.pus": "Pashto",
+ "languages.por": "Portugheză",
+ "languages.que": "Quechua",
+ "languages.roh": "Romani",
+ "languages.run": "Kirundi",
+ "languages.ron": "Română",
+ "languages.rus": "Rusă",
+ "languages.san": "Sanscrită",
+ "languages.srd": "Sardă",
+ "languages.snd": "Sindhi",
+ "languages.sme": "Northern Sami",
+ "languages.smo": "Samoan",
+ "languages.sag": "Sango",
+ "languages.srp": "Sârbă",
+ "languages.gla": "Scoţiană",
+ "languages.sna": "Shona",
+ "languages.sin": "Sinhala",
+ "languages.slk": "Slovacă",
+ "languages.slv": "Slovenă",
+ "languages.som": "Somali",
+ "languages.sot": "Southern Sotho",
+ "languages.spa": "Spaniolă",
+ "languages.sun": "Sundaneză",
+ "languages.swa": "Swahili",
+ "languages.ssw": "Swati",
+ "languages.swe": "Suedeză",
+ "languages.tam": "Tamilă",
+ "languages.tel": "Telugu",
+ "languages.tgk": "Tajik",
+ "languages.tha": "Thailandeză",
+ "languages.tir": "Tigrinya",
+ "languages.bod": "Tibetan Standard",
+ "languages.tuk": "Turkmenă",
+ "languages.tgl": "Tagalog",
+ "languages.tsn": "Tswana",
+ "languages.ton": "Tonga",
+ "languages.tur": "Turcă",
+ "languages.tso": "Tsonga",
+ "languages.tat": "Tatară",
+ "languages.twi": "Twi",
+ "languages.tah": "Tahitian",
+ "languages.uig": "Uighur",
+ "languages.ukr": "Ucrainiană",
+ "languages.urd": "Urdu",
+ "languages.uzb": "Uzbecă",
+ "languages.ven": "Venda",
+ "languages.vie": "Vietnameză",
+ "languages.vol": "Volapük",
+ "languages.win": "Valonă",
+ "languages.cym": "Galeză",
+ "languages.wol": "Wolof",
+ "languages.fry": "Western Frisian",
+ "languages.xho": "Xhosa",
+ "languages.yid": "Idiş",
+ "languages.yor": "Yoruba",
+ "languages.zha": "Zhuang",
+ "languages.zul": "Zulu",
+ "DO_NOT_TRANSLATE": "end"
+}
diff --git a/sources/lang/Localization_ru.json b/sources/lang/Localization_ru.json
new file mode 100644
index 0000000..d628a66
--- /dev/null
+++ b/sources/lang/Localization_ru.json
@@ -0,0 +1,293 @@
+{
+ "about.title": "О Программе COPS",
+ "allbooks.alphabetical.many": "Алфавитный указатель по {0} книгам",
+ "allbooks.alphabetical.none": "Алфавитный указатель. Книги без названия",
+ "allbooks.alphabetical.one": "Алфавитный указатель. Одна книга",
+ "allbooks.title": "Названия книг",
+ "authors.alphabetical.many": "Алфавитный указатель по {0} авторам",
+ "authors.alphabetical.none": "Алфавитный указатель. Книги без указания автора",
+ "authors.alphabetical.one": "Алфавитный указатель. Один автор",
+ "authors.title": "Авторы",
+ "authorword.many": "{0} авторов(а)",
+ "authorword.none": "Нет автора",
+ "authorword.one": "1 автор",
+ "bookentry.author": "{0} из {1}",
+ "bookword.many": "{0} книг(и)",
+ "bookword.none": "Нет книг",
+ "bookword.one": "1 книга",
+ "bookword.title": "Книги",
+ "cog.alternate": "Поиск, сортировка и фильтры",
+ "content.series": "Серии:",
+ "content.series.data": "Книга {0} в {1} серии",
+ "content.summary": "Резюме",
+ "customcolumn.boolean.no": "Нет",
+ "customcolumn.boolean.unknown": "Не задано",
+ "customcolumn.boolean.yes": "Да",
+ "customcolumn.date.format": "Y-m-d",
+ "customcolumn.date.unknown": "Не задано",
+ "customcolumn.description": "Custom column '{0}'",
+ "customcolumn.description.bool": "Index of a boolean value",
+ "customcolumn.description.enum.many": "Alphabetical index of the {0} values",
+ "customcolumn.description.enum.none": "Alphabetical index of absolutely no values",
+ "customcolumn.description.enum.one": "Alphabetical index of one value",
+ "customcolumn.description.rating": "Index of ratings",
+ "customcolumn.description.series.many": "Алфавитный указатель по {0} сериям",
+ "customcolumn.description.series.none": "Алфавитный указатель. Книги без указания серии",
+ "customcolumn.description.series.one": "Алфавитный указатель. Одна серия",
+ "customcolumn.enum.unknown": "Не задано",
+ "customcolumn.float.unknown": "Не задано",
+ "customcolumn.int.unknown": "Не задано",
+ "customcolumn.rating.unknown": "Не задано",
+ "customcolumn.stars.many": "{0} Stars",
+ "customcolumn.stars.none": "No Stars",
+ "customcolumn.stars.one": "1 Star",
+ "customize.email": "Укажите Ваш e-mail (возможность отправлять книги по почте)",
+ "customize.fancybox": "Эффектно всплывающее окно при увеличении обложки",
+ "customize.filter": "Включить фильтрацию по категориям",
+ "customize.ignored": "Исключить категории",
+ "customize.paging": "Максимальное число книг на странице (-1 - не ограничивать)",
+ "customize.style": "Тема",
+ "customize.title": "Изменить Интерфейс COPS",
+ "home.alternate": "Домой",
+ "i18n.coversection": "Обложка",
+ "language.title": "Язык",
+ "languages.alphabetical.many": "Алфавитный указатель по {0} языкам",
+ "languages.alphabetical.none": "Алфавитный указатель. Книги без указания языка",
+ "languages.alphabetical.one": "Алфавитный указатель. Один язык",
+ "languages.title": "Язык",
+ "mail.messagenotsent": "Сообщение не может быть отправлено",
+ "mail.messagesent": "Сообщение было отправлено",
+ "paging.next.alternate": "След.",
+ "paging.previous.alternate": "Пред.",
+ "permalink.alternate": "Постоянная Ссылка",
+ "pubdate.title": "Дата публикации",
+ "publisher.name": "Издательство",
+ "publishers.alphabetical.many": "Алфавитный указатель по {0} издательствам",
+ "publishers.alphabetical.none": "Алфавитный указатель. Книги без указания издательства",
+ "publishers.alphabetical.one": "Алфавитный указатель. Одно издательство",
+ "publishers.title": "Издательства",
+ "publisherword.many": "{0} издательств",
+ "publisherword.none": "Нет издательства",
+ "publisherword.one": "1 издательство",
+ "ratings.many": "{0} рейтинга",
+ "ratings.none": "Рейтинг не указан",
+ "ratings.one": "1 рейтинг",
+ "ratings.title": "Рейтинг",
+ "ratingword.many": "{0} звезд (ы)",
+ "ratingword.none": "Звезды не указаны",
+ "ratingword.one": "1 звезда",
+ "recent.list": "{0} недавно поступивших(ие) книг(и)",
+ "recent.title": "Недавние поступления",
+ "search.alternate": "Поиск",
+ "search.result": "Результаты поиска для *{0}*",
+ "search.result.author": "Результаты поиска для *{0}* авторов",
+ "search.result.book": "Результаты поиска для *{0}* книг",
+ "search.result.publisher": "Результаты поиска для *{0}* издательств",
+ "search.result.series": "Результаты поиска для *{0}* серий",
+ "search.result.tag": "Результаты поиска для *{0}* меток",
+ "search.sortorder.asc": "Возр.",
+ "search.sortorder.desc": "Убыв.",
+ "series.alphabetical.many": "Алфавитный указатель по {0} сериям",
+ "series.alphabetical.none": "Алфавитный указатель. Книги без указания серии",
+ "series.alphabetical.one": "Алфавитный указатель. Одна серия",
+ "series.title": "Серии",
+ "seriesword.many": "{0} серий(и)",
+ "seriesword.none": "Нет серий",
+ "seriesword.one": "1 серия",
+ "sort.alternate": "Сортировка",
+ "splitByLetter.book.other": "Другие книги",
+ "splitByLetter.letter": "{0} начать с {1}",
+ "tags.alphabetical.many": "Алфавитный указатель по {0} жанрам",
+ "tags.alphabetical.none": "Алфавитный указатель. Книги без указания жанра",
+ "tags.alphabetical.one": "Алфавитный указатель. Один жанр",
+ "tags.title": "Жанры",
+ "tagword.many": "{0} жанра(ов)",
+ "tagword.none": "Жанр не указан",
+ "tagword.one": "1 жанр",
+ "tagword.title": "Жанры",
+ "languages.abk": "Абхазский",
+ "languages.aaf": "Афарский",
+ "languages.afr": "Бурский",
+ "languages.aka": "Аканский",
+ "languages.sqi": "Албанский",
+ "languages.amh": "Амхарский",
+ "languages.ara": "Арабский",
+ "languages.arg": "Арагонский",
+ "languages.hye": "Армянский",
+ "languages.asm": "Ассамский",
+ "languages.ava": "Аварский",
+ "languages.ave": "Авестийский",
+ "languages.aym": "Аймара",
+ "languages.aze": "Азербайджанский",
+ "languages.bam": "Бамана",
+ "languages.bak": "Башкирский",
+ "languages.eus": "Баскский",
+ "languages.bel": "Белорусский",
+ "languages.ben": "Бенгальский",
+ "languages.bih": "Бихарский",
+ "languages.bis": "Бислама",
+ "languages.bos": "Боснийский",
+ "languages.bre": "Бретонский",
+ "languages.bul": "Болгарский",
+ "languages.mya": "Бирманский",
+ "languages.cat": "Каталонский",
+ "languages.cha": "Чаморро",
+ "languages.che": "Чеченский",
+ "languages.nya": "Ньянджа",
+ "languages.zho": "Китайский",
+ "languages.chv": "Чувашский",
+ "languages.cor": "Корнский",
+ "languages.cos": "Корсиканский",
+ "languages.cre": "Кри",
+ "languages.hrv": "Хорватский",
+ "languages.ces": "Чешский",
+ "languages.dan": "Датский",
+ "languages.div": "Мальдивский",
+ "languages.nld": "Нидерландский",
+ "languages.dzo": "Дзонг-кэ",
+ "languages.eng": "Английский",
+ "languages.epo": "Эсперанто",
+ "languages.est": "Эстонский",
+ "languages.ewe": "Эве",
+ "languages.fao": "Фарерский",
+ "languages.fij": "Фиджийский",
+ "languages.fin": "Финский",
+ "languages.fra": "Французский",
+ "languages.ful": "Фула",
+ "languages.glg": "Галисийский",
+ "languages.kat": "Грузинский",
+ "languages.deu": "Немецкий",
+ "languages.ell": "Греческий",
+ "languages.grn": "Гуарани",
+ "languages.guj": "Гуджарати",
+ "languages.hat": "Гаитянский креольский",
+ "languages.hau": "Хауса",
+ "languages.hed": "Иврит",
+ "languages.her": "Гереро",
+ "languages.hin": "Хинди",
+ "languages.hmo": "Хири-моту",
+ "languages.hun": "Венгерский",
+ "languages.ina": "Интерлингва",
+ "languages.ind": "Индонезийский",
+ "languages.ile": "Окциденталь",
+ "languages.gle": "Ирландский",
+ "languages.ibo": "Игбо",
+ "languages.ipk": "Инюпик",
+ "languages.ido": "Идо",
+ "languages.isl": "Исландский",
+ "languages.ita": "Итальянский",
+ "languages.iku": "Инуктитут",
+ "languages.jpn": "Японский",
+ "languages.jav": "Яванский",
+ "languages.kal": "Гренландский",
+ "languages.kan": "Ка́ннада (дравидийский)",
+ "languages.kau": "Канури",
+ "languages.kas": "Кашмирский",
+ "languages.kaz": "Казахский",
+ "languages.khm": "Кхмерский",
+ "languages.kik": "Кикуйю",
+ "languages.kin": "Руанда",
+ "languages.kir": "Киргизский",
+ "languages.kom": "Коми",
+ "languages.kon": "Конго",
+ "languages.kor": "Корейский",
+ "languages.kur": "Курдский",
+ "languages.kua": "Кваньяма",
+ "languages.lat": "Латинский",
+ "languages.ltz": "Люксембургский",
+ "languages.lug": "Луганда",
+ "languages.lim": "Лимбургский",
+ "languages.lin": "Лингала",
+ "languages.lao": "Лаосский",
+ "languages.lit": "Литовский",
+ "languages.lub": "Луба-катанга",
+ "languages.lav": "Латышский",
+ "languages.glv": "Мэнский",
+ "languages.mkd": "Македонский",
+ "languages.mlg": "Малагасийский",
+ "languages.msa": "Малайский",
+ "languages.mal": "Малаялам",
+ "languages.mlt": "Мальтийский",
+ "languages.mri": "Маори",
+ "languages.mar": "Маратхи",
+ "languages.mah": "Маршалльский",
+ "languages.mon": "Монгольский",
+ "languages.nau": "Науру",
+ "languages.nav": "Навахо",
+ "languages.nob": "Букмол (норвежский)",
+ "languages.nde": "Северный ндебеле",
+ "languages.nep": "Непальский",
+ "languages.ndo": "Ндонга",
+ "languages.nno": "Нюнорск (норвежский)",
+ "languages.nor": "Норвежский",
+ "languages.iii": "Носу (сычуаньский)",
+ "languages.nbl": "Южный ндебеле",
+ "languages.oci": "Окситанский",
+ "languages.oji": "Оджибве",
+ "languages.chu": "Старославянский",
+ "languages.orm": "Оромо",
+ "languages.ori": "Ория",
+ "languages.oss": "Осетинский",
+ "languages.pan": "Панджаби",
+ "languages.pli": "Пали",
+ "languages.fas": "Персидский",
+ "languages.pol": "Польский",
+ "languages.pus": "Пушту",
+ "languages.por": "Португальский",
+ "languages.que": "Кечуанский",
+ "languages.roh": "Ретороманский диалект",
+ "languages.run": "Рунди",
+ "languages.ron": "Румынский",
+ "languages.rus": "Русский",
+ "languages.san": "Санскрит",
+ "languages.srd": "Сардинский",
+ "languages.snd": "Синди",
+ "languages.sme": "Северносаамский",
+ "languages.smo": "Самоанский",
+ "languages.sag": "Санго",
+ "languages.srp": "Сербский",
+ "languages.gla": "Гэльский",
+ "languages.sna": "Шона",
+ "languages.sin": "Сингальский",
+ "languages.slk": "Словацкий",
+ "languages.slv": "Словенский",
+ "languages.som": "Сомали",
+ "languages.sot": "Сесото",
+ "languages.spa": "Испанский",
+ "languages.sun": "Суданский",
+ "languages.swa": "Суахили",
+ "languages.ssw": "Свати",
+ "languages.swe": "Шведский",
+ "languages.tam": "Тамильский",
+ "languages.tel": "Телугу",
+ "languages.tgk": "Таджикский",
+ "languages.tha": "Тайский",
+ "languages.tir": "Тигринья",
+ "languages.bod": "Тибетский Стандартный",
+ "languages.tuk": "Туркменский",
+ "languages.tgl": "Тагальский",
+ "languages.tsn": "Тсвана",
+ "languages.ton": "Тонга",
+ "languages.tur": "Турецкий",
+ "languages.tso": "Тсонга",
+ "languages.tat": "Татарский",
+ "languages.twi": "Тви",
+ "languages.tah": "Таитянский",
+ "languages.uig": "Уйгурский",
+ "languages.ukr": "Украинский",
+ "languages.urd": "Урду",
+ "languages.uzb": "Узбекский",
+ "languages.ven": "Венда",
+ "languages.vie": "Вьетнамский",
+ "languages.vol": "Волапюк",
+ "languages.win": "Валлонский",
+ "languages.cym": "Валлийский",
+ "languages.wol": "Волоф",
+ "languages.fry": "Фризский",
+ "languages.xho": "Коса",
+ "languages.yid": "Идиш",
+ "languages.yor": "Йоруба",
+ "languages.zha": "Чжуанский",
+ "languages.zul": "Зулусский",
+ "DO_NOT_TRANSLATE": "end"
+}
diff --git a/sources/lang/Localization_sl.json b/sources/lang/Localization_sl.json
new file mode 100644
index 0000000..01e6429
--- /dev/null
+++ b/sources/lang/Localization_sl.json
@@ -0,0 +1,293 @@
+{
+ "about.title": "About COPS",
+ "allbooks.alphabetical.many": "Alphabetical index of the {0} books",
+ "allbooks.alphabetical.none": "Alphabetical index of absolutely no books",
+ "allbooks.alphabetical.one": "Alphabetical index of the single book",
+ "allbooks.title": "All books",
+ "authors.alphabetical.many": "Alphabetical index of the {0} authors",
+ "authors.alphabetical.none": "Alphabetical index of absolutely no authors",
+ "authors.alphabetical.one": "Alphabetical index of the single author",
+ "authors.title": "Authors",
+ "authorword.many": "{0} authors",
+ "authorword.none": "No authors",
+ "authorword.one": "1 author",
+ "bookentry.author": "{0} by {1}",
+ "bookword.many": "{0} books",
+ "bookword.none": "No books",
+ "bookword.one": "1 book",
+ "bookword.title": "Books",
+ "cog.alternate": "Search, sort and filters",
+ "content.series": "Series:",
+ "content.series.data": "Book {0} in the {1} series",
+ "content.summary": "Povzetek",
+ "customcolumn.boolean.no": "Ne",
+ "customcolumn.boolean.unknown": "Ni določeno",
+ "customcolumn.boolean.yes": "Da",
+ "customcolumn.date.format": "Y-m-d",
+ "customcolumn.date.unknown": "Ni določeno",
+ "customcolumn.description": "Custom column '{0}'",
+ "customcolumn.description.bool": "Index of a boolean value",
+ "customcolumn.description.enum.many": "Alphabetical index of the {0} values",
+ "customcolumn.description.enum.none": "Alphabetical index of absolutely no values",
+ "customcolumn.description.enum.one": "Alphabetical index of one value",
+ "customcolumn.description.rating": "Index of ratings",
+ "customcolumn.description.series.many": "Alphabetical index of the {0} series",
+ "customcolumn.description.series.none": "Alphabetical index of absolutely no series",
+ "customcolumn.description.series.one": "Alphabetical index of the single series",
+ "customcolumn.enum.unknown": "Ni določeno",
+ "customcolumn.float.unknown": "Ni določeno",
+ "customcolumn.int.unknown": "Ni določeno",
+ "customcolumn.rating.unknown": "Ni določeno",
+ "customcolumn.stars.many": "{0} Stars",
+ "customcolumn.stars.none": "No Stars",
+ "customcolumn.stars.one": "1 Star",
+ "customize.email": "Set your email (to allow book emailing)",
+ "customize.fancybox": "Use Lightbox (books load in floating frame)",
+ "customize.filter": "Enable tag filtering",
+ "customize.ignored": "Ignored categories",
+ "customize.paging": "Max number of books per page (-1 to disable)",
+ "customize.style": "Theme",
+ "customize.title": "Customize COPS UI",
+ "home.alternate": "Domov",
+ "i18n.coversection": "Cover",
+ "language.title": "Jezik",
+ "languages.alphabetical.many": "Alphabetical index of the {0} languages",
+ "languages.alphabetical.none": "Alphabetical index of absolutely no languages",
+ "languages.alphabetical.one": "Alphabetical index of the single language",
+ "languages.title": "Languages",
+ "mail.messagenotsent": "Message could not be sent.",
+ "mail.messagesent": "Message has been sent",
+ "paging.next.alternate": "Naprej",
+ "paging.previous.alternate": "Prejšnje",
+ "permalink.alternate": "Permalink",
+ "pubdate.title": "Publication year",
+ "publisher.name": "Publisher",
+ "publishers.alphabetical.many": "Alphabetical index of the {0} publishers",
+ "publishers.alphabetical.none": "Alphabetical index of absolutely no publishers",
+ "publishers.alphabetical.one": "Alphabetical index of the single publisher",
+ "publishers.title": "Publishers",
+ "publisherword.many": "{0} publishers",
+ "publisherword.none": "No publishers",
+ "publisherword.one": "1 publisher",
+ "ratings.many": "{0} ratings",
+ "ratings.none": "no ratings",
+ "ratings.one": "1 rating",
+ "ratings.title": "Ratings",
+ "ratingword.many": "{0} stars",
+ "ratingword.none": "No star",
+ "ratingword.one": "1 star",
+ "recent.list": "{0} most recent books",
+ "recent.title": "Recent additions",
+ "search.alternate": "Iskanje",
+ "search.result": "Search result for *{0}*",
+ "search.result.author": "Search result for *{0}* in authors",
+ "search.result.book": "Search result for *{0}* in books",
+ "search.result.publisher": "Search result for *{0}* in publishers",
+ "search.result.series": "Search result for *{0}* in series",
+ "search.result.tag": "Search result for *{0}* in tags",
+ "search.sortorder.asc": "Asc",
+ "search.sortorder.desc": "Desc",
+ "series.alphabetical.many": "Alphabetical index of the {0} series",
+ "series.alphabetical.none": "Alphabetical index of absolutely no series",
+ "series.alphabetical.one": "Alphabetical index of the single series",
+ "series.title": "Series",
+ "seriesword.many": "{0} series",
+ "seriesword.none": "No series",
+ "seriesword.one": "1 series",
+ "sort.alternate": "Razvrsti",
+ "splitByLetter.book.other": "Other books",
+ "splitByLetter.letter": "{0} starting with {1}",
+ "tags.alphabetical.many": "Alphabetical index of the {0} tags",
+ "tags.alphabetical.none": "Alphabetical index of absolutely no tags",
+ "tags.alphabetical.one": "Alphabetical index of the single tag",
+ "tags.title": "Tags",
+ "tagword.many": "{0} tags",
+ "tagword.none": "No tags",
+ "tagword.one": "1 tag",
+ "tagword.title": "Tags",
+ "languages.abk": "Abkhaz",
+ "languages.aaf": "Afar",
+ "languages.afr": "Afrikaans",
+ "languages.aka": "Akan",
+ "languages.sqi": "Albanian",
+ "languages.amh": "Amharic",
+ "languages.ara": "Arabic",
+ "languages.arg": "Aragonese",
+ "languages.hye": "Armenian",
+ "languages.asm": "Assamese",
+ "languages.ava": "Avaric",
+ "languages.ave": "Avestan",
+ "languages.aym": "Aymara",
+ "languages.aze": "Azerbaijani",
+ "languages.bam": "Bambara",
+ "languages.bak": "Bashkir",
+ "languages.eus": "Basque",
+ "languages.bel": "Belarusian",
+ "languages.ben": "Bengali",
+ "languages.bih": "Bihari",
+ "languages.bis": "Bislama",
+ "languages.bos": "Bosnian",
+ "languages.bre": "Breton",
+ "languages.bul": "Bulgarian",
+ "languages.mya": "Burmese",
+ "languages.cat": "Catalan",
+ "languages.cha": "Chamorro",
+ "languages.che": "Chechen",
+ "languages.nya": "Chichewa",
+ "languages.zho": "Chinese",
+ "languages.chv": "Chuvash",
+ "languages.cor": "Cornish",
+ "languages.cos": "Corsican",
+ "languages.cre": "Cree",
+ "languages.hrv": "Croatian",
+ "languages.ces": "Czech",
+ "languages.dan": "Danish",
+ "languages.div": "Divehi",
+ "languages.nld": "Dutch",
+ "languages.dzo": "Dzongkha",
+ "languages.eng": "English",
+ "languages.epo": "Esperanto",
+ "languages.est": "Estonian",
+ "languages.ewe": "Ewe",
+ "languages.fao": "Faroese",
+ "languages.fij": "Fijian",
+ "languages.fin": "Finnish",
+ "languages.fra": "French",
+ "languages.ful": "Fula",
+ "languages.glg": "Galician",
+ "languages.kat": "Georgian",
+ "languages.deu": "German",
+ "languages.ell": "Greek",
+ "languages.grn": "Guaraní",
+ "languages.guj": "Gujarati",
+ "languages.hat": "Haitian",
+ "languages.hau": "Hausa",
+ "languages.hed": "Hebrew",
+ "languages.her": "Herero",
+ "languages.hin": "Hindi",
+ "languages.hmo": "Hiri Motu",
+ "languages.hun": "Hungarian",
+ "languages.ina": "Interlingua",
+ "languages.ind": "Indonesian",
+ "languages.ile": "Interlingue",
+ "languages.gle": "Irish",
+ "languages.ibo": "Igbo",
+ "languages.ipk": "Inupiaq",
+ "languages.ido": "Ido",
+ "languages.isl": "Icelandic",
+ "languages.ita": "Italian",
+ "languages.iku": "Inuktitut",
+ "languages.jpn": "Japanese",
+ "languages.jav": "Javanese",
+ "languages.kal": "Kalaallisut",
+ "languages.kan": "Kannada",
+ "languages.kau": "Kanuri",
+ "languages.kas": "Kashmiri",
+ "languages.kaz": "Kazakh",
+ "languages.khm": "Khmer",
+ "languages.kik": "Kikuyu",
+ "languages.kin": "Kinyarwanda",
+ "languages.kir": "Kyrgyz",
+ "languages.kom": "Komi",
+ "languages.kon": "Kongo",
+ "languages.kor": "Korean",
+ "languages.kur": "Kurdish",
+ "languages.kua": "Kwanyama",
+ "languages.lat": "Latin",
+ "languages.ltz": "Luxembourgish",
+ "languages.lug": "Ganda",
+ "languages.lim": "Limburgish",
+ "languages.lin": "Lingala",
+ "languages.lao": "Lao",
+ "languages.lit": "Lithuanian",
+ "languages.lub": "Luba-Katanga",
+ "languages.lav": "Latvian",
+ "languages.glv": "Manx",
+ "languages.mkd": "Macedonian",
+ "languages.mlg": "Malagasy",
+ "languages.msa": "Malay",
+ "languages.mal": "Malayalam",
+ "languages.mlt": "Maltese",
+ "languages.mri": "Māori",
+ "languages.mar": "Marathi",
+ "languages.mah": "Marshallese",
+ "languages.mon": "Mongolian",
+ "languages.nau": "Nauru",
+ "languages.nav": "Navajo",
+ "languages.nob": "Norwegian Bokmål",
+ "languages.nde": "North Ndebele",
+ "languages.nep": "Nepali",
+ "languages.ndo": "Ndonga",
+ "languages.nno": "Norwegian Nynorsk",
+ "languages.nor": "Norwegian",
+ "languages.iii": "Nuosu",
+ "languages.nbl": "South Ndebele",
+ "languages.oci": "Occitan",
+ "languages.oji": "Ojibwe",
+ "languages.chu": "Old Church Slavonic",
+ "languages.orm": "Oromo",
+ "languages.ori": "Oriya",
+ "languages.oss": "Ossetian",
+ "languages.pan": "Panjabi",
+ "languages.pli": "Pāli",
+ "languages.fas": "Persian",
+ "languages.pol": "Polish",
+ "languages.pus": "Pashto",
+ "languages.por": "Portuguese",
+ "languages.que": "Quechua",
+ "languages.roh": "Romansh",
+ "languages.run": "Kirundi",
+ "languages.ron": "Romanian",
+ "languages.rus": "Russian",
+ "languages.san": "Sanskrit",
+ "languages.srd": "Sardinian",
+ "languages.snd": "Sindhi",
+ "languages.sme": "Northern Sami",
+ "languages.smo": "Samoan",
+ "languages.sag": "Sango",
+ "languages.srp": "Serbian",
+ "languages.gla": "Scottish Gaelic",
+ "languages.sna": "Shona",
+ "languages.sin": "Sinhala",
+ "languages.slk": "Slovak",
+ "languages.slv": "Slovene",
+ "languages.som": "Somali",
+ "languages.sot": "Southern Sotho",
+ "languages.spa": "Spanish",
+ "languages.sun": "Sundanese",
+ "languages.swa": "Swahili",
+ "languages.ssw": "Swati",
+ "languages.swe": "Swedish",
+ "languages.tam": "Tamil",
+ "languages.tel": "Telugu",
+ "languages.tgk": "Tajik",
+ "languages.tha": "Thai",
+ "languages.tir": "Tigrinya",
+ "languages.bod": "Tibetan Standard",
+ "languages.tuk": "Turkmen",
+ "languages.tgl": "Tagalog",
+ "languages.tsn": "Tswana",
+ "languages.ton": "Tonga",
+ "languages.tur": "Turkish",
+ "languages.tso": "Tsonga",
+ "languages.tat": "Tatar",
+ "languages.twi": "Twi",
+ "languages.tah": "Tahitian",
+ "languages.uig": "Uighur",
+ "languages.ukr": "Ukrainian",
+ "languages.urd": "Urdu",
+ "languages.uzb": "Uzbek",
+ "languages.ven": "Venda",
+ "languages.vie": "Vietnamese",
+ "languages.vol": "Volapük",
+ "languages.win": "Walloon",
+ "languages.cym": "Welsh",
+ "languages.wol": "Wolof",
+ "languages.fry": "Western Frisian",
+ "languages.xho": "Xhosa",
+ "languages.yid": "Yiddish",
+ "languages.yor": "Yoruba",
+ "languages.zha": "Zhuang",
+ "languages.zul": "Zulu",
+ "DO_NOT_TRANSLATE": "end"
+}
diff --git a/sources/lang/Localization_sr.json b/sources/lang/Localization_sr.json
new file mode 100644
index 0000000..52f1428
--- /dev/null
+++ b/sources/lang/Localization_sr.json
@@ -0,0 +1,293 @@
+{
+ "about.title": "About COPS",
+ "allbooks.alphabetical.many": "Азбучни индекс {0} књига",
+ "allbooks.alphabetical.none": "Азбучни индекс без књига",
+ "allbooks.alphabetical.one": "Азбучни индекс једне књиге",
+ "allbooks.title": "Све књиге",
+ "authors.alphabetical.many": "Азбучни индекс {0} аутора",
+ "authors.alphabetical.none": "Азбучни индекс без аутора",
+ "authors.alphabetical.one": "Азбучни индекс једног аутора",
+ "authors.title": "Аутори",
+ "authorword.many": "{0} аутора",
+ "authorword.none": "Нема аутора",
+ "authorword.one": "1 аутор",
+ "bookentry.author": "{0} до {1}",
+ "bookword.many": "{0} књига",
+ "bookword.none": "Нема књига",
+ "bookword.one": "1 књига",
+ "bookword.title": "Књиге",
+ "cog.alternate": "Претрага, филтери и сортирање",
+ "content.series": "Комплет:",
+ "content.series.data": "Књига {0} у {1} комплету",
+ "content.summary": "Укупно",
+ "customcolumn.boolean.no": "No",
+ "customcolumn.boolean.unknown": "Not Set",
+ "customcolumn.boolean.yes": "Yes",
+ "customcolumn.date.format": "Y-m-d",
+ "customcolumn.date.unknown": "Not Set",
+ "customcolumn.description": "Custom column '{0}'",
+ "customcolumn.description.bool": "Index of a boolean value",
+ "customcolumn.description.enum.many": "Alphabetical index of the {0} values",
+ "customcolumn.description.enum.none": "Alphabetical index of absolutely no values",
+ "customcolumn.description.enum.one": "Alphabetical index of one value",
+ "customcolumn.description.rating": "Index of ratings",
+ "customcolumn.description.series.many": "Alphabetical index of the {0} series",
+ "customcolumn.description.series.none": "Alphabetical index of absolutely no series",
+ "customcolumn.description.series.one": "Alphabetical index of the single series",
+ "customcolumn.enum.unknown": "Not Set",
+ "customcolumn.float.unknown": "Not Set",
+ "customcolumn.int.unknown": "Not Set",
+ "customcolumn.rating.unknown": "Not Set",
+ "customcolumn.stars.many": "{0} Stars",
+ "customcolumn.stars.none": "No Stars",
+ "customcolumn.stars.one": "1 Star",
+ "customize.email": "Поставите вашу адресу (да се омогући слање књиге)",
+ "customize.fancybox": "Користи Lightbox",
+ "customize.filter": "Омогућити филтрирање по таговима",
+ "customize.ignored": "Категорије које се приказују",
+ "customize.paging": "Максималан број књига по страни (-1 онемогућити)",
+ "customize.style": "Тема",
+ "customize.title": "Подешавање COPS корисничког прегледа",
+ "home.alternate": "Почетна",
+ "i18n.coversection": "Насловна",
+ "language.title": "Језик",
+ "languages.alphabetical.many": "Азбучни индекс {0} језика",
+ "languages.alphabetical.none": "Индекс без језика",
+ "languages.alphabetical.one": "Азбучни индекс једног језика",
+ "languages.title": "Језици",
+ "mail.messagenotsent": "Поруку није могуће послати.",
+ "mail.messagesent": "Порука је послата",
+ "paging.next.alternate": "Следеће",
+ "paging.previous.alternate": "Претходно",
+ "permalink.alternate": "Везе",
+ "pubdate.title": "Година издања",
+ "publisher.name": "Издавач",
+ "publishers.alphabetical.many": "Азбучни индекс {0} издавача",
+ "publishers.alphabetical.none": "Индекс без издавача",
+ "publishers.alphabetical.one": "Индекс једног издавача",
+ "publishers.title": "Издавачи",
+ "publisherword.many": "{0} издавача",
+ "publisherword.none": "Нема издавача",
+ "publisherword.one": "1 издавач",
+ "ratings.many": "{0} ratings",
+ "ratings.none": "no ratings",
+ "ratings.one": "1 rating",
+ "ratings.title": "Ratings",
+ "ratingword.many": "{0} stars",
+ "ratingword.none": "No star",
+ "ratingword.one": "1 star",
+ "recent.list": "{0} најновије књиге",
+ "recent.title": "Последње постављено",
+ "search.alternate": "Претрага",
+ "search.result": "Резултати претраге за *{0}*",
+ "search.result.author": "Резултат претраге за *{0}* у ауторима",
+ "search.result.book": "Резултат претраге за *{0}* у књигама",
+ "search.result.publisher": "Резултат претраге за *{0}* у издавачима",
+ "search.result.series": "Резултат претраге за *{0}* у комплетима",
+ "search.result.tag": "Резултат претраге за *{0}* у ознакама",
+ "search.sortorder.asc": "Растуће",
+ "search.sortorder.desc": "Опадајуће",
+ "series.alphabetical.many": "Азбучни индекс {0} комплета",
+ "series.alphabetical.none": "Азбучни индекс без комплета",
+ "series.alphabetical.one": "Азбучни индекс у једном комплету",
+ "series.title": "Комплети",
+ "seriesword.many": "{0} комплета",
+ "seriesword.none": "Нема комплета",
+ "seriesword.one": "1 комплет",
+ "sort.alternate": "Сертирање",
+ "splitByLetter.book.other": "Друге књиге",
+ "splitByLetter.letter": "{0} почиње {1}",
+ "tags.alphabetical.many": "Азбучни индекс у {0} ознакама",
+ "tags.alphabetical.none": "Азбучни индекс без ознака",
+ "tags.alphabetical.one": "Азбучни индекс једне ознаке",
+ "tags.title": "Ознаке",
+ "tagword.many": "{0} ознака",
+ "tagword.none": "Нема ознака",
+ "tagword.one": "1 ознака",
+ "tagword.title": "Ознаке",
+ "languages.abk": "Abkhaz",
+ "languages.aaf": "Afar",
+ "languages.afr": "Afrikaans",
+ "languages.aka": "Akan",
+ "languages.sqi": "Albanian",
+ "languages.amh": "Amharic",
+ "languages.ara": "Arabic",
+ "languages.arg": "Aragonese",
+ "languages.hye": "Armenian",
+ "languages.asm": "Assamese",
+ "languages.ava": "Avaric",
+ "languages.ave": "Avestan",
+ "languages.aym": "Aymara",
+ "languages.aze": "Azerbaijani",
+ "languages.bam": "Bambara",
+ "languages.bak": "Bashkir",
+ "languages.eus": "Basque",
+ "languages.bel": "Belarusian",
+ "languages.ben": "Bengali",
+ "languages.bih": "Bihari",
+ "languages.bis": "Bislama",
+ "languages.bos": "Bosnian",
+ "languages.bre": "Breton",
+ "languages.bul": "Bulgarian",
+ "languages.mya": "Burmese",
+ "languages.cat": "Catalan",
+ "languages.cha": "Chamorro",
+ "languages.che": "Chechen",
+ "languages.nya": "Chichewa",
+ "languages.zho": "Chinese",
+ "languages.chv": "Chuvash",
+ "languages.cor": "Cornish",
+ "languages.cos": "Corsican",
+ "languages.cre": "Cree",
+ "languages.hrv": "Croatian",
+ "languages.ces": "Czech",
+ "languages.dan": "Danish",
+ "languages.div": "Divehi",
+ "languages.nld": "Dutch",
+ "languages.dzo": "Dzongkha",
+ "languages.eng": "English",
+ "languages.epo": "Esperanto",
+ "languages.est": "Estonian",
+ "languages.ewe": "Ewe",
+ "languages.fao": "Faroese",
+ "languages.fij": "Fijian",
+ "languages.fin": "Finnish",
+ "languages.fra": "French",
+ "languages.ful": "Fula",
+ "languages.glg": "Galician",
+ "languages.kat": "Georgian",
+ "languages.deu": "German",
+ "languages.ell": "Greek",
+ "languages.grn": "Guaraní",
+ "languages.guj": "Gujarati",
+ "languages.hat": "Haitian",
+ "languages.hau": "Hausa",
+ "languages.hed": "Hebrew",
+ "languages.her": "Herero",
+ "languages.hin": "Hindi",
+ "languages.hmo": "Hiri Motu",
+ "languages.hun": "Hungarian",
+ "languages.ina": "Interlingua",
+ "languages.ind": "Indonesian",
+ "languages.ile": "Interlingue",
+ "languages.gle": "Irish",
+ "languages.ibo": "Igbo",
+ "languages.ipk": "Inupiaq",
+ "languages.ido": "Ido",
+ "languages.isl": "Icelandic",
+ "languages.ita": "Italian",
+ "languages.iku": "Inuktitut",
+ "languages.jpn": "Japanese",
+ "languages.jav": "Javanese",
+ "languages.kal": "Kalaallisut",
+ "languages.kan": "Kannada",
+ "languages.kau": "Kanuri",
+ "languages.kas": "Kashmiri",
+ "languages.kaz": "Kazakh",
+ "languages.khm": "Khmer",
+ "languages.kik": "Kikuyu",
+ "languages.kin": "Kinyarwanda",
+ "languages.kir": "Kyrgyz",
+ "languages.kom": "Komi",
+ "languages.kon": "Kongo",
+ "languages.kor": "Korean",
+ "languages.kur": "Kurdish",
+ "languages.kua": "Kwanyama",
+ "languages.lat": "Latin",
+ "languages.ltz": "Luxembourgish",
+ "languages.lug": "Ganda",
+ "languages.lim": "Limburgish",
+ "languages.lin": "Lingala",
+ "languages.lao": "Lao",
+ "languages.lit": "Lithuanian",
+ "languages.lub": "Luba-Katanga",
+ "languages.lav": "Latvian",
+ "languages.glv": "Manx",
+ "languages.mkd": "Macedonian",
+ "languages.mlg": "Malagasy",
+ "languages.msa": "Malay",
+ "languages.mal": "Malayalam",
+ "languages.mlt": "Maltese",
+ "languages.mri": "Māori",
+ "languages.mar": "Marathi",
+ "languages.mah": "Marshallese",
+ "languages.mon": "Mongolian",
+ "languages.nau": "Nauru",
+ "languages.nav": "Navajo",
+ "languages.nob": "Norwegian Bokmål",
+ "languages.nde": "North Ndebele",
+ "languages.nep": "Nepali",
+ "languages.ndo": "Ndonga",
+ "languages.nno": "Norwegian Nynorsk",
+ "languages.nor": "Norwegian",
+ "languages.iii": "Nuosu",
+ "languages.nbl": "South Ndebele",
+ "languages.oci": "Occitan",
+ "languages.oji": "Ojibwe",
+ "languages.chu": "Old Church Slavonic",
+ "languages.orm": "Oromo",
+ "languages.ori": "Oriya",
+ "languages.oss": "Ossetian",
+ "languages.pan": "Panjabi",
+ "languages.pli": "Pāli",
+ "languages.fas": "Persian",
+ "languages.pol": "Polish",
+ "languages.pus": "Pashto",
+ "languages.por": "Portuguese",
+ "languages.que": "Quechua",
+ "languages.roh": "Romansh",
+ "languages.run": "Kirundi",
+ "languages.ron": "Romanian",
+ "languages.rus": "Russian",
+ "languages.san": "Sanskrit",
+ "languages.srd": "Sardinian",
+ "languages.snd": "Sindhi",
+ "languages.sme": "Northern Sami",
+ "languages.smo": "Samoan",
+ "languages.sag": "Sango",
+ "languages.srp": "Serbian",
+ "languages.gla": "Scottish Gaelic",
+ "languages.sna": "Shona",
+ "languages.sin": "Sinhala",
+ "languages.slk": "Slovak",
+ "languages.slv": "Slovene",
+ "languages.som": "Somali",
+ "languages.sot": "Southern Sotho",
+ "languages.spa": "Spanish",
+ "languages.sun": "Sundanese",
+ "languages.swa": "Swahili",
+ "languages.ssw": "Swati",
+ "languages.swe": "Swedish",
+ "languages.tam": "Tamil",
+ "languages.tel": "Telugu",
+ "languages.tgk": "Tajik",
+ "languages.tha": "Thai",
+ "languages.tir": "Tigrinya",
+ "languages.bod": "Tibetan Standard",
+ "languages.tuk": "Turkmen",
+ "languages.tgl": "Tagalog",
+ "languages.tsn": "Tswana",
+ "languages.ton": "Tonga",
+ "languages.tur": "Turkish",
+ "languages.tso": "Tsonga",
+ "languages.tat": "Tatar",
+ "languages.twi": "Twi",
+ "languages.tah": "Tahitian",
+ "languages.uig": "Uighur",
+ "languages.ukr": "Ukrainian",
+ "languages.urd": "Urdu",
+ "languages.uzb": "Uzbek",
+ "languages.ven": "Venda",
+ "languages.vie": "Vietnamese",
+ "languages.vol": "Volapük",
+ "languages.win": "Walloon",
+ "languages.cym": "Welsh",
+ "languages.wol": "Wolof",
+ "languages.fry": "Western Frisian",
+ "languages.xho": "Xhosa",
+ "languages.yid": "Yiddish",
+ "languages.yor": "Yoruba",
+ "languages.zha": "Zhuang",
+ "languages.zul": "Zulu",
+ "DO_NOT_TRANSLATE": "end"
+}
diff --git a/sources/lang/Localization_sv.json b/sources/lang/Localization_sv.json
new file mode 100644
index 0000000..90de410
--- /dev/null
+++ b/sources/lang/Localization_sv.json
@@ -0,0 +1,293 @@
+{
+ "about.title": "Om COPS",
+ "allbooks.alphabetical.many": "Alfabetisk lista över {0} böcker",
+ "allbooks.alphabetical.none": "Alfabetisk lista över inte en enda bok",
+ "allbooks.alphabetical.one": "Alfabetisk lista över en enda bok",
+ "allbooks.title": "Alla böcker",
+ "authors.alphabetical.many": "Alfabetisk lista över {0} författare",
+ "authors.alphabetical.none": "Alfabetisk lista över inte en enda författare",
+ "authors.alphabetical.one": "Alfabetisk lista över en enda författare",
+ "authors.title": "Författare",
+ "authorword.many": "{0} författare",
+ "authorword.none": "Inga författare",
+ "authorword.one": "1 författare",
+ "bookentry.author": "{0} av {1}",
+ "bookword.many": "{0} böcker",
+ "bookword.none": "Inga böcker",
+ "bookword.one": "1 bok",
+ "bookword.title": "Böcker",
+ "cog.alternate": "Sök, sortera och filtrera",
+ "content.series": "Serier:",
+ "content.series.data": "Bok {0} i {1} serien",
+ "content.summary": "Sammanfattning",
+ "customcolumn.boolean.no": "Nej",
+ "customcolumn.boolean.unknown": "Ej Defijerat",
+ "customcolumn.boolean.yes": "Ja",
+ "customcolumn.date.format": "Y-m-d",
+ "customcolumn.date.unknown": "Ej Defijerat",
+ "customcolumn.description": "Custom column '{0}'",
+ "customcolumn.description.bool": "Index of a boolean value",
+ "customcolumn.description.enum.many": "Alphabetical index of the {0} values",
+ "customcolumn.description.enum.none": "Alphabetical index of absolutely no values",
+ "customcolumn.description.enum.one": "Alphabetical index of one value",
+ "customcolumn.description.rating": "Index of ratings",
+ "customcolumn.description.series.many": "Alfabetisk lista över {0} serier",
+ "customcolumn.description.series.none": "Alfabetisk lista över inte en enda serie",
+ "customcolumn.description.series.one": "Alfabetisk lista över en enda serie",
+ "customcolumn.enum.unknown": "Ej Defijerat",
+ "customcolumn.float.unknown": "Ej Defijerat",
+ "customcolumn.int.unknown": "Ej Defijerat",
+ "customcolumn.rating.unknown": "Ej Defijerat",
+ "customcolumn.stars.many": "{0} Stars",
+ "customcolumn.stars.none": "No Stars",
+ "customcolumn.stars.one": "1 Star",
+ "customize.email": "Ange din epostadress (för att aktivera epost)",
+ "customize.fancybox": "Använd ljusbord",
+ "customize.filter": "Använd etikettfiltrering",
+ "customize.ignored": "Ignorerade kategorier",
+ "customize.paging": "Max antal böcker per sida (-1 för att inaktivera)",
+ "customize.style": "Tema",
+ "customize.title": "Anpassa COPS utseende",
+ "home.alternate": "Hem",
+ "i18n.coversection": "Framsida",
+ "language.title": "Språk",
+ "languages.alphabetical.many": "Alfabetisk lista över {0} språk",
+ "languages.alphabetical.none": "Alfabetisk lista över inte ett enda språk",
+ "languages.alphabetical.one": "Alfabetisk lista över ett enda spåk",
+ "languages.title": "Språk",
+ "mail.messagenotsent": "Mejlet kunde inte skickas.",
+ "mail.messagesent": "Mejlet är skickat",
+ "paging.next.alternate": "Nästa",
+ "paging.previous.alternate": "Föregående",
+ "permalink.alternate": "Permalink",
+ "pubdate.title": "Tryckår",
+ "publisher.name": "Förlag",
+ "publishers.alphabetical.many": "Alfabetisk lista över {0} förlag",
+ "publishers.alphabetical.none": "Alfabetisk lista över inte ett enda förlag",
+ "publishers.alphabetical.one": "Alfabetisk lista över ett enda förlag",
+ "publishers.title": "Förlag",
+ "publisherword.many": "{0} förlag",
+ "publisherword.none": "Inget förlag",
+ "publisherword.one": "1 förlag",
+ "ratings.many": "{0} ratings",
+ "ratings.none": "no ratings",
+ "ratings.one": "1 rating",
+ "ratings.title": "Ratings",
+ "ratingword.many": "{0} stars",
+ "ratingword.none": "No star",
+ "ratingword.one": "1 star",
+ "recent.list": "De {0} senaste böckerna",
+ "recent.title": "Nya böcker",
+ "search.alternate": "Sök",
+ "search.result": "Sökresultat för *{0}*",
+ "search.result.author": "Sökresultat för *{0}* i författare",
+ "search.result.book": "Sökresultat för *{0}* i böcker",
+ "search.result.publisher": "Sökresultat för *{0}* i förlag",
+ "search.result.series": "Sökresultat för *{0}* i serier",
+ "search.result.tag": "Sökresultat för *{0}* i etiketter",
+ "search.sortorder.asc": "Stigande",
+ "search.sortorder.desc": "Fallande",
+ "series.alphabetical.many": "Alfabetisk lista över {0} serier",
+ "series.alphabetical.none": "Alfabetisk lista över inte en enda serie",
+ "series.alphabetical.one": "Alfabetisk lista över en enda serie",
+ "series.title": "Serier",
+ "seriesword.many": "inga serier{0} serier",
+ "seriesword.none": "No series",
+ "seriesword.one": "1 serie",
+ "sort.alternate": "Sortera",
+ "splitByLetter.book.other": "Övriga böcker",
+ "splitByLetter.letter": "{0} börjar med {1}",
+ "tags.alphabetical.many": "Alfabetisk lista över {0} etiketter",
+ "tags.alphabetical.none": "Alfabetisk lista över inte en enda etikett",
+ "tags.alphabetical.one": "Alfabetisk lista över en enda etikett",
+ "tags.title": "Etiketter",
+ "tagword.many": "{0} etiketter",
+ "tagword.none": "Inga etiketter",
+ "tagword.one": "1 etikett",
+ "tagword.title": "Etiketter",
+ "languages.abk": "Abchaziska",
+ "languages.aaf": "Afar",
+ "languages.afr": "Afrikaans",
+ "languages.aka": "Akan",
+ "languages.sqi": "Albanska",
+ "languages.amh": "Amhariska",
+ "languages.ara": "Arabiska",
+ "languages.arg": "Aragonska",
+ "languages.hye": "Armenska",
+ "languages.asm": "Assamesiska",
+ "languages.ava": "Avariska",
+ "languages.ave": "Avestiska",
+ "languages.aym": "Aymara",
+ "languages.aze": "Azerbajdzjanska",
+ "languages.bam": "Bambara",
+ "languages.bak": "Basjkiriska",
+ "languages.eus": "Baskiska",
+ "languages.bel": "Vitrysska",
+ "languages.ben": "Bengali",
+ "languages.bih": "Bihariska",
+ "languages.bis": "Bislama",
+ "languages.bos": "Bosniska",
+ "languages.bre": "Bretonska",
+ "languages.bul": "Bulgariska",
+ "languages.mya": "Burmesiska",
+ "languages.cat": "Katalanska",
+ "languages.cha": "Chamorro",
+ "languages.che": "Tjetjenska",
+ "languages.nya": "Chichewa",
+ "languages.zho": "Kinesiska",
+ "languages.chv": "Chuvash",
+ "languages.cor": "Korniska",
+ "languages.cos": "Korsikanska",
+ "languages.cre": "Cree",
+ "languages.hrv": "Kroatiska",
+ "languages.ces": "Tjeckiska",
+ "languages.dan": "Danska",
+ "languages.div": "Divehi",
+ "languages.nld": "Holländska",
+ "languages.dzo": "Dzongkha",
+ "languages.eng": "Engelska",
+ "languages.epo": "Esperanto",
+ "languages.est": "Estniska",
+ "languages.ewe": "Ewe",
+ "languages.fao": "Färöiska",
+ "languages.fij": "Fijianska",
+ "languages.fin": "Finska",
+ "languages.fra": "Franska",
+ "languages.ful": "Fula",
+ "languages.glg": "Galiciska",
+ "languages.kat": "Georgiska",
+ "languages.deu": "Tyska",
+ "languages.ell": "Grekiska",
+ "languages.grn": "Guaraní",
+ "languages.guj": "Gujarati",
+ "languages.hat": "Haitisk kreol",
+ "languages.hau": "Hausa",
+ "languages.hed": "Hebreiska",
+ "languages.her": "Herero",
+ "languages.hin": "Hindi",
+ "languages.hmo": "Hiri Motu",
+ "languages.hun": "Ungerska",
+ "languages.ina": "Interlingua",
+ "languages.ind": "Indonesian",
+ "languages.ile": "Interlingue",
+ "languages.gle": "Iriska",
+ "languages.ibo": "Igbo",
+ "languages.ipk": "Inupiaq",
+ "languages.ido": "Ido",
+ "languages.isl": "Isländska",
+ "languages.ita": "Italienska",
+ "languages.iku": "Inuktitut",
+ "languages.jpn": "Japanska",
+ "languages.jav": "Javanesiska",
+ "languages.kal": "Grönländska",
+ "languages.kan": "Kannada",
+ "languages.kau": "Kanuri",
+ "languages.kas": "Kashmiriska",
+ "languages.kaz": "Kazakiska",
+ "languages.khm": "Khmer",
+ "languages.kik": "Kikuyu",
+ "languages.kin": "Kinyarwanda",
+ "languages.kir": "Kirgisiska",
+ "languages.kom": "Komi",
+ "languages.kon": "Kongo",
+ "languages.kor": "Koreanska",
+ "languages.kur": "Kurdiska",
+ "languages.kua": "Kwanyama",
+ "languages.lat": "Latin",
+ "languages.ltz": "Luxembourgiska",
+ "languages.lug": "Ganda",
+ "languages.lim": "Limburgiska",
+ "languages.lin": "Lingala",
+ "languages.lao": "Lao",
+ "languages.lit": "Litauiska",
+ "languages.lub": "Luba-Katanga",
+ "languages.lav": "Lettiska",
+ "languages.glv": "Manx",
+ "languages.mkd": "Makedonska",
+ "languages.mlg": "Malagassiska",
+ "languages.msa": "Malay",
+ "languages.mal": "Malayalam",
+ "languages.mlt": "Maltesiska",
+ "languages.mri": "Maori",
+ "languages.mar": "Marathi",
+ "languages.mah": "Marshallesiska",
+ "languages.mon": "Mongoliska",
+ "languages.nau": "Nauriska",
+ "languages.nav": "Navajo",
+ "languages.nob": "Norska Bokmål",
+ "languages.nde": "Nordndebele",
+ "languages.nep": "Nepalesiska",
+ "languages.ndo": "Ndonga",
+ "languages.nno": "Nynorsk",
+ "languages.nor": "Norska",
+ "languages.iii": "Nuosu",
+ "languages.nbl": "Sydndebele",
+ "languages.oci": "Occitanska",
+ "languages.oji": "Ojibwe",
+ "languages.chu": "Kyrkslaviska",
+ "languages.orm": "Oromo",
+ "languages.ori": "Oriya",
+ "languages.oss": "Ossetiska",
+ "languages.pan": "Punjabiska",
+ "languages.pli": "Pali",
+ "languages.fas": "Persiska",
+ "languages.pol": "Polska",
+ "languages.pus": "Pashto",
+ "languages.por": "Portugisiska",
+ "languages.que": "Quechua",
+ "languages.roh": "Rätoromanska",
+ "languages.run": "Kirundi",
+ "languages.ron": "Rumänska",
+ "languages.rus": "Rysska",
+ "languages.san": "Sanskrit",
+ "languages.srd": "Sardiska",
+ "languages.snd": "Sindhi",
+ "languages.sme": "Nordsamiska",
+ "languages.smo": "Samoanska",
+ "languages.sag": "Sango",
+ "languages.srp": "Serbiska",
+ "languages.gla": "Skotsk gaeliska",
+ "languages.sna": "Shona",
+ "languages.sin": "Sinhala",
+ "languages.slk": "Slovakiska",
+ "languages.slv": "Slovenska",
+ "languages.som": "Somaliska",
+ "languages.sot": "Sesotho",
+ "languages.spa": "Spanska",
+ "languages.sun": "Sundanesiska",
+ "languages.swa": "Swahili",
+ "languages.ssw": "Swati",
+ "languages.swe": "Svenska",
+ "languages.tam": "Tamil",
+ "languages.tel": "Telugu",
+ "languages.tgk": "Tajikiska",
+ "languages.tha": "Thailändska",
+ "languages.tir": "Tigrinya",
+ "languages.bod": "Tibetanska",
+ "languages.tuk": "Turkmeniska",
+ "languages.tgl": "Tagalog",
+ "languages.tsn": "Tswana",
+ "languages.ton": "Tonga",
+ "languages.tur": "Turkiska",
+ "languages.tso": "Tsonga",
+ "languages.tat": "Tatariska",
+ "languages.twi": "Twi",
+ "languages.tah": "Tahitian",
+ "languages.uig": "Uiguriska",
+ "languages.ukr": "Ukrainska",
+ "languages.urd": "Urdu",
+ "languages.uzb": "Uzbekiska",
+ "languages.ven": "Venda",
+ "languages.vie": "Vietnamesiska",
+ "languages.vol": "Volapük",
+ "languages.win": "Vallonska",
+ "languages.cym": "Walesiska",
+ "languages.wol": "Wolof",
+ "languages.fry": "Västfrisiska",
+ "languages.xho": "Xhosa",
+ "languages.yid": "Jiddish",
+ "languages.yor": "Yoruba",
+ "languages.zha": "Zhuang",
+ "languages.zul": "Zulu",
+ "DO_NOT_TRANSLATE": "end"
+}
diff --git a/sources/lang/Localization_tr.json b/sources/lang/Localization_tr.json
new file mode 100644
index 0000000..6f7e0ad
--- /dev/null
+++ b/sources/lang/Localization_tr.json
@@ -0,0 +1,293 @@
+{
+ "about.title": "COPS Hakkında",
+ "allbooks.alphabetical.many": "Alfabeye göre sıralanmış {0} Kitap",
+ "allbooks.alphabetical.none": "Alfabeye göre sıralanmış 0 Kitap",
+ "allbooks.alphabetical.one": "Alfabeye göre sıralanmış 1 Kitap",
+ "allbooks.title": "Bütün Kitaplar",
+ "authors.alphabetical.many": "Alfabeye göre sıralanmış {0} Yazar",
+ "authors.alphabetical.none": "Alfabeye göre sıralanmış 0 Yazar",
+ "authors.alphabetical.one": "Alfabeye göre sıralanmış 1Yazar",
+ "authors.title": "Yazarlar",
+ "authorword.many": "{0} Yazar",
+ "authorword.none": "0 Yazar",
+ "authorword.one": "1 Yazar",
+ "bookentry.author": "{1}'den {0}.",
+ "bookword.many": "{0} Kitap",
+ "bookword.none": "0 Kitap",
+ "bookword.one": "1 Kitap",
+ "bookword.title": "Kitaplar",
+ "cog.alternate": "Arama, sıralama ve filtreleme",
+ "content.series": "Seriler:",
+ "content.series.data": "{1} Serisinin {0}. Kitabı",
+ "content.summary": "Bilgi",
+ "customcolumn.boolean.no": "Hayır",
+ "customcolumn.boolean.unknown": "Ayarlı değil",
+ "customcolumn.boolean.yes": "Evet",
+ "customcolumn.date.format": "Y-m-d",
+ "customcolumn.date.unknown": "Ayarlı değil",
+ "customcolumn.description": "Custom column '{0}'",
+ "customcolumn.description.bool": "Index of a boolean value",
+ "customcolumn.description.enum.many": "Alphabetical index of the {0} values",
+ "customcolumn.description.enum.none": "Alphabetical index of absolutely no values",
+ "customcolumn.description.enum.one": "Alphabetical index of one value",
+ "customcolumn.description.rating": "Index of ratings",
+ "customcolumn.description.series.many": "Alfabeye göre sıralanmış {0} Seri",
+ "customcolumn.description.series.none": "Alfabeye göre sıralanmış 0 Seri",
+ "customcolumn.description.series.one": "Alfabeye göre sıralanmış 1 Seri",
+ "customcolumn.enum.unknown": "Ayarlı değil",
+ "customcolumn.float.unknown": "Ayarlı değil",
+ "customcolumn.int.unknown": "Ayarlı değil",
+ "customcolumn.rating.unknown": "Ayarlı değil",
+ "customcolumn.stars.many": "{0} Stars",
+ "customcolumn.stars.none": "No Stars",
+ "customcolumn.stars.one": "1 Star",
+ "customize.email": "E-Mail Adresi (kitap gönderilebilmesi için)",
+ "customize.fancybox": "Lightbox kullan (Kitap bilgileri yeni sayfa açılmasına gerek kalmadan görüntülenir)",
+ "customize.filter": "Etiket Filtreleme'yi Etkinleştir",
+ "customize.ignored": "Yoksayılan Kategoriler",
+ "customize.paging": "Bir sayfada gösterilecek en fazla kitap sayısı (Sınırsız için -1)",
+ "customize.style": "Tema",
+ "customize.title": "Görünüm Seçenekleri",
+ "home.alternate": "Anasayfa",
+ "i18n.coversection": "Kapak",
+ "language.title": "Dil",
+ "languages.alphabetical.many": "Alfabeye göre sıralanmış {0} Dil",
+ "languages.alphabetical.none": "Alfabeye göre sıralanmış 0 Dil",
+ "languages.alphabetical.one": "Alfabeye göre sıralanmış 1 Dil",
+ "languages.title": "Diller",
+ "mail.messagenotsent": "Mesaj gönderilemedi.",
+ "mail.messagesent": "Mesaj gönderildi.",
+ "paging.next.alternate": "Sonraki",
+ "paging.previous.alternate": "Önceki",
+ "permalink.alternate": "Kalıcı Bağlantı",
+ "pubdate.title": "Yayımlanma Yılı",
+ "publisher.name": "Yayınevi",
+ "publishers.alphabetical.many": "Alfabeye göre sıralanmış {0} Yayınevi",
+ "publishers.alphabetical.none": "Alfabeye göre sıralanmış 0 Yayınevi",
+ "publishers.alphabetical.one": "Alfabeye göre sıralanmış 1 Yayınevi",
+ "publishers.title": "Yayınevleri",
+ "publisherword.many": "{0} Yayınevi",
+ "publisherword.none": "0 Yayınevi",
+ "publisherword.one": "1 Yayınevi",
+ "ratings.many": "{0} Derecelendirme",
+ "ratings.none": "0 Derecelendirme",
+ "ratings.one": "1 Derecelendirme",
+ "ratings.title": "Derecelendirmeler",
+ "ratingword.many": "{0} Yıldız",
+ "ratingword.none": "0 Yıldız",
+ "ratingword.one": "1 Yıldız",
+ "recent.list": "Son Eklenen {0} Kitap",
+ "recent.title": "Son Eklenenler",
+ "search.alternate": "Arama",
+ "search.result": "*{0}* için arama sonuçları",
+ "search.result.author": "*{0}* için yazarlarda arama sonuçları",
+ "search.result.book": "*{0}* için kitaplarda arama sonuçları",
+ "search.result.publisher": "*{0}* için yayınevlerinde arama sonuçları",
+ "search.result.series": "*{0}* için serilerde arama sonuçları",
+ "search.result.tag": "*{0}* için etiketlerde arama sonuçları",
+ "search.sortorder.asc": "Artan",
+ "search.sortorder.desc": "Azalan",
+ "series.alphabetical.many": "Alfabeye göre sıralanmış {0} Seri",
+ "series.alphabetical.none": "Alfabeye göre sıralanmış 0 Seri",
+ "series.alphabetical.one": "Alfabeye göre sıralanmış 1 Seri",
+ "series.title": "Seriler",
+ "seriesword.many": "{0} Seri",
+ "seriesword.none": "0 Seri",
+ "seriesword.one": "1 Seri",
+ "sort.alternate": "Sıralama",
+ "splitByLetter.book.other": "Diğer Kitaplar",
+ "splitByLetter.letter": "{1} ile başlayan {0}",
+ "tags.alphabetical.many": "Alfabeye göre sıralanmış {0} Etiket",
+ "tags.alphabetical.none": "Alfabeye göre sıralanmış 0 Etiket",
+ "tags.alphabetical.one": "Alfabeye göre sıralanmış 1 Etiket",
+ "tags.title": "Etiketler",
+ "tagword.many": "{0} Etiket",
+ "tagword.none": "0 Etiket",
+ "tagword.one": "1 Etiket",
+ "tagword.title": "Etiketler",
+ "languages.abk": "Abhazya Dili",
+ "languages.aaf": "Afar Dili",
+ "languages.afr": "Afrikanca",
+ "languages.aka": "Akanca (Afrika dili)",
+ "languages.sqi": "Arnavutça",
+ "languages.amh": "Etiyopyaca",
+ "languages.ara": "Arapça",
+ "languages.arg": "Aragonca (İspanya)",
+ "languages.hye": "Ermenice",
+ "languages.asm": "Assamese (Hindistan)",
+ "languages.ava": "Avarca",
+ "languages.ave": "Avestan (Eski İran)",
+ "languages.aym": "Aymara (Güney Amerika)",
+ "languages.aze": "Azerice",
+ "languages.bam": "Bambara (Mali)",
+ "languages.bak": "Başkırca",
+ "languages.eus": "Baskça",
+ "languages.bel": "Beyaz Rusça",
+ "languages.ben": "Bengalce",
+ "languages.bih": "Bihari",
+ "languages.bis": "Bislama (Vanuatu; Kuzey Pasifik)",
+ "languages.bos": "Boşnakça",
+ "languages.bre": "Bretonca",
+ "languages.bul": "Bulgarca",
+ "languages.mya": "Burmaca",
+ "languages.cat": "Katalanca",
+ "languages.cha": "Chamorro Dili (Guam adaları)",
+ "languages.che": "Çeçence",
+ "languages.nya": "Chichewa Dili",
+ "languages.zho": "Çince",
+ "languages.chv": "Çuvaş (Türkçe)",
+ "languages.cor": "Cornish (Kelt)",
+ "languages.cos": "Korsikaca",
+ "languages.cre": "Cree (Kuzey Amerika yerlileri)",
+ "languages.hrv": "Hırvatça",
+ "languages.ces": "Çekçe",
+ "languages.dan": "Danca",
+ "languages.div": "Divehi dili",
+ "languages.nld": "Hollandaca",
+ "languages.dzo": "Dzongkha (Butan)",
+ "languages.eng": "İngilizce",
+ "languages.epo": "Esperanto Dili",
+ "languages.est": "Estonca",
+ "languages.ewe": "Ewe Dili (Afrika)",
+ "languages.fao": "Faroece",
+ "languages.fij": "Fiji dili",
+ "languages.fin": "Fince",
+ "languages.fra": "Fransızca",
+ "languages.ful": "Fulah (Afrika)",
+ "languages.glg": "Galce",
+ "languages.kat": "Gürcüce",
+ "languages.deu": "Almanca",
+ "languages.ell": "Yunanca",
+ "languages.grn": "Guarani (Paraguay)",
+ "languages.guj": "Gucaratça",
+ "languages.hat": "Haiti Dili",
+ "languages.hau": "Hausa Dili",
+ "languages.hed": "İbranice",
+ "languages.her": "Herero Dili",
+ "languages.hin": "Hintçe",
+ "languages.hmo": "Hiri Motu Dili",
+ "languages.hun": "Macarca",
+ "languages.ina": "Interlingua (Uluslararası Yardımcı Dil Kurumu)",
+ "languages.ind": "Endonezyaca",
+ "languages.ile": "Interlingue Dili",
+ "languages.gle": "İrlandaca",
+ "languages.ibo": "Igbo Dili",
+ "languages.ipk": "Inupiak Dili",
+ "languages.ido": "Ido Dili",
+ "languages.isl": "İzlandaca",
+ "languages.ita": "İtalyanca",
+ "languages.iku": "İnuitçe",
+ "languages.jpn": "Japonca",
+ "languages.jav": "Cava Dili",
+ "languages.kal": "Kalaallisut Dili",
+ "languages.kan": "Kannada Dili",
+ "languages.kau": "Kanuri Dili",
+ "languages.kas": "Keşmirce",
+ "languages.kaz": "Kazakça",
+ "languages.khm": "Khmer Dili",
+ "languages.kik": "Kikuyu Dili",
+ "languages.kin": "Kinyarwanda Dili",
+ "languages.kir": "Kırgızca",
+ "languages.kom": "Komi Dili",
+ "languages.kon": "Kongo Dili",
+ "languages.kor": "Korece",
+ "languages.kur": "Kürtçe",
+ "languages.kua": "Kwanyama Dili",
+ "languages.lat": "Latince",
+ "languages.ltz": "Lüksemburg Dili",
+ "languages.lug": "Ganda Dili",
+ "languages.lim": "Liburg Dili",
+ "languages.lin": "Lingala Dili",
+ "languages.lao": "Lao Dili",
+ "languages.lit": "Litvanca",
+ "languages.lub": "Luba-Katanga Dili",
+ "languages.lav": "Letonca",
+ "languages.glv": "Manx Dili (Galler)",
+ "languages.mkd": "Makedonca",
+ "languages.mlg": "Madagaskar Dili",
+ "languages.msa": "Malay Dili",
+ "languages.mal": "Malayalam Dili",
+ "languages.mlt": "Malta Dili",
+ "languages.mri": "Maorice",
+ "languages.mar": "Marathi Dili",
+ "languages.mah": "Marshall Dili",
+ "languages.mon": "Moğolca",
+ "languages.nau": "Nauru Dili",
+ "languages.nav": "Navajo Dili",
+ "languages.nob": "Norveççe Bokmal",
+ "languages.nde": "Ndebele; Kuzey",
+ "languages.nep": "Nepal Dili",
+ "languages.ndo": "Ndonga Dili",
+ "languages.nno": "Norveççe Nynorsk",
+ "languages.nor": "Norveçce",
+ "languages.iii": "Nuosu Dili",
+ "languages.nbl": "Ndebele; Güney",
+ "languages.oci": "Oksitanca",
+ "languages.oji": "Ojibwe Dili",
+ "languages.chu": "Slav; Eski",
+ "languages.orm": "Oromo Dili",
+ "languages.ori": "Oriya Dili",
+ "languages.oss": "Osetya Dili",
+ "languages.pan": "Pencabi Dili",
+ "languages.pli": "Pali Dili",
+ "languages.fas": "Farsça",
+ "languages.pol": "Lehçe",
+ "languages.pus": "Peştuca",
+ "languages.por": "Portekizce",
+ "languages.que": "Quechua Dili",
+ "languages.roh": "Romanca",
+ "languages.run": "Kirundi Dili",
+ "languages.ron": "Rumence",
+ "languages.rus": "Rusça",
+ "languages.san": "Sanskritçe",
+ "languages.srd": "Sardinya Dili",
+ "languages.snd": "Sindhi Dili",
+ "languages.sme": "Sami; Kuzeyli",
+ "languages.smo": "Samoa Dili",
+ "languages.sag": "Sangho Dili",
+ "languages.srp": "Sırpça",
+ "languages.gla": "Galce; İskoçyalı",
+ "languages.sna": "Shona Dili",
+ "languages.sin": "Sinhala Dili",
+ "languages.slk": "Slovakça",
+ "languages.slv": "Slovence",
+ "languages.som": "Somali Dili",
+ "languages.sot": "Güney Sotho Dili",
+ "languages.spa": "İspanyolca",
+ "languages.sun": "Sudan Dili",
+ "languages.swa": "Svahili dili",
+ "languages.ssw": "Siswati Dili",
+ "languages.swe": "İsveççe",
+ "languages.tam": "Tamil Dili",
+ "languages.tel": "Telugu Dili",
+ "languages.tgk": "Tacikçe",
+ "languages.tha": "Taylandça",
+ "languages.tir": "Tigrinya Dili",
+ "languages.bod": "Tibetçe",
+ "languages.tuk": "Türkmence",
+ "languages.tgl": "Tagalog Dili",
+ "languages.tsn": "Tsvana Dili",
+ "languages.ton": "Tonga Dili",
+ "languages.tur": "Türkçe",
+ "languages.tso": "Tsonga Dili",
+ "languages.tat": "Tatarca",
+ "languages.twi": "Twi Dili",
+ "languages.tah": "Tahitice",
+ "languages.uig": "Uygurca",
+ "languages.ukr": "Ukraynaca",
+ "languages.urd": "Urduca",
+ "languages.uzb": "Özbekçe",
+ "languages.ven": "Venda Dili",
+ "languages.vie": "Vietnam Dili",
+ "languages.vol": "Volapük Dili",
+ "languages.win": "Valon Dili",
+ "languages.cym": "Galce",
+ "languages.wol": "Volof Dili",
+ "languages.fry": "Batı Frizce",
+ "languages.xho": "Zosa dili",
+ "languages.yid": "Yidiş Dili",
+ "languages.yor": "Yoruba Dili",
+ "languages.zha": "Zhuang Dili",
+ "languages.zul": "Zulu Dili",
+ "DO_NOT_TRANSLATE": "end"
+}
diff --git a/sources/lang/Localization_ua.json b/sources/lang/Localization_ua.json
new file mode 100644
index 0000000..1d701f0
--- /dev/null
+++ b/sources/lang/Localization_ua.json
@@ -0,0 +1,293 @@
+{
+ "about.title": "Про COPS",
+ "allbooks.alphabetical.many": "Алфавітний покажчик {0} книг(и)",
+ "allbooks.alphabetical.none": "Назви не вказані",
+ "allbooks.alphabetical.one": "Єдина книга",
+ "allbooks.title": "Усі книги",
+ "authors.alphabetical.many": "Алфавітний покажчик {0} авторів(а)",
+ "authors.alphabetical.none": "Автори не вказані",
+ "authors.alphabetical.one": "Єдиний автор",
+ "authors.title": "Автори",
+ "authorword.many": "{0} авторів(а)",
+ "authorword.none": "Немає авторів(а)",
+ "authorword.one": "1 автор",
+ "bookentry.author": "{0} з {1}",
+ "bookword.many": "{0} книг(и)",
+ "bookword.none": "Немає книг(и)",
+ "bookword.one": "1 книга",
+ "bookword.title": "Книги",
+ "cog.alternate": "Пошук, сортування та фільтри",
+ "content.series": "Серії:",
+ "content.series.data": "Книга {0} з серії {1}",
+ "content.summary": "Короткий зміст",
+ "customcolumn.boolean.no": "Ні",
+ "customcolumn.boolean.unknown": "Не встановлено",
+ "customcolumn.boolean.yes": "Так",
+ "customcolumn.date.format": "Y-m-d",
+ "customcolumn.date.unknown": "Не встановлено",
+ "customcolumn.description": "Custom column '{0}'",
+ "customcolumn.description.bool": "Index of a boolean value",
+ "customcolumn.description.enum.many": "Alphabetical index of the {0} values",
+ "customcolumn.description.enum.none": "Alphabetical index of absolutely no values",
+ "customcolumn.description.enum.one": "Alphabetical index of one value",
+ "customcolumn.description.rating": "Index of ratings",
+ "customcolumn.description.series.many": "Алфавітний покажчик {0} серій(ї)",
+ "customcolumn.description.series.none": "Серії не вказано",
+ "customcolumn.description.series.one": "Єдина серія",
+ "customcolumn.enum.unknown": "Не встановлено",
+ "customcolumn.float.unknown": "Не встановлено",
+ "customcolumn.int.unknown": "Не встановлено",
+ "customcolumn.rating.unknown": "Не встановлено",
+ "customcolumn.stars.many": "{0} Stars",
+ "customcolumn.stars.none": "No Stars",
+ "customcolumn.stars.one": "1 Star",
+ "customize.email": "Вкажіть Ваш email (для відправки книг електронною поштою)",
+ "customize.fancybox": "Відкривати картку книги у спливаючому вікні",
+ "customize.filter": "Увімкнути фільтрацію за жанром",
+ "customize.ignored": "Виключити категорії",
+ "customize.paging": "Максимальна кількість книжок на сторінці (-1 - не обмежувати)",
+ "customize.style": "Тема",
+ "customize.title": "Налаштування зовнішнього вигляду COPS",
+ "home.alternate": "Додому",
+ "i18n.coversection": "Обкладинка",
+ "language.title": "Мова",
+ "languages.alphabetical.many": "Алфавітний покажчик {0} мов(и)",
+ "languages.alphabetical.none": "Мови не вказані",
+ "languages.alphabetical.one": "Єдина мова",
+ "languages.title": "Мови",
+ "mail.messagenotsent": "Повідомлення не може бути відправлено",
+ "mail.messagesent": "Повідомлення відправлено",
+ "paging.next.alternate": "Наступний",
+ "paging.previous.alternate": "Попередній",
+ "permalink.alternate": "Постійне посилання",
+ "pubdate.title": "Рік публікації",
+ "publisher.name": "Видавництво",
+ "publishers.alphabetical.many": "Алфавітний покажчик of the {0} видавництв(а)",
+ "publishers.alphabetical.none": "Видавництва не вказані",
+ "publishers.alphabetical.one": "Єдине видавництво",
+ "publishers.title": "Видавництва",
+ "publisherword.many": "{0} видавництв(а)",
+ "publisherword.none": "Немає видавництв(а)",
+ "publisherword.one": "1 видавництво",
+ "ratings.many": "{0} оцінок(ка)",
+ "ratings.none": "Немає оцінок",
+ "ratings.one": "1 оцінка",
+ "ratings.title": "Оцінки",
+ "ratingword.many": "{0} зірка(ок)",
+ "ratingword.none": "Немає зірок",
+ "ratingword.one": "1 зірка",
+ "recent.list": "{0} нещодавно доданих книжок",
+ "recent.title": "Останні надходження",
+ "search.alternate": "Пошук",
+ "search.result": "Результат пошуку *{0}*",
+ "search.result.author": "Результат пошуку *{0}* серед авторів",
+ "search.result.book": "Результат пошуку *{0}* серед книжок",
+ "search.result.publisher": "Результат пошуку *{0}* серед видавництв",
+ "search.result.series": "Результат пошуку *{0}* серед серій",
+ "search.result.tag": "Результат пошуку *{0}* серед жанрів",
+ "search.sortorder.asc": "Висх.",
+ "search.sortorder.desc": "Спад.",
+ "series.alphabetical.many": "Алфавітний покажчик {0} серій(ї)",
+ "series.alphabetical.none": "Серії не вказано",
+ "series.alphabetical.one": "Єдина серія",
+ "series.title": "Серії",
+ "seriesword.many": "{0} серій(я)",
+ "seriesword.none": "Немає серій",
+ "seriesword.one": "1 серія",
+ "sort.alternate": "Сортування",
+ "splitByLetter.book.other": "Книги за назвою",
+ "splitByLetter.letter": "{0} починається з {1}",
+ "tags.alphabetical.many": "Алфавітний покажчик {0} жанрів(а)",
+ "tags.alphabetical.none": "Жанри не вказано",
+ "tags.alphabetical.one": "Єдиний жанр",
+ "tags.title": "Жанри",
+ "tagword.many": "{0} жанрів",
+ "tagword.none": "Немає жанрів",
+ "tagword.one": "1 жанр",
+ "tagword.title": "Жанри",
+ "languages.abk": "Abkhaz",
+ "languages.aaf": "Afar",
+ "languages.afr": "Afrikaans",
+ "languages.aka": "Akan",
+ "languages.sqi": "Albanian",
+ "languages.amh": "Amharic",
+ "languages.ara": "Arabic",
+ "languages.arg": "Aragonese",
+ "languages.hye": "Armenian",
+ "languages.asm": "Assamese",
+ "languages.ava": "Avaric",
+ "languages.ave": "Avestan",
+ "languages.aym": "Aymara",
+ "languages.aze": "Azerbaijani",
+ "languages.bam": "Bambara",
+ "languages.bak": "Bashkir",
+ "languages.eus": "Basque",
+ "languages.bel": "Belarusian",
+ "languages.ben": "Bengali",
+ "languages.bih": "Bihari",
+ "languages.bis": "Bislama",
+ "languages.bos": "Bosnian",
+ "languages.bre": "Breton",
+ "languages.bul": "Bulgarian",
+ "languages.mya": "Burmese",
+ "languages.cat": "Catalan",
+ "languages.cha": "Chamorro",
+ "languages.che": "Chechen",
+ "languages.nya": "Chichewa",
+ "languages.zho": "Chinese",
+ "languages.chv": "Chuvash",
+ "languages.cor": "Cornish",
+ "languages.cos": "Corsican",
+ "languages.cre": "Cree",
+ "languages.hrv": "Croatian",
+ "languages.ces": "Czech",
+ "languages.dan": "Danish",
+ "languages.div": "Divehi",
+ "languages.nld": "Dutch",
+ "languages.dzo": "Dzongkha",
+ "languages.eng": "English",
+ "languages.epo": "Esperanto",
+ "languages.est": "Estonian",
+ "languages.ewe": "Ewe",
+ "languages.fao": "Faroese",
+ "languages.fij": "Fijian",
+ "languages.fin": "Finnish",
+ "languages.fra": "French",
+ "languages.ful": "Fula",
+ "languages.glg": "Galician",
+ "languages.kat": "Georgian",
+ "languages.deu": "German",
+ "languages.ell": "Greek",
+ "languages.grn": "Guaraní",
+ "languages.guj": "Gujarati",
+ "languages.hat": "Haitian",
+ "languages.hau": "Hausa",
+ "languages.hed": "Hebrew",
+ "languages.her": "Herero",
+ "languages.hin": "Hindi",
+ "languages.hmo": "Hiri Motu",
+ "languages.hun": "Hungarian",
+ "languages.ina": "Interlingua",
+ "languages.ind": "Indonesian",
+ "languages.ile": "Interlingue",
+ "languages.gle": "Irish",
+ "languages.ibo": "Igbo",
+ "languages.ipk": "Inupiaq",
+ "languages.ido": "Ido",
+ "languages.isl": "Icelandic",
+ "languages.ita": "Italian",
+ "languages.iku": "Inuktitut",
+ "languages.jpn": "Japanese",
+ "languages.jav": "Javanese",
+ "languages.kal": "Kalaallisut",
+ "languages.kan": "Kannada",
+ "languages.kau": "Kanuri",
+ "languages.kas": "Kashmiri",
+ "languages.kaz": "Kazakh",
+ "languages.khm": "Khmer",
+ "languages.kik": "Kikuyu",
+ "languages.kin": "Kinyarwanda",
+ "languages.kir": "Kyrgyz",
+ "languages.kom": "Komi",
+ "languages.kon": "Kongo",
+ "languages.kor": "Korean",
+ "languages.kur": "Kurdish",
+ "languages.kua": "Kwanyama",
+ "languages.lat": "Latin",
+ "languages.ltz": "Luxembourgish",
+ "languages.lug": "Ganda",
+ "languages.lim": "Limburgish",
+ "languages.lin": "Lingala",
+ "languages.lao": "Lao",
+ "languages.lit": "Lithuanian",
+ "languages.lub": "Luba-Katanga",
+ "languages.lav": "Latvian",
+ "languages.glv": "Manx",
+ "languages.mkd": "Macedonian",
+ "languages.mlg": "Malagasy",
+ "languages.msa": "Malay",
+ "languages.mal": "Malayalam",
+ "languages.mlt": "Maltese",
+ "languages.mri": "Māori",
+ "languages.mar": "Marathi",
+ "languages.mah": "Marshallese",
+ "languages.mon": "Mongolian",
+ "languages.nau": "Nauru",
+ "languages.nav": "Navajo",
+ "languages.nob": "Norwegian Bokmål",
+ "languages.nde": "North Ndebele",
+ "languages.nep": "Nepali",
+ "languages.ndo": "Ndonga",
+ "languages.nno": "Norwegian Nynorsk",
+ "languages.nor": "Norwegian",
+ "languages.iii": "Nuosu",
+ "languages.nbl": "South Ndebele",
+ "languages.oci": "Occitan",
+ "languages.oji": "Ojibwe",
+ "languages.chu": "Old Church Slavonic",
+ "languages.orm": "Oromo",
+ "languages.ori": "Oriya",
+ "languages.oss": "Ossetian",
+ "languages.pan": "Panjabi",
+ "languages.pli": "Pāli",
+ "languages.fas": "Persian",
+ "languages.pol": "Polish",
+ "languages.pus": "Pashto",
+ "languages.por": "Portuguese",
+ "languages.que": "Quechua",
+ "languages.roh": "Romansh",
+ "languages.run": "Kirundi",
+ "languages.ron": "Romanian",
+ "languages.rus": "Russian",
+ "languages.san": "Sanskrit",
+ "languages.srd": "Sardinian",
+ "languages.snd": "Sindhi",
+ "languages.sme": "Northern Sami",
+ "languages.smo": "Samoan",
+ "languages.sag": "Sango",
+ "languages.srp": "Serbian",
+ "languages.gla": "Scottish Gaelic",
+ "languages.sna": "Shona",
+ "languages.sin": "Sinhala",
+ "languages.slk": "Slovak",
+ "languages.slv": "Slovene",
+ "languages.som": "Somali",
+ "languages.sot": "Southern Sotho",
+ "languages.spa": "Spanish",
+ "languages.sun": "Sundanese",
+ "languages.swa": "Swahili",
+ "languages.ssw": "Swati",
+ "languages.swe": "Swedish",
+ "languages.tam": "Tamil",
+ "languages.tel": "Telugu",
+ "languages.tgk": "Tajik",
+ "languages.tha": "Thai",
+ "languages.tir": "Tigrinya",
+ "languages.bod": "Tibetan Standard",
+ "languages.tuk": "Turkmen",
+ "languages.tgl": "Tagalog",
+ "languages.tsn": "Tswana",
+ "languages.ton": "Tonga",
+ "languages.tur": "Turkish",
+ "languages.tso": "Tsonga",
+ "languages.tat": "Tatar",
+ "languages.twi": "Twi",
+ "languages.tah": "Tahitian",
+ "languages.uig": "Uighur",
+ "languages.ukr": "Українська",
+ "languages.urd": "Urdu",
+ "languages.uzb": "Uzbek",
+ "languages.ven": "Venda",
+ "languages.vie": "Vietnamese",
+ "languages.vol": "Volapük",
+ "languages.win": "Walloon",
+ "languages.cym": "Welsh",
+ "languages.wol": "Wolof",
+ "languages.fry": "Western Frisian",
+ "languages.xho": "Xhosa",
+ "languages.yid": "Yiddish",
+ "languages.yor": "Yoruba",
+ "languages.zha": "Zhuang",
+ "languages.zul": "Zulu",
+ "DO_NOT_TRANSLATE": "end"
+}
diff --git a/sources/lang/Localization_zh.json b/sources/lang/Localization_zh.json
new file mode 100644
index 0000000..269ed04
--- /dev/null
+++ b/sources/lang/Localization_zh.json
@@ -0,0 +1,293 @@
+{
+ "about.title": "关于COPS",
+ "allbooks.alphabetical.many": "{0} 本书籍的字母索引",
+ "allbooks.alphabetical.none": "无书籍",
+ "allbooks.alphabetical.one": "1 本书籍",
+ "allbooks.title": "所有书籍",
+ "authors.alphabetical.many": "{0} 位作者的字母索引",
+ "authors.alphabetical.none": "无作者",
+ "authors.alphabetical.one": "1 位作者",
+ "authors.title": "作者",
+ "authorword.many": "{0} 位作者",
+ "authorword.none": "未知",
+ "authorword.one": "1 位作者",
+ "bookentry.author": "{0} 由作者 {1}",
+ "bookword.many": "{0} 本书籍",
+ "bookword.none": "无书籍",
+ "bookword.one": "1 本书籍",
+ "bookword.title": "书名",
+ "cog.alternate": "搜索, 分类和过滤",
+ "content.series": "系列:",
+ "content.series.data": "系列 {1} 的第 {0} 本",
+ "content.summary": "概要",
+ "customcolumn.boolean.no": "否",
+ "customcolumn.boolean.unknown": "未设置",
+ "customcolumn.boolean.yes": "是",
+ "customcolumn.date.format": "Y-m-d",
+ "customcolumn.date.unknown": "未设置",
+ "customcolumn.description": "Custom column '{0}'",
+ "customcolumn.description.bool": "Index of a boolean value",
+ "customcolumn.description.enum.many": "Alphabetical index of the {0} values",
+ "customcolumn.description.enum.none": "Alphabetical index of absolutely no values",
+ "customcolumn.description.enum.one": "Alphabetical index of one value",
+ "customcolumn.description.rating": "Index of ratings",
+ "customcolumn.description.series.many": "{0} 个系列的字母索引",
+ "customcolumn.description.series.none": "无系列",
+ "customcolumn.description.series.one": "1 个系列",
+ "customcolumn.enum.unknown": "未设置",
+ "customcolumn.float.unknown": "未设置",
+ "customcolumn.int.unknown": "未设置",
+ "customcolumn.rating.unknown": "未设置",
+ "customcolumn.stars.many": "{0} Stars",
+ "customcolumn.stars.none": "No Stars",
+ "customcolumn.stars.one": "1 Star",
+ "customize.email": "设置您的电子邮件(以允许发送邮件)",
+ "customize.fancybox": "使用灯箱",
+ "customize.filter": "打开标签过滤",
+ "customize.ignored": "Ignored categories",
+ "customize.paging": "页内最大书籍数(-1表示禁用)",
+ "customize.style": "主题",
+ "customize.title": "自定义界面",
+ "home.alternate": "首页",
+ "i18n.coversection": "封面",
+ "language.title": "语言",
+ "languages.alphabetical.many": "{0} 语言的字母索引",
+ "languages.alphabetical.none": "无语言",
+ "languages.alphabetical.one": "1 种语言",
+ "languages.title": "语言",
+ "mail.messagenotsent": "信息未发送。",
+ "mail.messagesent": "信息已发送。",
+ "paging.next.alternate": "下一页",
+ "paging.previous.alternate": "上一页",
+ "permalink.alternate": "永久链接",
+ "pubdate.title": "出版时间",
+ "publisher.name": "Publisher",
+ "publishers.alphabetical.many": "Alphabetical index of the {0} publishers",
+ "publishers.alphabetical.none": "Alphabetical index of absolutely no publishers",
+ "publishers.alphabetical.one": "Alphabetical index of the single publisher",
+ "publishers.title": "Publishers",
+ "publisherword.many": "{0} publishers",
+ "publisherword.none": "No publishers",
+ "publisherword.one": "1 publisher",
+ "ratings.many": "{0} ratings",
+ "ratings.none": "no ratings",
+ "ratings.one": "1 rating",
+ "ratings.title": "Ratings",
+ "ratingword.many": "{0} stars",
+ "ratingword.none": "No star",
+ "ratingword.one": "1 star",
+ "recent.list": "{0} 本最近添加的书",
+ "recent.title": "最近添加",
+ "search.alternate": "搜索",
+ "search.result": "*{0}* 的搜索结果",
+ "search.result.author": "作者中 *{0}* 的搜索结果",
+ "search.result.book": "书籍中 *{0}* 的搜索结果",
+ "search.result.publisher": "Search result for *{0}* in publishers",
+ "search.result.series": "系列中 *{0}* 的搜索结果",
+ "search.result.tag": "标签中 *{0}* 的搜索结果",
+ "search.sortorder.asc": "升序",
+ "search.sortorder.desc": "降序",
+ "series.alphabetical.many": "{0} 个系列的字母索引",
+ "series.alphabetical.none": "无系列",
+ "series.alphabetical.one": "1 个系列",
+ "series.title": "系列",
+ "seriesword.many": "{0} 系列",
+ "seriesword.none": "无系列",
+ "seriesword.one": "1 个系列",
+ "sort.alternate": "排序",
+ "splitByLetter.book.other": "其他书籍",
+ "splitByLetter.letter": "{0} 以 {1} 开头",
+ "tags.alphabetical.many": "{0} 个标签的字母索引",
+ "tags.alphabetical.none": "无标签",
+ "tags.alphabetical.one": "1 个标签",
+ "tags.title": "标签",
+ "tagword.many": "{0} 标签",
+ "tagword.none": "无标签",
+ "tagword.one": "1 个标签",
+ "tagword.title": "标签",
+ "languages.abk": "Abkhaz",
+ "languages.aaf": "Afar",
+ "languages.afr": "Afrikaans",
+ "languages.aka": "Akan",
+ "languages.sqi": "Albanian",
+ "languages.amh": "Amharic",
+ "languages.ara": "Arabic",
+ "languages.arg": "Aragonese",
+ "languages.hye": "Armenian",
+ "languages.asm": "Assamese",
+ "languages.ava": "Avaric",
+ "languages.ave": "Avestan",
+ "languages.aym": "Aymara",
+ "languages.aze": "Azerbaijani",
+ "languages.bam": "Bambara",
+ "languages.bak": "Bashkir",
+ "languages.eus": "Basque",
+ "languages.bel": "Belarusian",
+ "languages.ben": "Bengali",
+ "languages.bih": "Bihari",
+ "languages.bis": "Bislama",
+ "languages.bos": "Bosnian",
+ "languages.bre": "Breton",
+ "languages.bul": "Bulgarian",
+ "languages.mya": "Burmese",
+ "languages.cat": "Catalan",
+ "languages.cha": "Chamorro",
+ "languages.che": "Chechen",
+ "languages.nya": "Chichewa",
+ "languages.zho": "简体中文",
+ "languages.chv": "Chuvash",
+ "languages.cor": "Cornish",
+ "languages.cos": "Corsican",
+ "languages.cre": "Cree",
+ "languages.hrv": "Croatian",
+ "languages.ces": "Czech",
+ "languages.dan": "Danish",
+ "languages.div": "Divehi",
+ "languages.nld": "Dutch",
+ "languages.dzo": "Dzongkha",
+ "languages.eng": "英语",
+ "languages.epo": "Esperanto",
+ "languages.est": "Estonian",
+ "languages.ewe": "Ewe",
+ "languages.fao": "Faroese",
+ "languages.fij": "Fijian",
+ "languages.fin": "Finnish",
+ "languages.fra": "法语",
+ "languages.ful": "Fula",
+ "languages.glg": "Galician",
+ "languages.kat": "Georgian",
+ "languages.deu": "German",
+ "languages.ell": "Greek",
+ "languages.grn": "Guaraní",
+ "languages.guj": "Gujarati",
+ "languages.hat": "Haitian",
+ "languages.hau": "Hausa",
+ "languages.hed": "Hebrew",
+ "languages.her": "Herero",
+ "languages.hin": "Hindi",
+ "languages.hmo": "Hiri Motu",
+ "languages.hun": "Hungarian",
+ "languages.ina": "Interlingua",
+ "languages.ind": "Indonesian",
+ "languages.ile": "Interlingue",
+ "languages.gle": "Irish",
+ "languages.ibo": "Igbo",
+ "languages.ipk": "Inupiaq",
+ "languages.ido": "Ido",
+ "languages.isl": "Icelandic",
+ "languages.ita": "Italian",
+ "languages.iku": "Inuktitut",
+ "languages.jpn": "日语",
+ "languages.jav": "Javanese",
+ "languages.kal": "Kalaallisut",
+ "languages.kan": "Kannada",
+ "languages.kau": "Kanuri",
+ "languages.kas": "Kashmiri",
+ "languages.kaz": "Kazakh",
+ "languages.khm": "Khmer",
+ "languages.kik": "Kikuyu",
+ "languages.kin": "Kinyarwanda",
+ "languages.kir": "Kyrgyz",
+ "languages.kom": "Komi",
+ "languages.kon": "Kongo",
+ "languages.kor": "Korean",
+ "languages.kur": "Kurdish",
+ "languages.kua": "Kwanyama",
+ "languages.lat": "Latin",
+ "languages.ltz": "Luxembourgish",
+ "languages.lug": "Ganda",
+ "languages.lim": "Limburgish",
+ "languages.lin": "Lingala",
+ "languages.lao": "Lao",
+ "languages.lit": "Lithuanian",
+ "languages.lub": "Luba-Katanga",
+ "languages.lav": "Latvian",
+ "languages.glv": "Manx",
+ "languages.mkd": "Macedonian",
+ "languages.mlg": "Malagasy",
+ "languages.msa": "Malay",
+ "languages.mal": "Malayalam",
+ "languages.mlt": "Maltese",
+ "languages.mri": "Māori",
+ "languages.mar": "Marathi",
+ "languages.mah": "Marshallese",
+ "languages.mon": "Mongolian",
+ "languages.nau": "Nauru",
+ "languages.nav": "Navajo",
+ "languages.nob": "Norwegian Bokmål",
+ "languages.nde": "North Ndebele",
+ "languages.nep": "Nepali",
+ "languages.ndo": "Ndonga",
+ "languages.nno": "Norwegian Nynorsk",
+ "languages.nor": "Norwegian",
+ "languages.iii": "Nuosu",
+ "languages.nbl": "South Ndebele",
+ "languages.oci": "Occitan",
+ "languages.oji": "Ojibwe",
+ "languages.chu": "Old Church Slavonic",
+ "languages.orm": "Oromo",
+ "languages.ori": "Oriya",
+ "languages.oss": "Ossetian",
+ "languages.pan": "Panjabi",
+ "languages.pli": "Pāli",
+ "languages.fas": "Persian",
+ "languages.pol": "Polish",
+ "languages.pus": "Pashto",
+ "languages.por": "Portuguese",
+ "languages.que": "Quechua",
+ "languages.roh": "Romansh",
+ "languages.run": "Kirundi",
+ "languages.ron": "Romanian",
+ "languages.rus": "Russian",
+ "languages.san": "Sanskrit",
+ "languages.srd": "Sardinian",
+ "languages.snd": "Sindhi",
+ "languages.sme": "Northern Sami",
+ "languages.smo": "Samoan",
+ "languages.sag": "Sango",
+ "languages.srp": "Serbian",
+ "languages.gla": "Scottish Gaelic",
+ "languages.sna": "Shona",
+ "languages.sin": "Sinhala",
+ "languages.slk": "Slovak",
+ "languages.slv": "Slovene",
+ "languages.som": "Somali",
+ "languages.sot": "Southern Sotho",
+ "languages.spa": "Spanish",
+ "languages.sun": "Sundanese",
+ "languages.swa": "Swahili",
+ "languages.ssw": "Swati",
+ "languages.swe": "Swedish",
+ "languages.tam": "Tamil",
+ "languages.tel": "Telugu",
+ "languages.tgk": "Tajik",
+ "languages.tha": "Thai",
+ "languages.tir": "Tigrinya",
+ "languages.bod": "Tibetan Standard",
+ "languages.tuk": "Turkmen",
+ "languages.tgl": "Tagalog",
+ "languages.tsn": "Tswana",
+ "languages.ton": "Tonga",
+ "languages.tur": "Turkish",
+ "languages.tso": "Tsonga",
+ "languages.tat": "Tatar",
+ "languages.twi": "Twi",
+ "languages.tah": "Tahitian",
+ "languages.uig": "Uighur",
+ "languages.ukr": "Ukrainian",
+ "languages.urd": "Urdu",
+ "languages.uzb": "Uzbek",
+ "languages.ven": "Venda",
+ "languages.vie": "Vietnamese",
+ "languages.vol": "Volapük",
+ "languages.win": "Walloon",
+ "languages.cym": "Welsh",
+ "languages.wol": "Wolof",
+ "languages.fry": "Western Frisian",
+ "languages.xho": "Xhosa",
+ "languages.yid": "Yiddish",
+ "languages.yor": "Yoruba",
+ "languages.zha": "Zhuang",
+ "languages.zul": "Zulu",
+ "DO_NOT_TRANSLATE": "end"
+}
diff --git a/sources/lib/Author.php b/sources/lib/Author.php
new file mode 100644
index 0000000..85e2860
--- /dev/null
+++ b/sources/lib/Author.php
@@ -0,0 +1,94 @@
+
+ */
+
+class Author extends Base
+{
+ const ALL_AUTHORS_ID = "cops:authors";
+
+ const AUTHOR_COLUMNS = "authors.id as id, authors.name as name, authors.sort as sort, count(*) as count";
+ const SQL_AUTHORS_BY_FIRST_LETTER = "select {0} from authors, books_authors_link where author = authors.id and upper (authors.sort) like ? group by authors.id, authors.name, authors.sort order by sort";
+ const SQL_AUTHORS_FOR_SEARCH = "select {0} from authors, books_authors_link where author = authors.id and (upper (authors.sort) like ? or upper (authors.name) like ?) group by authors.id, authors.name, authors.sort order by sort";
+ const SQL_ALL_AUTHORS = "select {0} from authors, books_authors_link where author = authors.id group by authors.id, authors.name, authors.sort order by sort";
+
+ public $id;
+ public $name;
+ public $sort;
+
+ public function __construct($post) {
+ $this->id = $post->id;
+ $this->name = str_replace("|", ",", $post->name);
+ $this->sort = $post->sort;
+ }
+
+ public function getUri () {
+ return "?page=".parent::PAGE_AUTHOR_DETAIL."&id=$this->id";
+ }
+
+ public function getEntryId () {
+ return self::ALL_AUTHORS_ID.":".$this->id;
+ }
+
+ public static function getEntryIdByLetter ($startingLetter) {
+ return self::ALL_AUTHORS_ID.":letter:".$startingLetter;
+ }
+
+ public static function getCount() {
+ // str_format (localize("authors.alphabetical", count(array))
+ return parent::getCountGeneric ("authors", self::ALL_AUTHORS_ID, parent::PAGE_ALL_AUTHORS);
+ }
+
+ public static function getAllAuthorsByFirstLetter() {
+ list (, $result) = parent::executeQuery ("select {0}
+from authors
+group by substr (upper (sort), 1, 1)
+order by substr (upper (sort), 1, 1)", "substr (upper (sort), 1, 1) as title, count(*) as count", "", array (), -1);
+ $entryArray = array();
+ while ($post = $result->fetchObject ())
+ {
+ array_push ($entryArray, new Entry ($post->title, Author::getEntryIdByLetter ($post->title),
+ str_format (localize("authorword", $post->count), $post->count), "text",
+ array ( new LinkNavigation ("?page=".parent::PAGE_AUTHORS_FIRST_LETTER."&id=". rawurlencode ($post->title))), "", $post->count));
+ }
+ return $entryArray;
+ }
+
+ public static function getAuthorsByStartingLetter($letter) {
+ return self::getEntryArray (self::SQL_AUTHORS_BY_FIRST_LETTER, array ($letter . "%"));
+ }
+
+ public static function getAuthorsForSearch($query) {
+ return self::getEntryArray (self::SQL_AUTHORS_FOR_SEARCH, array ($query . "%", $query . "%"));
+ }
+
+ public static function getAllAuthors() {
+ return self::getEntryArray (self::SQL_ALL_AUTHORS, array ());
+ }
+
+ public static function getEntryArray ($query, $params) {
+ return Base::getEntryArrayWithBookNumber ($query, self::AUTHOR_COLUMNS, $params, "Author");
+ }
+
+ public static function getAuthorById ($authorId) {
+ $result = parent::getDb ()->prepare('select ' . self::AUTHOR_COLUMNS . ' from authors where id = ?');
+ $result->execute (array ($authorId));
+ $post = $result->fetchObject ();
+ return new Author ($post);
+ }
+
+ public static function getAuthorByBookId ($bookId) {
+ $result = parent::getDb ()->prepare('select authors.id as id, authors.name as name, authors.sort as sort from authors, books_authors_link
+where author = authors.id
+and book = ?');
+ $result->execute (array ($bookId));
+ $authorArray = array ();
+ while ($post = $result->fetchObject ()) {
+ array_push ($authorArray, new Author ($post));
+ }
+ return $authorArray;
+ }
+}
diff --git a/sources/lib/Base.php b/sources/lib/Base.php
new file mode 100644
index 0000000..e21883a
--- /dev/null
+++ b/sources/lib/Base.php
@@ -0,0 +1,213 @@
+
+ */
+
+abstract class Base
+{
+ const PAGE_INDEX = "index";
+ const PAGE_ALL_AUTHORS = "1";
+ const PAGE_AUTHORS_FIRST_LETTER = "2";
+ const PAGE_AUTHOR_DETAIL = "3";
+ const PAGE_ALL_BOOKS = "4";
+ const PAGE_ALL_BOOKS_LETTER = "5";
+ const PAGE_ALL_SERIES = "6";
+ const PAGE_SERIE_DETAIL = "7";
+ const PAGE_OPENSEARCH = "8";
+ const PAGE_OPENSEARCH_QUERY = "9";
+ const PAGE_ALL_RECENT_BOOKS = "10";
+ const PAGE_ALL_TAGS = "11";
+ const PAGE_TAG_DETAIL = "12";
+ const PAGE_BOOK_DETAIL = "13";
+ const PAGE_ALL_CUSTOMS = "14";
+ const PAGE_CUSTOM_DETAIL = "15";
+ const PAGE_ABOUT = "16";
+ const PAGE_ALL_LANGUAGES = "17";
+ const PAGE_LANGUAGE_DETAIL = "18";
+ const PAGE_CUSTOMIZE = "19";
+ const PAGE_ALL_PUBLISHERS = "20";
+ const PAGE_PUBLISHER_DETAIL = "21";
+ const PAGE_ALL_RATINGS = "22";
+ const PAGE_RATING_DETAIL = "23";
+
+ const COMPATIBILITY_XML_ALDIKO = "aldiko";
+
+ private static $db = NULL;
+
+ public static function isMultipleDatabaseEnabled () {
+ global $config;
+ return is_array ($config['calibre_directory']);
+ }
+
+ public static function useAbsolutePath () {
+ global $config;
+ $path = self::getDbDirectory();
+ return preg_match ('/^\//', $path) || // Linux /
+ preg_match ('/^\w\:/', $path); // Windows X:
+ }
+
+ public static function noDatabaseSelected () {
+ return self::isMultipleDatabaseEnabled () && is_null (GetUrlParam (DB));
+ }
+
+ public static function getDbList () {
+ global $config;
+ if (self::isMultipleDatabaseEnabled ()) {
+ return $config['calibre_directory'];
+ } else {
+ return array ("" => $config['calibre_directory']);
+ }
+ }
+
+ public static function getDbNameList () {
+ global $config;
+ if (self::isMultipleDatabaseEnabled ()) {
+ return array_keys ($config['calibre_directory']);
+ } else {
+ return array ("");
+ }
+ }
+
+ public static function getDbName ($database = NULL) {
+ global $config;
+ if (self::isMultipleDatabaseEnabled ()) {
+ if (is_null ($database)) $database = GetUrlParam (DB, 0);
+ if (!is_null($database) && !preg_match('/^\d+$/', $database)) {
+ self::error ($database);
+ }
+ $array = array_keys ($config['calibre_directory']);
+ return $array[$database];
+ }
+ return "";
+ }
+
+ public static function getDbDirectory ($database = NULL) {
+ global $config;
+ if (self::isMultipleDatabaseEnabled ()) {
+ if (is_null ($database)) $database = GetUrlParam (DB, 0);
+ if (!is_null($database) && !preg_match('/^\d+$/', $database)) {
+ self::error ($database);
+ }
+ $array = array_values ($config['calibre_directory']);
+ return $array[$database];
+ }
+ return $config['calibre_directory'];
+ }
+
+
+ public static function getDbFileName ($database = NULL) {
+ return self::getDbDirectory ($database) .'metadata.db';
+ }
+
+ private static function error ($database) {
+ if (php_sapi_name() != "cli") {
+ header("location: checkconfig.php?err=1");
+ }
+ throw new Exception("Database <{$database}> not found.");
+ }
+
+ public static function getDb ($database = NULL) {
+ if (is_null (self::$db)) {
+ try {
+ if (is_readable (self::getDbFileName ($database))) {
+ self::$db = new PDO('sqlite:'. self::getDbFileName ($database));
+ if (useNormAndUp ()) {
+ self::$db->sqliteCreateFunction ('normAndUp', 'normAndUp', 1);
+ }
+ } else {
+ self::error ($database);
+ }
+ } catch (Exception $e) {
+ self::error ($database);
+ }
+ }
+ return self::$db;
+ }
+
+ public static function checkDatabaseAvailability () {
+ if (self::noDatabaseSelected ()) {
+ for ($i = 0; $i < count (self::getDbList ()); $i++) {
+ self::getDb ($i);
+ self::clearDb ();
+ }
+ } else {
+ self::getDb ();
+ }
+ return true;
+ }
+
+ public static function clearDb () {
+ self::$db = NULL;
+ }
+
+ public static function executeQuerySingle ($query, $database = NULL) {
+ return self::getDb ($database)->query($query)->fetchColumn();
+ }
+
+ public static function getCountGeneric($table, $id, $pageId, $numberOfString = NULL) {
+ if (!$numberOfString) {
+ $numberOfString = $table . ".alphabetical";
+ }
+ $count = self::executeQuerySingle ('select count(*) from ' . $table);
+ if ($count == 0) return NULL;
+ $entry = new Entry (localize($table . ".title"), $id,
+ str_format (localize($numberOfString, $count), $count), "text",
+ array ( new LinkNavigation ("?page=".$pageId)), "", $count);
+ return $entry;
+ }
+
+ public static function getEntryArrayWithBookNumber ($query, $columns, $params, $category) {
+ /* @var $result PDOStatement */
+
+ list (, $result) = self::executeQuery ($query, $columns, "", $params, -1);
+ $entryArray = array();
+ while ($post = $result->fetchObject ())
+ {
+ /* @var $instance Author|Tag|Serie|Publisher */
+
+ $instance = new $category ($post);
+ if (property_exists($post, "sort")) {
+ $title = $post->sort;
+ } else {
+ $title = $post->name;
+ }
+ array_push ($entryArray, new Entry ($title, $instance->getEntryId (),
+ str_format (localize("bookword", $post->count), $post->count), "text",
+ array ( new LinkNavigation ($instance->getUri ())), "", $post->count));
+ }
+ return $entryArray;
+ }
+
+ public static function executeQuery($query, $columns, $filter, $params, $n, $database = NULL, $numberPerPage = NULL) {
+ $totalResult = -1;
+
+ if (useNormAndUp ()) {
+ $query = preg_replace("/upper/", "normAndUp", $query);
+ $columns = preg_replace("/upper/", "normAndUp", $columns);
+ }
+
+ if (is_null ($numberPerPage)) {
+ $numberPerPage = getCurrentOption ("max_item_per_page");
+ }
+
+ if ($numberPerPage != -1 && $n != -1)
+ {
+ // First check total number of results
+ $result = self::getDb ($database)->prepare (str_format ($query, "count(*)", $filter));
+ $result->execute ($params);
+ $totalResult = $result->fetchColumn ();
+
+ // Next modify the query and params
+ $query .= " limit ?, ?";
+ array_push ($params, ($n - 1) * $numberPerPage, $numberPerPage);
+ }
+
+ $result = self::getDb ($database)->prepare(str_format ($query, $columns, $filter));
+ $result->execute ($params);
+ return array ($totalResult, $result);
+ }
+
+}
diff --git a/sources/lib/Book.php b/sources/lib/Book.php
new file mode 100644
index 0000000..66854e7
--- /dev/null
+++ b/sources/lib/Book.php
@@ -0,0 +1,651 @@
+
+ */
+
+// Silly thing because PHP forbid string concatenation in class const
+define ('SQL_BOOKS_LEFT_JOIN', 'left outer join comments on comments.book = books.id
+ left outer join books_ratings_link on books_ratings_link.book = books.id
+ left outer join ratings on books_ratings_link.rating = ratings.id ');
+define ('SQL_BOOKS_ALL', 'select {0} from books ' . SQL_BOOKS_LEFT_JOIN . ' order by books.sort ');
+define ('SQL_BOOKS_BY_PUBLISHER', 'select {0} from books_publishers_link, books ' . SQL_BOOKS_LEFT_JOIN . '
+ where books_publishers_link.book = books.id and publisher = ? {1} order by publisher');
+define ('SQL_BOOKS_BY_FIRST_LETTER', 'select {0} from books ' . SQL_BOOKS_LEFT_JOIN . '
+ where upper (books.sort) like ? order by books.sort');
+define ('SQL_BOOKS_BY_AUTHOR', 'select {0} from books_authors_link, books ' . SQL_BOOKS_LEFT_JOIN . '
+ left outer join books_series_link on books_series_link.book = books.id
+ where books_authors_link.book = books.id and author = ? {1} order by series desc, series_index asc, pubdate asc');
+define ('SQL_BOOKS_BY_SERIE', 'select {0} from books_series_link, books ' . SQL_BOOKS_LEFT_JOIN . '
+ where books_series_link.book = books.id and series = ? {1} order by series_index');
+define ('SQL_BOOKS_BY_TAG', 'select {0} from books_tags_link, books ' . SQL_BOOKS_LEFT_JOIN . '
+ where books_tags_link.book = books.id and tag = ? {1} order by sort');
+define ('SQL_BOOKS_BY_LANGUAGE', 'select {0} from books_languages_link, books ' . SQL_BOOKS_LEFT_JOIN . '
+ where books_languages_link.book = books.id and lang_code = ? {1} order by sort');
+define ('SQL_BOOKS_BY_CUSTOM', 'select {0} from {2}, books ' . SQL_BOOKS_LEFT_JOIN . '
+ where {2}.book = books.id and {2}.{3} = ? {1} order by sort');
+define ('SQL_BOOKS_BY_CUSTOM_BOOL_TRUE', 'select {0} from {2}, books ' . SQL_BOOKS_LEFT_JOIN . '
+ where {2}.book = books.id and {2}.value = 1 {1} order by sort');
+define ('SQL_BOOKS_BY_CUSTOM_BOOL_FALSE', 'select {0} from {2}, books ' . SQL_BOOKS_LEFT_JOIN . '
+ where {2}.book = books.id and {2}.value = 0 {1} order by sort');
+define ('SQL_BOOKS_BY_CUSTOM_BOOL_NULL', 'select {0} from books ' . SQL_BOOKS_LEFT_JOIN . '
+ where books.id not in (select book from {2}) {1} order by sort');
+define ('SQL_BOOKS_BY_CUSTOM_RATING', 'select {0} from books ' . SQL_BOOKS_LEFT_JOIN . '
+ left join {2} on {2}.book = books.id
+ left join {3} on {3}.id = {2}.{4}
+ where {3}.value = ? order by sort');
+define ('SQL_BOOKS_BY_CUSTOM_RATING_NULL', 'select {0} from books ' . SQL_BOOKS_LEFT_JOIN . '
+ left join {2} on {2}.book = books.id
+ left join {3} on {3}.id = {2}.{4}
+ where ((books.id not in (select {2}.book from {2})) or ({3}.value = 0)) {1} order by sort');
+define ('SQL_BOOKS_BY_CUSTOM_DATE', 'select {0} from {2}, books ' . SQL_BOOKS_LEFT_JOIN . '
+ where {2}.book = books.id and date({2}.value) = ? {1} order by sort');
+define ('SQL_BOOKS_BY_CUSTOM_DIRECT', 'select {0} from {2}, books ' . SQL_BOOKS_LEFT_JOIN . '
+ where {2}.book = books.id and {2}.value = ? {1} order by sort');
+define ('SQL_BOOKS_BY_CUSTOM_DIRECT_ID', 'select {0} from {2}, books ' . SQL_BOOKS_LEFT_JOIN . '
+ where {2}.book = books.id and {2}.id = ? {1} order by sort');
+define ('SQL_BOOKS_QUERY', 'select {0} from books ' . SQL_BOOKS_LEFT_JOIN . '
+ where (
+ exists (select null from authors, books_authors_link where book = books.id and author = authors.id and authors.name like ?) or
+ exists (select null from tags, books_tags_link where book = books.id and tag = tags.id and tags.name like ?) or
+ exists (select null from series, books_series_link on book = books.id and books_series_link.series = series.id and series.name like ?) or
+ exists (select null from publishers, books_publishers_link where book = books.id and books_publishers_link.publisher = publishers.id and publishers.name like ?) or
+ title like ?) {1} order by books.sort');
+define ('SQL_BOOKS_RECENT', 'select {0} from books ' . SQL_BOOKS_LEFT_JOIN . '
+ where 1=1 {1} order by timestamp desc limit ');
+define ('SQL_BOOKS_BY_RATING', 'select {0} from books ' . SQL_BOOKS_LEFT_JOIN . '
+ where books_ratings_link.book = books.id and ratings.id = ? {1} order by sort');
+
+class Book extends Base
+{
+ const ALL_BOOKS_UUID = 'urn:uuid';
+ const ALL_BOOKS_ID = 'cops:books';
+ const ALL_RECENT_BOOKS_ID = 'cops:recentbooks';
+ const BOOK_COLUMNS = 'books.id as id, books.title as title, text as comment, path, timestamp, pubdate, series_index, uuid, has_cover, ratings.rating';
+
+ const SQL_BOOKS_LEFT_JOIN = SQL_BOOKS_LEFT_JOIN;
+ const SQL_BOOKS_ALL = SQL_BOOKS_ALL;
+ const SQL_BOOKS_BY_PUBLISHER = SQL_BOOKS_BY_PUBLISHER;
+ const SQL_BOOKS_BY_FIRST_LETTER = SQL_BOOKS_BY_FIRST_LETTER;
+ const SQL_BOOKS_BY_AUTHOR = SQL_BOOKS_BY_AUTHOR;
+ const SQL_BOOKS_BY_SERIE = SQL_BOOKS_BY_SERIE;
+ const SQL_BOOKS_BY_TAG = SQL_BOOKS_BY_TAG;
+ const SQL_BOOKS_BY_LANGUAGE = SQL_BOOKS_BY_LANGUAGE;
+ const SQL_BOOKS_BY_CUSTOM = SQL_BOOKS_BY_CUSTOM;
+ const SQL_BOOKS_BY_CUSTOM_BOOL_TRUE = SQL_BOOKS_BY_CUSTOM_BOOL_TRUE;
+ const SQL_BOOKS_BY_CUSTOM_BOOL_FALSE = SQL_BOOKS_BY_CUSTOM_BOOL_FALSE;
+ const SQL_BOOKS_BY_CUSTOM_BOOL_NULL = SQL_BOOKS_BY_CUSTOM_BOOL_NULL;
+ const SQL_BOOKS_BY_CUSTOM_RATING = SQL_BOOKS_BY_CUSTOM_RATING;
+ const SQL_BOOKS_BY_CUSTOM_RATING_NULL = SQL_BOOKS_BY_CUSTOM_RATING_NULL;
+ const SQL_BOOKS_BY_CUSTOM_DATE = SQL_BOOKS_BY_CUSTOM_DATE;
+ const SQL_BOOKS_BY_CUSTOM_DIRECT = SQL_BOOKS_BY_CUSTOM_DIRECT;
+ const SQL_BOOKS_BY_CUSTOM_DIRECT_ID = SQL_BOOKS_BY_CUSTOM_DIRECT_ID;
+ const SQL_BOOKS_QUERY = SQL_BOOKS_QUERY;
+ const SQL_BOOKS_RECENT = SQL_BOOKS_RECENT;
+ const SQL_BOOKS_BY_RATING = SQL_BOOKS_BY_RATING;
+
+ const BAD_SEARCH = 'QQQQQ';
+
+ public $id;
+ public $title;
+ public $timestamp;
+ public $pubdate;
+ public $path;
+ public $uuid;
+ public $hasCover;
+ public $relativePath;
+ public $seriesIndex;
+ public $comment;
+ public $rating;
+ public $datas = NULL;
+ public $authors = NULL;
+ public $publisher = NULL;
+ public $serie = NULL;
+ public $tags = NULL;
+ public $languages = NULL;
+ public $format = array ();
+
+
+ public function __construct($line) {
+ $this->id = $line->id;
+ $this->title = $line->title;
+ $this->timestamp = strtotime($line->timestamp);
+ $this->pubdate = $line->pubdate;
+ $this->path = Base::getDbDirectory() . $line->path;
+ $this->relativePath = $line->path;
+ $this->seriesIndex = $line->series_index;
+ $this->comment = $line->comment;
+ $this->uuid = $line->uuid;
+ $this->hasCover = $line->has_cover;
+ if (!file_exists($this->getFilePath('jpg'))) {
+ // double check
+ $this->hasCover = 0;
+ }
+ $this->rating = $line->rating;
+ }
+
+ public function getEntryId() {
+ return self::ALL_BOOKS_UUID.':'.$this->uuid;
+ }
+
+ public static function getEntryIdByLetter ($startingLetter) {
+ return self::ALL_BOOKS_ID.':letter:'.$startingLetter;
+ }
+
+ public function getUri () {
+ return '?page='.parent::PAGE_BOOK_DETAIL.'&id=' . $this->id;
+ }
+
+ public function getDetailUrl () {
+ $urlParam = $this->getUri();
+ if (!is_null(GetUrlParam(DB))) $urlParam = addURLParameter($urlParam, DB, GetUrlParam (DB));
+ return 'index.php' . $urlParam;
+ }
+
+ public function getTitle () {
+ return $this->title;
+ }
+
+ /* Other class (author, series, tag, ...) initialization and accessors */
+
+ /**
+ * @return Author[]
+ */
+ public function getAuthors () {
+ if (is_null($this->authors)) {
+ $this->authors = Author::getAuthorByBookId($this->id);
+ }
+ return $this->authors;
+ }
+
+ public function getAuthorsName () {
+ return implode(', ', array_map(function ($author) { return $author->name; }, $this->getAuthors()));
+ }
+
+ public function getAuthorsSort () {
+ return implode(', ', array_map(function ($author) { return $author->sort; }, $this->getAuthors()));
+ }
+
+ public function getPublisher () {
+ if (is_null($this->publisher)) {
+ $this->publisher = Publisher::getPublisherByBookId($this->id);
+ }
+ return $this->publisher;
+ }
+
+ /**
+ * @return Serie
+ */
+ public function getSerie() {
+ if (is_null($this->serie)) {
+ $this->serie = Serie::getSerieByBookId($this->id);
+ }
+ return $this->serie;
+ }
+
+ /**
+ * @return string
+ */
+ public function getLanguages() {
+ $lang = array();
+ $result = parent::getDb()->prepare('select languages.lang_code
+ from books_languages_link, languages
+ where books_languages_link.lang_code = languages.id
+ and book = ?
+ order by item_order');
+ $result->execute(array($this->id));
+ while ($post = $result->fetchObject())
+ {
+ array_push($lang, Language::getLanguageString($post->lang_code));
+ }
+ return implode(', ', $lang);
+ }
+
+ /**
+ * @return Tag[]
+ */
+ public function getTags() {
+ if (is_null ($this->tags)) {
+ $this->tags = array();
+
+ $result = parent::getDb()->prepare('select tags.id as id, name
+ from books_tags_link, tags
+ where tag = tags.id
+ and book = ?
+ order by name');
+ $result->execute(array($this->id));
+ while ($post = $result->fetchObject())
+ {
+ array_push($this->tags, new Tag($post));
+ }
+ }
+ return $this->tags;
+ }
+
+ public function getTagsName() {
+ return implode(', ', array_map(function ($tag) { return $tag->name; }, $this->getTags()));
+ }
+
+ /**
+ * @return Data[]
+ */
+ public function getDatas()
+ {
+ if (is_null($this->datas)) {
+ $this->datas = Data::getDataByBook($this);
+ }
+ return $this->datas;
+ }
+
+ /* End of other class (author, series, tag, ...) initialization and accessors */
+
+ public static function getFilterString() {
+ $filter = getURLParam('tag', NULL);
+ if (empty($filter)) return '';
+
+ $exists = true;
+ if (preg_match("/^!(.*)$/", $filter, $matches)) {
+ $exists = false;
+ $filter = $matches[1];
+ }
+
+ $result = 'exists (select null from books_tags_link, tags where books_tags_link.book = books.id and books_tags_link.tag = tags.id and tags.name = "' . $filter . '")';
+
+ if (!$exists) {
+ $result = 'not ' . $result;
+ }
+
+ return 'and ' . $result;
+ }
+
+ public function GetMostInterestingDataToSendToKindle()
+ {
+ $bestFormatForKindle = array('EPUB', 'PDF', 'AZW3', 'MOBI');
+ $bestRank = -1;
+ $bestData = NULL;
+ foreach ($this->getDatas() as $data) {
+ $key = array_search($data->format, $bestFormatForKindle);
+ if ($key !== false && $key > $bestRank) {
+ $bestRank = $key;
+ $bestData = $data;
+ }
+ }
+ return $bestData;
+ }
+
+ public function getDataById($idData)
+ {
+ $reduced = array_filter($this->getDatas(), function ($data) use ($idData) {
+ return $data->id == $idData;
+ });
+ return reset($reduced);
+ }
+
+ public function getRating() {
+ if (is_null($this->rating) || $this->rating == 0) {
+ return '';
+ }
+ $retour = '';
+ for ($i = 0; $i < $this->rating / 2; $i++) {
+ $retour .= '★';
+ }
+ for ($i = 0; $i < 5 - $this->rating / 2; $i++) {
+ $retour .= '☆';
+ }
+ return $retour;
+ }
+
+ public function getPubDate() {
+ if (empty ($this->pubdate)) {
+ return '';
+ }
+ $dateY = (int) substr($this->pubdate, 0, 4);
+ if ($dateY > 102) {
+ return str_pad($dateY, 4, '0', STR_PAD_LEFT);
+ }
+ return '';
+ }
+
+ public function getComment($withSerie = true) {
+ $addition = '';
+ $se = $this->getSerie ();
+ if (!is_null ($se) && $withSerie) {
+ $addition = $addition . '' . localize('content.series') . ' ' . str_format(localize('content.series.data'), $this->seriesIndex, htmlspecialchars($se->name)) . " \n";
+ }
+ if (preg_match('/<\/(div|p|a|span)>/', $this->comment))
+ {
+ return $addition . html2xhtml($this->comment);
+ }
+ else
+ {
+ return $addition . htmlspecialchars($this->comment);
+ }
+ }
+
+ public function getDataFormat($format) {
+ $reduced = array_filter($this->getDatas(), function ($data) use ($format) {
+ return $data->format == $format;
+ });
+ return reset($reduced);
+ }
+
+ public function getFilePath($extension, $idData = NULL, $relative = false)
+ {
+ if ($extension == 'jpg')
+ {
+ $file = 'cover.jpg';
+ }
+ else
+ {
+ $data = $this->getDataById($idData);
+ if (!$data) return NULL;
+ $file = $data->name . '.' . strtolower($data->format);
+ }
+
+ if ($relative)
+ {
+ return $this->relativePath.'/'.$file;
+ }
+ else
+ {
+ return $this->path.'/'.$file;
+ }
+ }
+
+ public function getUpdatedEpub($idData)
+ {
+ global $config;
+ $data = $this->getDataById($idData);
+
+ try
+ {
+ $epub = new EPub($data->getLocalPath());
+
+ $epub->Title($this->title);
+ $authorArray = array();
+ foreach ($this->getAuthors() as $author) {
+ $authorArray[$author->sort] = $author->name;
+ }
+ $epub->Authors($authorArray);
+ $epub->Language($this->getLanguages());
+ $epub->Description ($this->getComment(false));
+ $epub->Subjects($this->getTagsName());
+ $epub->Cover2($this->getFilePath('jpg'), 'image/jpeg');
+ $epub->Calibre($this->uuid);
+ $se = $this->getSerie();
+ if (!is_null($se)) {
+ $epub->Serie($se->name);
+ $epub->SerieIndex($this->seriesIndex);
+ }
+ if ($config['cops_provide_kepub'] == '1' && preg_match('/Kobo/', $_SERVER['HTTP_USER_AGENT'])) {
+ $epub->updateForKepub();
+ }
+ $epub->download($data->getUpdatedFilenameEpub());
+ }
+ catch (Exception $e)
+ {
+ echo 'Exception : ' . $e->getMessage();
+ }
+ }
+
+ public function getThumbnail($width, $height, $outputfile = NULL) {
+ if (is_null($width) && is_null($height)) {
+ return false;
+ }
+
+ $file = $this->getFilePath('jpg');
+ // get image size
+ if ($size = GetImageSize($file)) {
+ $w = $size[0];
+ $h = $size[1];
+ //set new size
+ if (!is_null($width)) {
+ $nw = $width;
+ if ($nw >= $w) { return false; }
+ $nh = ($nw*$h)/$w;
+ } else {
+ $nh = $height;
+ if ($nh >= $h) { return false; }
+ $nw = ($nh*$w)/$h;
+ }
+ } else {
+ return false;
+ }
+
+ //draw the image
+ $src_img = imagecreatefromjpeg($file);
+ $dst_img = imagecreatetruecolor($nw,$nh);
+ imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $nw, $nh, $w, $h);//resizing the image
+ imagejpeg($dst_img,$outputfile,80);
+ imagedestroy($src_img);
+ imagedestroy($dst_img);
+
+ return true;
+ }
+
+ public function getLinkArray ()
+ {
+ $linkArray = array();
+
+ if ($this->hasCover)
+ {
+ array_push($linkArray, Data::getLink($this, 'jpg', 'image/jpeg', Link::OPDS_IMAGE_TYPE, 'cover.jpg', NULL));
+
+ array_push($linkArray, Data::getLink($this, 'jpg', 'image/jpeg', Link::OPDS_THUMBNAIL_TYPE, 'cover.jpg', NULL));
+ }
+
+ foreach ($this->getDatas() as $data)
+ {
+ if ($data->isKnownType())
+ {
+ array_push($linkArray, $data->getDataLink(Link::OPDS_ACQUISITION_TYPE, $data->format));
+ }
+ }
+
+ foreach ($this->getAuthors() as $author) {
+ /* @var $author Author */
+ array_push($linkArray, new LinkNavigation($author->getUri(), 'related', str_format(localize('bookentry.author'), localize('splitByLetter.book.other'), $author->name)));
+ }
+
+ $serie = $this->getSerie();
+ if (!is_null ($serie)) {
+ array_push($linkArray, new LinkNavigation($serie->getUri(), 'related', str_format(localize('content.series.data'), $this->seriesIndex, $serie->name)));
+ }
+
+ return $linkArray;
+ }
+
+
+ public function getEntry() {
+ return new EntryBook($this->getTitle(), $this->getEntryId(),
+ $this->getComment(), 'text/html',
+ $this->getLinkArray(), $this);
+ }
+
+ public static function getBookCount($database = NULL) {
+ return parent::executeQuerySingle('select count(*) from books', $database);
+ }
+
+ public static function getCount() {
+ global $config;
+ $nBooks = parent::executeQuerySingle('select count(*) from books');
+ $result = array();
+ $entry = new Entry(localize('allbooks.title'),
+ self::ALL_BOOKS_ID,
+ str_format(localize('allbooks.alphabetical', $nBooks), $nBooks), 'text',
+ array(new LinkNavigation('?page='.parent::PAGE_ALL_BOOKS)), '', $nBooks);
+ array_push($result, $entry);
+ if ($config['cops_recentbooks_limit'] > 0) {
+ $entry = new Entry(localize('recent.title'),
+ self::ALL_RECENT_BOOKS_ID,
+ str_format(localize('recent.list'), $config['cops_recentbooks_limit']), 'text',
+ array ( new LinkNavigation ('?page='.parent::PAGE_ALL_RECENT_BOOKS)), '', $config['cops_recentbooks_limit']);
+ array_push($result, $entry);
+ }
+ return $result;
+ }
+
+ public static function getBooksByAuthor($authorId, $n) {
+ return self::getEntryArray(self::SQL_BOOKS_BY_AUTHOR, array($authorId), $n);
+ }
+
+ public static function getBooksByRating($ratingId, $n) {
+ return self::getEntryArray(self::SQL_BOOKS_BY_RATING, array($ratingId), $n);
+ }
+
+ public static function getBooksByPublisher($publisherId, $n) {
+ return self::getEntryArray(self::SQL_BOOKS_BY_PUBLISHER, array($publisherId), $n);
+ }
+
+ public static function getBooksBySeries($serieId, $n) {
+ return self::getEntryArray(self::SQL_BOOKS_BY_SERIE, array($serieId), $n);
+ }
+
+ public static function getBooksByTag($tagId, $n) {
+ return self::getEntryArray(self::SQL_BOOKS_BY_TAG, array($tagId), $n);
+ }
+
+ public static function getBooksByLanguage($languageId, $n) {
+ return self::getEntryArray(self::SQL_BOOKS_BY_LANGUAGE, array($languageId), $n);
+ }
+
+ /**
+ * @param $customColumn CustomColumn
+ * @param $id integer
+ * @param $n integer
+ * @return array
+ */
+ public static function getBooksByCustom($customColumn, $id, $n) {
+ list($query, $params) = $customColumn->getQuery($id);
+
+ return self::getEntryArray($query, $params, $n);
+ }
+
+ public static function getBookById($bookId) {
+ $result = parent::getDb()->prepare('select ' . self::BOOK_COLUMNS . '
+from books ' . self::SQL_BOOKS_LEFT_JOIN . '
+where books.id = ?');
+ $result->execute(array($bookId));
+ while ($post = $result->fetchObject())
+ {
+ $book = new Book($post);
+ return $book;
+ }
+ return NULL;
+ }
+
+ public static function getBookByDataId($dataId) {
+ $result = parent::getDb()->prepare('select ' . self::BOOK_COLUMNS . ', data.name, data.format
+from data, books ' . self::SQL_BOOKS_LEFT_JOIN . '
+where data.book = books.id and data.id = ?');
+ $result->execute(array($dataId));
+ while ($post = $result->fetchObject())
+ {
+ $book = new Book($post);
+ $data = new Data($post, $book);
+ $data->id = $dataId;
+ $book->datas = array($data);
+ return $book;
+ }
+ return NULL;
+ }
+
+ public static function getBooksByQuery($query, $n, $database = NULL, $numberPerPage = NULL) {
+ $i = 0;
+ $critArray = array();
+ foreach (array(PageQueryResult::SCOPE_AUTHOR,
+ PageQueryResult::SCOPE_TAG,
+ PageQueryResult::SCOPE_SERIES,
+ PageQueryResult::SCOPE_PUBLISHER,
+ PageQueryResult::SCOPE_BOOK) as $key) {
+ if (in_array($key, getCurrentOption('ignored_categories')) ||
+ (!array_key_exists($key, $query) && !array_key_exists('all', $query))) {
+ $critArray[$i] = self::BAD_SEARCH;
+ }
+ else {
+ if (array_key_exists($key, $query)) {
+ $critArray[$i] = $query[$key];
+ } else {
+ $critArray[$i] = $query["all"];
+ }
+ }
+ $i++;
+ }
+ return self::getEntryArray(self::SQL_BOOKS_QUERY, $critArray, $n, $database, $numberPerPage);
+ }
+
+ public static function getBooks($n) {
+ list ($entryArray, $totalNumber) = self::getEntryArray(self::SQL_BOOKS_ALL , array (), $n);
+ return array($entryArray, $totalNumber);
+ }
+
+ public static function getAllBooks() {
+ /* @var $result PDOStatement */
+
+ list (, $result) = parent::executeQuery('select {0}
+from books
+group by substr (upper (sort), 1, 1)
+order by substr (upper (sort), 1, 1)', 'substr (upper (sort), 1, 1) as title, count(*) as count', self::getFilterString(), array(), -1);
+
+ $entryArray = array();
+ while ($post = $result->fetchObject())
+ {
+ array_push($entryArray, new Entry($post->title, Book::getEntryIdByLetter($post->title),
+ str_format(localize('bookword', $post->count), $post->count), 'text',
+ array(new LinkNavigation('?page='.parent::PAGE_ALL_BOOKS_LETTER.'&id='. rawurlencode($post->title))), '', $post->count));
+ }
+ return $entryArray;
+ }
+
+ public static function getBooksByStartingLetter($letter, $n, $database = NULL, $numberPerPage = NULL) {
+ return self::getEntryArray(self::SQL_BOOKS_BY_FIRST_LETTER, array($letter . '%'), $n, $database, $numberPerPage);
+ }
+
+ public static function getEntryArray($query, $params, $n, $database = NULL, $numberPerPage = NULL) {
+ /* @var $totalNumber integer */
+ /* @var $result PDOStatement */
+ list($totalNumber, $result) = parent::executeQuery($query, self::BOOK_COLUMNS, self::getFilterString(), $params, $n, $database, $numberPerPage);
+
+ $entryArray = array();
+ while ($post = $result->fetchObject())
+ {
+ $book = new Book($post);
+ array_push($entryArray, $book->getEntry());
+ }
+ return array($entryArray, $totalNumber);
+ }
+
+ public static function getAllRecentBooks() {
+ global $config;
+ list ($entryArray, ) = self::getEntryArray(self::SQL_BOOKS_RECENT . $config['cops_recentbooks_limit'], array(), -1);
+ return $entryArray;
+ }
+
+ /**
+ * The values of all the specified columns
+ *
+ * @param string[] $columns
+ * @return CustomColumn[]
+ */
+ public function getCustomColumnValues($columns, $asArray = false) {
+ $result = array();
+
+ foreach ($columns as $lookup) {
+ $col = CustomColumnType::createByLookup($lookup);
+ if (!is_null($col)) {
+ $cust = $col->getCustomByBook($this);
+ if (!is_null($cust)) {
+ if ($asArray) {
+ array_push($result, $cust->toArray());
+ } else {
+ array_push($result, $cust);
+ }
+ }
+ }
+ }
+
+ return $result;
+ }
+}
diff --git a/sources/lib/CustomColumn.php b/sources/lib/CustomColumn.php
new file mode 100644
index 0000000..195bdfd
--- /dev/null
+++ b/sources/lib/CustomColumn.php
@@ -0,0 +1,109 @@
+
+ */
+
+/**
+ * A CustomColumn with an value
+ */
+class CustomColumn extends Base
+{
+ /* @var string|integer the ID of the value */
+ public $valueID;
+ /* @var string the (string) representation of the value */
+ public $value;
+ /* @var CustomColumnType the custom column that contains the value */
+ public $customColumnType;
+ /* @var string the value encoded for HTML displaying */
+ public $htmlvalue;
+
+ /**
+ * CustomColumn constructor.
+ *
+ * @param integer $pid id of the chosen value
+ * @param string $pvalue string representation of the value
+ * @param CustomColumnType $pcustomColumnType the CustomColumn this value lives in
+ */
+ public function __construct($pid, $pvalue, $pcustomColumnType)
+ {
+ $this->valueID = $pid;
+ $this->value = $pvalue;
+ $this->customColumnType = $pcustomColumnType;
+ $this->htmlvalue = $this->customColumnType->encodeHTMLValue($this->value);
+ }
+
+ /**
+ * Get the URI to show all books with this value
+ *
+ * @return string
+ */
+ public function getUri()
+ {
+ return $this->customColumnType->getUri($this->valueID);
+ }
+
+ /**
+ * Get the EntryID to show all books with this value
+ *
+ * @return string
+ */
+ public function getEntryId()
+ {
+ return $this->customColumnType->getEntryId($this->valueID);
+ }
+
+ /**
+ * Get the query to find all books with this value
+ * the returning array has two values:
+ * - first the query (string)
+ * - second an array of all PreparedStatement parameters
+ *
+ * @return array
+ */
+ public function getQuery()
+ {
+ return $this->customColumnType->getQuery($this->valueID);
+ }
+
+ /**
+ * Return the value of this column as an HTML snippet
+ *
+ * @return string
+ */
+ public function getHTMLEncodedValue()
+ {
+ return $this->htmlvalue;
+ }
+
+ /**
+ * Create an CustomColumn by CustomColumnID and ValueID
+ *
+ * @param integer $customId the id of the customColumn
+ * @param integer $id the id of the chosen value
+ * @return CustomColumn|null
+ */
+ public static function createCustom($customId, $id)
+ {
+ $columnType = CustomColumnType::createByCustomID($customId);
+
+ return $columnType->getCustom($id);
+ }
+
+ /**
+ * Return this object as an array
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ return array(
+ 'valueID' => $this->valueID,
+ 'value' => $this->value,
+ 'customColumnType' => (array)$this->customColumnType,
+ 'htmlvalue' => $this->htmlvalue,
+ );
+ }
+}
diff --git a/sources/lib/CustomColumnType.php b/sources/lib/CustomColumnType.php
new file mode 100644
index 0000000..dda3819
--- /dev/null
+++ b/sources/lib/CustomColumnType.php
@@ -0,0 +1,314 @@
+
+ */
+
+/**
+ * A single calibre custom column
+ */
+abstract class CustomColumnType extends Base
+{
+ const ALL_CUSTOMS_ID = "cops:custom";
+
+ const CUSTOM_TYPE_TEXT = "text"; // type 1 + 2
+ const CUSTOM_TYPE_COMMENT = "comments"; // type 3
+ const CUSTOM_TYPE_SERIES = "series"; // type 4
+ const CUSTOM_TYPE_ENUM = "enumeration"; // type 5
+ const CUSTOM_TYPE_DATE = "datetime"; // type 6
+ const CUSTOM_TYPE_FLOAT = "float"; // type 7
+ const CUSTOM_TYPE_INT = "int"; // type 8
+ const CUSTOM_TYPE_RATING = "rating"; // type 9
+ const CUSTOM_TYPE_BOOL = "bool"; // type 10
+ const CUSTOM_TYPE_COMPOSITE = "composite"; // type 11 + 12
+
+ /** @var array[integer]CustomColumnType */
+ private static $customColumnCacheID = array();
+
+ /** @var array[string]CustomColumnType */
+ private static $customColumnCacheLookup = array();
+
+ /** @var integer the id of this column */
+ public $customId;
+ /** @var string name/title of this column */
+ public $columnTitle;
+ /** @var string the datatype of this column (one of the CUSTOM_TYPE_* constant values) */
+ public $datatype;
+ /** @var null|Entry[] */
+ private $customValues = NULL;
+
+ protected function __construct($pcustomId, $pdatatype)
+ {
+ $this->columnTitle = self::getTitleByCustomID($pcustomId);
+ $this->customId = $pcustomId;
+ $this->datatype = $pdatatype;
+ $this->customValues = NULL;
+ }
+
+ /**
+ * The URI to show all book swith a specific value in this column
+ *
+ * @param string|integer $id the id of the value to show
+ * @return string
+ */
+ public function getUri($id)
+ {
+ return "?page=" . parent::PAGE_CUSTOM_DETAIL . "&custom={$this->customId}&id={$id}";
+ }
+
+ /**
+ * The URI to show all the values of this column
+ *
+ * @return string
+ */
+ public function getUriAllCustoms()
+ {
+ return "?page=" . parent::PAGE_ALL_CUSTOMS . "&custom={$this->customId}";
+ }
+
+ /**
+ * The EntryID to show all book swith a specific value in this column
+ *
+ * @param string|integer $id the id of the value to show
+ * @return string
+ */
+ public function getEntryId($id)
+ {
+ return self::ALL_CUSTOMS_ID . ":" . $this->customId . ":" . $id;
+ }
+
+ /**
+ * The EntryID to show all the values of this column
+ *
+ * @return string
+ */
+ public function getAllCustomsId()
+ {
+ return self::ALL_CUSTOMS_ID . ":" . $this->customId;
+ }
+
+ /**
+ * The title of this column
+ *
+ * @return string
+ */
+ public function getTitle()
+ {
+ return $this->columnTitle;
+ }
+
+ /**
+ * The description of this column as it is definied in the database
+ *
+ * @return string|null
+ */
+ public function getDatabaseDescription()
+ {
+ $result = $this->getDb()->prepare('SELECT display FROM custom_columns WHERE id = ?');
+ $result->execute(array($this->customId));
+ if ($post = $result->fetchObject()) {
+ $json = json_decode($post->display);
+ return (isset($json->description) && !empty($json->description)) ? $json->description : NULL;
+ }
+ return NULL;
+ }
+
+ /**
+ * Get the Entry for this column
+ * This is used in the initializeContent method to display e.g. the index page
+ *
+ * @return Entry
+ */
+ public function getCount()
+ {
+ $ptitle = $this->getTitle();
+ $pid = $this->getAllCustomsId();
+ $pcontent = $this->getDescription();
+ $pcontentType = $this->datatype;
+ $plinkArray = array(new LinkNavigation($this->getUriAllCustoms()));
+ $pclass = "";
+ $pcount = $this->getDistinctValueCount();
+
+ return new Entry($ptitle, $pid, $pcontent, $pcontentType, $plinkArray, $pclass, $pcount);
+ }
+
+ /**
+ * Get the amount of distinct values for this column
+ *
+ * @return int
+ */
+ protected function getDistinctValueCount()
+ {
+ return count($this->getAllCustomValues());
+ }
+
+ /**
+ * Encode a value of this column ready to be displayed in an HTML document
+ *
+ * @param integer|string $value
+ * @return string
+ */
+ public function encodeHTMLValue($value)
+ {
+ return htmlspecialchars($value);
+ }
+
+ /**
+ * Get the datatype of a CustomColumn by its customID
+ *
+ * @param integer $customId
+ * @return string|null
+ */
+ private static function getDatatypeByCustomID($customId)
+ {
+ $result = parent::getDb()->prepare('SELECT datatype FROM custom_columns WHERE id = ?');
+ $result->execute(array($customId));
+ if ($post = $result->fetchObject()) {
+ return $post->datatype;
+ }
+ return NULL;
+ }
+
+ /**
+ * Create a CustomColumnType by CustomID
+ *
+ * @param integer $customId the id of the custom column
+ * @return CustomColumnType|null
+ * @throws Exception If the $customId is not found or the datatype is unknown
+ */
+ public static function createByCustomID($customId)
+ {
+ // Reuse already created CustomColumns for performance
+ if (array_key_exists($customId, self::$customColumnCacheID))
+ return self::$customColumnCacheID[$customId];
+
+ $datatype = self::getDatatypeByCustomID($customId);
+
+ switch ($datatype) {
+ case self::CUSTOM_TYPE_TEXT:
+ return self::$customColumnCacheID[$customId] = new CustomColumnTypeText($customId);
+ case self::CUSTOM_TYPE_SERIES:
+ return self::$customColumnCacheID[$customId] = new CustomColumnTypeSeries($customId);
+ case self::CUSTOM_TYPE_ENUM:
+ return self::$customColumnCacheID[$customId] = new CustomColumnTypeEnumeration($customId);
+ case self::CUSTOM_TYPE_COMMENT:
+ return self::$customColumnCacheID[$customId] = new CustomColumnTypeComment($customId);
+ case self::CUSTOM_TYPE_DATE:
+ return self::$customColumnCacheID[$customId] = new CustomColumnTypeDate($customId);
+ case self::CUSTOM_TYPE_FLOAT:
+ return self::$customColumnCacheID[$customId] = new CustomColumnTypeFloat($customId);
+ case self::CUSTOM_TYPE_INT:
+ return self::$customColumnCacheID[$customId] = new CustomColumnTypeInteger($customId);
+ case self::CUSTOM_TYPE_RATING:
+ return self::$customColumnCacheID[$customId] = new CustomColumnTypeRating($customId);
+ case self::CUSTOM_TYPE_BOOL:
+ return self::$customColumnCacheID[$customId] = new CustomColumnTypeBool($customId);
+ case self::CUSTOM_TYPE_COMPOSITE:
+ return NULL; //TODO Currently not supported
+ default:
+ throw new Exception("Unkown column type: " . $datatype);
+ }
+ }
+
+ /**
+ * Create a CustomColumnType by its lookup name
+ *
+ * @param string $lookup the lookup-name of the custom column
+ * @return CustomColumnType|null
+ */
+ public static function createByLookup($lookup)
+ {
+ // Reuse already created CustomColumns for performance
+ if (array_key_exists($lookup, self::$customColumnCacheLookup))
+ return self::$customColumnCacheLookup[$lookup];
+
+ $result = parent::getDb()->prepare('SELECT id FROM custom_columns WHERE label = ?');
+ $result->execute(array($lookup));
+ if ($post = $result->fetchObject()) {
+ return self::$customColumnCacheLookup[$lookup] = self::createByCustomID($post->id);
+ }
+ return self::$customColumnCacheLookup[$lookup] = NULL;
+ }
+
+ /**
+ * Return an entry array for all possible (in the DB used) values of this column
+ * These are the values used in the getUriAllCustoms() page
+ *
+ * @return Entry[]
+ */
+ public function getAllCustomValues()
+ {
+ // lazy loading
+ if ($this->customValues == NULL)
+ $this->customValues = $this->getAllCustomValuesFromDatabase();
+
+ return $this->customValues;
+ }
+
+ /**
+ * Get the title of a CustomColumn by its customID
+ *
+ * @param integer $customId
+ * @return string
+ */
+ protected static function getTitleByCustomID($customId)
+ {
+ $result = parent::getDb()->prepare('SELECT name FROM custom_columns WHERE id = ?');
+ $result->execute(array($customId));
+ if ($post = $result->fetchObject()) {
+ return $post->name;
+ }
+ return "";
+ }
+
+ /**
+ * Get the query to find all books with a specific value of this column
+ * the returning array has two values:
+ * - first the query (string)
+ * - second an array of all PreparedStatement parameters
+ *
+ * @param string|integer $id the id of the searched value
+ * @return array
+ */
+ abstract public function getQuery($id);
+
+ /**
+ * Get a CustomColumn for a specified (by ID) value
+ *
+ * @param string|integer $id the id of the searched value
+ * @return CustomColumn
+ */
+ abstract public function getCustom($id);
+
+ /**
+ * Return an entry array for all possible (in the DB used) values of this column by querying the database
+ *
+ * @return Entry[]
+ */
+ abstract protected function getAllCustomValuesFromDatabase();
+
+ /**
+ * The description used in the index page
+ *
+ * @return string
+ */
+ abstract public function getDescription();
+
+ /**
+ * Find the value of this column for a specific book
+ *
+ * @param Book $book
+ * @return CustomColumn
+ */
+ public abstract function getCustomByBook($book);
+
+ /**
+ * Is this column searchable by value
+ * only searchable columns can be displayed on the index page
+ *
+ * @return bool
+ */
+ public abstract function isSearchable();
+}
diff --git a/sources/lib/CustomColumnTypeBool.php b/sources/lib/CustomColumnTypeBool.php
new file mode 100644
index 0000000..1ac577f
--- /dev/null
+++ b/sources/lib/CustomColumnTypeBool.php
@@ -0,0 +1,94 @@
+
+ */
+
+class CustomColumnTypeBool extends CustomColumnType
+{
+ // PHP pre 5.6 does not support const arrays
+ private $BOOLEAN_NAMES = array(
+ -1 => "customcolumn.boolean.unknown", // localize("customcolumn.boolean.unknown")
+ 00 => "customcolumn.boolean.no", // localize("customcolumn.boolean.no")
+ +1 => "customcolumn.boolean.yes", // localize("customcolumn.boolean.yes")
+ );
+
+ protected function __construct($pcustomId)
+ {
+ parent::__construct($pcustomId, self::CUSTOM_TYPE_BOOL);
+ }
+
+ /**
+ * Get the name of the sqlite table for this column
+ *
+ * @return string|null
+ */
+ private function getTableName()
+ {
+ return "custom_column_{$this->customId}";
+ }
+
+ public function getQuery($id)
+ {
+ if ($id == -1) {
+ $query = str_format(Book::SQL_BOOKS_BY_CUSTOM_BOOL_NULL, "{0}", "{1}", $this->getTableName());
+ return array($query, array());
+ } else if ($id == 0) {
+ $query = str_format(Book::SQL_BOOKS_BY_CUSTOM_BOOL_FALSE, "{0}", "{1}", $this->getTableName());
+ return array($query, array());
+ } else if ($id == 1) {
+ $query = str_format(Book::SQL_BOOKS_BY_CUSTOM_BOOL_TRUE, "{0}", "{1}", $this->getTableName());
+ return array($query, array());
+ } else {
+ return NULL;
+ }
+ }
+
+ public function getCustom($id)
+ {
+ return new CustomColumn($id, localize($this->BOOLEAN_NAMES[$id]), $this);
+ }
+
+ protected function getAllCustomValuesFromDatabase()
+ {
+ $queryFormat = "SELECT coalesce({0}.value, -1) AS id, count(*) AS count FROM books LEFT JOIN {0} ON books.id = {0}.book GROUP BY {0}.value ORDER BY {0}.value";
+ $query = str_format($queryFormat, $this->getTableName());
+ $result = $this->getDb()->query($query);
+
+ $entryArray = array();
+ while ($post = $result->fetchObject()) {
+ $entryPContent = str_format(localize("bookword", $post->count), $post->count);
+ $entryPLinkArray = array(new LinkNavigation ($this->getUri($post->id)));
+
+ $entry = new Entry(localize($this->BOOLEAN_NAMES[$post->id]), $this->getEntryId($post->id), $entryPContent, $this->datatype, $entryPLinkArray, "", $post->count);
+
+ array_push($entryArray, $entry);
+ }
+ return $entryArray;
+ }
+
+ public function getDescription()
+ {
+ return localize("customcolumn.description.bool");
+ }
+
+ public function getCustomByBook($book)
+ {
+ $queryFormat = "SELECT {0}.value AS boolvalue FROM {0} WHERE {0}.book = {1}";
+ $query = str_format($queryFormat, $this->getTableName(), $book->id);
+
+ $result = $this->getDb()->query($query);
+ if ($post = $result->fetchObject()) {
+ return new CustomColumn($post->boolvalue, localize($this->BOOLEAN_NAMES[$post->boolvalue]), $this);
+ } else {
+ return new CustomColumn(-1, localize($this->BOOLEAN_NAMES[-1]), $this);
+ }
+ }
+
+ public function isSearchable()
+ {
+ return true;
+ }
+}
diff --git a/sources/lib/CustomColumnTypeComment.php b/sources/lib/CustomColumnTypeComment.php
new file mode 100644
index 0000000..3aae772
--- /dev/null
+++ b/sources/lib/CustomColumnTypeComment.php
@@ -0,0 +1,70 @@
+
+ */
+
+class CustomColumnTypeComment extends CustomColumnType
+{
+ protected function __construct($pcustomId)
+ {
+ parent::__construct($pcustomId, self::CUSTOM_TYPE_COMMENT);
+ }
+
+ /**
+ * Get the name of the sqlite table for this column
+ *
+ * @return string|null
+ */
+ private function getTableName()
+ {
+ return "custom_column_{$this->customId}";
+ }
+
+ public function getQuery($id)
+ {
+ $query = str_format(Book::SQL_BOOKS_BY_CUSTOM_DIRECT_ID, "{0}", "{1}", $this->getTableName());
+ return array($query, array($id));
+ }
+
+ public function getCustom($id)
+ {
+ return new CustomColumn($id, $id, $this);
+ }
+
+ public function encodeHTMLValue($value)
+ {
+ return "" . $value . "
"; // no htmlspecialchars, this is already HTML
+ }
+
+ protected function getAllCustomValuesFromDatabase()
+ {
+ return NULL;
+ }
+
+ public function getDescription()
+ {
+ $desc = $this->getDatabaseDescription();
+ if ($desc === NULL || empty($desc)) $desc = str_format(localize("customcolumn.description"), $this->getTitle());
+ return $desc;
+ }
+
+ public function getCustomByBook($book)
+ {
+ $queryFormat = "SELECT {0}.id AS id, {0}.value AS value FROM {0} WHERE {0}.book = {1}";
+ $query = str_format($queryFormat, $this->getTableName(), $book->id);
+
+ $result = $this->getDb()->query($query);
+ if ($post = $result->fetchObject()) {
+ return new CustomColumn($post->id, $post->value, $this);
+ }
+ return new CustomColumn(NULL, localize("customcolumn.float.unknown"), $this);
+ }
+
+ public function isSearchable()
+ {
+ return false;
+ }
+}
diff --git a/sources/lib/CustomColumnTypeDate.php b/sources/lib/CustomColumnTypeDate.php
new file mode 100644
index 0000000..cc3df35
--- /dev/null
+++ b/sources/lib/CustomColumnTypeDate.php
@@ -0,0 +1,87 @@
+
+ */
+
+class CustomColumnTypeDate extends CustomColumnType
+{
+ protected function __construct($pcustomId)
+ {
+ parent::__construct($pcustomId, self::CUSTOM_TYPE_DATE);
+ }
+
+ /**
+ * Get the name of the sqlite table for this column
+ *
+ * @return string|null
+ */
+ private function getTableName()
+ {
+ return "custom_column_{$this->customId}";
+ }
+
+ public function getQuery($id)
+ {
+ $date = new DateTime($id);
+ $query = str_format(Book::SQL_BOOKS_BY_CUSTOM_DATE, "{0}", "{1}", $this->getTableName());
+ return array($query, array($date->format("Y-m-d")));
+ }
+
+ public function getCustom($id)
+ {
+ $date = new DateTime($id);
+
+ return new CustomColumn($id, $date->format(localize("customcolumn.date.format")), $this);
+ }
+
+ protected function getAllCustomValuesFromDatabase()
+ {
+ $queryFormat = "SELECT date(value) AS datevalue, count(*) AS count FROM {0} GROUP BY datevalue";
+ $query = str_format($queryFormat, $this->getTableName());
+ $result = $this->getDb()->query($query);
+
+ $entryArray = array();
+ while ($post = $result->fetchObject()) {
+ $date = new DateTime($post->datevalue);
+ $id = $date->format("Y-m-d");
+
+ $entryPContent = str_format(localize("bookword", $post->count), $post->count);
+ $entryPLinkArray = array(new LinkNavigation ($this->getUri($id)));
+
+ $entry = new Entry($date->format(localize("customcolumn.date.format")), $this->getEntryId($id), $entryPContent, $this->datatype, $entryPLinkArray, "", $post->count);
+
+ array_push($entryArray, $entry);
+ }
+
+ return $entryArray;
+ }
+
+ public function getDescription()
+ {
+ $desc = $this->getDatabaseDescription();
+ if ($desc === NULL || empty($desc)) $desc = str_format(localize("customcolumn.description"), $this->getTitle());
+ return $desc;
+ }
+
+ public function getCustomByBook($book)
+ {
+ $queryFormat = "SELECT date({0}.value) AS datevalue FROM {0} WHERE {0}.book = {1}";
+ $query = str_format($queryFormat, $this->getTableName(), $book->id);
+
+ $result = $this->getDb()->query($query);
+ if ($post = $result->fetchObject()) {
+ $date = new DateTime($post->datevalue);
+
+ return new CustomColumn($date->format("Y-m-d"), $date->format(localize("customcolumn.date.format")), $this);
+ }
+ return new CustomColumn(NULL, localize("customcolumn.date.unknown"), $this);
+ }
+
+ public function isSearchable()
+ {
+ return true;
+ }
+}
diff --git a/sources/lib/CustomColumnTypeEnumeration.php b/sources/lib/CustomColumnTypeEnumeration.php
new file mode 100644
index 0000000..c4e303c
--- /dev/null
+++ b/sources/lib/CustomColumnTypeEnumeration.php
@@ -0,0 +1,102 @@
+
+ */
+
+class CustomColumnTypeEnumeration extends CustomColumnType
+{
+ protected function __construct($pcustomId)
+ {
+ parent::__construct($pcustomId, self::CUSTOM_TYPE_ENUM);
+ }
+
+ /**
+ * Get the name of the sqlite table for this column
+ *
+ * @return string|null
+ */
+ private function getTableName()
+ {
+ return "custom_column_{$this->customId}";
+ }
+
+ /**
+ * Get the name of the linking sqlite table for this column
+ * (or NULL if there is no linktable)
+ *
+ * @return string|null
+ */
+ private function getTableLinkName()
+ {
+ return "books_custom_column_{$this->customId}_link";
+ }
+
+ /**
+ * Get the name of the linking column in the linktable
+ *
+ * @return string|null
+ */
+ private function getTableLinkColumn()
+ {
+ return "value";
+ }
+
+ public function getQuery($id)
+ {
+ $query = str_format(Book::SQL_BOOKS_BY_CUSTOM, "{0}", "{1}", $this->getTableLinkName(), $this->getTableLinkColumn());
+ return array($query, array($id));
+ }
+
+ public function getCustom($id)
+ {
+ $result = $this->getDb()->prepare(str_format("SELECT id, value AS name FROM {0} WHERE id = ?", $this->getTableName()));
+ $result->execute(array($id));
+ if ($post = $result->fetchObject()) {
+ return new CustomColumn ($id, $post->name, $this);
+ }
+ return NULL;
+ }
+
+ protected function getAllCustomValuesFromDatabase()
+ {
+ $queryFormat = "SELECT {0}.id AS id, {0}.value AS name, count(*) AS count FROM {0}, {1} WHERE {0}.id = {1}.{2} GROUP BY {0}.id, {0}.value ORDER BY {0}.value";
+ $query = str_format($queryFormat, $this->getTableName(), $this->getTableLinkName(), $this->getTableLinkColumn());
+
+ $result = $this->getDb()->query($query);
+ $entryArray = array();
+ while ($post = $result->fetchObject()) {
+ $entryPContent = str_format(localize("bookword", $post->count), $post->count);
+ $entryPLinkArray = array(new LinkNavigation ($this->getUri($post->id)));
+
+ $entry = new Entry ($post->name, $this->getEntryId($post->id), $entryPContent, $this->datatype, $entryPLinkArray, "", $post->count);
+
+ array_push($entryArray, $entry);
+ }
+ return $entryArray;
+ }
+
+ public function getDescription()
+ {
+ return str_format(localize("customcolumn.description.enum", $this->getDistinctValueCount()), $this->getDistinctValueCount());
+ }
+
+ public function getCustomByBook($book)
+ {
+ $queryFormat = "SELECT {0}.id AS id, {0}.{2} AS name FROM {0}, {1} WHERE {0}.id = {1}.{2} AND {1}.book = {3}";
+ $query = str_format($queryFormat, $this->getTableName(), $this->getTableLinkName(), $this->getTableLinkColumn(), $book->id);
+
+ $result = $this->getDb()->query($query);
+ if ($post = $result->fetchObject()) {
+ return new CustomColumn($post->id, $post->name, $this);
+ }
+ return new CustomColumn(NULL, localize("customcolumn.enum.unknown"), $this);
+ }
+
+ public function isSearchable()
+ {
+ return true;
+ }
+}
diff --git a/sources/lib/CustomColumnTypeFloat.php b/sources/lib/CustomColumnTypeFloat.php
new file mode 100644
index 0000000..c7f21f5
--- /dev/null
+++ b/sources/lib/CustomColumnTypeFloat.php
@@ -0,0 +1,78 @@
+
+ */
+
+class CustomColumnTypeFloat extends CustomColumnType
+{
+ protected function __construct($pcustomId)
+ {
+ parent::__construct($pcustomId, self::CUSTOM_TYPE_FLOAT);
+ }
+
+ /**
+ * Get the name of the sqlite table for this column
+ *
+ * @return string|null
+ */
+ private function getTableName()
+ {
+ return "custom_column_{$this->customId}";
+ }
+
+ public function getQuery($id)
+ {
+ $query = str_format(Book::SQL_BOOKS_BY_CUSTOM_DIRECT, "{0}", "{1}", $this->getTableName());
+ return array($query, array($id));
+ }
+
+ public function getCustom($id)
+ {
+ return new CustomColumn($id, $id, $this);
+ }
+
+ protected function getAllCustomValuesFromDatabase()
+ {
+ $queryFormat = "SELECT value AS id, count(*) AS count FROM {0} GROUP BY value";
+ $query = str_format($queryFormat, $this->getTableName());
+
+ $result = $this->getDb()->query($query);
+ $entryArray = array();
+ while ($post = $result->fetchObject()) {
+ $entryPContent = str_format(localize("bookword", $post->count), $post->count);
+ $entryPLinkArray = array(new LinkNavigation($this->getUri($post->id)));
+
+ $entry = new Entry($post->id, $this->getEntryId($post->id), $entryPContent, $this->datatype, $entryPLinkArray, "", $post->count);
+
+ array_push($entryArray, $entry);
+ }
+ return $entryArray;
+ }
+
+ public function getDescription()
+ {
+ $desc = $this->getDatabaseDescription();
+ if ($desc === NULL || empty($desc)) $desc = str_format(localize("customcolumn.description"), $this->getTitle());
+ return $desc;
+ }
+
+ public function getCustomByBook($book)
+ {
+ $queryFormat = "SELECT {0}.value AS value FROM {0} WHERE {0}.book = {1}";
+ $query = str_format($queryFormat, $this->getTableName(), $book->id);
+
+ $result = $this->getDb()->query($query);
+ if ($post = $result->fetchObject()) {
+ return new CustomColumn($post->value, $post->value, $this);
+ }
+ return new CustomColumn(NULL, localize("customcolumn.float.unknown"), $this);
+ }
+
+ public function isSearchable()
+ {
+ return true;
+ }
+}
diff --git a/sources/lib/CustomColumnTypeInteger.php b/sources/lib/CustomColumnTypeInteger.php
new file mode 100644
index 0000000..82e7f37
--- /dev/null
+++ b/sources/lib/CustomColumnTypeInteger.php
@@ -0,0 +1,78 @@
+
+ */
+
+class CustomColumnTypeInteger extends CustomColumnType
+{
+ protected function __construct($pcustomId)
+ {
+ parent::__construct($pcustomId, self::CUSTOM_TYPE_INT);
+ }
+
+ /**
+ * Get the name of the sqlite table for this column
+ *
+ * @return string|null
+ */
+ private function getTableName()
+ {
+ return "custom_column_{$this->customId}";
+ }
+
+ public function getQuery($id)
+ {
+ $query = str_format(Book::SQL_BOOKS_BY_CUSTOM_DIRECT, "{0}", "{1}", $this->getTableName());
+ return array($query, array($id));
+ }
+
+ public function getCustom($id)
+ {
+ return new CustomColumn($id, $id, $this);
+ }
+
+ protected function getAllCustomValuesFromDatabase()
+ {
+ $queryFormat = "SELECT value AS id, count(*) AS count FROM {0} GROUP BY value";
+ $query = str_format($queryFormat, $this->getTableName());
+
+ $result = $this->getDb()->query($query);
+ $entryArray = array();
+ while ($post = $result->fetchObject()) {
+ $entryPContent = str_format(localize("bookword", $post->count), $post->count);
+ $entryPLinkArray = array(new LinkNavigation($this->getUri($post->id)));
+
+ $entry = new Entry($post->id, $this->getEntryId($post->id), $entryPContent, $this->datatype, $entryPLinkArray, "", $post->count);
+
+ array_push($entryArray, $entry);
+ }
+ return $entryArray;
+ }
+
+ public function getDescription()
+ {
+ $desc = $this->getDatabaseDescription();
+ if ($desc === NULL || empty($desc)) $desc = str_format(localize("customcolumn.description"), $this->getTitle());
+ return $desc;
+ }
+
+ public function getCustomByBook($book)
+ {
+ $queryFormat = "SELECT {0}.value AS value FROM {0} WHERE {0}.book = {1}";
+ $query = str_format($queryFormat, $this->getTableName(), $book->id);
+
+ $result = $this->getDb()->query($query);
+ if ($post = $result->fetchObject()) {
+ return new CustomColumn($post->value, $post->value, $this);
+ }
+ return new CustomColumn(NULL, localize("customcolumn.int.unknown"), $this);
+ }
+
+ public function isSearchable()
+ {
+ return true;
+ }
+}
diff --git a/sources/lib/CustomColumnTypeRating.php b/sources/lib/CustomColumnTypeRating.php
new file mode 100644
index 0000000..c1ceb98
--- /dev/null
+++ b/sources/lib/CustomColumnTypeRating.php
@@ -0,0 +1,110 @@
+
+ */
+
+class CustomColumnTypeRating extends CustomColumnType
+{
+ protected function __construct($pcustomId)
+ {
+ parent::__construct($pcustomId, self::CUSTOM_TYPE_RATING);
+ }
+
+ /**
+ * Get the name of the sqlite table for this column
+ *
+ * @return string|null
+ */
+ private function getTableName()
+ {
+ return "custom_column_{$this->customId}";
+ }
+
+ /**
+ * Get the name of the linking sqlite table for this column
+ * (or NULL if there is no linktable)
+ *
+ * @return string|null
+ */
+ private function getTableLinkName()
+ {
+ return "books_custom_column_{$this->customId}_link";
+ }
+
+ /**
+ * Get the name of the linking column in the linktable
+ *
+ * @return string|null
+ */
+ private function getTableLinkColumn()
+ {
+ return "value";
+ }
+
+ public function getQuery($id)
+ {
+ if ($id == 0) {
+ $query = str_format(Book::SQL_BOOKS_BY_CUSTOM_RATING_NULL, "{0}", "{1}", $this->getTableLinkName(), $this->getTableName(), $this->getTableLinkColumn());
+ return array($query, array());
+ } else {
+ $query = str_format(Book::SQL_BOOKS_BY_CUSTOM_RATING, "{0}", "{1}", $this->getTableLinkName(), $this->getTableName(), $this->getTableLinkColumn());
+ return array($query, array($id));
+ }
+ }
+
+ public function getCustom($id)
+ {
+ return new CustomColumn ($id, str_format(localize("customcolumn.stars", $id / 2), $id / 2), $this);
+ }
+
+ protected function getAllCustomValuesFromDatabase()
+ {
+ $queryFormat = "SELECT coalesce({0}.value, 0) AS value, count(*) AS count FROM books LEFT JOIN {1} ON books.id = {1}.book LEFT JOIN {0} ON {0}.id = {1}.value GROUP BY coalesce({0}.value, -1)";
+ $query = str_format($queryFormat, $this->getTableName(), $this->getTableLinkName());
+ $result = $this->getDb()->query($query);
+
+ $countArray = array(0 => 0, 2 => 0, 4 => 0, 6 => 0, 8 => 0, 10 => 0);
+ while ($row = $result->fetchObject()) {
+ $countArray[$row->value] = $row->count;
+ }
+
+ $entryArray = array();
+
+ for ($i = 0; $i <= 5; $i++) {
+ $count = $countArray[$i * 2];
+ $name = str_format(localize("customcolumn.stars", $i), $i);
+ $entryid = $this->getEntryId($i * 2);
+ $content = str_format(localize("bookword", $count), $count);
+ $linkarray = array(new LinkNavigation($this->getUri($i * 2)));
+ $entry = new Entry($name, $entryid, $content, $this->datatype, $linkarray, "", $count);
+ array_push($entryArray, $entry);
+ }
+
+ return $entryArray;
+ }
+
+ public function getDescription()
+ {
+ return localize("customcolumn.description.rating");
+ }
+
+ public function getCustomByBook($book)
+ {
+ $queryFormat = "SELECT {0}.value AS value FROM {0}, {1} WHERE {0}.id = {1}.{2} AND {1}.book = {3}";
+ $query = str_format($queryFormat, $this->getTableName(), $this->getTableLinkName(), $this->getTableLinkColumn(), $book->id);
+
+ $result = $this->getDb()->query($query);
+ if ($post = $result->fetchObject()) {
+ return new CustomColumn($post->value, str_format(localize("customcolumn.stars", $post->value / 2), $post->value / 2), $this);
+ }
+ return new CustomColumn(NULL, localize("customcolumn.rating.unknown"), $this);
+ }
+
+ public function isSearchable()
+ {
+ return true;
+ }
+}
diff --git a/sources/lib/CustomColumnTypeSeries.php b/sources/lib/CustomColumnTypeSeries.php
new file mode 100644
index 0000000..e37295e
--- /dev/null
+++ b/sources/lib/CustomColumnTypeSeries.php
@@ -0,0 +1,102 @@
+
+ */
+
+class CustomColumnTypeSeries extends CustomColumnType
+{
+ protected function __construct($pcustomId)
+ {
+ parent::__construct($pcustomId, self::CUSTOM_TYPE_SERIES);
+ }
+
+ /**
+ * Get the name of the sqlite table for this column
+ *
+ * @return string|null
+ */
+ private function getTableName()
+ {
+ return "custom_column_{$this->customId}";
+ }
+
+ /**
+ * Get the name of the linking sqlite table for this column
+ * (or NULL if there is no linktable)
+ *
+ * @return string|null
+ */
+ private function getTableLinkName()
+ {
+ return "books_custom_column_{$this->customId}_link";
+ }
+
+ /**
+ * Get the name of the linking column in the linktable
+ *
+ * @return string|null
+ */
+ private function getTableLinkColumn()
+ {
+ return "value";
+ }
+
+ public function getQuery($id)
+ {
+ $query = str_format(Book::SQL_BOOKS_BY_CUSTOM, "{0}", "{1}", $this->getTableLinkName(), $this->getTableLinkColumn());
+ return array($query, array($id));
+ }
+
+ public function getCustom($id)
+ {
+ $result = $this->getDb()->prepare(str_format("SELECT id, value AS name FROM {0} WHERE id = ?", $this->getTableName()));
+ $result->execute(array($id));
+ if ($post = $result->fetchObject()) {
+ return new CustomColumn($id, $post->name, $this);
+ }
+ return NULL;
+ }
+
+ protected function getAllCustomValuesFromDatabase()
+ {
+ $queryFormat = "SELECT {0}.id AS id, {0}.value AS name, count(*) AS count FROM {0}, {1} WHERE {0}.id = {1}.{2} GROUP BY {0}.id, {0}.value ORDER BY {0}.value";
+ $query = str_format($queryFormat, $this->getTableName(), $this->getTableLinkName(), $this->getTableLinkColumn());
+
+ $result = $this->getDb()->query($query);
+ $entryArray = array();
+ while ($post = $result->fetchObject()) {
+ $entryPContent = str_format(localize("bookword", $post->count), $post->count);
+ $entryPLinkArray = array(new LinkNavigation($this->getUri($post->id)));
+
+ $entry = new Entry($post->name, $this->getEntryId($post->id), $entryPContent, $this->datatype, $entryPLinkArray, "", $post->count);
+
+ array_push($entryArray, $entry);
+ }
+ return $entryArray;
+ }
+
+ public function getDescription()
+ {
+ return str_format(localize("customcolumn.description.series", $this->getDistinctValueCount()), $this->getDistinctValueCount());
+ }
+
+ public function getCustomByBook($book)
+ {
+ $queryFormat = "SELECT {0}.id AS id, {1}.{2} AS name, {1}.extra AS extra FROM {0}, {1} WHERE {0}.id = {1}.{2} AND {1}.book = {3}";
+ $query = str_format($queryFormat, $this->getTableName(), $this->getTableLinkName(), $this->getTableLinkColumn(), $book->id);
+
+ $result = $this->getDb()->query($query);
+ if ($post = $result->fetchObject()) {
+ return new CustomColumn($post->id, $post->name . " [" . $post->extra . "]", $this);
+ }
+ return new CustomColumn(NULL, "", $this);
+ }
+
+ public function isSearchable()
+ {
+ return true;
+ }
+}
diff --git a/sources/lib/CustomColumnTypeText.php b/sources/lib/CustomColumnTypeText.php
new file mode 100644
index 0000000..b2b21cd
--- /dev/null
+++ b/sources/lib/CustomColumnTypeText.php
@@ -0,0 +1,105 @@
+
+ */
+
+class CustomColumnTypeText extends CustomColumnType
+{
+ protected function __construct($pcustomId)
+ {
+ parent::__construct($pcustomId, self::CUSTOM_TYPE_TEXT);
+ }
+
+ /**
+ * Get the name of the sqlite table for this column
+ *
+ * @return string|null
+ */
+ private function getTableName()
+ {
+ return "custom_column_{$this->customId}";
+ }
+
+ /**
+ * Get the name of the linking sqlite table for this column
+ * (or NULL if there is no linktable)
+ *
+ * @return string|null
+ */
+ private function getTableLinkName()
+ {
+ return "books_custom_column_{$this->customId}_link";
+ }
+
+ /**
+ * Get the name of the linking column in the linktable
+ *
+ * @return string|null
+ */
+ private function getTableLinkColumn()
+ {
+ return "value";
+ }
+
+ public function getQuery($id)
+ {
+ $query = str_format(Book::SQL_BOOKS_BY_CUSTOM, "{0}", "{1}", $this->getTableLinkName(), $this->getTableLinkColumn());
+ return array($query, array($id));
+ }
+
+ public function getCustom($id)
+ {
+ $result = $this->getDb()->prepare(str_format("SELECT id, value AS name FROM {0} WHERE id = ?", $this->getTableName()));
+ $result->execute(array($id));
+ if ($post = $result->fetchObject()) {
+ return new CustomColumn($id, $post->name, $this);
+ }
+ return NULL;
+ }
+
+ protected function getAllCustomValuesFromDatabase()
+ {
+ $queryFormat = "SELECT {0}.id AS id, {0}.value AS name, count(*) AS count FROM {0}, {1} WHERE {0}.id = {1}.{2} GROUP BY {0}.id, {0}.value ORDER BY {0}.value";
+ $query = str_format($queryFormat, $this->getTableName(), $this->getTableLinkName(), $this->getTableLinkColumn());
+
+ $result = $this->getDb()->query($query);
+ $entryArray = array();
+ while ($post = $result->fetchObject())
+ {
+ $entryPContent = str_format(localize("bookword", $post->count), $post->count);
+ $entryPLinkArray = array(new LinkNavigation ($this->getUri($post->id)));
+
+ $entry = new Entry($post->name, $this->getEntryId($post->id), $entryPContent, $this->datatype, $entryPLinkArray, "", $post->count);
+
+ array_push($entryArray, $entry);
+ }
+ return $entryArray;
+ }
+
+ public function getDescription()
+ {
+ $desc = $this->getDatabaseDescription();
+ if ($desc === NULL || empty($desc)) $desc = str_format(localize("customcolumn.description"), $this->getTitle());
+ return $desc;
+ }
+
+ public function getCustomByBook($book)
+ {
+ $queryFormat = "SELECT {0}.id AS id, {0}.{2} AS name FROM {0}, {1} WHERE {0}.id = {1}.{2} AND {1}.book = {3} ORDER BY {0}.value";
+ $query = str_format($queryFormat, $this->getTableName(), $this->getTableLinkName(), $this->getTableLinkColumn(), $book->id);
+
+ $result = $this->getDb()->query($query);
+ if ($post = $result->fetchObject()) {
+ return new CustomColumn($post->id, $post->name, $this);
+ }
+ return new CustomColumn(NULL, "", $this);
+ }
+
+ public function isSearchable()
+ {
+ return true;
+ }
+}
diff --git a/sources/lib/Data.php b/sources/lib/Data.php
new file mode 100644
index 0000000..4c8eddf
--- /dev/null
+++ b/sources/lib/Data.php
@@ -0,0 +1,208 @@
+
+ */
+
+class Data extends Base
+{
+ public $id;
+ public $name;
+ public $format;
+ public $realFormat;
+ public $extension;
+ public $book;
+
+ public static $mimetypes = array(
+ 'aac' => 'audio/aac',
+ 'azw' => 'application/x-mobipocket-ebook',
+ 'azw1' => 'application/x-topaz-ebook',
+ 'azw2' => 'application/x-kindle-application',
+ 'azw3' => 'application/x-mobi8-ebook',
+ 'cbz' => 'application/x-cbz',
+ 'cbr' => 'application/x-cbr',
+ 'djv' => 'image/vnd.djvu',
+ 'djvu' => 'image/vnd.djvu',
+ 'doc' => 'application/msword',
+ 'epub' => 'application/epub+zip',
+ 'fb2' => 'text/fb2+xml',
+ 'ibooks'=> 'application/x-ibooks+zip',
+ 'kepub' => 'application/epub+zip',
+ 'kobo' => 'application/x-koboreader-ebook',
+ 'm4a' => 'audio/mp4',
+ 'mobi' => 'application/x-mobipocket-ebook',
+ 'mp3' => 'audio/mpeg',
+ 'lit' => 'application/x-ms-reader',
+ 'lrs' => 'text/x-sony-bbeb+xml',
+ 'lrf' => 'application/x-sony-bbeb',
+ 'lrx' => 'application/x-sony-bbeb',
+ 'ncx' => 'application/x-dtbncx+xml',
+ 'opf' => 'application/oebps-package+xml',
+ 'otf' => 'application/x-font-opentype',
+ 'pdb' => 'application/vnd.palm',
+ 'pdf' => 'application/pdf',
+ 'prc' => 'application/x-mobipocket-ebook',
+ 'rtf' => 'application/rtf',
+ 'svg' => 'image/svg+xml',
+ 'ttf' => 'application/x-font-truetype',
+ 'tpz' => 'application/x-topaz-ebook',
+ 'wav' => 'audio/wav',
+ 'wmf' => 'image/wmf',
+ 'xhtml' => 'application/xhtml+xml',
+ 'xpgt' => 'application/adobe-page-template+xml',
+ 'zip' => 'application/zip'
+ );
+
+ public function __construct($post, $book = null) {
+ $this->id = $post->id;
+ $this->name = $post->name;
+ $this->format = $post->format;
+ $this->realFormat = str_replace ("ORIGINAL_", "", $post->format);
+ $this->extension = strtolower ($this->realFormat);
+ $this->book = $book;
+ }
+
+ public function isKnownType () {
+ return array_key_exists ($this->extension, self::$mimetypes);
+ }
+
+ public function getMimeType () {
+ $result = "application/octet-stream";
+ if ($this->isKnownType ()) {
+ return self::$mimetypes [$this->extension];
+ } elseif (function_exists('finfo_open') === true) {
+ $finfo = finfo_open(FILEINFO_MIME_TYPE);
+
+ if (is_resource($finfo) === true)
+ {
+ $result = finfo_file($finfo, $this->getLocalPath ());
+ }
+
+ finfo_close($finfo);
+
+ }
+ return $result;
+ }
+
+ public function isEpubValidOnKobo () {
+ return $this->format == "EPUB" || $this->format == "KEPUB";
+ }
+
+ public function getFilename () {
+ return $this->name . "." . strtolower ($this->format);
+ }
+
+ public function getUpdatedFilename () {
+ return $this->book->getAuthorsSort () . " - " . $this->book->title;
+ }
+
+ public function getUpdatedFilenameEpub () {
+ return $this->getUpdatedFilename () . ".epub";
+ }
+
+ public function getUpdatedFilenameKepub () {
+ $str = $this->getUpdatedFilename () . ".kepub.epub";
+ return str_replace(array(':', '#', '&'),
+ array('-', '-', ' '), $str );
+ }
+
+ public function getDataLink ($rel, $title = NULL) {
+ global $config;
+
+ if ($rel == Link::OPDS_ACQUISITION_TYPE && $config['cops_use_url_rewriting'] == "1") {
+ return $this->getHtmlLinkWithRewriting($title);
+ }
+
+ return self::getLink ($this->book, $this->extension, $this->getMimeType (), $rel, $this->getFilename (), $this->id, $title);
+ }
+
+ public function getHtmlLink () {
+ return $this->getDataLink(Link::OPDS_ACQUISITION_TYPE)->href;
+ }
+
+ public function getLocalPath () {
+ return $this->book->path . "/" . $this->getFilename ();
+ }
+
+ public function getHtmlLinkWithRewriting ($title = NULL) {
+ global $config;
+
+ $database = "";
+ if (!is_null (GetUrlParam (DB))) $database = GetUrlParam (DB) . "/";
+
+ $href = "download/" . $this->id . "/" . $database;
+
+ if ($config['cops_provide_kepub'] == "1" &&
+ $this->isEpubValidOnKobo () &&
+ preg_match("/Kobo/", $_SERVER['HTTP_USER_AGENT'])) {
+ $href .= rawurlencode ($this->getUpdatedFilenameKepub ());
+ } else {
+ $href .= rawurlencode ($this->getFilename ());
+ }
+ return new Link ($href, $this->getMimeType (), Link::OPDS_ACQUISITION_TYPE, $title);
+ }
+
+ public static function getDataByBook ($book) {
+ $out = array ();
+ $result = parent::getDb ()->prepare('select id, format, name
+ from data where book = ?');
+ $result->execute (array ($book->id));
+
+ while ($post = $result->fetchObject ())
+ {
+ array_push ($out, new Data ($post, $book));
+ }
+ return $out;
+ }
+
+ public static function handleThumbnailLink ($urlParam, $height) {
+ global $config;
+
+ if (is_null ($height)) {
+ if (preg_match ('/feed.php/', $_SERVER["SCRIPT_NAME"])) {
+ $height = $config['cops_opds_thumbnail_height'];
+ }
+ else
+ {
+ $height = $config['cops_html_thumbnail_height'];
+ }
+ }
+ if ($config['cops_thumbnail_handling'] != "1") {
+ $urlParam = addURLParameter($urlParam, "height", $height);
+ }
+
+ return $urlParam;
+ }
+
+ public static function getLink ($book, $type, $mime, $rel, $filename, $idData, $title = NULL, $height = NULL)
+ {
+ global $config;
+
+ $urlParam = addURLParameter("", "data", $idData);
+
+ if (Base::useAbsolutePath () ||
+ $rel == Link::OPDS_THUMBNAIL_TYPE ||
+ ($type == "epub" && $config['cops_update_epub-metadata']))
+ {
+ if ($type != "jpg") $urlParam = addURLParameter($urlParam, "type", $type);
+ if ($rel == Link::OPDS_THUMBNAIL_TYPE) {
+ $urlParam = self::handleThumbnailLink($urlParam, $height);
+ }
+ $urlParam = addURLParameter($urlParam, "id", $book->id);
+ if (!is_null (GetUrlParam (DB))) $urlParam = addURLParameter ($urlParam, DB, GetUrlParam (DB));
+ if ($config['cops_thumbnail_handling'] != "1" &&
+ !empty ($config['cops_thumbnail_handling']) &&
+ $rel == Link::OPDS_THUMBNAIL_TYPE) {
+ return new Link ($config['cops_thumbnail_handling'], $mime, $rel, $title);
+ } else {
+ return new Link ("fetch.php?" . $urlParam, $mime, $rel, $title);
+ }
+ }
+ else
+ {
+ return new Link (str_replace('%2F','/',rawurlencode ($book->path."/".$filename)), $mime, $rel, $title);
+ }
+ }
+}
diff --git a/sources/lib/Entry.php b/sources/lib/Entry.php
new file mode 100644
index 0000000..dedfff8
--- /dev/null
+++ b/sources/lib/Entry.php
@@ -0,0 +1,78 @@
+
+ */
+
+class Entry
+{
+ public $title;
+ public $id;
+ public $content;
+ public $numberOfElement;
+ public $contentType;
+ public $linkArray;
+ public $localUpdated;
+ public $className;
+ private static $updated = NULL;
+
+ public static $icons = array(
+ Author::ALL_AUTHORS_ID => 'images/author.png',
+ Serie::ALL_SERIES_ID => 'images/serie.png',
+ Book::ALL_RECENT_BOOKS_ID => 'images/recent.png',
+ Tag::ALL_TAGS_ID => 'images/tag.png',
+ Language::ALL_LANGUAGES_ID => 'images/language.png',
+ CustomColumnType::ALL_CUSTOMS_ID => 'images/custom.png',
+ Rating::ALL_RATING_ID => 'images/rating.png',
+ "cops:books$" => 'images/allbook.png',
+ "cops:books:letter" => 'images/allbook.png',
+ Publisher::ALL_PUBLISHERS_ID => 'images/publisher.png'
+ );
+
+ public function getUpdatedTime () {
+ if (!is_null ($this->localUpdated)) {
+ return date (DATE_ATOM, $this->localUpdated);
+ }
+ if (is_null (self::$updated)) {
+ self::$updated = time();
+ }
+ return date (DATE_ATOM, self::$updated);
+ }
+
+ public function getNavLink () {
+ foreach ($this->linkArray as $link) {
+ /* @var $link LinkNavigation */
+
+ if ($link->type != Link::OPDS_NAVIGATION_TYPE) { continue; }
+
+ return $link->hrefXhtml ();
+ }
+ return "#";
+ }
+
+ public function __construct($ptitle, $pid, $pcontent, $pcontentType, $plinkArray, $pclass = "", $pcount = 0) {
+ global $config;
+ $this->title = $ptitle;
+ $this->id = $pid;
+ $this->content = $pcontent;
+ $this->contentType = $pcontentType;
+ $this->linkArray = $plinkArray;
+ $this->className = $pclass;
+ $this->numberOfElement = $pcount;
+
+ if ($config['cops_show_icons'] == 1)
+ {
+ foreach (self::$icons as $reg => $image)
+ {
+ if (preg_match ("/" . $reg . "/", $pid)) {
+ array_push ($this->linkArray, new Link (getUrlWithVersion ($image), "image/png", Link::OPDS_THUMBNAIL_TYPE));
+ break;
+ }
+ }
+ }
+
+ if (!is_null (GetUrlParam (DB))) $this->id = str_replace ("cops:", "cops:" . GetUrlParam (DB) . ":", $this->id);
+ }
+}
diff --git a/sources/lib/EntryBook.php b/sources/lib/EntryBook.php
new file mode 100644
index 0000000..04d1cb6
--- /dev/null
+++ b/sources/lib/EntryBook.php
@@ -0,0 +1,47 @@
+
+ */
+
+class EntryBook extends Entry
+{
+ public $book;
+
+ /**
+ * EntryBook constructor.
+ * @param string $ptitle
+ * @param integer $pid
+ * @param string $pcontent
+ * @param string $pcontentType
+ * @param array $plinkArray
+ * @param Book $pbook
+ */
+ public function __construct($ptitle, $pid, $pcontent, $pcontentType, $plinkArray, $pbook) {
+ parent::__construct ($ptitle, $pid, $pcontent, $pcontentType, $plinkArray);
+ $this->book = $pbook;
+ $this->localUpdated = $pbook->timestamp;
+ }
+
+ public function getCoverThumbnail () {
+ foreach ($this->linkArray as $link) {
+ /* @var $link LinkNavigation */
+
+ if ($link->rel == Link::OPDS_THUMBNAIL_TYPE)
+ return $link->hrefXhtml ();
+ }
+ return null;
+ }
+
+ public function getCover () {
+ foreach ($this->linkArray as $link) {
+ /* @var $link LinkNavigation */
+
+ if ($link->rel == Link::OPDS_IMAGE_TYPE)
+ return $link->hrefXhtml ();
+ }
+ return null;
+ }
+}
diff --git a/sources/lib/JSON_renderer.php b/sources/lib/JSON_renderer.php
new file mode 100644
index 0000000..4df3df5
--- /dev/null
+++ b/sources/lib/JSON_renderer.php
@@ -0,0 +1,254 @@
+
+ */
+
+require_once dirname(__FILE__) . '/../base.php';
+
+class JSONRenderer
+{
+ /**
+ * @param Book $book
+ * @return array
+ */
+ public static function getBookContentArray ($book) {
+ global $config;
+ $i = 0;
+ $preferedData = array ();
+ foreach ($config['cops_prefered_format'] as $format)
+ {
+ if ($i == 2) { break; }
+ if ($data = $book->getDataFormat ($format)) {
+ $i++;
+ array_push ($preferedData, array ("url" => $data->getHtmlLink (), "name" => $format));
+ }
+ }
+
+ $publisher = $book->getPublisher();
+ if (is_null ($publisher)) {
+ $pn = "";
+ $pu = "";
+ } else {
+ $pn = $publisher->name;
+ $link = new LinkNavigation ($publisher->getUri ());
+ $pu = $link->hrefXhtml ();
+ }
+
+ $serie = $book->getSerie ();
+ if (is_null ($serie)) {
+ $sn = "";
+ $scn = "";
+ $su = "";
+ } else {
+ $sn = $serie->name;
+ $scn = str_format (localize ("content.series.data"), $book->seriesIndex, $serie->name);
+ $link = new LinkNavigation ($serie->getUri ());
+ $su = $link->hrefXhtml ();
+ }
+ $cc = $book->getCustomColumnValues($config['cops_calibre_custom_column_list'], true);
+
+ return array ("id" => $book->id,
+ "hasCover" => $book->hasCover,
+ "preferedData" => $preferedData,
+ "rating" => $book->getRating (),
+ "publisherName" => $pn,
+ "publisherurl" => $pu,
+ "pubDate" => $book->getPubDate (),
+ "languagesName" => $book->getLanguages (),
+ "authorsName" => $book->getAuthorsName (),
+ "tagsName" => $book->getTagsName (),
+ "seriesName" => $sn,
+ "seriesIndex" => $book->seriesIndex,
+ "seriesCompleteName" => $scn,
+ "seriesurl" => $su,
+ "customcolumns_list" => $cc);
+ }
+
+ /**
+ * @param Book $book
+ * @return array
+ */
+ public static function getFullBookContentArray ($book) {
+ global $config;
+ $out = self::getBookContentArray ($book);
+ $database = GetUrlParam (DB);
+
+ $out ["coverurl"] = Data::getLink ($book, "jpg", "image/jpeg", Link::OPDS_IMAGE_TYPE, "cover.jpg", NULL)->hrefXhtml ();
+ $out ["thumbnailurl"] = Data::getLink ($book, "jpg", "image/jpeg", Link::OPDS_THUMBNAIL_TYPE, "cover.jpg", NULL, NULL, $config['cops_html_thumbnail_height'] * 2)->hrefXhtml ();
+ $out ["content"] = $book->getComment (false);
+ $out ["datas"] = array ();
+ $dataKindle = $book->GetMostInterestingDataToSendToKindle ();
+ foreach ($book->getDatas() as $data) {
+ $tab = array ("id" => $data->id, "format" => $data->format, "url" => $data->getHtmlLink (), "mail" => 0, "readerUrl" => "");
+ if (!empty ($config['cops_mail_configuration']) && !is_null ($dataKindle) && $data->id == $dataKindle->id) {
+ $tab ["mail"] = 1;
+ }
+ if ($data->format == "EPUB") {
+ $tab ["readerUrl"] = "epubreader.php?data={$data->id}&db={$database}";
+ }
+ array_push ($out ["datas"], $tab);
+ }
+ $out ["authors"] = array ();
+ foreach ($book->getAuthors () as $author) {
+ $link = new LinkNavigation ($author->getUri ());
+ array_push ($out ["authors"], array ("name" => $author->name, "url" => $link->hrefXhtml ()));
+ }
+ $out ["tags"] = array ();
+ foreach ($book->getTags () as $tag) {
+ $link = new LinkNavigation ($tag->getUri ());
+ array_push ($out ["tags"], array ("name" => $tag->name, "url" => $link->hrefXhtml ()));
+ }
+ $out ["customcolumns_preview"] = $book->getCustomColumnValues($config['cops_calibre_custom_column_preview'], true);
+
+ return $out;
+ }
+
+ public static function getContentArray ($entry) {
+ if ($entry instanceof EntryBook) {
+ $out = array ( "title" => $entry->title);
+ $out ["book"] = self::getBookContentArray ($entry->book);
+ return $out;
+ }
+ return array ( "title" => $entry->title, "content" => $entry->content, "navlink" => $entry->getNavLink (), "number" => $entry->numberOfElement );
+ }
+
+ public static function getContentArrayTypeahead ($page) {
+ $out = array ();
+ foreach ($page->entryArray as $entry) {
+ if ($entry instanceof EntryBook) {
+ array_push ($out, array ("class" => $entry->className, "title" => $entry->title, "navlink" => $entry->book->getDetailUrl ()));
+ } else {
+ if (empty ($entry->className) xor Base::noDatabaseSelected ()) {
+ array_push ($out, array ("class" => $entry->className, "title" => $entry->title, "navlink" => $entry->getNavLink ()));
+ } else {
+ array_push ($out, array ("class" => $entry->className, "title" => $entry->content, "navlink" => $entry->getNavLink ()));
+ }
+ }
+ }
+ return $out;
+ }
+
+ public static function addCompleteArray ($in) {
+ global $config;
+ $out = $in;
+
+ $out ["c"] = array ("version" => VERSION, "i18n" => array (
+ "coverAlt" => localize("i18n.coversection"),
+ "authorsTitle" => localize("authors.title"),
+ "bookwordTitle" => localize("bookword.title"),
+ "tagsTitle" => localize("tags.title"),
+ "seriesTitle" => localize("series.title"),
+ "customizeTitle" => localize ("customize.title"),
+ "aboutTitle" => localize ("about.title"),
+ "previousAlt" => localize ("paging.previous.alternate"),
+ "nextAlt" => localize ("paging.next.alternate"),
+ "searchAlt" => localize ("search.alternate"),
+ "sortAlt" => localize ("sort.alternate"),
+ "homeAlt" => localize ("home.alternate"),
+ "cogAlt" => localize ("cog.alternate"),
+ "permalinkAlt" => localize ("permalink.alternate"),
+ "publisherName" => localize("publisher.name"),
+ "pubdateTitle" => localize("pubdate.title"),
+ "languagesTitle" => localize("language.title"),
+ "contentTitle" => localize("content.summary"),
+ "sortorderAsc" => localize("search.sortorder.asc"),
+ "sortorderDesc" => localize("search.sortorder.desc"),
+ "customizeEmail" => localize("customize.email")),
+ "url" => array (
+ "detailUrl" => "index.php?page=13&id={0}&db={1}",
+ "coverUrl" => "fetch.php?id={0}&db={1}",
+ "thumbnailUrl" => "fetch.php?height=" . $config['cops_html_thumbnail_height'] . "&id={0}&db={1}"),
+ "config" => array (
+ "use_fancyapps" => $config ["cops_use_fancyapps"],
+ "max_item_per_page" => $config['cops_max_item_per_page'],
+ "kindleHack" => "",
+ "server_side_rendering" => useServerSideRendering (),
+ "html_tag_filter" => $config['cops_html_tag_filter']));
+ if ($config['cops_thumbnail_handling'] == "1") {
+ $out ["c"]["url"]["thumbnailUrl"] = $out ["c"]["url"]["coverUrl"];
+ } else if (!empty ($config['cops_thumbnail_handling'])) {
+ $out ["c"]["url"]["thumbnailUrl"] = $config['cops_thumbnail_handling'];
+ }
+ if (preg_match("/./", $_SERVER['HTTP_USER_AGENT'])) {
+ $out ["c"]["config"]["kindleHack"] = 'style="text-decoration: none !important;"';
+ }
+ return $out;
+ }
+
+ public static function getJson ($complete = false) {
+ global $config;
+ $page = getURLParam ("page", Base::PAGE_INDEX);
+ $query = getURLParam ("query");
+ $search = getURLParam ("search");
+ $qid = getURLParam ("id");
+ $n = getURLParam ("n", "1");
+ $database = GetUrlParam (DB);
+
+ $currentPage = Page::getPage ($page, $qid, $query, $n);
+ $currentPage->InitializeContent ();
+
+ if ($search) {
+ return self::getContentArrayTypeahead ($currentPage);
+ }
+
+ $out = array ( "title" => $currentPage->title);
+ $entries = array ();
+ foreach ($currentPage->entryArray as $entry) {
+ array_push ($entries, self::getContentArray ($entry));
+ }
+ if (!is_null ($currentPage->book)) {
+ $out ["book"] = self::getFullBookContentArray ($currentPage->book);
+ }
+ $out ["databaseId"] = GetUrlParam (DB, "");
+ $out ["databaseName"] = Base::getDbName ();
+ if ($out ["databaseId"] == "") {
+ $out ["databaseName"] = "";
+ }
+ $out ["fullTitle"] = $out ["title"];
+ if ($out ["databaseId"] != "" && $out ["databaseName"] != $out ["fullTitle"]) {
+ $out ["fullTitle"] = $out ["databaseName"] . " > " . $out ["fullTitle"];
+ }
+ $out ["page"] = $page;
+ $out ["multipleDatabase"] = Base::isMultipleDatabaseEnabled () ? 1 : 0;
+ $out ["entries"] = $entries;
+ $out ["isPaginated"] = 0;
+ if ($currentPage->isPaginated ()) {
+ $prevLink = $currentPage->getPrevLink ();
+ $nextLink = $currentPage->getNextLink ();
+ $out ["isPaginated"] = 1;
+ $out ["prevLink"] = "";
+ if (!is_null ($prevLink)) {
+ $out ["prevLink"] = $prevLink->hrefXhtml ();
+ }
+ $out ["nextLink"] = "";
+ if (!is_null ($nextLink)) {
+ $out ["nextLink"] = $nextLink->hrefXhtml ();
+ }
+ $out ["maxPage"] = $currentPage->getMaxPage ();
+ $out ["currentPage"] = $currentPage->n;
+ }
+ if (!is_null (getURLParam ("complete")) || $complete) {
+ $out = self::addCompleteArray ($out);
+ }
+
+ $out ["containsBook"] = 0;
+ if ($currentPage->containsBook ()) {
+ $out ["containsBook"] = 1;
+ }
+
+ $out["abouturl"] = "index.php" . addURLParameter ("?page=" . Base::PAGE_ABOUT, DB, $database);
+
+ if ($page == Base::PAGE_ABOUT) {
+ $temp = preg_replace ("/\About COPS\<\/h1\>/", "About COPS " . VERSION . " ", file_get_contents('about.html'));
+ $out ["fullhtml"] = $temp;
+ }
+
+ $out ["homeurl"] = "index.php";
+ if ($page != Base::PAGE_INDEX && !is_null ($database)) $out ["homeurl"] = $out ["homeurl"] . "?" . addURLParameter ("", DB, $database);
+
+ return $out;
+ }
+}
\ No newline at end of file
diff --git a/sources/lib/Language.php b/sources/lib/Language.php
new file mode 100644
index 0000000..bdf5dc8
--- /dev/null
+++ b/sources/lib/Language.php
@@ -0,0 +1,69 @@
+
+ */
+
+class Language extends Base
+{
+ const ALL_LANGUAGES_ID = "cops:languages";
+
+ public $id;
+ public $lang_code;
+
+ public function __construct($pid, $plang_code) {
+ $this->id = $pid;
+ $this->lang_code = $plang_code;
+ }
+
+ public function getUri () {
+ return "?page=".parent::PAGE_LANGUAGE_DETAIL."&id=$this->id";
+ }
+
+ public function getEntryId () {
+ return self::ALL_LANGUAGES_ID.":".$this->id;
+ }
+
+ public static function getLanguageString ($code) {
+ $string = localize("languages.".$code);
+ if (preg_match ("/^languages/", $string)) {
+ return $code;
+ }
+ return $string;
+ }
+
+ public static function getCount() {
+ // str_format (localize("languages.alphabetical", count(array))
+ return parent::getCountGeneric ("languages", self::ALL_LANGUAGES_ID, parent::PAGE_ALL_LANGUAGES);
+ }
+
+ public static function getLanguageById ($languageId) {
+ $result = parent::getDb ()->prepare('select id, lang_code from languages where id = ?');
+ $result->execute (array ($languageId));
+ if ($post = $result->fetchObject ()) {
+ return new Language ($post->id, Language::getLanguageString ($post->lang_code));
+ }
+ return NULL;
+ }
+
+
+
+ public static function getAllLanguages() {
+ $result = parent::getDb ()->query('select languages.id as id, languages.lang_code as lang_code, count(*) as count
+from languages, books_languages_link
+where languages.id = books_languages_link.lang_code
+group by languages.id, books_languages_link.lang_code
+order by languages.lang_code');
+ $entryArray = array();
+ while ($post = $result->fetchObject ())
+ {
+ $language = new Language ($post->id, $post->lang_code);
+ array_push ($entryArray, new Entry (Language::getLanguageString ($language->lang_code), $language->getEntryId (),
+ str_format (localize("bookword", $post->count), $post->count), "text",
+ array ( new LinkNavigation ($language->getUri ())), "", $post->count));
+ }
+ return $entryArray;
+ }
+}
diff --git a/sources/lib/Link.php b/sources/lib/Link.php
new file mode 100644
index 0000000..15d483a
--- /dev/null
+++ b/sources/lib/Link.php
@@ -0,0 +1,41 @@
+
+ */
+
+class Link
+{
+ const OPDS_THUMBNAIL_TYPE = "http://opds-spec.org/image/thumbnail";
+ const OPDS_IMAGE_TYPE = "http://opds-spec.org/image";
+ const OPDS_ACQUISITION_TYPE = "http://opds-spec.org/acquisition";
+ const OPDS_NAVIGATION_TYPE = "application/atom+xml;profile=opds-catalog;kind=navigation";
+ const OPDS_PAGING_TYPE = "application/atom+xml;profile=opds-catalog;kind=acquisition";
+
+ public $href;
+ public $type;
+ public $rel;
+ public $title;
+ public $facetGroup;
+ public $activeFacet;
+
+ public function __construct($phref, $ptype, $prel = NULL, $ptitle = NULL, $pfacetGroup = NULL, $pactiveFacet = FALSE) {
+ $this->href = $phref;
+ $this->type = $ptype;
+ $this->rel = $prel;
+ $this->title = $ptitle;
+ $this->facetGroup = $pfacetGroup;
+ $this->activeFacet = $pactiveFacet;
+ }
+
+ public function hrefXhtml () {
+ return $this->href;
+ }
+
+ public function getScriptName() {
+ $parts = explode('/', $_SERVER["SCRIPT_NAME"]);
+ return $parts[count($parts) - 1];
+ }
+}
diff --git a/sources/lib/LinkFacet.php b/sources/lib/LinkFacet.php
new file mode 100644
index 0000000..9ea9d63
--- /dev/null
+++ b/sources/lib/LinkFacet.php
@@ -0,0 +1,16 @@
+
+ */
+
+class LinkFacet extends Link
+{
+ public function __construct($phref, $ptitle = NULL, $pfacetGroup = NULL, $pactiveFacet = FALSE) {
+ parent::__construct ($phref, Link::OPDS_PAGING_TYPE, "http://opds-spec.org/facet", $ptitle, $pfacetGroup, $pactiveFacet);
+ if (!is_null (GetUrlParam (DB))) $this->href = addURLParameter ($this->href, DB, GetUrlParam (DB));
+ $this->href = parent::getScriptName() . $this->href;
+ }
+}
diff --git a/sources/lib/LinkNavigation.php b/sources/lib/LinkNavigation.php
new file mode 100644
index 0000000..98e6f93
--- /dev/null
+++ b/sources/lib/LinkNavigation.php
@@ -0,0 +1,21 @@
+
+ */
+
+class LinkNavigation extends Link
+{
+ public function __construct($phref, $prel = NULL, $ptitle = NULL) {
+ parent::__construct ($phref, Link::OPDS_NAVIGATION_TYPE, $prel, $ptitle);
+ if (!is_null (GetUrlParam (DB))) $this->href = addURLParameter ($this->href, DB, GetUrlParam (DB));
+ if (!preg_match ("#^\?(.*)#", $this->href) && !empty ($this->href)) $this->href = "?" . $this->href;
+ if (preg_match ("/(bookdetail|getJSON).php/", parent::getScriptName())) {
+ $this->href = "index.php" . $this->href;
+ } else {
+ $this->href = parent::getScriptName() . $this->href;
+ }
+ }
+}
diff --git a/sources/lib/OPDS_renderer.php b/sources/lib/OPDS_renderer.php
new file mode 100644
index 0000000..893e342
--- /dev/null
+++ b/sources/lib/OPDS_renderer.php
@@ -0,0 +1,278 @@
+
+ */
+
+require_once dirname(__FILE__) . '/../base.php';
+
+class OPDSRenderer
+{
+ private $xmlStream = NULL;
+ private $updated = NULL;
+
+ private function getUpdatedTime () {
+ if (is_null ($this->updated)) {
+ $this->updated = time();
+ }
+ return date (DATE_ATOM, $this->updated);
+ }
+
+ private function getXmlStream () {
+ if (is_null ($this->xmlStream)) {
+ $this->xmlStream = new XMLWriter();
+ $this->xmlStream->openMemory();
+ $this->xmlStream->setIndent (true);
+ }
+ return $this->xmlStream;
+ }
+
+ public function getOpenSearch () {
+ global $config;
+ $xml = new XMLWriter ();
+ $xml->openMemory ();
+ $xml->setIndent (true);
+ $xml->startDocument('1.0','UTF-8');
+ $xml->startElement ("OpenSearchDescription");
+ $xml->writeAttribute ("xmlns", "http://a9.com/-/spec/opensearch/1.1/");
+ $xml->startElement ("ShortName");
+ $xml->text ("My catalog");
+ $xml->endElement ();
+ $xml->startElement ("Description");
+ $xml->text ("Search for ebooks");
+ $xml->endElement ();
+ $xml->startElement ("InputEncoding");
+ $xml->text ("UTF-8");
+ $xml->endElement ();
+ $xml->startElement ("OutputEncoding");
+ $xml->text ("UTF-8");
+ $xml->endElement ();
+ $xml->startElement ("Image");
+ $xml->writeAttribute ("type", "image/x-icon");
+ $xml->writeAttribute ("width", "16");
+ $xml->writeAttribute ("height", "16");
+ $xml->text ($config['cops_icon']);
+ $xml->endElement ();
+ $xml->startElement ("Url");
+ $xml->writeAttribute ("type", 'application/atom+xml');
+ $urlparam = "?query={searchTerms}";
+ if (!is_null (GetUrlParam (DB))) $urlparam = addURLParameter ($urlparam, DB, GetUrlParam (DB));
+ $urlparam = str_replace ("%7B", "{", $urlparam);
+ $urlparam = str_replace ("%7D", "}", $urlparam);
+ $xml->writeAttribute ("template", $config['cops_full_url'] . 'feed.php' . $urlparam);
+ $xml->endElement ();
+ $xml->startElement ("Query");
+ $xml->writeAttribute ("role", "example");
+ $xml->writeAttribute ("searchTerms", "robot");
+ $xml->endElement ();
+ $xml->endElement ();
+ $xml->endDocument();
+ return $xml->outputMemory(true);
+ }
+
+ private function startXmlDocument ($page) {
+ global $config;
+ self::getXmlStream ()->startDocument('1.0','UTF-8');
+ self::getXmlStream ()->startElement ("feed");
+ self::getXmlStream ()->writeAttribute ("xmlns", "http://www.w3.org/2005/Atom");
+ self::getXmlStream ()->writeAttribute ("xmlns:xhtml", "http://www.w3.org/1999/xhtml");
+ self::getXmlStream ()->writeAttribute ("xmlns:opds", "http://opds-spec.org/2010/catalog");
+ self::getXmlStream ()->writeAttribute ("xmlns:opensearch", "http://a9.com/-/spec/opensearch/1.1/");
+ self::getXmlStream ()->writeAttribute ("xmlns:dcterms", "http://purl.org/dc/terms/");
+ self::getXmlStream ()->startElement ("title");
+ self::getXmlStream ()->text ($page->title);
+ self::getXmlStream ()->endElement ();
+ if ($page->subtitle != "")
+ {
+ self::getXmlStream ()->startElement ("subtitle");
+ self::getXmlStream ()->text ($page->subtitle);
+ self::getXmlStream ()->endElement ();
+ }
+ self::getXmlStream ()->startElement ("id");
+ if ($page->idPage)
+ {
+ $idPage = $page->idPage;
+ if (!is_null (GetUrlParam (DB))) $idPage = str_replace ("cops:", "cops:" . GetUrlParam (DB) . ":", $idPage);
+ self::getXmlStream ()->text ($idPage);
+ }
+ else
+ {
+ self::getXmlStream ()->text ($_SERVER['REQUEST_URI']);
+ }
+ self::getXmlStream ()->endElement ();
+ self::getXmlStream ()->startElement ("updated");
+ self::getXmlStream ()->text (self::getUpdatedTime ());
+ self::getXmlStream ()->endElement ();
+ self::getXmlStream ()->startElement ("icon");
+ self::getXmlStream ()->text ($page->favicon);
+ self::getXmlStream ()->endElement ();
+ self::getXmlStream ()->startElement ("author");
+ self::getXmlStream ()->startElement ("name");
+ self::getXmlStream ()->text ($page->authorName);
+ self::getXmlStream ()->endElement ();
+ self::getXmlStream ()->startElement ("uri");
+ self::getXmlStream ()->text ($page->authorUri);
+ self::getXmlStream ()->endElement ();
+ self::getXmlStream ()->startElement ("email");
+ self::getXmlStream ()->text ($page->authorEmail);
+ self::getXmlStream ()->endElement ();
+ self::getXmlStream ()->endElement ();
+ $link = new LinkNavigation ("", "start", "Home");
+ self::renderLink ($link);
+ $link = new LinkNavigation ("?" . getQueryString (), "self");
+ self::renderLink ($link);
+ $urlparam = "?";
+ if (!is_null (GetUrlParam (DB))) $urlparam = addURLParameter ($urlparam, DB, GetUrlParam (DB));
+ if ($config['cops_generate_invalid_opds_stream'] == 0 || preg_match("/(MantanoReader|FBReader)/", $_SERVER['HTTP_USER_AGENT'])) {
+ // Good and compliant way of handling search
+ $urlparam = addURLParameter ($urlparam, "page", Base::PAGE_OPENSEARCH);
+ $link = new Link ("feed.php" . $urlparam, "application/opensearchdescription+xml", "search", "Search here");
+ }
+ else
+ {
+ // Bad way, will be removed when OPDS client are fixed
+ $urlparam = addURLParameter ($urlparam, "query", "{searchTerms}");
+ $urlparam = str_replace ("%7B", "{", $urlparam);
+ $urlparam = str_replace ("%7D", "}", $urlparam);
+ $link = new Link ($config['cops_full_url'] . 'feed.php' . $urlparam, "application/atom+xml", "search", "Search here");
+ }
+ self::renderLink ($link);
+ if ($page->containsBook () && !is_null ($config['cops_books_filter']) && count ($config['cops_books_filter']) > 0) {
+ $Urlfilter = getURLParam ("tag", "");
+ foreach ($config['cops_books_filter'] as $lib => $filter) {
+ $link = new LinkFacet ("?" . addURLParameter (getQueryString (), "tag", $filter), $lib, localize ("tagword.title"), $filter == $Urlfilter);
+ self::renderLink ($link);
+ }
+ }
+ }
+
+ private function endXmlDocument () {
+ self::getXmlStream ()->endElement ();
+ self::getXmlStream ()->endDocument ();
+ return self::getXmlStream ()->outputMemory(true);
+ }
+
+ private function renderLink ($link) {
+ self::getXmlStream ()->startElement ("link");
+ self::getXmlStream ()->writeAttribute ("href", $link->href);
+ self::getXmlStream ()->writeAttribute ("type", $link->type);
+ if (!is_null ($link->rel)) {
+ self::getXmlStream ()->writeAttribute ("rel", $link->rel);
+ }
+ if (!is_null ($link->title)) {
+ self::getXmlStream ()->writeAttribute ("title", $link->title);
+ }
+ if (!is_null ($link->facetGroup)) {
+ self::getXmlStream ()->writeAttribute ("opds:facetGroup", $link->facetGroup);
+ }
+ if ($link->activeFacet) {
+ self::getXmlStream ()->writeAttribute ("opds:activeFacet", "true");
+ }
+ self::getXmlStream ()->endElement ();
+ }
+
+ private function getPublicationDate($book) {
+ $dateYmd = substr($book->pubdate, 0, 10);
+ $pubdate = \DateTime::createFromFormat('Y-m-d', $dateYmd);
+ if ($pubdate === false ||
+ $pubdate->format ("Y") == "0101" ||
+ $pubdate->format ("Y") == "0100") {
+ return "";
+ }
+ return $pubdate->format("Y-m-d");
+ }
+
+ private function renderEntry ($entry) {
+ self::getXmlStream ()->startElement ("title");
+ self::getXmlStream ()->text ($entry->title);
+ self::getXmlStream ()->endElement ();
+ self::getXmlStream ()->startElement ("updated");
+ self::getXmlStream ()->text (self::getUpdatedTime ());
+ self::getXmlStream ()->endElement ();
+ self::getXmlStream ()->startElement ("id");
+ self::getXmlStream ()->text ($entry->id);
+ self::getXmlStream ()->endElement ();
+ self::getXmlStream ()->startElement ("content");
+ self::getXmlStream ()->writeAttribute ("type", $entry->contentType);
+ if ($entry->contentType == "text") {
+ self::getXmlStream ()->text ($entry->content);
+ } else {
+ self::getXmlStream ()->writeRaw ($entry->content);
+ }
+ self::getXmlStream ()->endElement ();
+ foreach ($entry->linkArray as $link) {
+ self::renderLink ($link);
+ }
+
+ if (get_class ($entry) != "EntryBook") {
+ return;
+ }
+
+ foreach ($entry->book->getAuthors () as $author) {
+ self::getXmlStream ()->startElement ("author");
+ self::getXmlStream ()->startElement ("name");
+ self::getXmlStream ()->text ($author->name);
+ self::getXmlStream ()->endElement ();
+ self::getXmlStream ()->startElement ("uri");
+ self::getXmlStream ()->text ("feed.php" . $author->getUri ());
+ self::getXmlStream ()->endElement ();
+ self::getXmlStream ()->endElement ();
+ }
+ foreach ($entry->book->getTags () as $category) {
+ self::getXmlStream ()->startElement ("category");
+ self::getXmlStream ()->writeAttribute ("term", $category->name);
+ self::getXmlStream ()->writeAttribute ("label", $category->name);
+ self::getXmlStream ()->endElement ();
+ }
+ if ($entry->book->getPubDate () != "") {
+ self::getXmlStream ()->startElement ("dcterms:issued");
+ self::getXmlStream ()->text (self::getPublicationDate($entry->book));
+ self::getXmlStream ()->endElement ();
+ self::getXmlStream ()->startElement ("published");
+ self::getXmlStream ()->text (self::getPublicationDate($entry->book) . "T08:08:08Z");
+ self::getXmlStream ()->endElement ();
+ }
+
+ $lang = $entry->book->getLanguages ();
+ if (!empty ($lang)) {
+ self::getXmlStream ()->startElement ("dcterms:language");
+ self::getXmlStream ()->text ($lang);
+ self::getXmlStream ()->endElement ();
+ }
+
+ }
+
+ public function render ($page) {
+ global $config;
+ self::startXmlDocument ($page);
+ if ($page->isPaginated ())
+ {
+ self::getXmlStream ()->startElement ("opensearch:totalResults");
+ self::getXmlStream ()->text ($page->totalNumber);
+ self::getXmlStream ()->endElement ();
+ self::getXmlStream ()->startElement ("opensearch:itemsPerPage");
+ self::getXmlStream ()->text ($config['cops_max_item_per_page']);
+ self::getXmlStream ()->endElement ();
+ self::getXmlStream ()->startElement ("opensearch:startIndex");
+ self::getXmlStream ()->text (($page->n - 1) * $config['cops_max_item_per_page'] + 1);
+ self::getXmlStream ()->endElement ();
+ $prevLink = $page->getPrevLink ();
+ $nextLink = $page->getNextLink ();
+ if (!is_null ($prevLink)) {
+ self::renderLink ($prevLink);
+ }
+ if (!is_null ($nextLink)) {
+ self::renderLink ($nextLink);
+ }
+ }
+ foreach ($page->entryArray as $entry) {
+ self::getXmlStream ()->startElement ("entry");
+ self::renderEntry ($entry);
+ self::getXmlStream ()->endElement ();
+ }
+ return self::endXmlDocument ();
+ }
+}
+
diff --git a/sources/lib/Page.php b/sources/lib/Page.php
new file mode 100644
index 0000000..4fb4b17
--- /dev/null
+++ b/sources/lib/Page.php
@@ -0,0 +1,180 @@
+
+ */
+
+class Page
+{
+ public $title;
+ public $subtitle = "";
+ public $authorName = "";
+ public $authorUri = "";
+ public $authorEmail = "";
+ public $idPage;
+ public $idGet;
+ public $query;
+ public $favicon;
+ public $n;
+ public $book;
+ public $totalNumber = -1;
+
+ /* @var Entry[] */
+ public $entryArray = array();
+
+ public static function getPage ($pageId, $id, $query, $n)
+ {
+ switch ($pageId) {
+ case Base::PAGE_ALL_AUTHORS :
+ return new PageAllAuthors ($id, $query, $n);
+ case Base::PAGE_AUTHORS_FIRST_LETTER :
+ return new PageAllAuthorsLetter ($id, $query, $n);
+ case Base::PAGE_AUTHOR_DETAIL :
+ return new PageAuthorDetail ($id, $query, $n);
+ case Base::PAGE_ALL_TAGS :
+ return new PageAllTags ($id, $query, $n);
+ case Base::PAGE_TAG_DETAIL :
+ return new PageTagDetail ($id, $query, $n);
+ case Base::PAGE_ALL_LANGUAGES :
+ return new PageAllLanguages ($id, $query, $n);
+ case Base::PAGE_LANGUAGE_DETAIL :
+ return new PageLanguageDetail ($id, $query, $n);
+ case Base::PAGE_ALL_CUSTOMS :
+ return new PageAllCustoms ($id, $query, $n);
+ case Base::PAGE_CUSTOM_DETAIL :
+ return new PageCustomDetail ($id, $query, $n);
+ case Base::PAGE_ALL_RATINGS :
+ return new PageAllRating ($id, $query, $n);
+ case Base::PAGE_RATING_DETAIL :
+ return new PageRatingDetail ($id, $query, $n);
+ case Base::PAGE_ALL_SERIES :
+ return new PageAllSeries ($id, $query, $n);
+ case Base::PAGE_ALL_BOOKS :
+ return new PageAllBooks ($id, $query, $n);
+ case Base::PAGE_ALL_BOOKS_LETTER:
+ return new PageAllBooksLetter ($id, $query, $n);
+ case Base::PAGE_ALL_RECENT_BOOKS :
+ return new PageRecentBooks ($id, $query, $n);
+ case Base::PAGE_SERIE_DETAIL :
+ return new PageSerieDetail ($id, $query, $n);
+ case Base::PAGE_OPENSEARCH_QUERY :
+ return new PageQueryResult ($id, $query, $n);
+ case Base::PAGE_BOOK_DETAIL :
+ return new PageBookDetail ($id, $query, $n);
+ case Base::PAGE_ALL_PUBLISHERS:
+ return new PageAllPublishers ($id, $query, $n);
+ case Base::PAGE_PUBLISHER_DETAIL :
+ return new PagePublisherDetail ($id, $query, $n);
+ case Base::PAGE_ABOUT :
+ return new PageAbout ($id, $query, $n);
+ case Base::PAGE_CUSTOMIZE :
+ return new PageCustomize ($id, $query, $n);
+ default:
+ $page = new Page ($id, $query, $n);
+ $page->idPage = "cops:catalog";
+ return $page;
+ }
+ }
+
+ public function __construct($pid, $pquery, $pn) {
+ global $config;
+
+ $this->idGet = $pid;
+ $this->query = $pquery;
+ $this->n = $pn;
+ $this->favicon = $config['cops_icon'];
+ $this->authorName = empty($config['cops_author_name']) ? utf8_encode('Sébastien Lucas') : $config['cops_author_name'];
+ $this->authorUri = empty($config['cops_author_uri']) ? 'http://blog.slucas.fr' : $config['cops_author_uri'];
+ $this->authorEmail = empty($config['cops_author_email']) ? 'sebastien@slucas.fr' : $config['cops_author_email'];
+ }
+
+ public function InitializeContent ()
+ {
+ global $config;
+ $this->title = $config['cops_title_default'];
+ $this->subtitle = $config['cops_subtitle_default'];
+ if (Base::noDatabaseSelected ()) {
+ $i = 0;
+ foreach (Base::getDbNameList () as $key) {
+ $nBooks = Book::getBookCount ($i);
+ array_push ($this->entryArray, new Entry ($key, "cops:{$i}:catalog",
+ str_format (localize ("bookword", $nBooks), $nBooks), "text",
+ array ( new LinkNavigation ("?" . DB . "={$i}")), "", $nBooks));
+ $i++;
+ Base::clearDb ();
+ }
+ } else {
+ if (!in_array (PageQueryResult::SCOPE_AUTHOR, getCurrentOption ('ignored_categories'))) {
+ array_push ($this->entryArray, Author::getCount());
+ }
+ if (!in_array (PageQueryResult::SCOPE_SERIES, getCurrentOption ('ignored_categories'))) {
+ $series = Serie::getCount();
+ if (!is_null ($series)) array_push ($this->entryArray, $series);
+ }
+ if (!in_array (PageQueryResult::SCOPE_PUBLISHER, getCurrentOption ('ignored_categories'))) {
+ $publisher = Publisher::getCount();
+ if (!is_null ($publisher)) array_push ($this->entryArray, $publisher);
+ }
+ if (!in_array (PageQueryResult::SCOPE_TAG, getCurrentOption ('ignored_categories'))) {
+ $tags = Tag::getCount();
+ if (!is_null ($tags)) array_push ($this->entryArray, $tags);
+ }
+ if (!in_array (PageQueryResult::SCOPE_RATING, getCurrentOption ('ignored_categories'))) {
+ $rating = Rating::getCount();
+ if (!is_null ($rating)) array_push ($this->entryArray, $rating);
+ }
+ if (!in_array ("language", getCurrentOption ('ignored_categories'))) {
+ $languages = Language::getCount();
+ if (!is_null ($languages)) array_push ($this->entryArray, $languages);
+ }
+ foreach ($config['cops_calibre_custom_column'] as $lookup) {
+ $customColumn = CustomColumnType::createByLookup($lookup);
+ if (!is_null ($customColumn) && $customColumn->isSearchable()) {
+ array_push ($this->entryArray, $customColumn->getCount());
+ }
+ }
+ $this->entryArray = array_merge ($this->entryArray, Book::getCount());
+
+ if (Base::isMultipleDatabaseEnabled ()) $this->title = Base::getDbName ();
+ }
+ }
+
+ public function isPaginated ()
+ {
+ return (getCurrentOption ("max_item_per_page") != -1 &&
+ $this->totalNumber != -1 &&
+ $this->totalNumber > getCurrentOption ("max_item_per_page"));
+ }
+
+ public function getNextLink ()
+ {
+ $currentUrl = preg_replace ("/\&n=.*?$/", "", "?" . getQueryString ());
+ if (($this->n) * getCurrentOption ("max_item_per_page") < $this->totalNumber) {
+ return new LinkNavigation ($currentUrl . "&n=" . ($this->n + 1), "next", localize ("paging.next.alternate"));
+ }
+ return NULL;
+ }
+
+ public function getPrevLink ()
+ {
+ $currentUrl = preg_replace ("/\&n=.*?$/", "", "?" . getQueryString ());
+ if ($this->n > 1) {
+ return new LinkNavigation ($currentUrl . "&n=" . ($this->n - 1), "previous", localize ("paging.previous.alternate"));
+ }
+ return NULL;
+ }
+
+ public function getMaxPage ()
+ {
+ return ceil ($this->totalNumber / getCurrentOption ("max_item_per_page"));
+ }
+
+ public function containsBook ()
+ {
+ if (count ($this->entryArray) == 0) return false;
+ if (get_class ($this->entryArray [0]) == "EntryBook") return true;
+ return false;
+ }
+}
diff --git a/sources/lib/PageAbout.php b/sources/lib/PageAbout.php
new file mode 100644
index 0000000..9d10bd4
--- /dev/null
+++ b/sources/lib/PageAbout.php
@@ -0,0 +1,15 @@
+
+ */
+
+class PageAbout extends Page
+{
+ public function InitializeContent ()
+ {
+ $this->title = localize ("about.title");
+ }
+}
diff --git a/sources/lib/PageAllAuthors.php b/sources/lib/PageAllAuthors.php
new file mode 100644
index 0000000..239e7cd
--- /dev/null
+++ b/sources/lib/PageAllAuthors.php
@@ -0,0 +1,22 @@
+
+ */
+
+class PageAllAuthors extends Page
+{
+ public function InitializeContent ()
+ {
+ $this->title = localize("authors.title");
+ if (getCurrentOption ("author_split_first_letter") == 1) {
+ $this->entryArray = Author::getAllAuthorsByFirstLetter();
+ }
+ else {
+ $this->entryArray = Author::getAllAuthors();
+ }
+ $this->idPage = Author::ALL_AUTHORS_ID;
+ }
+}
diff --git a/sources/lib/PageAllAuthorsLetter.php b/sources/lib/PageAllAuthorsLetter.php
new file mode 100644
index 0000000..7acc39a
--- /dev/null
+++ b/sources/lib/PageAllAuthorsLetter.php
@@ -0,0 +1,17 @@
+
+ */
+
+class PageAllAuthorsLetter extends Page
+{
+ public function InitializeContent ()
+ {
+ $this->idPage = Author::getEntryIdByLetter ($this->idGet);
+ $this->entryArray = Author::getAuthorsByStartingLetter ($this->idGet);
+ $this->title = str_format (localize ("splitByLetter.letter"), str_format (localize ("authorword", count ($this->entryArray)), count ($this->entryArray)), $this->idGet);
+ }
+}
diff --git a/sources/lib/PageAllBooks.php b/sources/lib/PageAllBooks.php
new file mode 100644
index 0000000..88d5151
--- /dev/null
+++ b/sources/lib/PageAllBooks.php
@@ -0,0 +1,22 @@
+
+ */
+
+class PageAllBooks extends Page
+{
+ public function InitializeContent ()
+ {
+ $this->title = localize ("allbooks.title");
+ if (getCurrentOption ("titles_split_first_letter") == 1) {
+ $this->entryArray = Book::getAllBooks();
+ }
+ else {
+ list ($this->entryArray, $this->totalNumber) = Book::getBooks ($this->n);
+ }
+ $this->idPage = Book::ALL_BOOKS_ID;
+ }
+}
diff --git a/sources/lib/PageAllBooksLetter.php b/sources/lib/PageAllBooksLetter.php
new file mode 100644
index 0000000..98bd36c
--- /dev/null
+++ b/sources/lib/PageAllBooksLetter.php
@@ -0,0 +1,22 @@
+
+ */
+
+class PageAllBooksLetter extends Page
+{
+ public function InitializeContent ()
+ {
+ list ($this->entryArray, $this->totalNumber) = Book::getBooksByStartingLetter ($this->idGet, $this->n);
+ $this->idPage = Book::getEntryIdByLetter ($this->idGet);
+
+ $count = $this->totalNumber;
+ if ($count == -1)
+ $count = count ($this->entryArray);
+
+ $this->title = str_format (localize ("splitByLetter.letter"), str_format (localize ("bookword", $count), $count), $this->idGet);
+ }
+}
diff --git a/sources/lib/PageAllCustoms.php b/sources/lib/PageAllCustoms.php
new file mode 100644
index 0000000..1f7282d
--- /dev/null
+++ b/sources/lib/PageAllCustoms.php
@@ -0,0 +1,20 @@
+
+ */
+
+class PageAllCustoms extends Page
+{
+ public function InitializeContent ()
+ {
+ $customId = getURLParam ("custom", NULL);
+ $columnType = CustomColumnType::createByCustomID($customId);
+
+ $this->title = $columnType->getTitle();
+ $this->entryArray = $columnType->getAllCustomValues();
+ $this->idPage = $columnType->getAllCustomsId();
+ }
+}
diff --git a/sources/lib/PageAllLanguages.php b/sources/lib/PageAllLanguages.php
new file mode 100644
index 0000000..1217e39
--- /dev/null
+++ b/sources/lib/PageAllLanguages.php
@@ -0,0 +1,17 @@
+
+ */
+
+class PageAllLanguages extends Page
+{
+ public function InitializeContent ()
+ {
+ $this->title = localize("languages.title");
+ $this->entryArray = Language::getAllLanguages();
+ $this->idPage = Language::ALL_LANGUAGES_ID;
+ }
+}
diff --git a/sources/lib/PageAllPublishers.php b/sources/lib/PageAllPublishers.php
new file mode 100644
index 0000000..80157a6
--- /dev/null
+++ b/sources/lib/PageAllPublishers.php
@@ -0,0 +1,17 @@
+
+ */
+
+class PageAllPublishers extends Page
+{
+ public function InitializeContent ()
+ {
+ $this->title = localize("publishers.title");
+ $this->entryArray = Publisher::getAllPublishers();
+ $this->idPage = Publisher::ALL_PUBLISHERS_ID;
+ }
+}
diff --git a/sources/lib/PageAllRating.php b/sources/lib/PageAllRating.php
new file mode 100644
index 0000000..798fc3d
--- /dev/null
+++ b/sources/lib/PageAllRating.php
@@ -0,0 +1,17 @@
+
+ */
+
+class PageAllRating extends Page
+{
+ public function InitializeContent ()
+ {
+ $this->title = localize("ratings.title");
+ $this->entryArray = Rating::getAllRatings();
+ $this->idPage = Rating::ALL_RATING_ID;
+ }
+}
diff --git a/sources/lib/PageAllSeries.php b/sources/lib/PageAllSeries.php
new file mode 100644
index 0000000..5998e58
--- /dev/null
+++ b/sources/lib/PageAllSeries.php
@@ -0,0 +1,17 @@
+
+ */
+
+class PageAllSeries extends Page
+{
+ public function InitializeContent ()
+ {
+ $this->title = localize("series.title");
+ $this->entryArray = Serie::getAllSeries();
+ $this->idPage = Serie::ALL_SERIES_ID;
+ }
+}
diff --git a/sources/lib/PageAllTags.php b/sources/lib/PageAllTags.php
new file mode 100644
index 0000000..c8994af
--- /dev/null
+++ b/sources/lib/PageAllTags.php
@@ -0,0 +1,17 @@
+
+ */
+
+class PageAllTags extends Page
+{
+ public function InitializeContent ()
+ {
+ $this->title = localize("tags.title");
+ $this->entryArray = Tag::getAllTags();
+ $this->idPage = Tag::ALL_TAGS_ID;
+ }
+}
diff --git a/sources/lib/PageAuthorDetail.php b/sources/lib/PageAuthorDetail.php
new file mode 100644
index 0000000..d502390
--- /dev/null
+++ b/sources/lib/PageAuthorDetail.php
@@ -0,0 +1,18 @@
+
+ */
+
+class PageAuthorDetail extends Page
+{
+ public function InitializeContent ()
+ {
+ $author = Author::getAuthorById ($this->idGet);
+ $this->idPage = $author->getEntryId ();
+ $this->title = $author->name;
+ list ($this->entryArray, $this->totalNumber) = Book::getBooksByAuthor ($this->idGet, $this->n);
+ }
+}
diff --git a/sources/lib/PageBookDetail.php b/sources/lib/PageBookDetail.php
new file mode 100644
index 0000000..7eeadde
--- /dev/null
+++ b/sources/lib/PageBookDetail.php
@@ -0,0 +1,16 @@
+
+ */
+
+class PageBookDetail extends Page
+{
+ public function InitializeContent ()
+ {
+ $this->book = Book::getBookById ($this->idGet);
+ $this->title = $this->book->title;
+ }
+}
diff --git a/sources/lib/PageCustomDetail.php b/sources/lib/PageCustomDetail.php
new file mode 100644
index 0000000..29b33fa
--- /dev/null
+++ b/sources/lib/PageCustomDetail.php
@@ -0,0 +1,19 @@
+
+ */
+
+class PageCustomDetail extends Page
+{
+ public function InitializeContent ()
+ {
+ $customId = getURLParam ("custom", NULL);
+ $custom = CustomColumn::createCustom ($customId, $this->idGet);
+ $this->idPage = $custom->getEntryId ();
+ $this->title = $custom->value;
+ list ($this->entryArray, $this->totalNumber) = Book::getBooksByCustom ($custom, $this->idGet, $this->n);
+ }
+}
diff --git a/sources/lib/PageCustomize.php b/sources/lib/PageCustomize.php
new file mode 100644
index 0000000..80fcd14
--- /dev/null
+++ b/sources/lib/PageCustomize.php
@@ -0,0 +1,100 @@
+
+ */
+
+class PageCustomize extends Page
+{
+ private function isChecked ($key, $testedValue = 1) {
+ $value = getCurrentOption ($key);
+ if (is_array ($value)) {
+ if (in_array ($testedValue, $value)) {
+ return "checked='checked'";
+ }
+ } else {
+ if ($value == $testedValue) {
+ return "checked='checked'";
+ }
+ }
+ return "";
+ }
+
+ private function isSelected ($key, $value) {
+ if (getCurrentOption ($key) == $value) {
+ return "selected='selected'";
+ }
+ return "";
+ }
+
+ private function getStyleList () {
+ $result = array ();
+ foreach (glob ("templates/" . getCurrentTemplate () . "/styles/style-*.css") as $filename) {
+ if (preg_match ('/styles\/style-(.*?)\.css/', $filename, $m)) {
+ array_push ($result, $m [1]);
+ }
+ }
+ return $result;
+ }
+
+ public function InitializeContent ()
+ {
+ $this->title = localize ("customize.title");
+ $this->entryArray = array ();
+
+ $ignoredBaseArray = array (PageQueryResult::SCOPE_AUTHOR,
+ PageQueryResult::SCOPE_TAG,
+ PageQueryResult::SCOPE_SERIES,
+ PageQueryResult::SCOPE_PUBLISHER,
+ PageQueryResult::SCOPE_RATING,
+ "language");
+
+ $content = "";
+ array_push ($this->entryArray, new Entry ("Template", "",
+ "Click to switch to Bootstrap ", "text",
+ array ()));
+ if (!preg_match("/(Kobo|Kindle\/3.0|EBRD1101)/", $_SERVER['HTTP_USER_AGENT'])) {
+ $content .= '';
+ foreach ($this-> getStyleList () as $filename) {
+ $content .= "isSelected ("style", $filename) . ">{$filename} ";
+ }
+ $content .= ' ';
+ } else {
+ foreach ($this-> getStyleList () as $filename) {
+ $content .= " isChecked ("style", $filename) . " /> {$filename} ";
+ }
+ }
+ array_push ($this->entryArray, new Entry (localize ("customize.style"), "",
+ $content, "text",
+ array ()));
+ if (!useServerSideRendering ()) {
+ $content = ' isChecked ("use_fancyapps") . ' />';
+ array_push ($this->entryArray, new Entry (localize ("customize.fancybox"), "",
+ $content, "text",
+ array ()));
+ }
+ $content = ' ';
+ array_push ($this->entryArray, new Entry (localize ("customize.paging"), "",
+ $content, "text",
+ array ()));
+ $content = ' ';
+ array_push ($this->entryArray, new Entry (localize ("customize.email"), "",
+ $content, "text",
+ array ()));
+ $content = ' isChecked ("html_tag_filter") . ' />';
+ array_push ($this->entryArray, new Entry (localize ("customize.filter"), "",
+ $content, "text",
+ array ()));
+ $content = "";
+ foreach ($ignoredBaseArray as $key) {
+ $keyPlural = preg_replace ('/(ss)$/', 's', $key . "s");
+ $content .= ' isChecked ("ignored_categories", $key) . ' > ' . localize ("{$keyPlural}.title") . ' ';
+ }
+
+ array_push ($this->entryArray, new Entry (localize ("customize.ignored"), "",
+ $content, "text",
+ array ()));
+ }
+}
diff --git a/sources/lib/PageLanguageDetail.php b/sources/lib/PageLanguageDetail.php
new file mode 100644
index 0000000..dbbf86a
--- /dev/null
+++ b/sources/lib/PageLanguageDetail.php
@@ -0,0 +1,18 @@
+
+ */
+
+class PageLanguageDetail extends Page
+{
+ public function InitializeContent ()
+ {
+ $language = Language::getLanguageById ($this->idGet);
+ $this->idPage = $language->getEntryId ();
+ $this->title = $language->lang_code;
+ list ($this->entryArray, $this->totalNumber) = Book::getBooksByLanguage ($this->idGet, $this->n);
+ }
+}
diff --git a/sources/lib/PagePublisherDetail.php b/sources/lib/PagePublisherDetail.php
new file mode 100644
index 0000000..7b597f9
--- /dev/null
+++ b/sources/lib/PagePublisherDetail.php
@@ -0,0 +1,18 @@
+
+ */
+
+class PagePublisherDetail extends Page
+{
+ public function InitializeContent ()
+ {
+ $publisher = Publisher::getPublisherById ($this->idGet);
+ $this->title = $publisher->name;
+ list ($this->entryArray, $this->totalNumber) = Book::getBooksByPublisher ($this->idGet, $this->n);
+ $this->idPage = $publisher->getEntryId ();
+ }
+}
diff --git a/sources/lib/PageQueryResult.php b/sources/lib/PageQueryResult.php
new file mode 100644
index 0000000..7bafffc
--- /dev/null
+++ b/sources/lib/PageQueryResult.php
@@ -0,0 +1,163 @@
+
+ */
+
+class PageQueryResult extends Page
+{
+ const SCOPE_TAG = "tag";
+ const SCOPE_RATING = "rating";
+ const SCOPE_SERIES = "series";
+ const SCOPE_AUTHOR = "author";
+ const SCOPE_BOOK = "book";
+ const SCOPE_PUBLISHER = "publisher";
+
+ private function useTypeahead () {
+ return !is_null (getURLParam ("search"));
+ }
+
+ private function searchByScope ($scope, $limit = FALSE) {
+ $n = $this->n;
+ $numberPerPage = NULL;
+ $queryNormedAndUp = trim($this->query);
+ if (useNormAndUp ()) {
+ $queryNormedAndUp = normAndUp ($this->query);
+ }
+ if ($limit) {
+ $n = 1;
+ $numberPerPage = 5;
+ }
+ switch ($scope) {
+ case self::SCOPE_BOOK :
+ $array = Book::getBooksByStartingLetter ('%' . $queryNormedAndUp, $n, NULL, $numberPerPage);
+ break;
+ case self::SCOPE_AUTHOR :
+ $array = Author::getAuthorsForSearch ('%' . $queryNormedAndUp);
+ break;
+ case self::SCOPE_SERIES :
+ $array = Serie::getAllSeriesByQuery ($queryNormedAndUp);
+ break;
+ case self::SCOPE_TAG :
+ $array = Tag::getAllTagsByQuery ($queryNormedAndUp, $n, NULL, $numberPerPage);
+ break;
+ case self::SCOPE_PUBLISHER :
+ $array = Publisher::getAllPublishersByQuery ($queryNormedAndUp);
+ break;
+ default:
+ $array = Book::getBooksByQuery (
+ array ("all" => "%" . $queryNormedAndUp . "%"), $n);
+ }
+
+ return $array;
+ }
+
+ public function doSearchByCategory () {
+ $database = GetUrlParam (DB);
+ $out = array ();
+ $pagequery = Base::PAGE_OPENSEARCH_QUERY;
+ $dbArray = array ("");
+ $d = $database;
+ $query = $this->query;
+ // Special case when no databases were chosen, we search on all databases
+ if (Base::noDatabaseSelected ()) {
+ $dbArray = Base::getDbNameList ();
+ $d = 0;
+ }
+ foreach ($dbArray as $key) {
+ if (Base::noDatabaseSelected ()) {
+ array_push ($this->entryArray, new Entry ($key, DB . ":query:{$d}",
+ " ", "text",
+ array ( new LinkNavigation ("?" . DB . "={$d}")), "tt-header"));
+ Base::getDb ($d);
+ }
+ foreach (array (PageQueryResult::SCOPE_BOOK,
+ PageQueryResult::SCOPE_AUTHOR,
+ PageQueryResult::SCOPE_SERIES,
+ PageQueryResult::SCOPE_TAG,
+ PageQueryResult::SCOPE_PUBLISHER) as $key) {
+ if (in_array($key, getCurrentOption ('ignored_categories'))) {
+ continue;
+ }
+ $array = $this->searchByScope ($key, TRUE);
+
+ $i = 0;
+ if (count ($array) == 2 && is_array ($array [0])) {
+ $total = $array [1];
+ $array = $array [0];
+ } else {
+ $total = count($array);
+ }
+ if ($total > 0) {
+ // Comment to help the perl i18n script
+ // str_format (localize("bookword", count($array))
+ // str_format (localize("authorword", count($array))
+ // str_format (localize("seriesword", count($array))
+ // str_format (localize("tagword", count($array))
+ // str_format (localize("publisherword", count($array))
+ array_push ($this->entryArray, new Entry (str_format (localize ("search.result.{$key}"), $this->query), DB . ":query:{$d}:{$key}",
+ str_format (localize("{$key}word", $total), $total), "text",
+ array ( new LinkNavigation ("?page={$pagequery}&query={$query}&db={$d}&scope={$key}")),
+ Base::noDatabaseSelected () ? "" : "tt-header", $total));
+ }
+ if (!Base::noDatabaseSelected () && $this->useTypeahead ()) {
+ foreach ($array as $entry) {
+ array_push ($this->entryArray, $entry);
+ $i++;
+ if ($i > 4) { break; };
+ }
+ }
+ }
+ $d++;
+ if (Base::noDatabaseSelected ()) {
+ Base::clearDb ();
+ }
+ }
+ return $out;
+ }
+
+ public function InitializeContent ()
+ {
+ $scope = getURLParam ("scope");
+ if (empty ($scope)) {
+ $this->title = str_format (localize ("search.result"), $this->query);
+ } else {
+ // Comment to help the perl i18n script
+ // str_format (localize ("search.result.author"), $this->query)
+ // str_format (localize ("search.result.tag"), $this->query)
+ // str_format (localize ("search.result.series"), $this->query)
+ // str_format (localize ("search.result.book"), $this->query)
+ // str_format (localize ("search.result.publisher"), $this->query)
+ $this->title = str_format (localize ("search.result.{$scope}"), $this->query);
+ }
+
+ $crit = "%" . $this->query . "%";
+
+ // Special case when we are doing a search and no database is selected
+ if (Base::noDatabaseSelected () && !$this->useTypeahead ()) {
+ $i = 0;
+ foreach (Base::getDbNameList () as $key) {
+ Base::clearDb ();
+ list ($array, $totalNumber) = Book::getBooksByQuery (array ("all" => $crit), 1, $i, 1);
+ array_push ($this->entryArray, new Entry ($key, DB . ":query:{$i}",
+ str_format (localize ("bookword", $totalNumber), $totalNumber), "text",
+ array ( new LinkNavigation ("?" . DB . "={$i}&page=9&query=" . $this->query)), "", $totalNumber));
+ $i++;
+ }
+ return;
+ }
+ if (empty ($scope)) {
+ $this->doSearchByCategory ();
+ return;
+ }
+
+ $array = $this->searchByScope ($scope);
+ if (count ($array) == 2 && is_array ($array [0])) {
+ list ($this->entryArray, $this->totalNumber) = $array;
+ } else {
+ $this->entryArray = $array;
+ }
+ }
+}
diff --git a/sources/lib/PageRatingDetail.php b/sources/lib/PageRatingDetail.php
new file mode 100644
index 0000000..64c22c6
--- /dev/null
+++ b/sources/lib/PageRatingDetail.php
@@ -0,0 +1,18 @@
+
+ */
+
+class PageRatingDetail extends Page
+{
+ public function InitializeContent ()
+ {
+ $rating = Rating::getRatingById ($this->idGet);
+ $this->idPage = $rating->getEntryId ();
+ $this->title =str_format (localize ("ratingword", $rating->name/2), $rating->name/2);
+ list ($this->entryArray, $this->totalNumber) = Book::getBooksByRating ($this->idGet, $this->n);
+ }
+}
diff --git a/sources/lib/PageRecentBooks.php b/sources/lib/PageRecentBooks.php
new file mode 100644
index 0000000..2d484ed
--- /dev/null
+++ b/sources/lib/PageRecentBooks.php
@@ -0,0 +1,17 @@
+
+ */
+
+class PageRecentBooks extends Page
+{
+ public function InitializeContent ()
+ {
+ $this->title = localize ("recent.title");
+ $this->entryArray = Book::getAllRecentBooks ();
+ $this->idPage = Book::ALL_RECENT_BOOKS_ID;
+ }
+}
diff --git a/sources/lib/PageSerieDetail.php b/sources/lib/PageSerieDetail.php
new file mode 100644
index 0000000..f3d87cc
--- /dev/null
+++ b/sources/lib/PageSerieDetail.php
@@ -0,0 +1,18 @@
+
+ */
+
+class PageSerieDetail extends Page
+{
+ public function InitializeContent ()
+ {
+ $serie = Serie::getSerieById ($this->idGet);
+ $this->title = $serie->name;
+ list ($this->entryArray, $this->totalNumber) = Book::getBooksBySeries ($this->idGet, $this->n);
+ $this->idPage = $serie->getEntryId ();
+ }
+}
diff --git a/sources/lib/PageTagDetail.php b/sources/lib/PageTagDetail.php
new file mode 100644
index 0000000..0fe1c58
--- /dev/null
+++ b/sources/lib/PageTagDetail.php
@@ -0,0 +1,18 @@
+
+ */
+
+class PageTagDetail extends Page
+{
+ public function InitializeContent ()
+ {
+ $tag = Tag::getTagById ($this->idGet);
+ $this->idPage = $tag->getEntryId ();
+ $this->title = $tag->name;
+ list ($this->entryArray, $this->totalNumber) = Book::getBooksByTag ($this->idGet, $this->n);
+ }
+}
diff --git a/sources/lib/Publisher.php b/sources/lib/Publisher.php
new file mode 100644
index 0000000..892f982
--- /dev/null
+++ b/sources/lib/Publisher.php
@@ -0,0 +1,66 @@
+
+ */
+
+class Publisher extends Base
+{
+ const ALL_PUBLISHERS_ID = "cops:publishers";
+ const PUBLISHERS_COLUMNS = "publishers.id as id, publishers.name as name, count(*) as count";
+ const SQL_ALL_PUBLISHERS = "select {0} from publishers, books_publishers_link where publishers.id = publisher group by publishers.id, publishers.name order by publishers.name";
+ const SQL_PUBLISHERS_FOR_SEARCH = "select {0} from publishers, books_publishers_link where publishers.id = publisher and upper (publishers.name) like ? group by publishers.id, publishers.name order by publishers.name";
+
+
+ public $id;
+ public $name;
+
+ public function __construct($post) {
+ $this->id = $post->id;
+ $this->name = $post->name;
+ }
+
+ public function getUri () {
+ return "?page=".parent::PAGE_PUBLISHER_DETAIL."&id=$this->id";
+ }
+
+ public function getEntryId () {
+ return self::ALL_PUBLISHERS_ID.":".$this->id;
+ }
+
+ public static function getCount() {
+ // str_format (localize("publishers.alphabetical", count(array))
+ return parent::getCountGeneric ("publishers", self::ALL_PUBLISHERS_ID, parent::PAGE_ALL_PUBLISHERS);
+ }
+
+ public static function getPublisherByBookId ($bookId) {
+ $result = parent::getDb ()->prepare('select publishers.id as id, name
+from books_publishers_link, publishers
+where publishers.id = publisher and book = ?');
+ $result->execute (array ($bookId));
+ if ($post = $result->fetchObject ()) {
+ return new Publisher ($post);
+ }
+ return NULL;
+ }
+
+ public static function getPublisherById ($publisherId) {
+ $result = parent::getDb ()->prepare('select id, name
+from publishers where id = ?');
+ $result->execute (array ($publisherId));
+ if ($post = $result->fetchObject ()) {
+ return new Publisher ($post);
+ }
+ return NULL;
+ }
+
+ public static function getAllPublishers() {
+ return Base::getEntryArrayWithBookNumber (self::SQL_ALL_PUBLISHERS, self::PUBLISHERS_COLUMNS, array (), "Publisher");
+ }
+
+ public static function getAllPublishersByQuery($query) {
+ return Base::getEntryArrayWithBookNumber (self::SQL_PUBLISHERS_FOR_SEARCH, self::PUBLISHERS_COLUMNS, array ('%' . $query . '%'), "Publisher");
+ }
+}
diff --git a/sources/lib/Rating.php b/sources/lib/Rating.php
new file mode 100644
index 0000000..db79ef4
--- /dev/null
+++ b/sources/lib/Rating.php
@@ -0,0 +1,60 @@
+id = $pid;
+ $this->name = $pname;
+ }
+
+ public function getUri () {
+ return "?page=".parent::PAGE_RATING_DETAIL."&id=$this->id";
+ }
+
+ public function getEntryId () {
+ return self::ALL_RATING_ID.":".$this->id;
+ }
+
+ public static function getCount() {
+ // str_format (localize("ratings", count(array))
+ return parent::getCountGeneric ("ratings", self::ALL_RATING_ID, parent::PAGE_ALL_RATINGS, "ratings");
+ }
+
+ public static function getAllRatings() {
+ return self::getEntryArray (self::SQL_ALL_RATINGS, array ());
+ }
+
+ public static function getEntryArray ($query, $params) {
+ list (, $result) = parent::executeQuery ($query, self::RATING_COLUMNS, "", $params, -1);
+ $entryArray = array();
+ while ($post = $result->fetchObject ())
+ {
+ $ratingObj = new Rating ($post->id, $post->rating);
+ $rating=$post->rating/2;
+ $rating = str_format (localize("ratingword", $rating), $rating);
+ array_push ($entryArray, new Entry ($rating, $ratingObj->getEntryId (),
+ str_format (localize("bookword", $post->count), $post->count), "text",
+ array ( new LinkNavigation ($ratingObj->getUri ())), "", $post->count));
+ }
+ return $entryArray;
+ }
+
+ public static function getRatingById ($ratingId) {
+ $result = parent::getDb ()->prepare('select rating from ratings where id = ?');
+ $result->execute (array ($ratingId));
+ return new Rating ($ratingId, $result->fetchColumn ());
+ }
+}
diff --git a/sources/lib/Serie.php b/sources/lib/Serie.php
new file mode 100644
index 0000000..f4c1ecb
--- /dev/null
+++ b/sources/lib/Serie.php
@@ -0,0 +1,64 @@
+
+ */
+
+class Serie extends Base
+{
+ const ALL_SERIES_ID = "cops:series";
+ const SERIES_COLUMNS = "series.id as id, series.name as name, series.sort as sort, count(*) as count";
+ const SQL_ALL_SERIES = "select {0} from series, books_series_link where series.id = series group by series.id, series.name, series.sort order by series.sort";
+ const SQL_SERIES_FOR_SEARCH = "select {0} from series, books_series_link where series.id = series and upper (series.name) like ? group by series.id, series.name, series.sort order by series.sort";
+
+ public $id;
+ public $name;
+
+ public function __construct($post) {
+ $this->id = $post->id;
+ $this->name = $post->name;
+ }
+
+ public function getUri () {
+ return "?page=".parent::PAGE_SERIE_DETAIL."&id=$this->id";
+ }
+
+ public function getEntryId () {
+ return self::ALL_SERIES_ID.":".$this->id;
+ }
+
+ public static function getCount() {
+ // str_format (localize("series.alphabetical", count(array))
+ return parent::getCountGeneric ("series", self::ALL_SERIES_ID, parent::PAGE_ALL_SERIES);
+ }
+
+ public static function getSerieByBookId ($bookId) {
+ $result = parent::getDb ()->prepare('select series.id as id, name
+from books_series_link, series
+where series.id = series and book = ?');
+ $result->execute (array ($bookId));
+ if ($post = $result->fetchObject ()) {
+ return new Serie ($post);
+ }
+ return NULL;
+ }
+
+ public static function getSerieById ($serieId) {
+ $result = parent::getDb ()->prepare('select id, name from series where id = ?');
+ $result->execute (array ($serieId));
+ if ($post = $result->fetchObject ()) {
+ return new Serie ($post);
+ }
+ return NULL;
+ }
+
+ public static function getAllSeries() {
+ return Base::getEntryArrayWithBookNumber (self::SQL_ALL_SERIES, self::SERIES_COLUMNS, array (), "Serie");
+ }
+
+ public static function getAllSeriesByQuery($query) {
+ return Base::getEntryArrayWithBookNumber (self::SQL_SERIES_FOR_SEARCH, self::SERIES_COLUMNS, array ('%' . $query . '%'), "Serie");
+ }
+}
diff --git a/sources/lib/Tag.php b/sources/lib/Tag.php
new file mode 100644
index 0000000..174f121
--- /dev/null
+++ b/sources/lib/Tag.php
@@ -0,0 +1,63 @@
+
+ */
+
+class Tag extends Base
+{
+ const ALL_TAGS_ID = "cops:tags";
+ const TAG_COLUMNS = "tags.id as id, tags.name as name, count(*) as count";
+ const SQL_ALL_TAGS = "select {0} from tags, books_tags_link where tags.id = tag group by tags.id, tags.name order by tags.name";
+
+ public $id;
+ public $name;
+
+ public function __construct($post) {
+ $this->id = $post->id;
+ $this->name = $post->name;
+ }
+
+ public function getUri () {
+ return "?page=".parent::PAGE_TAG_DETAIL."&id=$this->id";
+ }
+
+ public function getEntryId () {
+ return self::ALL_TAGS_ID.":".$this->id;
+ }
+
+ public static function getCount() {
+ // str_format (localize("tags.alphabetical", count(array))
+ return parent::getCountGeneric ("tags", self::ALL_TAGS_ID, parent::PAGE_ALL_TAGS);
+ }
+
+ public static function getTagById ($tagId) {
+ $result = parent::getDb ()->prepare('select id, name from tags where id = ?');
+ $result->execute (array ($tagId));
+ if ($post = $result->fetchObject ()) {
+ return new Tag ($post);
+ }
+ return NULL;
+ }
+
+ public static function getAllTags() {
+ return Base::getEntryArrayWithBookNumber (self::SQL_ALL_TAGS, self::TAG_COLUMNS, array (), "Tag");
+ }
+
+ public static function getAllTagsByQuery($query, $n, $database = NULL, $numberPerPage = NULL) {
+ $columns = "tags.id as id, tags.name as name, (select count(*) from books_tags_link where tags.id = tag) as count";
+ $sql = 'select {0} from tags where upper (tags.name) like ? {1} order by tags.name';
+ list ($totalNumber, $result) = parent::executeQuery ($sql, $columns, "", array ('%' . $query . '%'), $n, $database, $numberPerPage);
+ $entryArray = array();
+ while ($post = $result->fetchObject ())
+ {
+ $tag = new Tag ($post);
+ array_push ($entryArray, new Entry ($tag->name, $tag->getEntryId (),
+ str_format (localize("bookword", $post->count), $post->count), "text",
+ array ( new LinkNavigation ($tag->getUri ()))));
+ }
+ return array ($entryArray, $totalNumber);
+ }
+}
diff --git a/sources/login.html b/sources/login.html
new file mode 100644
index 0000000..dfe01d7
--- /dev/null
+++ b/sources/login.html
@@ -0,0 +1,97 @@
+
+
+
+
+
+ COPS
+
+
+
+
+
+
+
+
diff --git a/sources/phpunit.xml.dist b/sources/phpunit.xml.dist
new file mode 100644
index 0000000..44059e1
--- /dev/null
+++ b/sources/phpunit.xml.dist
@@ -0,0 +1,30 @@
+
+
+
+
+
+ ./
+
+ ./resources
+ ./test
+ ./saucetest
+ ./vendor
+ config.php
+ config_default.php
+
+
+
+
+
+
+
+
+
+ ./test/
+
+
+
diff --git a/sources/resources/epub-loader/BaseExport.class.php b/sources/resources/epub-loader/BaseExport.class.php
new file mode 100644
index 0000000..95915b4
--- /dev/null
+++ b/sources/resources/epub-loader/BaseExport.class.php
@@ -0,0 +1,209 @@
+
+ */
+
+class BaseExport
+{
+ protected $mProperties = null;
+ protected $mFileName = '';
+ protected $mSearch = null;
+ protected $mReplace = null;
+
+ public $mFormatProperty = true;
+
+ /**
+ * Open an export file (or create if file does not exist)
+ *
+ * @param string Export file name
+ * @param boolean Force file creation
+ */
+ public function __construct($inFileName, $inCreate = false)
+ {
+ if ($inCreate && file_exists($inFileName)) {
+ if (!unlink($inFileName)) {
+ $error = sprintf('Cannot remove file: %s', $inFileName);
+ throw new Exception($error);
+ }
+ }
+
+ $this->mFileName = $inFileName;
+
+ $this->mProperties = array();
+ }
+
+ public function ClearProperties()
+ {
+ $this->mProperties = array();
+ }
+
+ public function SetProperty($inKey, $inValue)
+ {
+ // Don't store empty keys
+ if (empty($inKey)) {
+ return;
+ }
+
+ if ($this->mFormatProperty) {
+ $inValue = $this->FormatProperty($inValue);
+ }
+
+ $this->mProperties[$inKey] = $inValue;
+ }
+
+ /**
+ * Format a property
+ *
+ * @param string or array of strings to format
+ * @return string or array of strings formated
+ */
+ protected function FormatProperty($inValue)
+ {
+ if (!isset($inValue)) {
+ return '';
+ }
+ if (is_numeric($inValue)) {
+ return (string)$inValue;
+ }
+ if (is_array($inValue)) {
+ // Recursive call for arrays
+ foreach ($inValue as $key => $value) {
+ $inValue[$key] = $this->FormatProperty($value);
+ }
+ return $inValue;
+ }
+ if (!is_string($inValue) || empty($inValue)) {
+ return '';
+ }
+
+ // Replace html entities with normal characters
+ $str = html_entity_decode($inValue, ENT_COMPAT, 'UTF-8');
+ // Replace characters
+ if (isset($this->mSearch)) {
+ $str = str_replace($this->mSearch, $this->mReplace, $str);
+ }
+
+ // Strip double spaces
+ while (strpos($str, ' ') !== false) {
+ $str = str_replace(' ', ' ', $str);
+ }
+
+ // Trim
+ $str = trim($str);
+
+ return $str;
+ }
+
+ /**
+ * Save data to file
+ *
+ * @throws Exception if error
+ */
+ public function SaveToFile()
+ {
+ // Write the file
+ $content = $this->GetContent();
+ if (!file_put_contents($this->mFileName, $content)) {
+ $error = sprintf('Cannot save export to file: %s', $this->mFileName);
+ throw new Exception($error);
+ }
+ }
+
+ /**
+ * Send download http headers
+ *
+ * @param string $inFileName Download file name to display in the browser
+ * @param int $inFileSize Download file size
+ * @param string $inCodeSet Charset
+ * @throws exception if http headers have been already sent
+ *
+ * @return void
+ */
+ private function SendDownloadHeaders($inFileName, $inFileSize = null, $inCodeSet = 'utf-8')
+ {
+ // Throws excemtion if http headers have been already sent
+ $filename = '';
+ $linenum = 0;
+ if (headers_sent($filename, $linenum)) {
+ $error = sprintf('Http headers already sent by file: %s ligne %d', $filename, $linenum);
+ throw new Exception($error);
+ }
+
+ $inFileName = str_replace(' ', '', basename($inFileName)); // Cleanup file name
+ $ext = strtolower(substr(strrchr($inFileName, '.'), 1));
+
+ switch ($ext) {
+ case 'pdf':
+ $contentType = 'application/pdf';
+ break;
+ case 'zip':
+ $contentType = 'application/zip';
+ break;
+ case 'xml':
+ $contentType = 'text/xml';
+ if (!empty($inCodeSet)) {
+ $contentType .= '; charset=' . $inCodeSet . '"';
+ }
+ break;
+ case 'txt':
+ $contentType = 'text/plain';
+ if (!empty($inCodeSet)) {
+ $contentType .= '; charset=' . $inCodeSet . '"';
+ }
+ break;
+ case 'csv':
+ $contentType = 'text/csv';
+ if (!empty($inCodeSet)) {
+ $contentType .= '; charset=' . $inCodeSet . '"';
+ }
+ break;
+ case 'html':
+ $contentType = 'text/html';
+ if (!empty($inCodeSet)) {
+ $contentType .= '; charset=' . $inCodeSet . '"';
+ }
+ break;
+ default:
+ $contentType = 'application/force-download';
+ break;
+ }
+
+ // Send http headers for download
+ header('Content-disposition: attachment; filename="' . $inFileName . '"');
+ Header('Content-Type: ' . $contentType);
+ //header('Content-Transfer-Encoding: binary');
+ if (isset($inFileSize)) {
+ header('Content-Length: ' . $inFileSize);
+ }
+
+ // Send http headers to remove the browser cache
+ header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
+ header('Last-Modified: ' . gmdate("D, d M Y H:i:s") . ' GMT');
+ header('Cache-Control: no-store, no-cache, must-revalidate');
+ header('Cache-Control: post-check=0, pre-check=0', false);
+ header('Pragma: no-cache');
+ }
+
+ /**
+ * Download export and stop further script execution
+ */
+ public function Download()
+ {
+ $content = $this->GetContent();
+
+ // Send http download headers
+ $size = strlen($content);
+ $this->SendDownloadHeaders($this->mFileName, $size);
+
+ // Send file content to download
+ echo $content;
+
+ exit;
+ }
+
+}
+
+?>
diff --git a/sources/resources/epub-loader/BookExport.class.php b/sources/resources/epub-loader/BookExport.class.php
new file mode 100644
index 0000000..643982e
--- /dev/null
+++ b/sources/resources/epub-loader/BookExport.class.php
@@ -0,0 +1,143 @@
+
+ */
+
+require_once(realpath(dirname(__FILE__)) . '/CsvExport.class.php');
+
+class BookExport
+{
+ private $mExport = null;
+ private $mNbBook = 0;
+
+ const eExportTypeCsv = 1;
+ const CsvSeparator = "\t";
+
+ /**
+ * Open an export file (or create if file does not exist)
+ *
+ * @param string Export file name
+ * @param enum Export type
+ * @param boolean Force file creation
+ * @throws Exception if error
+ */
+ public function __construct($inFileName, $inExportType, $inCreate = false)
+ {
+ switch ($inExportType) {
+ case self::eExportTypeCsv:
+ $this->mExport = new CsvExport($inFileName, $inCreate);
+ break;
+ default:
+ $error = sprintf('Incorrect export type: %d', $inExportType);
+ throw new Exception($error);
+ }
+ }
+
+ /**
+ * Add an epub to the export
+ *
+ * @param string Epub file name
+ * @throws Exception if error
+ *
+ * @return string Empty string or error if any
+ */
+ public function AddEpub($inFileName)
+ {
+ $error = '';
+
+ try {
+ // Load the book infos
+ $bookInfos = new BookInfos();
+ $bookInfos->LoadFromEpub($inFileName);
+ // Add the book
+ $this->AddBook($bookInfos);
+ }
+ catch (Exception $e) {
+ $error = $e->getMessage();
+ }
+
+ return $error;
+ }
+
+ /**
+ * Add a new book to the export
+ *
+ * @param object BookInfo object
+ * @throws Exception if error
+ *
+ * @return void
+ */
+ private function AddBook($inBookInfo)
+ {
+ // Add export header
+ if ($this->mNbBook++ == 0) {
+ $i = 1;
+ $this->mExport->SetProperty($i++, 'Format');
+ $this->mExport->SetProperty($i++, 'Path');
+ $this->mExport->SetProperty($i++, 'Name');
+ $this->mExport->SetProperty($i++, 'Uuid');
+ $this->mExport->SetProperty($i++, 'Uri');
+ $this->mExport->SetProperty($i++, 'Title');
+ $this->mExport->SetProperty($i++, 'Authors');
+ $this->mExport->SetProperty($i++, 'AuthorsSort');
+ $this->mExport->SetProperty($i++, 'Language');
+ $this->mExport->SetProperty($i++, 'Description');
+ $this->mExport->SetProperty($i++, 'Subjects');
+ $this->mExport->SetProperty($i++, 'Cover');
+ $this->mExport->SetProperty($i++, 'Isbn');
+ $this->mExport->SetProperty($i++, 'Rights');
+ $this->mExport->SetProperty($i++, 'Publisher');
+ $this->mExport->SetProperty($i++, 'Serie');
+ $this->mExport->SetProperty($i++, 'SerieIndex');
+ $this->mExport->SetProperty($i++, 'CreationDate');
+ $this->mExport->SetProperty($i++, 'ModificationDate');
+ $this->mExport->AddContent();
+ }
+
+ // Add book infos to the export
+ $i = 1;
+ $this->mExport->SetProperty($i++, $inBookInfo->mFormat);
+ $this->mExport->SetProperty($i++, $inBookInfo->mPath);
+ $this->mExport->SetProperty($i++, $inBookInfo->mName);
+ $this->mExport->SetProperty($i++, $inBookInfo->mUuid);
+ $this->mExport->SetProperty($i++, $inBookInfo->mUri);
+ $this->mExport->SetProperty($i++, $inBookInfo->mTitle);
+ $this->mExport->SetProperty($i++, implode(' - ', $inBookInfo->mAuthors));
+ $this->mExport->SetProperty($i++, implode(' - ', array_keys($inBookInfo->mAuthors)));
+ $this->mExport->SetProperty($i++, $inBookInfo->mLanguage);
+ $this->mExport->SetProperty($i++, $inBookInfo->mDescription);
+ $this->mExport->SetProperty($i++, implode(' - ', $inBookInfo->mSubjects));
+ $this->mExport->SetProperty($i++, $inBookInfo->mCover);
+ $this->mExport->SetProperty($i++, $inBookInfo->mIsbn);
+ $this->mExport->SetProperty($i++, $inBookInfo->mRights);
+ $this->mExport->SetProperty($i++, $inBookInfo->mPublisher);
+ $this->mExport->SetProperty($i++, $inBookInfo->mSerie);
+ $this->mExport->SetProperty($i++, $inBookInfo->mSerieIndex);
+ $this->mExport->SetProperty($i++, $inBookInfo->mCreationDate);
+ $this->mExport->SetProperty($i++, $inBookInfo->mModificationDate);
+
+ $this->mExport->AddContent();
+ }
+
+ /**
+ * Download export and stop further script execution
+ */
+ public function Download()
+ {
+ $this->mExport->Download();
+ }
+
+ /**
+ * Save export to file
+ */
+ public function SaveToFile()
+ {
+ $this->mExport->SaveToFile();
+ }
+
+}
+
+?>
diff --git a/sources/resources/epub-loader/BookInfos.class.php b/sources/resources/epub-loader/BookInfos.class.php
new file mode 100644
index 0000000..a4ca0c5
--- /dev/null
+++ b/sources/resources/epub-loader/BookInfos.class.php
@@ -0,0 +1,74 @@
+
+ */
+
+require_once(realpath(dirname(__FILE__)) . '/ZipFile.class.php');
+require_once(realpath(dirname(dirname(__FILE__))) . '/php-epub-meta/epub.php');
+
+/**
+ * BookInfos class contains informations about a book,
+ * and methods to load this informations from multiple sources (eg epub file)
+ */
+class BookInfos
+{
+ public $mPath = '';
+ public $mName = '';
+ public $mFormat = '';
+ public $mUuid = '';
+ public $mUri = '';
+ public $mTitle = '';
+ public $mAuthors = null;
+ public $mLanguage = '';
+ public $mDescription = '';
+ public $mSubjects = null;
+ public $mCover = '';
+ public $mIsbn = '';
+ public $mRights = '';
+ public $mPublisher = '';
+ public $mSerie = '';
+ public $mSerieIndex = '';
+ public $mCreationDate = '';
+ public $mModificationDate = '';
+
+ /**
+ * Loads book infos from an epub file
+ *
+ * @param string Epub full file name
+ * @throws Exception if error
+ *
+ * @return void
+ */
+ public function LoadFromEpub($inFileName)
+ {
+ // Load the epub file
+ $ePub = new EPub($inFileName, 'ZipFile');
+
+ // Get the epub infos
+ $this->mFormat = 'epub';
+ $this->mPath = pathinfo($inFileName, PATHINFO_DIRNAME);
+ $this->mName = pathinfo($inFileName, PATHINFO_FILENAME);
+ $this->mUuid = $ePub->Uuid();
+ $this->mUri = $ePub->Uri();
+ $this->mTitle = $ePub->Title();
+ $this->mAuthors = $ePub->Authors();
+ $this->mLanguage = $ePub->Language();
+ $this->mDescription = $ePub->Description();
+ $this->mSubjects = $ePub->Subjects();
+ $cover = $ePub->Cover();
+ $this->mCover = ($cover['found'] !== false) ? $cover['found'] : '';
+ $this->mIsbn = $ePub->ISBN();
+ $this->mRights = $ePub->Copyright();
+ $this->mPublisher = $ePub->Publisher();
+ $this->mSerie = $ePub->Serie();
+ $this->mSerieIndex = $ePub->SerieIndex();
+ $this->mCreationDate = $ePub->CreationDate();
+ $this->mModificationDate = $ePub->ModificationDate();
+ }
+
+}
+
+?>
\ No newline at end of file
diff --git a/sources/resources/epub-loader/CalibreDbLoader.class.php b/sources/resources/epub-loader/CalibreDbLoader.class.php
new file mode 100644
index 0000000..10892b7
--- /dev/null
+++ b/sources/resources/epub-loader/CalibreDbLoader.class.php
@@ -0,0 +1,420 @@
+
+ */
+
+require_once(realpath(dirname(__FILE__)) . '/BookInfos.class.php');
+
+/**
+ * Calibre database sql file that comes unmodified from Calibre project:
+ * /calibre/resources/metadata_sqlite.sql
+ */
+define('CalibreCreateDbSql', realpath(dirname(__FILE__)) . '/metadata_sqlite.sql');
+
+/**
+ * CalibreDbLoader class allows to open or create a new Calibre database,
+ * and then add BookInfos objects into the database
+ */
+class CalibreDbLoader
+{
+ private $mDb = null;
+
+ /**
+ * Open a Calibre database (or create if database does not exist)
+ *
+ * @param string Calibre database file name
+ * @param boolean Force database creation
+ */
+ public function __construct($inDbFileName, $inCreate = false)
+ {
+ if ($inCreate) {
+ $this->CreateDatabase($inDbFileName);
+ }
+ else {
+ $this->OpenDatabase($inDbFileName);
+ }
+ }
+
+ /**
+ * Create an sqlite database
+ *
+ * @param string Database file name
+ * @throws Exception if error
+ *
+ * @return void
+ */
+ private function CreateDatabase($inDbFileName)
+ {
+ // Read the sql file
+ $content = file_get_contents(CalibreCreateDbSql);
+ if ($content === false) {
+ $error = sprintf('Cannot read sql file: %s', CalibreCreateDbSql);
+ throw new Exception($error);
+ }
+
+ // Remove the database file
+ if (file_exists($inDbFileName) && !unlink($inDbFileName)) {
+ $error = sprintf('Cannot remove database file: %s', $inDbFileName);
+ throw new Exception($error);
+ }
+
+ // Create the new database file
+ $this->OpenDatabase($inDbFileName);
+
+ // Create the database tables
+ try {
+ $sqlArray = explode('CREATE ', $content);
+ foreach ($sqlArray as $sql) {
+ $sql = trim($sql);
+ if (empty($sql)) {
+ continue;
+ }
+ $sql = 'CREATE ' . $sql;
+ $str = strtolower($sql);
+ if (strpos($str, 'create view') !== false) {
+ continue;
+ }
+ if (strpos($str, 'title_sort') !== false) {
+ continue;
+ }
+ $stmt = $this->mDb->prepare($sql);
+ $stmt->execute();
+ }
+ }
+ catch (Exception $e) {
+ $error = sprintf('Cannot create database: %s', $e->getMessage());
+ throw new Exception($error);
+ }
+ }
+
+ /**
+ * Open an sqlite database
+ *
+ * @param string Database file name
+ * @throws Exception if error
+ *
+ * @return void
+ */
+ private function OpenDatabase($inDbFileName)
+ {
+ try {
+ // Init the Data Source Name
+ $dsn = 'sqlite:' . $inDbFileName;
+ // Open the database
+ $this->mDb = new PDO($dsn); // Send an exception if error
+ $this->mDb->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+ //echo sprintf('Init database ok for: %s%s', $dsn, ' ');
+ }
+ catch (Exception $e) {
+ $error = sprintf('Cannot open database [%s]: %s', $dsn, $e->getMessage());
+ throw new Exception($error);
+ }
+ }
+
+ /**
+ * Add an epub to the db
+ *
+ * @param string Epub file name
+ * @throws Exception if error
+ *
+ * @return string Empty string or error if any
+ */
+ public function AddEpub($inFileName)
+ {
+ $error = '';
+
+ try {
+ // Load the book infos
+ $bookInfos = new BookInfos();
+ $bookInfos->LoadFromEpub($inFileName);
+ // Add the book
+ $this->AddBook($bookInfos);
+ }
+ catch (Exception $e) {
+ $error = $e->getMessage();
+ }
+
+ return $error;
+
+ }
+
+ /**
+ * Add a new book into the db
+ *
+ * @param object BookInfo object
+ * @throws Exception if error
+ *
+ * @return void
+ */
+ private function AddBook($inBookInfo)
+ {
+ // Check if the book uuid does not already exist
+ $sql = 'select b.id, b.title, b.path, d.name, d.format from books as b, data as d where d.book = b.id and uuid=:uuid';
+ $stmt = $this->mDb->prepare($sql);
+ $stmt->bindParam(':uuid', $inBookInfo->mUuid);
+ $stmt->execute();
+ while ($post = $stmt->fetchObject()) {
+ $error = sprintf('Multiple book id for uuid: %s (already in file "%s/%s.%s" title "%s")', $inBookInfo->mUuid, $post->path, $post->name, $post->format, $post->title);
+ throw new Exception($error);
+ }
+ // Add the book
+ $sql = 'insert into books(title, sort, pubdate, last_modified, series_index, uuid, path) values(:title, :sort, :pubdate, :lastmodified, :serieindex, :uuid, :path)';
+ $stmt = $this->mDb->prepare($sql);
+ $stmt->bindParam(':title', $inBookInfo->mTitle);
+ $stmt->bindParam(':sort', $inBookInfo->mTitle);
+ $stmt->bindParam(':pubdate', empty($inBookInfo->mCreationDate) ? null : $inBookInfo->mCreationDate);
+ $stmt->bindParam(':lastmodified', empty($inBookInfo->mModificationDate) ? '2000-01-01 00:00:00+00:00' : $inBookInfo->mModificationDate);
+ $stmt->bindParam(':serieindex', $inBookInfo->mSerieIndex);
+ $stmt->bindParam(':uuid', $inBookInfo->mUuid);
+ $stmt->bindParam(':path', $inBookInfo->mPath);
+ $stmt->execute();
+ // Get the book id
+ $sql = 'select id from books where uuid=:uuid';
+ $stmt = $this->mDb->prepare($sql);
+ $stmt->bindParam(':uuid', $inBookInfo->mUuid);
+ $stmt->execute();
+ $idBook = null;
+ while ($post = $stmt->fetchObject()) {
+ $idBook = $post->id;
+ break;
+ }
+ if (!isset($idBook)) {
+ $error = sprintf('Cannot find book id for uuid: %s', $inBookInfo->mUuid);
+ throw new Exception($error);
+ }
+ // Add the book formats
+ $sql = 'insert into data(book, format, name, uncompressed_size) values(:idBook, :format, :name, 0)';
+ $stmt = $this->mDb->prepare($sql);
+ $stmt->bindParam(':idBook', $idBook, PDO::PARAM_INT);
+ $stmt->bindParam(':format', $inBookInfo->mFormat);
+ $stmt->bindParam(':name', $inBookInfo->mName);
+ $stmt->execute();
+ // Add the book comments
+ $sql = 'insert into comments(book, text) values(:idBook, :text)';
+ $stmt = $this->mDb->prepare($sql);
+ $stmt->bindParam(':idBook', $idBook, PDO::PARAM_INT);
+ $stmt->bindParam(':text', $inBookInfo->mDescription);
+ $stmt->execute();
+ // Add the book identifiers
+ if (!empty($inBookInfo->mUri)) {
+ $sql = 'insert into identifiers(book, type, val) values(:idBook, :type, :value)';
+ $identifiers = array();
+ $identifiers['URI'] = $inBookInfo->mUri;
+ $identifiers['ISBN'] = $inBookInfo->mIsbn;
+ foreach ($identifiers as $key => $value) {
+ if (empty($value)) {
+ continue;
+ }
+ $stmt = $this->mDb->prepare($sql);
+ $stmt->bindParam(':idBook', $idBook, PDO::PARAM_INT);
+ $stmt->bindParam(':type', $key);
+ $stmt->bindParam(':value', $value);
+ $stmt->execute();
+ }
+ }
+ // Add the book serie
+ if (!empty($inBookInfo->mSerie)) {
+ // Get the serie id
+ $sql = 'select id from series where name=:serie';
+ $stmt = $this->mDb->prepare($sql);
+ $stmt->bindParam(':serie', $inBookInfo->mSerie);
+ $stmt->execute();
+ $post = $stmt->fetchObject();
+ if ($post) {
+ $idSerie = $post->id;
+ }
+ else {
+ // Add a new serie
+ $sql = 'insert into series(name, sort) values(:serie, :sort)';
+ $stmt = $this->mDb->prepare($sql);
+ $stmt->bindParam(':serie', $inBookInfo->mSerie);
+ $stmt->bindParam(':sort', $inBookInfo->mSerie);
+ $stmt->execute();
+ // Get the serie id
+ $sql = 'select id from series where name=:serie';
+ $stmt = $this->mDb->prepare($sql);
+ $stmt->bindParam(':serie', $inBookInfo->mSerie);
+ $stmt->execute();
+ $idSerie = null;
+ while ($post = $stmt->fetchObject()) {
+ if (!isset($idSerie)) {
+ $idSerie = $post->id;
+ }
+ else {
+ $error = sprintf('Multiple series for name: %s', $inBookInfo->mSerie);
+ throw new Exception($error);
+ }
+ }
+ if (!isset($idSerie)) {
+ $error = sprintf('Cannot find serie id for name: %s', $inBookInfo->mSerie);
+ throw new Exception($error);
+ }
+ }
+ // Add the book serie link
+ $sql = 'insert into books_series_link(book, series) values(:idBook, :idSerie)';
+ $stmt = $this->mDb->prepare($sql);
+ $stmt->bindParam(':idBook', $idBook, PDO::PARAM_INT);
+ $stmt->bindParam(':idSerie', $idSerie, PDO::PARAM_INT);
+ $stmt->execute();
+ }
+ // Add the book authors
+ foreach ($inBookInfo->mAuthors as $authorSort => $author) {
+ // Get the author id
+ $sql = 'select id from authors where name=:author';
+ $stmt = $this->mDb->prepare($sql);
+ $stmt->bindParam(':author', $author);
+ $stmt->execute();
+ $post = $stmt->fetchObject();
+ if ($post) {
+ $idAuthor = $post->id;
+ }
+ else {
+ // Add a new author
+ $sql = 'insert into authors(name, sort) values(:author, :sort)';
+ $stmt = $this->mDb->prepare($sql);
+ $stmt->bindParam(':author', $author);
+ $stmt->bindParam(':sort', $authorSort);
+ $stmt->execute();
+ // Get the author id
+ $sql = 'select id from authors where name=:author';
+ $stmt = $this->mDb->prepare($sql);
+ $stmt->bindParam(':author', $author);
+ $stmt->execute();
+ $idAuthor = null;
+ while ($post = $stmt->fetchObject()) {
+ if (!isset($idAuthor)) {
+ $idAuthor = $post->id;
+ }
+ else {
+ $error = sprintf('Multiple authors for name: %s', $author);
+ throw new Exception($error);
+ }
+ }
+ if (!isset($idAuthor)) {
+ $error = sprintf('Cannot find author id for name: %s', $author);
+ throw new Exception($error);
+ }
+ }
+ // Add the book author link
+ $sql = 'insert into books_authors_link(book, author) values(:idBook, :idAuthor)';
+ $stmt = $this->mDb->prepare($sql);
+ $stmt->bindParam(':idBook', $idBook, PDO::PARAM_INT);
+ $stmt->bindParam(':idAuthor', $idAuthor, PDO::PARAM_INT);
+ $stmt->execute();
+ }
+ // Add the book language
+ {
+ // Get the language id
+ $sql = 'select id from languages where lang_code=:language';
+ $stmt = $this->mDb->prepare($sql);
+ $stmt->bindParam(':language', $inBookInfo->mLanguage);
+ $stmt->execute();
+ $post = $stmt->fetchObject();
+ if ($post) {
+ $idLanguage = $post->id;
+ }
+ else {
+ // Add a new language
+ $sql = 'insert into languages(lang_code) values(:language)';
+ $stmt = $this->mDb->prepare($sql);
+ $stmt->bindParam(':language', $inBookInfo->mLanguage);
+ $stmt->execute();
+ // Get the language id
+ $sql = 'select id from languages where lang_code=:language';
+ $stmt = $this->mDb->prepare($sql);
+ $stmt->bindParam(':language', $inBookInfo->mLanguage);
+ $stmt->execute();
+ $idLanguage = null;
+ while ($post = $stmt->fetchObject()) {
+ if (!isset($idLanguage)) {
+ $idLanguage = $post->id;
+ }
+ else {
+ $error = sprintf('Multiple languages for lang_code: %s', $inBookInfo->mLanguage);
+ throw new Exception($error);
+ }
+ }
+ if (!isset($idLanguage)) {
+ $error = sprintf('Cannot find language id for lang_code: %s', $inBookInfo->mLanguage);
+ throw new Exception($error);
+ }
+ }
+ // Add the book language link
+ $itemOder = 0;
+ $sql = 'insert into books_languages_link(book, lang_code, item_order) values(:idBook, :idLanguage, :itemOrder)';
+ $stmt = $this->mDb->prepare($sql);
+ $stmt->bindParam(':idBook', $idBook, PDO::PARAM_INT);
+ $stmt->bindParam(':idLanguage', $idLanguage, PDO::PARAM_INT);
+ $stmt->bindParam(':itemOrder', $itemOder, PDO::PARAM_INT);
+ $stmt->execute();
+ }
+ // Add the book tags (subjects)
+ foreach ($inBookInfo->mSubjects as $subject) {
+ // Get the subject id
+ $sql = 'select id from tags where name=:subject';
+ $stmt = $this->mDb->prepare($sql);
+ $stmt->bindParam(':subject', $subject);
+ $stmt->execute();
+ $post = $stmt->fetchObject();
+ if ($post) {
+ $idSubject = $post->id;
+ }
+ else {
+ // Add a new subject
+ $sql = 'insert into tags(name) values(:subject)';
+ $stmt = $this->mDb->prepare($sql);
+ $stmt->bindParam(':subject', $subject);
+ $stmt->execute();
+ // Get the subject id
+ $sql = 'select id from tags where name=:subject';
+ $stmt = $this->mDb->prepare($sql);
+ $stmt->bindParam(':subject', $subject);
+ $stmt->execute();
+ $idSubject = null;
+ while ($post = $stmt->fetchObject()) {
+ if (!isset($idSubject)) {
+ $idSubject = $post->id;
+ }
+ else {
+ $error = sprintf('Multiple subjects for name: %s', $subject);
+ throw new Exception($error);
+ }
+ }
+ if (!isset($idSubject)) {
+ $error = sprintf('Cannot find subject id for name: %s', $subject);
+ throw new Exception($error);
+ }
+ }
+ // Add the book subject link
+ $sql = 'insert into books_tags_link(book, tag) values(:idBook, :idSubject)';
+ $stmt = $this->mDb->prepare($sql);
+ $stmt->bindParam(':idBook', $idBook, PDO::PARAM_INT);
+ $stmt->bindParam(':idSubject', $idSubject, PDO::PARAM_INT);
+ $stmt->execute();
+ }
+ }
+
+ /**
+ * Check database for debug
+ *
+ * @return void
+ */
+ private function CheckDatabase()
+ {
+ // Retrieve some infos for check only
+ $sql = 'select id, title, sort from books';
+ $stmt = $this->mDb->prepare($sql);
+ $stmt->execute();
+ while ($post = $stmt->fetchObject()) {
+ $id = $post->id;
+ $title = $post->title;
+ $sort = $post->sort;
+ }
+ }
+
+}
+
+?>
diff --git a/sources/resources/epub-loader/CsvExport.class.php b/sources/resources/epub-loader/CsvExport.class.php
new file mode 100644
index 0000000..595d699
--- /dev/null
+++ b/sources/resources/epub-loader/CsvExport.class.php
@@ -0,0 +1,77 @@
+
+ */
+
+require_once(realpath(dirname(__FILE__)) . '/BaseExport.class.php');
+
+class CsvExport extends BaseExport
+{
+ private $mLines = null;
+
+ const CsvSeparator = "\t";
+
+ /**
+ * Open an export file (or create if file does not exist)
+ *
+ * @param string Export file name
+ * @param boolean Force file creation
+ */
+ public function __construct($inFileName, $inCreate = false)
+ {
+ $this->mSearch = array("\r", "\n", self::CsvSeparator);
+ $this->mReplace = array('', ' ', '');
+
+ // Init container
+ $this->mLines = array();
+
+ parent::__construct($inFileName, $inCreate);
+ }
+
+ /**
+ * Add the current properties into the export content
+ * and reset the properties
+ */
+ public function AddContent()
+ {
+ $text = '';
+ foreach ($this->mProperties as $key => $value) {
+ $info = '';
+ if (is_array($value)) {
+ foreach ($value as $value1) {
+ // Escape quotes
+ if (strpos($value1, '\'') !== false) {
+ $value1 = '\'' . str_replace('\'', '\'\'', $value1) . '\'';
+ }
+ $text .= $value1 . self::CsvSeparator;
+ }
+ continue;
+ }
+ else {
+ // Escape quotes
+ if (strpos($value, '\'') !== false) {
+ $value = '\'' . str_replace('\'', '\'\'', $value) . '\'';
+ }
+ $info = $value;
+ }
+ $text .= $info . self::CsvSeparator;
+ }
+
+ $this->mLines[] = $text;
+
+ $this->ClearProperties();
+ }
+
+ protected function GetContent()
+ {
+ $text = implode("\n", $this->mLines) . "\n";
+
+ return $text;
+ }
+
+}
+
+?>
diff --git a/sources/resources/epub-loader/README b/sources/resources/epub-loader/README
new file mode 100644
index 0000000..cde011a
--- /dev/null
+++ b/sources/resources/epub-loader/README
@@ -0,0 +1,17 @@
+## =============================================================================
+## epub-loader readme
+## =============================================================================
+
+epub-loader is a utility ressource for ebooks.
+
+- CalibreDbLoader class allows create Calibre databases and add ebooks
+- BookExport class allows to export ebooks metadata in csv files
+- The app directory contains samples and allows to run actions
+
+
+## Installation
+## -----------------------------------------------------------------------------
+
+- If a first-time install, copy app/config.php.example to app/config.php
+- Edit config.php to match your config
+- Open the app directory url
diff --git a/sources/resources/epub-loader/ZipFile.class.php b/sources/resources/epub-loader/ZipFile.class.php
new file mode 100644
index 0000000..abcbd82
--- /dev/null
+++ b/sources/resources/epub-loader/ZipFile.class.php
@@ -0,0 +1,119 @@
+
+ */
+
+/**
+ * ZipFile class allows to open files inside a zip file with the standard php zip functions
+ */
+class ZipFile
+{
+ private $mZip;
+ private $mEntries;
+
+ public function __construct()
+ {
+ $this->mZip = null;
+ $this->mEntries = null;
+ }
+
+ /**
+ * Destructor
+ */
+ public function __destruct()
+ {
+ $this->Close();
+ }
+
+ /**
+ * Open a zip file and read it's entries
+ *
+ * @param string $inFileName
+ * @return boolean True if zip file has been correctly opended, else false
+ */
+ public function Open($inFileName)
+ {
+ $this->Close();
+
+ $this->mZip = zip_open($inFileName);
+ if (!$this->mZip) {
+ return false;
+ }
+
+ $this->mEntries = array();
+
+ while ($entry = zip_read($this->mZip)) {
+ $fileName = zip_entry_name($entry);
+ $this->mEntries[$fileName] = $entry;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check if a file exist in the zip entries
+ *
+ * @param string $inFileName File to search
+ *
+ * @return boolean True if the file exist, else false
+ */
+ public function FileExists($inFileName)
+ {
+ if (!isset($this->mZip)) {
+ return false;
+ }
+
+ if (!isset($this->mEntries[$inFileName])) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Read the content of a file in the zip entries
+ *
+ * @param string $inFileName File to search
+ *
+ * @return mixed File content the file exist, else false
+ */
+ public function FileRead($inFileName)
+ {
+ if (!isset($this->mZip)) {
+ return false;
+ }
+
+ if (!isset($this->mEntries[$inFileName])) {
+ return false;
+ }
+
+ $entry = $this->mEntries[$inFileName];
+ if (!zip_entry_open($this->mZip, $entry)) {
+ return false;
+ }
+ $data = zip_entry_read($entry, zip_entry_filesize($entry));
+ zip_entry_close($entry);
+
+ return $data;
+ }
+
+ /**
+ * Close the zip file
+ *
+ * @return void
+ */
+ public function Close()
+ {
+ if (!isset($this->mZip)) {
+ return false;
+ }
+
+ zip_close($this->mZip);
+ }
+
+}
+
+?>
\ No newline at end of file
diff --git a/sources/resources/epub-loader/app/.gitignore b/sources/resources/epub-loader/app/.gitignore
new file mode 100644
index 0000000..0d00eb9
--- /dev/null
+++ b/sources/resources/epub-loader/app/.gitignore
@@ -0,0 +1 @@
+epub-loader-config.php
diff --git a/sources/resources/epub-loader/app/action_csv_export.php b/sources/resources/epub-loader/app/action_csv_export.php
new file mode 100644
index 0000000..693feef
--- /dev/null
+++ b/sources/resources/epub-loader/app/action_csv_export.php
@@ -0,0 +1,31 @@
+
+ */
+
+// Init csv file
+$fileName = $dbConfig['db_path'] . DIRECTORY_SEPARATOR . basename($dbConfig['db_path']) . '_metadata.csv';
+try {
+ // Open or create the export file
+ $export = new BookExport($fileName, BookExport::eExportTypeCsv, true);
+ echo sprintf('Export ebooks to %s', $fileName) . ' ';
+ // Add the epub files into the export file
+ if (!empty($dbConfig['epub_path'])) {
+ $fileList = RecursiveGlob($dbConfig['epub_path'], '*.epub');
+ foreach ($fileList as $file) {
+ $error = $export->AddEpub($file);
+ if (!empty($error)) {
+ $gErrorArray[$file] = $error;
+ }
+ }
+ }
+ $export->SaveToFile();
+}
+catch (Exception $e) {
+ $gErrorArray[$fileName] = $e->getMessage();
+}
+
+?>
diff --git a/sources/resources/epub-loader/app/action_db_load.php b/sources/resources/epub-loader/app/action_db_load.php
new file mode 100644
index 0000000..ad21afc
--- /dev/null
+++ b/sources/resources/epub-loader/app/action_db_load.php
@@ -0,0 +1,30 @@
+
+ */
+
+// Init database file
+$fileName = $dbConfig['db_path'] . DIRECTORY_SEPARATOR . 'metadata.db';
+try {
+ // Open or create the database
+ $db = new CalibreDbLoader($fileName, $gConfig['create_db']);
+ echo sprintf('Load database %s', $fileName) . ' ';
+ // Add the epub files into the database
+ if (!empty($dbConfig['epub_path'])) {
+ $fileList = RecursiveGlob($dbConfig['epub_path'], '*.epub');
+ foreach ($fileList as $file) {
+ $error = $db->AddEpub($file);
+ if (!empty($error)) {
+ $gErrorArray[$file] = $error;
+ }
+ }
+ }
+}
+catch (Exception $e) {
+ $gErrorArray[$fileName] = $e->getMessage();
+}
+
+?>
diff --git a/sources/resources/epub-loader/app/cops-feed.php b/sources/resources/epub-loader/app/cops-feed.php
new file mode 100644
index 0000000..698404b
--- /dev/null
+++ b/sources/resources/epub-loader/app/cops-feed.php
@@ -0,0 +1,27 @@
+
+ */
+
+// Include config file
+$fileName = __DIR__ . DIRECTORY_SEPARATOR . 'epub-loader-config.php';
+if (!file_exists($fileName)) {
+ die ('Missing configuration file: ' . $fileName);
+}
+require_once($fileName);
+
+// Add cops directory to include path
+$includePath = ini_get('include_path');
+ini_set('include_path', $includePath . PATH_SEPARATOR . $gConfig['cops_directory']);
+
+// Include COPS feed
+$fileName = $gConfig['cops_directory'] . '/feed.php';
+if (!file_exists($fileName)) {
+ die ('Incorrect include file: ' . $fileName);
+}
+require_once($fileName);
+
+?>
diff --git a/sources/resources/epub-loader/app/epub-loader-config.php.example b/sources/resources/epub-loader/app/epub-loader-config.php.example
new file mode 100644
index 0000000..a0140d5
--- /dev/null
+++ b/sources/resources/epub-loader/app/epub-loader-config.php.example
@@ -0,0 +1,60 @@
+
+ */
+
+$gConfig = array();
+
+/**
+ * Application name
+ */
+$gConfig['app_name'] = 'Epub loader';
+
+/**
+ * Admin email
+ */
+$gConfig['admin_email'] = 'didier.corbiere@opale-concept.com';
+
+/**
+ * Cops directory
+ *
+ * This is the base path of Cops library
+ */
+$gConfig['cops_directory'] = dirname(dirname(dirname(__DIR__)));
+if (!is_dir($gConfig['cops_directory'])) {
+ die ('Incorrect Cops directory: ' . $gConfig['cops_directory']);
+}
+
+/**
+ * Create Calibre databases ?
+ *
+ * If true: databases are removed and recreated before loading ebooks
+ * If false: append ebooks into databases
+ */
+$gConfig['create_db'] = true;
+
+/**
+ * Databases infos
+ *
+ * For each database:
+ * name: The database name to display
+ * db_path: The path where to create the database
+ * epub_path: The path where to look for the epub files to load
+ * pdf_path: The path where to look for pdf files
+ */
+$gConfig['databases'] = array();
+$gConfig['databases'][] = array('name' => 'Littérature classique', 'db_path' => '/opt/ebooks/calibre/demo', 'epub_path' => '/opt/ebooks/epub/demo', 'pdf_path' => '');
+$gConfig['databases'][] = array('name' => 'Bibliothèque numérique romande', 'db_path' => '/opt/ebooks/calibre/bnr', 'epub_path' => '/opt/ebooks/epub/bnr', 'pdf_path' => '');
+$gConfig['databases'][] = array('name' => 'La Bibliothèque d\'Ebooks', 'db_path' => '/opt/ebooks/calibre/bibebook', 'epub_path' => '/opt/ebooks/epub/bibebook', 'pdf_path' => '');
+
+/**
+ * Available actions
+ */
+$gConfig['actions'] = array();
+$gConfig['actions']['csv_export'] = 'Csv export';
+$gConfig['actions']['db_load'] = 'Create database';
+
+?>
diff --git a/sources/resources/epub-loader/app/footer.php b/sources/resources/epub-loader/app/footer.php
new file mode 100644
index 0000000..13b1631
--- /dev/null
+++ b/sources/resources/epub-loader/app/footer.php
@@ -0,0 +1,47 @@
+
+
+
+' . "\n";
+ $str .= ' ' . "\n";
+ $title = 'Errors (' . count($gErrorArray) . ')';
+ $str .= '
' . "\n";
+ $str .= ' ' . "\n";
+ $str .= ' ' . $title . ' ' . "\n";
+ $str .= ' ' . "\n";
+ foreach ($gErrorArray as $fileName => $error) {
+ // Display error
+ $str .= ' ' . "\n";
+ $str .= ' ' . $fileName . ' ' . "\n";
+ $str .= ' ' . $error . ' ' . "\n";
+ $str .= ' ' . "\n";
+ }
+ $str .= '
' . "\n";
+ $str .= '
' . "\n";
+ $str .= ' ' . "\n";
+ echo $str;
+ }
+?>
+
+
+
+
+
+
+
+