diff --git a/public/configuration/UITexts.example b/public/configuration/UITexts.example index 423d2a8..2b87ccb 100644 --- a/public/configuration/UITexts.example +++ b/public/configuration/UITexts.example @@ -1,258 +1,268 @@ { - "notification.badge.received": "Nuovo Distintivo!", - "wiredfurni.badgereceived.title": "Distintivo ricevuto!", - "wiredfurni.badgereceived.body": "Hai appena ricevuto un nuovo Distintivo! Controlla nel tuo Inventario!", - "friendlist.search": "Search friends", - "purse.seasonal.currency.101": "cash", - "widget.chooser.checkall": "Select furniture", - "widget.chooser.btn.pickall": "pick up selected items!", - "wiredfurni.params.requireall.2": "If one of the selected furni has an avatar", - "wiredfurni.params.requireall.3": "If all selected furni have avatars on them", - "widget.settings.general": "General", - "widget.settings.general.title": "Adjust the default Nitro settings", - "widget.settings.volume": "Volume", - "widget.settings.interface": "Interface", - "widget.settings.interface.title": "Adjust the interface settings", - "widget.settings.interface.fps.automatic": "Set FPS to unlimited", - "widget.settings.interface.fps.warning": "Setting FPS to unlimited may cause performance issues!", - "widget.settings.interface.secondary": "Change the window header color", - "widget.settings.interface.reset": "Reset header color to default", - "widget.room.chat.hide_pets": "Hide pets", - "widget.room.chat.hide_avatars": "Hide avatars", - "widget.room.chat.hide_balloon": "Hide speech bubble", - "widget.room.chat.show_balloon": "Speech bubble", - "widget.room.chat.clear_history": "clear history", - "widget.room.youtube.shared": "YouTube is being shared", - "widget.room.youtube.open_video": "Open the video", - "wiredfurni.tooltip.select.tile": "Select tile", - "wiredfurni.tooltip.remove.tile": "Deselect tile", - "wiredfurni.tooltip.remove.5x5_tile": "select 5x5 tiles", - "wiredfurni.tooltip.remove.clear_tile": "Clear all selections", - "wiredfurni.params.furni_neighborhood.group.user": "Players", - "wiredfurni.params.furni_neighborhood.group.furni": "Furniture", - "wiredfurni.params.selector_option.bot": "No bots", - "wiredfurni.params.selector_option.pet": "No pets", - "catalog.title": "Catalog", - "catalog.favorites": "Favorites", - "catalog.favorites.pages": "Pages", - "catalog.favorites.furni": "Furni", - "catalog.favorites.empty": "No favorites", - "catalog.favorites.empty.hint": "Click the heart on furni or the star on pages to add them.", - "catalog.admin": "Admin", - "catalog.admin.new": "New", - "catalog.admin.root": "Root", - "catalog.admin.new.root.category": "New root category", - "catalog.admin.edit.root": "Edit Root", - "catalog.admin.edit": "Edit:", - "catalog.admin.edit.page": "Edit Page", - "catalog.admin.hidden": "hidden", - "catalog.admin.edit.title": "Edit \"%name%\"", - "catalog.admin.show": "Show", - "catalog.admin.hide": "Hide", - "catalog.admin.delete": "Delete", - "catalog.admin.delete.title": "Delete \"%name%\"", - "catalog.admin.delete.category.confirm": "Delete category \"%name%\" and all its content?", - "catalog.admin.delete.page": "Delete page", - "catalog.admin.delete.page.confirm": "Delete page \"%name%\"?", - "catalog.admin.delete.offer.confirm": "Are you sure you want to delete this offer?", - "catalog.admin.create": "Create", - "catalog.admin.save": "Save", - "catalog.admin.create.subpage": "Create sub-page", - "catalog.admin.order": "Order", - "catalog.admin.visible": "Visible", - "catalog.admin.enabled": "Enabled", - "catalog.admin.offer.new": "New Offer", - "catalog.admin.offer.edit": "Edit Offer", - "catalog.admin.offer.name": "Catalog Name", - "catalog.admin.offer.general": "General", - "catalog.admin.offer.quantity": "Quantity", - "catalog.admin.offer.prices": "Prices", - "catalog.admin.offer.credits": "Credits", - "catalog.admin.offer.points": "Points", - "catalog.admin.offer.points.type": "Points Type", - "catalog.admin.offer.options": "Options", - "catalog.admin.offer.club.only": "Club Only", - "catalog.admin.offer.extradata": "Extra Data (optional)....", - "catalog.admin.offer.have.offer": "Multi-discount (have_offer)", - "catalog.trophies.title": "Trophies", - "catalog.trophies.write.hint": "Write a text for the trophy before purchasing", - "catalog.trophies.inscription": "Trophy Inscription", - "catalog.trophies.inscription.placeholder": "Write the text that will appear on the trophy...", - "catalog.pets.show.colors": "Show colors", - "catalog.pets.choose.color": "Choose color", - "catalog.pets.choose.breed": "Choose breed", - "catalog.pets.back.breeds": "? Breeds", - "catalog.prefix.text": "Text", - "catalog.prefix.text.placeholder": "Enter text...", - "catalog.prefix.icon": "Icon", - "catalog.prefix.icon.remove": "Remove icon", - "catalog.prefix.effect": "Effect", - "catalog.prefix.color": "Color", - "catalog.prefix.color.single": "?? Single", - "catalog.prefix.color.per.letter": "?? Per Letter", - "catalog.prefix.color.hint": "Select a letter, then choose the color. Auto-advances.", - "catalog.prefix.color.apply.all.title": "Apply current color to all letters", - "catalog.prefix.color.apply.all": "Apply to all", - "catalog.prefix.color.selected": "Selected letter:", - "catalog.prefix.price": "Price:", - "catalog.prefix.price.amount": "5 Credits", - "catalog.prefix.purchased": "? Purchased!", - "catalog.prefix.purchase": "Purchase", - "modtools.userinfo.title": "User Info: %username%", - "modtools.userinfo.userName": "Name", - "modtools.userinfo.cfhCount": "CFHs", - "modtools.userinfo.abusiveCfhCount": "Abusive CFHs", - "modtools.userinfo.cautionCount": "Cautions", - "modtools.userinfo.banCount": "Bans", - "modtools.userinfo.lastSanctionTime": "Last Sanction", - "modtools.userinfo.tradingLockCount": "Trade Locks", - "modtools.userinfo.tradingExpiryDate": "Lock Expires", - "modtools.userinfo.minutesSinceLastLogin": "Last Login", - "modtools.userinfo.lastPurchaseDate": "Last Purchase", - "modtools.userinfo.primaryEmailAddress": "Email", - "modtools.userinfo.identityRelatedBanCount": "Banned Accs", - "modtools.userinfo.registrationAgeInMinutes": "Registered", - "modtools.userinfo.userClassification": "Rank", - "modtools.window.title": "Mod Tools", - "modtools.window.tools.room": "Room Tool", - "modtools.window.tools.chatlog": "Chatlog Tool", - "modtools.window.tools.report": "Report Tool", - "modtools.window.select.user": "Select a user", - "modtools.window.no.room": "Enter a room first", - "modtools.window.user.in_room": "Still in this room", - "modtools.window.user.left_room": "No longer in this room", - "modtools.window.user.clear": "Clear selection", - "modtools.window.tickets.open": "%count% open ticket", - "modtools.window.tickets.open.many": "%count% open tickets", - "modtools.window.section.room": "Room", - "modtools.window.section.user": "User", - "modtools.window.section.reports": "Reports", - "modtools.window.user.open_info": "Open Info", - "modtools.userinfo.refresh": "Refresh user info", - "modtools.userinfo.presence.in_room": "In room", - "modtools.userinfo.presence.in_room.title": "In the room you are observing", - "modtools.userinfo.presence.online": "Online", - "modtools.userinfo.presence.online.title": "Online on the hotel", - "modtools.userinfo.presence.offline": "Offline", - "modtools.userinfo.presence.offline.title": "Offline at panel open", - "modtools.userinfo.section.account": "Account", - "modtools.userinfo.section.activity": "Activity", - "modtools.userinfo.section.sanctions": "Sanctions", - "modtools.userinfo.section.trading": "Trading", - "modtools.userinfo.button.room.chat": "Room Chat", - "modtools.userinfo.button.send.message": "Send Message", - "modtools.userinfo.button.room.visits": "Room Visits", - "modtools.userinfo.button.mod.action": "Mod Action", - "modtools.userinfo.stat.cfh": "CFH", - "modtools.userinfo.stat.cautions": "Cautions", - "modtools.userinfo.stat.bans": "Bans", - "modtools.userinfo.stat.trade.locks": "Trade locks", - "modtools.roominfo.title": "Room Info", - "modtools.roominfo.refresh": "Refresh room info", - "modtools.roominfo.loading": "Loading…", - "modtools.roominfo.owner.here": "Owner here", - "modtools.roominfo.owner.away": "Owner away", - "modtools.roominfo.owner.title.here": "The room owner is currently inside", - "modtools.roominfo.owner.title.away": "The room owner is NOT inside", - "modtools.roominfo.stat.users": "Users", - "modtools.roominfo.stat.owner": "Owner", - "modtools.roominfo.owner.open": "Open %username%'s info", - "modtools.roominfo.button.visit": "Visit Room", - "modtools.roominfo.button.chatlog": "Chatlog", - "modtools.roominfo.moderate.title": "Moderate room", - "modtools.roominfo.moderate.kick": "Kick everyone out", - "modtools.roominfo.moderate.doorbell": "Enable the doorbell", - "modtools.roominfo.moderate.rename": "Change room name", - "modtools.roominfo.moderate.message.placeholder": "Mandatory message to deliver with the action…", - "modtools.roominfo.moderate.send.caution": "Send Caution", - "modtools.roominfo.moderate.send.alert": "Send Alert", - "modtools.user.message.title": "Send Message", - "modtools.user.message.recipient": "Message to", - "modtools.user.message.label": "Message", - "modtools.user.message.placeholder": "Write something useful — the user will see it as a moderator message.", - "modtools.user.message.empty": "Empty", - "modtools.user.message.chars": "%count% chars", - "modtools.user.message.send": "Send Message", - "modtools.user.modaction.title": "Mod Action: %username%", - "modtools.user.modaction.sanctioning": "Sanctioning", - "modtools.user.modaction.step.topic": "1. CFH Topic", - "modtools.user.modaction.step.topic.placeholder": "Select a topic…", - "modtools.user.modaction.step.sanction": "2. Sanction", - "modtools.user.modaction.step.sanction.placeholder": "Select a sanction…", - "modtools.user.modaction.step.message": "3. Custom message", - "modtools.user.modaction.step.message.optional": "(optional — overrides default)", - "modtools.user.modaction.message.placeholder": "Leave empty to use the default topic message", - "modtools.user.modaction.preview": "Preview", - "modtools.user.modaction.button.default": "Default Sanction", - "modtools.user.modaction.button.apply": "Apply Sanction", - "modtools.user.modaction.error.no.topic": "You must select a CFH topic", - "modtools.user.modaction.error.no.action": "You must select a CFH topic and Sanction", - "modtools.user.modaction.error.no.permission": "You do not have permission to do this", - "modtools.user.modaction.error.no.message": "Please write a message to user", - "modtools.user.modaction.error.no.permission.alert": "You have insufficient permissions", - "modtools.user.visits.title": "User Visits", - "modtools.user.visits.recent": "Recent visited rooms", - "modtools.user.visits.entries.one": "%count% entry", - "modtools.user.visits.entries.many": "%count% entries", - "modtools.user.visits.empty": "No recent visits", - "modtools.user.visits.time": "Time", - "modtools.user.visits.room": "Room name", - "modtools.user.visits.action": "Action", - "modtools.user.visits.visit": "Visit", - "modtools.user.visits.visit.title": "Visit room", - "modtools.user.chatlog.title": "User Chatlog", - "modtools.user.chatlog.title.with": "User Chatlog: %username%", - "modtools.user.chatlog.loading": "Loading chatlog…", - "modtools.room.chatlog.title": "Room Chatlog", - "modtools.chatlog.column.time": "Time", - "modtools.chatlog.column.user": "User", - "modtools.chatlog.column.message": "Message", - "modtools.chatlog.empty": "No messages", - "modtools.chatlog.visit": "Visit", - "modtools.chatlog.tools": "Tools", - "modtools.tickets.title": "Tickets", - "modtools.tickets.tab.open": "Open", - "modtools.tickets.tab.mine": "Mine", - "modtools.tickets.tab.picked": "All picked", - "modtools.tickets.column.type": "Type", - "modtools.tickets.column.reported": "Reported", - "modtools.tickets.column.opened": "Opened", - "modtools.tickets.column.picker": "Picker", - "modtools.tickets.empty.open": "No open issues", - "modtools.tickets.empty.mine": "No issues picked by you", - "modtools.tickets.empty.picked": "No picked issues", - "modtools.tickets.action.pick": "Pick", - "modtools.tickets.action.handle": "Handle", - "modtools.tickets.action.release": "Release", - "modtools.tickets.issue.title": "Resolving issue #%issueId%", - "modtools.tickets.issue.label": "Issue #%issueId%", - "modtools.tickets.issue.details": "Details", - "modtools.tickets.issue.field.source": "Source", - "modtools.tickets.issue.field.category": "Category", - "modtools.tickets.issue.field.description": "Description", - "modtools.tickets.issue.field.caller": "Caller", - "modtools.tickets.issue.field.reported": "Reported", - "modtools.tickets.issue.chatlog.view": "View chatlog", - "modtools.tickets.issue.chatlog.close": "Close chatlog", - "modtools.tickets.issue.resolve.heading": "Resolve as", - "modtools.tickets.issue.resolve.resolved": "Resolved", - "modtools.tickets.issue.resolve.useless": "Useless", - "modtools.tickets.issue.resolve.abusive": "Abusive", - "modtools.tickets.issue.release": "Release back to queue", - "modtools.tickets.cfh.chatlog.title": "Issue #%issueId% Chatlog", - "groupforum.list.tab.most_active": "Most active threads", - "groupforum.list.tab.my_forums": "My group forums", - "groupforum.list.no_forums": "There are no forums", - "groupforum.view.threads": "Number of threads", - "groupforum.thread.pin": "Pin thread", - "groupforum.thread.unpin": "Unpin thread", - "groupforum.thread.lock": "Lock thread", - "groupforum.thread.unlock": "Unlock thread", - "groupforum.thread.hide": "Hide thread", - "groupforum.thread.restore": "Restore thread", - "groupforum.thread.delete": "Delete thread + posts", - "groupforum.message.hide": "Hide message", - "group.forum.enable.caption": "Enable / Disable group forum", - "group.forum.enable.help": "If you disable the group forum, all posts will also be deleted!", - "groupforum.view.no_threads": "There are currently no active threads" + "notification.badge.received": "Nuovo Distintivo!", + "wiredfurni.badgereceived.title": "Distintivo ricevuto!", + "wiredfurni.badgereceived.body": "Hai appena ricevuto un nuovo Distintivo! Controlla nel tuo Inventario!", + "friendlist.search": "Search friends", + "purse.seasonal.currency.101": "cash", + "widget.chooser.checkall": "Select furniture", + "widget.chooser.btn.pickall": "pick up selected items!", + "wiredfurni.params.requireall.2": "If one of the selected furni has an avatar", + "wiredfurni.params.requireall.3": "If all selected furni have avatars on them", + "widget.settings.general": "General", + "widget.settings.general.title": "Adjust the default Nitro settings", + "widget.settings.volume": "Volume", + "widget.settings.interface": "Interface", + "widget.settings.interface.title": "Adjust the interface settings", + "widget.settings.interface.fps.automatic": "Set FPS to unlimited", + "widget.settings.interface.fps.warning": "Setting FPS to unlimited may cause performance issues!", + "widget.settings.interface.secondary": "Change the window header color", + "widget.settings.interface.reset": "Reset header color to default", + "widget.room.chat.hide_pets": "Hide pets", + "widget.room.chat.hide_avatars": "Hide avatars", + "widget.room.chat.hide_balloon": "Hide speech bubble", + "widget.room.chat.show_balloon": "Speech bubble", + "widget.room.chat.clear_history": "clear history", + "widget.room.youtube.shared": "YouTube is being shared", + "widget.room.youtube.open_video": "Open the video", + "wiredfurni.tooltip.select.tile": "Select tile", + "wiredfurni.tooltip.remove.tile": "Deselect tile", + "wiredfurni.tooltip.remove.5x5_tile": "select 5x5 tiles", + "wiredfurni.tooltip.remove.clear_tile": "Clear all selections", + "wiredfurni.params.furni_neighborhood.group.user": "Players", + "wiredfurni.params.furni_neighborhood.group.furni": "Furniture", + "wiredfurni.params.selector_option.bot": "No bots", + "wiredfurni.params.selector_option.pet": "No pets", + "catalog.title": "Catalog", + "catalog.favorites": "Favorites", + "catalog.favorites.pages": "Pages", + "catalog.favorites.furni": "Furni", + "catalog.favorites.empty": "No favorites", + "catalog.favorites.empty.hint": "Click the heart on furni or the star on pages to add them.", + "catalog.admin": "Admin", + "catalog.admin.new": "New", + "catalog.admin.root": "Root", + "catalog.admin.new.root.category": "New root category", + "catalog.admin.edit.root": "Edit Root", + "catalog.admin.edit": "Edit:", + "catalog.admin.edit.page": "Edit Page", + "catalog.admin.hidden": "hidden", + "catalog.admin.edit.title": "Edit \"%name%\"", + "catalog.admin.show": "Show", + "catalog.admin.hide": "Hide", + "catalog.admin.delete": "Delete", + "catalog.admin.delete.title": "Delete \"%name%\"", + "catalog.admin.delete.category.confirm": "Delete category \"%name%\" and all its content?", + "catalog.admin.delete.page": "Delete page", + "catalog.admin.delete.page.confirm": "Delete page \"%name%\"?", + "catalog.admin.delete.offer.confirm": "Are you sure you want to delete this offer?", + "catalog.admin.create": "Create", + "catalog.admin.save": "Save", + "catalog.admin.create.subpage": "Create sub-page", + "catalog.admin.order": "Order", + "catalog.admin.visible": "Visible", + "catalog.admin.enabled": "Enabled", + "catalog.admin.offer.new": "New Offer", + "catalog.admin.offer.edit": "Edit Offer", + "catalog.admin.offer.name": "Catalog Name", + "catalog.admin.offer.general": "General", + "catalog.admin.offer.quantity": "Quantity", + "catalog.admin.offer.prices": "Prices", + "catalog.admin.offer.credits": "Credits", + "catalog.admin.offer.points": "Points", + "catalog.admin.offer.points.type": "Points Type", + "catalog.admin.offer.options": "Options", + "catalog.admin.offer.club.only": "Club Only", + "catalog.admin.offer.extradata": "Extra Data (optional)....", + "catalog.admin.offer.have.offer": "Multi-discount (have_offer)", + "catalog.trophies.title": "Trophies", + "catalog.trophies.write.hint": "Write a text for the trophy before purchasing", + "catalog.trophies.inscription": "Trophy Inscription", + "catalog.trophies.inscription.placeholder": "Write the text that will appear on the trophy...", + "catalog.pets.show.colors": "Show colors", + "catalog.pets.choose.color": "Choose color", + "catalog.pets.choose.breed": "Choose breed", + "catalog.pets.back.breeds": "? Breeds", + "catalog.prefix.text": "Text", + "catalog.prefix.text.placeholder": "Enter text...", + "catalog.prefix.icon": "Icon", + "catalog.prefix.icon.remove": "Remove icon", + "catalog.prefix.effect": "Effect", + "catalog.prefix.color": "Color", + "catalog.prefix.color.single": "?? Single", + "catalog.prefix.color.per.letter": "?? Per Letter", + "catalog.prefix.color.hint": "Select a letter, then choose the color. Auto-advances.", + "catalog.prefix.color.apply.all.title": "Apply current color to all letters", + "catalog.prefix.color.apply.all": "Apply to all", + "catalog.prefix.color.selected": "Selected letter:", + "catalog.prefix.price": "Price:", + "catalog.prefix.price.amount": "5 Credits", + "catalog.prefix.purchased": "? Purchased!", + "catalog.prefix.purchase": "Purchase", + "modtools.userinfo.title": "User Info: %username%", + "modtools.userinfo.userName": "Name", + "modtools.userinfo.cfhCount": "CFHs", + "modtools.userinfo.abusiveCfhCount": "Abusive CFHs", + "modtools.userinfo.cautionCount": "Cautions", + "modtools.userinfo.banCount": "Bans", + "modtools.userinfo.lastSanctionTime": "Last Sanction", + "modtools.userinfo.tradingLockCount": "Trade Locks", + "modtools.userinfo.tradingExpiryDate": "Lock Expires", + "modtools.userinfo.minutesSinceLastLogin": "Last Login", + "modtools.userinfo.lastPurchaseDate": "Last Purchase", + "modtools.userinfo.primaryEmailAddress": "Email", + "modtools.userinfo.identityRelatedBanCount": "Banned Accs", + "modtools.userinfo.registrationAgeInMinutes": "Registered", + "modtools.userinfo.userClassification": "Rank", + "modtools.window.title": "Mod Tools", + "modtools.window.tools.room": "Room Tool", + "modtools.window.tools.chatlog": "Chatlog Tool", + "modtools.window.tools.report": "Report Tool", + "modtools.window.select.user": "Select a user", + "modtools.window.no.room": "Enter a room first", + "modtools.window.user.in_room": "Still in this room", + "modtools.window.user.left_room": "No longer in this room", + "modtools.window.user.clear": "Clear selection", + "modtools.window.tickets.open": "%count% open ticket", + "modtools.window.tickets.open.many": "%count% open tickets", + "modtools.window.section.room": "Room", + "modtools.window.section.user": "User", + "modtools.window.section.reports": "Reports", + "modtools.window.user.open_info": "Open Info", + "modtools.userinfo.refresh": "Refresh user info", + "modtools.userinfo.presence.in_room": "In room", + "modtools.userinfo.presence.in_room.title": "In the room you are observing", + "modtools.userinfo.presence.online": "Online", + "modtools.userinfo.presence.online.title": "Online on the hotel", + "modtools.userinfo.presence.offline": "Offline", + "modtools.userinfo.presence.offline.title": "Offline at panel open", + "modtools.userinfo.section.account": "Account", + "modtools.userinfo.section.activity": "Activity", + "modtools.userinfo.section.sanctions": "Sanctions", + "modtools.userinfo.section.trading": "Trading", + "modtools.userinfo.button.room.chat": "Room Chat", + "modtools.userinfo.button.send.message": "Send Message", + "modtools.userinfo.button.room.visits": "Room Visits", + "modtools.userinfo.button.mod.action": "Mod Action", + "modtools.userinfo.stat.cfh": "CFH", + "modtools.userinfo.stat.cautions": "Cautions", + "modtools.userinfo.stat.bans": "Bans", + "modtools.userinfo.stat.trade.locks": "Trade locks", + "modtools.roominfo.title": "Room Info", + "modtools.roominfo.refresh": "Refresh room info", + "modtools.roominfo.loading": "Loading…", + "modtools.roominfo.owner.here": "Owner here", + "modtools.roominfo.owner.away": "Owner away", + "modtools.roominfo.owner.title.here": "The room owner is currently inside", + "modtools.roominfo.owner.title.away": "The room owner is NOT inside", + "modtools.roominfo.stat.users": "Users", + "modtools.roominfo.stat.owner": "Owner", + "modtools.roominfo.owner.open": "Open %username%'s info", + "modtools.roominfo.button.visit": "Visit Room", + "modtools.roominfo.button.chatlog": "Chatlog", + "modtools.roominfo.moderate.title": "Moderate room", + "modtools.roominfo.moderate.kick": "Kick everyone out", + "modtools.roominfo.moderate.doorbell": "Enable the doorbell", + "modtools.roominfo.moderate.rename": "Change room name", + "modtools.roominfo.moderate.message.placeholder": "Mandatory message to deliver with the action…", + "modtools.roominfo.moderate.send.caution": "Send Caution", + "modtools.roominfo.moderate.send.alert": "Send Alert", + "modtools.user.message.title": "Send Message", + "modtools.user.message.recipient": "Message to", + "modtools.user.message.label": "Message", + "modtools.user.message.placeholder": "Write something useful — the user will see it as a moderator message.", + "modtools.user.message.empty": "Empty", + "modtools.user.message.chars": "%count% chars", + "modtools.user.message.send": "Send Message", + "modtools.user.modaction.title": "Mod Action: %username%", + "modtools.user.modaction.sanctioning": "Sanctioning", + "modtools.user.modaction.step.topic": "1. CFH Topic", + "modtools.user.modaction.step.topic.placeholder": "Select a topic…", + "modtools.user.modaction.step.sanction": "2. Sanction", + "modtools.user.modaction.step.sanction.placeholder": "Select a sanction…", + "modtools.user.modaction.step.message": "3. Custom message", + "modtools.user.modaction.step.message.optional": "(optional — overrides default)", + "modtools.user.modaction.message.placeholder": "Leave empty to use the default topic message", + "modtools.user.modaction.preview": "Preview", + "modtools.user.modaction.button.default": "Default Sanction", + "modtools.user.modaction.button.apply": "Apply Sanction", + "modtools.user.modaction.error.no.topic": "You must select a CFH topic", + "modtools.user.modaction.error.no.action": "You must select a CFH topic and Sanction", + "modtools.user.modaction.error.no.permission": "You do not have permission to do this", + "modtools.user.modaction.error.no.message": "Please write a message to user", + "modtools.user.modaction.error.no.permission.alert": "You have insufficient permissions", + "modtools.user.visits.title": "User Visits", + "modtools.user.visits.recent": "Recent visited rooms", + "modtools.user.visits.entries.one": "%count% entry", + "modtools.user.visits.entries.many": "%count% entries", + "modtools.user.visits.empty": "No recent visits", + "modtools.user.visits.time": "Time", + "modtools.user.visits.room": "Room name", + "modtools.user.visits.action": "Action", + "modtools.user.visits.visit": "Visit", + "modtools.user.visits.visit.title": "Visit room", + "modtools.user.chatlog.title": "User Chatlog", + "modtools.user.chatlog.title.with": "User Chatlog: %username%", + "modtools.user.chatlog.loading": "Loading chatlog…", + "modtools.room.chatlog.title": "Room Chatlog", + "modtools.chatlog.column.time": "Time", + "modtools.chatlog.column.user": "User", + "modtools.chatlog.column.message": "Message", + "modtools.chatlog.empty": "No messages", + "modtools.chatlog.visit": "Visit", + "modtools.chatlog.tools": "Tools", + "modtools.tickets.title": "Tickets", + "modtools.tickets.tab.open": "Open", + "modtools.tickets.tab.mine": "Mine", + "modtools.tickets.tab.picked": "All picked", + "modtools.tickets.column.type": "Type", + "modtools.tickets.column.reported": "Reported", + "modtools.tickets.column.opened": "Opened", + "modtools.tickets.column.picker": "Picker", + "modtools.tickets.empty.open": "No open issues", + "modtools.tickets.empty.mine": "No issues picked by you", + "modtools.tickets.empty.picked": "No picked issues", + "modtools.tickets.action.pick": "Pick", + "modtools.tickets.action.handle": "Handle", + "modtools.tickets.action.release": "Release", + "modtools.tickets.issue.title": "Resolving issue #%issueId%", + "modtools.tickets.issue.label": "Issue #%issueId%", + "modtools.tickets.issue.details": "Details", + "modtools.tickets.issue.field.source": "Source", + "modtools.tickets.issue.field.category": "Category", + "modtools.tickets.issue.field.description": "Description", + "modtools.tickets.issue.field.caller": "Caller", + "modtools.tickets.issue.field.reported": "Reported", + "modtools.tickets.issue.chatlog.view": "View chatlog", + "modtools.tickets.issue.chatlog.close": "Close chatlog", + "modtools.tickets.issue.resolve.heading": "Resolve as", + "modtools.tickets.issue.resolve.resolved": "Resolved", + "modtools.tickets.issue.resolve.useless": "Useless", + "modtools.tickets.issue.resolve.abusive": "Abusive", + "modtools.tickets.issue.release": "Release back to queue", + "modtools.tickets.cfh.chatlog.title": "Issue #%issueId% Chatlog", + "groupforum.list.tab.most_active": "Most active threads", + "groupforum.list.tab.my_forums": "My group forums", + "groupforum.list.no_forums": "There are no forums", + "groupforum.view.threads": "Number of threads", + "groupforum.thread.pin": "Pin thread", + "groupforum.thread.unpin": "Unpin thread", + "groupforum.thread.lock": "Lock thread", + "groupforum.thread.unlock": "Unlock thread", + "groupforum.thread.hide": "Hide thread", + "groupforum.thread.restore": "Restore thread", + "groupforum.thread.delete": "Delete thread + posts", + "groupforum.message.hide": "Hide message", + "group.forum.enable.caption": "Enable / Disable group forum", + "group.forum.enable.help": "If you disable the group forum, all posts will also be deleted!", + "groupforum.view.no_threads": "There are currently no active threads", + "loading.task.session": "Verifying session...", + "loading.task.renderer": "Initializing renderer...", + "loading.task.assets": "loading game assets...", + "loading.task.localization": "loading translations...", + "loading.task.avatar": "loading wardrobe...", + "loading.task.sounds": "loading sounds...", + "loading.task.startsession": "Starting session...", + "loading.task.userdata": "loading user data...", + "loading.task.rooms": "loading rooms...", + "loading.task.engine": "loading graphics engine...", } diff --git a/public/configuration/renderer-config.example b/public/configuration/renderer-config.example index 4d8fd1f..5745409 100644 --- a/public/configuration/renderer-config.example +++ b/public/configuration/renderer-config.example @@ -48,26 +48,9 @@ "timezone.settings": "Europe/Amsterdam", "youtube.publish.disabled": false, "user.badges.group.slot.enabled": true, - - "_comment_loading_screen": "Schermata di caricamento — sostituibili per traduzione/branding. Logo, sfondo e colore della barra usano lo standard CSS (URL, gradient, colore esadecimale). Le label compaiono sotto la barra di progresso man mano che ogni fase del boot completa.", "loading.logo.url": "", "loading.background": "", "loading.progress.color": "linear-gradient(90deg,#4f8cff,#2563eb)", - "loading.task.boot": "Avvio in corso...", - "loading.task.session": "Verifica sessione", - "loading.task.renderer": "Inizializzazione renderer", - "loading.task.warmup": "Caricamento contenuti...", - "loading.task.assets": "Sto caricando gli asset di gioco", - "loading.task.localization": "Sto caricando le traduzioni", - "loading.task.avatar": "Sto caricando il guardaroba", - "loading.task.sounds": "Sto caricando i suoni", - "loading.task.startsession": "Avvio sessione", - "loading.task.userdata": "Caricamento dati utente", - "loading.task.rooms": "Caricamento stanze", - "loading.task.engine": "Caricamento engine grafico", - "loading.task.connect": "Connessione al server", - "loading.task.ready": "Pronto!", - "login.screen.enabled": true, "login.endpoint": "${api.url}/api/auth/login", "login.register.endpoint": "${api.url}/api/auth/register", diff --git a/src/App.tsx b/src/App.tsx index 97fd260..55fce02 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -74,21 +74,30 @@ export const App: FC<{}> = props => const [ prepareTrigger, setPrepareTrigger ] = useState(0); const [ loadingProgress, setLoadingProgress ] = useState(0); const [ loadingTask, setLoadingTask ] = useState(''); - // Look up a loader-stage label from renderer-config so the strings the user - // sees during the boot ("Sto caricando il guardaroba", "Connessione…") can - // be translated by editing the JSON/JSON5 config — fallback keeps the - // Italian baseline shipped with the client. const taskLabel = useCallback((key: string, fallback: string): string => { try { - const raw = GetConfiguration().getValue(key, ''); - return (typeof raw === 'string' && raw.length) ? raw : fallback; + const locManager = GetLocalizationManager(); + if(locManager && typeof locManager.getValue === 'function') + { + const fromLoc = locManager.getValue(key, false); + + if(typeof fromLoc === 'string' && fromLoc.length && fromLoc !== key) return fromLoc; + } } catch + { } + + try { - return fallback; + const fromConfig = GetConfiguration().getValue(key, ''); + if(typeof fromConfig === 'string' && fromConfig.length) return fromConfig; } + catch + { } + + return fallback; }, []); const bumpProgress = useCallback((value: number, task?: string) => { @@ -111,9 +120,6 @@ export const App: FC<{}> = props => ClearRememberLogin(); try { delete (window as any).NitroConfig?.['sso.ticket']; } catch {} try { GetConfiguration().setValue('sso.ticket', ''); } catch {} - // Drop `?sso=` from the URL too — otherwise the next reload re-applies - // the same already-consumed ticket via bootstrap.ts and we loop right - // back into "Session expired" without ever showing the login form. try { const url = new URL(window.location.href); @@ -142,11 +148,6 @@ export const App: FC<{}> = props => const fallbackToLogin = useCallback(() => { - // When login.screen.enabled is false this hotel uses SSO-only auth - // (CMS issues the ticket and redirects here). Surfacing a login form - // on init failure would just dump an empty/broken placeholder, since - // the form's backgrounds and Turnstile aren't even configured. Send - // the user back to the hotel home page instead. const rawLoginEnabled = GetConfiguration().getValue('login.screen.enabled', false); const loginScreenEnabled = rawLoginEnabled === true || rawLoginEnabled === 'true' || rawLoginEnabled === 1; @@ -156,14 +157,7 @@ export const App: FC<{}> = props => showSessionExpired(); return; } - - // Using console.warn (not NitroLogger.log) on purpose: NitroLogger - // is gated on LOG_DEBUG, which only flips to true once startWarmup's - // GetConfiguration().init() completes. Auth-failure paths fire before - // that, so NitroLogger swallows their messages silently. console.warn('[App] fallbackToLogin — surfacing login form, credentials cleared'); - // Wipe whatever credential the server just rejected so the form is - // pristine and the next attempt isn't sabotaged by the same stale data. clearStoredCredentials(); setHomeUrl(''); setErrorMessage(''); @@ -200,8 +194,6 @@ export const App: FC<{}> = props => if(!remembered?.token?.length) { - // No remember token means we'd be reusing a one-shot ssoTicket that - // the server already consumed. Force the login screen instead. if(remembered) ClearRememberLogin(); console.warn('[App] tryRememberLogin → no token, returning empty'); return ''; @@ -250,9 +242,6 @@ export const App: FC<{}> = props => console.warn('[App] tryRememberLogin → fetch threw', error); } - // Any failure (rejected token, bad payload, network error) — drop the - // stored credentials. Never fall back to the cached ssoTicket: it's - // one-shot and reusing it leads straight to "Session expired". ClearRememberLogin(); console.warn('[App] tryRememberLogin → cleared remember, returning empty'); @@ -303,35 +292,12 @@ export const App: FC<{}> = props => } }, []); - // Mirror isReady into a ref so the socket handlers below can read the - // freshest value without needing to re-subscribe on every state change. useEffect(() => { isReadyRef.current = isReady; }, [ isReady ]); - - // Track whether a reconnect cycle is active. The renderer dispatches - // SOCKET_RECONNECTING when it starts retrying after an abnormal close - // (code != 1000/1001). On exhausted retries it fires SOCKET_RECONNECT_FAILED - // *and* a final SOCKET_CLOSED — we keep the flag set through that pair - // so ReconnectView's own overlay owns the UX and we don't double-render. useNitroEvent(NitroEventType.SOCKET_RECONNECTING, () => { reconnectInProgressRef.current = true; }); useNitroEvent(NitroEventType.SOCKET_REAUTHENTICATED, () => { reconnectInProgressRef.current = false; }); useNitroEvent(NitroEventType.SOCKET_CLOSED, () => { - // Three distinct close scenarios converge here: - // - // 1. !isReady — initial handshake just failed (server replied - // with "Bye" / code 1000 to a bad SSO ticket). The user never - // had a session. Surface the login form instead of the - // misleading "Session expired" diagnostic. - // - // 2. isReady && reconnect in progress — ReconnectView already - // owns the UX (its overlay shows attempts and the "Session - // expired" message on RECONNECT_FAILED). Stay out of its way. - // - // 3. isReady && no reconnect — instant server kick mid-game - // (code 1000 from the server side). No reconnect path will - // run. Show the legacy session-expired diagnostic so the - // user knows to reload. console.warn('[App] SOCKET_CLOSED fired', { isReady: isReadyRef.current, reconnectInProgress: reconnectInProgressRef.current @@ -390,7 +356,7 @@ export const App: FC<{}> = props => warmupPromiseRef.current = (async () => { await GetConfiguration().init(); - bumpProgress(25, taskLabel('loading.task.warmup', 'Caricamento contenuti...')); + bumpProgress(25, taskLabel('loader.waiting', 'Loading content...')); GetTicker().maxFPS = GetConfiguration().getValue('system.fps.max', 24); NitroLogger.LOG_DEBUG = GetConfiguration().getValue('system.log.debug', true); @@ -427,15 +393,11 @@ export const App: FC<{}> = props => loginImageUrls.forEach(preloadImage); gamedataUrls.forEach(url => preloadUrl(url)); - // Wire each warmup task to a progress bump so the bar reflects - // real subsystem-init completion, not a fake timer. Range 25→70. - // Each task carries a friendly label so the user sees what is - // currently being prepared instead of raw file names. const warmupTasks: { promise: Promise; label: string }[] = [ - { promise: GetAssetManager().downloadAssets(assetUrls), label: taskLabel('loading.task.assets', 'Sto caricando gli asset di gioco') }, - { promise: GetLocalizationManager().init(), label: taskLabel('loading.task.localization', 'Sto caricando le traduzioni') }, - { promise: GetAvatarRenderManager().init(), label: taskLabel('loading.task.avatar', 'Sto caricando il guardaroba') }, - { promise: GetSoundManager().init(), label: taskLabel('loading.task.sounds', 'Sto caricando i suoni') } + { promise: GetAssetManager().downloadAssets(assetUrls), label: taskLabel('loading.task.assets', 'Loading game assets...') }, + { promise: GetLocalizationManager().init(), label: taskLabel('loading.task.localization', 'Loading translations...') }, + { promise: GetAvatarRenderManager().init(), label: taskLabel('loading.task.avatar', 'Loading wardrobe...') }, + { promise: GetSoundManager().init(), label: taskLabel('loading.task.sounds', 'Loading sounds...') } ]; let warmupDone = 0; const warmupStart = 25; @@ -477,11 +439,6 @@ export const App: FC<{}> = props => { const prepare = async (width: number, height: number) => { - // Don't dump the actual SSO ticket — it's a one-shot bearer - // credential that grants access to the user's session, so - // logging it in console.warn would leak it via copied logs - // / screen shares / browser extension hooks. Boolean flag is - // enough for the diagnostic. console.warn('[App] prepare() start', { hasNitroConfig: !!window.NitroConfig, ssoTicketInConfig: !!window.NitroConfig?.['sso.ticket'], @@ -489,7 +446,7 @@ export const App: FC<{}> = props => hasUrlSso: !!new URLSearchParams(window.location.search).get('sso') }); - const bootLabel = taskLabel('loading.task.boot', 'Avvio in corso...'); + const bootLabel = taskLabel('loader', 'Booting...'); setLoadingProgress(0); setLoadingTask(bootLabel); bumpProgress(5, bootLabel); @@ -501,11 +458,6 @@ export const App: FC<{}> = props => let ssoTicket = window.NitroConfig['sso.ticket']; if(ssoTicket) GetConfiguration().setValue('sso.ticket', ssoTicket); - // Cattura il remember-token passato via URL (?token=&token_exp=) - // dal CMS Inertia /client e salvalo in localStorage. Serve a - // tryRememberLogin() in reconnect: chiama POST /api/auth/remember - // col token UUID, riceve un nuovo SSO ticket fresco invece di - // riusare quello cleared da Arcturus dopo il primo consume. try { const urlParams = new URLSearchParams(window.location.search); @@ -525,12 +477,10 @@ export const App: FC<{}> = props => console.warn('[App] failed to persist remember token from URL', e); } - bumpProgress(10, taskLabel('loading.task.session', 'Verifica sessione')); + bumpProgress(10, taskLabel('loading.task.session', 'Verifying session...')); if(!ssoTicket || ssoTicket === '') { - // Configuration is loaded lazily — fetch it up-front so the login - // screen toggle and Turnstile keys are available before we decide. let configInitError: unknown = null; try { @@ -592,23 +542,23 @@ export const App: FC<{}> = props => } const renderer = await startRenderer(width, height); - bumpProgress(20, taskLabel('loading.task.renderer', 'Inizializzazione renderer')); + bumpProgress(20, taskLabel('loading.task.renderer', 'Initializing renderer...')); await startWarmup(width, height); - bumpProgress(70, taskLabel('loading.task.startsession', 'Avvio sessione')); + bumpProgress(70, taskLabel('loading.task.startsession', 'Starting session...')); if(!gameInitPromiseRef.current) { gameInitPromiseRef.current = (async () => { await GetSessionDataManager().init(); - bumpProgress(78, taskLabel('loading.task.userdata', 'Caricamento dati utente')); + bumpProgress(78, taskLabel('loading.task.userdata', 'Loading user data...')); await GetRoomSessionManager().init(); - bumpProgress(85, taskLabel('loading.task.rooms', 'Caricamento stanze')); + bumpProgress(85, taskLabel('loading.task.rooms', 'Loading rooms...')); await GetRoomEngine().init(); - bumpProgress(92, taskLabel('loading.task.engine', 'Caricamento engine grafico')); + bumpProgress(92, taskLabel('loading.task.engine', 'Loading graphics engine...')); await GetCommunication().init(); - bumpProgress(98, taskLabel('loading.task.connect', 'Connessione al server')); + bumpProgress(98, taskLabel('generic.reconnecting', 'Connecting to server...')); })(); } @@ -637,7 +587,7 @@ export const App: FC<{}> = props => GetTicker().add(ticker => GetTexturePool().run()); } - bumpProgress(100, taskLabel('loading.task.ready', 'Pronto!')); + bumpProgress(100, taskLabel('onboarding.button.ready', 'Ready!')); setIsReady(true); setShowLogin(false); setIsEnteringHotel(false); @@ -645,23 +595,10 @@ export const App: FC<{}> = props => catch(err) { NitroLogger.error('[App] Initialization failed — falling back to login', err); - // Anything thrown out of the post-auth chain (renderer init, - // asset download, GetCommunication().init()) is an init/connect - // failure, not session expiration. The credential we used is - // suspect — drop it and present the login form so the user - // can retry instead of getting stuck on a stale "Session expired". onInitFailure(); } }; - // React Strict Mode in dev runs every effect twice (mount → cleanup → mount). - // `prepare()` is full of one-shot side effects (renderer init, websocket - // connect, NitroConfig mutation) — calling it twice with the same trigger - // value causes the second pass to race with the first and clobber state - // (e.g. the second pass falls through to onSessionExpired while the first - // had just set showLogin=true). Guard by trigger value: skip duplicate - // runs at the same trigger, but still re-run when handleAuthenticated - // bumps prepareTrigger after a successful login. if(lastPrepareTriggerRef.current === prepareTrigger) return; lastPrepareTriggerRef.current = prepareTrigger; @@ -682,12 +619,6 @@ export const App: FC<{}> = props => 0 } message={ errorMessage } homeUrl={ homeUrl } progress={ loadingProgress } currentTask={ loadingTask } /> } { !isReady && showLogin && } { isReady && } - { /* Reconnect overlay must NOT render before we've actually entered - the hotel — otherwise the renderer's auto-retry on an initial - handshake failure (e.g. emulator unreachable) would cover the - login form with "Reconnecting..." → "Session expired" and the - user wouldn't be able to interact with the form we just put up - via fallbackToLogin. */ } { isReady && }