diff --git a/src/main/java/net/knarcraft/bookswithoutborders/command/CommandBooksWithoutBorders.java b/src/main/java/net/knarcraft/bookswithoutborders/command/CommandBooksWithoutBorders.java index 10aad8b..9304834 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/command/CommandBooksWithoutBorders.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/command/CommandBooksWithoutBorders.java @@ -145,7 +145,7 @@ public class CommandBooksWithoutBorders implements TabExecutor { PluginCommand pluginCommand = BooksWithoutBorders.getInstance().getCommand(bwBCommand.toString()); if (pluginCommand == null) { BooksWithoutBorders.log(Level.SEVERE, StringFormatter.replacePlaceholder( - StaticMessage.COMMAND_NOT_REGISTERED.toString(), "{command}", bwBCommand.toString())); + StaticMessage.EXCEPTION_COMMAND_NOT_REGISTERED.toString(), "{command}", bwBCommand.toString())); return ""; } diff --git a/src/main/java/net/knarcraft/bookswithoutborders/command/CommandSetLore.java b/src/main/java/net/knarcraft/bookswithoutborders/command/CommandSetLore.java index 515f679..af85088 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/command/CommandSetLore.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/command/CommandSetLore.java @@ -40,7 +40,7 @@ public class CommandSetLore implements TabExecutor { ItemStack heldItem = InventoryHelper.getHeldItem(player, true); if (heldItem.getType() == Material.AIR) { - stringFormatter.displayErrorMessage(player, Translatable.ERROR_LORE_NO_ITEM); + stringFormatter.displayErrorMessage(player, Translatable.ERROR_NO_ITEM); return false; } diff --git a/src/main/java/net/knarcraft/bookswithoutborders/command/CommandSetTitle.java b/src/main/java/net/knarcraft/bookswithoutborders/command/CommandSetTitle.java index 23b5300..96ea871 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/command/CommandSetTitle.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/command/CommandSetTitle.java @@ -40,7 +40,7 @@ public class CommandSetTitle implements TabExecutor { ItemStack heldItem = InventoryHelper.getHeldItem(player, true); if (heldItem.getType() == Material.AIR) { - BooksWithoutBorders.sendErrorMessage(sender, "You must be holding an item to set title!"); + stringFormatter.displayErrorMessage(sender, Translatable.ERROR_NO_ITEM); return false; } @@ -49,7 +49,7 @@ public class CommandSetTitle implements TabExecutor { ItemMeta itemMetadata = heldItem.getItemMeta(); if (itemMetadata == null) { - BooksWithoutBorders.sendErrorMessage(sender, "Unable to get metadata for your held item!"); + stringFormatter.displayErrorMessage(sender, Translatable.ERROR_METADATA_MISSING); return false; } @@ -57,7 +57,7 @@ public class CommandSetTitle implements TabExecutor { ItemMeta newMetaData; if (heldItem.getType() == Material.WRITTEN_BOOK) { if (title.length() > 32) { - BooksWithoutBorders.sendErrorMessage(sender, "Book titles are capped at 32 characters!"); + stringFormatter.displayErrorMessage(sender, Translatable.ERROR_TITLE_LENGTH); return false; } BookMeta bookMetadata = (BookMeta) itemMetadata; @@ -70,7 +70,8 @@ public class CommandSetTitle implements TabExecutor { //Set the new metadata heldItem.setItemMeta(newMetaData); - BooksWithoutBorders.sendSuccessMessage(sender, "Title set to " + title + "!"); + stringFormatter.displaySuccessMessage(sender, + stringFormatter.replacePlaceholder(Translatable.SUCCESS_TITLE_SET, "{title}", title)); return true; } diff --git a/src/main/java/net/knarcraft/bookswithoutborders/command/CommandUnSign.java b/src/main/java/net/knarcraft/bookswithoutborders/command/CommandUnSign.java index 4223310..60d75b8 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/command/CommandUnSign.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/command/CommandUnSign.java @@ -1,9 +1,12 @@ package net.knarcraft.bookswithoutborders.command; import net.knarcraft.bookswithoutborders.BooksWithoutBorders; +import net.knarcraft.bookswithoutborders.config.Permission; +import net.knarcraft.bookswithoutborders.config.translation.Translatable; import net.knarcraft.bookswithoutborders.state.ItemSlot; import net.knarcraft.bookswithoutborders.utility.BookHelper; import net.knarcraft.bookswithoutborders.utility.InventoryHelper; +import net.knarcraft.knarlib.formatting.StringFormatter; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.command.TabExecutor; @@ -24,13 +27,17 @@ public class CommandUnSign implements TabExecutor { @Override public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] arguments) { + StringFormatter stringFormatter = BooksWithoutBorders.getStringFormatter(); if (!(sender instanceof Player player)) { - BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!"); + stringFormatter.displayErrorMessage(sender, Translatable.ERROR_PLAYER_ONLY); return false; } - if (InventoryHelper.notHoldingOneWrittenBookCheck(player, "You must be holding a signed book to unsign it!", - "You cannot unsign two books at once. Please un-equip one of the books you're holding!")) { + if (InventoryHelper.notHoldingOneWrittenBookCheck(player, + stringFormatter.replacePlaceholder(Translatable.ERROR_NOT_HOLDING_WRITTEN_BOOK, "{action}", + stringFormatter.getUnFormattedColoredMessage(Translatable.ACTION_UNSIGN)), + stringFormatter.replacePlaceholder(Translatable.ERROR_ONLY_ONE_BOOK, "{action}", + stringFormatter.getUnFormattedColoredMessage(Translatable.ACTION_UNSIGN)))) { return false; } @@ -50,14 +57,14 @@ public class CommandUnSign implements TabExecutor { //Get the old book BookMeta oldMetadata = InventoryHelper.getHeldBookMetadata(player, mainHand); if (oldMetadata == null) { - BooksWithoutBorders.sendErrorMessage(player, "Unable to get metadata from the held book!"); + BooksWithoutBorders.getStringFormatter().displayErrorMessage(player, Translatable.ERROR_METADATA_MISSING); return; } ItemStack heldBook = InventoryHelper.getHeldBook(player, mainHand); //Only allow the owner to un-sign the book if (BooksWithoutBorders.getConfiguration().getAuthorOnlyUnsign() && - !player.hasPermission("bookswithoutborders.bypassAuthorOnlyUnsign")) { + !player.hasPermission(Permission.AUTHOR_ONLY_UNSIGN.toString())) { if (BookHelper.isNotAuthor(player, Objects.requireNonNull(oldMetadata))) { return; } diff --git a/src/main/java/net/knarcraft/bookswithoutborders/config/Permission.java b/src/main/java/net/knarcraft/bookswithoutborders/config/Permission.java index 26daacd..b56860d 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/config/Permission.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/config/Permission.java @@ -37,6 +37,10 @@ public enum Permission { */ BYPASS_AUTHOR_ONLY_SAVE("bypassAuthorOnlySave"), + /** + * The permission for bypassing author only un-signing + */ + AUTHOR_ONLY_UNSIGN("bypassAuthorOnlyUnsign"), ; private final @NotNull String node; diff --git a/src/main/java/net/knarcraft/bookswithoutborders/config/StaticMessage.java b/src/main/java/net/knarcraft/bookswithoutborders/config/StaticMessage.java index ceb0645..7e06c4b 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/config/StaticMessage.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/config/StaticMessage.java @@ -9,7 +9,7 @@ public enum StaticMessage { BOOK_SAVING_FAILED("Saving failed! Aborting..."), BOOK_FOLDER_CREATE_FAILED("Unable to create necessary folders"), - COMMAND_NOT_REGISTERED("Command {command} has not been registered!"), + EXCEPTION_COMMAND_NOT_REGISTERED("Command {command} has not been registered!"), EXCEPTION_VAULT_NOT_AVAILABLE("Vault is unavailable, but book price is set to economy. Unsetting book cost!"), EXCEPTION_VAULT_PRICE_NOT_CHANGED("BooksWithoutBorders failed to hook into Vault! Book price not set!"), EXCEPTION_ENCRYPTED_FILE_DELETE_FAILED("Book encryption data failed to delete upon decryption!\nFile location: {path}"), @@ -17,6 +17,14 @@ public enum StaticMessage { EXCEPTION_SAVE_BOOK_FAILED("Unable to save book"), EXCEPTION_BOOKSHELF_SAVING_FAILED("Unable to save bookshelves!"), NOTICE_NO_BOOKSHELVES("BooksWithoutBorders found no bookshelves to load"), + EXCEPTION_BOOKSHELF_NAME_EMPTY("Bookshelves cannot have empty titles!"), + DEBUG_AES_INVALID_KEY("Invalid AES key given!"), + DEBUG_AES_INVALID_PARAMETERS("Invalid AES parameters given!"), + DEBUG_AES_INVALID_BLOCK_SIZE("Invalid AES block size during finalization"), + DEBUG_AES_INVALID_PADDING_FINALIZATION("Invalid AES padding during finalization"), + DEBUG_AES_INVALID_ALGORITHM("Invalid AES algorithm"), + DEBUG_AES_INVALID_PADDING_CIPHER("Invalid AES padding during Cipher generation"), + DEBUG_AES_INVALID_KEY_SPECIFICATION("Invalid AES key specification"), ; private final @NotNull String messageString; diff --git a/src/main/java/net/knarcraft/bookswithoutborders/config/translation/BookshelfMessage.java b/src/main/java/net/knarcraft/bookswithoutborders/config/translation/BookshelfMessage.java index dca1a0f..d71b57d 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/config/translation/BookshelfMessage.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/config/translation/BookshelfMessage.java @@ -41,7 +41,7 @@ public enum BookshelfMessage implements TranslatableMessage { @Override public @NotNull TranslatableMessage[] getAllMessages() { - return Translatable.values(); + return BookshelfMessage.values(); } } diff --git a/src/main/java/net/knarcraft/bookswithoutborders/config/translation/CostMessage.java b/src/main/java/net/knarcraft/bookswithoutborders/config/translation/CostMessage.java index 3e7878c..fe8644f 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/config/translation/CostMessage.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/config/translation/CostMessage.java @@ -51,7 +51,7 @@ public enum CostMessage implements TranslatableMessage { @Override public @NotNull TranslatableMessage[] getAllMessages() { - return Translatable.values(); + return CostMessage.values(); } } diff --git a/src/main/java/net/knarcraft/bookswithoutborders/config/translation/Formatting.java b/src/main/java/net/knarcraft/bookswithoutborders/config/translation/Formatting.java index 63c82c3..877c13c 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/config/translation/Formatting.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/config/translation/Formatting.java @@ -67,11 +67,91 @@ public enum Formatting implements TranslatableMessage { * The format used when formatting text on a title page */ NEUTRAL_TITLE_PAGE_TEXT_FORMAT, + + /** + * The format used when showing the header for public books by a specific author + */ + NEUTRAL_AUTHOR_PUBLIC_BOOKS_HEADER, + + /** + * The format used when showing the header for player books by a specific author + */ + NEUTRAL_AUTHOR_PLAYER_BOOKS_HEADER, + + /** + * The format used when showing the hover action for selecting a book by its path + */ + NEUTRAL_AUTHOR_BOOKS_PATH, + + /** + * The format used when displaying an empty author books page + */ + NEUTRAL_AUTHOR_BOOKS_INVALID_PAGE, + + /** + * The format used when showing current ant total pages in the book list + */ + NEUTRAL_BOOK_LIST_TOTAL_PAGES, + + /** + * The format used when showing the previous button in the book list + */ + NEUTRAL_BOOK_LIST_PREVIOUS_PAGE, + + /** + * The format used when showing the next button in the book list + */ + NEUTRAL_BOOK_LIST_NEXT_PAGE, + + /** + * The separator between book and author in the book list + */ + NEUTRAL_BOOK_LIST_AUTHOR_SEPARATOR, + + /** + * The format used when showing the page a link goes to in the book list + */ + NEUTRAL_BOOK_LIST_TO_PAGE, + + /** + * The format used when showing the hover hint on the book index + */ + NEUTRAL_BOOK_LIST_INDEX_HOVER, + + /** + * The format used when showing the header of the public book list + */ + NEUTRAL_BOOK_LIST_PUBLIC_BOOKS_HEADER, + + /** + * The format used when showing the header of the player book list + */ + NEUTRAL_BOOK_LIST_PLAYER_BOOKS_HEADER, + + /** + * The format used when showing the books by hover text + */ + NEUTRAL_BOOK_LIST_AUTHOR_HOVER, + + /** + * The format used when showing the book path hover text + */ + NEUTRAL_BOOK_LIST_PATH_HOVER, + + /** + * The format used when showing the book list select by index hover text + */ + NEUTRAL_BOOK_LIST_BOOK_INDEX_HOVER, + + /** + * The format used when showing book int index in the book list + */ + NEUTRAL_BOOK_LIST_BOOK_INDEX_NUMBER, ; @Override public @NotNull TranslatableMessage[] getAllMessages() { - return Translatable.values(); + return Formatting.values(); } } diff --git a/src/main/java/net/knarcraft/bookswithoutborders/config/translation/GiveMessage.java b/src/main/java/net/knarcraft/bookswithoutborders/config/translation/GiveMessage.java index 61e39e5..e49ca41 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/config/translation/GiveMessage.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/config/translation/GiveMessage.java @@ -41,7 +41,7 @@ public enum GiveMessage implements TranslatableMessage { @Override public @NotNull TranslatableMessage[] getAllMessages() { - return Translatable.values(); + return GiveMessage.values(); } } diff --git a/src/main/java/net/knarcraft/bookswithoutborders/config/translation/SaveMessage.java b/src/main/java/net/knarcraft/bookswithoutborders/config/translation/SaveMessage.java index de72ca9..dc704e0 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/config/translation/SaveMessage.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/config/translation/SaveMessage.java @@ -41,7 +41,7 @@ public enum SaveMessage implements TranslatableMessage { @Override public @NotNull TranslatableMessage[] getAllMessages() { - return Translatable.values(); + return SaveMessage.values(); } } diff --git a/src/main/java/net/knarcraft/bookswithoutborders/config/translation/Translatable.java b/src/main/java/net/knarcraft/bookswithoutborders/config/translation/Translatable.java index 34300a2..91d5178 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/config/translation/Translatable.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/config/translation/Translatable.java @@ -78,6 +78,11 @@ public enum Translatable implements TranslatableMessage { */ SUCCESS_LORE_SET, + /** + * The success message displayed when an item's title is successfully set + */ + SUCCESS_TITLE_SET, + /** * The error to display when the console attempts to run a player-only command */ @@ -128,6 +133,11 @@ public enum Translatable implements TranslatableMessage { */ ACTION_CHANGE_GENERATION, + /** + * The translation of the unsign action + */ + ACTION_UNSIGN, + /** * The error displayed when running a relevant command while holding one book in each hand */ @@ -291,12 +301,17 @@ public enum Translatable implements TranslatableMessage { /** * The error displayed when attempting to change the lore of an item without holding an item */ - ERROR_LORE_NO_ITEM, + ERROR_NO_ITEM, /** * The error displayed when attempting to change an item's title/display name without specifying the new title/display name */ ERROR_TITLE_EMPTY, + + /** + * The error displayed when attempting to change a book's title with a title that's too long + */ + ERROR_TITLE_LENGTH, ; @Override diff --git a/src/main/java/net/knarcraft/bookswithoutborders/container/Bookshelf.java b/src/main/java/net/knarcraft/bookswithoutborders/container/Bookshelf.java index 9d43e30..d854313 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/container/Bookshelf.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/container/Bookshelf.java @@ -1,5 +1,6 @@ package net.knarcraft.bookswithoutborders.container; +import net.knarcraft.bookswithoutborders.config.StaticMessage; import org.bukkit.Location; import org.jetbrains.annotations.NotNull; @@ -64,7 +65,7 @@ public class Bookshelf { */ public void setTitle(@NotNull String title) { if (title.isBlank()) { - throw new IllegalArgumentException("Bookshelves cannot have empty titles!"); + throw new IllegalArgumentException(StaticMessage.EXCEPTION_BOOKSHELF_NAME_EMPTY.toString()); } this.title = title; } diff --git a/src/main/java/net/knarcraft/bookswithoutborders/encryption/AES.java b/src/main/java/net/knarcraft/bookswithoutborders/encryption/AES.java index de8b29a..fad687e 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/encryption/AES.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/encryption/AES.java @@ -1,6 +1,7 @@ package net.knarcraft.bookswithoutborders.encryption; import net.knarcraft.bookswithoutborders.BooksWithoutBorders; +import net.knarcraft.bookswithoutborders.config.StaticMessage; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -97,8 +98,11 @@ public class AES implements Encryptor { //Initialize cipher try { aes.init(mode, secretKeySpec, ivParameterSpec); - } catch (InvalidKeyException | InvalidAlgorithmParameterException exception) { - BooksWithoutBorders.log(Level.SEVERE, "Invalid AES input given!"); + } catch (InvalidKeyException exception) { + BooksWithoutBorders.log(Level.FINE, StaticMessage.DEBUG_AES_INVALID_KEY.toString()); + return null; + } catch (InvalidAlgorithmParameterException exception) { + BooksWithoutBorders.log(Level.FINE, StaticMessage.DEBUG_AES_INVALID_PARAMETERS.toString()); return null; } //Perform encryption/decryption and output result @@ -106,10 +110,10 @@ public class AES implements Encryptor { byte[] output = aes.doFinal(getInputBytes(input, encrypt)); return createResult(output, encrypt); } catch (IllegalBlockSizeException exception) { - BooksWithoutBorders.log(Level.SEVERE, "Invalid AES block size during finalization"); + BooksWithoutBorders.log(Level.FINE, StaticMessage.DEBUG_AES_INVALID_BLOCK_SIZE.toString()); return null; } catch (BadPaddingException exception) { - BooksWithoutBorders.log(Level.SEVERE, "Invalid AES padding during finalization"); + BooksWithoutBorders.log(Level.FINE, StaticMessage.DEBUG_AES_INVALID_PADDING_FINALIZATION.toString()); return null; } } @@ -156,10 +160,10 @@ public class AES implements Encryptor { try { aes = Cipher.getInstance("AES/CBC/PKCS5Padding"); } catch (NoSuchAlgorithmException exception) { - BooksWithoutBorders.log(Level.SEVERE, "Invalid AES algorithm during Cipher generation"); + BooksWithoutBorders.log(Level.SEVERE, StaticMessage.DEBUG_AES_INVALID_ALGORITHM.toString()); return null; } catch (NoSuchPaddingException exception) { - BooksWithoutBorders.log(Level.SEVERE, "Invalid AES padding during Cipher generation"); + BooksWithoutBorders.log(Level.SEVERE, StaticMessage.DEBUG_AES_INVALID_PADDING_CIPHER.toString()); return null; } return aes; @@ -178,14 +182,14 @@ public class AES implements Encryptor { try { keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); } catch (NoSuchAlgorithmException exception) { - BooksWithoutBorders.log(Level.SEVERE, "Invalid AES algorithm"); + BooksWithoutBorders.log(Level.SEVERE, StaticMessage.DEBUG_AES_INVALID_ALGORITHM.toString()); return null; } SecretKey tmp; try { tmp = keyFactory.generateSecret(spec); } catch (InvalidKeySpecException exception) { - BooksWithoutBorders.log(Level.SEVERE, "Invalid AES key specification"); + BooksWithoutBorders.log(Level.SEVERE, StaticMessage.DEBUG_AES_INVALID_KEY_SPECIFICATION.toString()); return null; } return new SecretKeySpec(tmp.getEncoded(), "AES"); diff --git a/src/main/java/net/knarcraft/bookswithoutborders/encryption/GenenCrypt.java b/src/main/java/net/knarcraft/bookswithoutborders/encryption/GenenCrypt.java index f605cbc..ae1423f 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/encryption/GenenCrypt.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/encryption/GenenCrypt.java @@ -12,6 +12,8 @@ import java.util.Random; * *

Not sure where this was gotten from, but it does exist at * Stack Exchange.

+ * + * @author AkiraAkiba */ public class GenenCrypt implements Encryptor { @@ -58,11 +60,12 @@ public class GenenCrypt implements Encryptor { originalCodonList.remove(index); } - // define the characters that can be encoded, 64 in total - // 26 capital letters - // 10 digits - // space, newline, and tab - // the symbols . , ? " ! @ # $ % ^ & * ( ) - + = / _ \ : ; < > + /* define the characters that can be encoded, 64 in total + 26 capital letters + 10 digits + space, newline, and tab + the symbols . , ? " ! @ # $ % ^ & * ( ) - + = / _ \ : ; < > + */ availableCharacters = new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", " ", "\t", "\n", ".", ",", "?", "\"", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "-", "+", "=", "/", diff --git a/src/main/java/net/knarcraft/bookswithoutborders/encryption/Magic.java b/src/main/java/net/knarcraft/bookswithoutborders/encryption/Magic.java index 613d606..350c1a1 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/encryption/Magic.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/encryption/Magic.java @@ -5,6 +5,8 @@ import org.jetbrains.annotations.Nullable; /** * So-called "Magic" encryption which simply makes the contents unreadable + * + * @author AkiraAkiba */ public class Magic implements Encryptor { diff --git a/src/main/java/net/knarcraft/bookswithoutborders/encryption/OneTimePad.java b/src/main/java/net/knarcraft/bookswithoutborders/encryption/OneTimePad.java index ad7a362..b7ab334 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/encryption/OneTimePad.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/encryption/OneTimePad.java @@ -10,6 +10,8 @@ import java.util.Base64; /** * A one-time pad implementation + * + * @author AkiraAkiba */ public class OneTimePad implements Encryptor { diff --git a/src/main/java/net/knarcraft/bookswithoutborders/encryption/SubstitutionCipher.java b/src/main/java/net/knarcraft/bookswithoutborders/encryption/SubstitutionCipher.java index 965148d..053d5cb 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/encryption/SubstitutionCipher.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/encryption/SubstitutionCipher.java @@ -8,6 +8,8 @@ import java.util.StringTokenizer; /** * A simple substitution cipher + * + * @author AkiraAkiba */ public class SubstitutionCipher implements Encryptor { diff --git a/src/main/java/net/knarcraft/bookswithoutborders/gui/AuthorBookIndex.java b/src/main/java/net/knarcraft/bookswithoutborders/gui/AuthorBookIndex.java index 744d128..cbe0086 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/gui/AuthorBookIndex.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/gui/AuthorBookIndex.java @@ -1,8 +1,10 @@ package net.knarcraft.bookswithoutborders.gui; import net.knarcraft.bookswithoutborders.BooksWithoutBorders; +import net.knarcraft.bookswithoutborders.config.translation.Formatting; import net.knarcraft.bookswithoutborders.utility.BookFileHelper; import net.knarcraft.bookswithoutborders.utility.BookFormatter; +import net.knarcraft.knarlib.formatting.TranslatableMessage; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.ClickEvent; import net.md_5.bungee.api.chat.ComponentBuilder; @@ -33,9 +35,9 @@ public class AuthorBookIndex extends BookIndex { int totalPages = (int) Math.ceil((double) availableBooks.size() / booksPerPage); if (page > totalPages) { - sender.sendMessage(ChatColor.GRAY + "No such page"); + BooksWithoutBorders.getStringFormatter().displayErrorMessage(sender, Formatting.NEUTRAL_AUTHOR_BOOKS_INVALID_PAGE); } else { - showAuthorBooks(sender, command, page, totalPages, availableBooks, authorName); + showAuthorBooks(sender, command, page, totalPages, availableBooks, authorName, listPublic); } } @@ -48,19 +50,16 @@ public class AuthorBookIndex extends BookIndex { * @param totalPages

The total amount of pages

* @param availableBooks

All books available to the sender

* @param authorName

The name of the author currently shown

+ * @param listPublic

Whether to display public books

*/ private static void showAuthorBooks(@NotNull CommandSender sender, @NotNull String command, int page, - int totalPages, @NotNull List availableBooks, @NotNull String authorName) { + int totalPages, @NotNull List availableBooks, + @NotNull String authorName, boolean listPublic) { ComponentBuilder componentBuilder = new ComponentBuilder(); String navigationCommand = command + " author" + authorName; - componentBuilder.append("--- "); - if (command.toLowerCase().contains("public")) { - componentBuilder.append("Publicly saved books by: ").color(ChatColor.GREEN).append(authorName).color(ChatColor.AQUA); - } else { - componentBuilder.append("Your saved books by: ").color(ChatColor.GREEN).append(authorName).color(ChatColor.AQUA); - } - componentBuilder.append(" ---", ComponentBuilder.FormatRetention.NONE).append("\n"); + TranslatableMessage message = listPublic ? Formatting.NEUTRAL_AUTHOR_PUBLIC_BOOKS_HEADER : Formatting.NEUTRAL_AUTHOR_PLAYER_BOOKS_HEADER; + componentBuilder.append(color(message, "{author}", authorName)); displayBookList(componentBuilder, command, page, availableBooks); @@ -84,10 +83,15 @@ public class AuthorBookIndex extends BookIndex { @NotNull List availableBooks) { int startIndex = (page - 1) * booksPerPage; for (int bookIndex = startIndex; bookIndex < Math.min(startIndex + booksPerPage, availableBooks.size()); bookIndex++) { - componentBuilder.append(getNiceName(availableBooks.get(bookIndex))).color(ChatColor.WHITE).event( + String title = BookFileHelper.getBookTitleFromPath(availableBooks.get(bookIndex)); + String author = BookFileHelper.getBookAuthorFromPath(availableBooks.get(bookIndex)); + String niceName = color(title) + color(Formatting.NEUTRAL_BOOK_LIST_AUTHOR_SEPARATOR) + color(author); + + componentBuilder.append(niceName).color(ChatColor.WHITE).event( new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + command + " " + availableBooks.get(bookIndex))).event( - new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("Select book by path"))); + new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(BooksWithoutBorders.getStringFormatter(). + getUnFormattedColoredMessage(Formatting.NEUTRAL_AUTHOR_BOOKS_PATH)))); componentBuilder.append("\n"); } } diff --git a/src/main/java/net/knarcraft/bookswithoutborders/gui/BookIndex.java b/src/main/java/net/knarcraft/bookswithoutborders/gui/BookIndex.java index 5a639d7..54c5187 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/gui/BookIndex.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/gui/BookIndex.java @@ -1,7 +1,12 @@ package net.knarcraft.bookswithoutborders.gui; -import net.knarcraft.bookswithoutborders.utility.BookFileHelper; +import net.knarcraft.bookswithoutborders.BooksWithoutBorders; +import net.knarcraft.bookswithoutborders.config.translation.Formatting; import net.knarcraft.bookswithoutborders.utility.InputCleaningHelper; +import net.knarcraft.knarlib.formatting.StringFormatter; +import net.knarcraft.knarlib.formatting.TranslatableMessage; +import net.knarcraft.knarlib.property.ColorConversion; +import net.knarcraft.knarlib.util.ColorHelper; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.ClickEvent; import net.md_5.bungee.api.chat.ComponentBuilder; @@ -9,7 +14,9 @@ import net.md_5.bungee.api.chat.HoverEvent; import net.md_5.bungee.api.chat.hover.content.Text; import org.bukkit.command.CommandSender; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import java.util.List; import java.util.Map; public abstract class BookIndex { @@ -80,8 +87,11 @@ public abstract class BookIndex { * @param page

The current page

* @param totalPages

The total amount of pages

*/ - protected static void displayTotalPages(@NotNull ComponentBuilder componentBuilder, @NotNull String command, int page, int totalPages) { - componentBuilder.append("Page " + page + " of " + totalPages, + protected static void displayTotalPages(@NotNull ComponentBuilder componentBuilder, @NotNull String command, + int page, int totalPages) { + String pageDisplay = color(Formatting.NEUTRAL_BOOK_LIST_TOTAL_PAGES, List.of("{current}", "{total}"), + List.of(String.valueOf(page), String.valueOf(totalPages))); + componentBuilder.append(pageDisplay, ComponentBuilder.FormatRetention.NONE).color(interactColor).event(new HoverEvent( HoverEvent.Action.SHOW_TEXT, new Text("/" + command + " page" + page))).event( new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + command + " page" + page)); @@ -101,10 +111,12 @@ public abstract class BookIndex { char character = (char) ('a' + characterIndex); if (firstInstances.containsKey(character)) { int pageIndex = (firstInstances.get(character) / booksPerPage) + 1; - componentBuilder.append(character + "").color(interactColor).event( - new ClickEvent(ClickEvent.Action.RUN_COMMAND, - "/" + command + " page" + pageIndex)).event(new HoverEvent( - HoverEvent.Action.SHOW_TEXT, new Text("Books starting with " + character))); + HoverEvent hoverEvent = new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text( + color(Formatting.NEUTRAL_BOOK_LIST_INDEX_HOVER, "{character}", "" + character))); + ClickEvent clickEvent = new ClickEvent(ClickEvent.Action.RUN_COMMAND, + "/" + command + " page" + pageIndex); + + componentBuilder.append(character + "").color(interactColor).event(clickEvent).event(hoverEvent); } else { componentBuilder.append(character + "", ComponentBuilder.FormatRetention.NONE).color(inactiveColor); } @@ -121,14 +133,16 @@ public abstract class BookIndex { */ protected static void displayPreviousButton(@NotNull ComponentBuilder componentBuilder, @NotNull String command, int page) { + String previousPage = color(Formatting.NEUTRAL_BOOK_LIST_PREVIOUS_PAGE); if (page > 1) { String fullCommand = "/" + command + " page" + (page - 1); - HoverEvent prevPagePreview = new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("To page " + (page - 1))); + HoverEvent prevPagePreview = new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text( + color(Formatting.NEUTRAL_BOOK_LIST_TO_PAGE, "{page}", String.valueOf(page - 1)))); ClickEvent prevPageClick = new ClickEvent(ClickEvent.Action.RUN_COMMAND, fullCommand); - componentBuilder.append("[<] Previous", ComponentBuilder.FormatRetention.NONE).color(interactColor) - .event(prevPagePreview).event(prevPageClick); + componentBuilder.append(previousPage, ComponentBuilder.FormatRetention.NONE).color(interactColor). + event(prevPagePreview).event(prevPageClick); } else { - componentBuilder.append("[<] Previous", ComponentBuilder.FormatRetention.NONE).color(inactiveColor); + componentBuilder.append(previousPage, ComponentBuilder.FormatRetention.NONE).color(inactiveColor); } } @@ -142,29 +156,73 @@ public abstract class BookIndex { */ protected static void displayNextButton(@NotNull ComponentBuilder componentBuilder, @NotNull String command, int page, int totalPages) { + String nextPage = color(Formatting.NEUTRAL_BOOK_LIST_NEXT_PAGE); if (page < totalPages) { String fullCommand = "/" + command + " page" + (page + 1); - HoverEvent nextPagePreview = new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("To page " + (page + 1))); + HoverEvent nextPagePreview = new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text( + color(Formatting.NEUTRAL_BOOK_LIST_TO_PAGE, "{page}", String.valueOf(page + 1)))); ClickEvent nextPageClick = new ClickEvent(ClickEvent.Action.RUN_COMMAND, fullCommand); - componentBuilder.append("Next [>]", ComponentBuilder.FormatRetention.NONE).color(interactColor) + componentBuilder.append(nextPage, ComponentBuilder.FormatRetention.NONE).color(interactColor) .event(nextPagePreview).event(nextPageClick); } else { - componentBuilder.append("Next [>]", ComponentBuilder.FormatRetention.NONE).color(inactiveColor); + componentBuilder.append(nextPage, ComponentBuilder.FormatRetention.NONE).color(inactiveColor); } } /** - * Gets a nice name from a book's path + * Colors a translatable message * - * @param bookPath

The path of a book

- * @return

The prettified book name

+ * @param translatableMessage

The message to color

+ * @return

The colored message

*/ @NotNull - protected static String getNiceName(@NotNull String bookPath) { - String title = BookFileHelper.getBookTitleFromPath(bookPath); - String author = BookFileHelper.getBookAuthorFromPath(bookPath); - return ChatColor.translateAlternateColorCodes('&', - title + ChatColor.RESET + " by " + author + ChatColor.RESET); + protected static String color(@NotNull TranslatableMessage translatableMessage) { + return color(translatableMessage, (List) null, null); + } + + /** + * Colors a translatable message + * + * @param translatableMessage

The message to color

+ * @param placeholder

Placeholder to replace

+ * @param replacement

Replacement value

+ * @return

The colored message

+ */ + @NotNull + protected static String color(@NotNull TranslatableMessage translatableMessage, @NotNull String placeholder, + @NotNull String replacement) { + return color(translatableMessage, List.of(placeholder), List.of(replacement)); + } + + /** + * Colors a translatable message + * + * @param translatableMessage

The message to color

+ * @param placeholders

Placeholders to replace

+ * @param replacements

Replacement values

+ * @return

The colored message

+ */ + @NotNull + protected static String color(@NotNull TranslatableMessage translatableMessage, @Nullable List placeholders, + @Nullable List replacements) { + StringFormatter stringFormatter = BooksWithoutBorders.getStringFormatter(); + if (placeholders == null || replacements == null) { + return stringFormatter.getUnFormattedColoredMessage(translatableMessage); + } else { + return ColorHelper.translateColorCodes(stringFormatter.replacePlaceholders(translatableMessage, + placeholders, replacements), ColorConversion.RGB); + } + } + + /** + * Colors a message + * + * @param input

The message to color

+ * @return

The colored message

+ */ + @NotNull + protected static String color(@NotNull String input) { + return ColorHelper.translateColorCodes(input, ColorConversion.RGB); } } diff --git a/src/main/java/net/knarcraft/bookswithoutborders/gui/PagedBookIndex.java b/src/main/java/net/knarcraft/bookswithoutborders/gui/PagedBookIndex.java index 16e2fbf..7b8d941 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/gui/PagedBookIndex.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/gui/PagedBookIndex.java @@ -1,7 +1,10 @@ package net.knarcraft.bookswithoutborders.gui; import net.knarcraft.bookswithoutborders.BooksWithoutBorders; +import net.knarcraft.bookswithoutborders.config.translation.Formatting; +import net.knarcraft.bookswithoutborders.utility.BookFileHelper; import net.knarcraft.bookswithoutborders.utility.BookFormatter; +import net.knarcraft.knarlib.formatting.TranslatableMessage; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.ClickEvent; import net.md_5.bungee.api.chat.ComponentBuilder; @@ -42,9 +45,9 @@ public class PagedBookIndex extends BookIndex { int totalPages = (int) Math.ceil((double) availableBooks.size() / booksPerPage); if (page > totalPages) { - sender.sendMessage(ChatColor.GRAY + "No such page"); + BooksWithoutBorders.getStringFormatter().displayErrorMessage(sender, Formatting.NEUTRAL_AUTHOR_BOOKS_INVALID_PAGE); } else { - showBookMenu(sender, command, page, totalPages, availableBooks, firstInstances); + showBookMenu(sender, command, page, totalPages, availableBooks, firstInstances, listPublic); } } @@ -57,19 +60,15 @@ public class PagedBookIndex extends BookIndex { * @param totalPages

The total amount of pages

* @param availableBooks

All books available to the sender

* @param firstInstances

The map between a character, and the index of the first instance of that character in the book list

+ * @param listPublic

Whether to display public books

*/ private static void showBookMenu(@NotNull CommandSender sender, @NotNull String command, int page, int totalPages, @NotNull List availableBooks, - @NotNull Map firstInstances) { + @NotNull Map firstInstances, boolean listPublic) { ComponentBuilder componentBuilder = new ComponentBuilder(); - componentBuilder.append("--- "); - if (command.toLowerCase().contains("public")) { - componentBuilder.append("Publicly saved books").color(ChatColor.GREEN); - } else { - componentBuilder.append("Your saved books").color(ChatColor.GREEN); - } - componentBuilder.append(" ---", ComponentBuilder.FormatRetention.NONE).append("\n"); + TranslatableMessage message = listPublic ? Formatting.NEUTRAL_BOOK_LIST_PUBLIC_BOOKS_HEADER : Formatting.NEUTRAL_BOOK_LIST_PLAYER_BOOKS_HEADER; + componentBuilder.append(color(message)); displayBookList(componentBuilder, command, page, availableBooks); displayPreviousButton(componentBuilder, command, page); @@ -94,24 +93,23 @@ public class PagedBookIndex extends BookIndex { @NotNull List availableBooks) { int startIndex = (page - 1) * booksPerPage; for (int bookIndex = startIndex; bookIndex < Math.min(startIndex + booksPerPage, availableBooks.size()); bookIndex++) { - componentBuilder.append("[" + (bookIndex + 1) + "]").color(interactColor).event( - new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + command + " " + - (bookIndex + 1))).event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, - new Text("Select book by index"))); + String title = color(BookFileHelper.getBookTitleFromPath(availableBooks.get(bookIndex))); + String author = color(BookFileHelper.getBookAuthorFromPath(availableBooks.get(bookIndex))); + ClickEvent indexClick = new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + command + " " + (bookIndex + 1)); + HoverEvent indexHover = new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(color(Formatting.NEUTRAL_BOOK_LIST_BOOK_INDEX_HOVER))); + ClickEvent pathClick = new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + command + " " + availableBooks.get(bookIndex)); + HoverEvent pathHover = new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(color(Formatting.NEUTRAL_BOOK_LIST_PATH_HOVER))); + ClickEvent authorClick = new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/" + command + " author" + BookFormatter.stripColor(author) + " page1"); + HoverEvent authorHover = new HoverEvent(HoverEvent.Action.SHOW_TEXT, + new Text(color(Formatting.NEUTRAL_BOOK_LIST_AUTHOR_HOVER, "{author}", BookFormatter.stripColor(author)))); + + componentBuilder.append(color(Formatting.NEUTRAL_BOOK_LIST_BOOK_INDEX_NUMBER, "{index}", + String.valueOf(bookIndex + 1))).color(interactColor).event(indexClick).event(indexHover); componentBuilder.append(" ", ComponentBuilder.FormatRetention.NONE); - - String[] parts = getNiceName(availableBooks.get(bookIndex)).split(" by "); - componentBuilder.append(parts[0]).color(ChatColor.WHITE).event( - new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + command + " " + - availableBooks.get(bookIndex))).event( - new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("Select book by path"))); - componentBuilder.append(" by ", ComponentBuilder.FormatRetention.NONE).color(ChatColor.WHITE); - componentBuilder.append(parts[1]).color(ChatColor.WHITE).event( - new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/" + command + " author" + - BookFormatter.stripColor(parts[1]) + " page1")).event( - new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("Books by " + - BookFormatter.stripColor(parts[1])))); + componentBuilder.append(title).color(ChatColor.WHITE).event(pathClick).event(pathHover); + componentBuilder.append(color(Formatting.NEUTRAL_BOOK_LIST_AUTHOR_SEPARATOR), ComponentBuilder.FormatRetention.NONE).color(ChatColor.WHITE); + componentBuilder.append(author).color(ChatColor.WHITE).event(authorClick).event(authorHover); componentBuilder.append("\n"); } } diff --git a/src/main/java/net/knarcraft/bookswithoutborders/utility/InventoryHelper.java b/src/main/java/net/knarcraft/bookswithoutborders/utility/InventoryHelper.java index 461638e..bfc65bc 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/utility/InventoryHelper.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/utility/InventoryHelper.java @@ -3,6 +3,7 @@ package net.knarcraft.bookswithoutborders.utility; import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.state.BookHoldingState; import net.knarcraft.bookswithoutborders.state.ItemSlot; +import net.knarcraft.knarlib.formatting.StringFormatter; import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; @@ -79,16 +80,17 @@ public final class InventoryHelper { */ public static boolean notHoldingOneWritableBookCheck(@NotNull Player player, @NotNull String noBookMessage, @NotNull String twoBooksMessage) { + StringFormatter stringFormatter = BooksWithoutBorders.getStringFormatter(); BookHoldingState holdingState = getBookHoldingState(player); if (holdingState == BookHoldingState.NONE || holdingState == BookHoldingState.SIGNED_BOTH_HANDS || holdingState == BookHoldingState.SIGNED_MAIN_HAND || holdingState == BookHoldingState.SIGNED_OFF_HAND) { - BooksWithoutBorders.sendErrorMessage(player, noBookMessage); + stringFormatter.displayErrorMessage(player, noBookMessage); return true; } if (holdingState == BookHoldingState.UNSIGNED_BOTH_HANDS) { - BooksWithoutBorders.sendErrorMessage(player, twoBooksMessage); + stringFormatter.displayErrorMessage(player, twoBooksMessage); return true; } diff --git a/src/main/resources/strings.yml b/src/main/resources/strings.yml index ac4cf1d..e2c316c 100644 --- a/src/main/resources/strings.yml +++ b/src/main/resources/strings.yml @@ -23,6 +23,7 @@ en: SUCCESS_BOOKSHELF_LORE_SET: "Lore successfully saved!" SUCCESS_GENERATION_CHANGED: "Book generation successfully changed!" SUCCESS_LORE_SET: "Added lore to item!" + SUCCESS_TITLE_SET: "Title set to {title}!" ACTION_COPY: "copy" ACTION_CLEAR: "clear" ACTION_DECRYPT: "decrypt" @@ -30,6 +31,7 @@ en: ACTION_ADD_TITLE_AUTHOR_PAGE: "add an author title page to" ACTION_SET_AUTHOR: "set author" ACTION_CHANGE_GENERATION: "change generation" + ACTION_UNSIGN: "unsign" ERROR_PLAYER_ONLY: "This command can only be used by a player!" ERROR_NOT_HOLDING_WRITTEN_BOOK: "You must be holding a written book to {action} it!" ERROR_NOT_HOLDING_WRITABLE_BOOK: "You must be holding a writable book to {action} it!" @@ -86,8 +88,12 @@ en: ERROR_GENERATION_NOT_SPECIFIED: "You must specify the new generation for your book!" ERROR_GENERATION_INVALID: "Invalid book generation specified!" ERROR_LORE_EMPTY: "You must specify the new lore to set!" - ERROR_LORE_NO_ITEM: "Must be holding an item to set lore!" + ERROR_NO_ITEM: "You must be holding an item to use this command!" ERROR_TITLE_EMPTY: "You must specify the new title/display name to set!" + ERROR_TITLE_LENGTH: "Book titles are capped at 32 characters!" + # ---------------------------------- # + # Custom formatting for some output # + # ---------------------------------- # NEUTRAL_COMMANDS_HEADER: | &nBooks without Borders help page&r &e[] = optional, <> = required (see each description for exceptions) @@ -100,8 +106,28 @@ en: NEUTRAL_COMMANDS_COMMAND_NO_PERMISSION_REQUIRED: "None" NEUTRAL_COMMANDS_COMMAND_PERMISSION: " &7{{permission}}" NEUTRAL_COMMANDS_ALIASES: " &f(&b{aliases}&f)" - NEUTRAL_UNKNOWN_AUTHOR: "Unknown" - NEUTRAL_UNKNOWN_TITLE: "Untitled" NEUTRAL_TITLE_PAGE_TITLE_AUTHOR_FORMAT: "{title}{separator}By: {author}" NEUTRAL_TITLE_PAGE_HEADER_FORMAT: "\n&n&l{header}&r" - NEUTRAL_TITLE_PAGE_TEXT_FORMAT: "\n\n&o{text}&r" \ No newline at end of file + NEUTRAL_TITLE_PAGE_TEXT_FORMAT: "\n\n&o{text}&r" + NEUTRAL_AUTHOR_PUBLIC_BOOKS_HEADER: "--- &aPublicly saved books by: &b{author}&r ---\n" + NEUTRAL_AUTHOR_PLAYER_BOOKS_HEADER: "--- &aYour saved books by: &b{author}&r ---\n" + NEUTRAL_AUTHOR_BOOKS_PATH: "Select book by path" + NEUTRAL_AUTHOR_BOOKS_INVALID_PAGE: "&7No such page" + NEUTRAL_BOOK_LIST_TOTAL_PAGES: "Page {current} of {total}" + NEUTRAL_BOOK_LIST_PREVIOUS_PAGE: "[<] Previous" + NEUTRAL_BOOK_LIST_NEXT_PAGE: "Next [>]" + NEUTRAL_BOOK_LIST_AUTHOR_SEPARATOR: "&r by " + NEUTRAL_BOOK_LIST_TO_PAGE: "To page {page}" + NEUTRAL_BOOK_LIST_INDEX_HOVER: "Books starting with {character}" + NEUTRAL_BOOK_LIST_PUBLIC_BOOKS_HEADER: "--- &aPublicly saved books&r ---\n" + NEUTRAL_BOOK_LIST_PLAYER_BOOKS_HEADER: "--- &aYour saved books&r ---\n" + NEUTRAL_BOOK_LIST_AUTHOR_HOVER: "Books by {author}" + NEUTRAL_BOOK_LIST_PATH_HOVER: "Select book by path" + NEUTRAL_BOOK_LIST_BOOK_INDEX_HOVER: "Select book by index" + NEUTRAL_BOOK_LIST_BOOK_INDEX_NUMBER: "[{index}]" + # -----------------------------------------# + # Translations of unknown/untitled author. # + # Altering this might cause problems. # + # -----------------------------------------# + NEUTRAL_UNKNOWN_AUTHOR: "Unknown" + NEUTRAL_UNKNOWN_TITLE: "Untitled" \ No newline at end of file