plugins/AstralJobs/messages.yml holds every player-facing string the plugin sends. It's loaded into the JobsMessages enum on enable (loadEnum("messages.yml", JobsMessages.class)), so keys are kebab-case (LEVEL_UP → level-up) and values are MiniMessage strings that resolve the standard AstralCore variable tags (<success>, <warning>, <error>, plus server-defined ones like <secondary_color>/<text_color>) and any placeholder reachable from the container the message is sent with.
Default file
level-up: "<success>Vous êtes passé au niveau <#26d971>%level% <#80ed99>dans le métier de <#26d971>%job% <#80ed99>! Récupérez votre récompense dans le <#26d971>/metier<#80ed99>."
experience-gained: "<font:hud:icons>f</font> <font:hud:shift_1><gray><#26d971>%notification_money% <gray>| <white><font:hud:icons>g</font> <gray><#26d971>%notification_experience% <gray>| <#26d971>%notification_current%<gray>/<#26d971>%notification_total%"
# Boosters
same-type-booster-active: "<warning>Vous avez déjà un élixir d'activé du même type. Veuillez attendre qu'il expire avant d'en activer un autre."
# -- Personal
personal-booster-activated: "<success>Votre <secondary_color>%booster_formattedType% <#80ed99>a été activé pour <secondary_color>$format-date(%booster_remainingTime%, format-duration) <#80ed99>!"
personal-booster-expired: "<warning>Votre <secondary_color>%booster_formattedType% <#ffd60a>a expiré."
# -- Global
global-booster-activated: "<success>Votre booster <secondary_color>%booster_formattedType% <#80ed99>a été activé pour <secondary_color>$format-date(%booster_remainingTime%, format-duration) <#80ed99>!"
global-booster-activated-notification: "<success>Un booster <secondary_color>%booster_formattedType% <#80ed99>a été activé pour <secondary_color>$format-date(%booster_remainingTime%, format-duration) <#80ed99>!"
global-booster-broadcast: "<success><secondary_color>%booster_owner_name% <#80ed99>a activé un booster <secondary_color>%booster_formattedType% <#80ed99>pour tout le serveur pendant <secondary_color>$format-date(%booster_remainingTime%, format-duration) <#80ed99>!"
global-booster-queued: "<success>Votre booster <secondary_color>%booster_formattedType% <#80ed99>a été mis en attente il sera activé dans <secondary_color>$format-date(%booster_timeUntilActive%, format-duration) <#80ed99>!"
global-booster-expired: "<warning>Votre booster <secondary_color>%booster_formattedType% <#ffd60a>a expiré."
# Boosters types
type-experience: "Élixir d'experience"
type-money: "Élixir de richesse"
type-global: "Élixir global"
# Rewards
reward-cannot-be-claimed: "<warning>Vous ne pouvez récupérer cette récompense. Vous devez passer niveau <#26d971>%level% <#ffd60a>pour la récupérer."
no-rewards-available: "<warning>Il n'y a pas de récompense disponible."
# Jobs info
job-info-header: "<gold>--- <primary_color>Métiers - Niveau totaux %levels% <gold>---"
job-info-entry: "<secondary_color>%job% <text_color>Niveau <secondary_color>%level% <text_color>| Experience: <secondary_color>%current%<text_color>/<secondary_color>%required%"
job-info-footer: ""
# Miscellaneous
unexpected-error: "<error>Une erreur est apparue. Veuillez réessayer plus tard."
Progression
Key
Placeholders
Sent to
When
level-up
%level%, %job%
Player
Once per level gained, inside ActionService's level-up loop — a single action that crosses several thresholds fires this once per threshold crossed. %level% is the new level; %job% is the job's configured display-name.
Rendered as a raw action-bar packet (WrapperPlayServerActionBar via PacketEvents, not sendActionBar) by NotificationService#sendNotification. A background is spliced in via AstralHudHook#addBackground when AstralHud is present (see Overview → Integrations).
experience-gained isn't sent per XP gain — ActionService accumulates every surviving experience/money gain for a player+job into a NotificationEntry (NotificationService#update), and a repeating task (NotificationRenderTask, every 5 ticks) re-renders the action bar for any entry that hasn't rendered in the last 350 ms (NOTIFICATION_COOLDOWN_MS) and has updated within the last 5 s (STALE_TIMEOUT_MS). Past that 5 s idle window the entry is flushed and dropped instead (NotificationCleanUpTask) — see Jobs → Level-up flow for the full accumulate/flush cycle.
NotificationEntry exposes its accumulated values two-decimal-formatted, registered under its own notification namespace rather than via registerDirect:
Placeholder
Resolves to
%notification_money%
Money accumulated since the last render.
%notification_experience%
XP accumulated since the last render.
%notification_current%
The player's current XP in that job.
%notification_total%
XP required for the next level — the progress-bar denominator, not a job total.
%notification_level%
The number of levels gained since the entry was created — NotificationEntry#incrementLevel is called once per level-up and never reset while the entry lives, so this is a level-up count, not the player's job level.
%notification_job%
The job's display-name.
Boosters
Key
Placeholders
Sent to
When
same-type-booster-active
—
Player
BoosterService#use finds the player already has an active/queued booster of the same type and scope as the item being used (BoosterRepository#hasActiveBooster). The item is refunded to the player's mailbox. The blueprint's %booster_*% chain is registered on the container even though the default text doesn't use it.
personal-booster-activated
%booster_formattedType%, %booster_remainingTime%
Player
An INDIVIDUAL-scope booster is created and saved successfully in BoosterService#use.
personal-booster-expired
%booster_formattedType%
Owner
The watchdog's BoosterService#expire deletes an INDIVIDUAL booster whose owner is currently online.
global-booster-activated
%booster_formattedType%, %booster_remainingTime%
Player (purchaser), then the owner
Sent by BoosterService#use to the purchaser when the new GLOBAL booster is immediately active. Otherwise sent later by BoosterService#activate, to the owner if they're online, when the queued booster crosses activatesAt on a watchdog tick.
global-booster-activated-notification
%booster_formattedType%, %booster_remainingTime%
Every other online player
BoosterService#activate fires this to everyone except the owner — only for a booster that goes live on a watchdog tick, i.e. a queued global reaching activatesAt (a global that is already running when the server starts is pre-marked by BoosterService's constructor, so a restart never re-announces it). use() pre-marks immediately-active boosters as activated, so in that case the rest of the server sees only global-booster-broadcast.
BoosterService#broadcastGlobalActivation, via AstralCore's ChatService#broadcastMessage. GLOBAL-scope only, and fired exactly once per booster — both use() and the watchdog's activate() gate it behind the same atomic first-activation set. Silently skipped when no ChatService is registered.
BoosterService#use when the new GLOBAL booster is not immediately active — i.e. it was chained behind an already-running global of the same type.
global-booster-expired
%booster_formattedType%
Owner
The watchdog's BoosterService#expire deletes a GLOBAL booster whose owner is currently online.
Activating a second GLOBAL booster of the same type while one is already running doesn't reject it — BoosterRepository#findLastGloballyQueued chains the new one behind the currently-running one (activatesAt = the running booster's expiresAt), and BoosterWatchdogTask activates it once that time arrives. The purchaser gets global-booster-queued at purchase time and global-booster-activated when it actually goes live.
See Placeholders → booster_* (active) for the full BoosterEntity placeholder catalogue, including how %booster_remainingTime%/%booster_timeUntilActive% are rendered through $format-date(…, format-duration).
Booster type labels
%booster_formattedType% on an active booster resolves to one of these three keys via BoosterType#message (BoosterType → JobsMessages):
BoosterType
Key
Default text
EXPERIENCE
type-experience
Élixir d'experience
MONEY
type-money
Élixir de richesse
BOTH
type-global
Élixir global
Rewards
Key
Placeholders
Sent to
When
reward-cannot-be-claimed
%level%
Player
The claim-job-reward action runs for a level the player hasn't reached yet. %level% is the reward's required level, not the player's current one.
no-rewards-available
—
Player
/jobs claim finds no unclaimed reward at or below the player's current level, across any job.
Jobs info
Sent in sequence by /jobs info: one job-info-header, then one job-info-entry per job the player has a JobLevelEntity for, then one job-info-footer.
Key
Placeholders
When
job-info-header
%levels%
First line. %levels% is the sum of the player's level across every job.
job-info-entry
%job%, %level%, %current%, %required%
One per job. %current% and %required% are the player's experience and the XP needed for the next level (job.computeLevelProgress(level + 1)), both formatted to two decimals — the same threshold used by the level-up loop, so this always agrees with when the next level-up message will fire.
job-info-footer
—
Last line. Shipped as an empty string, so by default it renders nothing.
Miscellaneous
Key
Placeholders
Sent to
When
unexpected-error
—
Player
Generic catch-all: menu-open failure on /jobs, missing JobsPlayerData on /jobs claim or the claim-job-reward action, no JobLevelEntity at all on /jobs info, or a BoosterRepository check/save failure in BoosterService#use. The triggering exception is always logged server-side.