//  [java]
    public void translate(GeyserSession session, BookEditPacket packet) {
        if (packet.getText() != null && !packet.getText().isEmpty() && packet.getText().getBytes(StandardCharsets.UTF_8).length > MAXIMUM_PAGE_LENGTH) {
            session.getConnector().getLogger().warning("Page length greater than server allowed!");
            return;
        }

        GeyserItemStack itemStack = session.getPlayerInventory().getItemInHand();
        if (itemStack != null) {
            CompoundTag tag = itemStack.getNbt() != null ? itemStack.getNbt() : new CompoundTag("");
            ItemStack bookItem = new ItemStack(itemStack.getJavaId(), itemStack.getAmount(), tag);
            List<Tag> pages = tag.contains("pages") ? new LinkedList<>(((ListTag) tag.get("pages")).getValue()) : new LinkedList<>();

            int page = packet.getPageNumber();
            switch (packet.getAction()) {
                case ADD_PAGE: {
                    // Add empty pages in between
                    for (int i = pages.size(); i < page; i++) {
                        pages.add(i, new StringTag("", ""));
                    }
                    pages.add(page, new StringTag("", packet.getText()));
                    break;
                }
                // Called whenever a page is modified
                case REPLACE_PAGE: {
                    if (page < pages.size()) {
                        pages.set(page, new StringTag("", packet.getText()));
                    } else {
                        // Add empty pages in between
                        for (int i = pages.size(); i < page; i++) {
                            pages.add(i, new StringTag("", ""));
                        }
                        pages.add(page, new StringTag("", packet.getText()));
                    }
                    break;
                }
                case DELETE_PAGE: {
                    if (page < pages.size()) {
                        pages.remove(page);
                    }
                    break;
                }
                case SWAP_PAGES: {
                    int page2 = packet.getSecondaryPageNumber();
                    if (page < pages.size() && page2 < pages.size()) {
                        Collections.swap(pages, page, page2);
                    }
                    break;
                }
                case SIGN_BOOK: {
                    tag.put(new StringTag("author", packet.getAuthor()));
                    tag.put(new StringTag("title", packet.getTitle()));
                    break;
                }
                default:
                    return;
            }
            // Remove empty pages at the end
            while (pages.size() > 0) {
                StringTag currentPage = (StringTag) pages.get(pages.size() - 1);
                if (currentPage.getValue() == null || currentPage.getValue().isEmpty()) {
                    pages.remove(pages.size() - 1);
                } else {
                    break;
                }
            }
            tag.put(new ListTag("pages", pages));
            // Update local copy
            session.getPlayerInventory().setItem(36 + session.getPlayerInventory().getHeldItemSlot(), GeyserItemStack.from(bookItem), session);
            session.getInventoryTranslator().updateInventory(session, session.getPlayerInventory());

            List<String> networkPages = new ArrayList<>();
            for (Tag pageTag : pages) {
                networkPages.add(((StringTag) pageTag).getValue());
            }

            String title;
            if (packet.getAction() == BookEditPacket.Action.SIGN_BOOK) {
                // Add title to packet so the server knows we're signing
                if (packet.getTitle().getBytes(StandardCharsets.UTF_8).length > MAXIMUM_TITLE_LENGTH) {
                    session.getConnector().getLogger().warning("Book title larger than server allows!");
                    return;
                }

                title = packet.getTitle();
            } else {
                title = null;
            }

            session.getBookEditCache().setPacket(new ClientEditBookPacket(session.getPlayerInventory().getHeldItemSlot(), networkPages, title));
            // There won't be any more book updates after this, so we can try sending the edit packet immediately
            if (packet.getAction() == BookEditPacket.Action.SIGN_BOOK) {
                session.getBookEditCache().checkForSend();
            }
        }
    }