diff --git a/content/docs/en/guides/plugin/setting-up-env.mdx b/content/docs/en/guides/plugin/setting-up-env.mdx
index 3c002a45..3b981b8b 100644
--- a/content/docs/en/guides/plugin/setting-up-env.mdx
+++ b/content/docs/en/guides/plugin/setting-up-env.mdx
@@ -61,7 +61,24 @@ You can now try the command `mvn -version` to verify Maven is installed correctl
## Setting Up Your Workspace
-### 1. Add HytaleServer.jar Dependency
+### 1. Clone the Plugin Template
+
+Instead of creating an empty directory, we'll use the official Hytale plugin template:
+
+```bash
+# Clone the template repository
+git clone https://github.com/HytaleModding/plugin-template.git MyFirstMod
+cd MyFirstMod
+```
+
+### 2. Open in IntelliJ IDEA
+
+1. Open IntelliJ IDEA
+2. Click "Open" and navigate to your `MyFirstMod` directory
+3. IntelliJ will automatically detect it as a Maven project
+4. Wait for the project to index and dependencies to download
+
+### 3. Add HytaleServer.jar Dependency
Before you can start modding, you need to add the HytaleServer.jar file as a dependency:
@@ -73,21 +90,19 @@ Before you can start modding, you need to add the HytaleServer.jar file as a dep
- Click on the **+ icon**
- Select the **HytaleServer.jar** you downloaded
-### 2. Clone the Plugin Template
+### 4. Add the HytaleServer to your Artifacts
-Instead of creating an empty directory, we'll use the official Hytale plugin template:
+
+This is a temporary workaround until Hypixel provides an official Maven repository for HytaleServer.jar
+
+
+Run this command in your console
```bash
-# Clone the template repository
-git clone https://github.com/HytaleModding/plugin-template.git MyFirstMod
-cd MyFirstMod
+mvn install:install-file -Dfile=[ROUTE TO HytaleServer.jar] -DgroupId=com.hypixel.hytale -DartifactId=HytaleServer-parent -Dversion=1.0-SNAPSHOT -Dpackaging=jar
```
-### 3. Open in IntelliJ IDEA
-1. Open IntelliJ IDEA
-2. Click "Open" and navigate to your `MyFirstMod` directory
-3. IntelliJ will automatically detect it as a Maven project
-4. Wait for the project to index and dependencies to download
+Replace `[ROUTE TO HytaleServer.jar]` with the actual path to the `HytaleServer.jar` file on your system.
## Next Steps
@@ -97,8 +112,8 @@ Now that you have your development environment set up with the plugin template:
2. **Explore the code structure** - Familiarize yourself with the template's organization
3. **Start modding** - Begin writing your first Hytale plugin!
-The plugin template includes:
-- Pre-configured Gradle build system
-- Example plugin structure
-- Essential dependencies
-- Development tools configuration
+Learn how to:
+ - [Create Commands](./creating-commands)
+ - [Create Events](./creating-events)
+
+and more! Explore the documentation to learn about all available features and best practices.
\ No newline at end of file
diff --git a/content/docs/en/guides/plugin/text-hologram.mdx b/content/docs/en/guides/plugin/text-hologram.mdx
new file mode 100644
index 00000000..3ff613fd
--- /dev/null
+++ b/content/docs/en/guides/plugin/text-hologram.mdx
@@ -0,0 +1,237 @@
+---
+title: "Title Holograms"
+description: "Learn how to create title holograms."
+authors:
+ - name: "Bird"
+ link: "https://discord.com/users/495709580068651009"
+---
+
+# Overview
+
+In this guide, you'll learn how to create a **title hologram** using an entity nameplate.
+
+## Steps
+
+1. Open and equip the **Entity Grabber Tool**.
+
+
+
+
+This tool will be used later to **adjust the position of the nameplate**, so it’s useful to keep it equipped throughout the process.
+
+
+---
+
+2. Spawn a **small entity** (for example, rubble or a similar object), then **scale it down** to lowest possible value.
+
+
+
+---
+
+3. Hover over the entity you just spawned and run the following command:
+
+```
+/entity nameplate "Text Here"
+```
+
+
+
+---
+
+Your title hologram should now be visible in the world, floating where the entity is placed.
+
+
+
+---
+
+## Bonus Section - Creating Title Holograms With Code
+
+This section shows how to create the same title hologram using a **server plugin command** instead of in-game tools.
+
+The example below creates a command called `/titlehologram` that spawns an invisible entity with a floating nameplate at the player’s location.
+
+```java
+package org.example.plugin;
+
+import com.hypixel.hytale.component.Holder;
+import com.hypixel.hytale.math.vector.Transform;
+import com.hypixel.hytale.server.core.command.system.CommandContext;
+import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase;
+import com.hypixel.hytale.server.core.entity.UUIDComponent;
+import com.hypixel.hytale.server.core.entity.entities.ProjectileComponent;
+import com.hypixel.hytale.server.core.entity.nameplate.Nameplate;
+import com.hypixel.hytale.server.core.modules.entity.component.Intangible;
+import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
+import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId;
+import com.hypixel.hytale.server.core.universe.PlayerRef;
+import com.hypixel.hytale.server.core.universe.Universe;
+import com.hypixel.hytale.server.core.universe.world.World;
+import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
+
+import javax.annotation.Nonnull;
+import java.util.UUID;
+
+public class TitleHologramCommand extends CommandBase {
+
+ public TitleHologramCommand() {
+ super("TitleHologram", "Create a title hologram.");
+ }
+
+ @Override
+ protected void executeSync(@Nonnull CommandContext ctx) {
+
+ UUID playerUUID = ctx.sender().getUuid();
+ PlayerRef playerRef = Universe.get().getPlayer(playerUUID);
+ World world = Universe.get().getWorld(playerRef.getWorldUuid());
+ Transform playerTransform = playerRef.getTransform();
+
+ world.execute(() -> {
+
+ Holder holder = EntityStore.REGISTRY.newHolder();
+ ProjectileComponent projectileComponent = new ProjectileComponent("Projectile");
+ holder.putComponent(ProjectileComponent.getComponentType(), projectileComponent);
+ holder.putComponent(TransformComponent.getComponentType(), new TransformComponent(playerTransform.getPosition().clone(), playerTransform.getRotation().clone()));
+ holder.ensureComponent(UUIDComponent.getComponentType());
+ holder.ensureComponent(Intangible.getComponentType());
+
+ if (projectileComponent.getProjectile() == null) {
+ projectileComponent.initialize();
+ if (projectileComponent.getProjectile() == null) {
+ return;
+ }
+ }
+ holder.addComponent(NetworkId.getComponentType(), new NetworkId(world.getEntityStore().getStore().getExternalData().takeNextNetworkId()));
+ holder.addComponent(Nameplate.getComponentType(), new Nameplate("Testing Holograms"));
+
+ world.getEntityStore().getStore().addEntity(holder, com.hypixel.hytale.component.AddReason.SPAWN);
+ });
+ }
+}
+```
+
+---
+
+### 1. Getting the Player and World
+
+```java
+UUID playerUUID = ctx.sender().getUuid();
+PlayerRef playerRef = Universe.get().getPlayer(playerUUID);
+World world = Universe.get().getWorld(playerRef.getWorldUuid());
+Transform playerTransform = playerRef.getTransform();
+```
+
+This retrieves:
+
+| Value | Purpose |
+| ----------------- | ------------------------------------- |
+| `playerUUID` | The unique ID of the command sender |
+| `playerRef` | A server-side reference to the player |
+| `world` | The world the player is currently in |
+| `playerTransform` | The player’s position and rotation |
+
+This allows the hologram to be spawned **exactly where the player is standing**.
+
+---
+
+### 2. Spawning on the World Thread
+
+```java
+world.execute(() -> {
+```
+
+All entity operations must run on the world thread.
+Everything inside this block executes safely on the correct game thread.
+
+---
+
+### 3. Creating the Entity
+
+```java
+Holder holder = EntityStore.REGISTRY.newHolder();
+ProjectileComponent projectileComponent = new ProjectileComponent("Projectile");
+```
+
+The projectile is only used as a **valid entity shell**.
+It will never move or behave like a real projectile.
+
+---
+
+### 4. Setting Position & Components
+
+```java
+holder.putComponent(ProjectileComponent.getComponentType(), projectileComponent);
+holder.putComponent(TransformComponent.getComponentType(), new TransformComponent(playerTransform.getPosition().clone(), playerTransform.getRotation().clone()));
+holder.ensureComponent(UUIDComponent.getComponentType());
+holder.ensureComponent(Intangible.getComponentType());
+```
+
+This builds an invisible, stationary entity at the player’s position.
+
+| Component | Purpose |
+| --------------------- | ------------------------------------------------ |
+| `ProjectileComponent` | Provides a valid entity shell |
+| `TransformComponent` | Sets the entity’s position and rotation |
+| `UUIDComponent` | Gives the entity a unique identity |
+| `Intangible` | Makes the entity non-collidable and unselectable |
+
+The hologram will appear exactly at the player’s position and cannot be interacted with physically.
+
+---
+
+### 5. Initializing the Projectile
+
+```java
+if (projectileComponent.getProjectile() == null) {
+ projectileComponent.initialize();
+ if (projectileComponent.getProjectile() == null) {
+ return;
+ }
+}
+```
+
+This ensures the projectile entity is fully created.
+If initialization fails, the hologram will not spawn.
+
+---
+
+### 6. Setting Network & Nameplate Components
+
+```java
+holder.addComponent(
+ NetworkId.getComponentType(),
+ new NetworkId(
+ world.getEntityStore()
+ .getStore()
+ .getExternalData()
+ .takeNextNetworkId()
+ )
+);
+
+holder.addComponent(
+ Nameplate.getComponentType(),
+ new Nameplate("Testing Holograms")
+);
+```
+
+These final components make the hologram visible and give it its text.
+
+| Component | Purpose |
+| --------------------- | ------------------------------- |
+| `NetworkId` | Allows the entity to be synced |
+| `Nameplate` | The actual hologram text |
+
+---
+
+### 7. Spawning the Entity
+
+```java
+world.getEntityStore()
+ .getStore()
+ .addEntity(holder, com.hypixel.hytale.component.AddReason.SPAWN);
+```
+
+This inserts the hologram entity into the world, making it active and visible.
+
+---
+
+
\ No newline at end of file
diff --git a/content/docs/en/guides/plugin/ui.mdx b/content/docs/en/guides/plugin/ui.mdx
new file mode 100644
index 00000000..565078f7
--- /dev/null
+++ b/content/docs/en/guides/plugin/ui.mdx
@@ -0,0 +1,229 @@
+---
+title: Custom UI
+description: Learn how to show custom UI to the player
+authors:
+ - name: "underscore95"
+ url: "https://github.com/underscore95"
+---
+
+## Important Information
+ - All .ui files must be included in your plugin resources folder, specifically they should be in resources/Common/UI/Custom
+ - Ensure your manifest.json contains `"IncludesAssetPack": true`
+ - The Hytale client has a "Diagnostic Mode" setting under General, this will give more detailed error messages.
+
+## Useful Resources
+
+Video Tutorials:
+ - CustomUIHud Tutorial: https://www.youtube.com/watch?v=u4pGShklEKs
+ - InteractiveCustomUIPage Tutorial: https://www.youtube.com/watch?v=NOFWQt9wEbk
+
+Examples:
+ - Simple InteractiveCustomUIPage Example: https://github.com/underscore95/Hytale-Sandbox-Plugin/tree/ui-pages
+ - Complicated InteractiveCustomUIPage Example: https://github.com/Buuz135/AdminUI/tree/main
+
+## .ui files
+
+Hytale currently uses .ui files to render UI, these are currently deprecated and Hytale is planning to move to NoesisGUI.
+The transition has not happened yet and .ui files are the only way to render UI.
+
+UI is defined using a .ui file which functions similarly to HTML and CSS.
+
+### UI Elements
+
+A .ui file contains a tree of UI elements, declaring a UI element uses the following syntax:
+```
+Group {
+ TextField #MyInput {
+ Style: $Common.@DefaultInputFieldStyle;
+ Background: $Common.@InputBoxBackground;
+ Anchor: (Top: 10, Width: 200, Height: 50);
+ }
+}
+```
+
+`TextField` and `Group` are the types of UI elements.
+ - A text field is an input where the user can enter text.
+ - A group is an empty UI element, similar to a div in HTML.
+
+`#MyInput` is the ID of the TextField UI element, this is required for Java code to access the element later.
+
+### Variables
+
+You can define variables using the following syntax:
+```
+@MyTex = PatchStyle(TexturePath: "MyBackground.png");
+```
+
+### Textures
+
+Textures can be loaded using
+```
+PatchStyle(TexturePath: "MyBackground.png");
+```
+
+The path is relative to the .ui file.
+Your textures must be included in your resource folder.
+
+If applying a texture as the background of a UI element, you do not need to match size, the texture will automatically be stretched.
+
+### Including other .ui files
+
+You can include other .ui files using `$Common = "Common.ui";`, this allows you to access variables like so: `Style: $Common.@DefaultInputFieldStyle;`
+
+## HUDs
+
+A HUD is an element of UI that stays on the screen all the time, for example the players hot bar or health bar.
+It cannot be interacted with.
+
+### CustomUIHud
+
+Create a Java class that extends `CustomUIHud` and overrides the `build` function.
+
+The `build` function has a `UICommandBuilder` parameter, this allows you to add .ui files to the HUD.
+```
+uiCommandBuilder.append("MyUI.ui"); // This file must be located at resources/Common/UI/Custom/MyUI.ui
+```
+
+### Showing & Hiding UI
+
+You can get the HudManager using `Player#getHudManager`.
+ - Use `HudManager#setCustomHud` to show UI.
+ - Use `HudManager#hideHudComponent` to hide UI.
+ - https://www.curseforge.com/hytale/mods/multiplehud allows showing multiple custom HUDs
+
+## UI Pages
+
+Pages are another type of UI, these prevent the player from interacting with the game and unlock the players mouse.
+Some examples of pages include: Crafting menu, pause menu
+
+### CustomUIPage
+
+If you do not need user input, you can make a class extending CustomUIPage, this is very similar to CustomUIHud.
+
+### InteractiveCustomUIPage
+
+You must extend InteractiveCustomUIPage to receive events and user input.
+
+The following UI is used for the below code:
+```
+$Common = "Common.ui";
+
+@MyTex = PatchStyle(TexturePath: "MyBackground.png");
+
+Group {
+ LayoutMode: Center;
+
+ Group #MyPanel {
+ Background: @MyTex;
+ Anchor: (Width: 800, Height: 1000);
+ LayoutMode: Top;
+
+ Label #MyLabel {
+ Style: (FontSize: 32, Alignment: Center);
+ Anchor: (Top: 50);
+ Text: "MyText";
+ Padding: (Full: 10);
+ }
+
+ TextField #MyInput {
+ Style: $Common.@DefaultInputFieldStyle;
+ Background: $Common.@InputBoxBackground;
+ Anchor: (Top: 10, Width: 200, Height: 50);
+ Padding: (Full: 10);
+ }
+ }
+}
+```
+
+InteractiveCustomUIPage takes a generic argument, this is a class containing any UI data you want the client to send to the server.
+
+```
+public static class Data {
+ public static final BuilderCodec CODEC = BuilderCodec.builder(Data.class, Data::new)
+ .append(new KeyedCodec<>("@MyInput", Codec.STRING), (data, value) -> data.value = value, data -> data.value).add()
+ .build();
+
+ private String value; // Value of the TextField input
+}
+```
+
+InteractiveCustomUIPage constructor takes a PlayerRef, CustomPageLifetime, and `BuilderCodec`.
+ - PlayerRef is the player you want to show UI to.
+ - CustomPageLifetime is an enum controlling if the player can close the UI or not
+
+`BuilderCodec` tells the server how to create the Data object from the JSON the client sends.
+Example value in the Data class above.
+
+This passes the Class and a lambda function that constructs the Data object, the server uses this to deserialize the JSON.:
+```
+BuilderCodec.builder(Data.class, Data::new)
+```
+
+This provides a setter and getter lambda function to the field that should contain whatever @MyInput is set to:
+```
+.append(new KeyedCodec<>("@MyInput", Codec.STRING), (data, value) -> data.value = value, data -> data.value).add()
+```
+
+The following build method is used:
+```
+@Override
+public void build(@Nonnull Ref ref, @Nonnull UICommandBuilder uiCommandBuilder, @Nonnull UIEventBuilder uiEventBuilder, @Nonnull Store store) {
+ uiCommandBuilder.append("MyUI.ui");
+ uiEventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#MyInput", EventData.of("@MyInput", "#MyInput.Value"), false);
+}
+```
+
+This says we are listening for whenever the value of #MyInput changes:
+```
+CustomUIEventBindingType.ValueChanged, "#MyInput"
+```
+
+You may be wondering where `@MyInput` in the codec previously came from, it is defined here.
+This creates the codec value `@MyInput` and maps it to `#MyInput.Value`:
+```
+EventData.of("@MyInput", "#MyInput.Value")
+```
+
+The following `handleDataEvent` will be overridden, take care to override the one that has a `Data` parameter rather than `String raw` (this is the raw json).
+```
+@Override
+public void handleDataEvent(@Nonnull Ref ref, @Nonnull Store store, Data data) {
+ super.handleDataEvent(ref, store, data);
+
+ System.out.println("EVENT: " + data.value);
+
+ sendUpdate();
+}
+```
+
+You must always either switch to a new UI or call `sendUpdate();` otherwise the Hytale client will display "Loading..." forever and prevent the user from interacting with your UI.
+This will be familiar if you have ever made commands in a Discord bot, where you have to acknowledge interactions.
+
+### Opening UI pages
+
+```
+player.getPageManager().openCustomPage(ref, store, MyUI(playerRef));
+```
+
+## Dynamically Updating UI
+
+In many cases you want to update your UI at run time.
+
+Here is an example of updating a Label's (with id MyLabel) text content, this method should be added to your UI Java class:
+
+```
+public void updateText(String newText) {
+ UICommandBuilder uiCommandBuilder = new UICommandBuilder();
+ uiCommandBuilder.set("#MyLabel.TextSpans", Message.raw(newText));
+ update(false, uiCommandBuilder); // false = don't clear existing UI
+}
+
+## Common Issues
+
+### Failed to apply custom ui hud commands
+
+This means your .ui file has something wrong with it.
+
+### Could not find document XXXXX for Custom UI Append command
+
+This means your .ui file wasn't in the location your Java code said it was, double check that your path is correct.
\ No newline at end of file
diff --git a/messages/en.json b/messages/en.json
index 4ac96eaa..245babd1 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -70,6 +70,19 @@
},
"community_projects": {
"title": "Community Projects"
- }
+ },
+ "keywords": [
+ "hytale modding",
+ "hytale",
+ "hytale plugins",
+ "hytale mods",
+ "how to mod hytale",
+ "modding tutorial",
+ "modding guides",
+ "hytale modding guides",
+ "hytale modding tutorial",
+ "how to start modding Hytale",
+ "how to make a mod"
+ ]
}
}
diff --git a/public/assets/guides/tile-hologram-1.png b/public/assets/guides/tile-hologram-1.png
new file mode 100644
index 00000000..1502e4fc
Binary files /dev/null and b/public/assets/guides/tile-hologram-1.png differ
diff --git a/public/assets/guides/tile-hologram-2.png b/public/assets/guides/tile-hologram-2.png
new file mode 100644
index 00000000..836f48dd
Binary files /dev/null and b/public/assets/guides/tile-hologram-2.png differ
diff --git a/public/assets/guides/tile-hologram-3.png b/public/assets/guides/tile-hologram-3.png
new file mode 100644
index 00000000..002e354a
Binary files /dev/null and b/public/assets/guides/tile-hologram-3.png differ
diff --git a/public/assets/guides/tile-hologram-4.png b/public/assets/guides/tile-hologram-4.png
new file mode 100644
index 00000000..ec8992e5
Binary files /dev/null and b/public/assets/guides/tile-hologram-4.png differ
diff --git a/public/assets/guides/tile-hologram-5.png b/public/assets/guides/tile-hologram-5.png
new file mode 100644
index 00000000..28ada167
Binary files /dev/null and b/public/assets/guides/tile-hologram-5.png differ
diff --git a/public/og.png b/public/og.png
deleted file mode 100644
index a99d1168..00000000
Binary files a/public/og.png and /dev/null differ
diff --git a/src/app/[lang]/(home)/opengraph-image.tsx b/src/app/[lang]/(home)/opengraph-image.tsx
new file mode 100644
index 00000000..551d8efc
--- /dev/null
+++ b/src/app/[lang]/(home)/opengraph-image.tsx
@@ -0,0 +1,29 @@
+import { ImageResponse } from "next/og";
+
+// Image metadata
+export const size = {
+ width: 1200,
+ height: 630,
+};
+
+export const contentType = "image/png";
+
+// Image generation
+export default async function Image({ params }: { params: { slug: string } }) {
+ return new ImageResponse(
+ // ImageResponse JSX element
+
+ helloworld
+
,
+ );
+}
diff --git a/src/app/[lang]/docs/[[...slug]]/page.tsx b/src/app/[lang]/docs/[[...slug]]/page.tsx
index d354c329..692d4577 100644
--- a/src/app/[lang]/docs/[[...slug]]/page.tsx
+++ b/src/app/[lang]/docs/[[...slug]]/page.tsx
@@ -8,7 +8,7 @@ import {
} from "fumadocs-ui/page";
import { notFound } from "next/navigation";
import { getMDXComponents } from "@/mdx-components";
-import type { Metadata } from "next";
+import type { Metadata, ResolvingMetadata } from "next";
import { createRelativeLink } from "fumadocs-ui/mdx";
import { branch } from "@/git-info.json";
import { ViewTransition } from "react";
@@ -94,6 +94,7 @@ export async function generateStaticParams() {
export async function generateMetadata(
props: PageProps<"/[lang]/docs/[[...slug]]">,
+ parent: ResolvingMetadata,
): Promise {
const params = await props.params;
const page = source.getPage(params.slug, params.lang);
@@ -102,31 +103,19 @@ export async function generateMetadata(
const slug = params.slug || [];
const imageUrl = `/api/og/docs/${params.lang}${slug.length > 0 ? "/" + slug.join("/") : ""}`;
const pageKeywords = (page.data as any).keywords || [];
- const globalKeywords = [
- "hytale modding",
- "hytale",
- "hytale plugins",
- "hytale mods",
- "how to mod hytale",
- "modding tutorial",
- "modding guides",
- "hytale modding guides",
- "hytale modding tutorial",
- "how to start modding Hytale",
- "how to make a mod",
- ];
+ const parentMetadata = await parent;
+ const parentKeywords = parentMetadata.keywords || [];
- if (ogLanguageBlacklist.includes(params.lang))
- return {
- title: page.data.title,
- description: page.data.description,
- keywords: [...globalKeywords, ...pageKeywords],
- };
+ const baseMetadata = {
+ title: page.data.title,
+ description: page.data.description,
+ keywords: [...parentKeywords, ...pageKeywords],
+ };
+
+ if (ogLanguageBlacklist.includes(params.lang)) return baseMetadata;
else
return {
- title: page.data.title,
- description: page.data.description,
- keywords: [...globalKeywords, ...pageKeywords],
+ ...baseMetadata,
openGraph: {
images: [
{
diff --git a/src/app/[lang]/layout.tsx b/src/app/[lang]/layout.tsx
index 7d98c484..e7ec3fbc 100644
--- a/src/app/[lang]/layout.tsx
+++ b/src/app/[lang]/layout.tsx
@@ -4,8 +4,10 @@ import { i18n } from "@/lib/i18n";
import Script from "next/script";
import englishTranslations from "@/../messages/en.json";
import { Geist, Geist_Mono } from "next/font/google";
-import type { Metadata } from "next";
+import type { Metadata, ResolvingMetadata } from "next";
import { cn } from "@/lib/utils";
+import fallbackMessages from "@/../messages/en.json";
+import { headers } from "next/headers";
const geist = Geist({
subsets: ["latin"],
@@ -16,25 +18,6 @@ const geistMono = Geist_Mono({
variable: "--font-mono",
});
-export const metadata: Metadata = {
- metadataBase: new URL(
- process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000",
- ),
- keywords: [
- "hytale modding",
- "hytale",
- "hytale plugins",
- "hytale mods",
- "how to mod hytale",
- "modding tutorial",
- "modding guides",
- "hytale modding guides",
- "hytale modding tutorial",
- "how to start modding Hytale",
- "how to make a mod",
- ],
-};
-
const translations = Object.fromEntries(
i18n.languages.map((lang) => {
const messages = require(`@/../messages/${lang}.json`);
@@ -71,3 +54,41 @@ export default async function RootLayout({