Developer Overview¶
Architecture¶
uSkyBlock is a standard Bukkit plugin. The main components:
- Island management — creates, stores, and regenerates island regions using WorldEdit schematics and WorldGuard region protection.
- Party/team system — handles island membership and per-member permission flags.
- Challenge system — evaluates requirements and tracks player completion.
- Async job queue — heavy operations (island generation, deletion, chunk regen) run off the main thread.
- Adventure / MiniMessage — all user-facing text goes through the Adventure library. Strings are formatted with MiniMessage tags.
- i18n — messages are externalized in gettext-style
.pofiles in the repository. See Translation system below. - Economy — optional Vault hook; the plugin degrades gracefully if Vault is absent.
Building¶
Requires Java 21; the Gradle wrapper is included. A normal build needs no other tools — the runtime translation bundle is generated natively. Only the translation task (rebuilding message templates) additionally needs the gettext tools; see Translation system.
Output: uSkyBlock-Plugin/build/libs/uSkyBlock.jar
API integration¶
The API is published to the uSkyBlock Maven repository (not Maven Central). Add the
repository and the uSkyBlock-APIv2 artifact as a provided/compileOnly dependency.
Browse the classes and methods in the API Reference.
Replace VERSION with the latest release listed in the
repository.
<!-- Maven -->
<repositories>
<repository>
<id>uskyblock</id>
<url>https://uskyblock.ovh/maven/uskyblock/</url>
</repository>
</repositories>
<dependency>
<groupId>ovh.uskyblock</groupId>
<artifactId>uSkyBlock-APIv2</artifactId>
<version>VERSION</version>
<scope>provided</scope>
</dependency>
// Gradle (Kotlin DSL)
repositories {
maven("https://uskyblock.ovh/maven/uskyblock/")
}
dependencies {
compileOnly("ovh.uskyblock:uSkyBlock-APIv2:VERSION")
}
Declare a soft dependency so uSkyBlock loads first:
Get the API instance after the plugin enables:
The legacy uSkyBlockAPI interface (artifact uSkyBlock-Core) is still available and provides island/player data methods like getIslandLevel(player), getIslandInfo(player), and getTopTen(). It is stable but APIv2 is the direction going forward.
Events¶
The API fires Bukkit events you can listen to:
uSkyBlockEvent— base event for all uSkyBlock actionsuSkyBlockScoreChangedEvent— island score recalculated (carries the newIslandScore)CreateIslandEvent,RestartIslandEvent— island lifecycleMemberJoinedEvent,IslandChatEvent— social actions
Register a listener the standard Bukkit way:
@EventHandler
public void onScoreChanged(uSkyBlockScoreChangedEvent event) {
IslandScore score = event.getScore();
// ...
}
Translation system¶
uSkyBlock uses a custom gettext-based i18n pipeline. All user-facing strings are translatable and formatted with MiniMessage. Translations are committed to the repository; fixes arrive as pull requests against the .po files (see Translations).
Writing translatable strings¶
Use these methods from I18nUtil and Msg:
| Method | Returns | Use for |
|---|---|---|
tr("message") |
Component |
Translate a string and get a Component back |
sendTr(sender, "message") |
void | Translate and send to a player/sender |
sendErrorTr(sender, "message") |
void | Same, but styled as an error (red) |
marktr("message") |
String |
Mark a string for extraction without translating it at call time (used for command descriptions) |
Strings use MiniMessage syntax. Placeholders are passed as TagResolver arguments:
// Simple message
sendTr(player, "Island created successfully.");
// With placeholders — use Placeholder.number() or Placeholder.unparsed()
sendTr(player, "Your rank is: <rank>.", number("rank", rank.getRank(), PRIMARY));
// Error style
sendErrorTr(player, "You must wait <seconds> seconds.", number("seconds", cooldown));
// Mark for extraction only (translated later, e.g. in help text)
super("create|c", "usb.island.create", "?schematic", marktr("create an island"));
The Placeholder utility class (us.talabrek.ultimateskyblock.message.Placeholder) provides helpers for building tag resolvers: number(), unparsed(), component(), and legacy().
Add // I18N: comments above tricky strings to give translators context — these are extracted into the POT files automatically.
How extraction works¶
The translation task uses xgettext to scan all Java source files for calls to tr, marktr, sendTr, sendErrorTr, and trLegacy. The extracted strings are split into three domains:
| Domain | POT file | Purpose |
|---|---|---|
player_facing |
keys.player_facing.pot |
Messages players see |
admin_ops |
keys.admin_ops.pot |
Messages for server operators |
system_debug |
keys.system_debug.pot |
Debug/internal logging |
Domain assignment is based on the source file's package. Classes in command/admin/ and specific debug classes go to admin_ops or system_debug; everything else is player_facing. See the classification rules in uSkyBlock-Core/build.gradle.kts.
POT templates and translated PO files live under uSkyBlock-Core/src/main/i18n/.
Rebuilding translations¶
Any time you add, remove, or change a translatable string, rebuild the templates:
This runs extractTranslation (extract strings → split into the three domain POT files), then merges the domains per locale and generates the extra locales (Pirate, Kitteh) under build/. Commit the updated POT files. CI runs extractTranslation on every pull request and fails if the committed POT files are out of date.
Only the extraction step needs gettext (xgettext, msgcat); merging, the Pirate/Kitteh locales, and bundling into i18n.zip run natively, so a normal ./gradlew build needs no external tools.
If you only change code without touching translatable strings, this step is not needed.
Translating new strings¶
You do not need to translate new strings yourself. Until a language has a translation, players see the English text. The maintainers translate new and changed strings and commit them to the domain .po files.
Runtime locale resolution¶
I18nUtil.initialize(folder, locale) loads translations at startup from three sources in priority order:
i18n.zipbundled in the JAR (packaged translations)po/resources in the JAR (fallback)plugins/uSkyBlock/i18n/on disk (admin overrides)
The server-wide locale is set via language in config.yml. Admins can switch it at runtime with /usb lang.
Previewing docs locally¶
python3 -m venv .venv-docs
source .venv-docs/bin/activate
pip install -r docs/requirements-docs.txt
mkdocs serve -f docs/mkdocs.yml
Docs are served at http://127.0.0.1:8000.